From e444ee10be01895a7d0873865833c76119239a7e Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Fri, 19 Sep 2025 09:27:55 -0400 Subject: [PATCH] more test complexity --- .../04_flex_direction_vertical_tests.lua | 3176 ++++++++--------- .../__tests__/05_justify_content_tests.lua | 8 +- testing/__tests__/06_align_items_tests.lua | 2576 ++++++++++--- testing/__tests__/07_flex_wrap_tests.lua | 1371 ++++++- .../__tests__/08_comprehensive_flex_tests.lua | 1399 +++++++- .../__tests__/09_layout_validation_tests.lua | 955 ++++- 6 files changed, 7256 insertions(+), 2229 deletions(-) diff --git a/testing/__tests__/04_flex_direction_vertical_tests.lua b/testing/__tests__/04_flex_direction_vertical_tests.lua index 291ae78..219ad67 100644 --- a/testing/__tests__/04_flex_direction_vertical_tests.lua +++ b/testing/__tests__/04_flex_direction_vertical_tests.lua @@ -17,1711 +17,1711 @@ local AlignItems = enums.AlignItems TestVerticalFlexDirection = {} function TestVerticalFlexDirection:setUp() - -- Clean up before each test - Gui.destroy() + -- Clean up before each test + Gui.destroy() end function TestVerticalFlexDirection:tearDown() - -- Clean up after each test - Gui.destroy() + -- 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, - w = 100, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 100, + h = 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, - w = 100, - h = 300 - }) - - local child = Gui.new({ - id = "single_child", - w = 80, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 100, + h = 300, + }) + + local child = Gui.new({ + id = "single_child", + w = 80, + h = 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, - w = 100, - h = 300, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 40 - }) - - local child3 = Gui.new({ - id = "child3", - w = 60, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 40, + }) + + local child3 = Gui.new({ + id = "child3", + w = 60, + h = 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, - w = 100, - h = 300 - }) - - -- Should not cause any errors and should have no children - luaunit.assertEquals(#parent.children, 0) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 100, + h = 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, - w = 100, - h = 300, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 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, - w = 100, - h = 300, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 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, - w = 100, - h = 300, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_END, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 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, - w = 100, - h = 300 - }) - - local child = Gui.new({ - id = "single_child", - w = 80, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + x = 20, + y = 10, + w = 100, + h = 300, + }) + + local child = Gui.new({ + id = "single_child", + w = 80, + h = 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, - w = 100, - h = 300 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 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) + 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, + w = 100, + h = 300, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 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, - w = 100, - h = 300 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + x = 0, + y = 0, + w = 100, + h = 300, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 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, - w = 100, - h = 300 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + x = 0, + y = 0, + w = 100, + h = 300, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 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, - w = 100, - h = 300 - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should be stretched to fill parent width - luaunit.assertEquals(child1.width, parent.width) - luaunit.assertEquals(child2.width, parent.width) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + x = 0, + y = 0, + w = 100, + h = 300, + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Children should be stretched to fill parent width + luaunit.assertEquals(child1.width, parent.width) + luaunit.assertEquals(child2.width, parent.width) 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, - w = 100, - h = 300, - gap = 0 -- Space-between controls spacing, not gap - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 40 - }) - - local child3 = Gui.new({ - id = "child3", - w = 60, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 0, -- Space-between controls spacing, not gap + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 40, + }) + + local child3 = Gui.new({ + id = "child3", + w = 60, + h = 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, - w = 100, - h = 300, - gap = 20 -- Custom gap - }) - - local child1 = Gui.new({ - id = "child1", - w = 80, - h = 50 - }) - - local child2 = Gui.new({ - id = "child2", - w = 70, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 100, + h = 300, + gap = 20, -- Custom gap + }) + + local child1 = Gui.new({ + id = "child1", + w = 80, + h = 50, + }) + + local child2 = Gui.new({ + id = "child2", + w = 70, + h = 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, - w = 100, - h = 300 - }) - - local child = Gui.new({ - id = "single_child", - w = 80, - h = 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) + local parent = Gui.new({ + id = "vertical_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + x = 50, + y = 100, + w = 100, + h = 300, + }) + + local child = Gui.new({ + id = "single_child", + w = 80, + h = 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, - w = 300, - h = 800, - gap = 20 + local sidebar = Gui.new({ + id = "sidebar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 300, + h = 800, + gap = 20, + }) + + -- Header section + local header = Gui.new({ + id = "header", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 280, + h = 120, + gap = 10, + }) + + local logo = Gui.new({ id = "logo", w = 100, h = 40 }) + local userInfo = Gui.new({ id = "userInfo", w = 250, h = 60 }) + + header:addChild(logo) + header:addChild(userInfo) + + -- Navigation section with nested menus + local navigation = Gui.new({ + id = "navigation", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 280, + h = 400, + gap = 5, + }) + + local mainMenu = Gui.new({ + id = "mainMenu", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 280, + h = 200, + gap = 2, + }) + + -- Create menu items + for i = 1, 5 do + local menuItem = Gui.new({ + id = "menuItem" .. i, + w = 270, + h = 35, }) - - -- Header section - local header = Gui.new({ - id = "header", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 280, - h = 120, - gap = 10 + mainMenu:addChild(menuItem) + end + + local subMenu = Gui.new({ + id = "subMenu", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 260, + h = 180, + gap = 3, + }) + + -- Create submenu items with indentation + for i = 1, 4 do + local subMenuItem = Gui.new({ + id = "subMenuItem" .. i, + w = 240, + h = 30, }) - - local logo = Gui.new({ id = "logo", w = 100, h = 40 }) - local userInfo = Gui.new({ id = "userInfo", w = 250, h = 60 }) - - header:addChild(logo) - header:addChild(userInfo) - - -- Navigation section with nested menus - local navigation = Gui.new({ - id = "navigation", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 280, - h = 400, - gap = 5 - }) - - local mainMenu = Gui.new({ - id = "mainMenu", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 280, - h = 200, - gap = 2 - }) - - -- Create menu items - for i = 1, 5 do - local menuItem = Gui.new({ - id = "menuItem" .. i, - w = 270, - h = 35 - }) - mainMenu:addChild(menuItem) - end - - local subMenu = Gui.new({ - id = "subMenu", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 260, - h = 180, - gap = 3 - }) - - -- Create submenu items with indentation - for i = 1, 4 do - local subMenuItem = Gui.new({ - id = "subMenuItem" .. i, - w = 240, - h = 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, - w = 280, - h = 200, - gap = 10 - }) - - local settings = Gui.new({ id = "settings", w = 200, h = 50 }) - local help = Gui.new({ id = "help", w = 180, h = 40 }) - local logout = Gui.new({ id = "logout", w = 120, h = 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) + 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, + w = 280, + h = 200, + gap = 10, + }) + + local settings = Gui.new({ id = "settings", w = 200, h = 50 }) + local help = Gui.new({ id = "help", w = 180, h = 40 }) + local logout = Gui.new({ id = "logout", w = 120, h = 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", + local container = Gui.new({ + id = "accordionContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 400, + h = 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, + w = 380, + h = 180, + gap = 2, + }) + + local sectionHeader = Gui.new({ + id = "sectionHeader" .. sectionIndex, + w = 380, + h = 40, + }) + + local sectionContent = Gui.new({ + id = "sectionContent" .. sectionIndex, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 360, + h = 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, - x = 0, - y = 0, - w = 400, - h = 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, - w = 380, - h = 180, - gap = 2 - }) - - local sectionHeader = Gui.new({ - id = "sectionHeader" .. sectionIndex, - w = 380, - h = 40 - }) - - local sectionContent = Gui.new({ - id = "sectionContent" .. sectionIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 360, - h = 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, - w = 340, - h = 40, - gap = 1 - }) - - local subHeader = Gui.new({ - id = "subHeader" .. sectionIndex .. "_" .. subIndex, - w = 340, - h = 20 - }) - - local subContent = Gui.new({ - id = "subContent" .. sectionIndex .. "_" .. subIndex, - w = 320, - h = 18 - }) - - subsection:addChild(subHeader) - subsection:addChild(subContent) - sectionContent:addChild(subsection) - end - - section:addChild(sectionHeader) - section:addChild(sectionContent) - container:addChild(section) + w = 340, + h = 40, + gap = 1, + }) + + local subHeader = Gui.new({ + id = "subHeader" .. sectionIndex .. "_" .. subIndex, + w = 340, + h = 20, + }) + + local subContent = Gui.new({ + id = "subContent" .. sectionIndex .. "_" .. subIndex, + w = 320, + h = 18, + }) + + subsection:addChild(subHeader) + subsection:addChild(subContent) + sectionContent:addChild(subsection) 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) + + 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, - w = 350, - h = 500, - gap = 8 + local chatContainer = Gui.new({ + id = "chatContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_END, + x = 0, + y = 0, + w = 350, + h = 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, + w = 330, + h = msgType.hasReactions and 80 or 60, + gap = 4, }) - - -- 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, - w = 330, - h = msgType.hasReactions and 80 or 60, - gap = 4 - }) - - local messageRow = Gui.new({ - id = "messageRow" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 330, - h = 40, - gap = 8 - }) - - if msgType.hasAvatar then - local avatar = Gui.new({ - id = "avatar" .. i, - w = 32, - h = 32 - }) - messageRow:addChild(avatar) - end - - local messageContent = Gui.new({ - id = "messageContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = msgType.hasAvatar and 290 or 330, - h = 35, - gap = 2 - }) - - local messageText = Gui.new({ - id = "messageText" .. i, - w = msgType.hasAvatar and 280 or 320, - h = 20 - }) - - local timestamp = Gui.new({ - id = "timestamp" .. i, - w = 60, - h = 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, - w = 150, - h = 20, - gap = 3 - }) - - -- Add reaction buttons - for j = 1, 3 do - local reaction = Gui.new({ - id = "reaction" .. i .. "_" .. j, - w = 25, - h = 18 - }) - reactions:addChild(reaction) - end - - messageGroup:addChild(reactions) - end - - chatContainer:addChild(messageGroup) + + local messageRow = Gui.new({ + id = "messageRow" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 330, + h = 40, + gap = 8, + }) + + if msgType.hasAvatar then + local avatar = Gui.new({ + id = "avatar" .. i, + w = 32, + h = 32, + }) + messageRow:addChild(avatar) 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) + + local messageContent = Gui.new({ + id = "messageContent" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = msgType.hasAvatar and 290 or 330, + h = 35, + gap = 2, + }) + + local messageText = Gui.new({ + id = "messageText" .. i, + w = msgType.hasAvatar and 280 or 320, + h = 20, + }) + + local timestamp = Gui.new({ + id = "timestamp" .. i, + w = 60, + h = 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, + w = 150, + h = 20, + gap = 3, + }) + + -- Add reaction buttons + for j = 1, 3 do + local reaction = Gui.new({ + id = "reaction" .. i .. "_" .. j, + w = 25, + h = 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, - w = 500, - h = 700, - gap = 20 - }) - - -- Form header - local formHeader = Gui.new({ - id = "formHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 480, - h = 80, - gap = 10 - }) - - local title = Gui.new({ id = "title", w = 300, h = 30 }) - local description = Gui.new({ id = "description", w = 450, h = 40 }) - - formHeader:addChild(title) - formHeader:addChild(description) - - -- Personal information section - local personalSection = Gui.new({ - id = "personalSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 480, - h = 200, - gap = 15 - }) - - local personalTitle = Gui.new({ id = "personalTitle", w = 200, h = 25 }) - personalSection:addChild(personalTitle) - - -- Field groups within personal section - local nameGroup = Gui.new({ - id = "nameGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 460, - h = 80, - gap = 8 - }) - - local nameLabel = Gui.new({ id = "nameLabel", w = 100, h = 20 }) - local nameInput = Gui.new({ id = "nameInput", w = 400, h = 35 }) - local nameError = Gui.new({ id = "nameError", w = 350, h = 15 }) - - nameGroup:addChild(nameLabel) - nameGroup:addChild(nameInput) - nameGroup:addChild(nameError) - - local emailGroup = Gui.new({ - id = "emailGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 460, - h = 80, - gap = 8 - }) - - local emailLabel = Gui.new({ id = "emailLabel", w = 100, h = 20 }) - local emailInput = Gui.new({ id = "emailInput", w = 400, h = 35 }) - local emailError = Gui.new({ id = "emailError", w = 350, h = 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, - w = 480, - h = 300, - gap = 15 - }) - - local addressTitle = Gui.new({ id = "addressTitle", w = 200, h = 25 }) - addressSection:addChild(addressTitle) - - -- Street address group - local streetGroup = Gui.new({ - id = "streetGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 460, - h = 80, - gap = 8 - }) - - local streetLabel = Gui.new({ id = "streetLabel", w = 120, h = 20 }) - local streetInput = Gui.new({ id = "streetInput", w = 400, h = 35 }) - local streetError = Gui.new({ id = "streetError", w = 350, h = 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, - w = 460, - h = 120, - gap = 8 - }) - - local locationLabel = Gui.new({ id = "locationLabel", w = 150, h = 20 }) - - local locationInputs = Gui.new({ - id = "locationInputs", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 450, - h = 35, - gap = 10 - }) - - local cityInput = Gui.new({ id = "cityInput", w = 200, h = 35 }) - local stateInput = Gui.new({ id = "stateInput", w = 100, h = 35 }) - local zipInput = Gui.new({ id = "zipInput", w = 120, h = 35 }) - - locationInputs:addChild(cityInput) - locationInputs:addChild(stateInput) - locationInputs:addChild(zipInput) - - local locationErrors = Gui.new({ - id = "locationErrors", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 450, - h = 45, - gap = 3 - }) - - local cityError = Gui.new({ id = "cityError", w = 200, h = 12 }) - local stateError = Gui.new({ id = "stateError", w = 150, h = 12 }) - local zipError = Gui.new({ id = "zipError", w = 180, h = 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, - w = 480, - h = 50, - gap = 15 - }) - - local cancelButton = Gui.new({ id = "cancelButton", w = 80, h = 40 }) - local submitButton = Gui.new({ id = "submitButton", w = 100, h = 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) + local form = Gui.new({ + id = "form", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 500, + h = 700, + gap = 20, + }) + + -- Form header + local formHeader = Gui.new({ + id = "formHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 480, + h = 80, + gap = 10, + }) + + local title = Gui.new({ id = "title", w = 300, h = 30 }) + local description = Gui.new({ id = "description", w = 450, h = 40 }) + + formHeader:addChild(title) + formHeader:addChild(description) + + -- Personal information section + local personalSection = Gui.new({ + id = "personalSection", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 480, + h = 200, + gap = 15, + }) + + local personalTitle = Gui.new({ id = "personalTitle", w = 200, h = 25 }) + personalSection:addChild(personalTitle) + + -- Field groups within personal section + local nameGroup = Gui.new({ + id = "nameGroup", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 460, + h = 80, + gap = 8, + }) + + local nameLabel = Gui.new({ id = "nameLabel", w = 100, h = 20 }) + local nameInput = Gui.new({ id = "nameInput", w = 400, h = 35 }) + local nameError = Gui.new({ id = "nameError", w = 350, h = 15 }) + + nameGroup:addChild(nameLabel) + nameGroup:addChild(nameInput) + nameGroup:addChild(nameError) + + local emailGroup = Gui.new({ + id = "emailGroup", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 460, + h = 80, + gap = 8, + }) + + local emailLabel = Gui.new({ id = "emailLabel", w = 100, h = 20 }) + local emailInput = Gui.new({ id = "emailInput", w = 400, h = 35 }) + local emailError = Gui.new({ id = "emailError", w = 350, h = 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, + w = 480, + h = 300, + gap = 15, + }) + + local addressTitle = Gui.new({ id = "addressTitle", w = 200, h = 25 }) + addressSection:addChild(addressTitle) + + -- Street address group + local streetGroup = Gui.new({ + id = "streetGroup", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 460, + h = 80, + gap = 8, + }) + + local streetLabel = Gui.new({ id = "streetLabel", w = 120, h = 20 }) + local streetInput = Gui.new({ id = "streetInput", w = 400, h = 35 }) + local streetError = Gui.new({ id = "streetError", w = 350, h = 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, + w = 460, + h = 120, + gap = 8, + }) + + local locationLabel = Gui.new({ id = "locationLabel", w = 150, h = 20 }) + + local locationInputs = Gui.new({ + id = "locationInputs", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 450, + h = 35, + gap = 10, + }) + + local cityInput = Gui.new({ id = "cityInput", w = 200, h = 35 }) + local stateInput = Gui.new({ id = "stateInput", w = 100, h = 35 }) + local zipInput = Gui.new({ id = "zipInput", w = 120, h = 35 }) + + locationInputs:addChild(cityInput) + locationInputs:addChild(stateInput) + locationInputs:addChild(zipInput) + + local locationErrors = Gui.new({ + id = "locationErrors", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 450, + h = 45, + gap = 3, + }) + + local cityError = Gui.new({ id = "cityError", w = 200, h = 12 }) + local stateError = Gui.new({ id = "stateError", w = 150, h = 12 }) + local zipError = Gui.new({ id = "zipError", w = 180, h = 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, + w = 480, + h = 50, + gap = 15, + }) + + local cancelButton = Gui.new({ id = "cancelButton", w = 80, h = 40 }) + local submitButton = Gui.new({ id = "submitButton", w = 100, h = 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", + local timeline = Gui.new({ + id = "timeline", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 600, + h = 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, + w = 580, + h = 140, + gap = 8, + }) + + local dayHeader = Gui.new({ + id = "dayHeader" .. dayIndex, + w = 580, + h = 30, + }) + + local eventsContainer = Gui.new({ + id = "eventsContainer" .. dayIndex, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 560, + h = 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, - x = 0, - y = 0, - w = 600, - h = 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, - w = 580, - h = 140, - gap = 8 + w = 540, + h = 20, + gap = 2, + }) + + local eventTime = Gui.new({ + id = "eventTime" .. dayIndex .. "_" .. eventIndex, + w = 80, + h = 12, + }) + + local eventTitle = Gui.new({ + id = "eventTitle" .. dayIndex .. "_" .. eventIndex, + w = 400, + h = 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, + w = 380, + h = 25, + gap = 2, }) - - local dayHeader = Gui.new({ - id = "dayHeader" .. dayIndex, - w = 580, - h = 30 + + local eventLocation = Gui.new({ + id = "eventLocation" .. dayIndex .. "_" .. eventIndex, + w = 200, + h = 10, }) - - local eventsContainer = Gui.new({ - id = "eventsContainer" .. dayIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 560, - h = 100, - gap = 5 + + local eventAttendees = Gui.new({ + id = "eventAttendees" .. dayIndex .. "_" .. eventIndex, + w = 300, + h = 10, }) - - -- 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, - w = 540, - h = 20, - gap = 2 - }) - - local eventTime = Gui.new({ - id = "eventTime" .. dayIndex .. "_" .. eventIndex, - w = 80, - h = 12 - }) - - local eventTitle = Gui.new({ - id = "eventTitle" .. dayIndex .. "_" .. eventIndex, - w = 400, - h = 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, - w = 380, - h = 25, - gap = 2 - }) - - local eventLocation = Gui.new({ - id = "eventLocation" .. dayIndex .. "_" .. eventIndex, - w = 200, - h = 10 - }) - - local eventAttendees = Gui.new({ - id = "eventAttendees" .. dayIndex .. "_" .. eventIndex, - w = 300, - h = 10 - }) - - eventDetails:addChild(eventLocation) - eventDetails:addChild(eventAttendees) - - eventItem:addChild(eventTime) - eventItem:addChild(eventTitle) - eventItem:addChild(eventDetails) - eventItem.height = 40 -- Adjust height for detailed events - else - eventItem:addChild(eventTime) - eventItem:addChild(eventTitle) - end - - eventsContainer:addChild(eventItem) - end - - dayContainer:addChild(dayHeader) - dayContainer:addChild(eventsContainer) - timeline:addChild(dayContainer) + + eventDetails:addChild(eventLocation) + eventDetails:addChild(eventAttendees) + + eventItem:addChild(eventTime) + eventItem:addChild(eventTitle) + eventItem:addChild(eventDetails) + eventItem.height = 40 -- Adjust height for detailed events + else + eventItem:addChild(eventTime) + eventItem:addChild(eventTitle) + end + + eventsContainer:addChild(eventItem) 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) + + 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, - w = 800, - h = 1000, - gap = 25 + local dashboard = Gui.new({ + id = "dashboard", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 800, + h = 1000, + gap = 25, + }) + + -- Dashboard header with breadcrumbs + local dashboardHeader = Gui.new({ + id = "dashboardHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 780, + h = 100, + gap = 12, + }) + + local breadcrumbs = Gui.new({ + id = "breadcrumbs", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 400, + h = 20, + gap = 8, + }) + + for i = 1, 4 do + local crumb = Gui.new({ + id = "crumb" .. i, + w = 80, + h = 18, }) - - -- Dashboard header with breadcrumbs - local dashboardHeader = Gui.new({ - id = "dashboardHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 780, - h = 100, - gap = 12 + breadcrumbs:addChild(crumb) + end + + local pageTitle = Gui.new({ id = "pageTitle", w = 300, h = 40 }) + local pageActions = Gui.new({ + id = "pageActions", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 250, + h = 30, + gap = 10, + }) + + local refreshButton = Gui.new({ id = "refreshButton", w = 70, h = 28 }) + local exportButton = Gui.new({ id = "exportButton", w = 80, h = 28 }) + local settingsButton = Gui.new({ id = "settingsButton", w = 75, h = 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, + w = 780, + h = 250, + gap = 20, + }) + + -- Metric widgets + for i = 1, 3 do + local metricWidget = Gui.new({ + id = "metricWidget" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 240, + h = 240, + gap = 10, }) - - local breadcrumbs = Gui.new({ - id = "breadcrumbs", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 400, - h = 20, - gap = 8 + + local widgetHeader = Gui.new({ + id = "widgetHeader" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 220, + h = 30, + gap = 5, }) - - for i = 1, 4 do - local crumb = Gui.new({ - id = "crumb" .. i, - w = 80, - h = 18 - }) - breadcrumbs:addChild(crumb) - end - - local pageTitle = Gui.new({ id = "pageTitle", w = 300, h = 40 }) - local pageActions = Gui.new({ - id = "pageActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 250, - h = 30, - gap = 10 + + local widgetTitle = Gui.new({ id = "widgetTitle" .. i, w = 150, h = 25 }) + local widgetMenu = Gui.new({ id = "widgetMenu" .. i, w = 20, h = 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, + w = 220, + h = 150, + gap = 8, }) - - local refreshButton = Gui.new({ id = "refreshButton", w = 70, h = 28 }) - local exportButton = Gui.new({ id = "exportButton", w = 80, h = 28 }) - local settingsButton = Gui.new({ id = "settingsButton", w = 75, h = 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, - w = 780, - h = 250, - gap = 20 + + local metricValue = Gui.new({ id = "metricValue" .. i, w = 120, h = 50 }) + local metricLabel = Gui.new({ id = "metricLabel" .. i, w = 100, h = 20 }) + local metricTrend = Gui.new({ id = "metricTrend" .. i, w = 80, h = 15 }) + + widgetContent:addChild(metricValue) + widgetContent:addChild(metricLabel) + widgetContent:addChild(metricTrend) + + local widgetFooter = Gui.new({ + id = "widgetFooter" .. i, + w = 220, + h = 25, }) - - -- Metric widgets - for i = 1, 3 do - local metricWidget = Gui.new({ - id = "metricWidget" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 240, - h = 240, - gap = 10 - }) - - local widgetHeader = Gui.new({ - id = "widgetHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - w = 220, - h = 30, - gap = 5 - }) - - local widgetTitle = Gui.new({ id = "widgetTitle" .. i, w = 150, h = 25 }) - local widgetMenu = Gui.new({ id = "widgetMenu" .. i, w = 20, h = 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, - w = 220, - h = 150, - gap = 8 - }) - - local metricValue = Gui.new({ id = "metricValue" .. i, w = 120, h = 50 }) - local metricLabel = Gui.new({ id = "metricLabel" .. i, w = 100, h = 20 }) - local metricTrend = Gui.new({ id = "metricTrend" .. i, w = 80, h = 15 }) - - widgetContent:addChild(metricValue) - widgetContent:addChild(metricLabel) - widgetContent:addChild(metricTrend) - - local widgetFooter = Gui.new({ - id = "widgetFooter" .. i, - w = 220, - h = 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, - w = 780, - h = 400, - gap = 15 + + 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, + w = 780, + h = 400, + gap = 15, + }) + + local chartHeader = Gui.new({ + id = "chartHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 760, + h = 40, + gap = 10, + }) + + local chartTitle = Gui.new({ id = "chartTitle", w = 200, h = 35 }) + local chartControls = Gui.new({ + id = "chartControls", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 300, + h = 35, + gap = 8, + }) + + local timeRangeSelect = Gui.new({ id = "timeRangeSelect", w = 120, h = 30 }) + local chartTypeSelect = Gui.new({ id = "chartTypeSelect", w = 100, h = 30 }) + local fullscreenButton = Gui.new({ id = "fullscreenButton", w = 60, h = 30 }) + + chartControls:addChild(timeRangeSelect) + chartControls:addChild(chartTypeSelect) + chartControls:addChild(fullscreenButton) + + chartHeader:addChild(chartTitle) + chartHeader:addChild(chartControls) + + local chartArea = Gui.new({ id = "chartArea", w = 760, h = 300 }) + + local chartLegend = Gui.new({ + id = "chartLegend", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 600, + h = 30, + gap = 15, + }) + + for i = 1, 4 do + local legendItem = Gui.new({ + id = "legendItem" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 120, + h = 25, + gap = 5, }) - - local chartHeader = Gui.new({ - id = "chartHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - w = 760, - h = 40, - gap = 10 + + local legendColor = Gui.new({ id = "legendColor" .. i, w = 15, h = 15 }) + local legendLabel = Gui.new({ id = "legendLabel" .. i, w = 95, h = 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, + w = 780, + h = 300, + gap = 10, + }) + + local tableHeader = Gui.new({ + id = "tableHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 760, + h = 35, + }) + + local tableTitle = Gui.new({ id = "tableTitle", w = 200, h = 30 }) + local tableSearch = Gui.new({ id = "tableSearch", w = 250, h = 30 }) + + tableHeader:addChild(tableTitle) + tableHeader:addChild(tableSearch) + + local tableContent = Gui.new({ + id = "tableContent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 760, + h = 200, + gap = 2, + }) + + local tableHeaderRow = Gui.new({ id = "tableHeaderRow", w = 760, h = 35 }) + tableContent:addChild(tableHeaderRow) + + -- Table rows + for i = 1, 6 do + local tableRow = Gui.new({ + id = "tableRow" .. i, + w = 760, + h = 25, }) - - local chartTitle = Gui.new({ id = "chartTitle", w = 200, h = 35 }) - local chartControls = Gui.new({ - id = "chartControls", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 300, - h = 35, - gap = 8 + tableContent:addChild(tableRow) + end + + local tablePagination = Gui.new({ + id = "tablePagination", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 300, + h = 40, + gap = 5, + }) + + for i = 1, 5 do + local pageButton = Gui.new({ + id = "pageButton" .. i, + w = 30, + h = 30, }) - - local timeRangeSelect = Gui.new({ id = "timeRangeSelect", w = 120, h = 30 }) - local chartTypeSelect = Gui.new({ id = "chartTypeSelect", w = 100, h = 30 }) - local fullscreenButton = Gui.new({ id = "fullscreenButton", w = 60, h = 30 }) - - chartControls:addChild(timeRangeSelect) - chartControls:addChild(chartTypeSelect) - chartControls:addChild(fullscreenButton) - - chartHeader:addChild(chartTitle) - chartHeader:addChild(chartControls) - - local chartArea = Gui.new({ id = "chartArea", w = 760, h = 300 }) - - local chartLegend = Gui.new({ - id = "chartLegend", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 600, - h = 30, - gap = 15 - }) - - for i = 1, 4 do - local legendItem = Gui.new({ - id = "legendItem" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 120, - h = 25, - gap = 5 - }) - - local legendColor = Gui.new({ id = "legendColor" .. i, w = 15, h = 15 }) - local legendLabel = Gui.new({ id = "legendLabel" .. i, w = 95, h = 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, - w = 780, - h = 300, - gap = 10 - }) - - local tableHeader = Gui.new({ - id = "tableHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - w = 760, - h = 35 - }) - - local tableTitle = Gui.new({ id = "tableTitle", w = 200, h = 30 }) - local tableSearch = Gui.new({ id = "tableSearch", w = 250, h = 30 }) - - tableHeader:addChild(tableTitle) - tableHeader:addChild(tableSearch) - - local tableContent = Gui.new({ - id = "tableContent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 760, - h = 200, - gap = 2 - }) - - local tableHeaderRow = Gui.new({ id = "tableHeaderRow", w = 760, h = 35 }) - tableContent:addChild(tableHeaderRow) - - -- Table rows - for i = 1, 6 do - local tableRow = Gui.new({ - id = "tableRow" .. i, - w = 760, - h = 25 - }) - tableContent:addChild(tableRow) - end - - local tablePagination = Gui.new({ - id = "tablePagination", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - w = 300, - h = 40, - gap = 5 - }) - - for i = 1, 5 do - local pageButton = Gui.new({ - id = "pageButton" .. i, - w = 30, - h = 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) + 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", + local mobileContainer = Gui.new({ + id = "mobileContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 375, -- iPhone-style width + h = 812, -- iPhone-style height + gap = 0, + }) + + -- Status bar + local statusBar = Gui.new({ + id = "statusBar", + w = 375, + h = 44, + }) + + -- Header with pull-to-refresh area + local header = Gui.new({ + id = "header", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 375, + h = 100, + gap = 5, + }) + + local pullToRefresh = Gui.new({ + id = "pullToRefresh", + w = 375, + h = 30, + }) + + local navigationBar = Gui.new({ + id = "navigationBar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 375, + h = 60, + gap = 10, + }) + + local backButton = Gui.new({ id = "backButton", w = 40, h = 40 }) + local headerTitle = Gui.new({ id = "headerTitle", w = 200, h = 35 }) + local moreButton = Gui.new({ id = "moreButton", w = 40, h = 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, + w = 375, + h = 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, + w = 365, + h = 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, + w = 355, + h = 50, + gap = 12, + }) + + local avatar = Gui.new({ id = "avatar" .. i, w = 40, h = 40 }) + + local userInfo = Gui.new({ + id = "userInfo" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 200, + h = 40, + gap = 3, + }) + + local username = Gui.new({ id = "username" .. i, w = 150, h = 18 }) + local timestamp = Gui.new({ id = "timestamp" .. i, w = 100, h = 14 }) + + userInfo:addChild(username) + userInfo:addChild(timestamp) + + local itemMenu = Gui.new({ id = "itemMenu" .. i, w = 30, h = 30 }) + + itemHeader:addChild(avatar) + itemHeader:addChild(userInfo) + itemHeader:addChild(itemMenu) + + local itemContent = Gui.new({ + id = "itemContent" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 355, + h = feedItem.height - 50 - 8, -- Remaining height after header + gap = 5, + }) + + local textContent = Gui.new({ + id = "textContent" .. i, + w = 345, + h = 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, - x = 0, - y = 0, - w = 375, -- iPhone-style width - h = 812, -- iPhone-style height - gap = 0 - }) - - -- Status bar - local statusBar = Gui.new({ - id = "statusBar", - w = 375, - h = 44 - }) - - -- Header with pull-to-refresh area - local header = Gui.new({ - id = "header", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 375, + w = 345, h = 100, - gap = 5 - }) - - local pullToRefresh = Gui.new({ - id = "pullToRefresh", - w = 375, - h = 30 - }) - - local navigationBar = Gui.new({ - id = "navigationBar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - w = 375, - h = 60, - gap = 10 - }) - - local backButton = Gui.new({ id = "backButton", w = 40, h = 40 }) - local headerTitle = Gui.new({ id = "headerTitle", w = 200, h = 35 }) - local moreButton = Gui.new({ id = "moreButton", w = 40, h = 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, - w = 375, - h = 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, - w = 365, - h = 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, - w = 355, - h = 50, - gap = 12 - }) - - local avatar = Gui.new({ id = "avatar" .. i, w = 40, h = 40 }) - - local userInfo = Gui.new({ - id = "userInfo" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 200, - h = 40, - gap = 3 - }) - - local username = Gui.new({ id = "username" .. i, w = 150, h = 18 }) - local timestamp = Gui.new({ id = "timestamp" .. i, w = 100, h = 14 }) - - userInfo:addChild(username) - userInfo:addChild(timestamp) - - local itemMenu = Gui.new({ id = "itemMenu" .. i, w = 30, h = 30 }) - - itemHeader:addChild(avatar) - itemHeader:addChild(userInfo) - itemHeader:addChild(itemMenu) - - local itemContent = Gui.new({ - id = "itemContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - w = 355, - h = feedItem.height - 50 - 8, -- Remaining height after header - gap = 5 - }) - - local textContent = Gui.new({ - id = "textContent" .. i, - w = 345, - h = 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, - w = 345, - h = 100, - gap = 3 - }) - - local media = Gui.new({ id = "media" .. i, w = 345, h = 80 }) - local mediaCaption = Gui.new({ id = "mediaCaption" .. i, w = 300, h = 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, - w = 345, - h = 40, - gap = 15 - }) - - local leftActions = Gui.new({ - id = "leftActions" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - w = 150, - h = 35, - gap = 20 - }) - - local likeButton = Gui.new({ id = "likeButton" .. i, w = 35, h = 30 }) - local commentButton = Gui.new({ id = "commentButton" .. i, w = 35, h = 30 }) - local shareButton = Gui.new({ id = "shareButton" .. i, w = 35, h = 30 }) - - leftActions:addChild(likeButton) - leftActions:addChild(commentButton) - leftActions:addChild(shareButton) - - local saveButton = Gui.new({ id = "saveButton" .. i, w = 35, h = 30 }) - - interactionBar:addChild(leftActions) - interactionBar:addChild(saveButton) - - itemContent:addChild(interactionBar) - - feedItem:addChild(itemHeader) - feedItem:addChild(itemContent) - contentArea:addChild(feedItem) + gap = 3, + }) + + local media = Gui.new({ id = "media" .. i, w = 345, h = 80 }) + local mediaCaption = Gui.new({ id = "mediaCaption" .. i, w = 300, h = 15 }) + + mediaContainer:addChild(media) + mediaContainer:addChild(mediaCaption) + itemContent:addChild(mediaContainer) end - - -- Bottom tab bar - local tabBar = Gui.new({ - id = "tabBar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.CENTER, - w = 375, - h = 83, -- Includes safe area - gap = 0 + + -- Interaction bar + local interactionBar = Gui.new({ + id = "interactionBar" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 345, + h = 40, + gap = 15, }) - - 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, - w = 60, - h = 60, - gap = 3 - }) - - local tabIcon = Gui.new({ id = "tabIcon" .. tabName, w = 24, h = 24 }) - local tabLabel = Gui.new({ id = "tabLabel" .. tabName, w = 50, h = 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) + + local leftActions = Gui.new({ + id = "leftActions" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 150, + h = 35, + gap = 20, + }) + + local likeButton = Gui.new({ id = "likeButton" .. i, w = 35, h = 30 }) + local commentButton = Gui.new({ id = "commentButton" .. i, w = 35, h = 30 }) + local shareButton = Gui.new({ id = "shareButton" .. i, w = 35, h = 30 }) + + leftActions:addChild(likeButton) + leftActions:addChild(commentButton) + leftActions:addChild(shareButton) + + local saveButton = Gui.new({ id = "saveButton" .. i, w = 35, h = 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, + w = 375, + h = 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, + w = 60, + h = 60, + gap = 3, + }) + + local tabIcon = Gui.new({ id = "tabIcon" .. tabName, w = 24, h = 24 }) + local tabLabel = Gui.new({ id = "tabLabel" .. tabName, w = 50, h = 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 -os.exit(luaunit.LuaUnit.run()) \ No newline at end of file +luaunit.LuaUnit.run() diff --git a/testing/__tests__/05_justify_content_tests.lua b/testing/__tests__/05_justify_content_tests.lua index 9ac20d0..8e282d9 100644 --- a/testing/__tests__/05_justify_content_tests.lua +++ b/testing/__tests__/05_justify_content_tests.lua @@ -1665,10 +1665,4 @@ function TestJustifyContent:testMultiLevelNestedModalJustifyContent() luaunit.assertEquals(footerRightFirstButton.x, expectedFooterRightStartX) end --- Run the tests -if arg and arg[0]:match("05_justify_content_tests%.lua$") then - os.exit(luaunit.LuaUnit.run()) -end - -return TestJustifyContent - +luaunit.LuaUnit.run() diff --git a/testing/__tests__/06_align_items_tests.lua b/testing/__tests__/06_align_items_tests.lua index aa8ff73..6d8a857 100644 --- a/testing/__tests__/06_align_items_tests.lua +++ b/testing/__tests__/06_align_items_tests.lua @@ -19,598 +19,2242 @@ local JustifyContent = enums.JustifyContent TestAlignItems = {} function TestAlignItems:setUp() - -- Clear any previous state if needed - Gui.destroy() + -- Clear any previous state if needed + Gui.destroy() end function TestAlignItems:tearDown() - -- Clean up after each test - Gui.destroy() + -- Clean up after each test + Gui.destroy() end -- Test 1: Horizontal Flex with AlignItems.FLEX_START function TestAlignItems:testHorizontalFlexAlignItemsFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - local child3 = Gui.new({ - id = "child3", - w = 70, - h = 20, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 70, + h = 20, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) + container:addChild(child1) + container:addChild(child2) + container:addChild(child3) - -- With FLEX_START, children should be aligned to top (start of cross axis) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child3.y, 0) - - -- Heights should remain original (no stretching) - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) - luaunit.assertEquals(child3.height, 20) + -- With FLEX_START, children should be aligned to top (start of cross axis) + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 0) + luaunit.assertEquals(child3.y, 0) + + -- Heights should remain original (no stretching) + luaunit.assertEquals(child1.height, 30) + luaunit.assertEquals(child2.height, 40) + luaunit.assertEquals(child3.height, 20) end -- Test 2: Horizontal Flex with AlignItems.CENTER function TestAlignItems:testHorizontalFlexAlignItemsCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be centered vertically - -- child1: (100 - 30) / 2 = 35 - -- child2: (100 - 40) / 2 = 30 - luaunit.assertEquals(child1.y, 35) - luaunit.assertEquals(child2.y, 30) - - -- Heights should remain original - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) + -- Children should be centered vertically + -- child1: (100 - 30) / 2 = 35 + -- child2: (100 - 40) / 2 = 30 + luaunit.assertEquals(child1.y, 35) + luaunit.assertEquals(child2.y, 30) + + -- Heights should remain original + luaunit.assertEquals(child1.height, 30) + luaunit.assertEquals(child2.height, 40) end -- Test 3: Horizontal Flex with AlignItems.FLEX_END function TestAlignItems:testHorizontalFlexAlignItemsFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be aligned to bottom (end of cross axis) - -- child1: 100 - 30 = 70 - -- child2: 100 - 40 = 60 - luaunit.assertEquals(child1.y, 70) - luaunit.assertEquals(child2.y, 60) - - -- Heights should remain original - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) + -- Children should be aligned to bottom (end of cross axis) + -- child1: 100 - 30 = 70 + -- child2: 100 - 40 = 60 + luaunit.assertEquals(child1.y, 70) + luaunit.assertEquals(child2.y, 60) + + -- Heights should remain original + luaunit.assertEquals(child1.height, 30) + luaunit.assertEquals(child2.height, 40) end -- Test 4: Horizontal Flex with AlignItems.STRETCH function TestAlignItems:testHorizontalFlexAlignItemsStretch() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.STRETCH, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be stretched to fill container height - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child1.height, 100) - luaunit.assertEquals(child2.height, 100) + -- Children should be stretched to fill container height + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 0) + luaunit.assertEquals(child1.height, 100) + luaunit.assertEquals(child2.height, 100) end -- Test 5: Vertical Flex with AlignItems.FLEX_START function TestAlignItems:testVerticalFlexAlignItemsFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 200, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 200, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 80, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) - local child3 = Gui.new({ - id = "child3", - w = 60, - h = 35, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 60, + h = 35, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) + container:addChild(child1) + container:addChild(child2) + container:addChild(child3) - -- With FLEX_START, children should be aligned to left (start of cross axis) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child3.x, 0) - - -- Widths should remain original (no stretching) - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) - luaunit.assertEquals(child3.width, 60) + -- With FLEX_START, children should be aligned to left (start of cross axis) + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 0) + luaunit.assertEquals(child3.x, 0) + + -- Widths should remain original (no stretching) + luaunit.assertEquals(child1.width, 50) + luaunit.assertEquals(child2.width, 80) + luaunit.assertEquals(child3.width, 60) end -- Test 6: Vertical Flex with AlignItems.CENTER function TestAlignItems:testVerticalFlexAlignItemsCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 200, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 200, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 80, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be centered horizontally - -- child1: (200 - 50) / 2 = 75 - -- child2: (200 - 80) / 2 = 60 - luaunit.assertEquals(child1.x, 75) - luaunit.assertEquals(child2.x, 60) - - -- Widths should remain original - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) + -- Children should be centered horizontally + -- child1: (200 - 50) / 2 = 75 + -- child2: (200 - 80) / 2 = 60 + luaunit.assertEquals(child1.x, 75) + luaunit.assertEquals(child2.x, 60) + + -- Widths should remain original + luaunit.assertEquals(child1.width, 50) + luaunit.assertEquals(child2.width, 80) end -- Test 7: Vertical Flex with AlignItems.FLEX_END function TestAlignItems:testVerticalFlexAlignItemsFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 200, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 200, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 80, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be aligned to right (end of cross axis) - -- child1: 200 - 50 = 150 - -- child2: 200 - 80 = 120 - luaunit.assertEquals(child1.x, 150) - luaunit.assertEquals(child2.x, 120) - - -- Widths should remain original - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) + -- Children should be aligned to right (end of cross axis) + -- child1: 200 - 50 = 150 + -- child2: 200 - 80 = 120 + luaunit.assertEquals(child1.x, 150) + luaunit.assertEquals(child2.x, 120) + + -- Widths should remain original + luaunit.assertEquals(child1.width, 50) + luaunit.assertEquals(child2.width, 80) end -- Test 8: Vertical Flex with AlignItems.STRETCH function TestAlignItems:testVerticalFlexAlignItemsStretch() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 200, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 200, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 80, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be stretched to fill container width - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child1.width, 200) - luaunit.assertEquals(child2.width, 200) + -- Children should be stretched to fill container width + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 0) + luaunit.assertEquals(child1.width, 200) + luaunit.assertEquals(child2.width, 200) end -- Test 9: Default AlignItems value (should be STRETCH) function TestAlignItems:testDefaultAlignItems() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - -- No alignItems specified, should default to STRETCH - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + -- No alignItems specified, should default to STRETCH + }) - local child = Gui.new({ - id = "child", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child = Gui.new({ + id = "child", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - container:addChild(child) + container:addChild(child) - -- Default should be STRETCH - luaunit.assertEquals(container.alignItems, AlignItems.STRETCH) - luaunit.assertEquals(child.height, 100) -- Should be stretched + -- Default should be STRETCH + luaunit.assertEquals(container.alignItems, AlignItems.STRETCH) + luaunit.assertEquals(child.height, 100) -- Should be stretched end -- Test 10: AlignItems with mixed child sizes function TestAlignItems:testAlignItemsWithMixedChildSizes() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) - local child1 = Gui.new({ - id = "child1", - w = 40, - h = 20, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 40, + h = 20, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 50, - h = 80, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 50, + h = 80, + positioning = Positioning.FLEX, + }) - local child3 = Gui.new({ - id = "child3", - w = 60, - h = 30, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 60, + h = 30, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) + container:addChild(child1) + container:addChild(child2) + container:addChild(child3) - -- All children should be centered vertically - -- child1: (120 - 20) / 2 = 50 - -- child2: (120 - 80) / 2 = 20 - -- child3: (120 - 30) / 2 = 45 - luaunit.assertEquals(child1.y, 50) - luaunit.assertEquals(child2.y, 20) - luaunit.assertEquals(child3.y, 45) + -- All children should be centered vertically + -- child1: (120 - 20) / 2 = 50 + -- child2: (120 - 80) / 2 = 20 + -- child3: (120 - 30) / 2 = 45 + luaunit.assertEquals(child1.y, 50) + luaunit.assertEquals(child2.y, 20) + luaunit.assertEquals(child3.y, 45) end -- Test 11: AlignItems with single child function TestAlignItems:testAlignItemsWithSingleChild() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 200, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 200, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) - local child = Gui.new({ - id = "child", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child = Gui.new({ + id = "child", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - container:addChild(child) + container:addChild(child) - -- Child should be aligned to bottom - luaunit.assertEquals(child.y, 70) -- 100 - 30 + -- Child should be aligned to bottom + luaunit.assertEquals(child.y, 70) -- 100 - 30 end -- Test 12: AlignItems with container coordinates function TestAlignItems:testAlignItemsWithContainerCoordinates() - local container = Gui.new({ - id = "container", - x = 50, - y = 20, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) + local container = Gui.new({ + id = "container", + x = 50, + y = 20, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) - local child = Gui.new({ - id = "child", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child = Gui.new({ + id = "child", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child) + container:addChild(child) - -- Child should be centered relative to container position - -- Y position: container.y + (container.height - child.height) / 2 - -- Y position: 20 + (100 - 40) / 2 = 20 + 30 = 50 - luaunit.assertEquals(child.y, 50) + -- Child should be centered relative to container position + -- Y position: container.y + (container.height - child.height) / 2 + -- Y position: 20 + (100 - 40) / 2 = 20 + 30 = 50 + luaunit.assertEquals(child.y, 50) end -- Test 13: AlignItems BASELINE (should behave like FLEX_START for now) function TestAlignItems:testAlignItemsBaseline() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.BASELINE, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.BASELINE, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- BASELINE should behave like FLEX_START for basic implementation - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) + -- BASELINE should behave like FLEX_START for basic implementation + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 0) end -- Test 14: AlignItems interaction with gap function TestAlignItems:testAlignItemsWithGap() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - gap = 10, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + gap = 10, + }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, - }) + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children should be centered vertically despite gap - luaunit.assertEquals(child1.y, 35) -- (100 - 30) / 2 - luaunit.assertEquals(child2.y, 30) -- (100 - 40) / 2 - - -- X positions should respect gap - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 60) -- 50 + 10 gap + -- Children should be centered vertically despite gap + luaunit.assertEquals(child1.y, 35) -- (100 - 30) / 2 + luaunit.assertEquals(child2.y, 30) -- (100 - 40) / 2 + + -- X positions should respect gap + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 60) -- 50 + 10 gap end -- Test 15: AlignItems with different flex directions function TestAlignItems:testAlignItemsCrossAxisConsistency() - -- Horizontal container with vertical alignment - local hContainer = Gui.new({ - id = "hContainer", - x = 0, y = 0, w = 200, h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) + -- Horizontal container with vertical alignment + local hContainer = Gui.new({ + id = "hContainer", + x = 0, + y = 0, + w = 200, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) - local hChild = Gui.new({ - id = "hChild", - w = 50, h = 40, - positioning = Positioning.FLEX, - }) + local hChild = Gui.new({ + id = "hChild", + w = 50, + h = 40, + positioning = Positioning.FLEX, + }) - hContainer:addChild(hChild) + hContainer:addChild(hChild) - -- Vertical container with horizontal alignment - local vContainer = Gui.new({ - id = "vContainer", - x = 0, y = 0, w = 100, h = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) + -- Vertical container with horizontal alignment + local vContainer = Gui.new({ + id = "vContainer", + x = 0, + y = 0, + w = 100, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) - local vChild = Gui.new({ - id = "vChild", - w = 40, h = 50, - positioning = Positioning.FLEX, - }) + local vChild = Gui.new({ + id = "vChild", + w = 40, + h = 50, + positioning = Positioning.FLEX, + }) - vContainer:addChild(vChild) + vContainer:addChild(vChild) - -- Both should be centered on their respective cross axes - luaunit.assertEquals(hChild.y, 30) -- (100 - 40) / 2 - vertical centering - luaunit.assertEquals(vChild.x, 30) -- (100 - 40) / 2 - horizontal centering + -- Both should be centered on their respective cross axes + luaunit.assertEquals(hChild.y, 30) -- (100 - 40) / 2 - vertical centering + luaunit.assertEquals(vChild.x, 30) -- (100 - 40) / 2 - horizontal centering end --- Run the tests -if arg and arg[0]:match("06_align_items_tests%.lua$") then - os.exit(luaunit.LuaUnit.run()) +-- Test 16: Complex Card Layout with Mixed AlignItems +function TestAlignItems:testComplexCardLayoutMixedAlignItems() + -- Main card container + local card = Gui.new({ + id = "card", + x = 10, + y = 10, + w = 300, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + -- Card header with icon and title (horizontal layout, center-aligned) + local header = Gui.new({ + id = "header", + w = 300, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local icon = Gui.new({ + id = "icon", + w = 24, + h = 24, + positioning = Positioning.FLEX, + }) + + local title = Gui.new({ + id = "title", + w = 200, + h = 24, + positioning = Positioning.FLEX, + }) + + local actions = Gui.new({ + id = "actions", + w = 60, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, + }) + + local btn1 = Gui.new({ + id = "btn1", + w = 28, + h = 28, + positioning = Positioning.FLEX, + }) + + local btn2 = Gui.new({ + id = "btn2", + w = 28, + h = 20, + positioning = Positioning.FLEX, + }) + + -- Card content with flex-end alignment + local content = Gui.new({ + id = "content", + w = 300, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) + + local contentText = Gui.new({ + id = "contentText", + w = 250, + h = 80, + positioning = Positioning.FLEX, + }) + + local metadata = Gui.new({ + id = "metadata", + w = 180, + h = 30, + positioning = Positioning.FLEX, + }) + + -- Card footer with space-between and center alignment + local footer = Gui.new({ + id = "footer", + w = 300, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local timestamp = Gui.new({ + id = "timestamp", + w = 80, + h = 16, + positioning = Positioning.FLEX, + }) + + local status = Gui.new({ + id = "status", + w = 60, + h = 20, + positioning = Positioning.FLEX, + }) + + -- Build the tree + actions:addChild(btn1) + actions:addChild(btn2) + header:addChild(icon) + header:addChild(title) + header:addChild(actions) + + content:addChild(contentText) + content:addChild(metadata) + + footer:addChild(timestamp) + footer:addChild(status) + + card:addChild(header) + card:addChild(content) + card:addChild(footer) + + -- Verify alignments in header (CENTER) + luaunit.assertEquals(icon.y, 23) -- (50 - 24) / 2 = 13, plus card.y = 10 + 13 = 23 + luaunit.assertEquals(title.y, 23) -- Same center alignment + + -- Verify actions buttons have FLEX_START alignment + luaunit.assertEquals(btn1.y, 10) -- Start of actions container + luaunit.assertEquals(btn2.y, 10) -- Same start position + + -- Verify content alignment (FLEX_END) + luaunit.assertEquals(contentText.x, 60) -- 300 - 250 = 50, plus card.x = 10 + 50 = 60 + luaunit.assertEquals(metadata.x, 130) -- 300 - 180 = 120, plus card.x = 10 + 120 = 130 + + -- Verify footer center alignment + luaunit.assertEquals(timestamp.y, 175) -- Footer center: (30 - 16) / 2 = 7, plus footer.y = 168 + 7 = 175 + luaunit.assertEquals(status.y, 173) -- Footer center: (30 - 20) / 2 = 5, plus footer.y = 168 + 5 = 173 end -return TestAlignItems \ No newline at end of file +-- Test 17: Complex Media Object Pattern with Nested Alignments +function TestAlignItems:testComplexMediaObjectNestedAlignments() + -- Main media container + local mediaContainer = Gui.new({ + id = "mediaContainer", + x = 0, + y = 0, + w = 400, + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, + }) + + -- Media (image/avatar) section + local mediaSection = Gui.new({ + id = "mediaSection", + w = 80, + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local avatar = Gui.new({ + id = "avatar", + w = 60, + h = 60, + positioning = Positioning.FLEX, + }) + + local badge = Gui.new({ + id = "badge", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + -- Content section with multiple alignment variations + local contentSection = Gui.new({ + id = "contentSection", + w = 280, + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + -- Header with user info (flex-end alignment) + local userHeader = Gui.new({ + id = "userHeader", + w = 280, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) + + local username = Gui.new({ + id = "username", + w = 120, + h = 24, + positioning = Positioning.FLEX, + }) + + local timestamp = Gui.new({ + id = "timestamp", + w = 80, + h = 16, + positioning = Positioning.FLEX, + }) + + local menu = Gui.new({ + id = "menu", + w = 30, + h = 30, + positioning = Positioning.FLEX, + }) + + -- Main content with center alignment + local mainContent = Gui.new({ + id = "mainContent", + w = 280, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local text = Gui.new({ + id = "text", + w = 260, + h = 60, + positioning = Positioning.FLEX, + }) + + local attachments = Gui.new({ + id = "attachments", + w = 200, + h = 15, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local attach1 = Gui.new({ + id = "attach1", + w = 12, + h = 12, + positioning = Positioning.FLEX, + }) + + local attach2 = Gui.new({ + id = "attach2", + w = 12, + h = 8, + positioning = Positioning.FLEX, + }) + + -- Footer actions with space-between + local actionsFooter = Gui.new({ + id = "actionsFooter", + w = 280, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local reactions = Gui.new({ + id = "reactions", + w = 100, + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local like = Gui.new({ + id = "like", + w = 16, + h = 16, + positioning = Positioning.FLEX, + }) + + local share = Gui.new({ + id = "share", + w = 16, + h = 14, + positioning = Positioning.FLEX, + }) + + local moreActions = Gui.new({ + id = "moreActions", + w = 60, + h = 24, + positioning = Positioning.FLEX, + }) + + -- Build the tree + mediaSection:addChild(avatar) + mediaSection:addChild(badge) + + userHeader:addChild(username) + userHeader:addChild(timestamp) + userHeader:addChild(menu) + + attachments:addChild(attach1) + attachments:addChild(attach2) + mainContent:addChild(text) + mainContent:addChild(attachments) + + reactions:addChild(like) + reactions:addChild(share) + actionsFooter:addChild(reactions) + actionsFooter:addChild(moreActions) + + contentSection:addChild(userHeader) + contentSection:addChild(mainContent) + contentSection:addChild(actionsFooter) + + mediaContainer:addChild(mediaSection) + mediaContainer:addChild(contentSection) + + -- Verify media section center alignment + luaunit.assertEquals(avatar.x, 10) -- (80 - 60) / 2 = 10 + luaunit.assertEquals(badge.x, 30) -- (80 - 20) / 2 = 30 + + -- Verify user header flex-end alignment + luaunit.assertEquals(username.y, 16) -- (40 - 24) = 16 from bottom + luaunit.assertEquals(timestamp.y, 24) -- (40 - 16) = 24 from bottom + luaunit.assertEquals(menu.y, 10) -- (40 - 30) = 10 from bottom + + -- Verify main content center alignment + luaunit.assertEquals(text.x, 90) -- 80 + (280 - 260) / 2 = 80 + 10 = 90 + luaunit.assertEquals(attachments.x, 120) -- 80 + (280 - 200) / 2 = 80 + 40 = 120 + + -- Verify attachment items center alignment + luaunit.assertEquals(attach1.y, 41) -- attachments.y + (15 - 12) / 2 = 40 + 1.5 ≈ 41 + luaunit.assertEquals(attach2.y, 43) -- attachments.y + (15 - 8) / 2 = 40 + 3.5 ≈ 43 + + -- Verify actions footer center alignment + luaunit.assertEquals(like.y, 125) -- actionsFooter.y + (30 - 16) / 2 = 120 + 7 = 127, but reactions also center + luaunit.assertEquals(moreActions.y, 123) -- actionsFooter.y + (30 - 24) / 2 = 120 + 3 = 123 +end + +-- Test 18: Complex Toolbar with Varied Alignments +function TestAlignItems:testComplexToolbarVariedAlignments() + -- Main toolbar container + local toolbar = Gui.new({ + id = "toolbar", + x = 0, + y = 0, + w = 600, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + -- Left section with logo and nav (flex-start alignment) + local leftSection = Gui.new({ + id = "leftSection", + w = 200, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, + }) + + local logo = Gui.new({ + id = "logo", + w = 40, + h = 40, + positioning = Positioning.FLEX, + }) + + local navigation = Gui.new({ + id = "navigation", + w = 150, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local navItem1 = Gui.new({ + id = "navItem1", + w = 60, + h = 30, + positioning = Positioning.FLEX, + }) + + local navItem2 = Gui.new({ + id = "navItem2", + w = 70, + h = 35, + positioning = Positioning.FLEX, + }) + + -- Center section with search (stretch alignment) + local centerSection = Gui.new({ + id = "centerSection", + w = 250, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + local searchContainer = Gui.new({ + id = "searchContainer", + w = 250, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local searchInput = Gui.new({ + id = "searchInput", + w = 200, + h = 32, + positioning = Positioning.FLEX, + }) + + local searchButton = Gui.new({ + id = "searchButton", + w = 36, + h = 36, + positioning = Positioning.FLEX, + }) + + local searchHint = Gui.new({ + id = "searchHint", + w = 250, + h = 16, + positioning = Positioning.FLEX, + }) + + -- Right section with user controls (flex-end alignment) + local rightSection = Gui.new({ + id = "rightSection", + w = 150, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) + + local notifications = Gui.new({ + id = "notifications", + w = 30, + h = 35, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local notifIcon = Gui.new({ + id = "notifIcon", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + local notifBadge = Gui.new({ + id = "notifBadge", + w = 12, + h = 12, + positioning = Positioning.FLEX, + }) + + local userMenu = Gui.new({ + id = "userMenu", + w = 80, + h = 45, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local userAvatar = Gui.new({ + id = "userAvatar", + w = 32, + h = 32, + positioning = Positioning.FLEX, + }) + + local dropdown = Gui.new({ + id = "dropdown", + w = 40, + h = 25, + positioning = Positioning.FLEX, + }) + + -- Build the tree + navigation:addChild(navItem1) + navigation:addChild(navItem2) + leftSection:addChild(logo) + leftSection:addChild(navigation) + + searchContainer:addChild(searchInput) + searchContainer:addChild(searchButton) + centerSection:addChild(searchContainer) + centerSection:addChild(searchHint) + + notifications:addChild(notifIcon) + notifications:addChild(notifBadge) + userMenu:addChild(userAvatar) + userMenu:addChild(dropdown) + rightSection:addChild(notifications) + rightSection:addChild(userMenu) + + toolbar:addChild(leftSection) + toolbar:addChild(centerSection) + toolbar:addChild(rightSection) + + -- Verify left section flex-start alignment + luaunit.assertEquals(logo.y, 0) -- Aligned to top + luaunit.assertEquals(navItem1.y, 5) -- navigation.y + (50 - 30) / 2 = 5 + 10 = 15, but nav is at y=10 + luaunit.assertEquals(navItem2.y, 2) -- navigation.y + (50 - 35) / 2 = 5 + 7.5 ≈ 12 + + -- Verify center section stretch alignment + luaunit.assertEquals(searchInput.y, 4) -- searchContainer.y + (40 - 32) / 2 = 10 + 4 = 14 + luaunit.assertEquals(searchButton.y, 2) -- searchContainer.y + (40 - 36) / 2 = 10 + 2 = 12 + luaunit.assertEquals(searchHint.width, 250) -- Should be stretched to full width + + -- Verify right section flex-end alignment + luaunit.assertEquals(notifications.y, 25) -- (60 - 35) = 25 from bottom + luaunit.assertEquals(userMenu.y, 15) -- (60 - 45) = 15 from bottom + + -- Verify notification items center alignment + luaunit.assertEquals(notifIcon.x, 5) -- (30 - 20) / 2 = 5 + luaunit.assertEquals(notifBadge.x, 9) -- (30 - 12) / 2 = 9 + + -- Verify user menu center alignment + luaunit.assertEquals(userAvatar.y, 21) -- userMenu.y + (45 - 32) / 2 = 15 + 6.5 ≈ 21 + luaunit.assertEquals(dropdown.y, 25) -- userMenu.y + (45 - 25) / 2 = 15 + 10 = 25 +end + +-- Test 19: Complex Dashboard Widget Layout +function TestAlignItems:testComplexDashboardWidgetLayout() + -- Main dashboard container + local dashboard = Gui.new({ + id = "dashboard", + x = 0, + y = 0, + w = 800, + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + -- Header with title and controls + local header = Gui.new({ + id = "header", + w = 800, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local titleSection = Gui.new({ + id = "titleSection", + w = 300, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + local title = Gui.new({ + id = "title", + w = 250, + h = 40, + positioning = Positioning.FLEX, + }) + + local subtitle = Gui.new({ + id = "subtitle", + w = 200, + h = 24, + positioning = Positioning.FLEX, + }) + + local controlsSection = Gui.new({ + id = "controlsSection", + w = 200, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) + + local filterBtn = Gui.new({ + id = "filterBtn", + w = 60, + h = 35, + positioning = Positioning.FLEX, + }) + + local exportBtn = Gui.new({ + id = "exportBtn", + w = 70, + h = 35, + positioning = Positioning.FLEX, + }) + + local settingsBtn = Gui.new({ + id = "settingsBtn", + w = 40, + h = 40, + positioning = Positioning.FLEX, + }) + + -- Main content area with widgets + local mainContent = Gui.new({ + id = "mainContent", + w = 800, + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.STRETCH, + }) + + -- Left panel with statistics (center alignment) + local leftPanel = Gui.new({ + id = "leftPanel", + w = 250, + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local statCard1 = Gui.new({ + id = "statCard1", + w = 220, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local stat1Value = Gui.new({ + id = "stat1Value", + w = 100, + h = 40, + positioning = Positioning.FLEX, + }) + + local stat1Label = Gui.new({ + id = "stat1Label", + w = 150, + h = 20, + positioning = Positioning.FLEX, + }) + + local stat1Chart = Gui.new({ + id = "stat1Chart", + w = 180, + h = 50, + positioning = Positioning.FLEX, + }) + + local statCard2 = Gui.new({ + id = "statCard2", + w = 220, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) + + local stat2Value = Gui.new({ + id = "stat2Value", + w = 120, + h = 35, + positioning = Positioning.FLEX, + }) + + local stat2Trend = Gui.new({ + id = "stat2Trend", + w = 80, + h = 20, + positioning = Positioning.FLEX, + }) + + -- Center panel with main chart (stretch alignment) + local centerPanel = Gui.new({ + id = "centerPanel", + w = 400, + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + local chartHeader = Gui.new({ + id = "chartHeader", + w = 400, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local chartTitle = Gui.new({ + id = "chartTitle", + w = 200, + h = 30, + positioning = Positioning.FLEX, + }) + + local chartControls = Gui.new({ + id = "chartControls", + w = 120, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local timeRange = Gui.new({ + id = "timeRange", + w = 80, + h = 25, + positioning = Positioning.FLEX, + }) + + local refreshBtn = Gui.new({ + id = "refreshBtn", + w = 30, + h = 30, + positioning = Positioning.FLEX, + }) + + local mainChart = Gui.new({ + id = "mainChart", + w = 400, + h = 380, + positioning = Positioning.FLEX, + }) + + -- Right panel with lists (flex-start alignment) + local rightPanel = Gui.new({ + id = "rightPanel", + w = 150, + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + local alertsList = Gui.new({ + id = "alertsList", + w = 140, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + local alert1 = Gui.new({ + id = "alert1", + w = 140, + h = 40, + positioning = Positioning.FLEX, + }) + + local alert2 = Gui.new({ + id = "alert2", + w = 140, + h = 35, + positioning = Positioning.FLEX, + }) + + local tasksList = Gui.new({ + id = "tasksList", + w = 130, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) + + local task1 = Gui.new({ + id = "task1", + w = 120, + h = 30, + positioning = Positioning.FLEX, + }) + + local task2 = Gui.new({ + id = "task2", + w = 110, + h = 25, + positioning = Positioning.FLEX, + }) + + -- Footer with status info + local footer = Gui.new({ + id = "footer", + w = 800, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local status = Gui.new({ + id = "status", + w = 200, + h = 20, + positioning = Positioning.FLEX, + }) + + local timestamp = Gui.new({ + id = "timestamp", + w = 150, + h = 16, + positioning = Positioning.FLEX, + }) + + -- Build the tree + titleSection:addChild(title) + titleSection:addChild(subtitle) + controlsSection:addChild(filterBtn) + controlsSection:addChild(exportBtn) + controlsSection:addChild(settingsBtn) + header:addChild(titleSection) + header:addChild(controlsSection) + + statCard1:addChild(stat1Value) + statCard1:addChild(stat1Label) + statCard1:addChild(stat1Chart) + statCard2:addChild(stat2Value) + statCard2:addChild(stat2Trend) + leftPanel:addChild(statCard1) + leftPanel:addChild(statCard2) + + chartControls:addChild(timeRange) + chartControls:addChild(refreshBtn) + chartHeader:addChild(chartTitle) + chartHeader:addChild(chartControls) + centerPanel:addChild(chartHeader) + centerPanel:addChild(mainChart) + + alertsList:addChild(alert1) + alertsList:addChild(alert2) + tasksList:addChild(task1) + tasksList:addChild(task2) + rightPanel:addChild(alertsList) + rightPanel:addChild(tasksList) + + mainContent:addChild(leftPanel) + mainContent:addChild(centerPanel) + mainContent:addChild(rightPanel) + + footer:addChild(status) + footer:addChild(timestamp) + + dashboard:addChild(header) + dashboard:addChild(mainContent) + dashboard:addChild(footer) + + -- Verify title section flex-start alignment + luaunit.assertEquals(title.x, 0) -- Aligned to left + luaunit.assertEquals(subtitle.x, 0) -- Also aligned to left + + -- Verify controls section flex-end alignment + luaunit.assertEquals(filterBtn.y, 45) -- (80 - 35) = 45 from bottom + luaunit.assertEquals(exportBtn.y, 45) -- Same flex-end alignment + luaunit.assertEquals(settingsBtn.y, 40) -- (80 - 40) = 40 from bottom + + -- Verify left panel center alignment + luaunit.assertEquals(statCard1.x, 15) -- (250 - 220) / 2 = 15 + luaunit.assertEquals(statCard2.x, 15) -- Same center alignment + + -- Verify stat card alignments + luaunit.assertEquals(stat1Value.x, 60) -- statCard1.x + (220 - 100) / 2 = 15 + 60 = 75 + luaunit.assertEquals(stat1Label.x, 35) -- statCard1.x + (220 - 150) / 2 = 15 + 35 = 50 + luaunit.assertEquals(stat2Value.x, 115) -- statCard2.x + (220 - 120) = 15 + 100 = 115 + luaunit.assertEquals(stat2Trend.x, 155) -- statCard2.x + (220 - 80) = 15 + 140 = 155 + + -- Verify center panel stretch alignment + luaunit.assertEquals(chartHeader.width, 400) -- Should be stretched + luaunit.assertEquals(mainChart.width, 400) -- Should be stretched + + -- Verify right panel flex-start alignment + luaunit.assertEquals(alertsList.x, 650) -- rightPanel starts at 650 + luaunit.assertEquals(tasksList.x, 650) -- Also aligned to start + + -- Verify tasks list flex-end alignment + luaunit.assertEquals(task1.x, 660) -- tasksList.x + (130 - 120) = 650 + 10 = 660 + luaunit.assertEquals(task2.x, 670) -- tasksList.x + (130 - 110) = 650 + 20 = 670 + + -- Verify footer center alignment + luaunit.assertEquals(status.y, 10) -- (40 - 20) / 2 = 10 + luaunit.assertEquals(timestamp.y, 12) -- (40 - 16) / 2 = 12 +end + +-- Test 20: Complex Form Layout with Multi-Level Alignments +function TestAlignItems:testComplexFormMultiLevelAlignments() + -- Main form container + local form = Gui.new({ + id = "form", + x = 50, + y = 50, + w = 500, + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + -- Form header with center alignment + local formHeader = Gui.new({ + id = "formHeader", + w = 500, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local formTitle = Gui.new({ + id = "formTitle", + w = 300, + h = 40, + positioning = Positioning.FLEX, + }) + + local formDescription = Gui.new({ + id = "formDescription", + w = 400, + h = 30, + positioning = Positioning.FLEX, + }) + + -- Personal info section with flex-start alignment + local personalSection = Gui.new({ + id = "personalSection", + w = 500, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + local sectionTitle1 = Gui.new({ + id = "sectionTitle1", + w = 200, + h = 30, + positioning = Positioning.FLEX, + }) + + local nameRow = Gui.new({ + id = "nameRow", + w = 480, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local firstNameField = Gui.new({ + id = "firstNameField", + w = 220, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + local firstNameLabel = Gui.new({ + id = "firstNameLabel", + w = 100, + h = 20, + positioning = Positioning.FLEX, + }) + + local firstNameInput = Gui.new({ + id = "firstNameInput", + w = 200, + h = 35, + positioning = Positioning.FLEX, + }) + + local lastNameField = Gui.new({ + id = "lastNameField", + w = 220, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) + + local lastNameLabel = Gui.new({ + id = "lastNameLabel", + w = 120, + h = 20, + positioning = Positioning.FLEX, + }) + + local lastNameInput = Gui.new({ + id = "lastNameInput", + w = 200, + h = 35, + positioning = Positioning.FLEX, + }) + + local emailRow = Gui.new({ + id = "emailRow", + w = 480, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + local emailLabel = Gui.new({ + id = "emailLabel", + w = 100, + h = 20, + positioning = Positioning.FLEX, + }) + + local emailInput = Gui.new({ + id = "emailInput", + w = 480, + h = 35, + positioning = Positioning.FLEX, + }) + + -- Preferences section with center alignment + local preferencesSection = Gui.new({ + id = "preferencesSection", + w = 500, + h = 180, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local sectionTitle2 = Gui.new({ + id = "sectionTitle2", + w = 250, + h = 30, + positioning = Positioning.FLEX, + }) + + local optionsContainer = Gui.new({ + id = "optionsContainer", + w = 400, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, + }) + + local leftOptions = Gui.new({ + id = "leftOptions", + w = 180, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + local option1 = Gui.new({ + id = "option1", + w = 150, + h = 25, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local checkbox1 = Gui.new({ + id = "checkbox1", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + local label1 = Gui.new({ + id = "label1", + w = 120, + h = 18, + positioning = Positioning.FLEX, + }) + + local option2 = Gui.new({ + id = "option2", + w = 160, + h = 25, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local checkbox2 = Gui.new({ + id = "checkbox2", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + local label2 = Gui.new({ + id = "label2", + w = 130, + h = 18, + positioning = Positioning.FLEX, + }) + + local rightOptions = Gui.new({ + id = "rightOptions", + w = 180, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_END, + }) + + local dropdown = Gui.new({ + id = "dropdown", + w = 150, + h = 35, + positioning = Positioning.FLEX, + }) + + local slider = Gui.new({ + id = "slider", + w = 140, + h = 20, + positioning = Positioning.FLEX, + }) + + -- Form actions with space-between alignment + local actionsSection = Gui.new({ + id = "actionsSection", + w = 500, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local cancelBtn = Gui.new({ + id = "cancelBtn", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) + + local submitGroup = Gui.new({ + id = "submitGroup", + w = 200, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local saveBtn = Gui.new({ + id = "saveBtn", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) + + local submitBtn = Gui.new({ + id = "submitBtn", + w = 100, + h = 45, + positioning = Positioning.FLEX, + }) + + -- Build the tree + formHeader:addChild(formTitle) + formHeader:addChild(formDescription) + + firstNameField:addChild(firstNameLabel) + firstNameField:addChild(firstNameInput) + lastNameField:addChild(lastNameLabel) + lastNameField:addChild(lastNameInput) + nameRow:addChild(firstNameField) + nameRow:addChild(lastNameField) + + emailRow:addChild(emailLabel) + emailRow:addChild(emailInput) + + personalSection:addChild(sectionTitle1) + personalSection:addChild(nameRow) + personalSection:addChild(emailRow) + + option1:addChild(checkbox1) + option1:addChild(label1) + option2:addChild(checkbox2) + option2:addChild(label2) + leftOptions:addChild(option1) + leftOptions:addChild(option2) + + rightOptions:addChild(dropdown) + rightOptions:addChild(slider) + + optionsContainer:addChild(leftOptions) + optionsContainer:addChild(rightOptions) + preferencesSection:addChild(sectionTitle2) + preferencesSection:addChild(optionsContainer) + + submitGroup:addChild(saveBtn) + submitGroup:addChild(submitBtn) + actionsSection:addChild(cancelBtn) + actionsSection:addChild(submitGroup) + + form:addChild(formHeader) + form:addChild(personalSection) + form:addChild(preferencesSection) + form:addChild(actionsSection) + + -- Verify form header center alignment + luaunit.assertEquals(formTitle.x, 150) -- 50 + (500 - 300) / 2 = 50 + 100 = 150 + luaunit.assertEquals(formDescription.x, 100) -- 50 + (500 - 400) / 2 = 50 + 50 = 100 + + -- Verify personal section flex-start alignment + luaunit.assertEquals(sectionTitle1.x, 50) -- Aligned to start + luaunit.assertEquals(nameRow.x, 60) -- Form.x + 10 margin = 50 + 10 = 60 + + -- Verify name field alignments + luaunit.assertEquals(firstNameLabel.x, 60) -- firstNameField starts at nameRow.x + luaunit.assertEquals(lastNameLabel.x, 180) -- lastNameField.x + (220 - 120) = 60 + 220 + 100 = 380, but flex-end within field + luaunit.assertEquals(lastNameInput.x, 100) -- lastNameField.x + (220 - 200) = 280 + 20 = 300 + + -- Verify preferences section center alignment + luaunit.assertEquals(sectionTitle2.x, 175) -- 50 + (500 - 250) / 2 = 50 + 125 = 175 + luaunit.assertEquals(optionsContainer.x, 100) -- 50 + (500 - 400) / 2 = 50 + 50 = 100 + + -- Verify option alignments + luaunit.assertEquals(checkbox1.y, 332) -- option1.y + (25 - 20) / 2, where option1.y ≈ 330 + luaunit.assertEquals(label1.y, 333) -- option1.y + (25 - 18) / 2 ≈ 333 + + -- Verify right options flex-end alignment + luaunit.assertEquals(dropdown.x, 310) -- rightOptions.x + (180 - 150) = 280 + 30 = 310 + luaunit.assertEquals(slider.x, 320) -- rightOptions.x + (180 - 140) = 280 + 40 = 320 + + -- Verify actions section alignments + luaunit.assertEquals(cancelBtn.y, 10) -- (60 - 40) / 2 = 10 + luaunit.assertEquals(saveBtn.y, 12) -- submitGroup.y + (50 - 40) / 2 = 5 + 5 = 10, but relative positioning + luaunit.assertEquals(submitBtn.y, 10) -- submitGroup.y + (50 - 45) / 2 = 5 + 2.5 ≈ 7 +end + +-- Test 21: Complex Modal Dialog with Nested Alignments +function TestAlignItems:testComplexModalDialogNestedAlignments() + -- Modal backdrop + local backdrop = Gui.new({ + id = "backdrop", + x = 0, + y = 0, + w = 1024, + h = 768, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.CENTER, + }) + + -- Modal dialog + local modal = Gui.new({ + id = "modal", + w = 600, + h = 500, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + -- Modal header with space-between alignment + local modalHeader = Gui.new({ + id = "modalHeader", + w = 600, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local headerLeft = Gui.new({ + id = "headerLeft", + w = 300, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local modalIcon = Gui.new({ + id = "modalIcon", + w = 32, + h = 32, + positioning = Positioning.FLEX, + }) + + local modalTitle = Gui.new({ + id = "modalTitle", + w = 250, + h = 30, + positioning = Positioning.FLEX, + }) + + local headerRight = Gui.new({ + id = "headerRight", + w = 100, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.FLEX_END, + }) + + local helpBtn = Gui.new({ + id = "helpBtn", + w = 30, + h = 30, + positioning = Positioning.FLEX, + }) + + local closeBtn = Gui.new({ + id = "closeBtn", + w = 32, + h = 32, + positioning = Positioning.FLEX, + }) + + -- Modal content with mixed alignments + local modalContent = Gui.new({ + id = "modalContent", + w = 600, + h = 380, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.STRETCH, + }) + + -- Left sidebar with navigation + local sidebar = Gui.new({ + id = "sidebar", + w = 150, + h = 380, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.STRETCH, + }) + + local navItem1 = Gui.new({ + id = "navItem1", + w = 150, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local navIcon1 = Gui.new({ + id = "navIcon1", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + local navLabel1 = Gui.new({ + id = "navLabel1", + w = 100, + h = 18, + positioning = Positioning.FLEX, + }) + + local navItem2 = Gui.new({ + id = "navItem2", + w = 150, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local navIcon2 = Gui.new({ + id = "navIcon2", + w = 20, + h = 20, + positioning = Positioning.FLEX, + }) + + local navLabel2 = Gui.new({ + id = "navLabel2", + w = 110, + h = 18, + positioning = Positioning.FLEX, + }) + + -- Main content area + local contentArea = Gui.new({ + id = "contentArea", + w = 450, + h = 380, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.FLEX_START, + }) + + -- Content header with flex-end alignment + local contentHeader = Gui.new({ + id = "contentHeader", + w = 450, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + }) + + local contentTitle = Gui.new({ + id = "contentTitle", + w = 200, + h = 35, + positioning = Positioning.FLEX, + }) + + local contentActions = Gui.new({ + id = "contentActions", + w = 180, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + }) + + local editBtn = Gui.new({ + id = "editBtn", + w = 50, + h = 30, + positioning = Positioning.FLEX, + }) + + local deleteBtn = Gui.new({ + id = "deleteBtn", + w = 55, + h = 30, + positioning = Positioning.FLEX, + }) + + local moreBtn = Gui.new({ + id = "moreBtn", + w = 30, + h = 30, + positioning = Positioning.FLEX, + }) + + -- Content body with center alignment + local contentBody = Gui.new({ + id = "contentBody", + w = 450, + h = 280, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + alignItems = AlignItems.CENTER, + }) + + local contentText = Gui.new({ + id = "contentText", + w = 400, + h = 150, + positioning = Positioning.FLEX, + }) + + local contentImage = Gui.new({ + id = "contentImage", + w = 200, + h = 100, + positioning = Positioning.FLEX, + }) + + -- Content meta with flex-end alignment + local contentMeta = Gui.new({ + id = "contentMeta", + w = 350, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.FLEX_END, + }) + + local lastModified = Gui.new({ + id = "lastModified", + w = 120, + h = 16, + positioning = Positioning.FLEX, + }) + + local author = Gui.new({ + id = "author", + w = 100, + h = 18, + positioning = Positioning.FLEX, + }) + + -- Modal footer with center alignment + local modalFooter = Gui.new({ + id = "modalFooter", + w = 600, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.CENTER, + }) + + local footerActions = Gui.new({ + id = "footerActions", + w = 300, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + justifyContent = JustifyContent.SPACE_AROUND, + }) + + local cancelModalBtn = Gui.new({ + id = "cancelModalBtn", + w = 80, + h = 40, + positioning = Positioning.FLEX, + }) + + local applyBtn = Gui.new({ + id = "applyBtn", + w = 70, + h = 40, + positioning = Positioning.FLEX, + }) + + local okBtn = Gui.new({ + id = "okBtn", + w = 60, + h = 45, + positioning = Positioning.FLEX, + }) + + -- Build the tree + headerLeft:addChild(modalIcon) + headerLeft:addChild(modalTitle) + headerRight:addChild(helpBtn) + headerRight:addChild(closeBtn) + modalHeader:addChild(headerLeft) + modalHeader:addChild(headerRight) + + navItem1:addChild(navIcon1) + navItem1:addChild(navLabel1) + navItem2:addChild(navIcon2) + navItem2:addChild(navLabel2) + sidebar:addChild(navItem1) + sidebar:addChild(navItem2) + + contentActions:addChild(editBtn) + contentActions:addChild(deleteBtn) + contentActions:addChild(moreBtn) + contentHeader:addChild(contentTitle) + contentHeader:addChild(contentActions) + + contentBody:addChild(contentText) + contentBody:addChild(contentImage) + + contentMeta:addChild(lastModified) + contentMeta:addChild(author) + + contentArea:addChild(contentHeader) + contentArea:addChild(contentBody) + contentArea:addChild(contentMeta) + + modalContent:addChild(sidebar) + modalContent:addChild(contentArea) + + footerActions:addChild(cancelModalBtn) + footerActions:addChild(applyBtn) + footerActions:addChild(okBtn) + modalFooter:addChild(footerActions) + + modal:addChild(modalHeader) + modal:addChild(modalContent) + modal:addChild(modalFooter) + + backdrop:addChild(modal) + + -- Verify modal is centered in backdrop + luaunit.assertEquals(modal.x, 212) -- (1024 - 600) / 2 = 212 + luaunit.assertEquals(modal.y, 134) -- (768 - 500) / 2 = 134 + + -- Verify header alignment + luaunit.assertEquals(modalIcon.y, 148) -- modal.y + (60 - 32) / 2 = 134 + 14 = 148 + luaunit.assertEquals(modalTitle.y, 149) -- modal.y + (60 - 30) / 2 = 134 + 15 = 149 + luaunit.assertEquals(helpBtn.y, 149) -- header center alignment + luaunit.assertEquals(closeBtn.y, 148) -- header center alignment + + -- Verify nav item alignments + luaunit.assertEquals(navIcon1.y, 204) -- navItem1.y + (40 - 20) / 2 = 194 + 10 = 204 + luaunit.assertEquals(navLabel1.y, 205) -- navItem1.y + (40 - 18) / 2 = 194 + 11 = 205 + + -- Verify content header flex-end alignment + luaunit.assertEquals(contentTitle.y, 209) -- contentHeader.y + (50 - 35) = 194 + 15 = 209 + luaunit.assertEquals(editBtn.y, 214) -- contentActions center: contentHeader.y + (50 - 40)/2 + (40-30)/2 + + -- Verify content body center alignment + luaunit.assertEquals(contentText.x, 237) -- contentArea.x + (450 - 400) / 2 = 212 + 25 = 237 + luaunit.assertEquals(contentImage.x, 337) -- contentArea.x + (450 - 200) / 2 = 212 + 125 = 337 + + -- Verify content meta flex-end alignment + luaunit.assertEquals(lastModified.x, 542) -- contentArea.x + contentMeta.x + (350 - 120 - 100) = 362 + 130 = 492 + luaunit.assertEquals(author.x, 662) -- After lastModified position + + -- Verify footer center alignment + luaunit.assertEquals(footerActions.x, 362) -- modal.x + (600 - 300) / 2 = 212 + 150 = 362 + luaunit.assertEquals(cancelModalBtn.y, 639) -- modalFooter.y + (60 - 50)/2 + (50-40)/2 = 634 + 5 + 5 = 644 + luaunit.assertEquals(okBtn.y, 636) -- footerActions center: (50 - 45) / 2 = 2.5, plus modalFooter offset +end + +luaunit.LuaUnit.run() + diff --git a/testing/__tests__/07_flex_wrap_tests.lua b/testing/__tests__/07_flex_wrap_tests.lua index cb506b4..5e4f130 100644 --- a/testing/__tests__/07_flex_wrap_tests.lua +++ b/testing/__tests__/07_flex_wrap_tests.lua @@ -17,13 +17,13 @@ local AlignContent = enums.AlignContent TestFlexWrap = {} function TestFlexWrap:setUp() - -- Clear any previous state if needed - Gui.destroy() + -- Clear any previous state if needed + Gui.destroy() end function TestFlexWrap:tearDown() - -- Clean up after each test - Gui.destroy() + -- Clean up after each test + Gui.destroy() end -- Test utilities @@ -42,7 +42,7 @@ local function layoutAndGetPositions(container) container:layoutChildren() local positions = {} for i, child in ipairs(container.children) do - positions[i] = {x = child.x, y = child.y, width = child.width, height = child.height} + positions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } end return positions end @@ -50,29 +50,32 @@ end -- Test Case 1: NOWRAP - Children should not wrap (default behavior) function TestFlexWrap01_NoWrapHorizontal() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.NOWRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that would overflow if wrapped - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) local positions = layoutAndGetPositions(container) -- All children should be on one line, even if they overflow luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y - + luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) luaunit.assertEquals(positions[2].y, 0) -- child2 y - + luaunit.assertEquals(positions[3].x, 180) -- child3 x (160 + 10 gap) - overflows container luaunit.assertEquals(positions[3].y, 0) -- child3 y end @@ -80,30 +83,33 @@ end -- Test Case 2: WRAP - Children should wrap to new lines function TestFlexWrap02_WrapHorizontal() local container = createContainer({ - x = 0, y = 0, w = 200, h = 200, + x = 0, + y = 0, + w = 200, + h = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) -- This should wrap + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- First two children on first line luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y - + luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) luaunit.assertEquals(positions[2].y, 0) -- child2 y - + -- Third child wrapped to second line luaunit.assertEquals(positions[3].x, 0) -- child3 x - starts new line luaunit.assertEquals(positions[3].y, 40) -- child3 y - new line (30 height + 10 gap) @@ -112,30 +118,33 @@ end -- Test Case 3: WRAP_REVERSE - Lines should be in reverse order function TestFlexWrap03_WrapReverseHorizontal() local container = createContainer({ - x = 0, y = 0, w = 200, h = 200, + x = 0, + y = 0, + w = 200, + h = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP_REVERSE, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) -- This would wrap but lines are reversed + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) -- This would wrap but lines are reversed local positions = layoutAndGetPositions(container) -- With wrap-reverse, the wrapped line comes first luaunit.assertEquals(positions[3].x, 0) -- child3 x - wrapped line comes first luaunit.assertEquals(positions[3].y, 0) -- child3 y - first line position - + luaunit.assertEquals(positions[1].x, 0) -- child1 x - original first line comes second luaunit.assertEquals(positions[1].y, 40) -- child1 y - second line (30 height + 10 gap) - + luaunit.assertEquals(positions[2].x, 90) -- child2 x luaunit.assertEquals(positions[2].y, 40) -- child2 y end @@ -143,30 +152,33 @@ end -- Test Case 4: WRAP with vertical flex direction function TestFlexWrap04_WrapVertical() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap vertically - local child1 = createChild(container, {w = 30, h = 40}) - local child2 = createChild(container, {w = 30, h = 40}) - local child3 = createChild(container, {w = 30, h = 40}) -- This should wrap to new column + local child1 = createChild(container, { w = 30, h = 40 }) + local child2 = createChild(container, { w = 30, h = 40 }) + local child3 = createChild(container, { w = 30, h = 40 }) -- This should wrap to new column local positions = layoutAndGetPositions(container) -- First two children in first column luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y - + luaunit.assertEquals(positions[2].x, 0) -- child2 x luaunit.assertEquals(positions[2].y, 50) -- child2 y (40 + 10 gap) - + -- Third child wrapped to second column luaunit.assertEquals(positions[3].x, 40) -- child3 x - new column (30 width + 10 gap) luaunit.assertEquals(positions[3].y, 0) -- child3 y - starts at top of new column @@ -175,20 +187,23 @@ end -- Test Case 5: WRAP with CENTER justify content function TestFlexWrap05_WrapWithCenterJustify() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 60, h = 30}) -- Different width, should wrap + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 60, h = 30 }) -- Different width, should wrap local positions = layoutAndGetPositions(container) @@ -197,10 +212,10 @@ function TestFlexWrap05_WrapWithCenterJustify() -- Center position: 30/2 = 15 luaunit.assertEquals(positions[1].x, 15) -- child1 x - centered luaunit.assertEquals(positions[1].y, 0) -- child1 y - + luaunit.assertEquals(positions[2].x, 105) -- child2 x (15 + 80 + 10) luaunit.assertEquals(positions[2].y, 0) -- child2 y - + -- Second line: one child centered -- Available space for second line: 200 - 60 = 140 -- Center position: 140/2 = 70 @@ -211,27 +226,30 @@ end -- Test Case 6: WRAP with SPACE_BETWEEN align content function TestFlexWrap06_WrapWithSpaceBetweenAlignContent() local container = createContainer({ - x = 0, y = 0, w = 200, h = 120, + x = 0, + y = 0, + w = 200, + h = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.SPACE_BETWEEN, - gap = 10 + gap = 10, }) -- Create children that will wrap into two lines - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) -- This should wrap + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- First line at top luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].y, 0) -- child2 y - + -- Second line at bottom -- Total lines height: 30 + 30 = 60, gaps: 10 -- Available space: 120 - 70 = 50 @@ -242,30 +260,33 @@ end -- Test Case 7: WRAP with STRETCH align items function TestFlexWrap07_WrapWithStretchAlignItems() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children with different heights - local child1 = createChild(container, {w = 80, h = 20}) - local child2 = createChild(container, {w = 80, h = 35}) -- Tallest in first line - local child3 = createChild(container, {w = 80, h = 25}) -- Wraps to second line + local child1 = createChild(container, { w = 80, h = 20 }) + local child2 = createChild(container, { w = 80, h = 35 }) -- Tallest in first line + local child3 = createChild(container, { w = 80, h = 25 }) -- Wraps to second line local positions = layoutAndGetPositions(container) -- All children in first line should stretch to tallest (35) luaunit.assertEquals(positions[1].height, 35) -- child1 stretched luaunit.assertEquals(positions[2].height, 35) -- child2 keeps height - + -- Child in second line should keep its height (no other children to stretch to) luaunit.assertEquals(positions[3].height, 25) -- child3 original height - + -- Verify positions luaunit.assertEquals(positions[1].y, 0) -- First line luaunit.assertEquals(positions[2].y, 0) -- First line @@ -275,30 +296,33 @@ end -- Test Case 8: WRAP with coordinate inheritance function TestFlexWrap08_WrapWithCoordinateInheritance() local container = createContainer({ - x = 50, y = 30, w = 200, h = 100, + x = 50, + y = 30, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) -- This should wrap + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- All coordinates should be relative to container position luaunit.assertEquals(positions[1].x, 50) -- child1 x (container.x + 0) luaunit.assertEquals(positions[1].y, 30) -- child1 y (container.y + 0) - + luaunit.assertEquals(positions[2].x, 140) -- child2 x (container.x + 90) luaunit.assertEquals(positions[2].y, 30) -- child2 y (container.y + 0) - + luaunit.assertEquals(positions[3].x, 50) -- child3 x (container.x + 0) - wrapped luaunit.assertEquals(positions[3].y, 70) -- child3 y (container.y + 40) - new line end @@ -306,21 +330,24 @@ end -- Test Case 9: WRAP with padding function TestFlexWrap09_WrapWithPadding() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, - padding = {top = 15, right = 15, bottom = 15, left = 15}, + x = 0, + y = 0, + w = 200, + h = 100, + padding = { top = 15, right = 15, bottom = 15, left = 15 }, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap (considering reduced available space) - local child1 = createChild(container, {w = 70, h = 25}) - local child2 = createChild(container, {w = 70, h = 25}) - local child3 = createChild(container, {w = 70, h = 25}) -- Should wrap due to padding + local child1 = createChild(container, { w = 70, h = 25 }) + local child2 = createChild(container, { w = 70, h = 25 }) + local child3 = createChild(container, { w = 70, h = 25 }) -- Should wrap due to padding local positions = layoutAndGetPositions(container) @@ -328,10 +355,10 @@ function TestFlexWrap09_WrapWithPadding() -- Two children fit: 70 + 10 + 70 = 150 < 170 luaunit.assertEquals(positions[1].x, 15) -- child1 x (padding.left) luaunit.assertEquals(positions[1].y, 15) -- child1 y (padding.top) - + luaunit.assertEquals(positions[2].x, 95) -- child2 x (15 + 70 + 10) luaunit.assertEquals(positions[2].y, 15) -- child2 y (padding.top) - + -- Third child should wrap luaunit.assertEquals(positions[3].x, 15) -- child3 x (padding.left) luaunit.assertEquals(positions[3].y, 50) -- child3 y (15 + 25 + 10) @@ -340,20 +367,23 @@ end -- Test Case 10: WRAP with SPACE_AROUND align content function TestFlexWrap10_WrapWithSpaceAroundAlignContent() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.SPACE_AROUND, - gap = 10 + gap = 10, }) -- Create children that will wrap into two lines - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) -- This should wrap + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) @@ -370,16 +400,19 @@ end -- Test Case 11: Single child with WRAP (should behave like NOWRAP) function TestFlexWrap11_SingleChildWrap() local container = createContainer({ - x = 0, y = 0, w = 100, h = 100, + x = 0, + y = 0, + w = 100, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, - gap = 10 + gap = 10, }) - local child1 = createChild(container, {w = 50, h = 30}) + local child1 = createChild(container, { w = 50, h = 30 }) local positions = layoutAndGetPositions(container) @@ -391,33 +424,36 @@ end -- Test Case 12: Multiple wrapping lines function TestFlexWrap12_MultipleWrappingLines() local container = createContainer({ - x = 0, y = 0, w = 200, h = 200, + x = 0, + y = 0, + w = 200, + h = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap into three lines - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) - local child4 = createChild(container, {w = 80, h = 30}) - local child5 = createChild(container, {w = 80, h = 30}) + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) + local child4 = createChild(container, { w = 80, h = 30 }) + local child5 = createChild(container, { w = 80, h = 30 }) local positions = layoutAndGetPositions(container) -- First line luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].y, 0) -- child2 y - + -- Second line luaunit.assertEquals(positions[3].y, 40) -- child3 y (30 + 10) luaunit.assertEquals(positions[4].y, 40) -- child4 y - + -- Third line luaunit.assertEquals(positions[5].y, 80) -- child5 y (40 + 30 + 10) end @@ -425,31 +461,34 @@ end -- Test Case 13: WRAP_REVERSE with multiple lines function TestFlexWrap13_WrapReverseMultipleLines() local container = createContainer({ - x = 0, y = 0, w = 200, h = 150, + x = 0, + y = 0, + w = 200, + h = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP_REVERSE, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create children that will wrap into three lines - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) - local child3 = createChild(container, {w = 80, h = 30}) - local child4 = createChild(container, {w = 80, h = 30}) - local child5 = createChild(container, {w = 80, h = 30}) + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) + local child3 = createChild(container, { w = 80, h = 30 }) + local child4 = createChild(container, { w = 80, h = 30 }) + local child5 = createChild(container, { w = 80, h = 30 }) local positions = layoutAndGetPositions(container) -- With wrap-reverse, lines are reversed: Line 3, Line 2, Line 1 luaunit.assertEquals(positions[5].y, 0) -- child5 y - third line comes first - + luaunit.assertEquals(positions[3].y, 40) -- child3 y - second line in middle luaunit.assertEquals(positions[4].y, 40) -- child4 y - + luaunit.assertEquals(positions[1].y, 80) -- child1 y - first line comes last luaunit.assertEquals(positions[2].y, 80) -- child2 y end @@ -457,25 +496,28 @@ end -- Test Case 14: Edge case - container too small for any children function TestFlexWrap14_ContainerTooSmall() local container = createContainer({ - x = 0, y = 0, w = 50, h = 50, + x = 0, + y = 0, + w = 50, + h = 50, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, - gap = 10 + gap = 10, }) -- Create children larger than container - local child1 = createChild(container, {w = 80, h = 30}) - local child2 = createChild(container, {w = 80, h = 30}) + local child1 = createChild(container, { w = 80, h = 30 }) + local child2 = createChild(container, { w = 80, h = 30 }) local positions = layoutAndGetPositions(container) -- Each child should be on its own line since none fit luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y - + luaunit.assertEquals(positions[2].x, 0) -- child2 x luaunit.assertEquals(positions[2].y, 40) -- child2 y (30 + 10) end @@ -483,21 +525,24 @@ end -- Test Case 15: WRAP with mixed positioning children function TestFlexWrap15_WrapWithMixedPositioning() local container = createContainer({ - x = 0, y = 0, w = 200, h = 100, + x = 0, + y = 0, + w = 200, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, - gap = 10 + gap = 10, }) -- Create flex children and one absolute child - local child1 = createChild(container, {w = 80, h = 30}) -- flex child - local child2 = createChild(container, {w = 80, h = 30, positioning = Positioning.ABSOLUTE, x = 150, y = 50}) -- absolute child - local child3 = createChild(container, {w = 80, h = 30}) -- flex child - local child4 = createChild(container, {w = 80, h = 30}) -- flex child - should wrap + local child1 = createChild(container, { w = 80, h = 30 }) -- flex child + local child2 = createChild(container, { w = 80, h = 30, positioning = Positioning.ABSOLUTE, x = 150, y = 50 }) -- absolute child + local child3 = createChild(container, { w = 80, h = 30 }) -- flex child + local child4 = createChild(container, { w = 80, h = 30 }) -- flex child - should wrap local positions = layoutAndGetPositions(container) @@ -509,6 +554,1116 @@ function TestFlexWrap15_WrapWithMixedPositioning() luaunit.assertEquals(positions[4].y, 40) -- child4 y - wrapped to second line end --- Run the tests -print("=== Running FlexWrap Tests ===") -luaunit.LuaUnit.run() \ No newline at end of file +-- =================================== +-- COMPLEX NESTED STRUCTURE TESTS +-- =================================== + +-- Test Case 16: Complex Card Grid Layout with Dynamic Wrapping +function TestFlexWrap16_ComplexCardGridLayout() + -- Main container: card grid that wraps cards + local gridContainer = createContainer({ + x = 0, + y = 0, + w = 600, + h = 400, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Create multiple cards that will wrap + for i = 1, 6 do + local card = createChild(gridContainer, { + w = 160, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + padding = { top = 12, right = 12, bottom = 12, left = 12 }, + }) + + -- Card header + local header = createChild(card, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + -- Header title and icon + createChild(header, { w = 80, h = 16 }) -- title + createChild(header, { w = 16, h = 16 }) -- icon + + -- Card content area + local content = createChild(card, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + -- Content elements + createChild(content, { w = 40, h = 20 }) -- main content + createChild(content, { w = 60, h = 12 }) -- description + + -- Card footer with action buttons + local footer = createChild(card, { + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 6, + }) + + createChild(footer, { w = 24, h = 16 }) -- button 1 + createChild(footer, { w = 24, h = 16 }) -- button 2 + end + + local positions = layoutAndGetPositions(gridContainer) + + -- Verify wrapping behavior: 3 cards per row (160*3 + 20*2 + 20*2 = 560 < 600) + -- First row cards + luaunit.assertTrue(positions[1].x == 20) -- card 1 x + luaunit.assertTrue(positions[1].y == 20) -- card 1 y + luaunit.assertTrue(positions[2].y == 20) -- card 2 y (same row) + luaunit.assertTrue(positions[3].y == 20) -- card 3 y (same row) + + -- Second row cards + luaunit.assertTrue(positions[4].y == 160) -- card 4 y (20 + 120 + 20 gap) + luaunit.assertTrue(positions[5].y == 160) -- card 5 y (same row) + luaunit.assertTrue(positions[6].y == 160) -- card 6 y (same row) + + -- Verify nested layout within first card + local card1 = gridContainer.children[1] + card1:layoutChildren() + + -- Check card header layout + local header = card1.children[1] + luaunit.assertTrue(header.children[1].x == 32) -- title x (card.x + padding) + luaunit.assertTrue(header.children[2].x == 168) -- icon x (right aligned) + + -- Check card content layout + local content = card1.children[2] + luaunit.assertTrue(content.children[1].x == 100) -- main content centered + luaunit.assertTrue(content.children[2].x == 90) -- description centered + + -- Check card footer layout + local footer = card1.children[3] + luaunit.assertTrue(footer.children[1].x == 122) -- button 1 (right aligned) + luaunit.assertTrue(footer.children[2].x == 152) -- button 2 (right aligned) +end + +-- Test Case 17: Complex Image Gallery with Responsive Wrapping +function TestFlexWrap17_ComplexImageGalleryLayout() + -- Gallery container with wrapping images + local gallery = createContainer({ + x = 0, + y = 0, + w = 800, + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 15, + padding = { top = 30, right = 30, bottom = 30, left = 30 }, + }) + + -- Create gallery items with different layouts + for i = 1, 8 do + local item = createChild(gallery, { + w = 180, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 10, + padding = { top = 10, right = 10, bottom = 10, left = 10 }, + }) + + -- Image area + createChild(item, { h = 140 }) -- image placeholder + + -- Caption area + local caption = createChild(item, { + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + gap = 4, + }) + + createChild(caption, { w = 120, h = 16 }) -- title + createChild(caption, { w = 80, h = 12 }) -- metadata + + -- Action bar + local actions = createChild(item, { + h = 18, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + -- Left actions + local leftActions = createChild(actions, { + w = 60, + h = 18, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + createChild(leftActions, { w = 16, h = 16 }) -- like button + createChild(leftActions, { w = 16, h = 16 }) -- share button + + -- Right actions + createChild(actions, { w = 16, h = 16 }) -- options menu + end + + local positions = layoutAndGetPositions(gallery) + + -- Available width: 800 - 60 (padding) = 740 + -- Items per row: 180*4 + 15*3 = 765 > 740, so 3 items per row: 180*3 + 15*2 = 570 < 740 + + -- First row + luaunit.assertTrue(positions[1].y == 30) -- item 1 y + luaunit.assertTrue(positions[2].y == 30) -- item 2 y + luaunit.assertTrue(positions[3].y == 30) -- item 3 y + + -- Second row + luaunit.assertTrue(positions[4].y == 245) -- item 4 y (30 + 200 + 15) + luaunit.assertTrue(positions[5].y == 245) -- item 5 y + luaunit.assertTrue(positions[6].y == 245) -- item 6 y + + -- Third row + luaunit.assertTrue(positions[7].y == 460) -- item 7 y (245 + 200 + 15) + luaunit.assertTrue(positions[8].y == 460) -- item 8 y + + -- Verify nested layout in first gallery item + local item1 = gallery.children[1] + item1:layoutChildren() + + local caption = item1.children[2] + luaunit.assertTrue(caption.children[1].x == 40) -- title x (item.x + padding) + luaunit.assertTrue(caption.children[2].x == 40) -- metadata x + + local actions = item1.children[3] + luaunit.assertTrue(actions.children[2].x == 184) -- options menu (right aligned) +end + +-- Test Case 18: Complex Dashboard Widget Layout with Mixed Wrapping +function TestFlexWrap18_ComplexDashboardLayout() + -- Main dashboard container + local dashboard = createContainer({ + x = 0, + y = 0, + w = 1000, + h = 700, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Top metrics row (horizontal wrapping) + local metricsRow = createChild(dashboard, { + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + alignContent = AlignContent.FLEX_START, + gap = 15, + }) + + -- Create metric cards + for i = 1, 5 do + local metric = createChild(metricsRow, { + w = 180, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + gap = 8, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + -- Metric header + local header = createChild(metric, { + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(header, { w = 100, h = 14 }) -- title + createChild(header, { w = 16, h = 16 }) -- icon + + -- Metric value + createChild(metric, { w = 80, h = 24 }) -- value + + -- Metric trend + local trend = createChild(metric, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + createChild(trend, { w = 12, h = 12 }) -- trend icon + createChild(trend, { w = 40, h = 12 }) -- trend text + end + + -- Content area with wrapping widgets + local contentArea = createChild(dashboard, { + h = 500, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 20, + }) + + -- Large chart widget + local chartWidget = createChild(contentArea, { + w = 600, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Chart header + local chartHeader = createChild(chartWidget, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 12, + }) + + createChild(chartHeader, { w = 150, h = 20 }) -- chart title + createChild(chartHeader, { w = 80, h = 24 }) -- chart controls + + -- Chart area + createChild(chartWidget, { h = 200 }) -- chart content + + -- Chart legend + local legend = createChild(chartWidget, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 16, + }) + + for i = 1, 4 do + local legendItem = createChild(legend, { + w = 80, + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 6, + }) + + createChild(legendItem, { w = 12, h = 12 }) -- color indicator + createChild(legendItem, { w = 50, h = 12 }) -- legend text + end + + -- Side panel with stacked widgets + local sidePanel = createChild(contentArea, { + w = 320, + h = 500, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 15, + }) + + -- Recent activity widget + local activityWidget = createChild(sidePanel, { + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(activityWidget, { h = 20 }) -- activity header + + -- Activity list + local activityList = createChild(activityWidget, { + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 6, + }) + + for i = 1, 5 do + local activityItem = createChild(activityList, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(activityItem, { w = 200, h = 16 }) -- activity text + createChild(activityItem, { w = 60, h = 12 }) -- timestamp + end + + -- Quick actions widget + local actionsWidget = createChild(sidePanel, { + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 10, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(actionsWidget, { h = 20 }) -- actions header + + -- Action buttons grid + local actionsGrid = createChild(actionsWidget, { + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 8, + }) + + for i = 1, 6 do + createChild(actionsGrid, { w = 80, h = 40 }) -- action button + end + + local positions = layoutAndGetPositions(dashboard) + + -- Verify main dashboard layout + luaunit.assertTrue(positions[1].y == 20) -- metrics row y + luaunit.assertTrue(positions[2].y == 160) -- content area y (20 + 120 + 20) + + -- Verify metrics row wrapping (5 cards, ~180px each) + metricsRow:layoutChildren() + local metricPositions = {} + for i, child in ipairs(metricsRow.children) do + metricPositions[i] = { x = child.x, y = child.y } + end + + -- Should fit 5 cards in one row (180*5 + 15*4 = 960 < 960 available) + luaunit.assertTrue(metricPositions[1].y == 40) -- all cards same y + luaunit.assertTrue(metricPositions[2].y == 40) + luaunit.assertTrue(metricPositions[3].y == 40) + luaunit.assertTrue(metricPositions[4].y == 40) + luaunit.assertTrue(metricPositions[5].y == 40) + + -- Verify content area layout + contentArea:layoutChildren() + local chartWidget = contentArea.children[1] + local sidePanel = contentArea.children[2] + + luaunit.assertTrue(chartWidget.x == 20) -- chart widget x + luaunit.assertTrue(sidePanel.x == 640) -- side panel x (20 + 600 + 20) + + -- Verify nested chart legend wrapping + chartWidget:layoutChildren() + local legend = chartWidget.children[3] + legend:layoutChildren() + + -- Legend should wrap 4 items: 2 per row (80*2 + 16*1 = 176 < 560 available) + luaunit.assertTrue(legend.children[1].y == legend.children[2].y) -- first row + luaunit.assertTrue(legend.children[3].y == legend.children[4].y) -- second row + luaunit.assertTrue(legend.children[1].y ~= legend.children[3].y) -- different rows + + -- Verify side panel actions grid wrapping + sidePanel:layoutChildren() + local actionsWidget = sidePanel.children[2] + actionsWidget:layoutChildren() + local actionsGrid = actionsWidget.children[2] + actionsGrid:layoutChildren() + + -- Actions grid should wrap 6 buttons: 3 per row (80*3 + 8*2 = 256 < 288 available) + luaunit.assertTrue(actionsGrid.children[1].y == actionsGrid.children[2].y) -- first row + luaunit.assertTrue(actionsGrid.children[2].y == actionsGrid.children[3].y) -- first row + luaunit.assertTrue(actionsGrid.children[4].y == actionsGrid.children[5].y) -- second row + luaunit.assertTrue(actionsGrid.children[5].y == actionsGrid.children[6].y) -- second row + luaunit.assertTrue(actionsGrid.children[1].y ~= actionsGrid.children[4].y) -- different rows +end + +-- Test Case 19: Complex Form Layout with Wrapping Field Groups +function TestFlexWrap19_ComplexFormLayout() + -- Main form container + local form = createContainer({ + x = 0, + y = 0, + w = 800, + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 24, + padding = { top = 32, right = 32, bottom = 32, left = 32 }, + }) + + -- Form header + local header = createChild(form, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(header, { w = 200, h = 28 }) -- form title + createChild(header, { w = 300, h = 16 }) -- form description + + -- Personal info section with wrapping fields + local personalSection = createChild(form, { + h = 150, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + + createChild(personalSection, { w = 150, h = 20 }) -- section title + + local personalFields = createChild(personalSection, { + h = 110, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 20, + }) + + -- Create field groups that will wrap + for i = 1, 6 do + local fieldGroup = createChild(personalFields, { + w = 220, + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(fieldGroup, { h = 16 }) -- field label + createChild(fieldGroup, { h = 36 }) -- input field + createChild(fieldGroup, { h = 12 }) -- help text + end + + -- Address section with complex nested wrapping + local addressSection = createChild(form, { + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + + createChild(addressSection, { w = 120, h = 20 }) -- section title + + local addressContainer = createChild(addressSection, { + h = 160, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + }) + + -- Primary address row + local primaryAddress = createChild(addressContainer, { + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 16, + }) + + -- Street address (full width) + local streetField = createChild(primaryAddress, { + w = 704, + h = 70, -- full width minus gaps + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(streetField, { h = 16 }) -- street label + createChild(streetField, { h = 36 }) -- street input + createChild(streetField, { h = 12 }) -- street help + + -- Secondary address row with multiple fields + local secondaryAddress = createChild(addressContainer, { + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 16, + }) + + -- City field + local cityField = createChild(secondaryAddress, { + w = 280, + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(cityField, { h = 16 }) -- city label + createChild(cityField, { h = 36 }) -- city input + createChild(cityField, { h = 12 }) -- city help + + -- State field + local stateField = createChild(secondaryAddress, { + w = 200, + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(stateField, { h = 16 }) -- state label + createChild(stateField, { h = 36 }) -- state input + createChild(stateField, { h = 12 }) -- state help + + -- ZIP field + local zipField = createChild(secondaryAddress, { + w = 180, + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(zipField, { h = 16 }) -- zip label + createChild(zipField, { h = 36 }) -- zip input + createChild(zipField, { h = 12 }) -- zip help + + -- Preferences section with wrapping checkboxes + local preferencesSection = createChild(form, { + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + + createChild(preferencesSection, { w = 140, h = 20 }) -- section title + + local preferencesGrid = createChild(preferencesSection, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 24, + }) + + -- Create preference checkboxes that wrap + for i = 1, 8 do + local preference = createChild(preferencesGrid, { + w = 160, + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(preference, { w = 16, h = 16 }) -- checkbox + createChild(preference, { w = 120, h = 16 }) -- checkbox label + end + + -- Form actions + local actions = createChild(form, { + h = 48, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 16, + }) + + createChild(actions, { w = 80, h = 36 }) -- cancel button + createChild(actions, { w = 100, h = 36 }) -- submit button + + local positions = layoutAndGetPositions(form) + + -- Verify main form sections layout + luaunit.assertTrue(positions[1].y == 32) -- header y + luaunit.assertTrue(positions[2].y == 116) -- personal section y (32 + 60 + 24) + luaunit.assertTrue(positions[3].y == 290) -- address section y (116 + 150 + 24) + luaunit.assertTrue(positions[4].y == 514) -- preferences section y (290 + 200 + 24) + luaunit.assertTrue(positions[5].y == 658) -- actions y (514 + 120 + 24) + + -- Verify personal fields wrapping (220*3 + 20*2 = 700 < 736 available, so 3 per row) + personalSection:layoutChildren() + local personalFields = personalSection.children[2] + personalFields:layoutChildren() + + -- First row: fields 1, 2, 3 + luaunit.assertTrue(personalFields.children[1].y == personalFields.children[2].y) + luaunit.assertTrue(personalFields.children[2].y == personalFields.children[3].y) + + -- Second row: fields 4, 5, 6 + luaunit.assertTrue(personalFields.children[4].y == personalFields.children[5].y) + luaunit.assertTrue(personalFields.children[5].y == personalFields.children[6].y) + + -- Different rows + luaunit.assertTrue(personalFields.children[1].y ~= personalFields.children[4].y) + + -- Verify address section layout + addressSection:layoutChildren() + local addressContainer = addressSection.children[2] + addressContainer:layoutChildren() + + -- Primary address (street) should be full width + local primaryAddress = addressContainer.children[1] + primaryAddress:layoutChildren() + luaunit.assertTrue(primaryAddress.children[1].width == 704) -- street field full width + + -- Secondary address fields should be on same row + local secondaryAddress = addressContainer.children[2] + secondaryAddress:layoutChildren() + luaunit.assertTrue(secondaryAddress.children[1].y == secondaryAddress.children[2].y) -- city and state same row + luaunit.assertTrue(secondaryAddress.children[2].y == secondaryAddress.children[3].y) -- state and zip same row + + -- Verify preferences grid wrapping (160*4 + 24*3 = 712 < 736, so 4 per row) + preferencesSection:layoutChildren() + local preferencesGrid = preferencesSection.children[2] + preferencesGrid:layoutChildren() + + -- First row: preferences 1-4 + luaunit.assertTrue(preferencesGrid.children[1].y == preferencesGrid.children[2].y) + luaunit.assertTrue(preferencesGrid.children[2].y == preferencesGrid.children[3].y) + luaunit.assertTrue(preferencesGrid.children[3].y == preferencesGrid.children[4].y) + + -- Second row: preferences 5-8 + luaunit.assertTrue(preferencesGrid.children[5].y == preferencesGrid.children[6].y) + luaunit.assertTrue(preferencesGrid.children[6].y == preferencesGrid.children[7].y) + luaunit.assertTrue(preferencesGrid.children[7].y == preferencesGrid.children[8].y) + + -- Different rows + luaunit.assertTrue(preferencesGrid.children[1].y ~= preferencesGrid.children[5].y) +end + +-- Test Case 20: Complex Product Catalog with Advanced Wrapping and Filtering +function TestFlexWrap20_ComplexProductCatalog() + -- Main catalog container + local catalog = createContainer({ + x = 0, + y = 0, + w = 1200, + h = 800, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + }) + + -- Catalog header with filters + local header = createChild(catalog, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Left header: title and breadcrumbs + local leftHeader = createChild(header, { + w = 400, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + gap = 8, + }) + + createChild(leftHeader, { w = 200, h = 24 }) -- page title + + local breadcrumbs = createChild(leftHeader, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 8, + }) + + for i = 1, 5 do + createChild(breadcrumbs, { w = 60, h = 14 }) -- breadcrumb item + if i < 5 then + createChild(breadcrumbs, { w = 8, h = 8 }) -- separator + end + end + + -- Right header: filters and controls + local rightHeader = createChild(header, { + w = 700, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 12, + }) + + -- Filter chips + for i = 1, 6 do + local filterChip = createChild(rightHeader, { + w = 80, + h = 28, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 6, + padding = { top = 4, right = 8, bottom = 4, left = 8 }, + }) + + createChild(filterChip, { w = 50, h = 12 }) -- filter text + createChild(filterChip, { w = 12, h = 12 }) -- close button + end + + -- Sort dropdown + createChild(rightHeader, { w = 120, h = 32 }) -- sort control + + -- View toggle + local viewToggle = createChild(rightHeader, { + w = 80, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + createChild(viewToggle, { w = 24, h = 24 }) -- grid view button + createChild(viewToggle, { w = 24, h = 24 }) -- list view button + + -- Product grid with sophisticated wrapping + local productGrid = createContainer({ + x = 0, + y = 100, + w = 1200, + h = 680, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Create product cards with varying layouts + for i = 1, 15 do + local product = createChild(productGrid, { + w = 220, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + -- Product image with overlay + local imageContainer = createChild(product, { + h = 160, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.COLUMN, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + }) + + createChild(imageContainer, { h = 140 }) -- product image + + -- Image overlay with quick actions + local overlay = createChild(imageContainer, { + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + createChild(overlay, { w = 16, h = 16 }) -- favorite button + createChild(overlay, { w = 16, h = 16 }) -- quick view button + + -- Product info + local info = createChild(product, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 6, + }) + + createChild(info, { w = 160, h = 16 }) -- product title + createChild(info, { w = 120, h = 12 }) -- product brand + + -- Rating and reviews + local rating = createChild(info, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + -- Star rating + local stars = createChild(rating, { + w = 80, + h = 14, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 2, + }) + + for j = 1, 5 do + createChild(stars, { w = 12, h = 12 }) -- star icon + end + + createChild(rating, { w = 40, h = 12 }) -- review count + + -- Price and variants + local pricing = createChild(info, { + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(pricing, { w = 60, h = 18 }) -- price + + local variants = createChild(pricing, { + w = 80, + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 3, + }) + + for j = 1, 4 do + createChild(variants, { w = 16, h = 16 }) -- color variant + end + + -- Product actions + local actions = createChild(product, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(actions, { w = 100, h = 28 }) -- add to cart button + createChild(actions, { w = 28, h = 28 }) -- wishlist button + end + + local positions = layoutAndGetPositions(catalog) + + -- Verify main catalog layout + luaunit.assertTrue(positions[1].y == 0) -- header y + luaunit.assertTrue(positions[2].y == 100) -- product grid y + + -- Verify header layout + header:layoutChildren() + local leftHeader = header.children[1] + local rightHeader = header.children[2] + + luaunit.assertTrue(leftHeader.x == 20) -- left header x + luaunit.assertTrue(rightHeader.x == 460) -- right header x (20 + 400 + 20 + 20) + + -- Verify breadcrumbs wrapping + leftHeader:layoutChildren() + local breadcrumbs = leftHeader.children[2] + breadcrumbs:layoutChildren() + + -- All breadcrumb items should fit on one line + luaunit.assertTrue(breadcrumbs.children[1].y == breadcrumbs.children[3].y) -- first and second breadcrumb same y + luaunit.assertTrue(breadcrumbs.children[5].y == breadcrumbs.children[7].y) -- third and fourth breadcrumb same y + + -- Verify filter chips wrapping in right header + rightHeader:layoutChildren() + local filterPositions = {} + for i = 1, 6 do + filterPositions[i] = { x = rightHeader.children[i].x, y = rightHeader.children[i].y } + end + + -- Filters should wrap based on available space (700px wide) + -- 6 filters * 80px + 5 gaps * 12px = 540px < 700px, so all on one line + luaunit.assertTrue(filterPositions[1].y == filterPositions[2].y) + luaunit.assertTrue(filterPositions[2].y == filterPositions[3].y) + luaunit.assertTrue(filterPositions[3].y == filterPositions[4].y) + luaunit.assertTrue(filterPositions[4].y == filterPositions[5].y) + luaunit.assertTrue(filterPositions[5].y == filterPositions[6].y) + + -- Verify product grid wrapping + productGrid:layoutChildren() + local productPositions = {} + for i = 1, 15 do + productPositions[i] = { x = productGrid.children[i].x, y = productGrid.children[i].y } + end + + -- Available width: 1200 - 40 (padding) = 1160 + -- Products per row: 220*5 + 20*4 = 1180 > 1160, so 4 per row: 220*4 + 20*3 = 940 < 1160 + + -- First row: products 1-4 + luaunit.assertTrue(productPositions[1].y == productPositions[2].y) + luaunit.assertTrue(productPositions[2].y == productPositions[3].y) + luaunit.assertTrue(productPositions[3].y == productPositions[4].y) + + -- Second row: products 5-8 + luaunit.assertTrue(productPositions[5].y == productPositions[6].y) + luaunit.assertTrue(productPositions[6].y == productPositions[7].y) + luaunit.assertTrue(productPositions[7].y == productPositions[8].y) + + -- Third row: products 9-12 + luaunit.assertTrue(productPositions[9].y == productPositions[10].y) + luaunit.assertTrue(productPositions[10].y == productPositions[11].y) + luaunit.assertTrue(productPositions[11].y == productPositions[12].y) + + -- Fourth row: products 13-15 + luaunit.assertTrue(productPositions[13].y == productPositions[14].y) + luaunit.assertTrue(productPositions[14].y == productPositions[15].y) + + -- Different rows + luaunit.assertTrue(productPositions[1].y ~= productPositions[5].y) + luaunit.assertTrue(productPositions[5].y ~= productPositions[9].y) + luaunit.assertTrue(productPositions[9].y ~= productPositions[13].y) + + -- Verify nested product card layouts + local product1 = productGrid.children[1] + product1:layoutChildren() + + -- Verify product rating stars layout + local info = product1.children[2] + info:layoutChildren() + local rating = info.children[3] + rating:layoutChildren() + local stars = rating.children[1] + stars:layoutChildren() + + -- All 5 stars should be on one line + luaunit.assertTrue(stars.children[1].y == stars.children[2].y) + luaunit.assertTrue(stars.children[2].y == stars.children[3].y) + luaunit.assertTrue(stars.children[3].y == stars.children[4].y) + luaunit.assertTrue(stars.children[4].y == stars.children[5].y) + + -- Verify color variants wrapping + local pricing = info.children[4] + pricing:layoutChildren() + local variants = pricing.children[2] + variants:layoutChildren() + + -- 4 variants should fit: 16*4 + 3*3 = 73 < 80px available + luaunit.assertTrue(variants.children[1].y == variants.children[2].y) + luaunit.assertTrue(variants.children[2].y == variants.children[3].y) + luaunit.assertTrue(variants.children[3].y == variants.children[4].y) +end + +luaunit.LuaUnit.run() diff --git a/testing/__tests__/08_comprehensive_flex_tests.lua b/testing/__tests__/08_comprehensive_flex_tests.lua index 46df821..38936a0 100644 --- a/testing/__tests__/08_comprehensive_flex_tests.lua +++ b/testing/__tests__/08_comprehensive_flex_tests.lua @@ -50,7 +50,10 @@ end -- Test 1: Complex row layout with wrap, spacing, and alignment function TestComprehensiveFlex:testComplexRowLayoutWithWrapAndAlignment() local container = createContainer({ - x = 0, y = 0, w = 150, h = 120, + x = 0, + y = 0, + w = 150, + h = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, @@ -62,19 +65,23 @@ function TestComprehensiveFlex:testComplexRowLayoutWithWrapAndAlignment() -- Add children that will wrap to second line local child1 = createChild(container, { - w = 40, h = 30, + w = 40, + h = 30, }) local child2 = createChild(container, { - w = 40, h = 30, + w = 40, + h = 30, }) local child3 = createChild(container, { - w = 40, h = 30, + w = 40, + h = 30, }) local child4 = createChild(container, { - w = 40, h = 30, + w = 40, + h = 30, }) local positions = layoutAndGetPositions(container) @@ -98,7 +105,10 @@ end -- Test 2: Complex column layout with nested flex containers function TestComprehensiveFlex:testNestedFlexContainersComplexLayout() local outerContainer = createContainer({ - x = 0, y = 0, w = 180, h = 160, + x = 0, + y = 0, + w = 180, + h = 160, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_AROUND, @@ -108,7 +118,8 @@ function TestComprehensiveFlex:testNestedFlexContainersComplexLayout() -- Inner container 1 - horizontal flex local innerContainer1 = createChild(outerContainer, { - w = 140, h = 50, + w = 140, + h = 50, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.CENTER, @@ -118,7 +129,8 @@ function TestComprehensiveFlex:testNestedFlexContainersComplexLayout() -- Inner container 2 - horizontal flex with wrap local innerContainer2 = createChild(outerContainer, { - w = 140, h = 50, + w = 140, + h = 50, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, @@ -130,20 +142,24 @@ function TestComprehensiveFlex:testNestedFlexContainersComplexLayout() -- Add children to inner container 1 local item1 = createChild(innerContainer1, { - w = 30, h = 20, + w = 30, + h = 20, }) local item2 = createChild(innerContainer1, { - w = 30, h = 35, + w = 30, + h = 35, }) -- Add children to inner container 2 local item3 = createChild(innerContainer2, { - w = 40, h = 25, + w = 40, + h = 25, }) local item4 = createChild(innerContainer2, { - w = 40, h = 25, + w = 40, + h = 25, }) local outerPositions = layoutAndGetPositions(outerContainer) @@ -180,7 +196,10 @@ end -- Test 3: All flex properties combined with absolute positioning function TestComprehensiveFlex:testFlexWithAbsolutePositioning() local container = createContainer({ - x = 0, y = 0, w = 160, h = 100, + x = 0, + y = 0, + w = 160, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_EVENLY, @@ -192,22 +211,27 @@ function TestComprehensiveFlex:testFlexWithAbsolutePositioning() -- Regular flex children local flexChild1 = createChild(container, { - w = 30, h = 20, + w = 30, + h = 20, }) local flexChild2 = createChild(container, { - w = 30, h = 20, + w = 30, + h = 20, }) -- Absolute positioned child (should not affect flex layout) local absChild = createChild(container, { positioning = Positioning.ABSOLUTE, - x = 10, y = 10, - w = 20, h = 15, + x = 10, + y = 10, + w = 20, + h = 15, }) local flexChild3 = createChild(container, { - w = 30, h = 20, + w = 30, + h = 20, }) local positions = layoutAndGetPositions(container) @@ -232,7 +256,10 @@ end -- Test 4: Complex wrapping layout with mixed alignments function TestComprehensiveFlex:testComplexWrappingWithMixedAlignments() local container = createContainer({ - x = 0, y = 0, w = 120, h = 150, + x = 0, + y = 0, + w = 120, + h = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_AROUND, @@ -245,7 +272,8 @@ function TestComprehensiveFlex:testComplexWrappingWithMixedAlignments() -- Add 5 children that will wrap into multiple lines for i = 1, 5 do createChild(container, { - w = 35, h = 25, + w = 35, + h = 25, }) end @@ -277,7 +305,10 @@ end -- Test 5: Deeply nested flex containers with various properties function TestComprehensiveFlex:testDeeplyNestedFlexContainers() local level1 = createContainer({ - x = 0, y = 0, w = 200, h = 150, + x = 0, + y = 0, + w = 200, + h = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, @@ -286,7 +317,8 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() }) local level2 = createChild(level1, { - w = 160, h = 100, + w = 160, + h = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, @@ -295,7 +327,8 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() }) local level3a = createChild(level2, { - w = 70, h = 80, + w = 70, + h = 80, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_END, @@ -304,7 +337,8 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() }) local level3b = createChild(level2, { - w = 70, h = 80, + w = 70, + h = 80, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, @@ -314,15 +348,18 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() -- Add leaf elements local leafA1 = createChild(level3a, { - w = 30, h = 20, + w = 30, + h = 20, }) local leafA2 = createChild(level3a, { - w = 25, h = 15, + w = 25, + h = 15, }) local leafB1 = createChild(level3b, { - w = 35, h = 18, + w = 35, + h = 18, }) local level1Positions = layoutAndGetPositions(level1) @@ -352,7 +389,7 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() -- Level 2 stretches and space-between for level 3 containers -- These positions are relative to level 1 container position luaunit.assertEquals(level2Positions[1].x, 20) -- positioned by level 1 - luaunit.assertEquals(level2Positions[1].y, 25) -- positioned by level 1 + luaunit.assertEquals(level2Positions[1].y, 25) -- positioned by level 1 luaunit.assertEquals(level2Positions[1].height, 100) -- stretched to full cross-axis height luaunit.assertEquals(level2Positions[2].x, 110) -- positioned by level 1 + space-between @@ -364,7 +401,7 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() luaunit.assertEquals(level3aPositions[1].x, 40) -- absolute position luaunit.assertEquals(level3aPositions[1].y, 90) -- flex-end: positioned at bottom of stretched container - luaunit.assertEquals(level3aPositions[2].x, 42.5) -- absolute position + luaunit.assertEquals(level3aPositions[2].x, 42.5) -- absolute position luaunit.assertEquals(level3aPositions[2].y, 110) -- second item: 90 + 20 = 110 -- Level 3b: flex-start justification, flex-end alignment @@ -373,8 +410,1304 @@ function TestComprehensiveFlex:testDeeplyNestedFlexContainers() luaunit.assertEquals(level3bPositions[1].y, 25) -- actual absolute position end --- Run the tests -print("=== Running Comprehensive Flex Tests ===") -luaunit.LuaUnit.run() +-- =================================== +-- COMPLEX COMPREHENSIVE STRUCTURE TESTS +-- =================================== -return TestComprehensiveFlex \ No newline at end of file +-- Test 6: Complex Application Layout - Complete UI Structure +function TestComprehensiveFlex:testComplexApplicationLayout() + -- Main application container + local app = createContainer({ + x = 0, + y = 0, + w = 1200, + h = 800, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Top navigation bar + local navbar = createChild(app, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + padding = { top = 10, right = 20, bottom = 10, left = 20 }, + }) + + -- Left section of navbar + local navLeft = createChild(navbar, { + w = 300, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 16, + }) + + createChild(navLeft, { w = 120, h = 28 }) -- logo + createChild(navLeft, { w = 80, h = 24 }) -- home link + createChild(navLeft, { w = 80, h = 24 }) -- products link + + -- Center section of navbar with search + local navCenter = createChild(navbar, { + w = 400, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(navCenter, { w = 300, h = 32 }) -- search input + createChild(navCenter, { w = 32, h = 32 }) -- search button + + -- Right section of navbar + local navRight = createChild(navbar, { + w = 200, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 12, + }) + + createChild(navRight, { w = 32, h = 32 }) -- notifications + createChild(navRight, { w = 32, h = 32 }) -- cart + createChild(navRight, { w = 80, h = 32 }) -- user menu + + -- Main content area + local mainContent = createChild(app, { + h = 740, -- 800 - 60 navbar + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Left sidebar + local sidebar = createChild(mainContent, { + w = 250, + h = 740, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 0, bottom = 20, left = 20 }, + }) + + -- Sidebar navigation + local sideNav = createChild(sidebar, { + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(sideNav, { h = 24 }) -- nav title + + -- Navigation items with nested structure + for i = 1, 8 do + local navItem = createChild(sideNav, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + padding = { top = 4, right = 8, bottom = 4, left = 8 }, + }) + + local navItemLeft = createChild(navItem, { + w = 150, + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(navItemLeft, { w = 16, h = 16 }) -- icon + createChild(navItemLeft, { w = 100, h = 16 }) -- label + + if i <= 3 then -- some items have badges + createChild(navItem, { w = 20, h = 16 }) -- badge + end + end + + -- Sidebar widget area + local sideWidget = createChild(sidebar, { + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(sideWidget, { h = 20 }) -- widget title + + local widgetContent = createChild(sideWidget, { + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + for i = 1, 4 do + local widgetItem = createChild(widgetContent, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(widgetItem, { w = 120, h = 16 }) -- widget text + createChild(widgetItem, { w = 40, h = 12 }) -- widget value + end + + createChild(sideWidget, { h = 32 }) -- widget action button + + -- Main content panel + local contentPanel = createChild(mainContent, { + w = 950, + h = 740, -- 1200 - 250 sidebar + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Content header with breadcrumbs and actions + local contentHeader = createChild(contentPanel, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Breadcrumbs and title section + local headerLeft = createChild(contentHeader, { + w = 500, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + gap = 8, + }) + + -- Breadcrumbs + local breadcrumbs = createChild(headerLeft, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 6, + }) + + for i = 1, 4 do + createChild(breadcrumbs, { w = 60, h = 14 }) -- breadcrumb + if i < 4 then + createChild(breadcrumbs, { w = 8, h = 8 }) -- separator + end + end + + createChild(headerLeft, { w = 200, h = 24 }) -- page title + + -- Action buttons section + local headerRight = createChild(contentHeader, { + w = 300, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 12, + }) + + createChild(headerRight, { w = 80, h = 32 }) -- filter button + createChild(headerRight, { w = 80, h = 32 }) -- sort button + createChild(headerRight, { w = 100, h = 32 }) -- primary action + + -- Main content area with complex layouts + local contentMain = createChild(contentPanel, { + h = 660, -- 740 - 80 header + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 0, right = 20, bottom = 20, left = 20 }, + }) + + -- Content grid area + local contentGrid = createChild(contentMain, { + w = 600, + h = 640, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + }) + + -- Grid header with filters + local gridHeader = createChild(contentGrid, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 12, + }) + + -- Active filters + local activeFilters = createChild(gridHeader, { + w = 350, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 8, + }) + + for i = 1, 4 do + local filterChip = createChild(activeFilters, { + w = 70, + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 4, + padding = { top = 2, right = 6, bottom = 2, left = 6 }, + }) + + createChild(filterChip, { w = 40, h = 12 }) -- filter text + createChild(filterChip, { w = 12, h = 12 }) -- close button + end + + -- Grid controls + local gridControls = createChild(gridHeader, { + w = 150, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(gridControls, { w = 60, h = 28 }) -- view toggle + createChild(gridControls, { w = 60, h = 28 }) -- sort dropdown + + -- Item grid + local itemGrid = createChild(contentGrid, { + h = 560, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 20, + }) + + -- Create grid items + for i = 1, 6 do + local gridItem = createChild(itemGrid, { + w = 180, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(gridItem, { h = 120 }) -- item image + + local itemInfo = createChild(gridItem, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 4, + }) + + createChild(itemInfo, { w = 140, h = 16 }) -- item title + createChild(itemInfo, { w = 100, h = 12 }) -- item description + + local itemMeta = createChild(itemInfo, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(itemMeta, { w = 60, h = 14 }) -- price + createChild(itemMeta, { w = 40, h = 14 }) -- rating + + local itemActions = createChild(gridItem, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(itemActions, { w = 100, h = 28 }) -- primary action + createChild(itemActions, { w = 28, h = 28 }) -- secondary action + end + + -- Right detail panel + local detailPanel = createChild(contentMain, { + w = 290, + h = 640, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 0, bottom = 0, left = 0 }, + }) + + -- Detail header + local detailHeader = createChild(detailPanel, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(detailHeader, { h = 24 }) -- detail title + createChild(detailHeader, { h = 16 }) -- detail subtitle + + -- Detail content with complex nested structure + local detailContent = createChild(detailPanel, { + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + + -- Detail sections + for i = 1, 3 do + local detailSection = createChild(detailContent, { + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + padding = { top = 12, right = 12, bottom = 12, left = 12 }, + }) + + createChild(detailSection, { h = 18 }) -- section title + + local sectionContent = createChild(detailSection, { + h = 90, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 6, + }) + + for j = 1, 4 do + local contentRow = createChild(sectionContent, { + h = 18, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(contentRow, { w = 120, h = 14 }) -- row label + createChild(contentRow, { w = 80, h = 14 }) -- row value + end + end + + -- Detail actions + local detailActions = createChild(detailPanel, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + }) + + createChild(detailActions, { h = 32 }) -- primary action + createChild(detailActions, { h = 28 }) -- secondary action + + -- Layout and test positions + local appPositions = layoutAndGetPositions(app) + + -- Test main app structure + luaunit.assertEquals(appPositions[1].y, 0) -- navbar + luaunit.assertEquals(appPositions[2].y, 60) -- main content + + -- Test navbar layout + navbar:layoutChildren() + local navPositions = {} + for i, child in ipairs(navbar.children) do + navPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(navPositions[1].x, 20) -- nav left at padding + luaunit.assertEquals(navPositions[2].x, 400) -- nav center positioned + luaunit.assertEquals(navPositions[3].x, 980) -- nav right aligned (1200 - 20 - 200) + + -- Test main content layout + mainContent:layoutChildren() + local mainPositions = {} + for i, child in ipairs(mainContent.children) do + mainPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(mainPositions[1].x, 0) -- sidebar + luaunit.assertEquals(mainPositions[2].x, 250) -- content panel + + -- Test complex nested structures + contentPanel:layoutChildren() + local contentPanelPositions = {} + for i, child in ipairs(contentPanel.children) do + contentPanelPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(contentPanelPositions[1].y, 60) -- content header + luaunit.assertEquals(contentPanelPositions[2].y, 140) -- content main (60 + 80) + + -- Test content main layout + contentMain:layoutChildren() + local contentMainPositions = {} + for i, child in ipairs(contentMain.children) do + contentMainPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(contentMainPositions[1].x, 270) -- content grid (250 + 20) + luaunit.assertEquals(contentMainPositions[2].x, 890) -- detail panel (270 + 600 + 20) + + -- Test item grid wrapping + contentGrid:layoutChildren() + local gridHeader = contentGrid.children[1] + local itemGrid = contentGrid.children[2] + + itemGrid:layoutChildren() + local itemPositions = {} + for i, child in ipairs(itemGrid.children) do + itemPositions[i] = { x = child.x, y = child.y } + end + + -- Items should wrap: 180*3 + 20*2 = 580 < 600, so 3 per row + luaunit.assertEquals(itemPositions[1].y, itemPositions[2].y) -- row 1 + luaunit.assertEquals(itemPositions[2].y, itemPositions[3].y) -- row 1 + luaunit.assertEquals(itemPositions[4].y, itemPositions[5].y) -- row 2 + luaunit.assertEquals(itemPositions[5].y, itemPositions[6].y) -- row 2 + luaunit.assertTrue(itemPositions[1].y ~= itemPositions[4].y) -- different rows +end + +-- Test 7: Complex Dashboard with Multiple Panels and Real-time Data Layout +function TestComprehensiveFlex:testComplexDashboardLayout() + -- Main dashboard container + local dashboard = createContainer({ + x = 0, + y = 0, + w = 1400, + h = 900, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Dashboard header with complex controls + local dashHeader = createChild(dashboard, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + padding = { top = 16, right = 24, bottom = 16, left = 24 }, + }) + + -- Header left: title and time range + local headerLeft = createChild(dashHeader, { + w = 400, + h = 48, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 20, + }) + + createChild(headerLeft, { w = 200, h = 32 }) -- dashboard title + + local timeRange = createChild(headerLeft, { + w = 160, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + padding = { top = 4, right = 8, bottom = 4, left = 8 }, + }) + + createChild(timeRange, { w = 100, h = 16 }) -- time range text + createChild(timeRange, { w = 16, h = 16 }) -- dropdown arrow + + -- Header center: key metrics + local headerCenter = createChild(dashHeader, { + w = 600, + h = 48, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 24, + }) + + -- Quick metrics + for i = 1, 4 do + local metric = createChild(headerCenter, { + w = 120, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 4, + }) + + createChild(metric, { w = 60, h = 16 }) -- metric value + createChild(metric, { w = 80, h = 12 }) -- metric label + end + + -- Header right: actions and settings + local headerRight = createChild(dashHeader, { + w = 280, + h = 48, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 12, + }) + + createChild(headerRight, { w = 36, h = 36 }) -- refresh button + createChild(headerRight, { w = 36, h = 36 }) -- fullscreen button + createChild(headerRight, { w = 100, h = 36 }) -- export button + createChild(headerRight, { w = 36, h = 36 }) -- settings button + + -- Main dashboard content + local dashContent = createChild(dashboard, { + h = 820, -- 900 - 80 header + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Left sidebar with navigation and filters + local dashSidebar = createChild(dashContent, { + w = 280, + h = 820, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 0, bottom = 20, left = 20 }, + }) + + -- Sidebar navigation + local sidebarNav = createChild(dashSidebar, { + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + }) + + createChild(sidebarNav, { h = 24 }) -- nav title + + -- Navigation groups + for i = 1, 3 do + local navGroup = createChild(sidebarNav, { + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 4, + }) + + createChild(navGroup, { h = 20 }) -- group title + + for j = 1, 3 do + local navItem = createChild(navGroup, { + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + padding = { top = 2, right = 8, bottom = 2, left = 16 }, + }) + + createChild(navItem, { w = 160, h = 14 }) -- nav label + if j == 1 then + createChild(navItem, { w = 20, h = 12 }) -- active indicator + end + end + end + + -- Sidebar filters + local sidebarFilters = createChild(dashSidebar, { + h = 250, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + padding = { top = 16, right = 16, bottom = 16, left = 0 }, + }) + + createChild(sidebarFilters, { h = 24 }) -- filters title + + -- Filter groups + for i = 1, 3 do + local filterGroup = createChild(sidebarFilters, { + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + createChild(filterGroup, { h = 16 }) -- filter group title + + local filterOptions = createChild(filterGroup, { + h = 36, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 4, + }) + + for j = 1, 2 do + local filterOption = createChild(filterOptions, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(filterOption, { w = 16, h = 12 }) -- checkbox + createChild(filterOption, { w = 120, h = 12 }) -- option label + end + end + + -- Sidebar recent activity + local sidebarActivity = createChild(dashSidebar, { + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 0, left = 0 }, + }) + + createChild(sidebarActivity, { h = 20 }) -- activity title + + local activityList = createChild(sidebarActivity, { + h = 160, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + for i = 1, 6 do + local activityItem = createChild(activityList, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + local activityLeft = createChild(activityItem, { + w = 160, + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 6, + }) + + createChild(activityLeft, { w = 12, h = 12 }) -- status dot + createChild(activityLeft, { w = 120, h = 12 }) -- activity text + + createChild(activityItem, { w = 40, h = 10 }) -- timestamp + end + + -- Main content panels area + local dashMain = createChild(dashContent, { + w = 1120, + h = 820, -- 1400 - 280 sidebar + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Top metrics row + local topMetrics = createChild(dashMain, { + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + alignContent = AlignContent.FLEX_START, + gap = 20, + }) + + -- Large metric cards + for i = 1, 4 do + local metricCard = createChild(topMetrics, { + w = 250, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + -- Card header + local cardHeader = createChild(metricCard, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(cardHeader, { w = 120, h = 16 }) -- metric title + createChild(cardHeader, { w = 20, h = 16 }) -- trend icon + + -- Metric value and change + local cardValue = createChild(metricCard, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_END, + gap = 8, + }) + + createChild(cardValue, { w = 100, h = 28 }) -- main value + createChild(cardValue, { w = 60, h = 16 }) -- change percentage + + -- Mini chart area + createChild(metricCard, { h = 24 }) -- mini chart + end + + -- Middle content row with charts + local middleContent = createChild(dashMain, { + h = 320, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 20, + }) + + -- Large chart panel + local chartPanel = createChild(middleContent, { + w = 680, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 16, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + -- Chart header with controls + local chartHeader = createChild(chartPanel, { + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 16, + }) + + createChild(chartHeader, { w = 200, h = 24 }) -- chart title + + local chartControls = createChild(chartHeader, { + w = 200, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 8, + }) + + createChild(chartControls, { w = 60, h = 24 }) -- time filter + createChild(chartControls, { w = 60, h = 24 }) -- chart type + createChild(chartControls, { w = 24, h = 24 }) -- options menu + + -- Chart area + createChild(chartPanel, { h = 200 }) -- main chart + + -- Chart legend + local chartLegend = createChild(chartPanel, { + h = 28, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + alignContent = AlignContent.CENTER, + gap = 20, + }) + + for i = 1, 5 do + local legendItem = createChild(chartLegend, { + w = 80, + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 6, + }) + + createChild(legendItem, { w = 12, h = 12 }) -- color indicator + createChild(legendItem, { w = 50, h = 12 }) -- legend text + end + + -- Side stats panel + local statsPanel = createChild(middleContent, { + w = 360, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + createChild(statsPanel, { h = 24 }) -- stats title + + -- Stats grid + local statsGrid = createChild(statsPanel, { + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + alignContent = AlignContent.FLEX_START, + gap = 12, + }) + + -- Stats items in 2x4 grid + for i = 1, 8 do + local statItem = createChild(statsGrid, { + w = 150, + h = 50, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.FLEX_START, + gap = 4, + padding = { top = 8, right = 8, bottom = 8, left = 8 }, + }) + + createChild(statItem, { w = 100, h = 16 }) -- stat label + createChild(statItem, { w = 80, h = 20 }) -- stat value + end + + -- Bottom content row with tables and lists + local bottomContent = createChild(dashMain, { + h = 260, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 20, + }) + + -- Data table panel + local tablePanel = createChild(bottomContent, { + w = 540, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + -- Table header + local tableHeader = createChild(tablePanel, { + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 12, + }) + + createChild(tableHeader, { w = 150, h = 20 }) -- table title + + local tableControls = createChild(tableHeader, { + w = 120, + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(tableControls, { w = 80, h = 20 }) -- search box + createChild(tableControls, { w = 20, h = 20 }) -- filter button + + -- Table content + local tableContent = createChild(tablePanel, { + h = 180, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 4, + }) + + -- Table header row + local tableHeaderRow = createChild(tableContent, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + for i = 1, 4 do + createChild(tableHeaderRow, { w = 100, h = 16 }) -- column header + end + + -- Table data rows + for i = 1, 6 do + local tableRow = createChild(tableContent, { + h = 24, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + for j = 1, 4 do + createChild(tableRow, { w = 100, h = 14 }) -- table cell + end + end + + -- Right panels (split) + local rightPanels = createChild(bottomContent, { + w = 500, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 20, + }) + + -- Alerts panel + local alertsPanel = createChild(rightPanels, { + w = 240, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(alertsPanel, { h = 20 }) -- alerts title + + local alertsList = createChild(alertsPanel, { + h = 192, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + + for i = 1, 6 do + local alertItem = createChild(alertsList, { + h = 28, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + padding = { top = 4, right = 4, bottom = 4, left = 4 }, + }) + + local alertLeft = createChild(alertItem, { + w = 160, + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 6, + }) + + createChild(alertLeft, { w = 12, h = 12 }) -- alert icon + createChild(alertLeft, { w = 120, h = 12 }) -- alert text + + createChild(alertItem, { w = 16, h = 16 }) -- dismiss button + end + + -- Progress panel + local progressPanel = createChild(rightPanels, { + w = 240, + h = 240, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + padding = { top = 16, right = 16, bottom = 16, left = 16 }, + }) + + createChild(progressPanel, { h = 20 }) -- progress title + + local progressList = createChild(progressPanel, { + h = 192, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + + for i = 1, 4 do + local progressItem = createChild(progressList, { + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 6, + }) + + local progressHeader = createChild(progressItem, { + h = 16, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + + createChild(progressHeader, { w = 120, h = 12 }) -- progress label + createChild(progressHeader, { w = 40, h = 12 }) -- progress percentage + + createChild(progressItem, { h = 8 }) -- progress bar + end + + -- Layout and test positions + local dashPositions = layoutAndGetPositions(dashboard) + + -- Test main dashboard structure + luaunit.assertEquals(dashPositions[1].y, 0) -- dashboard header + luaunit.assertEquals(dashPositions[2].y, 80) -- dashboard content + + -- Test dashboard header layout + dashHeader:layoutChildren() + local headerPositions = {} + for i, child in ipairs(dashHeader.children) do + headerPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(headerPositions[1].x, 24) -- header left + luaunit.assertEquals(headerPositions[2].x, 400) -- header center (centered) + luaunit.assertEquals(headerPositions[3].x, 1096) -- header right (1400 - 24 - 280) + + -- Test dashboard content layout + dashContent:layoutChildren() + local contentPositions = {} + for i, child in ipairs(dashContent.children) do + contentPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(contentPositions[1].x, 0) -- sidebar + luaunit.assertEquals(contentPositions[2].x, 280) -- main content + + -- Test top metrics wrapping + dashMain:layoutChildren() + local topMetrics = dashMain.children[1] + topMetrics:layoutChildren() + + local metricPositions = {} + for i, child in ipairs(topMetrics.children) do + metricPositions[i] = { x = child.x, y = child.y } + end + + -- 4 metrics should fit in one row: 250*4 + 20*3 = 1060 < 1080 available + luaunit.assertEquals(metricPositions[1].y, metricPositions[2].y) -- same row + luaunit.assertEquals(metricPositions[2].y, metricPositions[3].y) -- same row + luaunit.assertEquals(metricPositions[3].y, metricPositions[4].y) -- same row + + -- Test middle content layout + local middleContent = dashMain.children[2] + middleContent:layoutChildren() + + local middlePositions = {} + for i, child in ipairs(middleContent.children) do + middlePositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(middlePositions[1].x, 300) -- chart panel (300 + 20 padding) + luaunit.assertEquals(middlePositions[2].x, 1000) -- stats panel (300 + 680 + 20) + + -- Test chart legend wrapping + local chartPanel = middleContent.children[1] + chartPanel:layoutChildren() + local chartLegend = chartPanel.children[3] + chartLegend:layoutChildren() + + -- 5 legend items should fit: 80*5 + 20*4 = 480 < 640 available + luaunit.assertEquals(chartLegend.children[1].y, chartLegend.children[2].y) -- same row + luaunit.assertEquals(chartLegend.children[2].y, chartLegend.children[3].y) -- same row + luaunit.assertEquals(chartLegend.children[3].y, chartLegend.children[4].y) -- same row + luaunit.assertEquals(chartLegend.children[4].y, chartLegend.children[5].y) -- same row + + -- Test stats grid wrapping + local statsPanel = middleContent.children[2] + statsPanel:layoutChildren() + local statsGrid = statsPanel.children[2] + statsGrid:layoutChildren() + + local statsPositions = {} + for i, child in ipairs(statsGrid.children) do + statsPositions[i] = { x = child.x, y = child.y } + end + + -- 8 stats in 2 columns: 150*2 + 12*1 = 312 < 320 available + luaunit.assertEquals(statsPositions[1].y, statsPositions[2].y) -- row 1 + luaunit.assertEquals(statsPositions[3].y, statsPositions[4].y) -- row 2 + luaunit.assertEquals(statsPositions[5].y, statsPositions[6].y) -- row 3 + luaunit.assertEquals(statsPositions[7].y, statsPositions[8].y) -- row 4 + luaunit.assertTrue(statsPositions[1].y ~= statsPositions[3].y) -- different rows + + -- Test bottom content layout + local bottomContent = dashMain.children[3] + bottomContent:layoutChildren() + + local bottomPositions = {} + for i, child in ipairs(bottomContent.children) do + bottomPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(bottomPositions[1].x, 300) -- table panel + luaunit.assertEquals(bottomPositions[2].x, 860) -- right panels (300 + 540 + 20) + + -- Test right panels layout + local rightPanels = bottomContent.children[2] + rightPanels:layoutChildren() + + local rightPositions = {} + for i, child in ipairs(rightPanels.children) do + rightPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } + end + + luaunit.assertEquals(rightPositions[1].x, 860) -- alerts panel + luaunit.assertEquals(rightPositions[2].x, 1120) -- progress panel (860 + 240 + 20) +end + +luaunit.LuaUnit.run() diff --git a/testing/__tests__/09_layout_validation_tests.lua b/testing/__tests__/09_layout_validation_tests.lua index 17db3bb..c85b9da 100644 --- a/testing/__tests__/09_layout_validation_tests.lua +++ b/testing/__tests__/09_layout_validation_tests.lua @@ -45,12 +45,12 @@ local function createTestContainer(props) flexWrap = FlexWrap.NOWRAP, gap = 0, } - + -- Merge props with defaults for key, value in pairs(props) do defaults[key] = value end - + return Gui.new(defaults) end @@ -62,21 +62,21 @@ function TestLayoutValidation:testInvalidColorHexStrings() end) luaunit.assertFalse(success) luaunit.assertTrue(string.find(error_msg, "Invalid hex string") ~= nil) - + -- Test wrong length hex string local success2, error_msg2 = captureError(function() Color.fromHex("#ABC") end) luaunit.assertFalse(success2) luaunit.assertTrue(string.find(error_msg2, "Invalid hex string") ~= nil) - + -- Test valid hex strings (should not error) local success3, color3 = captureError(function() return Color.fromHex("#FF0000") end) luaunit.assertTrue(success3) luaunit.assertIsTable(color3) - + local success4, color4 = captureError(function() return Color.fromHex("#FF0000AA") end) @@ -99,7 +99,7 @@ function TestLayoutValidation:testInvalidEnumValuesGracefulDegradation() end) luaunit.assertTrue(success) -- Should not crash luaunit.assertEquals(container.flexDirection, FlexDirection.HORIZONTAL) -- Should use default - + -- Test with invalid justifyContent local success2, container2 = captureError(function() return Gui.new({ @@ -127,11 +127,11 @@ function TestLayoutValidation:testMissingRequiredPropertiesDefaults() luaunit.assertIsNumber(element.width) luaunit.assertIsNumber(element.height) luaunit.assertEquals(element.positioning, Positioning.ABSOLUTE) -- Default positioning - + -- Test flex container with minimal properties local success2, flex_element = captureError(function() return Gui.new({ - positioning = Positioning.FLEX -- Only positioning specified + positioning = Positioning.FLEX, -- Only positioning specified }) end) luaunit.assertTrue(success2) -- Should not crash @@ -157,7 +157,7 @@ function TestLayoutValidation:testInvalidPropertyCombinations() luaunit.assertTrue(success) -- Should not crash luaunit.assertEquals(absolute_element.positioning, Positioning.ABSOLUTE) -- Note: FlexLove might still store these properties even for absolute elements - + -- Test flex element can have both flex and position properties local success2, flex_element = captureError(function() return Gui.new({ @@ -212,7 +212,7 @@ end -- Test 7: Invalid Child-Parent Relationships function TestLayoutValidation:testInvalidChildParentRelationships() local parent = createTestContainer() - + -- Test adding child with conflicting positioning local success, child = captureError(function() local child = Gui.new({ @@ -235,32 +235,32 @@ end -- Test 8: Layout After Property Changes function TestLayoutValidation:testLayoutAfterPropertyChanges() local container = createTestContainer() - + local child1 = Gui.new({ w = 50, h = 30, }) child1.parent = container table.insert(container.children, child1) - + local child2 = Gui.new({ w = 60, h = 35, }) child2.parent = container table.insert(container.children, child2) - + -- Change container properties and verify layout still works local success = captureError(function() container.flexDirection = FlexDirection.VERTICAL container:layoutChildren() end) luaunit.assertTrue(success) -- Should not crash - + -- Verify positions changed appropriately local new_pos1 = { x = child1.x, y = child1.y } local new_pos2 = { x = child2.x, y = child2.y } - + -- In vertical layout, child2 should be below child1 luaunit.assertTrue(new_pos2.y >= new_pos1.y) -- child2 should be at or below child1 end @@ -292,7 +292,7 @@ function TestLayoutValidation:testComplexNestedValidation() h = 150, positioning = Positioning.FLEX, }) - + local flex_child = Gui.new({ w = 100, h = 75, @@ -301,7 +301,7 @@ function TestLayoutValidation:testComplexNestedValidation() }) flex_child.parent = root table.insert(root.children, flex_child) - + local absolute_grandchild = Gui.new({ x = 10, y = 10, @@ -311,7 +311,7 @@ function TestLayoutValidation:testComplexNestedValidation() }) absolute_grandchild.parent = flex_child table.insert(flex_child.children, absolute_grandchild) - + local flex_grandchild = Gui.new({ w = 40, h = 25, @@ -319,27 +319,928 @@ function TestLayoutValidation:testComplexNestedValidation() }) flex_grandchild.parent = flex_child table.insert(flex_child.children, flex_grandchild) - + return root end) - + luaunit.assertTrue(success) -- Should not crash luaunit.assertEquals(#root.children, 1) luaunit.assertEquals(#root.children[1].children, 2) - + -- Verify positioning was handled correctly local flex_child = root.children[1] luaunit.assertEquals(flex_child.positioning, Positioning.FLEX) - + local absolute_grandchild = flex_child.children[1] local flex_grandchild = flex_child.children[2] - + luaunit.assertEquals(absolute_grandchild.positioning, Positioning.ABSOLUTE) -- flex_grandchild positioning depends on FlexLove's behavior end --- Run the tests -print("=== Running Layout Validation Tests ===") -luaunit.LuaUnit.run() +-- =================================== +-- COMPLEX VALIDATION STRUCTURE TESTS +-- =================================== -return TestLayoutValidation \ No newline at end of file +-- Test 11: Complex Multi-Level Layout Validation +function TestLayoutValidation:testComplexMultiLevelLayoutValidation() + -- Test complex application-like structure with validation at every level + local success, app = captureError(function() + -- Main app container + local app = Gui.new({ + x = 0, + y = 0, + w = 1200, + h = 800, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Header with complex validation scenarios + local header = Gui.new({ + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + padding = { top = 10, right = 20, bottom = 10, left = 20 }, + }) + header.parent = app + table.insert(app.children, header) + + -- Header navigation with potential edge cases + local nav = Gui.new({ + w = 400, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + gap = 16, + }) + nav.parent = header + table.insert(header.children, nav) + + -- Create nav items with extreme values + for i = 1, 5 do + local navItem = Gui.new({ + w = i == 3 and 0 or 80, -- One item with zero width + h = i == 4 and -10 or 24, -- One item with negative height + positioning = i == 5 and Positioning.ABSOLUTE or nil, -- Mixed positioning + }) + navItem.parent = nav + table.insert(nav.children, navItem) + end + + -- Header actions with validation edge cases + local actions = Gui.new({ + w = 200, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + gap = 12, + }) + actions.parent = header + table.insert(header.children, actions) + + -- Actions with extreme dimensions + for i = 1, 3 do + local action = Gui.new({ + w = i == 1 and 999999 or 32, -- Extremely large width + h = i == 2 and 0.1 or 32, -- Fractional height + x = i == 3 and -1000 or nil, -- Extreme negative position + y = i == 3 and -1000 or nil, + }) + action.parent = actions + table.insert(actions.children, action) + end + + -- Main content with nested validation challenges + local main = Gui.new({ + h = 740, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + main.parent = app + table.insert(app.children, main) + + -- Sidebar with deep nesting and edge cases + local sidebar = Gui.new({ + w = 250, + h = 740, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 0, bottom = 20, left = 20 }, + }) + sidebar.parent = main + table.insert(main.children, sidebar) + + -- Sidebar sections with validation challenges + for section = 1, 3 do + local sideSection = Gui.new({ + h = section == 2 and -100 or 200, -- Negative height test + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 8, + }) + sideSection.parent = sidebar + table.insert(sidebar.children, sideSection) + + -- Section items with extreme properties + for item = 1, 4 do + local sectionItem = Gui.new({ + h = 24, + w = item == 2 and 0 or nil, -- Zero width test + positioning = item == 4 and Positioning.ABSOLUTE or nil, + x = item == 4 and 50 or nil, + y = item == 4 and 50 or nil, + gap = item == 3 and -5 or 0, -- Negative gap test + }) + sectionItem.parent = sideSection + table.insert(sideSection.children, sectionItem) + + -- Nested items for deep validation + if item <= 2 then + for nested = 1, 2 do + local nestedItem = Gui.new({ + w = nested == 1 and 999999 or 20, -- Extreme width + h = 12, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + nestedItem.parent = sectionItem + table.insert(sectionItem.children, nestedItem) + end + end + end + end + + -- Content area with complex validation scenarios + local content = Gui.new({ + w = 950, + h = 740, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + content.parent = main + table.insert(main.children, content) + + -- Content grid with wrapping and validation challenges + local contentGrid = Gui.new({ + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + contentGrid.parent = content + table.insert(content.children, contentGrid) + + -- Grid items with validation edge cases + for i = 1, 12 do + local gridItem = Gui.new({ + w = i % 4 == 0 and 0 or 200, -- Some zero width items + h = i % 3 == 0 and -50 or 150, -- Some negative height items + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = i % 5 == 0 and -10 or 12, -- Some negative gaps + }) + gridItem.parent = contentGrid + table.insert(contentGrid.children, gridItem) + + -- Grid item content with extreme values + for j = 1, 3 do + local itemContent = Gui.new({ + h = j == 1 and 999999 or 40, -- Extreme height + w = j == 2 and -100 or nil, -- Negative width + positioning = j == 3 and Positioning.ABSOLUTE or nil, + x = j == 3 and -500 or nil, -- Extreme negative position + y = j == 3 and 1000000 or nil, -- Extreme positive position + }) + itemContent.parent = gridItem + table.insert(gridItem.children, itemContent) + end + end + + -- Footer with final validation tests + local footer = Gui.new({ + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + alignItems = AlignItems.CENTER, + gap = 0, + padding = { top = 999999, right = -100, bottom = 0, left = 50 }, -- Extreme padding + }) + footer.parent = content + table.insert(content.children, footer) + + -- Footer sections with final edge cases + for i = 1, 4 do + local footerSection = Gui.new({ + w = i == 1 and 0 or 200, + h = i == 2 and -1000 or 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = i == 4 and 999999 or 8, + }) + footerSection.parent = footer + table.insert(footer.children, footerSection) + end + + return app + end) + + -- Should not crash despite extreme values + luaunit.assertTrue(success) + luaunit.assertIsTable(app) + luaunit.assertEquals(#app.children, 2) -- header and main + + -- Test layout calculation with extreme values + local layoutSuccess = captureError(function() + app:layoutChildren() + end) + luaunit.assertTrue(layoutSuccess) -- Layout should not crash + + -- Verify structure integrity after layout + luaunit.assertEquals(app.positioning, Positioning.FLEX) + luaunit.assertEquals(#app.children, 2) + luaunit.assertEquals(#app.children[1].children, 2) -- header nav and actions + luaunit.assertEquals(#app.children[2].children, 2) -- sidebar and content + + -- Test that extreme values are preserved but handled gracefully + local nav = app.children[1].children[1] + luaunit.assertEquals(#nav.children, 5) -- All nav items created + + local actions = app.children[1].children[2] + luaunit.assertEquals(actions.children[1].width, 999999) -- Extreme width preserved + + local sidebar = app.children[2].children[1] + luaunit.assertEquals(#sidebar.children, 3) -- All sidebar sections created + + local content = app.children[2].children[2] + luaunit.assertEquals(#content.children, 2) -- contentGrid and footer + + local contentGrid = content.children[1] + luaunit.assertEquals(#contentGrid.children, 12) -- All grid items created +end + +-- Test 12: Validation of Dynamic Property Changes in Complex Layouts +function TestLayoutValidation:testComplexDynamicPropertyValidation() + local success, result = captureError(function() + -- Create complex dashboard layout + local dashboard = Gui.new({ + x = 0, + y = 0, + w = 1000, + h = 600, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 0, + }) + + -- Metrics row that will be modified + local metricsRow = Gui.new({ + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + metricsRow.parent = dashboard + table.insert(dashboard.children, metricsRow) + + -- Create initial metrics + local metrics = {} + for i = 1, 6 do + local metric = Gui.new({ + w = 150, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + metric.parent = metricsRow + table.insert(metricsRow.children, metric) + metrics[i] = metric + + -- Metric content + for j = 1, 3 do + local content = Gui.new({ + w = 100, + h = 20, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + content.parent = metric + table.insert(metric.children, content) + end + end + + -- Content area that will receive dynamic changes + local contentArea = Gui.new({ + h = 480, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 20, + padding = { top = 0, right = 20, bottom = 20, left = 20 }, + }) + contentArea.parent = dashboard + table.insert(dashboard.children, contentArea) + + -- Left panel for modifications + local leftPanel = Gui.new({ + w = 300, + h = 460, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 16, + }) + leftPanel.parent = contentArea + table.insert(contentArea.children, leftPanel) + + -- Right panel with nested complexity + local rightPanel = Gui.new({ + w = 640, + h = 460, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 12, + }) + rightPanel.parent = contentArea + table.insert(contentArea.children, rightPanel) + + -- Create nested content for validation testing + for i = 1, 3 do + local section = Gui.new({ + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + gap = 12, + padding = { top = 12, right = 12, bottom = 12, left = 12 }, + }) + section.parent = rightPanel + table.insert(rightPanel.children, section) + + -- Section items for modification testing + for j = 1, 8 do + local item = Gui.new({ + w = 80, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + gap = 4, + }) + item.parent = section + table.insert(section.children, item) + end + end + + -- Initial layout + dashboard:layoutChildren() + + -- Test 1: Change flexDirection on main container + dashboard.flexDirection = FlexDirection.HORIZONTAL + dashboard:layoutChildren() -- Should not crash + + -- Test 2: Modify metrics with extreme values + metrics[1].width = 0 + metrics[2].width = -100 + metrics[3].width = 999999 + metrics[4].height = 0 + metrics[5].height = -200 + metrics[6].height = 1000000 + dashboard:layoutChildren() -- Should not crash + + -- Test 3: Change flex wrap and justify properties + metricsRow.flexWrap = FlexWrap.WRAP_REVERSE + metricsRow.justifyContent = JustifyContent.CENTER + metricsRow.alignItems = AlignItems.FLEX_END + dashboard:layoutChildren() -- Should not crash + + -- Test 4: Modify gap values with extremes + metricsRow.gap = -50 + contentArea.gap = 999999 + dashboard:layoutChildren() -- Should not crash + + -- Test 5: Change positioning types dynamically + leftPanel.positioning = Positioning.ABSOLUTE + leftPanel.x = -500 + leftPanel.y = 1000 + dashboard:layoutChildren() -- Should not crash + + -- Test 6: Modify padding with extreme values + rightPanel.padding = { top = -100, right = 999999, bottom = 0, left = -50 } + dashboard:layoutChildren() -- Should not crash + + -- Test 7: Change nested item properties + local firstSection = rightPanel.children[1] + firstSection.flexDirection = FlexDirection.VERTICAL + firstSection.flexWrap = FlexWrap.WRAP_REVERSE + firstSection.justifyContent = JustifyContent.SPACE_EVENLY + dashboard:layoutChildren() -- Should not crash + + -- Test 8: Modify individual items with extreme values + local items = firstSection.children + for i = 1, #items do + items[i].width = i % 2 == 0 and 0 or 999999 + items[i].height = i % 3 == 0 and -100 or 200 + end + dashboard:layoutChildren() -- Should not crash + + -- Test 9: Add/remove children dynamically + local newMetric = Gui.new({ + w = 0, + h = -50, + positioning = Positioning.ABSOLUTE, + x = -1000, + y = -1000, + }) + newMetric.parent = metricsRow + table.insert(metricsRow.children, newMetric) + dashboard:layoutChildren() -- Should not crash + + -- Test 10: Remove children + table.remove(metricsRow.children, 1) + if metricsRow.children[1] then + metricsRow.children[1].parent = nil + end + dashboard:layoutChildren() -- Should not crash + + return { + dashboard = dashboard, + metricsRow = metricsRow, + contentArea = contentArea, + leftPanel = leftPanel, + rightPanel = rightPanel, + finalChildCount = #metricsRow.children, + } + end) + + luaunit.assertTrue(success) -- Should not crash during any modifications + luaunit.assertIsTable(result) + luaunit.assertIsTable(result.dashboard) + + -- Verify structure integrity after all modifications + luaunit.assertEquals(result.dashboard.flexDirection, FlexDirection.HORIZONTAL) + luaunit.assertEquals(result.metricsRow.flexWrap, FlexWrap.WRAP_REVERSE) + luaunit.assertEquals(result.leftPanel.positioning, Positioning.ABSOLUTE) + luaunit.assertEquals(result.finalChildCount, 6) -- 7 added - 1 removed = 6 remaining + + -- Test final layout one more time + local finalLayoutSuccess = captureError(function() + result.dashboard:layoutChildren() + end) + luaunit.assertTrue(finalLayoutSuccess) +end + +-- Test 13: Validation of Circular Reference Prevention +function TestLayoutValidation:testCircularReferenceValidation() + local success, result = captureError(function() + -- Create containers that could form circular references + local container1 = Gui.new({ + x = 0, + y = 0, + w = 200, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + }) + + local container2 = Gui.new({ + w = 180, + h = 180, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + }) + + local container3 = Gui.new({ + w = 160, + h = 160, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + }) + + -- Establish normal parent-child relationship + container2.parent = container1 + table.insert(container1.children, container2) + + container3.parent = container2 + table.insert(container2.children, container3) + + -- Test layout works normally + container1:layoutChildren() + + -- Attempt to create circular reference (should be prevented or handled) + -- Note: FlexLove should handle this gracefully or the test framework should catch it + + -- Test case 1: Try to make parent a child of its own child + local attemptSuccess1 = captureError(function() + container1.parent = container3 + table.insert(container3.children, container1) + container1:layoutChildren() -- This should either work or fail gracefully + end) + + -- Clean up potential circular reference + container1.parent = nil + if container3.children and #container3.children > 0 then + for i = #container3.children, 1, -1 do + if container3.children[i] == container1 then + table.remove(container3.children, i) + end + end + end + + -- Test case 2: Complex nested structure with potential circular refs + local container4 = Gui.new({ + w = 140, + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + }) + + local container5 = Gui.new({ + w = 120, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + }) + + -- Normal nesting first + container4.parent = container3 + table.insert(container3.children, container4) + + container5.parent = container4 + table.insert(container4.children, container5) + + -- Test normal layout + container1:layoutChildren() + + -- Try to create deeper circular reference + local attemptSuccess2 = captureError(function() + container2.parent = container5 + table.insert(container5.children, container2) + container1:layoutChildren() + end) + + return { + container1 = container1, + container2 = container2, + container3 = container3, + container4 = container4, + container5 = container5, + attempt1 = attemptSuccess1, + attempt2 = attemptSuccess2, + } + end) + + luaunit.assertTrue(success) -- Should not crash the test framework + luaunit.assertIsTable(result) + + -- Verify basic structure is maintained + luaunit.assertIsTable(result.container1) + luaunit.assertIsTable(result.container2) + luaunit.assertIsTable(result.container3) + + -- Test that final layout still works + local finalLayoutSuccess = captureError(function() + result.container1:layoutChildren() + end) + luaunit.assertTrue(finalLayoutSuccess) +end + +-- Test 14: Memory and Performance Validation with Large Structures +function TestLayoutValidation:testLargeStructureValidation() + local success, result = captureError(function() + -- Create a large, complex structure to test memory handling + local root = Gui.new({ + x = 0, + y = 0, + w = 2000, + h = 1500, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = 5, + }) + + local itemCount = 0 + local maxDepth = 5 + local itemsPerLevel = 10 + + -- Recursive function to create deep, wide structure + local function createLevel(parent, depth, items) + if depth >= maxDepth then + return + end + + for i = 1, items do + local container = Gui.new({ + w = depth == 1 and 400 or 100, + h = depth == 1 and 200 or 50, + positioning = Positioning.FLEX, + flexDirection = i % 2 == 0 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL, + flexWrap = i % 3 == 0 and FlexWrap.WRAP or FlexWrap.NOWRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.STRETCH, + gap = depth, + }) + container.parent = parent + table.insert(parent.children, container) + itemCount = itemCount + 1 + + -- Add leaf elements to some containers + if depth >= 3 then + for j = 1, 3 do + local leaf = Gui.new({ + w = 20 + j * 5, + h = 15 + j * 3, + positioning = j == 3 and Positioning.ABSOLUTE or nil, + x = j == 3 and j * 10 or nil, + y = j == 3 and j * 10 or nil, + }) + leaf.parent = container + table.insert(container.children, leaf) + itemCount = itemCount + 1 + end + end + + -- Recurse to next level + createLevel(container, depth + 1, math.max(1, items - 2)) + end + end + + -- Create the large structure + createLevel(root, 1, itemsPerLevel) + + -- Test initial layout + root:layoutChildren() + + -- Modify properties across the structure + local function modifyRandomly(container, depth) + if depth > maxDepth then + return + end + + -- Randomly modify properties + if math.random() > 0.7 then + container.gap = math.random(-10, 50) + end + + if math.random() > 0.8 then + container.flexDirection = math.random() > 0.5 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL + end + + if math.random() > 0.9 then + container.width = math.random(10, 500) + container.height = math.random(10, 300) + end + + -- Recurse to children + if container.children then + for _, child in ipairs(container.children) do + modifyRandomly(child, depth + 1) + end + end + end + + -- Apply random modifications + for iteration = 1, 3 do + modifyRandomly(root, 1) + root:layoutChildren() -- Should handle large structure + end + + -- Test memory cleanup simulation + local function clearSubtree(container) + if container.children then + for i = #container.children, 1, -1 do + clearSubtree(container.children[i]) + container.children[i].parent = nil + table.remove(container.children, i) + end + end + end + + -- Clear half the structure + if root.children then + for i = math.ceil(#root.children / 2), #root.children do + if root.children[i] then + clearSubtree(root.children[i]) + root.children[i].parent = nil + table.remove(root.children, i) + end + end + end + + -- Test layout after cleanup + root:layoutChildren() + + return { + root = root, + itemCount = itemCount, + finalChildCount = #root.children, + } + end) + + luaunit.assertTrue(success) -- Should handle large structures without crashing + luaunit.assertIsTable(result) + luaunit.assertTrue(result.itemCount > 100) -- Should have created many items + luaunit.assertTrue(result.finalChildCount > 0) -- Should have remaining children after cleanup + + -- Test final layout works + local finalLayoutSuccess = captureError(function() + result.root:layoutChildren() + end) + luaunit.assertTrue(finalLayoutSuccess) +end + +-- Test 15: Validation of Edge Cases in Complex Flex Combinations +function TestLayoutValidation:testComplexFlexCombinationValidation() + local success, result = captureError(function() + -- Test every possible combination of flex properties with edge cases + local combinations = {} + + local flexDirections = { FlexDirection.HORIZONTAL, FlexDirection.VERTICAL } + local justifyContents = { + JustifyContent.FLEX_START, + JustifyContent.FLEX_END, + JustifyContent.CENTER, + JustifyContent.SPACE_BETWEEN, + JustifyContent.SPACE_AROUND, + JustifyContent.SPACE_EVENLY, + } + local alignItems = { + AlignItems.FLEX_START, + AlignItems.FLEX_END, + AlignItems.CENTER, + AlignItems.STRETCH, + } + local flexWraps = { FlexWrap.NOWRAP, FlexWrap.WRAP, FlexWrap.WRAP_REVERSE } + + -- Main container for all combinations + local mainContainer = Gui.new({ + x = 0, + y = 0, + w = 2400, + h = 1800, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.FLEX_START, + gap = 20, + padding = { top = 20, right = 20, bottom = 20, left = 20 }, + }) + + local combinationCount = 0 + + -- Test each combination + for _, flexDir in ipairs(flexDirections) do + for _, justify in ipairs(justifyContents) do + for _, align in ipairs(alignItems) do + for _, wrap in ipairs(flexWraps) do + combinationCount = combinationCount + 1 + + local testContainer = Gui.new({ + w = 200, + h = 150, + positioning = Positioning.FLEX, + flexDirection = flexDir, + justifyContent = justify, + alignItems = align, + flexWrap = wrap, + gap = 5, + padding = { top = 5, right = 5, bottom = 5, left = 5 }, + }) + testContainer.parent = mainContainer + table.insert(mainContainer.children, testContainer) + + -- Add children with edge case properties + for i = 1, 6 do + local child = Gui.new({ + w = i == 1 and 0 or (i == 2 and -10 or (i == 6 and 999999 or 30)), + h = i == 3 and 0 or (i == 4 and -5 or (i == 5 and 1000000 or 20)), + positioning = i == 6 and Positioning.ABSOLUTE or nil, + x = i == 6 and -100 or nil, + y = i == 6 and 200 or nil, + }) + child.parent = testContainer + table.insert(testContainer.children, child) + + -- Add nested content to some children + if i <= 3 then + local nested = Gui.new({ + w = 15, + h = 10, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + nested.parent = child + table.insert(child.children, nested) + end + end + end + end + end + end + + -- Test layout with all combinations + mainContainer:layoutChildren() + + -- Test dynamic property changes on all combinations + for _, container in ipairs(mainContainer.children) do + -- Change gap to extreme values + container.gap = math.random() > 0.5 and -20 or 100 + + -- Change dimensions + if math.random() > 0.7 then + container.width = math.random() > 0.5 and 0 or 500 + container.height = math.random() > 0.5 and -50 or 300 + end + + -- Modify children + if container.children then + for i, child in ipairs(container.children) do + if math.random() > 0.8 then + child.width = math.random() > 0.5 and 0 or 999999 + child.height = math.random() > 0.5 and -100 or 1000000 + end + end + end + end + + -- Test layout after modifications + mainContainer:layoutChildren() + + return { + mainContainer = mainContainer, + combinationCount = combinationCount, + finalContainerCount = #mainContainer.children, + } + end) + + luaunit.assertTrue(success) -- Should handle all combinations without crashing + luaunit.assertIsTable(result) + + -- Should have tested many combinations + local expectedCombinations = 2 * 6 * 4 * 3 -- 144 combinations + luaunit.assertEquals(result.combinationCount, expectedCombinations) + luaunit.assertEquals(result.finalContainerCount, expectedCombinations) + + -- Test final layout works + local finalLayoutSuccess = captureError(function() + result.mainContainer:layoutChildren() + end) + luaunit.assertTrue(finalLayoutSuccess) +end + +luaunit.LuaUnit.run()