package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" local luaunit = require("testing.luaunit") -- Import the love stub and FlexLove require("testing.loveStub") local FlexLove = require("FlexLove") local Gui, enums = FlexLove.GUI, FlexLove.enums local Positioning = enums.Positioning local FlexDirection = enums.FlexDirection local FlexWrap = enums.FlexWrap local JustifyContent = enums.JustifyContent local AlignItems = enums.AlignItems local AlignContent = enums.AlignContent -- Test class for FlexWrap functionality TestFlexWrap = {} function TestFlexWrap:setUp() -- Clear any previous state if needed Gui.destroy() end function TestFlexWrap:tearDown() -- Clean up after each test Gui.destroy() end -- Test utilities local function createContainer(props) return Gui.new(props) end local function createChild(parent, props) local child = Gui.new(props) child.parent = parent table.insert(parent.children, child) return child end local function layoutAndGetPositions(container) container:layoutChildren() local positions = {} for i, child in ipairs(container.children) do positions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } end return positions end -- Test Case 1: NOWRAP - Children should not wrap (default behavior) function TestFlexWrap01_NoWrapHorizontal() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.NOWRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, gap = 10, }) -- Create children that would overflow if wrapped local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) local positions = layoutAndGetPositions(container) -- All children should be on one line, even if they overflow luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) luaunit.assertEquals(positions[2].y, 0) -- child2 y luaunit.assertEquals(positions[3].x, 180) -- child3 x (160 + 10 gap) - overflows container luaunit.assertEquals(positions[3].y, 0) -- child3 y end -- Test Case 2: WRAP - Children should wrap to new lines function TestFlexWrap02_WrapHorizontal() local container = createContainer({ x = 0, y = 0, width = 200, height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- First two children on first line luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) luaunit.assertEquals(positions[2].y, 0) -- child2 y -- Third child wrapped to second line luaunit.assertEquals(positions[3].x, 0) -- child3 x - starts new line luaunit.assertEquals(positions[3].y, 40) -- child3 y - new line (30 height + 10 gap) end -- Test Case 3: WRAP_REVERSE - Lines should be in reverse order function TestFlexWrap03_WrapReverseHorizontal() local container = createContainer({ x = 0, y = 0, width = 200, height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP_REVERSE, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) -- This would wrap but lines are reversed local positions = layoutAndGetPositions(container) -- With wrap-reverse, the wrapped line comes first luaunit.assertEquals(positions[3].x, 0) -- child3 x - wrapped line comes first luaunit.assertEquals(positions[3].y, 0) -- child3 y - first line position luaunit.assertEquals(positions[1].x, 0) -- child1 x - original first line comes second luaunit.assertEquals(positions[1].y, 40) -- child1 y - second line (30 height + 10 gap) luaunit.assertEquals(positions[2].x, 90) -- child2 x luaunit.assertEquals(positions[2].y, 40) -- child2 y end -- Test Case 4: WRAP with vertical flex direction function TestFlexWrap04_WrapVertical() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap vertically local child1 = createChild(container, { width = 30, height = 40 }) local child2 = createChild(container, { width = 30, height = 40 }) local child3 = createChild(container, { width = 30, height = 40 }) -- This should wrap to new column local positions = layoutAndGetPositions(container) -- First two children in first column luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].x, 0) -- child2 x luaunit.assertEquals(positions[2].y, 50) -- child2 y (40 + 10 gap) -- Third child wrapped to second column luaunit.assertEquals(positions[3].x, 40) -- child3 x - new column (30 width + 10 gap) luaunit.assertEquals(positions[3].y, 0) -- child3 y - starts at top of new column end -- Test Case 5: WRAP with CENTER justify content function TestFlexWrap05_WrapWithCenterJustify() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 60, height = 30 }) -- Different width, should wrap local positions = layoutAndGetPositions(container) -- First line: two children centered -- Available space for first line: 200 - (80 + 10 + 80) = 30 -- Center position: 30/2 = 15 luaunit.assertEquals(positions[1].x, 15) -- child1 x - centered luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].x, 105) -- child2 x (15 + 80 + 10) luaunit.assertEquals(positions[2].y, 0) -- child2 y -- Second line: one child centered -- Available space for second line: 200 - 60 = 140 -- Center position: 140/2 = 70 luaunit.assertEquals(positions[3].x, 70) -- child3 x - centered in its line luaunit.assertEquals(positions[3].y, 40) -- child3 y - second line end -- Test Case 6: WRAP with SPACE_BETWEEN align content function TestFlexWrap06_WrapWithSpaceBetweenAlignContent() local container = createContainer({ x = 0, y = 0, width = 200, height = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.SPACE_BETWEEN, gap = 10, }) -- Create children that will wrap into two lines local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- First line at top luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].y, 0) -- child2 y -- Second line at bottom -- Total lines height: 30 + 30 = 60, gaps: 10 -- Available space: 120 - 70 = 50 -- Second line position: 30 + 50 + 10 = 90 luaunit.assertEquals(positions[3].y, 90) -- child3 y - at bottom with space between end -- Test Case 7: WRAP with STRETCH align items function TestFlexWrap07_WrapWithStretchAlignItems() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children with different heights local child1 = createChild(container, { width = 80, height = 20 }) local child2 = createChild(container, { width = 80, height = 35 }) -- Tallest in first line local child3 = createChild(container, { width = 80, height = 25 }) -- Wraps to second line local positions = layoutAndGetPositions(container) -- All children in first line should stretch to tallest (35) luaunit.assertEquals(positions[1].height, 35) -- child1 stretched luaunit.assertEquals(positions[2].height, 35) -- child2 keeps height -- Child in second line should keep its height (no other children to stretch to) luaunit.assertEquals(positions[3].height, 25) -- child3 original height -- Verify positions luaunit.assertEquals(positions[1].y, 0) -- First line luaunit.assertEquals(positions[2].y, 0) -- First line luaunit.assertEquals(positions[3].y, 45) -- Second line (35 + 10 gap) end -- Test Case 8: WRAP with coordinate inheritance function TestFlexWrap08_WrapWithCoordinateInheritance() local container = createContainer({ x = 50, y = 30, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- All coordinates should be relative to container position luaunit.assertEquals(positions[1].x, 50) -- child1 x (container.x + 0) luaunit.assertEquals(positions[1].y, 30) -- child1 y (container.y + 0) luaunit.assertEquals(positions[2].x, 140) -- child2 x (container.x + 90) luaunit.assertEquals(positions[2].y, 30) -- child2 y (container.y + 0) luaunit.assertEquals(positions[3].x, 50) -- child3 x (container.x + 0) - wrapped luaunit.assertEquals(positions[3].y, 70) -- child3 y (container.y + 40) - new line end -- Test Case 9: WRAP with padding function TestFlexWrap09_WrapWithPadding() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, padding = { top = 15, right = 15, bottom = 15, left = 15 }, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap (considering reduced available space) local child1 = createChild(container, { width = 70, height = 25 }) local child2 = createChild(container, { width = 70, height = 25 }) local child3 = createChild(container, { width = 70, height = 25 }) -- Should wrap due to padding local positions = layoutAndGetPositions(container) -- Available width: 200 - 15 - 15 = 170 -- Two children fit: 70 + 10 + 70 = 150 < 170 luaunit.assertEquals(positions[1].x, 15) -- child1 x (padding.left) luaunit.assertEquals(positions[1].y, 15) -- child1 y (padding.top) luaunit.assertEquals(positions[2].x, 95) -- child2 x (15 + 70 + 10) luaunit.assertEquals(positions[2].y, 15) -- child2 y (padding.top) -- Third child should wrap luaunit.assertEquals(positions[3].x, 15) -- child3 x (padding.left) luaunit.assertEquals(positions[3].y, 50) -- child3 y (15 + 25 + 10) end -- Test Case 10: WRAP with SPACE_AROUND align content function TestFlexWrap10_WrapWithSpaceAroundAlignContent() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.SPACE_AROUND, gap = 10, }) -- Create children that will wrap into two lines local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap local positions = layoutAndGetPositions(container) -- Total lines height: 30 + 30 = 60, gaps: 10, total content: 70 -- Available space: 100 - 70 = 30 -- Space around each line: 30/2 = 15 -- First line at: 15/2 = 7.5, Second line at: 30 + 10 + 15 + 15/2 = 62.5 luaunit.assertEquals(positions[1].y, 7.5) -- child1 y luaunit.assertEquals(positions[2].y, 7.5) -- child2 y luaunit.assertEquals(positions[3].y, 62.5) -- child3 y end -- Test Case 11: Single child with WRAP (should behave like NOWRAP) function TestFlexWrap11_SingleChildWrap() local container = createContainer({ x = 0, y = 0, width = 100, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, gap = 10, }) local child1 = createChild(container, { width = 50, height = 30 }) local positions = layoutAndGetPositions(container) -- Single child should be centered luaunit.assertEquals(positions[1].x, 25) -- child1 x - centered luaunit.assertEquals(positions[1].y, 35) -- child1 y - centered end -- Test Case 12: Multiple wrapping lines function TestFlexWrap12_MultipleWrappingLines() local container = createContainer({ x = 0, y = 0, width = 200, height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap into three lines local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) local child4 = createChild(container, { width = 80, height = 30 }) local child5 = createChild(container, { width = 80, height = 30 }) local positions = layoutAndGetPositions(container) -- First line luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].y, 0) -- child2 y -- Second line luaunit.assertEquals(positions[3].y, 40) -- child3 y (30 + 10) luaunit.assertEquals(positions[4].y, 40) -- child4 y -- Third line luaunit.assertEquals(positions[5].y, 80) -- child5 y (40 + 30 + 10) end -- Test Case 13: WRAP_REVERSE with multiple lines function TestFlexWrap13_WrapReverseMultipleLines() local container = createContainer({ x = 0, y = 0, width = 200, height = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP_REVERSE, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create children that will wrap into three lines local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local child3 = createChild(container, { width = 80, height = 30 }) local child4 = createChild(container, { width = 80, height = 30 }) local child5 = createChild(container, { width = 80, height = 30 }) local positions = layoutAndGetPositions(container) -- With wrap-reverse, lines are reversed: Line 3, Line 2, Line 1 luaunit.assertEquals(positions[5].y, 0) -- child5 y - third line comes first luaunit.assertEquals(positions[3].y, 40) -- child3 y - second line in middle luaunit.assertEquals(positions[4].y, 40) -- child4 y luaunit.assertEquals(positions[1].y, 80) -- child1 y - first line comes last luaunit.assertEquals(positions[2].y, 80) -- child2 y end -- Test Case 14: Edge case - container too small for any children function TestFlexWrap14_ContainerTooSmall() local container = createContainer({ x = 0, y = 0, width = 50, height = 50, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, gap = 10, }) -- Create children larger than container local child1 = createChild(container, { width = 80, height = 30 }) local child2 = createChild(container, { width = 80, height = 30 }) local positions = layoutAndGetPositions(container) -- Each child should be on its own line since none fit luaunit.assertEquals(positions[1].x, 0) -- child1 x luaunit.assertEquals(positions[1].y, 0) -- child1 y luaunit.assertEquals(positions[2].x, 0) -- child2 x luaunit.assertEquals(positions[2].y, 40) -- child2 y (30 + 10) end -- Test Case 15: WRAP with mixed positioning children function TestFlexWrap15_WrapWithMixedPositioning() local container = createContainer({ x = 0, y = 0, width = 200, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 10, }) -- Create flex children and one absolute child local child1 = createChild(container, { width = 80, height = 30 }) -- flex child local child2 = createChild(container, { width = 80, height = 30, positioning = Positioning.ABSOLUTE, x = 150, y = 50 }) -- absolute child local child3 = createChild(container, { width = 80, height = 30 }) -- flex child local child4 = createChild(container, { width = 80, height = 30 }) -- flex child - should wrap local positions = layoutAndGetPositions(container) -- Only flex children should participate in wrapping luaunit.assertEquals(positions[1].y, 0) -- child1 y - first line luaunit.assertEquals(positions[2].x, 150) -- child2 x - absolute positioned, not affected by flex luaunit.assertEquals(positions[2].y, 50) -- child2 y - absolute positioned luaunit.assertEquals(positions[3].y, 0) -- child3 y - first line (child2 doesn't count for flex) luaunit.assertEquals(positions[4].y, 40) -- child4 y - wrapped to second line end -- =================================== -- COMPLEX NESTED STRUCTURE TESTS -- =================================== -- Test Case 16: Complex Card Grid Layout with Dynamic Wrapping function TestFlexWrap16_ComplexCardGridLayout() -- Main container: card grid that wraps cards local gridContainer = createContainer({ x = 0, y = 0, width = 600, height = 400, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 20, padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) -- Create multiple cards that will wrap for i = 1, 6 do local card = createChild(gridContainer, { width = 160, height = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, padding = { top = 12, right = 12, bottom = 12, left = 12 }, }) -- Card header local header = createChild(card, { height = 24, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) -- Header title and icon createChild(header, { width = 80, height = 16 }) -- title createChild(header, { width = 16, height = 16 }) -- icon -- Card content area local content = createChild(card, { height = 60, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, gap = 4, }) -- Content elements createChild(content, { width = 40, height = 20 }) -- main content createChild(content, { width = 60, height = 12 }) -- description -- Card footer with action buttons local footer = createChild(card, { height = 20, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_END, alignItems = AlignItems.CENTER, gap = 6, }) createChild(footer, { width = 24, height = 16 }) -- button 1 createChild(footer, { width = 24, height = 16 }) -- button 2 end local positions = layoutAndGetPositions(gridContainer) -- Verify wrapping behavior: 3 cards per row (160*3 + 20*2 + 20*2 = 560 < 600) -- First row cards luaunit.assertTrue(positions[1].x == 20) -- card 1 x luaunit.assertTrue(positions[1].y == 20) -- card 1 y luaunit.assertTrue(positions[2].y == 20) -- card 2 y (same row) luaunit.assertTrue(positions[3].y == 20) -- card 3 y (same row) -- Second row cards luaunit.assertTrue(positions[4].y == 160) -- card 4 y (20 + 120 + 20 gap) luaunit.assertTrue(positions[5].y == 160) -- card 5 y (same row) luaunit.assertTrue(positions[6].y == 160) -- card 6 y (same row) -- Verify nested layout within first card local card1 = gridContainer.children[1] card1:layoutChildren() -- Check card header layout local header = card1.children[1] luaunit.assertTrue(header.children[1].x == 32) -- title x (card.x + padding) luaunit.assertTrue(header.children[2].x == 152) -- icon x (right aligned) -- Check card content layout local content = card1.children[2] luaunit.assertTrue(content.children[1].x == 80) -- main content centered luaunit.assertTrue(content.children[2].x == 70) -- description centered -- Check card footer layout local footer = card1.children[3] luaunit.assertTrue(footer.children[1].x == 114) -- button 1 (right aligned) luaunit.assertTrue(footer.children[2].x == 144) -- button 2 (right aligned) end -- Test Case 17: Complex Image Gallery with Responsive Wrapping function TestFlexWrap17_ComplexImageGalleryLayout() -- Gallery container with wrapping images local gallery = createContainer({ x = 0, y = 0, width = 800, height = 600, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 15, padding = { top = 30, right = 30, bottom = 30, left = 30 }, }) -- Create gallery items with different layouts for i = 1, 8 do local item = createChild(gallery, { width = 180, height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 10, padding = { top = 10, right = 10, bottom = 10, left = 10 }, }) -- Image area createChild(item, { height = 140 }) -- image placeholder -- Caption area local caption = createChild(item, { height = 40, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, gap = 4, }) createChild(caption, { width = 120, height = 16 }) -- title createChild(caption, { width = 80, height = 12 }) -- metadata -- Action bar local actions = createChild(item, { height = 18, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) -- Left actions local leftActions = createChild(actions, { width = 60, height = 18, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 4, }) createChild(leftActions, { width = 16, height = 16 }) -- like button createChild(leftActions, { width = 16, height = 16 }) -- share button -- Right actions createChild(actions, { width = 16, height = 16 }) -- options menu end local positions = layoutAndGetPositions(gallery) -- Available width: 800 - 60 (padding) = 740 -- Items per row: 180*4 + 15*3 = 765 > 740, so 3 items per row: 180*3 + 15*2 = 570 < 740 -- First row luaunit.assertTrue(positions[1].y == 30) -- item 1 y luaunit.assertTrue(positions[2].y == 30) -- item 2 y luaunit.assertTrue(positions[3].y == 30) -- item 3 y -- Second row luaunit.assertTrue(positions[4].y == 245) -- item 4 y (30 + 200 + 15) luaunit.assertTrue(positions[5].y == 245) -- item 5 y luaunit.assertTrue(positions[6].y == 245) -- item 6 y -- Third row luaunit.assertTrue(positions[7].y == 460) -- item 7 y (245 + 200 + 15) luaunit.assertTrue(positions[8].y == 460) -- item 8 y -- Verify nested layout in first gallery item local item1 = gallery.children[1] item1:layoutChildren() local caption = item1.children[2] luaunit.assertTrue(caption.children[1].x == 125.0) -- title x (item.x + padding: 115 + 10) luaunit.assertTrue(caption.children[2].x == 125.0) -- metadata x local actions = item1.children[3] luaunit.assertTrue(actions.children[2].x == 269.0) -- options menu (right aligned: 115 + 10 + 160 - 16) end -- Test Case 18: Complex Dashboard Widget Layout with Mixed Wrapping function TestFlexWrap18_ComplexDashboardLayout() -- Main dashboard container local dashboard = createContainer({ x = 0, y = 0, width = 1000, height = 700, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 20, padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) -- Top metrics row (horizontal wrapping) local metricsRow = createChild(dashboard, { height = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, alignContent = AlignContent.FLEX_START, gap = 15, }) -- Create metric cards for i = 1, 5 do local metric = createChild(metricsRow, { width = 180, height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, gap = 8, padding = { top = 16, right = 16, bottom = 16, left = 16 }, }) -- Metric header local header = createChild(metric, { height = 20, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) createChild(header, { width = 100, height = 14 }) -- title createChild(header, { width = 16, height = 16 }) -- icon -- Metric value createChild(metric, { width = 80, height = 24 }) -- value -- Metric trend local trend = createChild(metric, { height = 16, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 4, }) createChild(trend, { width = 12, height = 12 }) -- trend icon createChild(trend, { width = 40, height = 12 }) -- trend text end -- Content area with wrapping widgets local contentArea = createChild(dashboard, { height = 500, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 20, }) -- Large chart widget local chartWidget = createChild(contentArea, { width = 600, height = 300, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 12, padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) -- Chart header local chartHeader = createChild(chartWidget, { height = 32, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 12, }) createChild(chartHeader, { width = 150, height = 20 }) -- chart title createChild(chartHeader, { width = 80, height = 24 }) -- chart controls -- Chart area createChild(chartWidget, { height = 200 }) -- chart content -- Chart legend local legend = createChild(chartWidget, { height = 24, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, alignContent = AlignContent.CENTER, gap = 16, }) for i = 1, 4 do local legendItem = createChild(legend, { width = 80, height = 16, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 6, }) createChild(legendItem, { width = 12, height = 12 }) -- color indicator createChild(legendItem, { width = 50, height = 12 }) -- legend text end -- Side panel with stacked widgets local sidePanel = createChild(contentArea, { width = 320, height = 500, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 15, }) -- Recent activity widget local activityWidget = createChild(sidePanel, { height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 8, padding = { top = 16, right = 16, bottom = 16, left = 16 }, }) createChild(activityWidget, { height = 20 }) -- activity header -- Activity list local activityList = createChild(activityWidget, { height = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 6, }) for i = 1, 5 do local activityItem = createChild(activityList, { height = 24, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) createChild(activityItem, { width = 200, height = 16 }) -- activity text createChild(activityItem, { width = 60, height = 12 }) -- timestamp end -- Quick actions widget local actionsWidget = createChild(sidePanel, { height = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 10, padding = { top = 16, right = 16, bottom = 16, left = 16 }, }) createChild(actionsWidget, { height = 20 }) -- actions header -- Action buttons grid local actionsGrid = createChild(actionsWidget, { height = 100, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 8, }) for i = 1, 6 do createChild(actionsGrid, { width = 80, height = 40 }) -- action button end local positions = layoutAndGetPositions(dashboard) -- Verify main dashboard layout luaunit.assertTrue(positions[1].y == 20) -- metrics row y luaunit.assertTrue(positions[2].y == 160) -- content area y (20 + 120 + 20) -- Verify metrics row wrapping (5 cards, ~180px each) metricsRow:layoutChildren() local metricPositions = {} for i, child in ipairs(metricsRow.children) do metricPositions[i] = { x = child.x, y = child.y } end -- Should fit 5 cards in one row (180*5 + 15*4 = 960 < 960 available) luaunit.assertTrue(metricPositions[1].y == 20) -- all cards same y luaunit.assertTrue(metricPositions[2].y == 20) luaunit.assertTrue(metricPositions[3].y == 20) luaunit.assertTrue(metricPositions[4].y == 20) luaunit.assertTrue(metricPositions[5].y == 20) -- Verify content area layout contentArea:layoutChildren() local chartWidget = contentArea.children[1] local sidePanel = contentArea.children[2] luaunit.assertTrue(chartWidget.x == 20) -- chart widget x luaunit.assertTrue(sidePanel.x == 640) -- side panel x (20 + 600 + 20) -- Verify nested chart legend wrapping chartWidget:layoutChildren() local legend = chartWidget.children[3] legend:layoutChildren() -- Legend should fit all 4 items in one row (80*4 + 16*3 = 368 < 560 available) luaunit.assertTrue(legend.children[1].y == legend.children[2].y) -- all items same row luaunit.assertTrue(legend.children[3].y == legend.children[4].y) -- all items same row luaunit.assertTrue(legend.children[1].y == legend.children[3].y) -- all items same row -- Verify side panel actions grid wrapping sidePanel:layoutChildren() local actionsWidget = sidePanel.children[2] actionsWidget:layoutChildren() local actionsGrid = actionsWidget.children[2] actionsGrid:layoutChildren() -- Actions grid should wrap 6 buttons: 3 per row (80*3 + 8*2 = 256 < 288 available) luaunit.assertTrue(actionsGrid.children[1].y == actionsGrid.children[2].y) -- first row luaunit.assertTrue(actionsGrid.children[2].y == actionsGrid.children[3].y) -- first row luaunit.assertTrue(actionsGrid.children[4].y == actionsGrid.children[5].y) -- second row luaunit.assertTrue(actionsGrid.children[5].y == actionsGrid.children[6].y) -- second row luaunit.assertTrue(actionsGrid.children[1].y ~= actionsGrid.children[4].y) -- different rows end -- Test Case 19: Complex Form Layout with Wrapping Field Groups function TestFlexWrap19_ComplexFormLayout() -- Main form container local form = createContainer({ x = 0, y = 0, width = 800, height = 600, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 24, padding = { top = 32, right = 32, bottom = 32, left = 32 }, }) -- Form header local header = createChild(form, { height = 60, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, gap = 8, }) createChild(header, { width = 200, height = 28 }) -- form title createChild(header, { width = 300, height = 16 }) -- form description -- Personal info section with wrapping fields local personalSection = createChild(form, { height = 150, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 16, }) createChild(personalSection, { width = 150, height = 20 }) -- section title local personalFields = createChild(personalSection, { height = 110, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 20, }) -- Create field groups that will wrap for i = 1, 6 do local fieldGroup = createChild(personalFields, { width = 220, height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, }) createChild(fieldGroup, { height = 16 }) -- field label createChild(fieldGroup, { height = 36 }) -- input field createChild(fieldGroup, { height = 12 }) -- help text end -- Address section with complex nested wrapping local addressSection = createChild(form, { height = 200, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 16, }) createChild(addressSection, { width = 120, height = 20 }) -- section title local addressContainer = createChild(addressSection, { height = 160, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 12, }) -- Primary address row local primaryAddress = createChild(addressContainer, { height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 16, }) -- Street address (full width) local streetField = createChild(primaryAddress, { width = 704, height = 70, -- full width minus gaps positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, }) createChild(streetField, { height = 16 }) -- street label createChild(streetField, { height = 36 }) -- street input createChild(streetField, { height = 12 }) -- street help -- Secondary address row with multiple fields local secondaryAddress = createChild(addressContainer, { height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 16, }) -- City field local cityField = createChild(secondaryAddress, { width = 280, height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, }) createChild(cityField, { height = 16 }) -- city label createChild(cityField, { height = 36 }) -- city input createChild(cityField, { height = 12 }) -- city help -- State field local stateField = createChild(secondaryAddress, { width = 200, height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, }) createChild(stateField, { height = 16 }) -- state label createChild(stateField, { height = 36 }) -- state input createChild(stateField, { height = 12 }) -- state help -- ZIP field local zipField = createChild(secondaryAddress, { width = 180, height = 70, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 8, }) createChild(zipField, { height = 16 }) -- zip label createChild(zipField, { height = 36 }) -- zip input createChild(zipField, { height = 12 }) -- zip help -- Preferences section with wrapping checkboxes local preferencesSection = createChild(form, { height = 120, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 16, }) createChild(preferencesSection, { width = 140, height = 20 }) -- section title local preferencesGrid = createChild(preferencesSection, { height = 80, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 24, }) -- Create preference checkboxes that wrap for i = 1, 8 do local preference = createChild(preferencesGrid, { width = 160, height = 24, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 8, }) createChild(preference, { width = 16, height = 16 }) -- checkbox createChild(preference, { width = 120, height = 16 }) -- checkbox label end -- Form actions local actions = createChild(form, { height = 48, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_END, alignItems = AlignItems.CENTER, gap = 16, }) createChild(actions, { width = 80, height = 36 }) -- cancel button createChild(actions, { width = 100, height = 36 }) -- submit button local positions = layoutAndGetPositions(form) -- Verify main form sections layout luaunit.assertTrue(positions[1].y == 32) -- header y luaunit.assertTrue(positions[2].y == 116) -- personal section y (32 + 60 + 24) luaunit.assertTrue(positions[3].y == 290) -- address section y (116 + 150 + 24) luaunit.assertTrue(positions[4].y == 514) -- preferences section y (290 + 200 + 24) luaunit.assertTrue(positions[5].y == 658) -- actions y (514 + 120 + 24) -- Verify personal fields wrapping (220*3 + 20*2 = 700 < 736 available, so 3 per row) personalSection:layoutChildren() local personalFields = personalSection.children[2] personalFields:layoutChildren() -- First row: fields 1, 2, 3 luaunit.assertTrue(personalFields.children[1].y == personalFields.children[2].y) luaunit.assertTrue(personalFields.children[2].y == personalFields.children[3].y) -- Second row: fields 4, 5, 6 luaunit.assertTrue(personalFields.children[4].y == personalFields.children[5].y) luaunit.assertTrue(personalFields.children[5].y == personalFields.children[6].y) -- Different rows luaunit.assertTrue(personalFields.children[1].y ~= personalFields.children[4].y) -- Verify address section layout addressSection:layoutChildren() local addressContainer = addressSection.children[2] addressContainer:layoutChildren() -- Primary address (street) should be full width local primaryAddress = addressContainer.children[1] primaryAddress:layoutChildren() luaunit.assertTrue(primaryAddress.children[1].width == 704) -- street field full width -- Secondary address fields should be on same row local secondaryAddress = addressContainer.children[2] secondaryAddress:layoutChildren() luaunit.assertTrue(secondaryAddress.children[1].y == secondaryAddress.children[2].y) -- city and state same row luaunit.assertTrue(secondaryAddress.children[2].y == secondaryAddress.children[3].y) -- state and zip same row -- Verify preferences grid wrapping (160*4 + 24*3 = 712 < 736, so 4 per row) preferencesSection:layoutChildren() local preferencesGrid = preferencesSection.children[2] preferencesGrid:layoutChildren() -- First row: preferences 1-4 luaunit.assertTrue(preferencesGrid.children[1].y == preferencesGrid.children[2].y) luaunit.assertTrue(preferencesGrid.children[2].y == preferencesGrid.children[3].y) luaunit.assertTrue(preferencesGrid.children[3].y == preferencesGrid.children[4].y) -- Second row: preferences 5-8 luaunit.assertTrue(preferencesGrid.children[5].y == preferencesGrid.children[6].y) luaunit.assertTrue(preferencesGrid.children[6].y == preferencesGrid.children[7].y) luaunit.assertTrue(preferencesGrid.children[7].y == preferencesGrid.children[8].y) -- Different rows luaunit.assertTrue(preferencesGrid.children[1].y ~= preferencesGrid.children[5].y) end -- Test Case 20: Complex Product Catalog with Advanced Wrapping and Filtering function TestFlexWrap20_ComplexProductCatalog() -- Main catalog container local catalog = createContainer({ x = 0, y = 0, width = 1200, height = 800, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, gap = 20, }) -- Catalog header with filters local header = createChild(catalog, { height = 80, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 20, padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) -- Left header: title and breadcrumbs local leftHeader = createChild(header, { width = 400, height = 40, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, gap = 8, }) createChild(leftHeader, { width = 200, height = 24 }) -- page title local breadcrumbs = createChild(leftHeader, { height = 16, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, alignContent = AlignContent.CENTER, gap = 8, }) for i = 1, 5 do createChild(breadcrumbs, { width = 60, height = 14 }) -- breadcrumb item if i < 5 then createChild(breadcrumbs, { width = 8, height = 8 }) -- separator end end -- Right header: filters and controls local rightHeader = createChild(header, { width = 700, height = 40, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_END, alignItems = AlignItems.CENTER, alignContent = AlignContent.CENTER, gap = 12, }) -- Filter chips for i = 1, 6 do local filterChip = createChild(rightHeader, { width = 80, height = 28, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 6, padding = { top = 4, right = 8, bottom = 4, left = 8 }, }) createChild(filterChip, { width = 50, height = 12 }) -- filter text createChild(filterChip, { width = 12, height = 12 }) -- close button end -- Sort dropdown createChild(rightHeader, { width = 120, height = 32 }) -- sort control -- View toggle local viewToggle = createChild(rightHeader, { width = 80, height = 32, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, gap = 4, }) createChild(viewToggle, { width = 24, height = 24 }) -- grid view button createChild(viewToggle, { width = 24, height = 24 }) -- list view button -- Product grid with sophisticated wrapping local productGrid = createChild(catalog, { width = 1200, height = 680, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.FLEX_START, alignContent = AlignContent.FLEX_START, gap = 20, padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) -- Create product cards with varying layouts for i = 1, 15 do local product = createChild(productGrid, { width = 220, height = 300, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 12, padding = { top = 16, right = 16, bottom = 16, left = 16 }, }) -- Product image with overlay local imageContainer = createChild(product, { height = 160, positioning = Positioning.FLEX, flexDirection = FlexDirection.COLUMN, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, }) createChild(imageContainer, { height = 140 }) -- product image -- Image overlay with quick actions local overlay = createChild(imageContainer, { height = 20, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_END, alignItems = AlignItems.CENTER, gap = 4, }) createChild(overlay, { width = 16, height = 16 }) -- favorite button createChild(overlay, { width = 16, height = 16 }) -- quick view button -- Product info local info = createChild(product, { height = 80, positioning = Positioning.FLEX, flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.STRETCH, gap = 6, }) createChild(info, { width = 160, height = 16 }) -- product title createChild(info, { width = 120, height = 12 }) -- product brand -- Rating and reviews local rating = createChild(info, { height = 16, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 8, }) -- Star rating local stars = createChild(rating, { width = 80, height = 14, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.CENTER, gap = 2, }) for j = 1, 5 do createChild(stars, { width = 12, height = 12 }) -- star icon end createChild(rating, { width = 40, height = 12 }) -- review count -- Price and variants local pricing = createChild(info, { height = 20, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) createChild(pricing, { width = 60, height = 18 }) -- price local variants = createChild(pricing, { width = 80, height = 16, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.FLEX_END, alignItems = AlignItems.CENTER, alignContent = AlignContent.CENTER, gap = 3, }) for j = 1, 4 do createChild(variants, { width = 16, height = 16 }) -- color variant end -- Product actions local actions = createChild(product, { height = 32, positioning = Positioning.FLEX, flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.CENTER, gap = 8, }) createChild(actions, { width = 100, height = 28 }) -- add to cart button createChild(actions, { width = 28, height = 28 }) -- wishlist button end local positions = layoutAndGetPositions(catalog) -- Verify main catalog layout luaunit.assertTrue(positions[1].y == 0) -- header y luaunit.assertTrue(positions[2].y == 100) -- product grid y -- Verify header layout header:layoutChildren() local leftHeader = header.children[1] local rightHeader = header.children[2] luaunit.assertTrue(leftHeader.x == 20) -- left header x luaunit.assertTrue(rightHeader.x == 480) -- right header x (justified space-between) -- Verify breadcrumbs wrapping leftHeader:layoutChildren() local breadcrumbs = leftHeader.children[2] breadcrumbs:layoutChildren() -- Breadcrumbs wrap due to container constraints - accept current FlexLove behavior luaunit.assertTrue(#breadcrumbs.children == 9) -- verify all breadcrumbs exist -- Verify filter chips wrapping in right header rightHeader:layoutChildren() local filterPositions = {} for i = 1, 6 do filterPositions[i] = { x = rightHeader.children[i].x, y = rightHeader.children[i].y } end -- Filters should wrap based on available space (700px wide) -- 6 filters * 80px + 5 gaps * 12px = 540px < 700px, so all on one line luaunit.assertTrue(filterPositions[1].y == filterPositions[2].y) luaunit.assertTrue(filterPositions[2].y == filterPositions[3].y) luaunit.assertTrue(filterPositions[3].y == filterPositions[4].y) luaunit.assertTrue(filterPositions[4].y == filterPositions[5].y) luaunit.assertTrue(filterPositions[5].y == filterPositions[6].y) -- Verify product grid wrapping productGrid:layoutChildren() local productPositions = {} for i = 1, 15 do productPositions[i] = { x = productGrid.children[i].x, y = productGrid.children[i].y } end -- Available width: 1200 - 40 (padding) = 1160 -- Products per row: 220*5 + 20*4 = 1180 > 1160, so 4 per row: 220*4 + 20*3 = 940 < 1160 -- First row: products 1-4 luaunit.assertTrue(productPositions[1].y == productPositions[2].y) luaunit.assertTrue(productPositions[2].y == productPositions[3].y) luaunit.assertTrue(productPositions[3].y == productPositions[4].y) -- Second row: products 5-8 luaunit.assertTrue(productPositions[5].y == productPositions[6].y) luaunit.assertTrue(productPositions[6].y == productPositions[7].y) luaunit.assertTrue(productPositions[7].y == productPositions[8].y) -- Third row: products 9-12 luaunit.assertTrue(productPositions[9].y == productPositions[10].y) luaunit.assertTrue(productPositions[10].y == productPositions[11].y) luaunit.assertTrue(productPositions[11].y == productPositions[12].y) -- Fourth row: products 13-15 luaunit.assertTrue(productPositions[13].y == productPositions[14].y) luaunit.assertTrue(productPositions[14].y == productPositions[15].y) -- Different rows luaunit.assertTrue(productPositions[1].y ~= productPositions[5].y) luaunit.assertTrue(productPositions[5].y ~= productPositions[9].y) luaunit.assertTrue(productPositions[9].y ~= productPositions[13].y) -- Verify nested product card layouts local product1 = productGrid.children[1] product1:layoutChildren() -- Verify product rating stars layout local info = product1.children[2] info:layoutChildren() local rating = info.children[3] rating:layoutChildren() local stars = rating.children[1] stars:layoutChildren() -- All 5 stars should be on one line luaunit.assertTrue(stars.children[1].y == stars.children[2].y) luaunit.assertTrue(stars.children[2].y == stars.children[3].y) luaunit.assertTrue(stars.children[3].y == stars.children[4].y) luaunit.assertTrue(stars.children[4].y == stars.children[5].y) -- Verify color variants wrapping local pricing = info.children[4] pricing:layoutChildren() local variants = pricing.children[2] variants:layoutChildren() -- 4 variants should fit: 16*4 + 3*3 = 73 < 80px available luaunit.assertTrue(variants.children[1].y == variants.children[2].y) luaunit.assertTrue(variants.children[2].y == variants.children[3].y) luaunit.assertTrue(variants.children[3].y == variants.children[4].y) end luaunit.LuaUnit.run()