From c6d8c1b7685c48cb745fbd96a84439b39529110c Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Fri, 19 Sep 2025 00:52:55 -0400 Subject: [PATCH] expanded testing --- .../01_absolute_positioning_basic_tests.lua | 763 +++++++ ...bsolute_positioning_child_layout_tests.lua | 1745 ++++++++++---- .../03_flex_direction_horizontal_tests.lua | 1964 ++++++++++++---- .../04_flex_direction_vertical_tests.lua | 1208 ++++++++++ .../__tests__/05_justify_content_tests.lua | 2017 +++++++++++++---- testing/__tests__/06_align_items_tests.lua | 1 + 6 files changed, 6309 insertions(+), 1389 deletions(-) diff --git a/testing/__tests__/01_absolute_positioning_basic_tests.lua b/testing/__tests__/01_absolute_positioning_basic_tests.lua index 05f13bc..48e06af 100644 --- a/testing/__tests__/01_absolute_positioning_basic_tests.lua +++ b/testing/__tests__/01_absolute_positioning_basic_tests.lua @@ -358,4 +358,767 @@ function TestAbsolutePositioningBasic:testLargeCoordinateValues() luaunit.assertEquals(elem.z, 1000) end +-- =========================================================================== +-- COMPLEX TREE/BRANCHING STRUCTURE TESTS +-- =========================================================================== + +-- Test 16: Complex nested absolute tree structure (4 levels deep) +function TestAbsolutePositioningBasic:testComplexNestedAbsoluteTree() + -- Create a 4-level deep tree structure following CSS absolute positioning + -- Root (absolute) -> Child1 (absolute) -> Grandchild1 (absolute) -> GreatGrandchild1 (absolute) + -- -> Child2 (absolute) -> Grandchild2 (absolute) -> GreatGrandchild2 (absolute) + + local root = Gui.new({ + id = "root", + x = 100, + y = 100, + w = 800, + h = 600, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Left branch + local child1 = Gui.new({ + parent = root, + id = "child1", + x = 50, + y = 50, + w = 300, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + local grandchild1 = Gui.new({ + parent = child1, + id = "grandchild1", + x = 25, + y = 25, + w = 150, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local greatGrandchild1 = Gui.new({ + parent = grandchild1, + id = "greatGrandchild1", + x = 10, + y = 10, + w = 50, + h = 75, + positioning = Positioning.ABSOLUTE, + z = 4, + }) + + -- Right branch + local child2 = Gui.new({ + parent = root, + id = "child2", + x = 450, + y = 50, + w = 300, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + local grandchild2 = Gui.new({ + parent = child2, + id = "grandchild2", + x = 125, + y = 175, + w = 150, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local greatGrandchild2 = Gui.new({ + parent = grandchild2, + id = "greatGrandchild2", + x = 90, + y = 160, + w = 50, + h = 75, + positioning = Positioning.ABSOLUTE, + z = 4, + }) + + -- Verify tree structure + luaunit.assertEquals(#root.children, 2) + luaunit.assertEquals(root.children[1], child1) + luaunit.assertEquals(root.children[2], child2) + + luaunit.assertEquals(#child1.children, 1) + luaunit.assertEquals(child1.children[1], grandchild1) + + luaunit.assertEquals(#child2.children, 1) + luaunit.assertEquals(child2.children[1], grandchild2) + + luaunit.assertEquals(#grandchild1.children, 1) + luaunit.assertEquals(grandchild1.children[1], greatGrandchild1) + + luaunit.assertEquals(#grandchild2.children, 1) + luaunit.assertEquals(grandchild2.children[1], greatGrandchild2) + + -- Verify absolute positioning behavior (all maintain their own coordinates) + luaunit.assertEquals(child1.x, 50) + luaunit.assertEquals(child1.y, 50) + luaunit.assertEquals(child2.x, 450) + luaunit.assertEquals(child2.y, 50) + + luaunit.assertEquals(grandchild1.x, 25) + luaunit.assertEquals(grandchild1.y, 25) + luaunit.assertEquals(grandchild2.x, 125) + luaunit.assertEquals(grandchild2.y, 175) + + luaunit.assertEquals(greatGrandchild1.x, 10) + luaunit.assertEquals(greatGrandchild1.y, 10) + luaunit.assertEquals(greatGrandchild2.x, 90) + luaunit.assertEquals(greatGrandchild2.y, 160) +end + +-- Test 17: Binary tree structure with absolute positioning +function TestAbsolutePositioningBasic:testBinaryTreeAbsoluteStructure() + -- Create a binary tree structure where each node has exactly 2 children + local root = Gui.new({ + id = "root", + x = 400, + y = 100, + w = 100, + h = 50, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Level 1 + local leftChild = Gui.new({ + parent = root, + id = "left", + x = 200, + y = 200, + w = 80, + h = 40, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + local rightChild = Gui.new({ + parent = root, + id = "right", + x = 600, + y = 200, + w = 80, + h = 40, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + -- Level 2 - Left subtree + local leftLeft = Gui.new({ + parent = leftChild, + id = "leftLeft", + x = 100, + y = 300, + w = 60, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local leftRight = Gui.new({ + parent = leftChild, + id = "leftRight", + x = 300, + y = 300, + w = 60, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + -- Level 2 - Right subtree + local rightLeft = Gui.new({ + parent = rightChild, + id = "rightLeft", + x = 500, + y = 300, + w = 60, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local rightRight = Gui.new({ + parent = rightChild, + id = "rightRight", + x = 700, + y = 300, + w = 60, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + -- Verify binary tree structure + luaunit.assertEquals(#root.children, 2) + luaunit.assertEquals(#leftChild.children, 2) + luaunit.assertEquals(#rightChild.children, 2) + luaunit.assertEquals(#leftLeft.children, 0) + luaunit.assertEquals(#leftRight.children, 0) + luaunit.assertEquals(#rightLeft.children, 0) + luaunit.assertEquals(#rightRight.children, 0) + + -- Verify all nodes maintain their absolute positions + luaunit.assertEquals(root.x, 400) + luaunit.assertEquals(leftChild.x, 200) + luaunit.assertEquals(rightChild.x, 600) + luaunit.assertEquals(leftLeft.x, 100) + luaunit.assertEquals(leftRight.x, 300) + luaunit.assertEquals(rightLeft.x, 500) + luaunit.assertEquals(rightRight.x, 700) +end + +-- Test 18: Multi-branch tree with stacked z-indices (CSS z-index stacking context) +function TestAbsolutePositioningBasic:testMultiBranchZIndexStacking() + -- Create overlapping elements with complex z-index hierarchies + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 1000, + h = 1000, + positioning = Positioning.ABSOLUTE, + z = 0, + }) + + -- Background layer (z=1) + local background = Gui.new({ + parent = container, + id = "background", + x = 100, + y = 100, + w = 800, + h = 800, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Middle layer branch (z=5) + local middleParent = Gui.new({ + parent = container, + id = "middleParent", + x = 200, + y = 200, + w = 600, + h = 600, + positioning = Positioning.ABSOLUTE, + z = 5, + }) + + local middleChild1 = Gui.new({ + parent = middleParent, + id = "middleChild1", + x = 50, + y = 50, + w = 200, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 1, -- relative to middleParent + }) + + local middleChild2 = Gui.new({ + parent = middleParent, + id = "middleChild2", + x = 350, + y = 350, + w = 200, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 2, -- relative to middleParent, above middleChild1 + }) + + -- Foreground layer (z=10) + local foreground = Gui.new({ + parent = container, + id = "foreground", + x = 300, + y = 300, + w = 400, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 10, + }) + + local foregroundChild = Gui.new({ + parent = foreground, + id = "foregroundChild", + x = 150, + y = 150, + w = 100, + h = 100, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Verify stacking order by z-index + luaunit.assertEquals(background.z, 1) + luaunit.assertEquals(middleParent.z, 5) + luaunit.assertEquals(middleChild1.z, 1) + luaunit.assertEquals(middleChild2.z, 2) + luaunit.assertEquals(foreground.z, 10) + luaunit.assertEquals(foregroundChild.z, 1) + + -- Verify structure + luaunit.assertEquals(#container.children, 3) + luaunit.assertEquals(#middleParent.children, 2) + luaunit.assertEquals(#foreground.children, 1) + + -- All elements should maintain their absolute positions + luaunit.assertEquals(middleChild1.x, 50) + luaunit.assertEquals(middleChild2.x, 350) + luaunit.assertEquals(foregroundChild.x, 150) +end + +-- Test 19: Wide shallow tree (many siblings at same level) +function TestAbsolutePositioningBasic:testWideShallowAbsoluteTree() + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 2000, + h = 500, + positioning = Positioning.ABSOLUTE, + z = 0, + }) + + -- Create 10 siblings in a row + local siblings = {} + for i = 1, 10 do + siblings[i] = Gui.new({ + parent = container, + id = "sibling" .. i, + x = i * 180, + y = 100, + w = 150, + h = 300, + positioning = Positioning.ABSOLUTE, + z = i, -- Each has different z-index + }) + + -- Each sibling has 3 children + for j = 1, 3 do + Gui.new({ + parent = siblings[i], + id = "child" .. i .. "_" .. j, + x = 25, + y = j * 80, + w = 100, + h = 60, + positioning = Positioning.ABSOLUTE, + z = j, + }) + end + end + + -- Verify wide structure + luaunit.assertEquals(#container.children, 10) + + for i = 1, 10 do + luaunit.assertEquals(#siblings[i].children, 3) + luaunit.assertEquals(siblings[i].x, i * 180) + luaunit.assertEquals(siblings[i].z, i) + end +end + +-- Test 20: Asymmetric tree with mixed absolute positioning +function TestAbsolutePositioningBasic:testAsymmetricAbsoluteTree() + -- Root with asymmetric branch structure + local root = Gui.new({ + id = "root", + x = 500, + y = 100, + w = 200, + h = 100, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Left branch: deep nesting + local leftBranch = Gui.new({ + parent = root, + id = "leftBranch", + x = 100, + y = 250, + w = 150, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + local leftDeep1 = Gui.new({ + parent = leftBranch, + id = "leftDeep1", + x = 25, + y = 50, + w = 100, + h = 80, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local leftDeep2 = Gui.new({ + parent = leftDeep1, + id = "leftDeep2", + x = 10, + y = 10, + w = 80, + h = 60, + positioning = Positioning.ABSOLUTE, + z = 4, + }) + + local leftDeep3 = Gui.new({ + parent = leftDeep2, + id = "leftDeep3", + x = 5, + y = 5, + w = 70, + h = 50, + positioning = Positioning.ABSOLUTE, + z = 5, + }) + + -- Right branch: wide shallow + local rightBranch = Gui.new({ + parent = root, + id = "rightBranch", + x = 800, + y = 250, + w = 400, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + -- Multiple children for right branch + for i = 1, 5 do + Gui.new({ + parent = rightBranch, + id = "rightChild" .. i, + x = i * 70, + y = 50, + w = 60, + h = 100, + positioning = Positioning.ABSOLUTE, + z = i, + }) + end + + -- Verify asymmetric structure + luaunit.assertEquals(#root.children, 2) + luaunit.assertEquals(#leftBranch.children, 1) -- Deep chain + luaunit.assertEquals(#rightBranch.children, 5) -- Wide spread + + -- Verify deep chain + luaunit.assertEquals(#leftDeep1.children, 1) + luaunit.assertEquals(#leftDeep2.children, 1) + luaunit.assertEquals(#leftDeep3.children, 0) + + -- Verify positions maintained + luaunit.assertEquals(leftBranch.x, 100) + luaunit.assertEquals(rightBranch.x, 800) + luaunit.assertEquals(leftDeep3.x, 5) +end + +-- Test 21: Overlapping absolute elements with negative coordinates +function TestAbsolutePositioningBasic:testOverlappingNegativeCoordinates() + local viewport = Gui.new({ + id = "viewport", + x = 500, + y = 500, + w = 400, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 0, + }) + + -- Elements that extend outside viewport boundaries + local topLeft = Gui.new({ + parent = viewport, + id = "topLeft", + x = -100, + y = -100, + w = 200, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + local topRight = Gui.new({ + parent = viewport, + id = "topRight", + x = 300, + y = -50, + w = 200, + h = 150, + positioning = Positioning.ABSOLUTE, + z = 2, + }) + + local bottomLeft = Gui.new({ + parent = viewport, + id = "bottomLeft", + x = -50, + y = 350, + w = 150, + h = 200, + positioning = Positioning.ABSOLUTE, + z = 3, + }) + + local center = Gui.new({ + parent = viewport, + id = "center", + x = 150, + y = 150, + w = 100, + h = 100, + positioning = Positioning.ABSOLUTE, + z = 10, -- Highest z-index + }) + + -- Verify negative coordinates are preserved + luaunit.assertEquals(topLeft.x, -100) + luaunit.assertEquals(topLeft.y, -100) + luaunit.assertEquals(topRight.x, 300) + luaunit.assertEquals(topRight.y, -50) + luaunit.assertEquals(bottomLeft.x, -50) + luaunit.assertEquals(bottomLeft.y, 350) + + -- Center element with highest z-index + luaunit.assertEquals(center.z, 10) + luaunit.assertEquals(center.x, 150) + luaunit.assertEquals(center.y, 150) + + luaunit.assertEquals(#viewport.children, 4) +end + +-- Test 22: Tree with circular-like positioning (elements in circle pattern) +function TestAbsolutePositioningBasic:testCircularPositioningPattern() + local center = Gui.new({ + id = "center", + x = 400, + y = 400, + w = 100, + h = 100, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Create 8 elements positioned in a circle around center + local radius = 200 + local centerX, centerY = 450, 450 -- Center point of circle + + for i = 1, 8 do + local angle = (i - 1) * (math.pi * 2 / 8) -- 8 evenly spaced angles + local x = centerX + radius * math.cos(angle) + local y = centerY + radius * math.sin(angle) + + local orbiter = Gui.new({ + parent = center, + id = "orbiter" .. i, + x = math.floor(x), + y = math.floor(y), + w = 50, + h = 50, + positioning = Positioning.ABSOLUTE, + z = i, + }) + + -- Each orbiter has a small child + Gui.new({ + parent = orbiter, + id = "orbiterChild" .. i, + x = 10, + y = 10, + w = 30, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + end + + -- Verify circular structure + luaunit.assertEquals(#center.children, 8) + + for i = 1, 8 do + luaunit.assertEquals(#center.children[i].children, 1) + luaunit.assertEquals(center.children[i].z, i) + end +end + +-- Test 23: Deep single-branch chain (maximum depth test) +function TestAbsolutePositioningBasic:testDeepSingleBranchChain() + local current = Gui.new({ + id = "root", + x = 100, + y = 100, + w = 500, + h = 500, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Create a 15-level deep chain + for i = 2, 15 do + local child = Gui.new({ + parent = current, + id = "depth" .. i, + x = 10, + y = 10, + w = math.max(50, 500 - (i * 25)), -- Decreasing width + h = math.max(50, 500 - (i * 25)), -- Decreasing height + positioning = Positioning.ABSOLUTE, + z = i, + }) + current = child + end + + -- Verify deep chain structure + current = Gui.topElements[1] -- root element + luaunit.assertEquals(current.id, "root") + + for i = 2, 15 do + luaunit.assertEquals(#current.children, 1) + current = current.children[1] + luaunit.assertEquals(current.id, "depth" .. i) + luaunit.assertEquals(current.z, i) + luaunit.assertEquals(current.x, 10) + luaunit.assertEquals(current.y, 10) + end + + -- Last element should have no children + luaunit.assertEquals(#current.children, 0) +end + +-- Test 24: Complex branching with mixed z-indices and overlapping regions +function TestAbsolutePositioningBasic:testComplexBranchingWithOverlaps() + -- Create a complex layout simulating a windowing system + local desktop = Gui.new({ + id = "desktop", + x = 0, + y = 0, + w = 1920, + h = 1080, + positioning = Positioning.ABSOLUTE, + z = 0, + }) + + -- Taskbar + local taskbar = Gui.new({ + parent = desktop, + id = "taskbar", + x = 0, + y = 1040, + w = 1920, + h = 40, + positioning = Positioning.ABSOLUTE, + z = 100, -- Always on top + }) + + -- Windows with different z-indices + local window1 = Gui.new({ + parent = desktop, + id = "window1", + x = 100, + y = 100, + w = 600, + h = 400, + positioning = Positioning.ABSOLUTE, + z = 10, + }) + + local window2 = Gui.new({ + parent = desktop, + id = "window2", + x = 300, + y = 200, + w = 500, + h = 350, + positioning = Positioning.ABSOLUTE, + z = 15, -- Above window1 + }) + + local window3 = Gui.new({ + parent = desktop, + id = "window3", + x = 200, + y = 150, + w = 400, + h = 300, + positioning = Positioning.ABSOLUTE, + z = 5, -- Behind window1 and window2 + }) + + -- Each window has title bar and content + for i, window in ipairs({ window1, window2, window3 }) do + local titlebar = Gui.new({ + parent = window, + id = window.id .. "_titlebar", + x = 0, + y = 0, + w = window.width, + h = 30, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + local content = Gui.new({ + parent = window, + id = window.id .. "_content", + x = 0, + y = 30, + w = window.width, + h = window.height - 30, + positioning = Positioning.ABSOLUTE, + z = 1, + }) + + -- Content has multiple child elements + for j = 1, 3 do + Gui.new({ + parent = content, + id = window.id .. "_item" .. j, + x = j * 50, + y = j * 40, + w = 80, + h = 30, + positioning = Positioning.ABSOLUTE, + z = j, + }) + end + end + + -- Verify complex structure + luaunit.assertEquals(#desktop.children, 4) -- taskbar + 3 windows + luaunit.assertEquals(taskbar.z, 100) -- Highest z-index + luaunit.assertEquals(window1.z, 10) + luaunit.assertEquals(window2.z, 15) + luaunit.assertEquals(window3.z, 5) + + -- Each window has titlebar and content + luaunit.assertEquals(#window1.children, 2) + luaunit.assertEquals(#window2.children, 2) + luaunit.assertEquals(#window3.children, 2) + + -- Each content area has 3 items + for i, window in ipairs({ window1, window2, window3 }) do + local content = window.children[2] -- content is second child + luaunit.assertEquals(#content.children, 3) + end +end + luaunit.LuaUnit.run() diff --git a/testing/__tests__/02_absolute_positioning_child_layout_tests.lua b/testing/__tests__/02_absolute_positioning_child_layout_tests.lua index 7bf986c..b8306cc 100644 --- a/testing/__tests__/02_absolute_positioning_child_layout_tests.lua +++ b/testing/__tests__/02_absolute_positioning_child_layout_tests.lua @@ -18,520 +18,1367 @@ local AlignItems = enums.AlignItems TestAbsolutePositioningChildLayout = {} function TestAbsolutePositioningChildLayout:setUp() - -- Clean up before each test - Gui.destroy() + -- Clean up before each test + Gui.destroy() end function TestAbsolutePositioningChildLayout:tearDown() - -- Clean up after each test - Gui.destroy() + -- Clean up after each test + Gui.destroy() end -- Test 1: Adding children to absolute positioned parents function TestAbsolutePositioningChildLayout:testAddChildToAbsoluteParent() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - parent:addChild(child) - - -- Verify child was added - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child) - luaunit.assertEquals(child.parent, parent) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local child = Gui.new({ + id = "child", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + parent:addChild(child) + + -- Verify child was added + luaunit.assertEquals(#parent.children, 1) + luaunit.assertEquals(parent.children[1], child) + luaunit.assertEquals(child.parent, parent) end -- Test 2: Children maintain their own coordinates function TestAbsolutePositioningChildLayout:testChildrenMaintainCoordinates() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local child1 = Gui.new({ - id = "child1", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - x = 75, - y = 85, - w = 40, - h = 25 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should maintain their original coordinates - luaunit.assertEquals(child1.x, 10) - luaunit.assertEquals(child1.y, 20) - luaunit.assertEquals(child2.x, 75) - luaunit.assertEquals(child2.y, 85) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local child1 = Gui.new({ + id = "child1", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + x = 75, + y = 85, + w = 40, + h = 25, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Children should maintain their original coordinates + luaunit.assertEquals(child1.x, 10) + luaunit.assertEquals(child1.y, 20) + luaunit.assertEquals(child2.x, 75) + luaunit.assertEquals(child2.y, 85) end -- Test 3: Absolute positioned elements don't call layoutChildren() logic function TestAbsolutePositioningChildLayout:testAbsoluteParentSkipsLayoutChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150, - flexDirection = FlexDirection.HORIZONTAL - }) - - local child1 = Gui.new({ - id = "child1", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - x = 200, -- Way beyond parent w - this would be repositioned in flex layout - y = 300, - w = 40, - h = 25 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- In absolute positioning, children should keep their original positions - -- regardless of flex direction or justification - luaunit.assertEquals(child1.x, 10) - luaunit.assertEquals(child1.y, 20) - luaunit.assertEquals(child2.x, 200) -- Not repositioned by flex layout - luaunit.assertEquals(child2.y, 300) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + flexDirection = FlexDirection.HORIZONTAL, + }) + + local child1 = Gui.new({ + id = "child1", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + x = 200, -- Way beyond parent w - this would be repositioned in flex layout + y = 300, + w = 40, + h = 25, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- In absolute positioning, children should keep their original positions + -- regardless of flex direction or justification + luaunit.assertEquals(child1.x, 10) + luaunit.assertEquals(child1.y, 20) + luaunit.assertEquals(child2.x, 200) -- Not repositioned by flex layout + luaunit.assertEquals(child2.y, 300) end -- Test 4: Adding children to absolute parent doesn't affect parent's flex properties function TestAbsolutePositioningChildLayout:testAbsoluteParentFlexPropertiesUnchanged() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.FLEX_END - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - -- Store original values - local originalFlexDirection = parent.flexDirection - local originalJustifyContent = parent.justifyContent - local originalAlignItems = parent.alignItems - local originalX = parent.x - local originalY = parent.y - - parent:addChild(child) - - -- Parent properties should remain unchanged - luaunit.assertEquals(parent.flexDirection, originalFlexDirection) - luaunit.assertEquals(parent.justifyContent, originalJustifyContent) - luaunit.assertEquals(parent.alignItems, originalAlignItems) - luaunit.assertEquals(parent.x, originalX) - luaunit.assertEquals(parent.y, originalY) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.FLEX_END, + }) + + local child = Gui.new({ + id = "child", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + -- Store original values + local originalFlexDirection = parent.flexDirection + local originalJustifyContent = parent.justifyContent + local originalAlignItems = parent.alignItems + local originalX = parent.x + local originalY = parent.y + + parent:addChild(child) + + -- Parent properties should remain unchanged + luaunit.assertEquals(parent.flexDirection, originalFlexDirection) + luaunit.assertEquals(parent.justifyContent, originalJustifyContent) + luaunit.assertEquals(parent.alignItems, originalAlignItems) + luaunit.assertEquals(parent.x, originalX) + luaunit.assertEquals(parent.y, originalY) end -- Test 5: Multiple children added to absolute parent maintain independent positioning function TestAbsolutePositioningChildLayout:testMultipleChildrenIndependentPositioning() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - w = 300, - h = 300 + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 300, + h = 300, + }) + + local children = {} + for i = 1, 5 do + children[i] = Gui.new({ + id = "child" .. i, + x = i * 25, + y = i * 30, + w = 20, + h = 15, }) - - local children = {} - for i = 1, 5 do - children[i] = Gui.new({ - id = "child" .. i, - x = i * 25, - y = i * 30, - w = 20, - h = 15 - }) - parent:addChild(children[i]) - end - - -- Verify each child maintains its position - for i = 1, 5 do - luaunit.assertEquals(children[i].x, i * 25) - luaunit.assertEquals(children[i].y, i * 30) - luaunit.assertEquals(children[i].parent, parent) - end - - luaunit.assertEquals(#parent.children, 5) + parent:addChild(children[i]) + end + + -- Verify each child maintains its position + for i = 1, 5 do + luaunit.assertEquals(children[i].x, i * 25) + luaunit.assertEquals(children[i].y, i * 30) + luaunit.assertEquals(children[i].parent, parent) + end + + luaunit.assertEquals(#parent.children, 5) end -- Test 6: Absolute children don't participate in flex layout of their parent function TestAbsolutePositioningChildLayout:testAbsoluteChildrenIgnoreFlexLayout() - local parent = Gui.new({ - id = "flex_parent", - positioning = Positioning.FLEX, - x = 0, - y = 0, - w = 300, - h = 100, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN - }) - - local flexChild = Gui.new({ - id = "flex_child", - w = 50, - h = 30 - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 200, - y = 40, - w = 50, - h = 30 - }) - - parent:addChild(flexChild) - parent:addChild(absoluteChild) - - -- The absolute child should maintain its position - luaunit.assertEquals(absoluteChild.x, 200) - luaunit.assertEquals(absoluteChild.y, 40) - - -- The flex child should be positioned by the flex layout (at the start since it's the only flex child) - -- Note: exact positioning depends on flex implementation, but it shouldn't be at 200,40 - luaunit.assertNotEquals(flexChild.x, 200) + local parent = Gui.new({ + id = "flex_parent", + positioning = Positioning.FLEX, + x = 0, + y = 0, + w = 300, + h = 100, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + }) + + local flexChild = Gui.new({ + id = "flex_child", + w = 50, + h = 30, + }) + + local absoluteChild = Gui.new({ + id = "absolute_child", + positioning = Positioning.ABSOLUTE, + x = 200, + y = 40, + w = 50, + h = 30, + }) + + parent:addChild(flexChild) + parent:addChild(absoluteChild) + + -- The absolute child should maintain its position + luaunit.assertEquals(absoluteChild.x, 200) + luaunit.assertEquals(absoluteChild.y, 40) + + -- The flex child should be positioned by the flex layout (at the start since it's the only flex child) + -- Note: exact positioning depends on flex implementation, but it shouldn't be at 200,40 + luaunit.assertNotEquals(flexChild.x, 200) end -- Test 7: Child coordinates remain independent of parent position changes function TestAbsolutePositioningChildLayout:testChildCoordinatesIndependentOfParentChanges() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - w = 50, - h = 40 - }) - - parent:addChild(child) - - -- Change parent position - parent.x = 300 - parent.y = 250 - - -- Child coordinates should remain unchanged (they're relative to parent) - luaunit.assertEquals(child.x, 25) - luaunit.assertEquals(child.y, 30) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local child = Gui.new({ + id = "child", + x = 25, + y = 30, + w = 50, + h = 40, + }) + + parent:addChild(child) + + -- Change parent position + parent.x = 300 + parent.y = 250 + + -- Child coordinates should remain unchanged (they're relative to parent) + luaunit.assertEquals(child.x, 25) + luaunit.assertEquals(child.y, 30) end -- Test 8: Nested absolute positioning function TestAbsolutePositioningChildLayout:testNestedAbsolutePositioning() - local grandparent = Gui.new({ - id = "grandparent", - positioning = Positioning.ABSOLUTE, - x = 50, - y = 25, - w = 400, - h = 300 - }) - - local parent = Gui.new({ - id = "parent", - positioning = Positioning.ABSOLUTE, - x = 75, - y = 50, - w = 200, - h = 150 - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - grandparent:addChild(parent) - parent:addChild(child) - - -- Verify the hierarchy - luaunit.assertEquals(parent.parent, grandparent) - luaunit.assertEquals(child.parent, parent) - - -- Verify positions are maintained at each level - luaunit.assertEquals(grandparent.x, 50) - luaunit.assertEquals(parent.x, 75) - luaunit.assertEquals(child.x, 10) + local grandparent = Gui.new({ + id = "grandparent", + positioning = Positioning.ABSOLUTE, + x = 50, + y = 25, + w = 400, + h = 300, + }) + + local parent = Gui.new({ + id = "parent", + positioning = Positioning.ABSOLUTE, + x = 75, + y = 50, + w = 200, + h = 150, + }) + + local child = Gui.new({ + id = "child", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + grandparent:addChild(parent) + parent:addChild(child) + + -- Verify the hierarchy + luaunit.assertEquals(parent.parent, grandparent) + luaunit.assertEquals(child.parent, parent) + + -- Verify positions are maintained at each level + luaunit.assertEquals(grandparent.x, 50) + luaunit.assertEquals(parent.x, 75) + luaunit.assertEquals(child.x, 10) end -- Test 9: Absolute parent with flex children maintains flex properties function TestAbsolutePositioningChildLayout:testAbsoluteParentWithFlexChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - w = 50, - h = 30 - }) - - parent:addChild(flexChild) - - -- Child should maintain its flex positioning mode - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) - luaunit.assertEquals(flexChild.parent, parent) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local flexChild = Gui.new({ + id = "flex_child", + positioning = Positioning.FLEX, + w = 50, + h = 30, + }) + + parent:addChild(flexChild) + + -- Child should maintain its flex positioning mode + luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) + luaunit.assertEquals(flexChild.parent, parent) end -- Test 10: Auto-sizing behavior with absolute parent and children function TestAbsolutePositioningChildLayout:testAutoSizingWithAbsoluteParentAndChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50 - -- No w/h specified, so it should auto-size - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - w = 50, - h = 30 - }) - - parent:addChild(child) - - -- Auto-sizing should still work for absolute parents - -- (though the exact behavior may depend on implementation) - luaunit.assertTrue(parent.width >= 0) - luaunit.assertTrue(parent.height >= 0) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + -- No w/h specified, so it should auto-size + }) + + local child = Gui.new({ + id = "child", + x = 10, + y = 20, + w = 50, + h = 30, + }) + + parent:addChild(child) + + -- Auto-sizing should still work for absolute parents + -- (though the exact behavior may depend on implementation) + luaunit.assertTrue(parent.width >= 0) + luaunit.assertTrue(parent.height >= 0) end -- Test 11: Children added to absolute parent preserve their positioning type function TestAbsolutePositioningChildLayout:testChildrenPreservePositioningType() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 25, - y = 30, - w = 50, - h = 40 - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - w = 60, - h = 35 - }) - - parent:addChild(absoluteChild) - parent:addChild(flexChild) - - -- Children should maintain their original positioning types - luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local absoluteChild = Gui.new({ + id = "absolute_child", + positioning = Positioning.ABSOLUTE, + x = 25, + y = 30, + w = 50, + h = 40, + }) + + local flexChild = Gui.new({ + id = "flex_child", + positioning = Positioning.FLEX, + w = 60, + h = 35, + }) + + parent:addChild(absoluteChild) + parent:addChild(flexChild) + + -- Children should maintain their original positioning types + luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) + luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) end -- Test 12: Parent-child coordinate relationships function TestAbsolutePositioningChildLayout:testParentChildCoordinateRelationships() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - w = 50, - h = 40 - }) - - parent:addChild(child) - - -- Child coordinates should be relative to parent - -- Note: This test verifies the conceptual relationship - -- The actual implementation might handle coordinate systems differently - luaunit.assertEquals(child.x, 25) -- Child maintains its relative coordinates - luaunit.assertEquals(child.y, 30) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local child = Gui.new({ + id = "child", + x = 25, + y = 30, + w = 50, + h = 40, + }) + + parent:addChild(child) + + -- Child coordinates should be relative to parent + -- Note: This test verifies the conceptual relationship + -- The actual implementation might handle coordinate systems differently + luaunit.assertEquals(child.x, 25) -- Child maintains its relative coordinates + luaunit.assertEquals(child.y, 30) end -- Test 13: Adding child doesn't trigger parent repositioning function TestAbsolutePositioningChildLayout:testAddChildNoParentRepositioning() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 150, - y = 75, - w = 200, - h = 150 - }) - - local originalX = parent.x - local originalY = parent.y - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - w = 50, - h = 40 - }) - - parent:addChild(child) - - -- Parent position should remain unchanged after adding child - luaunit.assertEquals(parent.x, originalX) - luaunit.assertEquals(parent.y, originalY) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 150, + y = 75, + w = 200, + h = 150, + }) + + local originalX = parent.x + local originalY = parent.y + + local child = Gui.new({ + id = "child", + x = 25, + y = 30, + w = 50, + h = 40, + }) + + parent:addChild(child) + + -- Parent position should remain unchanged after adding child + luaunit.assertEquals(parent.x, originalX) + luaunit.assertEquals(parent.y, originalY) end -- Test 14: Children table is properly maintained function TestAbsolutePositioningChildLayout:testChildrenTableMaintained() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 200, - h = 150 - }) - - local child1 = Gui.new({id = "child1", x = 10, y = 20, w = 50, h = 30}) - local child2 = Gui.new({id = "child2", x = 70, y = 80, w = 40, h = 25}) - local child3 = Gui.new({id = "child3", x = 120, y = 90, w = 30, h = 35}) - - parent:addChild(child1) - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child1) - - parent:addChild(child2) - luaunit.assertEquals(#parent.children, 2) - luaunit.assertEquals(parent.children[2], child2) - - parent:addChild(child3) - luaunit.assertEquals(#parent.children, 3) - luaunit.assertEquals(parent.children[3], child3) - - -- Verify all children have correct parent reference - luaunit.assertEquals(child1.parent, parent) - luaunit.assertEquals(child2.parent, parent) - luaunit.assertEquals(child3.parent, parent) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 200, + h = 150, + }) + + local child1 = Gui.new({ id = "child1", x = 10, y = 20, w = 50, h = 30 }) + local child2 = Gui.new({ id = "child2", x = 70, y = 80, w = 40, h = 25 }) + local child3 = Gui.new({ id = "child3", x = 120, y = 90, w = 30, h = 35 }) + + parent:addChild(child1) + luaunit.assertEquals(#parent.children, 1) + luaunit.assertEquals(parent.children[1], child1) + + parent:addChild(child2) + luaunit.assertEquals(#parent.children, 2) + luaunit.assertEquals(parent.children[2], child2) + + parent:addChild(child3) + luaunit.assertEquals(#parent.children, 3) + luaunit.assertEquals(parent.children[3], child3) + + -- Verify all children have correct parent reference + luaunit.assertEquals(child1.parent, parent) + luaunit.assertEquals(child2.parent, parent) + luaunit.assertEquals(child3.parent, parent) end -- Test 15: Absolute parent with mixed child types function TestAbsolutePositioningChildLayout:testAbsoluteParentMixedChildTypes() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - w = 300, - h = 200 - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 25, - y = 30, - w = 50, - h = 40 - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - w = 60, - h = 35 - }) - - parent:addChild(absoluteChild) - parent:addChild(flexChild) - - -- Both children should be added successfully - luaunit.assertEquals(#parent.children, 2) - luaunit.assertEquals(parent.children[1], absoluteChild) - luaunit.assertEquals(parent.children[2], flexChild) - - -- Children should maintain their positioning types and properties - luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) - luaunit.assertEquals(absoluteChild.x, 25) - luaunit.assertEquals(absoluteChild.y, 30) + local parent = Gui.new({ + id = "absolute_parent", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 50, + w = 300, + h = 200, + }) + + local absoluteChild = Gui.new({ + id = "absolute_child", + positioning = Positioning.ABSOLUTE, + x = 25, + y = 30, + w = 50, + h = 40, + }) + + local flexChild = Gui.new({ + id = "flex_child", + positioning = Positioning.FLEX, + w = 60, + h = 35, + }) + + parent:addChild(absoluteChild) + parent:addChild(flexChild) + + -- Both children should be added successfully + luaunit.assertEquals(#parent.children, 2) + luaunit.assertEquals(parent.children[1], absoluteChild) + luaunit.assertEquals(parent.children[2], flexChild) + + -- Children should maintain their positioning types and properties + luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) + luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) + luaunit.assertEquals(absoluteChild.x, 25) + luaunit.assertEquals(absoluteChild.y, 30) end -- Run the tests if arg and arg[0] == debug.getinfo(1, "S").source:sub(2) then - os.exit(luaunit.LuaUnit.run()) -end \ No newline at end of file + os.exit(luaunit.LuaUnit.run()) +end + +-- =========================================================================== +-- COMPLEX MULTI-LEVEL HIERARCHY TESTS +-- =========================================================================== + +-- Test 16: Deep hierarchy with mixed positioning types (CSS-like behavior) +function TestAbsolutePositioningChildLayout:testDeepHierarchyMixedPositioning() + -- Create a complex hierarchy: absolute -> flex -> absolute -> flex + local absoluteRoot = Gui.new({ + id = "absoluteRoot", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 100, + w = 800, + h = 600, + }) + + local flexLevel1 = Gui.new({ + parent = absoluteRoot, + id = "flexLevel1", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + x = 50, -- Should be ignored due to flex positioning + y = 50, + w = 700, + h = 500, + gap = 20, + }) + + -- Add absolute children to flex parent + local absoluteChild1 = Gui.new({ + parent = flexLevel1, + id = "absoluteChild1", + positioning = Positioning.ABSOLUTE, + x = 600, -- Absolute position within flex parent + y = 400, + w = 150, + h = 100, + }) + + local flexChild1 = Gui.new({ + parent = flexLevel1, + id = "flexChild1", + positioning = Positioning.FLEX, + w = 200, + h = 150, + }) + + local flexChild2 = Gui.new({ + parent = flexLevel1, + id = "flexChild2", + positioning = Positioning.FLEX, + w = 200, + h = 150, + }) + + -- Add grandchildren to flex children + local absoluteGrandchild = Gui.new({ + parent = flexChild1, + id = "absoluteGrandchild", + positioning = Positioning.ABSOLUTE, + x = 75, + y = 75, + w = 50, + h = 50, + }) + + local flexGrandchild = Gui.new({ + parent = flexChild2, + id = "flexGrandchild", + positioning = Positioning.FLEX, + w = 100, + h = 75, + }) + + -- Verify hierarchy structure + luaunit.assertEquals(#absoluteRoot.children, 1) + luaunit.assertEquals(absoluteRoot.children[1], flexLevel1) + + luaunit.assertEquals(#flexLevel1.children, 3) + luaunit.assertTrue( + flexLevel1.children[1] == absoluteChild1 + or flexLevel1.children[2] == absoluteChild1 + or flexLevel1.children[3] == absoluteChild1 + ) + + luaunit.assertEquals(#flexChild1.children, 1) + luaunit.assertEquals(flexChild1.children[1], absoluteGrandchild) + + luaunit.assertEquals(#flexChild2.children, 1) + luaunit.assertEquals(flexChild2.children[1], flexGrandchild) + + -- Verify absolute elements maintain their positioning + luaunit.assertEquals(absoluteChild1.x, 600) + luaunit.assertEquals(absoluteChild1.y, 400) + luaunit.assertEquals(absoluteGrandchild.x, 75) + luaunit.assertEquals(absoluteGrandchild.y, 75) +end + +-- Test 17: Multi-branch tree with absolute parents having flex and absolute children +function TestAbsolutePositioningChildLayout:testMultiBranchAbsoluteWithMixedChildren() + local root = Gui.new({ + id = "root", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1200, + h = 800, + }) + + -- Left branch: Absolute parent with flex children + local leftAbsoluteParent = Gui.new({ + parent = root, + id = "leftAbsoluteParent", + positioning = Positioning.ABSOLUTE, + x = 50, + y = 50, + w = 500, + h = 700, + }) + + -- Flex container within absolute parent + local leftFlexContainer = Gui.new({ + parent = leftAbsoluteParent, + id = "leftFlexContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 400, + h = 600, + gap = 15, + }) + + -- Add multiple flex children + for i = 1, 4 do + local flexChild = Gui.new({ + parent = leftFlexContainer, + id = "leftFlexChild" .. i, + positioning = Positioning.FLEX, + w = 350, + h = 120, + }) + + -- Each flex child has absolute grandchildren + for j = 1, 2 do + Gui.new({ + parent = flexChild, + id = "leftAbsGrandchild" .. i .. "_" .. j, + positioning = Positioning.ABSOLUTE, + x = j * 100, + y = 20, + w = 80, + h = 80, + }) + end + end + + -- Add some absolute children to the absolute parent + for i = 1, 3 do + Gui.new({ + parent = leftAbsoluteParent, + id = "leftAbsoluteChild" .. i, + positioning = Positioning.ABSOLUTE, + x = 450, + y = i * 200, + w = 40, + h = 150, + }) + end + + -- Right branch: Similar structure but different layout + local rightAbsoluteParent = Gui.new({ + parent = root, + id = "rightAbsoluteParent", + positioning = Positioning.ABSOLUTE, + x = 650, + y = 50, + w = 500, + h = 700, + }) + + local rightFlexContainer = Gui.new({ + parent = rightAbsoluteParent, + id = "rightFlexContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + w = 450, + h = 200, + gap = 10, + }) + + -- Add horizontal flex children + for i = 1, 3 do + local flexChild = Gui.new({ + parent = rightFlexContainer, + id = "rightFlexChild" .. i, + positioning = Positioning.FLEX, + w = 130, + h = 180, + }) + + -- Nested flex container + local nestedFlex = Gui.new({ + parent = flexChild, + id = "rightNestedFlex" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 120, + h = 170, + gap = 5, + }) + + -- Add children to nested flex + for j = 1, 3 do + Gui.new({ + parent = nestedFlex, + id = "rightNestedChild" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = 110, + h = 50, + }) + end + end + + -- Verify structure + luaunit.assertEquals(#root.children, 2) + luaunit.assertEquals(#leftAbsoluteParent.children, 4) -- 1 flex container + 3 absolute children + luaunit.assertEquals(#rightAbsoluteParent.children, 1) -- 1 flex container + + luaunit.assertEquals(#leftFlexContainer.children, 4) + luaunit.assertEquals(#rightFlexContainer.children, 3) + + -- Verify absolute children maintain positions + for i = 1, 3 do + local absChild = leftAbsoluteParent.children[i + 1] -- Skip flex container (first child) + luaunit.assertEquals(absChild.x, 450) + luaunit.assertEquals(absChild.y, i * 200) + end +end + +-- Test 18: Cascade of absolute positioned containers with z-index conflicts +function TestAbsolutePositioningChildLayout:testCascadeAbsoluteWithZIndexConflicts() + local viewport = Gui.new({ + id = "viewport", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1000, + h = 1000, + z = 0, + }) + + -- Create overlapping layers with complex z-index hierarchies + local layers = {} + for i = 1, 5 do + layers[i] = Gui.new({ + parent = viewport, + id = "layer" .. i, + positioning = Positioning.ABSOLUTE, + x = i * 50, + y = i * 50, + w = 600, + h = 600, + z = 6 - i, -- Reverse z-index (layer1=5, layer2=4, etc.) + }) + + -- Each layer has sublayers with conflicting z-indices + for j = 1, 3 do + local sublayer = Gui.new({ + parent = layers[i], + id = "sublayer" .. i .. "_" .. j, + positioning = Positioning.ABSOLUTE, + x = j * 100, + y = j * 100, + w = 200, + h = 200, + z = j, -- Same z-index pattern across all layers + }) + + -- Each sublayer has items + for k = 1, 2 do + Gui.new({ + parent = sublayer, + id = "item" .. i .. "_" .. j .. "_" .. k, + positioning = Positioning.ABSOLUTE, + x = k * 30, + y = k * 30, + w = 50, + h = 50, + z = k, + }) + end + end + end + + -- Verify layer structure and z-index ordering + luaunit.assertEquals(#viewport.children, 5) + + for i = 1, 5 do + luaunit.assertEquals(layers[i].z, 6 - i) + luaunit.assertEquals(#layers[i].children, 3) + + for j = 1, 3 do + local sublayer = layers[i].children[j] + luaunit.assertEquals(sublayer.z, j) + luaunit.assertEquals(#sublayer.children, 2) + + for k = 1, 2 do + local item = sublayer.children[k] + luaunit.assertEquals(item.z, k) + luaunit.assertEquals(item.x, k * 30) + luaunit.assertEquals(item.y, k * 30) + end + end + end +end + +-- Test 19: Grid-like structure using absolute positioning +function TestAbsolutePositioningChildLayout:testGridStructureAbsolutePositioning() + local grid = Gui.new({ + id = "grid", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 100, + w = 800, + h = 600, + }) + + local rows, cols = 4, 5 + local cellWidth, cellHeight = 150, 120 + local gap = 10 + + -- Create grid cells + local cells = {} + for row = 1, rows do + cells[row] = {} + for col = 1, cols do + local x = (col - 1) * (cellWidth + gap) + local y = (row - 1) * (cellHeight + gap) + + cells[row][col] = Gui.new({ + parent = grid, + id = "cell_" .. row .. "_" .. col, + positioning = Positioning.ABSOLUTE, + x = x, + y = y, + w = cellWidth, + h = cellHeight, + z = row * cols + col, -- Unique z-index for each cell + }) + + -- Each cell has a header and content + local header = Gui.new({ + parent = cells[row][col], + id = "header_" .. row .. "_" .. col, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = cellWidth, + h = 30, + z = 1, + }) + + local content = Gui.new({ + parent = cells[row][col], + id = "content_" .. row .. "_" .. col, + positioning = Positioning.ABSOLUTE, + x = 5, + y = 35, + w = cellWidth - 10, + h = cellHeight - 40, + z = 1, + }) + + -- Content has multiple items + for i = 1, 3 do + Gui.new({ + parent = content, + id = "item_" .. row .. "_" .. col .. "_" .. i, + positioning = Positioning.ABSOLUTE, + x = 10, + y = i * 25, + w = cellWidth - 30, + h = 20, + z = i, + }) + end + end + end + + -- Verify grid structure + luaunit.assertEquals(#grid.children, rows * cols) + + for row = 1, rows do + for col = 1, cols do + local cell = cells[row][col] + local expectedX = (col - 1) * (cellWidth + gap) + local expectedY = (row - 1) * (cellHeight + gap) + + luaunit.assertEquals(cell.x, expectedX) + luaunit.assertEquals(cell.y, expectedY) + luaunit.assertEquals(#cell.children, 2) -- header + content + + local content = cell.children[2] + luaunit.assertEquals(#content.children, 3) -- 3 items + end + end +end + +-- Test 20: Complex nested modal/dialog system +function TestAbsolutePositioningChildLayout:testComplexModalDialogSystem() + local app = Gui.new({ + id = "app", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1920, + h = 1080, + z = 0, + }) + + -- Main content + local mainContent = Gui.new({ + parent = app, + id = "mainContent", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1920, + h = 1080, + z = 1, + }) + + -- Modal overlay + local modalOverlay = Gui.new({ + parent = app, + id = "modalOverlay", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1920, + h = 1080, + z = 1000, -- High z-index for overlay + }) + + -- Primary modal + local primaryModal = Gui.new({ + parent = modalOverlay, + id = "primaryModal", + positioning = Positioning.ABSOLUTE, + x = 460, -- Centered: (1920 - 1000) / 2 + y = 290, -- Centered: (1080 - 500) / 2 + w = 1000, + h = 500, + z = 1001, + }) + + -- Modal header + local modalHeader = Gui.new({ + parent = primaryModal, + id = "modalHeader", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1000, + h = 50, + z = 1, + }) + + -- Modal content with tabs + local modalContent = Gui.new({ + parent = primaryModal, + id = "modalContent", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 50, + w = 1000, + h = 400, + z = 1, + }) + + -- Tab system + local tabContainer = Gui.new({ + parent = modalContent, + id = "tabContainer", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1000, + h = 40, + z = 2, + }) + + -- Create tabs + for i = 1, 4 do + Gui.new({ + parent = tabContainer, + id = "tab" .. i, + positioning = Positioning.ABSOLUTE, + x = (i - 1) * 250, + y = 0, + w = 250, + h = 40, + z = i, + }) + end + + -- Tab content area + local tabContentArea = Gui.new({ + parent = modalContent, + id = "tabContentArea", + positioning = Positioning.ABSOLUTE, + x = 10, + y = 50, + w = 980, + h = 340, + z = 1, + }) + + -- Secondary modal (popup within modal) + local secondaryModal = Gui.new({ + parent = modalOverlay, + id = "secondaryModal", + positioning = Positioning.ABSOLUTE, + x = 710, -- Offset from primary modal + y = 340, + w = 500, + h = 400, + z = 1002, -- Above primary modal + }) + + -- Tooltip system + local tooltipContainer = Gui.new({ + parent = app, + id = "tooltipContainer", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1920, + h = 1080, + z = 2000, -- Highest z-index + }) + + local tooltip = Gui.new({ + parent = tooltipContainer, + id = "tooltip", + positioning = Positioning.ABSOLUTE, + x = 800, + y = 600, + w = 200, + h = 50, + z = 2001, + }) + + -- Verify complex modal structure + luaunit.assertEquals(#app.children, 3) -- main, modal overlay, tooltip container + luaunit.assertEquals(#modalOverlay.children, 2) -- primary + secondary modal + luaunit.assertEquals(#primaryModal.children, 2) -- header + content + luaunit.assertEquals(#modalContent.children, 2) -- tab container + content area + luaunit.assertEquals(#tabContainer.children, 4) -- 4 tabs + luaunit.assertEquals(#tooltipContainer.children, 1) -- tooltip + + -- Verify z-index hierarchy + luaunit.assertEquals(mainContent.z, 1) + luaunit.assertEquals(modalOverlay.z, 1000) + luaunit.assertEquals(primaryModal.z, 1001) + luaunit.assertEquals(secondaryModal.z, 1002) + luaunit.assertEquals(tooltipContainer.z, 2000) + luaunit.assertEquals(tooltip.z, 2001) +end + +-- Test 21: Tree with dynamic branching (simulating DOM-like structure) +function TestAbsolutePositioningChildLayout:testDynamicBranchingDOMStructure() + -- Simulate a complex web page structure + local document = Gui.new({ + id = "document", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1200, + h = 2000, + z = 0, + }) + + -- Header + local header = Gui.new({ + parent = document, + id = "header", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 1200, + h = 100, + z = 10, + }) + + -- Navigation in header + local nav = Gui.new({ + parent = header, + id = "nav", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 20, + w = 1000, + h = 60, + z = 1, + }) + + -- Nav items + for i = 1, 5 do + local navItem = Gui.new({ + parent = nav, + id = "navItem" .. i, + positioning = Positioning.ABSOLUTE, + x = (i - 1) * 200, + y = 0, + w = 180, + h = 60, + z = i, + }) + + -- Dropdown for each nav item + if i <= 3 then -- Only first 3 have dropdowns + local dropdown = Gui.new({ + parent = navItem, + id = "dropdown" .. i, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 60, + w = 180, + h = 200, + z = 100, -- High z-index for dropdown + }) + + -- Dropdown items + for j = 1, 4 do + Gui.new({ + parent = dropdown, + id = "dropdownItem" .. i .. "_" .. j, + positioning = Positioning.ABSOLUTE, + x = 0, + y = (j - 1) * 50, + w = 180, + h = 50, + z = j, + }) + end + end + end + + -- Main content area + local main = Gui.new({ + parent = document, + id = "main", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 100, + w = 1200, + h = 1700, + z = 1, + }) + + -- Sidebar + local sidebar = Gui.new({ + parent = main, + id = "sidebar", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 300, + h = 1700, + z = 2, + }) + + -- Sidebar widgets + for i = 1, 6 do + local widget = Gui.new({ + parent = sidebar, + id = "widget" .. i, + positioning = Positioning.ABSOLUTE, + x = 10, + y = (i - 1) * 280 + 10, + w = 280, + h = 260, + z = i, + }) + + -- Widget header + Gui.new({ + parent = widget, + id = "widgetHeader" .. i, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 280, + h = 40, + z = 1, + }) + + -- Widget content with items + local widgetContent = Gui.new({ + parent = widget, + id = "widgetContent" .. i, + positioning = Positioning.ABSOLUTE, + x = 5, + y = 45, + w = 270, + h = 210, + z = 1, + }) + + -- Items in widget + for j = 1, 4 do + Gui.new({ + parent = widgetContent, + id = "widgetItem" .. i .. "_" .. j, + positioning = Positioning.ABSOLUTE, + x = 5, + y = (j - 1) * 50, + w = 260, + h = 45, + z = j, + }) + end + end + + -- Content area + local content = Gui.new({ + parent = main, + id = "content", + positioning = Positioning.ABSOLUTE, + x = 320, + y = 0, + w = 880, + h = 1700, + z = 1, + }) + + -- Articles in content + for i = 1, 3 do + local article = Gui.new({ + parent = content, + id = "article" .. i, + positioning = Positioning.ABSOLUTE, + x = 20, + y = (i - 1) * 550 + 20, + w = 840, + h = 500, + z = i, + }) + + -- Article header, content, footer + local articleHeader = Gui.new({ + parent = article, + id = "articleHeader" .. i, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 0, + w = 840, + h = 80, + z = 1, + }) + + local articleContent = Gui.new({ + parent = article, + id = "articleContent" .. i, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 80, + w = 840, + h = 350, + z = 1, + }) + + local articleFooter = Gui.new({ + parent = article, + id = "articleFooter" .. i, + positioning = Positioning.ABSOLUTE, + x = 0, + y = 430, + w = 840, + h = 70, + z = 1, + }) + + -- Comments in article content + for j = 1, 3 do + Gui.new({ + parent = articleContent, + id = "comment" .. i .. "_" .. j, + positioning = Positioning.ABSOLUTE, + x = 20, + y = 50 + (j - 1) * 100, + w = 800, + h = 80, + z = j, + }) + end + end + + -- Footer + local footer = Gui.new({ + parent = document, + id = "footer", + positioning = Positioning.ABSOLUTE, + x = 0, + y = 1800, + w = 1200, + h = 200, + z = 10, + }) + + -- Verify complex DOM structure + luaunit.assertEquals(#document.children, 3) -- header, main, footer + luaunit.assertEquals(#header.children, 1) -- nav + luaunit.assertEquals(#nav.children, 5) -- 5 nav items + luaunit.assertEquals(#main.children, 2) -- sidebar, content + luaunit.assertEquals(#sidebar.children, 6) -- 6 widgets + luaunit.assertEquals(#content.children, 3) -- 3 articles + + -- Verify nav dropdowns + for i = 1, 3 do + local navItem = nav.children[i] + luaunit.assertEquals(#navItem.children, 1) -- dropdown + local dropdown = navItem.children[1] + luaunit.assertEquals(#dropdown.children, 4) -- 4 dropdown items + end + + -- Verify widgets + for i = 1, 6 do + local widget = sidebar.children[i] + luaunit.assertEquals(#widget.children, 2) -- header + content + local widgetContent = widget.children[2] + luaunit.assertEquals(#widgetContent.children, 4) -- 4 items + end + + -- Verify articles + for i = 1, 3 do + local article = content.children[i] + luaunit.assertEquals(#article.children, 3) -- header, content, footer + local articleContent = article.children[2] + luaunit.assertEquals(#articleContent.children, 3) -- 3 comments + end +end + +-- Run the tests +luaunit.LuaUnit.run() + diff --git a/testing/__tests__/03_flex_direction_horizontal_tests.lua b/testing/__tests__/03_flex_direction_horizontal_tests.lua index bca10df..7d53b3e 100644 --- a/testing/__tests__/03_flex_direction_horizontal_tests.lua +++ b/testing/__tests__/03_flex_direction_horizontal_tests.lua @@ -17,509 +17,1595 @@ local AlignItems = enums.AlignItems TestHorizontalFlexDirection = {} function TestHorizontalFlexDirection:setUp() - -- Clean up before each test - Gui.destroy() + -- Clean up before each test + Gui.destroy() end function TestHorizontalFlexDirection:tearDown() - -- Clean up after each test - Gui.destroy() + -- Clean up after each test + Gui.destroy() end -- Test 1: Basic element creation with horizontal flex direction function TestHorizontalFlexDirection:testCreateElementWithHorizontalFlexDirection() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - -- Verify element was created with correct properties - luaunit.assertEquals(parent.positioning, Positioning.FLEX) - luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(parent.width, 300) - luaunit.assertEquals(parent.height, 100) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + -- Verify element was created with correct properties + luaunit.assertEquals(parent.positioning, Positioning.FLEX) + luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) + luaunit.assertEquals(parent.width, 300) + luaunit.assertEquals(parent.height, 100) end -- Test 2: Default flex direction should be horizontal function TestHorizontalFlexDirection:testDefaultFlexDirectionIsHorizontal() - local parent = Gui.new({ - id = "default_parent", - positioning = Positioning.FLEX, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - -- Default flex direction should be horizontal - luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) + local parent = Gui.new({ + id = "default_parent", + positioning = Positioning.FLEX, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + -- Default flex direction should be horizontal + luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) end -- Test 3: Children positioned horizontally along x-axis function TestHorizontalFlexDirection:testChildrenPositionedHorizontally() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - local child3 = Gui.new({ - id = "child3", - w = 40, - h = 35 - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- Children should be positioned horizontally - -- child1 should be at x=0 (start) - luaunit.assertEquals(child1.x, 0) - - -- child2 should be positioned after child1 + gap - local expectedChild2X = child1.width + parent.gap - luaunit.assertEquals(child2.x, expectedChild2X) - - -- child3 should be positioned after child2 + gap - local expectedChild3X = child1.width + parent.gap + child2.width + parent.gap - luaunit.assertEquals(child3.x, expectedChild3X) - - -- All children should have same y position as parent - luaunit.assertEquals(child1.y, parent.y) - luaunit.assertEquals(child2.y, parent.y) - luaunit.assertEquals(child3.y, parent.y) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + local child3 = Gui.new({ + id = "child3", + w = 40, + h = 35, + }) + + parent:addChild(child1) + parent:addChild(child2) + parent:addChild(child3) + + -- Children should be positioned horizontally + -- child1 should be at x=0 (start) + luaunit.assertEquals(child1.x, 0) + + -- child2 should be positioned after child1 + gap + local expectedChild2X = child1.width + parent.gap + luaunit.assertEquals(child2.x, expectedChild2X) + + -- child3 should be positioned after child2 + gap + local expectedChild3X = child1.width + parent.gap + child2.width + parent.gap + luaunit.assertEquals(child3.x, expectedChild3X) + + -- All children should have same y position as parent + luaunit.assertEquals(child1.y, parent.y) + luaunit.assertEquals(child2.y, parent.y) + luaunit.assertEquals(child3.y, parent.y) end -- Test 4: Horizontal layout with gap property function TestHorizontalFlexDirection:testHorizontalLayoutWithGap() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - gap = 20, -- Custom gap - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Verify gap is applied correctly - luaunit.assertEquals(parent.gap, 20) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, child1.width + 20) -- 50 + 20 = 70 + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + gap = 20, -- Custom gap + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Verify gap is applied correctly + luaunit.assertEquals(parent.gap, 20) + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, child1.width + 20) -- 50 + 20 = 70 end -- Test 5: Horizontal layout with flex-start justification (default) function TestHorizontalFlexDirection:testHorizontalLayoutFlexStart() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With flex-start, children should start at the beginning - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, child1.width + parent.gap) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- With flex-start, children should start at the beginning + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, child1.width + parent.gap) end -- Test 6: Horizontal layout with center justification function TestHorizontalFlexDirection:testHorizontalLayoutCenter() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - x = 0, - y = 0, - w = 300, - h = 100, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected center positioning - local totalChildWidth = child1.width + child2.width + parent.gap - local availableSpace = parent.width - totalChildWidth - local startX = availableSpace / 2 - - luaunit.assertEquals(child1.x, startX) - luaunit.assertEquals(child2.x, startX + child1.width + parent.gap) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + x = 0, + y = 0, + w = 300, + h = 100, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Calculate expected center positioning + local totalChildWidth = child1.width + child2.width + parent.gap + local availableSpace = parent.width - totalChildWidth + local startX = availableSpace / 2 + + luaunit.assertEquals(child1.x, startX) + luaunit.assertEquals(child2.x, startX + child1.width + parent.gap) end -- Test 7: Horizontal layout with flex-end justification function TestHorizontalFlexDirection:testHorizontalLayoutFlexEnd() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - x = 0, - y = 0, - w = 300, - h = 100, - gap = 10 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected flex-end positioning - local totalChildWidth = child1.width + child2.width + parent.gap - local availableSpace = parent.width - totalChildWidth - - luaunit.assertEquals(child1.x, availableSpace) - luaunit.assertEquals(child2.x, availableSpace + child1.width + parent.gap) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + x = 0, + y = 0, + w = 300, + h = 100, + gap = 10, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Calculate expected flex-end positioning + local totalChildWidth = child1.width + child2.width + parent.gap + local availableSpace = parent.width - totalChildWidth + + luaunit.assertEquals(child1.x, availableSpace) + luaunit.assertEquals(child2.x, availableSpace + child1.width + parent.gap) end -- Test 8: Horizontal layout with space-between justification function TestHorizontalFlexDirection:testHorizontalLayoutSpaceBetween() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - x = 0, - y = 0, - w = 300, - h = 100, - gap = 0 -- Space-between doesn't use gap, it distributes available space - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - local child3 = Gui.new({ - id = "child3", - w = 40, - h = 35 - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- With space-between, first child at start, last at end, others distributed - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child3.x, parent.width - child3.width) - - -- child2 should be positioned in the middle - local availableSpace = parent.width - (child1.width + child2.width + child3.width) - local spaceBetweenItems = availableSpace / 2 -- 2 gaps for 3 children - luaunit.assertEquals(child2.x, child1.width + spaceBetweenItems) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + x = 0, + y = 0, + w = 300, + h = 100, + gap = 0, -- Space-between doesn't use gap, it distributes available space + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + local child3 = Gui.new({ + id = "child3", + w = 40, + h = 35, + }) + + parent:addChild(child1) + parent:addChild(child2) + parent:addChild(child3) + + -- With space-between, first child at start, last at end, others distributed + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child3.x, parent.width - child3.width) + + -- child2 should be positioned in the middle + local availableSpace = parent.width - (child1.width + child2.width + child3.width) + local spaceBetweenItems = availableSpace / 2 -- 2 gaps for 3 children + luaunit.assertEquals(child2.x, child1.width + spaceBetweenItems) end -- Test 9: Single child in horizontal layout function TestHorizontalFlexDirection:testSingleChildHorizontalLayout() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - x = 10, - y = 20, - w = 300, - h = 100 - }) - - local child = Gui.new({ - id = "single_child", - w = 50, - h = 30 - }) - - parent:addChild(child) - - -- Single child with center justification should be centered - local expectedX = parent.x + (parent.width - child.width) / 2 - luaunit.assertEquals(child.x, expectedX) - luaunit.assertEquals(child.y, parent.y) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + x = 10, + y = 20, + w = 300, + h = 100, + }) + + local child = Gui.new({ + id = "single_child", + w = 50, + h = 30, + }) + + parent:addChild(child) + + -- Single child with center justification should be centered + local expectedX = parent.x + (parent.width - child.width) / 2 + luaunit.assertEquals(child.x, expectedX) + luaunit.assertEquals(child.y, parent.y) end -- Test 10: Empty parent (no children) horizontal layout function TestHorizontalFlexDirection:testEmptyParentHorizontalLayout() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - -- No children added - luaunit.assertEquals(#parent.children, 0) - - -- Should not cause any errors when layoutChildren is called - parent:layoutChildren() -- This should not throw an error + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + -- No children added + luaunit.assertEquals(#parent.children, 0) + + -- Should not cause any errors when layoutChildren is called + parent:layoutChildren() -- This should not throw an error end -- Test 11: Horizontal layout coordinate system relative to parent function TestHorizontalFlexDirection:testHorizontalLayoutCoordinateSystem() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 100, - y = 50, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children coordinates should be relative to parent position - luaunit.assertEquals(child1.x, parent.x + 0) -- First child at parent's x - luaunit.assertEquals(child1.y, parent.y) -- Same y as parent - - luaunit.assertEquals(child2.x, parent.x + child1.width + parent.gap) - luaunit.assertEquals(child2.y, parent.y) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + x = 100, + y = 50, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- Children coordinates should be relative to parent position + luaunit.assertEquals(child1.x, parent.x + 0) -- First child at parent's x + luaunit.assertEquals(child1.y, parent.y) -- Same y as parent + + luaunit.assertEquals(child2.x, parent.x + child1.width + parent.gap) + luaunit.assertEquals(child2.y, parent.y) end -- Test 12: Horizontal layout maintains child heights function TestHorizontalFlexDirection:testHorizontalLayoutMaintainsChildHeights() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, -- Explicitly set to maintain child heights - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 70 -- Different height - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- In horizontal layout, child heights should be preserved - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 70) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_START, -- Explicitly set to maintain child heights + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 70, -- Different height + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- In horizontal layout, child heights should be preserved + luaunit.assertEquals(child1.height, 30) + luaunit.assertEquals(child2.height, 70) end -- Test 13: Horizontal layout with align-items stretch function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsStretch() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items stretch in horizontal layout, children should stretch to parent height - luaunit.assertEquals(child1.height, parent.height) - luaunit.assertEquals(child2.height, parent.height) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.STRETCH, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- With align-items stretch in horizontal layout, children should stretch to parent height + luaunit.assertEquals(child1.height, parent.height) + luaunit.assertEquals(child2.height, parent.height) end -- Test 14: Horizontal layout with align-items center function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsCenter() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items center in horizontal layout, children should be centered vertically - local expectedChild1Y = parent.y + (parent.height - child1.height) / 2 - local expectedChild2Y = parent.y + (parent.height - child2.height) / 2 - - luaunit.assertEquals(child1.y, expectedChild1Y) - luaunit.assertEquals(child2.y, expectedChild2Y) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- With align-items center in horizontal layout, children should be centered vertically + local expectedChild1Y = parent.y + (parent.height - child1.height) / 2 + local expectedChild2Y = parent.y + (parent.height - child2.height) / 2 + + luaunit.assertEquals(child1.y, expectedChild1Y) + luaunit.assertEquals(child2.y, expectedChild2Y) end -- Test 15: Horizontal layout with align-items flex-end function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsFlexEnd() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - x = 0, - y = 0, - w = 300, - h = 100 - }) - - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30 - }) - - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40 - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items flex-end in horizontal layout, children should be aligned to bottom - local expectedChild1Y = parent.y + parent.height - child1.height - local expectedChild2Y = parent.y + parent.height - child2.height - - luaunit.assertEquals(child1.y, expectedChild1Y) - luaunit.assertEquals(child2.y, expectedChild2Y) + local parent = Gui.new({ + id = "horizontal_parent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.FLEX_END, + x = 0, + y = 0, + w = 300, + h = 100, + }) + + local child1 = Gui.new({ + id = "child1", + w = 50, + h = 30, + }) + + local child2 = Gui.new({ + id = "child2", + w = 60, + h = 40, + }) + + parent:addChild(child1) + parent:addChild(child2) + + -- With align-items flex-end in horizontal layout, children should be aligned to bottom + local expectedChild1Y = parent.y + parent.height - child1.height + local expectedChild2Y = parent.y + parent.height - child2.height + + luaunit.assertEquals(child1.y, expectedChild1Y) + luaunit.assertEquals(child2.y, expectedChild2Y) end -- Run the tests if arg and arg[0] == debug.getinfo(1, "S").source:sub(2) then - os.exit(luaunit.LuaUnit.run()) -end \ No newline at end of file + os.exit(luaunit.LuaUnit.run()) +end + +-- =========================================================================== +-- COMPLEX NESTED FLEX CONTAINER TESTS +-- =========================================================================== + +-- Test 16: Nested horizontal flex containers (flexbox within flexbox) +function TestHorizontalFlexDirection:testNestedHorizontalFlexContainers() + local outerContainer = Gui.new({ + id = "outerContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + x = 0, + y = 0, + w = 1200, + h = 300, + gap = 20, + }) + + -- Create 3 nested flex containers + for i = 1, 3 do + local innerContainer = Gui.new({ + parent = outerContainer, + id = "innerContainer" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 350, + h = 280, + gap = 10, + }) + + -- Each inner container has 4 flex items + for j = 1, 4 do + Gui.new({ + parent = innerContainer, + id = "item" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = 70, + h = 120, + }) + end + end + + -- Verify nested structure + luaunit.assertEquals(#outerContainer.children, 3) + + for i = 1, 3 do + local innerContainer = outerContainer.children[i] + luaunit.assertEquals(innerContainer.positioning, Positioning.FLEX) + luaunit.assertEquals(innerContainer.flexDirection, FlexDirection.HORIZONTAL) + luaunit.assertEquals(#innerContainer.children, 4) + + -- Verify inner items are positioned horizontally within their container + for j = 1, 4 do + local item = innerContainer.children[j] + luaunit.assertEquals(item.positioning, Positioning.FLEX) + luaunit.assertEquals(item.width, 70) + luaunit.assertEquals(item.height, 120) + end + end +end + +-- Test 17: Complex grid layout using nested horizontal flex +function TestHorizontalFlexDirection:testComplexGridLayoutNestedHorizontalFlex() + local gridContainer = Gui.new({ + id = "gridContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 1000, + h = 800, + gap = 15, + }) + + -- Create 4 rows, each being a horizontal flex container + for row = 1, 4 do + local rowContainer = Gui.new({ + parent = gridContainer, + id = "row" .. row, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + w = 980, + h = 180, + gap = 12, + }) + + -- Each row has varying number of columns + local colCount = row + 2 -- Row 1 has 3 cols, row 2 has 4 cols, etc. + for col = 1, colCount do + local cell = Gui.new({ + parent = rowContainer, + id = "cell" .. row .. "_" .. col, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = (980 - (colCount - 1) * 12) / colCount, -- Dynamic width + h = 160, + }) + + -- Each cell contains a nested horizontal layout with icons + for icon = 1, 3 do + Gui.new({ + parent = cell, + id = "icon" .. row .. "_" .. col .. "_" .. icon, + positioning = Positioning.FLEX, + w = 30, + h = 30, + }) + end + end + end + + -- Verify grid structure + luaunit.assertEquals(#gridContainer.children, 4) + + for row = 1, 4 do + local rowContainer = gridContainer.children[row] + local expectedColCount = row + 2 + luaunit.assertEquals(#rowContainer.children, expectedColCount) + luaunit.assertEquals(rowContainer.flexDirection, FlexDirection.HORIZONTAL) + luaunit.assertEquals(rowContainer.justifyContent, JustifyContent.SPACE_EVENLY) + + for col = 1, expectedColCount do + local cell = rowContainer.children[col] + luaunit.assertEquals(#cell.children, 3) -- 3 icons per cell + luaunit.assertEquals(cell.flexDirection, FlexDirection.HORIZONTAL) + luaunit.assertEquals(cell.justifyContent, JustifyContent.CENTER) + end + end +end + +-- Test 18: Horizontal flex with mixed positioning children (absolute within flex) +function TestHorizontalFlexDirection:testHorizontalFlexWithMixedPositioningChildren() + local flexContainer = Gui.new({ + id = "flexContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + x = 100, + y = 100, + w = 800, + h = 200, + gap = 20, + }) + + -- Add flex children + local flexChild1 = Gui.new({ + parent = flexContainer, + id = "flexChild1", + positioning = Positioning.FLEX, + w = 150, + h = 180, + }) + + local flexChild2 = Gui.new({ + parent = flexContainer, + id = "flexChild2", + positioning = Positioning.FLEX, + w = 150, + h = 180, + }) + + local flexChild3 = Gui.new({ + parent = flexContainer, + id = "flexChild3", + positioning = Positioning.FLEX, + w = 150, + h = 180, + }) + + -- Add absolute positioned children (should not participate in flex layout) + local absoluteChild1 = Gui.new({ + parent = flexContainer, + id = "absoluteChild1", + positioning = Positioning.ABSOLUTE, + x = 600, + y = 50, + w = 100, + h = 100, + }) + + local absoluteChild2 = Gui.new({ + parent = flexContainer, + id = "absoluteChild2", + positioning = Positioning.ABSOLUTE, + x = 650, + y = 75, + w = 80, + h = 80, + }) + + -- Add nested flex containers within flex children + for i, flexChild in ipairs({ flexChild1, flexChild2, flexChild3 }) do + local nestedFlex = Gui.new({ + parent = flexChild, + id = "nestedFlex" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 140, + h = 170, + gap = 5, + }) + + -- Add items to nested flex + for j = 1, 2 do + Gui.new({ + parent = nestedFlex, + id = "nestedItem" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = 60, + h = 80, + }) + end + + -- Add absolute child to flex child + Gui.new({ + parent = flexChild, + id = "absInFlex" .. i, + positioning = Positioning.ABSOLUTE, + x = 120, + y = 150, + w = 25, + h = 25, + }) + end + + -- Verify mixed positioning structure + luaunit.assertEquals(#flexContainer.children, 5) -- 3 flex + 2 absolute + + -- Verify absolute children maintain their positions + luaunit.assertEquals(absoluteChild1.x, 600) + luaunit.assertEquals(absoluteChild1.y, 50) + luaunit.assertEquals(absoluteChild2.x, 650) + luaunit.assertEquals(absoluteChild2.y, 75) + + -- Verify nested flex structures + for i = 1, 3 do + local flexChild = flexContainer.children[i] + luaunit.assertEquals(#flexChild.children, 2) -- nested flex + absolute + + local nestedFlex = flexChild.children[1] + luaunit.assertEquals(nestedFlex.positioning, Positioning.FLEX) + luaunit.assertEquals(#nestedFlex.children, 2) + + local absInFlex = flexChild.children[2] + luaunit.assertEquals(absInFlex.positioning, Positioning.ABSOLUTE) + luaunit.assertEquals(absInFlex.x, 120) + luaunit.assertEquals(absInFlex.y, 150) + end +end + +-- Test 19: Multi-level horizontal flex navigation system +function TestHorizontalFlexDirection:testMultiLevelHorizontalFlexNavigation() + local navSystem = Gui.new({ + id = "navSystem", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 1200, + h = 400, + gap = 0, + }) + + -- Primary navigation (horizontal) + local primaryNav = Gui.new({ + parent = navSystem, + id = "primaryNav", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 1200, + h = 60, + gap = 0, + }) + + -- Primary nav items + local primaryItems = { "Home", "Products", "Services", "About", "Contact" } + for i, itemName in ipairs(primaryItems) do + local primaryItem = Gui.new({ + parent = primaryNav, + id = "primaryItem" .. i, + positioning = Positioning.FLEX, + w = 240, + h = 60, + }) + + -- Some primary items have secondary navigation + if i == 2 or i == 3 then -- Products and Services have sub-menus + local secondaryNav = Gui.new({ + parent = primaryItem, + id = "secondaryNav" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 240, + h = 40, + gap = 5, + }) + + -- Secondary nav items + local secCount = i == 2 and 4 or 3 -- Products has 4, Services has 3 + for j = 1, secCount do + local secondaryItem = Gui.new({ + parent = secondaryNav, + id = "secondaryItem" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = (240 - (secCount - 1) * 5) / secCount, + h = 35, + }) + + -- Tertiary items for some secondary items + if j <= 2 then + local tertiaryNav = Gui.new({ + parent = secondaryItem, + id = "tertiaryNav" .. i .. "_" .. j, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + w = (240 - (secCount - 1) * 5) / secCount, + h = 25, + gap = 2, + }) + + -- Tertiary items + for k = 1, 2 do + Gui.new({ + parent = tertiaryNav, + id = "tertiaryItem" .. i .. "_" .. j .. "_" .. k, + positioning = Positioning.FLEX, + w = ((240 - (secCount - 1) * 5) / secCount - 2) / 2, + h = 20, + }) + end + end + end + end + end + + -- Secondary navigation bar (horizontal) + local secondaryNavBar = Gui.new({ + parent = navSystem, + id = "secondaryNavBar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 1200, + h = 50, + gap = 30, + }) + + -- Breadcrumb navigation + for i = 1, 5 do + local breadcrumb = Gui.new({ + parent = secondaryNavBar, + id = "breadcrumb" .. i, + positioning = Positioning.FLEX, + w = 120, + h = 40, + }) + end + + -- Verify navigation structure + luaunit.assertEquals(#navSystem.children, 2) -- primary nav + secondary nav bar + luaunit.assertEquals(#primaryNav.children, 5) -- 5 primary items + luaunit.assertEquals(#secondaryNavBar.children, 5) -- 5 breadcrumbs + + -- Verify Products (item 2) has secondary navigation + local productsItem = primaryNav.children[2] + luaunit.assertEquals(#productsItem.children, 1) -- secondary nav + local productsSecondary = productsItem.children[1] + luaunit.assertEquals(#productsSecondary.children, 4) -- 4 secondary items + + -- Verify tertiary navigation exists + for j = 1, 2 do + local secondaryItem = productsSecondary.children[j] + luaunit.assertEquals(#secondaryItem.children, 1) -- tertiary nav + local tertiaryNav = secondaryItem.children[1] + luaunit.assertEquals(#tertiaryNav.children, 2) -- 2 tertiary items + end +end + +-- Test 20: Horizontal flex card layout with dynamic sizing +function TestHorizontalFlexDirection:testHorizontalFlexCardLayoutDynamicSizing() + local cardContainer = Gui.new({ + id = "cardContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + x = 50, + y = 50, + w = 1400, + h = 600, + gap = 25, + }) + + -- Create cards with different content complexities + local cardConfigs = { + { width = 300, items = 3, hasImage = true }, + { width = 250, items = 2, hasImage = false }, + { width = 350, items = 4, hasImage = true }, + { width = 280, items = 3, hasImage = true }, + { width = 320, items = 5, hasImage = false }, + } + + for i, config in ipairs(cardConfigs) do + local card = Gui.new({ + parent = cardContainer, + id = "card" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = config.width, + h = 550, + gap = 10, + }) + + -- Card header (horizontal layout) + local cardHeader = Gui.new({ + parent = card, + id = "cardHeader" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = config.width - 20, + h = 50, + gap = 10, + }) + + -- Header title and actions + Gui.new({ + parent = cardHeader, + id = "cardTitle" .. i, + positioning = Positioning.FLEX, + w = (config.width - 30) * 0.7, + h = 40, + }) + + local headerActions = Gui.new({ + parent = cardHeader, + id = "headerActions" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + w = (config.width - 30) * 0.3, + h = 40, + gap = 5, + }) + + -- Action buttons + for j = 1, 2 do + Gui.new({ + parent = headerActions, + id = "actionBtn" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = ((config.width - 30) * 0.3 - 5) / 2, + h = 35, + }) + end + + -- Card image (if configured) + if config.hasImage then + Gui.new({ + parent = card, + id = "cardImage" .. i, + positioning = Positioning.FLEX, + w = config.width - 20, + h = 200, + }) + end + + -- Card content area + local cardContent = Gui.new({ + parent = card, + id = "cardContent" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = config.width - 20, + h = config.hasImage and 240 or 440, + gap = 8, + }) + + -- Content items + for j = 1, config.items do + local contentItem = Gui.new({ + parent = cardContent, + id = "contentItem" .. i .. "_" .. j, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + w = config.width - 40, + h = (config.hasImage and 230 or 430) / config.items - 8, + gap = 10, + }) + + -- Item icon and text + Gui.new({ + parent = contentItem, + id = "itemIcon" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = 30, + h = (config.hasImage and 230 or 430) / config.items - 8, + }) + + Gui.new({ + parent = contentItem, + id = "itemText" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = config.width - 80, + h = (config.hasImage and 230 or 430) / config.items - 8, + }) + end + + -- Card footer (horizontal layout) + local cardFooter = Gui.new({ + parent = card, + id = "cardFooter" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = config.width - 20, + h = 50, + gap = 15, + }) + + -- Footer buttons + for j = 1, 3 do + Gui.new({ + parent = cardFooter, + id = "footerBtn" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = (config.width - 50) / 3, + h = 40, + }) + end + end + + -- Verify card structure + luaunit.assertEquals(#cardContainer.children, 5) + + for i, config in ipairs(cardConfigs) do + local card = cardContainer.children[i] + luaunit.assertEquals(card.width, config.width) + + -- Count expected children: header + content + footer + optional image + local expectedChildren = 3 + (config.hasImage and 1 or 0) + luaunit.assertEquals(#card.children, expectedChildren) + + -- Verify header structure + local cardHeader = card.children[1] + luaunit.assertEquals(#cardHeader.children, 2) -- title + actions + local headerActions = cardHeader.children[2] + luaunit.assertEquals(#headerActions.children, 2) -- 2 action buttons + + -- Verify content structure + local contentIndex = config.hasImage and 3 or 2 + local cardContent = card.children[contentIndex] + luaunit.assertEquals(#cardContent.children, config.items) + + -- Verify each content item has icon and text + for j = 1, config.items do + local contentItem = cardContent.children[j] + luaunit.assertEquals(#contentItem.children, 2) -- icon + text + end + + -- Verify footer structure + local footerIndex = config.hasImage and 4 or 3 + local cardFooter = card.children[footerIndex] + luaunit.assertEquals(#cardFooter.children, 3) -- 3 footer buttons + end +end + +-- Test 21: Horizontal flex with overflow and scrolling simulation +function TestHorizontalFlexDirection:testHorizontalFlexOverflowScrolling() + local scrollContainer = Gui.new({ + id = "scrollContainer", + positioning = Positioning.ABSOLUTE, + x = 100, + y = 100, + w = 800, + h = 200, + }) + + -- Content container (wider than viewport) + local contentContainer = Gui.new({ + parent = scrollContainer, + id = "contentContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + x = 0, -- This would change with scrolling + y = 0, + w = 2000, -- Wider than scroll container + h = 180, + gap = 20, + }) + + -- Create many items that exceed viewport width + for i = 1, 15 do + local item = Gui.new({ + parent = contentContainer, + id = "scrollItem" .. i, + positioning = Positioning.FLEX, + w = 120, + h = 160, + }) + + -- Each item has internal horizontal layout + local itemContent = Gui.new({ + parent = item, + id = "itemContent" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 110, + h = 150, + gap = 5, + }) + + -- Item components + for j = 1, 2 do + local component = Gui.new({ + parent = itemContent, + id = "component" .. i .. "_" .. j, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 50, + h = 140, + gap = 3, + }) + + -- Sub-components in vertical layout + for k = 1, 3 do + Gui.new({ + parent = component, + id = "subComponent" .. i .. "_" .. j .. "_" .. k, + positioning = Positioning.FLEX, + w = 45, + h = 42, + }) + end + end + end + + -- Verify overflow structure + luaunit.assertEquals(#scrollContainer.children, 1) + luaunit.assertEquals(#contentContainer.children, 15) + + -- Verify content is wider than container + luaunit.assertTrue(contentContainer.width > scrollContainer.width) + + -- Verify nested structure + for i = 1, 15 do + local item = contentContainer.children[i] + luaunit.assertEquals(#item.children, 1) -- item content + + local itemContent = item.children[1] + luaunit.assertEquals(#itemContent.children, 2) -- 2 components + luaunit.assertEquals(itemContent.flexDirection, FlexDirection.HORIZONTAL) + + for j = 1, 2 do + local component = itemContent.children[j] + luaunit.assertEquals(#component.children, 3) -- 3 sub-components + luaunit.assertEquals(component.flexDirection, FlexDirection.VERTICAL) + end + end +end + +-- Test 22: Complex horizontal dashboard layout +function TestHorizontalFlexDirection:testComplexHorizontalDashboardLayout() + local dashboard = Gui.new({ + id = "dashboard", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 1600, + h = 1000, + gap = 0, + }) + + -- Dashboard header (horizontal) + local header = Gui.new({ + parent = dashboard, + id = "dashboardHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 1600, + h = 80, + gap = 20, + }) + + -- Header left section + local headerLeft = Gui.new({ + parent = header, + id = "headerLeft", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + w = 400, + h = 60, + gap = 15, + }) + + -- Logo and title + Gui.new({ + parent = headerLeft, + id = "logo", + positioning = Positioning.FLEX, + w = 60, + h = 60, + }) + + Gui.new({ + parent = headerLeft, + id = "title", + positioning = Positioning.FLEX, + w = 300, + h = 60, + }) + + -- Header center - search and navigation + local headerCenter = Gui.new({ + parent = header, + id = "headerCenter", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 800, + h = 60, + gap = 20, + }) + + -- Search bar + Gui.new({ + parent = headerCenter, + id = "searchBar", + positioning = Positioning.FLEX, + w = 400, + h = 50, + }) + + -- Quick actions + local quickActions = Gui.new({ + parent = headerCenter, + id = "quickActions", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + w = 300, + h = 50, + gap = 10, + }) + + for i = 1, 4 do + Gui.new({ + parent = quickActions, + id = "quickAction" .. i, + positioning = Positioning.FLEX, + w = 65, + h = 45, + }) + end + + -- Header right - user section + local headerRight = Gui.new({ + parent = header, + id = "headerRight", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + w = 300, + h = 60, + gap = 10, + }) + + -- Notifications and user menu + Gui.new({ + parent = headerRight, + id = "notifications", + positioning = Positioning.FLEX, + w = 50, + h = 50, + }) + + Gui.new({ + parent = headerRight, + id = "userMenu", + positioning = Positioning.FLEX, + w = 200, + h = 50, + }) + + -- Main dashboard content (horizontal sections) + local mainContent = Gui.new({ + parent = dashboard, + id = "mainContent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + w = 1600, + h = 920, + gap = 0, + }) + + -- Left sidebar + local leftSidebar = Gui.new({ + parent = mainContent, + id = "leftSidebar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 250, + h = 920, + gap = 10, + }) + + -- Sidebar sections + for i = 1, 5 do + local sidebarSection = Gui.new({ + parent = leftSidebar, + id = "sidebarSection" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 240, + h = 170, + gap = 5, + }) + + -- Section header + Gui.new({ + parent = sidebarSection, + id = "sectionHeader" .. i, + positioning = Positioning.FLEX, + w = 230, + h = 30, + }) + + -- Section items + for j = 1, 4 do + Gui.new({ + parent = sidebarSection, + id = "sectionItem" .. i .. "_" .. j, + positioning = Positioning.FLEX, + w = 230, + h = 30, + }) + end + end + + -- Center content area (horizontal widget layout) + local centerArea = Gui.new({ + parent = mainContent, + id = "centerArea", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 1100, + h = 920, + gap = 20, + }) + + -- Top widgets row + local topWidgets = Gui.new({ + parent = centerArea, + id = "topWidgets", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 1080, + h = 280, + gap = 20, + }) + + -- Create 4 top widgets + for i = 1, 4 do + local widget = Gui.new({ + parent = topWidgets, + id = "topWidget" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 250, + h = 260, + gap = 10, + }) + + -- Widget header + local widgetHeader = Gui.new({ + parent = widget, + id = "topWidgetHeader" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 240, + h = 40, + gap = 10, + }) + + Gui.new({ + parent = widgetHeader, + id = "widgetTitle" .. i, + positioning = Positioning.FLEX, + w = 180, + h = 35, + }) + + Gui.new({ + parent = widgetHeader, + id = "widgetControls" .. i, + positioning = Positioning.FLEX, + w = 50, + h = 35, + }) + + -- Widget content + Gui.new({ + parent = widget, + id = "topWidgetContent" .. i, + positioning = Positioning.FLEX, + w = 240, + h = 200, + }) + end + + -- Bottom content area + local bottomWidgets = Gui.new({ + parent = centerArea, + id = "bottomWidgets", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + w = 1080, + h = 600, + gap = 30, + }) + + -- Large widget and smaller widgets + local largeWidget = Gui.new({ + parent = bottomWidgets, + id = "largeWidget", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 700, + h = 580, + gap = 15, + }) + + -- Large widget components + Gui.new({ + parent = largeWidget, + id = "largeWidgetHeader", + positioning = Positioning.FLEX, + w = 680, + h = 50, + }) + + local largeWidgetContent = Gui.new({ + parent = largeWidget, + id = "largeWidgetContent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 680, + h = 500, + gap = 20, + }) + + -- Chart and details + Gui.new({ + parent = largeWidgetContent, + id = "chart", + positioning = Positioning.FLEX, + w = 400, + h = 480, + }) + + local details = Gui.new({ + parent = largeWidgetContent, + id = "details", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 240, + h = 480, + gap = 10, + }) + + -- Detail items + for i = 1, 6 do + Gui.new({ + parent = details, + id = "detailItem" .. i, + positioning = Positioning.FLEX, + w = 230, + h = 70, + }) + end + + -- Small widgets column + local smallWidgets = Gui.new({ + parent = bottomWidgets, + id = "smallWidgets", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 300, + h = 580, + gap = 20, + }) + + -- Create 3 small widgets + for i = 1, 3 do + local smallWidget = Gui.new({ + parent = smallWidgets, + id = "smallWidget" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 280, + h = 173, + gap = 10, + }) + + Gui.new({ + parent = smallWidget, + id = "smallWidgetHeader" .. i, + positioning = Positioning.FLEX, + w = 270, + h = 35, + }) + + Gui.new({ + parent = smallWidget, + id = "smallWidgetContent" .. i, + positioning = Positioning.FLEX, + w = 270, + h = 128, + }) + end + + -- Right sidebar + local rightSidebar = Gui.new({ + parent = mainContent, + id = "rightSidebar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 250, + h = 920, + gap = 15, + }) + + -- Activity feed + for i = 1, 8 do + Gui.new({ + parent = rightSidebar, + id = "activityItem" .. i, + positioning = Positioning.FLEX, + w = 240, + h = 100, + }) + end + + -- Verify dashboard structure + luaunit.assertEquals(#dashboard.children, 2) -- header + main content + luaunit.assertEquals(#header.children, 3) -- left + center + right + luaunit.assertEquals(#headerLeft.children, 2) -- logo + title + luaunit.assertEquals(#headerCenter.children, 2) -- search + quick actions + luaunit.assertEquals(#quickActions.children, 4) -- 4 quick actions + luaunit.assertEquals(#headerRight.children, 2) -- notifications + user menu + + luaunit.assertEquals(#mainContent.children, 3) -- left sidebar + center + right sidebar + luaunit.assertEquals(#leftSidebar.children, 5) -- 5 sidebar sections + luaunit.assertEquals(#centerArea.children, 2) -- top widgets + bottom widgets + luaunit.assertEquals(#topWidgets.children, 4) -- 4 top widgets + luaunit.assertEquals(#bottomWidgets.children, 2) -- large widget + small widgets + luaunit.assertEquals(#smallWidgets.children, 3) -- 3 small widgets + luaunit.assertEquals(#rightSidebar.children, 8) -- 8 activity items + + -- Verify large widget structure + luaunit.assertEquals(#largeWidget.children, 2) -- header + content + luaunit.assertEquals(#largeWidgetContent.children, 2) -- chart + details + luaunit.assertEquals(#details.children, 6) -- 6 detail items +end + +-- Run the tests +luaunit.LuaUnit.run() diff --git a/testing/__tests__/04_flex_direction_vertical_tests.lua b/testing/__tests__/04_flex_direction_vertical_tests.lua index 1a32aec..291ae78 100644 --- a/testing/__tests__/04_flex_direction_vertical_tests.lua +++ b/testing/__tests__/04_flex_direction_vertical_tests.lua @@ -515,5 +515,1213 @@ function TestVerticalFlexDirection:testVerticalLayoutWithPositioningOffset() 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 + }) + + -- 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 + }) + 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) +end + +-- Test 17: Multi-level accordion/collapsible vertical layout +function TestVerticalFlexDirection:testMultiLevelAccordionLayout() + local container = Gui.new({ + id = "accordionContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + 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) + 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 + }) + + -- 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) + 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) +end + +-- Test 20: Calendar/timeline vertical layout with nested events +function TestVerticalFlexDirection:testCalendarTimelineLayout() + local timeline = Gui.new({ + id = "timeline", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + 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, + 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) + 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 + }) + + -- 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 + }) + 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 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 + }) + + 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 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) +end + +-- Test 22: Mobile-style vertical stack with pull-to-refresh and infinite scroll +function TestVerticalFlexDirection:testMobileVerticalStackLayout() + local mobileContainer = Gui.new({ + id = "mobileContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + 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, + 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) + 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 diff --git a/testing/__tests__/05_justify_content_tests.lua b/testing/__tests__/05_justify_content_tests.lua index b5475a1..9ac20d0 100644 --- a/testing/__tests__/05_justify_content_tests.lua +++ b/testing/__tests__/05_justify_content_tests.lua @@ -13,647 +13,1662 @@ local Gui, enums = FlexLove.GUI, FlexLove.enums local Positioning = enums.Positioning local FlexDirection = enums.FlexDirection local JustifyContent = enums.JustifyContent +local AlignItems = enums.AlignItems -- Test class for justify content functionality TestJustifyContent = {} function TestJustifyContent:setUp() - -- Clear any previous state if needed - Gui.destroy() + -- Clear any previous state if needed + Gui.destroy() end function TestJustifyContent:tearDown() - -- Clean up after each test - Gui.destroy() + -- Clean up after each test + Gui.destroy() end -- Test 1: Horizontal Flex with JustifyContent.FLEX_START function TestJustifyContent:testHorizontalFlexJustifyContentFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + gap = 0, + }) - 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 = 35, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 70, + 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 positioned from the start (left) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 50) - luaunit.assertEquals(child3.x, 110) - - -- Y positions should be 0 (aligned to top by default) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child3.y, 0) + -- With FLEX_START, children should be positioned from the start (left) + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 50) + luaunit.assertEquals(child3.x, 110) + + -- Y positions should be 0 (aligned to top by default) + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 0) + luaunit.assertEquals(child3.y, 0) end -- Test 2: Horizontal Flex with JustifyContent.CENTER function TestJustifyContent:testHorizontalFlexJustifyContentCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + gap = 0, + }) - 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) - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Center offset: 190 / 2 = 95 - luaunit.assertEquals(child1.x, 95) - luaunit.assertEquals(child2.x, 145) + -- Total child width: 50 + 60 = 110 + -- Available space: 300 - 110 = 190 + -- Center offset: 190 / 2 = 95 + luaunit.assertEquals(child1.x, 95) + luaunit.assertEquals(child2.x, 145) end -- Test 3: Horizontal Flex with JustifyContent.FLEX_END function TestJustifyContent:testHorizontalFlexJustifyContentFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + gap = 0, + }) - 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) - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Children should be positioned from the end - luaunit.assertEquals(child1.x, 190) - luaunit.assertEquals(child2.x, 240) + -- Total child width: 50 + 60 = 110 + -- Available space: 300 - 110 = 190 + -- Children should be positioned from the end + luaunit.assertEquals(child1.x, 190) + luaunit.assertEquals(child2.x, 240) end -- Test 4: Horizontal Flex with JustifyContent.SPACE_BETWEEN function TestJustifyContent:testHorizontalFlexJustifyContentSpaceBetween() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 0, + }) - 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 = 40, - h = 35, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 40, + h = 35, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) + container:addChild(child1) + container:addChild(child2) + container:addChild(child3) - -- Total child width: 50 + 60 + 40 = 150 - -- Available space: 300 - 150 = 150 - -- Space between 3 children: 150 / 2 = 75 - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 125) -- 0 + 50 + 75 - luaunit.assertEquals(child3.x, 260) -- 125 + 60 + 75 + -- Total child width: 50 + 60 + 40 = 150 + -- Available space: 300 - 150 = 150 + -- Space between 3 children: 150 / 2 = 75 + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 125) -- 0 + 50 + 75 + luaunit.assertEquals(child3.x, 260) -- 125 + 60 + 75 end -- Test 5: Horizontal Flex with JustifyContent.SPACE_AROUND function TestJustifyContent:testHorizontalFlexJustifyContentSpaceAround() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + gap = 0, + }) - 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) - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Space around each: 190 / 2 = 95 (FlexLove divides by number of children) - -- Start position: 95 / 2 = 47.5 - -- Item spacing: 0 + 95 = 95 - luaunit.assertEquals(child1.x, 47.5) - luaunit.assertEquals(child2.x, 192.5) -- 47.5 + 50 + 95 + -- Total child width: 50 + 60 = 110 + -- Available space: 300 - 110 = 190 + -- Space around each: 190 / 2 = 95 (FlexLove divides by number of children) + -- Start position: 95 / 2 = 47.5 + -- Item spacing: 0 + 95 = 95 + luaunit.assertEquals(child1.x, 47.5) + luaunit.assertEquals(child2.x, 192.5) -- 47.5 + 50 + 95 end -- Test 6: Horizontal Flex with JustifyContent.SPACE_EVENLY function TestJustifyContent:testHorizontalFlexJustifyContentSpaceEvenly() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + gap = 0, + }) - 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) - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Space evenly: 190 / 3 = 63.33... (equal spaces at start, between, and end) - local expectedSpace = 190 / 3 - luaunit.assertAlmostEquals(child1.x, expectedSpace, 0.01) - luaunit.assertAlmostEquals(child2.x, expectedSpace + 50 + expectedSpace, 0.01) + -- Total child width: 50 + 60 = 110 + -- Available space: 300 - 110 = 190 + -- Space evenly: 190 / 3 = 63.33... (equal spaces at start, between, and end) + local expectedSpace = 190 / 3 + luaunit.assertAlmostEquals(child1.x, expectedSpace, 0.01) + luaunit.assertAlmostEquals(child2.x, expectedSpace + 50 + expectedSpace, 0.01) end -- Test 7: Vertical Flex with JustifyContent.FLEX_START function TestJustifyContent:testVerticalFlexJustifyContentFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_START, + gap = 0, + }) - 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 = 35, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 70, + 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 positioned from the start (top) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 30) - luaunit.assertEquals(child3.y, 70) - - -- X positions should be 0 (aligned to left by default) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child3.x, 0) + -- With FLEX_START, children should be positioned from the start (top) + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 30) + luaunit.assertEquals(child3.y, 70) + + -- X positions should be 0 (aligned to left by default) + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 0) + luaunit.assertEquals(child3.x, 0) end -- Test 8: Vertical Flex with JustifyContent.CENTER function TestJustifyContent:testVerticalFlexJustifyContentCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + gap = 0, + }) - 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) - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Center offset: 230 / 2 = 115 - luaunit.assertEquals(child1.y, 115) - luaunit.assertEquals(child2.y, 145) + -- Total child height: 30 + 40 = 70 + -- Available space: 300 - 70 = 230 + -- Center offset: 230 / 2 = 115 + luaunit.assertEquals(child1.y, 115) + luaunit.assertEquals(child2.y, 145) end -- Test 9: Vertical Flex with JustifyContent.FLEX_END function TestJustifyContent:testVerticalFlexJustifyContentFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_END, + gap = 0, + }) - 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) - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Children should be positioned from the end - luaunit.assertEquals(child1.y, 230) - luaunit.assertEquals(child2.y, 260) + -- Total child height: 30 + 40 = 70 + -- Available space: 300 - 70 = 230 + -- Children should be positioned from the end + luaunit.assertEquals(child1.y, 230) + luaunit.assertEquals(child2.y, 260) end -- Test 10: Vertical Flex with JustifyContent.SPACE_BETWEEN function TestJustifyContent:testVerticalFlexJustifyContentSpaceBetween() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 0, + }) - 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 = 40, - h = 35, - positioning = Positioning.FLEX, - }) + local child3 = Gui.new({ + id = "child3", + w = 40, + h = 35, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) + container:addChild(child1) + container:addChild(child2) + container:addChild(child3) - -- Total child height: 30 + 40 + 35 = 105 - -- Available space: 300 - 105 = 195 - -- Space between 3 children: 195 / 2 = 97.5 - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 127.5) -- 0 + 30 + 97.5 - luaunit.assertEquals(child3.y, 265) -- 127.5 + 40 + 97.5 + -- Total child height: 30 + 40 + 35 = 105 + -- Available space: 300 - 105 = 195 + -- Space between 3 children: 195 / 2 = 97.5 + luaunit.assertEquals(child1.y, 0) + luaunit.assertEquals(child2.y, 127.5) -- 0 + 30 + 97.5 + luaunit.assertEquals(child3.y, 265) -- 127.5 + 40 + 97.5 end -- Test 11: Vertical Flex with JustifyContent.SPACE_AROUND function TestJustifyContent:testVerticalFlexJustifyContentSpaceAround() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_AROUND, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_AROUND, + gap = 0, + }) - 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) - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Space around each: 230 / 2 = 115 (FlexLove divides by number of children) - -- Start position: 115 / 2 = 57.5 - -- Item spacing: 0 + 115 = 115 - luaunit.assertEquals(child1.y, 57.5) - luaunit.assertEquals(child2.y, 202.5) -- 57.5 + 30 + 115 + -- Total child height: 30 + 40 = 70 + -- Available space: 300 - 70 = 230 + -- Space around each: 230 / 2 = 115 (FlexLove divides by number of children) + -- Start position: 115 / 2 = 57.5 + -- Item spacing: 0 + 115 = 115 + luaunit.assertEquals(child1.y, 57.5) + luaunit.assertEquals(child2.y, 202.5) -- 57.5 + 30 + 115 end -- Test 12: Vertical Flex with JustifyContent.SPACE_EVENLY function TestJustifyContent:testVerticalFlexJustifyContentSpaceEvenly() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_EVENLY, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 300, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_EVENLY, + gap = 0, + }) - 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) - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Space evenly: 230 / 3 = 76.67... (equal spaces at start, between, and end) - local expectedSpace = 230 / 3 - luaunit.assertAlmostEquals(child1.y, expectedSpace, 0.01) - luaunit.assertAlmostEquals(child2.y, expectedSpace + 30 + expectedSpace, 0.01) + -- Total child height: 30 + 40 = 70 + -- Available space: 300 - 70 = 230 + -- Space evenly: 230 / 3 = 76.67... (equal spaces at start, between, and end) + local expectedSpace = 230 / 3 + luaunit.assertAlmostEquals(child1.y, expectedSpace, 0.01) + luaunit.assertAlmostEquals(child2.y, expectedSpace + 30 + expectedSpace, 0.01) end -- Test 13: JustifyContent with Single Child function TestJustifyContent:testJustifyContentWithSingleChild() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + gap = 0, + }) - 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, + }) - container:addChild(child1) + container:addChild(child1) - -- With single child and CENTER, child should be centered - -- Available space: 300 - 50 = 250 - -- Center offset: 250 / 2 = 125 - luaunit.assertEquals(child1.x, 125) + -- With single child and CENTER, child should be centered + -- Available space: 300 - 50 = 250 + -- Center offset: 250 / 2 = 125 + luaunit.assertEquals(child1.x, 125) end -- Test 14: JustifyContent with No Available Space function TestJustifyContent:testJustifyContentWithNoAvailableSpace() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - w = 100, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) + local container = Gui.new({ + id = "container", + x = 0, + y = 0, + w = 100, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 0, + }) - 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 = 50, - h = 40, - positioning = Positioning.FLEX, - }) + local child2 = Gui.new({ + id = "child2", + w = 50, + h = 40, + positioning = Positioning.FLEX, + }) - container:addChild(child1) - container:addChild(child2) + container:addChild(child1) + container:addChild(child2) - -- Children exactly fill container width (100) - -- Should fall back to FLEX_START behavior - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 50) + -- Children exactly fill container width (100) + -- Should fall back to FLEX_START behavior + luaunit.assertEquals(child1.x, 0) + luaunit.assertEquals(child2.x, 50) end -- Test 15: JustifyContent Preservation with Parent Coordinates function TestJustifyContent:testJustifyContentWithParentCoordinates() - local parent = Gui.new({ - id = "parent", - x = 50, - y = 30, - w = 400, - h = 200, - positioning = Positioning.ABSOLUTE, + local parent = Gui.new({ + id = "parent", + x = 50, + y = 30, + w = 400, + h = 200, + positioning = Positioning.ABSOLUTE, + }) + + local container = Gui.new({ + id = "container", + x = 20, + y = 10, + w = 300, + h = 100, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + gap = 0, + }) + + 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, + }) + + parent:addChild(container) + container:addChild(child1) + container:addChild(child2) + + -- Container should maintain its own coordinates since parent is ABSOLUTE + luaunit.assertEquals(container.x, 20) -- container keeps its own x + luaunit.assertEquals(container.y, 10) -- container keeps its own y + + -- Children should be centered within container coordinate system + -- Total child width: 50 + 60 = 110 + -- Available space: 300 - 110 = 190 + -- Center offset: 190 / 2 = 95 + -- Children are positioned in absolute coordinates: container.x + offset + luaunit.assertEquals(child1.x, 115) -- container.x(20) + center_offset(95) + luaunit.assertEquals(child2.x, 165) -- container.x(20) + center_offset(95) + child1.width(50) +end + +-- Test 16: Complex navigation bar with space-between and nested elements +function TestJustifyContent:testComplexNavigationBarLayout() + local navbar = Gui.new({ + id = "navbar", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + x = 0, + y = 0, + w = 1200, + h = 80, + gap = 0, + }) + + -- Logo section + local logoSection = Gui.new({ + id = "logoSection", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + w = 250, + h = 70, + gap = 15, + }) + + local logo = Gui.new({ id = "logo", w = 60, h = 50 }) + local brandName = Gui.new({ id = "brandName", w = 120, h = 30 }) + local beta = Gui.new({ id = "beta", w = 40, h = 20 }) + + logoSection:addChild(logo) + logoSection:addChild(brandName) + logoSection:addChild(beta) + + -- Navigation menu + local navMenu = Gui.new({ + id = "navMenu", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 500, + h = 70, + gap = 30, + }) + + local menuItems = { "Home", "Products", "Solutions", "About", "Contact" } + for i, itemName in ipairs(menuItems) do + local menuItem = Gui.new({ + id = "menuItem" .. itemName, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 80, + h = 60, + gap = 5, }) - local container = Gui.new({ - id = "container", - x = 20, - y = 10, - w = 300, - h = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, + local itemText = Gui.new({ id = "itemText" .. itemName, w = 70, h = 20 }) + local itemDot = Gui.new({ id = "itemDot" .. itemName, w = 6, h = 6 }) + + menuItem:addChild(itemText) + menuItem:addChild(itemDot) + navMenu:addChild(menuItem) + end + + -- Action buttons section + local actionsSection = Gui.new({ + id = "actionsSection", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 300, + h = 70, + gap = 12, + }) + + local searchButton = Gui.new({ id = "searchButton", w = 40, h = 40 }) + local loginButton = Gui.new({ id = "loginButton", w = 80, h = 35 }) + local signupButton = Gui.new({ id = "signupButton", w = 100, h = 40 }) + local mobileMenu = Gui.new({ id = "mobileMenu", w = 35, h = 35 }) + + actionsSection:addChild(searchButton) + actionsSection:addChild(loginButton) + actionsSection:addChild(signupButton) + actionsSection:addChild(mobileMenu) + + navbar:addChild(logoSection) + navbar:addChild(navMenu) + navbar:addChild(actionsSection) + + -- Verify space-between layout: elements should be distributed with equal space between + luaunit.assertEquals(logoSection.x, navbar.x) + luaunit.assertEquals(actionsSection.x + actionsSection.width, navbar.x + navbar.width) + + -- Center menu should be positioned between logo and actions + local expectedMenuX = logoSection.x + + logoSection.width + + ((navbar.width - logoSection.width - navMenu.width - actionsSection.width) / 2) + luaunit.assertEquals(navMenu.x, expectedMenuX) + + -- Verify nested center alignment in menu items + local firstMenuItem = navMenu.children[1] + local menuItemsWidth = 5 * 80 + 4 * 30 -- 5 items × 80px + 4 gaps × 30px = 520px + local menuStartX = navMenu.x + (navMenu.width - menuItemsWidth) / 2 + luaunit.assertEquals(firstMenuItem.x, menuStartX) + + -- Verify flex-end alignment in actions + local expectedActionsContentWidth = 40 + 80 + 100 + 35 + 3 * 12 -- widths + gaps = 291px + local expectedActionsStartX = actionsSection.x + actionsSection.width - expectedActionsContentWidth + luaunit.assertEquals(searchButton.x, expectedActionsStartX) +end + +-- Test 17: Dashboard metrics layout with space-around +function TestJustifyContent:testDashboardMetricsSpaceAround() + local metricsContainer = Gui.new({ + id = "metricsContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + x = 0, + y = 0, + w = 1000, + h = 200, + gap = 0, + }) + + -- Create metric cards with different complexities + local metrics = { + { title = "Revenue", hasChart = true, hasTrend = true }, + { title = "Users", hasChart = false, hasTrend = true }, + { title = "Orders", hasChart = true, hasTrend = false }, + { title = "Growth", hasChart = false, hasTrend = true }, + } + + for i, metric in ipairs(metrics) do + local metricCard = Gui.new({ + id = "metricCard" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 220, + h = 180, + gap = 10, }) - local child1 = Gui.new({ - id = "child1", - w = 50, - h = 30, - positioning = Positioning.FLEX, + -- Card header + local cardHeader = Gui.new({ + id = "cardHeader" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 200, + h = 30, + gap = 5, }) - local child2 = Gui.new({ - id = "child2", - w = 60, - h = 40, - positioning = Positioning.FLEX, + local metricTitle = Gui.new({ id = "metricTitle" .. i, w = 100, h = 25 }) + local metricIcon = Gui.new({ id = "metricIcon" .. i, w = 24, h = 24 }) + + cardHeader:addChild(metricTitle) + cardHeader:addChild(metricIcon) + + -- Value section + local valueSection = Gui.new({ + id = "valueSection" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 200, + h = 80, + gap = 8, }) - parent:addChild(container) - container:addChild(child1) - container:addChild(child2) + local mainValue = Gui.new({ id = "mainValue" .. i, w = 120, h = 40 }) + local valueLabel = Gui.new({ id = "valueLabel" .. i, w = 80, h = 16 }) - -- Container should maintain its own coordinates since parent is ABSOLUTE - luaunit.assertEquals(container.x, 20) -- container keeps its own x - luaunit.assertEquals(container.y, 10) -- container keeps its own y + valueSection:addChild(mainValue) + valueSection:addChild(valueLabel) - -- Children should be centered within container coordinate system - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Center offset: 190 / 2 = 95 - -- Children are positioned in absolute coordinates: container.x + offset - luaunit.assertEquals(child1.x, 115) -- container.x(20) + center_offset(95) - luaunit.assertEquals(child2.x, 165) -- container.x(20) + center_offset(95) + child1.width(50) + if metric.hasTrend then + local trendIndicator = Gui.new({ id = "trendIndicator" .. i, w = 60, h = 20 }) + valueSection:addChild(trendIndicator) + valueSection.height = 100 + end + + -- Bottom section (chart or additional info) + local bottomSection = Gui.new({ + id = "bottomSection" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = metric.hasChart and JustifyContent.CENTER or JustifyContent.SPACE_EVENLY, + alignItems = AlignItems.CENTER, + w = 200, + h = 50, + gap = 5, + }) + + if metric.hasChart then + local miniChart = Gui.new({ id = "miniChart" .. i, w = 150, h = 40 }) + bottomSection:addChild(miniChart) + else + -- Add comparison indicators + local prevPeriod = Gui.new({ id = "prevPeriod" .. i, w = 60, h = 20 }) + local comparison = Gui.new({ id = "comparison" .. i, w = 40, h = 20 }) + local target = Gui.new({ id = "target" .. i, w = 60, h = 20 }) + + bottomSection:addChild(prevPeriod) + bottomSection:addChild(comparison) + bottomSection:addChild(target) + end + + metricCard:addChild(cardHeader) + metricCard:addChild(valueSection) + metricCard:addChild(bottomSection) + metricsContainer:addChild(metricCard) + end + + -- Verify space-around distribution + local totalCardsWidth = 4 * 220 -- 880px + local availableSpace = 1000 - 880 -- 120px + local spaceAroundEach = availableSpace / 4 -- 30px around each card + local spaceAtEnds = spaceAroundEach / 2 -- 15px at each end + + local firstCard = metricsContainer.children[1] + local secondCard = metricsContainer.children[2] + + luaunit.assertEquals(firstCard.x, spaceAtEnds) + luaunit.assertEquals(secondCard.x, spaceAtEnds + 220 + spaceAroundEach) + + -- Verify nested space-between in card headers + local firstCardHeader = firstCard.children[1] + local headerTitle = firstCardHeader.children[1] + local headerIcon = firstCardHeader.children[2] + + luaunit.assertEquals(headerTitle.x, firstCardHeader.x) + luaunit.assertEquals(headerIcon.x + headerIcon.width, firstCardHeader.x + firstCardHeader.width) +end + +-- Test 18: Complex form layout with varied justify content +function TestJustifyContent:testComplexFormJustifyContentLayout() + local formContainer = Gui.new({ + id = "formContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 600, + h = 800, + gap = 25, + }) + + -- Form header with space-between + local formHeader = Gui.new({ + id = "formHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 580, + h = 60, + gap = 0, + }) + + local headerLeft = Gui.new({ + id = "headerLeft", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 200, + h = 50, + gap = 5, + }) + + local formTitle = Gui.new({ id = "formTitle", w = 180, h = 30 }) + local formSubtitle = Gui.new({ id = "formSubtitle", w = 200, h = 15 }) + + headerLeft:addChild(formTitle) + headerLeft:addChild(formSubtitle) + + local headerRight = Gui.new({ + id = "headerRight", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 150, + h = 50, + gap = 10, + }) + + local helpButton = Gui.new({ id = "helpButton", w = 30, h = 30 }) + local closeButton = Gui.new({ id = "closeButton", w = 30, h = 30 }) + + headerRight:addChild(helpButton) + headerRight:addChild(closeButton) + + formHeader:addChild(headerLeft) + formHeader:addChild(headerRight) + + -- Field sections with different alignments + local personalSection = Gui.new({ + id = "personalSection", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 580, + h = 250, + gap = 15, + }) + + local sectionTitle = Gui.new({ + id = "sectionTitle", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + w = 580, + h = 30, + gap = 10, + }) + + local titleText = Gui.new({ id = "titleText", w = 150, h = 25 }) + local titleIcon = Gui.new({ id = "titleIcon", w = 20, h = 20 }) + + sectionTitle:addChild(titleText) + sectionTitle:addChild(titleIcon) + + -- Field rows with different layouts + local nameRow = Gui.new({ + id = "nameRow", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 580, + h = 60, + gap = 15, + }) + + local firstNameGroup = Gui.new({ + id = "firstNameGroup", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 275, + h = 55, + gap = 5, + }) + + local firstNameLabel = Gui.new({ id = "firstNameLabel", w = 80, h = 20 }) + local firstNameInput = Gui.new({ id = "firstNameInput", w = 275, h = 30 }) + + firstNameGroup:addChild(firstNameLabel) + firstNameGroup:addChild(firstNameInput) + + local lastNameGroup = Gui.new({ + id = "lastNameGroup", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 275, + h = 55, + gap = 5, + }) + + local lastNameLabel = Gui.new({ id = "lastNameLabel", w = 80, h = 20 }) + local lastNameInput = Gui.new({ id = "lastNameInput", w = 275, h = 30 }) + + lastNameGroup:addChild(lastNameLabel) + lastNameGroup:addChild(lastNameInput) + + nameRow:addChild(firstNameGroup) + nameRow:addChild(lastNameGroup) + + -- Contact preferences with space-evenly + local contactRow = Gui.new({ + id = "contactRow", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + alignItems = AlignItems.CENTER, + w = 580, + h = 50, + gap = 0, + }) + + local emailOption = Gui.new({ + id = "emailOption", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + w = 120, + h = 40, + gap = 8, + }) + + local emailCheckbox = Gui.new({ id = "emailCheckbox", w = 20, h = 20 }) + local emailLabel = Gui.new({ id = "emailLabel", w = 60, h = 18 }) + + emailOption:addChild(emailCheckbox) + emailOption:addChild(emailLabel) + + local phoneOption = Gui.new({ + id = "phoneOption", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + w = 120, + h = 40, + gap = 8, + }) + + local phoneCheckbox = Gui.new({ id = "phoneCheckbox", w = 20, h = 20 }) + local phoneLabel = Gui.new({ id = "phoneLabel", w = 60, h = 18 }) + + phoneOption:addChild(phoneCheckbox) + phoneOption:addChild(phoneLabel) + + local smsOption = Gui.new({ + id = "smsOption", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + w = 100, + h = 40, + gap = 8, + }) + + local smsCheckbox = Gui.new({ id = "smsCheckbox", w = 20, h = 20 }) + local smsLabel = Gui.new({ id = "smsLabel", w = 50, h = 18 }) + + smsOption:addChild(smsCheckbox) + smsOption:addChild(smsLabel) + + contactRow:addChild(emailOption) + contactRow:addChild(phoneOption) + contactRow:addChild(smsOption) + + personalSection:addChild(sectionTitle) + personalSection:addChild(nameRow) + personalSection:addChild(contactRow) + + -- Form actions with varied justification + local actionsSection = Gui.new({ + id = "actionsSection", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 580, + h = 120, + gap = 20, + }) + + local primaryActions = Gui.new({ + id = "primaryActions", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 580, + h = 45, + gap = 15, + }) + + local cancelButton = Gui.new({ id = "cancelButton", w = 80, h = 40 }) + local saveButton = Gui.new({ id = "saveButton", w = 100, h = 40 }) + local submitButton = Gui.new({ id = "submitButton", w = 120, h = 40 }) + + primaryActions:addChild(cancelButton) + primaryActions:addChild(saveButton) + primaryActions:addChild(submitButton) + + local secondaryActions = Gui.new({ + id = "secondaryActions", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 580, + h = 35, + gap = 25, + }) + + local resetButton = Gui.new({ id = "resetButton", w = 70, h = 30 }) + local previewButton = Gui.new({ id = "previewButton", w = 80, h = 30 }) + + secondaryActions:addChild(resetButton) + secondaryActions:addChild(previewButton) + + actionsSection:addChild(primaryActions) + actionsSection:addChild(secondaryActions) + + formContainer:addChild(formHeader) + formContainer:addChild(personalSection) + formContainer:addChild(actionsSection) + + -- Verify complex justify content behaviors + -- Header space-between + luaunit.assertEquals(headerLeft.x, formHeader.x) + luaunit.assertEquals(headerRight.x + headerRight.width, formHeader.x + formHeader.width) + + -- Name row space-between + luaunit.assertEquals(firstNameGroup.x, nameRow.x) + luaunit.assertEquals(lastNameGroup.x + lastNameGroup.width, nameRow.x + nameRow.width) + + -- Contact preferences space-evenly + local totalOptionsWidth = 120 + 120 + 100 -- 340px + local availableSpace = 580 - 340 -- 240px + local evenSpacing = 240 / 4 -- 60px (spaces before, between, between, after) + + luaunit.assertEquals(emailOption.x, contactRow.x + evenSpacing) + luaunit.assertEquals(phoneOption.x, emailOption.x + emailOption.width + evenSpacing) + luaunit.assertEquals(smsOption.x, phoneOption.x + phoneOption.width + evenSpacing) + + -- Primary actions flex-end + local totalPrimaryWidth = 80 + 100 + 120 + 2 * 15 -- 330px including gaps + local expectedPrimaryStartX = primaryActions.x + primaryActions.width - totalPrimaryWidth + luaunit.assertEquals(cancelButton.x, expectedPrimaryStartX) + + -- Secondary actions center + local totalSecondaryWidth = 70 + 80 + 25 -- 175px including gap + local expectedSecondaryStartX = secondaryActions.x + (secondaryActions.width - totalSecondaryWidth) / 2 + luaunit.assertEquals(resetButton.x, expectedSecondaryStartX) +end + +-- Test 19: Grid-like layout with justify content variations +function TestJustifyContent:testGridLayoutJustifyContentVariations() + local gridContainer = Gui.new({ + id = "gridContainer", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + x = 0, + y = 0, + w = 800, + h = 600, + gap = 20, + }) + + -- Row 1: Space-between + local row1 = Gui.new({ + id = "row1", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 780, + h = 120, + gap = 0, + }) + + for i = 1, 4 do + local card = Gui.new({ + id = "row1Card" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 150, + h = 100, + gap = 10, + }) + + local cardIcon = Gui.new({ id = "row1Icon" .. i, w = 40, h = 40 }) + local cardLabel = Gui.new({ id = "row1Label" .. i, w = 100, h = 20 }) + + card:addChild(cardIcon) + card:addChild(cardLabel) + row1:addChild(card) + end + + -- Row 2: Space-around + local row2 = Gui.new({ + id = "row2", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + w = 780, + h = 120, + gap = 0, + }) + + for i = 1, 3 do + local card = Gui.new({ + id = "row2Card" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 200, + h = 100, + gap = 5, + }) + + local cardHeader = Gui.new({ + id = "row2Header" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 180, + h = 25, + gap = 0, + }) + + local headerTitle = Gui.new({ id = "row2HeaderTitle" .. i, w = 100, h = 20 }) + local headerIcon = Gui.new({ id = "row2HeaderIcon" .. i, w = 20, h = 20 }) + + cardHeader:addChild(headerTitle) + cardHeader:addChild(headerIcon) + + local cardContent = Gui.new({ id = "row2Content" .. i, w = 180, h = 40 }) + local cardFooter = Gui.new({ id = "row2Footer" .. i, w = 180, h = 20 }) + + card:addChild(cardHeader) + card:addChild(cardContent) + card:addChild(cardFooter) + row2:addChild(card) + end + + -- Row 3: Space-evenly + local row3 = Gui.new({ + id = "row3", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + w = 780, + h = 120, + gap = 0, + }) + + for i = 1, 5 do + local item = Gui.new({ + id = "row3Item" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 100, + h = 100, + gap = 5, + }) + + local itemValue = Gui.new({ id = "row3Value" .. i, w = 60, h = 30 }) + local itemLabel = Gui.new({ id = "row3Label" .. i, w = 80, h = 15 }) + local itemTrend = Gui.new({ id = "row3Trend" .. i, w = 40, h = 12 }) + + item:addChild(itemValue) + item:addChild(itemLabel) + item:addChild(itemTrend) + row3:addChild(item) + end + + -- Row 4: Center with overflow behavior + local row4 = Gui.new({ + id = "row4", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + w = 780, + h = 120, + gap = 10, + }) + + -- Create many items that might overflow + for i = 1, 8 do + local chip = Gui.new({ + id = "row4Chip" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 90, + h = 30, + gap = 5, + }) + + local chipIcon = Gui.new({ id = "row4ChipIcon" .. i, w = 16, h = 16 }) + local chipText = Gui.new({ id = "row4ChipText" .. i, w = 60, h = 14 }) + + chip:addChild(chipIcon) + chip:addChild(chipText) + row4:addChild(chip) + end + + gridContainer:addChild(row1) + gridContainer:addChild(row2) + gridContainer:addChild(row3) + gridContainer:addChild(row4) + + -- Verify row 1 space-between (4 cards, 150px each) + local row1Card1 = row1.children[1] + local row1Card4 = row1.children[4] + + luaunit.assertEquals(row1Card1.x, row1.x) + luaunit.assertEquals(row1Card4.x + row1Card4.width, row1.x + row1.width) + + -- Verify row 2 space-around (3 cards, 200px each) + local totalRow2Width = 3 * 200 -- 600px + local row2AvailableSpace = 780 - 600 -- 180px + local row2SpaceAround = 180 / 3 -- 60px around each + local row2SpaceAtEnds = 60 / 2 -- 30px at ends + + local row2Card1 = row2.children[1] + luaunit.assertEquals(row2Card1.x, row2.x + row2SpaceAtEnds) + + -- Verify row 3 space-evenly (5 items, 100px each) + local totalRow3Width = 5 * 100 -- 500px + local row3AvailableSpace = 780 - 500 -- 280px + local row3EvenSpacing = 280 / 6 -- 46.67px (6 spaces: before, between×4, after) + + local row3Item1 = row3.children[1] + luaunit.assertAlmostEquals(row3Item1.x, row3.x + row3EvenSpacing, 0.1) + + -- Verify row 4 center behavior (8 chips, 90px each + 7 gaps of 10px = 790px) + -- Should overflow slightly but center the content + local totalRow4Width = 8 * 90 + 7 * 10 -- 790px (larger than container) + local row4Offset = (row4.width - totalRow4Width) / 2 -- Should be negative + local row4Chip1 = row4.children[1] + + -- When content is larger than container, should start at calculated offset (possibly negative) + luaunit.assertEquals(row4Chip1.x, row4.x + row4Offset) + + -- Verify nested justify content in cards + local row2Card1Header = row2.children[1].children[1] + local headerTitle = row2Card1Header.children[1] + local headerIcon = row2Card1Header.children[2] + + luaunit.assertEquals(headerTitle.x, row2Card1Header.x) + luaunit.assertEquals(headerIcon.x + headerIcon.width, row2Card1Header.x + row2Card1Header.width) +end + +-- Test 20: Multi-level nested justify content with modal dialogs +function TestJustifyContent:testMultiLevelNestedModalJustifyContent() + local modalOverlay = Gui.new({ + id = "modalOverlay", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + x = 0, + y = 0, + w = 1200, + h = 800, + gap = 0, + }) + + local modal = Gui.new({ + id = "modal", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + w = 600, + h = 500, + gap = 0, + }) + + -- Modal header with space-between + local modalHeader = Gui.new({ + id = "modalHeader", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 580, + h = 60, + gap = 0, + }) + + local headerLeft = Gui.new({ + id = "headerLeft", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + alignItems = AlignItems.CENTER, + w = 200, + h = 50, + gap = 12, + }) + + local modalIcon = Gui.new({ id = "modalIcon", w = 24, h = 24 }) + local modalTitle = Gui.new({ id = "modalTitle", w = 150, h = 30 }) + + headerLeft:addChild(modalIcon) + headerLeft:addChild(modalTitle) + + local headerRight = Gui.new({ + id = "headerRight", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 100, + h = 50, + gap = 8, + }) + + local minimizeButton = Gui.new({ id = "minimizeButton", w = 30, h = 30 }) + local closeButton = Gui.new({ id = "closeButton", w = 30, h = 30 }) + + headerRight:addChild(minimizeButton) + headerRight:addChild(closeButton) + + modalHeader:addChild(headerLeft) + modalHeader:addChild(headerRight) + + -- Modal content with complex nested layouts + local modalContent = Gui.new({ + id = "modalContent", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 580, + h = 380, + gap = 20, + }) + + -- Tab navigation + local tabNavigation = Gui.new({ + id = "tabNavigation", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_EVENLY, + w = 580, + h = 50, + gap = 0, + }) + + local tabs = { "General", "Advanced", "Security", "Notifications" } + for i, tabName in ipairs(tabs) do + local tab = Gui.new({ + id = "tab" .. tabName, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = 120, + h = 45, + gap = 5, + }) + + local tabText = Gui.new({ id = "tabText" .. tabName, w = 80, h = 18 }) + local tabIndicator = Gui.new({ id = "tabIndicator" .. tabName, w = 60, h = 3 }) + + tab:addChild(tabText) + tab:addChild(tabIndicator) + tabNavigation:addChild(tab) + end + + -- Content area with settings rows + local settingsArea = Gui.new({ + id = "settingsArea", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + w = 580, + h = 250, + gap = 15, + }) + + -- Setting rows with different alignments + for i = 1, 4 do + local settingRow = Gui.new({ + id = "settingRow" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = i % 2 == 1 and JustifyContent.SPACE_BETWEEN or JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + w = 560, + h = 50, + gap = 15, + }) + + local settingInfo = Gui.new({ + id = "settingInfo" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + w = 300, + h = 40, + gap = 3, + }) + + local settingLabel = Gui.new({ id = "settingLabel" .. i, w = 200, h = 20 }) + local settingDescription = Gui.new({ id = "settingDescription" .. i, w = 280, h = 14 }) + + settingInfo:addChild(settingLabel) + settingInfo:addChild(settingDescription) + + local settingControl = Gui.new({ + id = "settingControl" .. i, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + w = i % 2 == 1 and 100 or 200, + h = 35, + gap = 8, + }) + + if i % 2 == 1 then + -- Toggle switch + local toggle = Gui.new({ id = "toggle" .. i, w = 60, h = 30 }) + settingControl:addChild(toggle) + else + -- Dropdown or input + local input = Gui.new({ id = "input" .. i, w = 120, h = 30 }) + local button = Gui.new({ id = "button" .. i, w = 60, h = 28 }) + settingControl:addChild(input) + settingControl:addChild(button) + end + + settingRow:addChild(settingInfo) + settingRow:addChild(settingControl) + settingsArea:addChild(settingRow) + end + + modalContent:addChild(tabNavigation) + modalContent:addChild(settingsArea) + + -- Modal footer with action buttons + local modalFooter = Gui.new({ + id = "modalFooter", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + w = 580, + h = 60, + gap = 0, + }) + + local footerLeft = Gui.new({ + id = "footerLeft", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_START, + alignItems = AlignItems.CENTER, + w = 200, + h = 50, + gap = 10, + }) + + local resetButton = Gui.new({ id = "resetButton", w = 80, h = 35 }) + local helpLink = Gui.new({ id = "helpLink", w = 60, h = 20 }) + + footerLeft:addChild(resetButton) + footerLeft:addChild(helpLink) + + local footerRight = Gui.new({ + id = "footerRight", + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + w = 250, + h = 50, + gap = 12, + }) + + local cancelButton = Gui.new({ id = "cancelButton", w = 70, h = 35 }) + local applyButton = Gui.new({ id = "applyButton", w = 70, h = 35 }) + local saveButton = Gui.new({ id = "saveButton", w = 80, h = 35 }) + + footerRight:addChild(cancelButton) + footerRight:addChild(applyButton) + footerRight:addChild(saveButton) + + modalFooter:addChild(footerLeft) + modalFooter:addChild(footerRight) + + modal:addChild(modalHeader) + modal:addChild(modalContent) + modal:addChild(modalFooter) + modalOverlay:addChild(modal) + + -- Verify modal is centered in overlay + local expectedModalX = modalOverlay.x + (modalOverlay.width - modal.width) / 2 + luaunit.assertEquals(modal.x, expectedModalX) + + -- Verify modal space-between layout + luaunit.assertEquals(modalHeader.y, modal.y) + luaunit.assertEquals(modalFooter.y + modalFooter.height, modal.y + modal.height) + + -- Verify header space-between + luaunit.assertEquals(headerLeft.x, modalHeader.x) + luaunit.assertEquals(headerRight.x + headerRight.width, modalHeader.x + modalHeader.width) + + -- Verify tab space-evenly distribution + local totalTabsWidth = 4 * 120 -- 480px + local tabAvailableSpace = 580 - 480 -- 100px + local tabEvenSpacing = 100 / 5 -- 20px (5 spaces: before, between×3, after) + + local firstTab = tabNavigation.children[1] + luaunit.assertEquals(firstTab.x, tabNavigation.x + tabEvenSpacing) + + -- Verify setting rows alternate justification + local setting1 = settingsArea.children[1] -- space-between + local setting2 = settingsArea.children[2] -- flex-start + + local setting1Info = setting1.children[1] + local setting1Control = setting1.children[2] + + luaunit.assertEquals(setting1Info.x, setting1.x) + luaunit.assertEquals(setting1Control.x + setting1Control.width, setting1.x + setting1.width) + + local setting2Info = setting2.children[1] + local setting2Control = setting2.children[2] + + luaunit.assertEquals(setting2Info.x, setting2.x) + luaunit.assertEquals(setting2Control.x, setting2Info.x + setting2Info.width + setting2.gap) + + -- Verify footer space-between + luaunit.assertEquals(footerLeft.x, modalFooter.x) + luaunit.assertEquals(footerRight.x + footerRight.width, modalFooter.x + modalFooter.width) + + -- Verify nested button layouts in footer + local footerRightFirstButton = footerRight.children[1] + local footerRightLastButton = footerRight.children[3] + local expectedFooterRightStartX = footerRight.x + footerRight.width - (70 + 70 + 80 + 2 * 12) + + 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()) + os.exit(luaunit.LuaUnit.run()) end -return TestJustifyContent \ No newline at end of file +return TestJustifyContent + diff --git a/testing/__tests__/06_align_items_tests.lua b/testing/__tests__/06_align_items_tests.lua index 3be7154..aa8ff73 100644 --- a/testing/__tests__/06_align_items_tests.lua +++ b/testing/__tests__/06_align_items_tests.lua @@ -13,6 +13,7 @@ local Gui, enums = FlexLove.GUI, FlexLove.enums local Positioning = enums.Positioning local FlexDirection = enums.FlexDirection local AlignItems = enums.AlignItems +local JustifyContent = enums.JustifyContent -- Test class for align items functionality TestAlignItems = {}