-- Test suite for vertical flex direction functionality -- Tests that flex layout works correctly with vertical direction package.path = package.path .. ";?.lua" local luaunit = require("testing.luaunit") require("testing.loveStub") -- Required to mock LOVE functions local FlexLove = require("FlexLove") local Gui, enums = FlexLove.GUI, FlexLove.enums local Positioning = enums.Positioning local FlexDirection = enums.FlexDirection local JustifyContent = enums.JustifyContent local AlignItems = enums.AlignItems -- Test class TestVerticalFlexDirection = {} function TestVerticalFlexDirection:setUp() -- Clean up before each test Gui.destroy() end function TestVerticalFlexDirection:tearDown() -- Clean up after each test Gui.destroy() end -- Test 1: Basic element creation with vertical flex direction function TestVerticalFlexDirection:testCreateElementWithVerticalFlexDirection() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 100, height = 300, }) -- Verify element was created with correct properties luaunit.assertEquals(parent.positioning, Positioning.FLEX) luaunit.assertEquals(parent.flexDirection, FlexDirection.VERTICAL) luaunit.assertEquals(parent.width, 100) luaunit.assertEquals(parent.height, 300) end -- Test 2: Single child vertical layout function TestVerticalFlexDirection:testSingleChildVerticalLayout() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 100, height = 300, }) local child = Gui.new({ id = "single_child", width = 80, height = 50, }) parent:addChild(child) -- Child should be positioned at top of parent (flex-start default) luaunit.assertEquals(child.x, parent.x) luaunit.assertEquals(child.y, parent.y) end -- Test 3: Multiple children vertical layout function TestVerticalFlexDirection:testMultipleChildrenVerticalLayout() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 100, height = 300, gap = 10, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) local child3 = Gui.new({ id = "child3", width = 60, height = 30, }) parent:addChild(child1) parent:addChild(child2) parent:addChild(child3) -- Children should be positioned vertically with gaps luaunit.assertEquals(child1.x, parent.x) luaunit.assertEquals(child1.y, parent.y) luaunit.assertEquals(child2.x, parent.x) luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) luaunit.assertEquals(child3.x, parent.x) luaunit.assertEquals(child3.y, child2.y + child2.height + parent.gap) end -- Test 4: Empty parent (no children) vertical layout function TestVerticalFlexDirection:testEmptyParentVerticalLayout() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 100, height = 300, }) -- Should not cause any errors and should have no children luaunit.assertEquals(#parent.children, 0) end -- Test 5: Vertical layout with flex-start justification (default) function TestVerticalFlexDirection:testVerticalLayoutFlexStart() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, x = 0, y = 0, width = 100, height = 300, gap = 10, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Children should be positioned at top (flex-start) luaunit.assertEquals(child1.y, parent.y) luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) end -- Test 6: Vertical layout with center justification function TestVerticalFlexDirection:testVerticalLayoutCenter() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, x = 0, y = 0, width = 100, height = 300, gap = 10, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Calculate expected center positioning local totalChildHeight = child1.height + child2.height + parent.gap local availableSpace = parent.height - totalChildHeight local startY = availableSpace / 2 luaunit.assertEquals(child1.y, parent.y + startY) luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) end -- Test 7: Vertical layout with flex-end justification function TestVerticalFlexDirection:testVerticalLayoutFlexEnd() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_END, x = 0, y = 0, width = 100, height = 300, gap = 10, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Calculate expected end positioning local totalChildHeight = child1.height + child2.height + parent.gap local availableSpace = parent.height - totalChildHeight local startY = availableSpace luaunit.assertEquals(child1.y, parent.y + startY) luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) end -- Test 8: Single child with center justification function TestVerticalFlexDirection:testSingleChildVerticalLayoutCentered() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, x = 20, y = 10, width = 100, height = 300, }) local child = Gui.new({ id = "single_child", width = 80, height = 50, }) parent:addChild(child) -- Single child with center justification should be centered local expectedY = parent.y + (parent.height - child.height) / 2 luaunit.assertEquals(child.y, expectedY) luaunit.assertEquals(child.x, parent.x) end -- Test 9: Vertical layout maintains child widths function TestVerticalFlexDirection:testVerticalLayoutMaintainsChildWidths() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, alignItems = AlignItems.FLEX_START, -- Explicitly set to maintain child widths x = 0, y = 0, width = 100, height = 300, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 60, height = 40, -- Different width }) parent:addChild(child1) parent:addChild(child2) -- In vertical layout, child widths should be preserved luaunit.assertEquals(child1.width, 80) luaunit.assertEquals(child2.width, 60) end -- Test 10: Vertical layout with align-items center function TestVerticalFlexDirection:testVerticalLayoutAlignItemsCenter() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, alignItems = AlignItems.CENTER, x = 0, y = 0, width = 100, height = 300, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 60, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Children should be centered horizontally local expectedX1 = parent.x + (parent.width - child1.width) / 2 local expectedX2 = parent.x + (parent.width - child2.width) / 2 luaunit.assertEquals(child1.x, expectedX1) luaunit.assertEquals(child2.x, expectedX2) end -- Test 11: Vertical layout with align-items flex-end function TestVerticalFlexDirection:testVerticalLayoutAlignItemsFlexEnd() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, alignItems = AlignItems.FLEX_END, x = 0, y = 0, width = 100, height = 300, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 60, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Children should be aligned to the right local expectedX1 = parent.x + parent.width - child1.width local expectedX2 = parent.x + parent.width - child2.width luaunit.assertEquals(child1.x, expectedX1) luaunit.assertEquals(child2.x, expectedX2) end -- Test 12: Vertical layout with align-items stretch function TestVerticalFlexDirection:testVerticalLayoutAlignItemsStretch() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, alignItems = AlignItems.STRETCH, x = 0, y = 0, width = 100, height = 300, }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 60, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Children with explicit widths should keep them (CSS flexbox behavior) luaunit.assertEquals(child1.width, 80) luaunit.assertEquals(child2.width, 60) end -- Test 13: Vertical layout with space-between function TestVerticalFlexDirection:testVerticalLayoutSpaceBetween() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, x = 0, y = 0, width = 100, height = 300, gap = 0, -- Space-between controls spacing, not gap }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) local child3 = Gui.new({ id = "child3", width = 60, height = 30, }) parent:addChild(child1) parent:addChild(child2) parent:addChild(child3) -- First child should be at start luaunit.assertEquals(child1.y, parent.y) -- Last child should be at end luaunit.assertEquals(child3.y, parent.y + parent.height - child3.height) -- Middle child should be evenly spaced local remainingSpace = parent.height - child1.height - child2.height - child3.height local spaceBetween = remainingSpace / 2 luaunit.assertEquals(child2.y, child1.y + child1.height + spaceBetween) end -- Test 14: Vertical layout with custom gap function TestVerticalFlexDirection:testVerticalLayoutCustomGap() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 100, height = 300, gap = 20, -- Custom gap }) local child1 = Gui.new({ id = "child1", width = 80, height = 50, }) local child2 = Gui.new({ id = "child2", width = 70, height = 40, }) parent:addChild(child1) parent:addChild(child2) -- Children should be positioned with custom gap luaunit.assertEquals(child1.y, parent.y) luaunit.assertEquals(child2.y, child1.y + child1.height + 20) end -- Test 15: Vertical layout with positioning offset function TestVerticalFlexDirection:testVerticalLayoutWithPositioningOffset() local parent = Gui.new({ id = "vertical_parent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, x = 50, y = 100, width = 100, height = 300, }) local child = Gui.new({ id = "single_child", width = 80, height = 50, }) parent:addChild(child) -- Child should respect parent's position offset local expectedY = parent.y + (parent.height - child.height) / 2 luaunit.assertEquals(child.x, parent.x) luaunit.assertEquals(child.y, expectedY) end -- Test 16: Complex vertical sidebar layout with nested sections function TestVerticalFlexDirection:testComplexVerticalSidebarLayout() local sidebar = Gui.new({ id = "sidebar", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 300, height = 800, gap = 20, }) -- Header section local header = Gui.new({ id = "header", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 280, height = 120, gap = 10, }) local logo = Gui.new({ id = "logo", width = 100, height = 40 }) local userInfo = Gui.new({ id = "userInfo", width = 250, height = 60 }) header:addChild(logo) header:addChild(userInfo) -- Navigation section with nested menus local navigation = Gui.new({ id = "navigation", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 280, height = 400, gap = 5, }) local mainMenu = Gui.new({ id = "mainMenu", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 280, height = 200, gap = 2, }) -- Create menu items for i = 1, 5 do local menuItem = Gui.new({ id = "menuItem" .. i, width = 270, height = 35, }) mainMenu:addChild(menuItem) end local subMenu = Gui.new({ id = "subMenu", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 260, height = 180, gap = 3, }) -- Create submenu items with indentation for i = 1, 4 do local subMenuItem = Gui.new({ id = "subMenuItem" .. i, width = 240, height = 30, }) subMenu:addChild(subMenuItem) end navigation:addChild(mainMenu) navigation:addChild(subMenu) -- Footer section local footer = Gui.new({ id = "footer", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_END, width = 280, height = 200, gap = 10, }) local settings = Gui.new({ id = "settings", width = 200, height = 50 }) local help = Gui.new({ id = "help", width = 180, height = 40 }) local logout = Gui.new({ id = "logout", width = 120, height = 35 }) footer:addChild(settings) footer:addChild(help) footer:addChild(logout) sidebar:addChild(header) sidebar:addChild(navigation) sidebar:addChild(footer) -- Verify complex nested positioning luaunit.assertEquals(header.y, sidebar.y) luaunit.assertEquals(navigation.y, header.y + header.height + sidebar.gap) luaunit.assertEquals(footer.y, navigation.y + navigation.height + sidebar.gap) -- Verify nested menu structure luaunit.assertEquals(logo.y, header.y) luaunit.assertEquals(userInfo.y, logo.y + logo.height + header.gap) -- Verify submenu positioning luaunit.assertEquals(subMenu.y, mainMenu.y + mainMenu.height + navigation.gap) end -- Test 17: Multi-level accordion/collapsible vertical layout function TestVerticalFlexDirection:testMultiLevelAccordionLayout() local container = Gui.new({ id = "accordionContainer", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 400, height = 600, gap = 5, }) -- Create multiple accordion sections for sectionIndex = 1, 3 do local section = Gui.new({ id = "section" .. sectionIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 380, height = 180, gap = 2, }) local sectionHeader = Gui.new({ id = "sectionHeader" .. sectionIndex, width = 380, height = 40, }) local sectionContent = Gui.new({ id = "sectionContent" .. sectionIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 360, height = 130, gap = 3, }) -- Add subsections within each section for subIndex = 1, 3 do local subsection = Gui.new({ id = "subsection" .. sectionIndex .. "_" .. subIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 340, height = 40, gap = 1, }) local subHeader = Gui.new({ id = "subHeader" .. sectionIndex .. "_" .. subIndex, width = 340, height = 20, }) local subContent = Gui.new({ id = "subContent" .. sectionIndex .. "_" .. subIndex, width = 320, height = 18, }) subsection:addChild(subHeader) subsection:addChild(subContent) sectionContent:addChild(subsection) end section:addChild(sectionHeader) section:addChild(sectionContent) container:addChild(section) end -- Verify accordion structure local firstSection = container.children[1] local secondSection = container.children[2] local thirdSection = container.children[3] luaunit.assertEquals(firstSection.y, container.y) luaunit.assertEquals(secondSection.y, firstSection.y + firstSection.height + container.gap) luaunit.assertEquals(thirdSection.y, secondSection.y + secondSection.height + container.gap) -- Verify nested subsection positioning local firstSectionContent = firstSection.children[2] local firstSubsection = firstSectionContent.children[1] local secondSubsection = firstSectionContent.children[2] luaunit.assertEquals(firstSubsection.y, firstSectionContent.y) luaunit.assertEquals(secondSubsection.y, firstSubsection.y + firstSubsection.height + firstSectionContent.gap) end -- Test 18: Vertical chat/message thread layout function TestVerticalFlexDirection:testVerticalChatMessageLayout() local chatContainer = Gui.new({ id = "chatContainer", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_END, x = 0, y = 0, width = 350, height = 500, gap = 8, }) -- Create message threads with varying complexity local messageTypes = { { sender = "user", hasAvatar = true, hasReactions = false }, { sender = "bot", hasAvatar = true, hasReactions = true }, { sender = "user", hasAvatar = false, hasReactions = true }, { sender = "system", hasAvatar = false, hasReactions = false }, } for i, msgType in ipairs(messageTypes) do local messageGroup = Gui.new({ id = "messageGroup" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 330, height = msgType.hasReactions and 80 or 60, gap = 4, }) local messageRow = Gui.new({ id = "messageRow" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 330, height = 40, gap = 8, }) if msgType.hasAvatar then local avatar = Gui.new({ id = "avatar" .. i, width = 32, height = 32, }) messageRow:addChild(avatar) end local messageContent = Gui.new({ id = "messageContent" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = msgType.hasAvatar and 290 or 330, height = 35, gap = 2, }) local messageText = Gui.new({ id = "messageText" .. i, width = msgType.hasAvatar and 280 or 320, height = 20, }) local timestamp = Gui.new({ id = "timestamp" .. i, width = 60, height = 12, }) messageContent:addChild(messageText) messageContent:addChild(timestamp) messageRow:addChild(messageContent) messageGroup:addChild(messageRow) if msgType.hasReactions then local reactions = Gui.new({ id = "reactions" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 150, height = 20, gap = 3, }) -- Add reaction buttons for j = 1, 3 do local reaction = Gui.new({ id = "reaction" .. i .. "_" .. j, width = 25, height = 18, }) reactions:addChild(reaction) end messageGroup:addChild(reactions) end chatContainer:addChild(messageGroup) end -- Verify messages are positioned from bottom (flex-end) local lastMessage = chatContainer.children[#chatContainer.children] local expectedLastY = chatContainer.y + chatContainer.height - lastMessage.height -- Note: This test may fail if flex-end positioning isn't implemented correctly -- but demonstrates the expected CSS behavior luaunit.assertEquals(lastMessage.y + lastMessage.height, chatContainer.y + chatContainer.height) end -- Test 19: Nested form layout with sections and field groups function TestVerticalFlexDirection:testNestedFormLayout() local form = Gui.new({ id = "form", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 500, height = 700, gap = 20, }) -- Form header local formHeader = Gui.new({ id = "formHeader", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 480, height = 80, gap = 10, }) local title = Gui.new({ id = "title", width = 300, height = 30 }) local description = Gui.new({ id = "description", width = 450, height = 40 }) formHeader:addChild(title) formHeader:addChild(description) -- Personal information section local personalSection = Gui.new({ id = "personalSection", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 480, height = 200, gap = 15, }) local personalTitle = Gui.new({ id = "personalTitle", width = 200, height = 25 }) personalSection:addChild(personalTitle) -- Field groups within personal section local nameGroup = Gui.new({ id = "nameGroup", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 460, height = 80, gap = 8, }) local nameLabel = Gui.new({ id = "nameLabel", width = 100, height = 20 }) local nameInput = Gui.new({ id = "nameInput", width = 400, height = 35 }) local nameError = Gui.new({ id = "nameError", width = 350, height = 15 }) nameGroup:addChild(nameLabel) nameGroup:addChild(nameInput) nameGroup:addChild(nameError) local emailGroup = Gui.new({ id = "emailGroup", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 460, height = 80, gap = 8, }) local emailLabel = Gui.new({ id = "emailLabel", width = 100, height = 20 }) local emailInput = Gui.new({ id = "emailInput", width = 400, height = 35 }) local emailError = Gui.new({ id = "emailError", width = 350, height = 15 }) emailGroup:addChild(emailLabel) emailGroup:addChild(emailInput) emailGroup:addChild(emailError) personalSection:addChild(nameGroup) personalSection:addChild(emailGroup) -- Address section with complex nested structure local addressSection = Gui.new({ id = "addressSection", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 480, height = 300, gap = 15, }) local addressTitle = Gui.new({ id = "addressTitle", width = 200, height = 25 }) addressSection:addChild(addressTitle) -- Street address group local streetGroup = Gui.new({ id = "streetGroup", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 460, height = 80, gap = 8, }) local streetLabel = Gui.new({ id = "streetLabel", width = 120, height = 20 }) local streetInput = Gui.new({ id = "streetInput", width = 400, height = 35 }) local streetError = Gui.new({ id = "streetError", width = 350, height = 15 }) streetGroup:addChild(streetLabel) streetGroup:addChild(streetInput) streetGroup:addChild(streetError) -- City/State/Zip compound group local locationGroup = Gui.new({ id = "locationGroup", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 460, height = 120, gap = 8, }) local locationLabel = Gui.new({ id = "locationLabel", width = 150, height = 20 }) local locationInputs = Gui.new({ id = "locationInputs", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 450, height = 35, gap = 10, }) local cityInput = Gui.new({ id = "cityInput", width = 200, height = 35 }) local stateInput = Gui.new({ id = "stateInput", width = 100, height = 35 }) local zipInput = Gui.new({ id = "zipInput", width = 120, height = 35 }) locationInputs:addChild(cityInput) locationInputs:addChild(stateInput) locationInputs:addChild(zipInput) local locationErrors = Gui.new({ id = "locationErrors", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 450, height = 45, gap = 3, }) local cityError = Gui.new({ id = "cityError", width = 200, height = 12 }) local stateError = Gui.new({ id = "stateError", width = 150, height = 12 }) local zipError = Gui.new({ id = "zipError", width = 180, height = 12 }) locationErrors:addChild(cityError) locationErrors:addChild(stateError) locationErrors:addChild(zipError) locationGroup:addChild(locationLabel) locationGroup:addChild(locationInputs) locationGroup:addChild(locationErrors) addressSection:addChild(streetGroup) addressSection:addChild(locationGroup) -- Form actions local formActions = Gui.new({ id = "formActions", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_END, width = 480, height = 50, gap = 15, }) local cancelButton = Gui.new({ id = "cancelButton", width = 80, height = 40 }) local submitButton = Gui.new({ id = "submitButton", width = 100, height = 40 }) formActions:addChild(cancelButton) formActions:addChild(submitButton) form:addChild(formHeader) form:addChild(personalSection) form:addChild(addressSection) form:addChild(formActions) -- Verify complex form structure luaunit.assertEquals(formHeader.y, form.y) luaunit.assertEquals(personalSection.y, formHeader.y + formHeader.height + form.gap) luaunit.assertEquals(addressSection.y, personalSection.y + personalSection.height + form.gap) luaunit.assertEquals(formActions.y, addressSection.y + addressSection.height + form.gap) -- Verify nested field group positioning luaunit.assertEquals(nameGroup.y, personalTitle.y + personalTitle.height + personalSection.gap) luaunit.assertEquals(emailGroup.y, nameGroup.y + nameGroup.height + personalSection.gap) -- Verify triple-nested error positioning luaunit.assertEquals(cityError.y, locationErrors.y) luaunit.assertEquals(stateError.y, cityError.y + cityError.height + locationErrors.gap) luaunit.assertEquals(zipError.y, stateError.y + stateError.height + locationErrors.gap) end -- Test 20: Calendar/timeline vertical layout with nested events function TestVerticalFlexDirection:testCalendarTimelineLayout() local timeline = Gui.new({ id = "timeline", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 600, height = 800, gap = 10, }) -- Create days with events local daysOfWeek = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" } for dayIndex, dayName in ipairs(daysOfWeek) do local dayContainer = Gui.new({ id = "day" .. dayIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 580, height = 140, gap = 8, }) local dayHeader = Gui.new({ id = "dayHeader" .. dayIndex, width = 580, height = 30, }) local eventsContainer = Gui.new({ id = "eventsContainer" .. dayIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 560, height = 100, gap = 5, }) -- Add events for each day (varying number) local eventCount = math.min(dayIndex + 1, 4) -- 2-4 events per day for eventIndex = 1, eventCount do local eventItem = Gui.new({ id = "event" .. dayIndex .. "_" .. eventIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 540, height = 20, gap = 2, }) local eventTime = Gui.new({ id = "eventTime" .. dayIndex .. "_" .. eventIndex, width = 80, height = 12, }) local eventTitle = Gui.new({ id = "eventTitle" .. dayIndex .. "_" .. eventIndex, width = 400, height = 15, }) -- Some events have additional details if eventIndex % 2 == 0 then local eventDetails = Gui.new({ id = "eventDetails" .. dayIndex .. "_" .. eventIndex, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 380, height = 25, gap = 2, }) local eventLocation = Gui.new({ id = "eventLocation" .. dayIndex .. "_" .. eventIndex, width = 200, height = 10, }) local eventAttendees = Gui.new({ id = "eventAttendees" .. dayIndex .. "_" .. eventIndex, width = 300, height = 10, }) eventDetails:addChild(eventLocation) eventDetails:addChild(eventAttendees) eventItem:addChild(eventTime) eventItem:addChild(eventTitle) eventItem:addChild(eventDetails) eventItem.height = 40 -- Adjust height for detailed events eventItem.units.height = { value = 40, unit = "px" } -- Keep units in sync else eventItem:addChild(eventTime) eventItem:addChild(eventTitle) end eventsContainer:addChild(eventItem) end dayContainer:addChild(dayHeader) dayContainer:addChild(eventsContainer) timeline:addChild(dayContainer) end -- Verify timeline structure local firstDay = timeline.children[1] local secondDay = timeline.children[2] local thirdDay = timeline.children[3] luaunit.assertEquals(firstDay.y, timeline.y) luaunit.assertEquals(secondDay.y, firstDay.y + firstDay.height + timeline.gap) luaunit.assertEquals(thirdDay.y, secondDay.y + secondDay.height + timeline.gap) -- Verify nested event positioning within first day local firstDayEvents = firstDay.children[2] -- eventsContainer local firstEvent = firstDayEvents.children[1] local secondEvent = firstDayEvents.children[2] luaunit.assertEquals(firstEvent.y, firstDayEvents.y) luaunit.assertEquals(secondEvent.y, firstEvent.y + firstEvent.height + firstDayEvents.gap) end -- Test 21: Complex dashboard widget layout function TestVerticalFlexDirection:testComplexDashboardWidgetLayout() local dashboard = Gui.new({ id = "dashboard", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 800, height = 1000, gap = 25, }) -- Dashboard header with breadcrumbs local dashboardHeader = Gui.new({ id = "dashboardHeader", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 780, height = 100, gap = 12, }) local breadcrumbs = Gui.new({ id = "breadcrumbs", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 400, height = 20, gap = 8, }) for i = 1, 4 do local crumb = Gui.new({ id = "crumb" .. i, width = 80, height = 18, }) breadcrumbs:addChild(crumb) end local pageTitle = Gui.new({ id = "pageTitle", width = 300, height = 40 }) local pageActions = Gui.new({ id = "pageActions", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 250, height = 30, gap = 10, }) local refreshButton = Gui.new({ id = "refreshButton", width = 70, height = 28 }) local exportButton = Gui.new({ id = "exportButton", width = 80, height = 28 }) local settingsButton = Gui.new({ id = "settingsButton", width = 75, height = 28 }) pageActions:addChild(refreshButton) pageActions:addChild(exportButton) pageActions:addChild(settingsButton) dashboardHeader:addChild(breadcrumbs) dashboardHeader:addChild(pageTitle) dashboardHeader:addChild(pageActions) -- Widget grid rows (simulated as vertical sections) local topWidgetRow = Gui.new({ id = "topWidgetRow", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 780, height = 250, gap = 20, }) -- Metric widgets for i = 1, 3 do local metricWidget = Gui.new({ id = "metricWidget" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 240, height = 240, gap = 10, }) local widgetHeader = Gui.new({ id = "widgetHeader" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, width = 220, height = 30, gap = 5, }) local widgetTitle = Gui.new({ id = "widgetTitle" .. i, width = 150, height = 25 }) local widgetMenu = Gui.new({ id = "widgetMenu" .. i, width = 20, height = 20 }) widgetHeader:addChild(widgetTitle) widgetHeader:addChild(widgetMenu) local widgetContent = Gui.new({ id = "widgetContent" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, width = 220, height = 150, gap = 8, }) local metricValue = Gui.new({ id = "metricValue" .. i, width = 120, height = 50 }) local metricLabel = Gui.new({ id = "metricLabel" .. i, width = 100, height = 20 }) local metricTrend = Gui.new({ id = "metricTrend" .. i, width = 80, height = 15 }) widgetContent:addChild(metricValue) widgetContent:addChild(metricLabel) widgetContent:addChild(metricTrend) local widgetFooter = Gui.new({ id = "widgetFooter" .. i, width = 220, height = 25, }) metricWidget:addChild(widgetHeader) metricWidget:addChild(widgetContent) metricWidget:addChild(widgetFooter) topWidgetRow:addChild(metricWidget) end -- Chart widget section local chartSection = Gui.new({ id = "chartSection", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 780, height = 400, gap = 15, }) local chartHeader = Gui.new({ id = "chartHeader", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, width = 760, height = 40, gap = 10, }) local chartTitle = Gui.new({ id = "chartTitle", width = 200, height = 35 }) local chartControls = Gui.new({ id = "chartControls", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 300, height = 35, gap = 8, }) local timeRangeSelect = Gui.new({ id = "timeRangeSelect", width = 120, height = 30 }) local chartTypeSelect = Gui.new({ id = "chartTypeSelect", width = 100, height = 30 }) local fullscreenButton = Gui.new({ id = "fullscreenButton", width = 60, height = 30 }) chartControls:addChild(timeRangeSelect) chartControls:addChild(chartTypeSelect) chartControls:addChild(fullscreenButton) chartHeader:addChild(chartTitle) chartHeader:addChild(chartControls) local chartArea = Gui.new({ id = "chartArea", width = 760, height = 300 }) local chartLegend = Gui.new({ id = "chartLegend", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 600, height = 30, gap = 15, }) for i = 1, 4 do local legendItem = Gui.new({ id = "legendItem" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 120, height = 25, gap = 5, }) local legendColor = Gui.new({ id = "legendColor" .. i, width = 15, height = 15 }) local legendLabel = Gui.new({ id = "legendLabel" .. i, width = 95, height = 20 }) legendItem:addChild(legendColor) legendItem:addChild(legendLabel) chartLegend:addChild(legendItem) end chartSection:addChild(chartHeader) chartSection:addChild(chartArea) chartSection:addChild(chartLegend) -- Table widget section local tableSection = Gui.new({ id = "tableSection", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 780, height = 300, gap = 10, }) local tableHeader = Gui.new({ id = "tableHeader", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, width = 760, height = 35, }) local tableTitle = Gui.new({ id = "tableTitle", width = 200, height = 30 }) local tableSearch = Gui.new({ id = "tableSearch", width = 250, height = 30 }) tableHeader:addChild(tableTitle) tableHeader:addChild(tableSearch) local tableContent = Gui.new({ id = "tableContent", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 760, height = 200, gap = 2, }) local tableHeaderRow = Gui.new({ id = "tableHeaderRow", width = 760, height = 35 }) tableContent:addChild(tableHeaderRow) -- Table rows for i = 1, 6 do local tableRow = Gui.new({ id = "tableRow" .. i, width = 760, height = 25, }) tableContent:addChild(tableRow) end local tablePagination = Gui.new({ id = "tablePagination", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.CENTER, width = 300, height = 40, gap = 5, }) for i = 1, 5 do local pageButton = Gui.new({ id = "pageButton" .. i, width = 30, height = 30, }) tablePagination:addChild(pageButton) end tableSection:addChild(tableHeader) tableSection:addChild(tableContent) tableSection:addChild(tablePagination) dashboard:addChild(dashboardHeader) dashboard:addChild(topWidgetRow) dashboard:addChild(chartSection) dashboard:addChild(tableSection) -- Verify complex dashboard structure luaunit.assertEquals(dashboardHeader.y, dashboard.y) luaunit.assertEquals(topWidgetRow.y, dashboardHeader.y + dashboardHeader.height + dashboard.gap) luaunit.assertEquals(chartSection.y, topWidgetRow.y + topWidgetRow.height + dashboard.gap) luaunit.assertEquals(tableSection.y, chartSection.y + chartSection.height + dashboard.gap) -- Verify nested widget structure local firstWidget = topWidgetRow.children[1] local widgetHeader = firstWidget.children[1] local widgetContent = firstWidget.children[2] local widgetFooter = firstWidget.children[3] luaunit.assertEquals(widgetHeader.y, firstWidget.y) luaunit.assertEquals(widgetContent.y, widgetHeader.y + widgetHeader.height + firstWidget.gap) luaunit.assertEquals(widgetFooter.y, widgetContent.y + widgetContent.height + firstWidget.gap) -- Verify chart legend item structure local firstLegendItem = chartLegend.children[1] local legendColor = firstLegendItem.children[1] local legendLabel = firstLegendItem.children[2] luaunit.assertEquals(legendColor.x, firstLegendItem.x) luaunit.assertEquals(legendLabel.x, legendColor.x + legendColor.width + firstLegendItem.gap) end -- Test 22: Mobile-style vertical stack with pull-to-refresh and infinite scroll function TestVerticalFlexDirection:testMobileVerticalStackLayout() local mobileContainer = Gui.new({ id = "mobileContainer", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, x = 0, y = 0, width = 375, -- iPhone-style width height = 812, -- iPhone-style height gap = 0, }) -- Status bar local statusBar = Gui.new({ id = "statusBar", width = 375, height = 44, }) -- Header with pull-to-refresh area local header = Gui.new({ id = "header", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 375, height = 100, gap = 5, }) local pullToRefresh = Gui.new({ id = "pullToRefresh", width = 375, height = 30, }) local navigationBar = Gui.new({ id = "navigationBar", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, width = 375, height = 60, gap = 10, }) local backButton = Gui.new({ id = "backButton", width = 40, height = 40 }) local headerTitle = Gui.new({ id = "headerTitle", width = 200, height = 35 }) local moreButton = Gui.new({ id = "moreButton", width = 40, height = 40 }) navigationBar:addChild(backButton) navigationBar:addChild(headerTitle) navigationBar:addChild(moreButton) header:addChild(pullToRefresh) header:addChild(navigationBar) -- Content area with scrollable list local contentArea = Gui.new({ id = "contentArea", positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 375, height = 600, gap = 1, }) -- Feed items with varying complexity for i = 1, 8 do local feedItem = Gui.new({ id = "feedItem" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 365, height = i % 3 == 0 and 200 or 120, -- Some items are taller gap = 8, }) local itemHeader = Gui.new({ id = "itemHeader" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, alignItems = AlignItems.CENTER, width = 355, height = 50, gap = 12, }) local avatar = Gui.new({ id = "avatar" .. i, width = 40, height = 40 }) local userInfo = Gui.new({ id = "userInfo" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 200, height = 40, gap = 3, }) local username = Gui.new({ id = "username" .. i, width = 150, height = 18 }) local timestamp = Gui.new({ id = "timestamp" .. i, width = 100, height = 14 }) userInfo:addChild(username) userInfo:addChild(timestamp) local itemMenu = Gui.new({ id = "itemMenu" .. i, width = 30, height = 30 }) itemHeader:addChild(avatar) itemHeader:addChild(userInfo) itemHeader:addChild(itemMenu) local itemContent = Gui.new({ id = "itemContent" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 355, height = feedItem.height - 50 - 8, -- Remaining height after header gap = 5, }) local textContent = Gui.new({ id = "textContent" .. i, width = 345, height = 25, }) itemContent:addChild(textContent) -- Some items have media if i % 3 == 0 then local mediaContainer = Gui.new({ id = "mediaContainer" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, width = 345, height = 100, gap = 3, }) local media = Gui.new({ id = "media" .. i, width = 345, height = 80 }) local mediaCaption = Gui.new({ id = "mediaCaption" .. i, width = 300, height = 15 }) mediaContainer:addChild(media) mediaContainer:addChild(mediaCaption) itemContent:addChild(mediaContainer) end -- Interaction bar local interactionBar = Gui.new({ id = "interactionBar" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, width = 345, height = 40, gap = 15, }) local leftActions = Gui.new({ id = "leftActions" .. i, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, width = 150, height = 35, gap = 20, }) local likeButton = Gui.new({ id = "likeButton" .. i, width = 35, height = 30 }) local commentButton = Gui.new({ id = "commentButton" .. i, width = 35, height = 30 }) local shareButton = Gui.new({ id = "shareButton" .. i, width = 35, height = 30 }) leftActions:addChild(likeButton) leftActions:addChild(commentButton) leftActions:addChild(shareButton) local saveButton = Gui.new({ id = "saveButton" .. i, width = 35, height = 30 }) interactionBar:addChild(leftActions) interactionBar:addChild(saveButton) itemContent:addChild(interactionBar) feedItem:addChild(itemHeader) feedItem:addChild(itemContent) contentArea:addChild(feedItem) end -- Bottom tab bar local tabBar = Gui.new({ id = "tabBar", positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_AROUND, alignItems = AlignItems.CENTER, width = 375, height = 83, -- Includes safe area gap = 0, }) local tabItems = { "home", "search", "create", "activity", "profile" } for i, tabName in ipairs(tabItems) do local tab = Gui.new({ id = "tab" .. tabName, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, alignItems = AlignItems.CENTER, width = 60, height = 60, gap = 3, }) local tabIcon = Gui.new({ id = "tabIcon" .. tabName, width = 24, height = 24 }) local tabLabel = Gui.new({ id = "tabLabel" .. tabName, width = 50, height = 12 }) tab:addChild(tabIcon) tab:addChild(tabLabel) tabBar:addChild(tab) end mobileContainer:addChild(statusBar) mobileContainer:addChild(header) mobileContainer:addChild(contentArea) mobileContainer:addChild(tabBar) -- Verify mobile layout structure luaunit.assertEquals(statusBar.y, mobileContainer.y) luaunit.assertEquals(header.y, statusBar.y + statusBar.height) luaunit.assertEquals(contentArea.y, header.y + header.height) luaunit.assertEquals(tabBar.y, contentArea.y + contentArea.height) -- Verify feed item structure local firstFeedItem = contentArea.children[1] local secondFeedItem = contentArea.children[2] luaunit.assertEquals(firstFeedItem.y, contentArea.y) luaunit.assertEquals(secondFeedItem.y, firstFeedItem.y + firstFeedItem.height + contentArea.gap) -- Verify nested interaction structure local itemHeader = firstFeedItem.children[1] local itemContent = firstFeedItem.children[2] local interactionBar = itemContent.children[#itemContent.children] -- Last child luaunit.assertEquals(itemHeader.y, firstFeedItem.y) luaunit.assertEquals(itemContent.y, itemHeader.y + itemHeader.height + firstFeedItem.gap) -- Verify tab structure local firstTab = tabBar.children[1] local tabIcon = firstTab.children[1] local tabLabel = firstTab.children[2] luaunit.assertEquals(tabIcon.y, firstTab.y) luaunit.assertEquals(tabLabel.y, tabIcon.y + tabIcon.height + firstTab.gap) end -- Run the tests luaunit.LuaUnit.run()