-- Test suite for LayoutEngine.lua module -- Tests layout engine initialization and basic layout calculations package.path = package.path .. ";./?.lua;./modules/?.lua" -- Load love stub before anything else require("testing.loveStub") local luaunit = require("testing.luaunit") local LayoutEngine = require("modules.LayoutEngine") local Units = require("modules.Units") local utils = require("modules.utils") -- Mock dependencies local mockContext = { getScaleFactors = function() return 1, 1 end, baseScale = nil, _cachedViewport = nil, } local mockErrorHandler = { error = function(module, msg) end, warn = function(module, msg) end, } local mockGrid = { layoutGridItems = function(element) end, } local deps = { utils = utils, Grid = mockGrid, Units = Units, Context = mockContext, ErrorHandler = mockErrorHandler, } -- Test suite for LayoutEngine.new() TestLayoutEngineNew = {} function TestLayoutEngineNew:testNewWithDefaults() local layout = LayoutEngine.new({}, deps) luaunit.assertNotNil(layout) luaunit.assertEquals(layout.positioning, utils.enums.Positioning.FLEX) luaunit.assertEquals(layout.flexDirection, utils.enums.FlexDirection.HORIZONTAL) luaunit.assertEquals(layout.justifyContent, utils.enums.JustifyContent.FLEX_START) luaunit.assertEquals(layout.alignItems, utils.enums.AlignItems.STRETCH) luaunit.assertEquals(layout.alignContent, utils.enums.AlignContent.STRETCH) luaunit.assertEquals(layout.flexWrap, utils.enums.FlexWrap.NOWRAP) luaunit.assertEquals(layout.gap, 10) end function TestLayoutEngineNew:testNewWithCustomProps() local layout = LayoutEngine.new({ positioning = utils.enums.Positioning.GRID, flexDirection = utils.enums.FlexDirection.VERTICAL, justifyContent = utils.enums.JustifyContent.CENTER, alignItems = utils.enums.AlignItems.CENTER, gap = 20, gridRows = 3, gridColumns = 4, }, deps) luaunit.assertEquals(layout.positioning, utils.enums.Positioning.GRID) luaunit.assertEquals(layout.flexDirection, utils.enums.FlexDirection.VERTICAL) luaunit.assertEquals(layout.justifyContent, utils.enums.JustifyContent.CENTER) luaunit.assertEquals(layout.alignItems, utils.enums.AlignItems.CENTER) luaunit.assertEquals(layout.gap, 20) luaunit.assertEquals(layout.gridRows, 3) luaunit.assertEquals(layout.gridColumns, 4) end function TestLayoutEngineNew:testNewStoresDependencies() local layout = LayoutEngine.new({}, deps) luaunit.assertNotNil(layout._Grid) luaunit.assertNotNil(layout._Units) luaunit.assertNotNil(layout._Context) luaunit.assertNotNil(layout._ErrorHandler) end -- Test suite for LayoutEngine:initialize() TestLayoutEngineInitialize = {} function TestLayoutEngineInitialize:testInitialize() local layout = LayoutEngine.new({}, deps) local mockElement = { id = "test" } layout:initialize(mockElement) luaunit.assertEquals(layout.element, mockElement) end -- Test suite for LayoutEngine:calculateAutoWidth() TestLayoutEngineAutoWidth = {} function TestLayoutEngineAutoWidth:testAutoWidthNoElement() local layout = LayoutEngine.new({}, deps) local width = layout:calculateAutoWidth() luaunit.assertEquals(width, 0) end function TestLayoutEngineAutoWidth:testAutoWidthNoChildren() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, }, deps) local mockElement = { children = {}, calculateTextWidth = function() return 100 end, } layout:initialize(mockElement) local width = layout:calculateAutoWidth() luaunit.assertEquals(width, 100) -- Just text width end function TestLayoutEngineAutoWidth:testAutoWidthHorizontalWithGap() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, gap = 10, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 50 end, } local mockChild2 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 60 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 70 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextWidth = function() return 0 end, } layout:initialize(mockElement) local width = layout:calculateAutoWidth() -- 50 + 60 + 70 = 180, plus 2 gaps (10 each) = 200 luaunit.assertEquals(width, 200) end function TestLayoutEngineAutoWidth:testAutoWidthVerticalTakesMax() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.VERTICAL, gap = 10, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 50 end, } local mockChild2 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 150 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 75 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextWidth = function() return 0 end, } layout:initialize(mockElement) local width = layout:calculateAutoWidth() -- Should take maximum width (150) luaunit.assertEquals(width, 150) end function TestLayoutEngineAutoWidth:testAutoWidthSkipsAbsoluteChildren() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, gap = 10, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 50 end, } local mockChild2 = { _explicitlyAbsolute = true, -- Should be skipped getBorderBoxWidth = function() return 1000 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 60 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextWidth = function() return 0 end, } layout:initialize(mockElement) local width = layout:calculateAutoWidth() -- 50 + 60 = 110, plus 1 gap (10) = 120 (mockChild2 is skipped) luaunit.assertEquals(width, 120) end -- Test suite for LayoutEngine:calculateAutoHeight() TestLayoutEngineAutoHeight = {} function TestLayoutEngineAutoHeight:testAutoHeightNoElement() local layout = LayoutEngine.new({}, deps) local height = layout:calculateAutoHeight() luaunit.assertEquals(height, 0) end function TestLayoutEngineAutoHeight:testAutoHeightNoChildren() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.VERTICAL, }, deps) local mockElement = { children = {}, calculateTextHeight = function() return 50 end, } layout:initialize(mockElement) local height = layout:calculateAutoHeight() luaunit.assertEquals(height, 50) -- Just text height end function TestLayoutEngineAutoHeight:testAutoHeightVerticalWithGap() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.VERTICAL, gap = 5, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 30 end, } local mockChild2 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 40 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 50 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextHeight = function() return 0 end, } layout:initialize(mockElement) local height = layout:calculateAutoHeight() -- 30 + 40 + 50 = 120, plus 2 gaps (5 each) = 130 luaunit.assertEquals(height, 130) end function TestLayoutEngineAutoHeight:testAutoHeightHorizontalTakesMax() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, gap = 5, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 30 end, } local mockChild2 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 100 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 50 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextHeight = function() return 0 end, } layout:initialize(mockElement) local height = layout:calculateAutoHeight() -- Should take maximum height (100) luaunit.assertEquals(height, 100) end function TestLayoutEngineAutoHeight:testAutoHeightSkipsAbsoluteChildren() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.VERTICAL, gap = 5, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 30 end, } local mockChild2 = { _explicitlyAbsolute = true, -- Should be skipped getBorderBoxHeight = function() return 1000 end, } local mockChild3 = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 40 end, } local mockElement = { children = { mockChild1, mockChild2, mockChild3 }, calculateTextHeight = function() return 0 end, } layout:initialize(mockElement) local height = layout:calculateAutoHeight() -- 30 + 40 = 70, plus 1 gap (5) = 75 (mockChild2 is skipped) luaunit.assertEquals(height, 75) end -- Test suite for LayoutEngine:applyPositioningOffsets() TestLayoutEnginePositioningOffsets = {} function TestLayoutEnginePositioningOffsets:testApplyOffsetsNilChild() local layout = LayoutEngine.new({}, deps) -- Should not error layout:applyPositioningOffsets(nil) end function TestLayoutEnginePositioningOffsets:testApplyOffsetsNoParent() local layout = LayoutEngine.new({}, deps) local mockChild = { parent = nil, top = 10, } -- Should not error, just return early layout:applyPositioningOffsets(mockChild) end function TestLayoutEnginePositioningOffsets:testApplyTopOffset() local layout = LayoutEngine.new({}, deps) local mockParent = { x = 100, y = 200, padding = { left = 10, top = 20, right = 10, bottom = 20 }, } local mockChild = { parent = mockParent, positioning = utils.enums.Positioning.ABSOLUTE, _explicitlyAbsolute = true, x = 0, y = 0, top = 30, } layout:applyPositioningOffsets(mockChild) -- y should be parent.y + parent.padding.top + top -- 200 + 20 + 30 = 250 luaunit.assertEquals(mockChild.y, 250) end function TestLayoutEnginePositioningOffsets:testApplyLeftOffset() local layout = LayoutEngine.new({}, deps) local mockParent = { x = 100, y = 200, padding = { left = 10, top = 20, right = 10, bottom = 20 }, } local mockChild = { parent = mockParent, positioning = utils.enums.Positioning.ABSOLUTE, _explicitlyAbsolute = true, x = 0, y = 0, left = 40, } layout:applyPositioningOffsets(mockChild) -- x should be parent.x + parent.padding.left + left -- 100 + 10 + 40 = 150 luaunit.assertEquals(mockChild.x, 150) end function TestLayoutEnginePositioningOffsets:testApplyBottomOffset() local layout = LayoutEngine.new({}, deps) local mockParent = { x = 100, y = 200, width = 400, height = 300, padding = { left = 10, top = 20, right = 10, bottom = 20 }, } local mockChild = { parent = mockParent, positioning = utils.enums.Positioning.ABSOLUTE, _explicitlyAbsolute = true, x = 0, y = 0, bottom = 50, getBorderBoxHeight = function() return 80 end, } layout:applyPositioningOffsets(mockChild) -- y should be parent.y + parent.padding.top + parent.height - bottom - childHeight -- 200 + 20 + 300 - 50 - 80 = 390 luaunit.assertEquals(mockChild.y, 390) end function TestLayoutEnginePositioningOffsets:testApplyRightOffset() local layout = LayoutEngine.new({}, deps) local mockParent = { x = 100, y = 200, width = 400, height = 300, padding = { left = 10, top = 20, right = 10, bottom = 20 }, } local mockChild = { parent = mockParent, positioning = utils.enums.Positioning.ABSOLUTE, _explicitlyAbsolute = true, x = 0, y = 0, right = 60, getBorderBoxWidth = function() return 100 end, } layout:applyPositioningOffsets(mockChild) -- x should be parent.x + parent.padding.left + parent.width - right - childWidth -- 100 + 10 + 400 - 60 - 100 = 350 luaunit.assertEquals(mockChild.x, 350) end function TestLayoutEnginePositioningOffsets:testSkipsFlexChildren() local layout = LayoutEngine.new({}, deps) local mockParent = { x = 100, y = 200, padding = { left = 10, top = 20, right = 10, bottom = 20 }, } local mockChild = { parent = mockParent, positioning = utils.enums.Positioning.ABSOLUTE, _explicitlyAbsolute = false, -- Participates in flex layout x = 500, y = 600, top = 30, left = 40, } layout:applyPositioningOffsets(mockChild) -- Should not apply offsets for flex children luaunit.assertEquals(mockChild.x, 500) -- Unchanged luaunit.assertEquals(mockChild.y, 600) -- Unchanged end -- Test suite for LayoutEngine:layoutChildren() TestLayoutEngineLayoutChildren = {} function TestLayoutEngineLayoutChildren:testLayoutChildrenNoElement() local layout = LayoutEngine.new({}, deps) -- Should not error layout:layoutChildren() end function TestLayoutEngineLayoutChildren:testLayoutChildrenNoChildren() local layout = LayoutEngine.new({}, deps) local mockElement = { children = {}, } layout:initialize(mockElement) -- Should not error layout:layoutChildren() end function TestLayoutEngineLayoutChildren:testLayoutChildrenAbsolutePositioning() local layout = LayoutEngine.new({ positioning = utils.enums.Positioning.ABSOLUTE, }, deps) local mockElement = { children = {}, padding = { left = 0, top = 0, right = 0, bottom = 0 }, } layout:initialize(mockElement) -- Should handle absolute positioning (doesn't layout children, just applies offsets) layout:layoutChildren() end function TestLayoutEngineLayoutChildren:testLayoutChildrenRelativePositioning() local layout = LayoutEngine.new({ positioning = utils.enums.Positioning.RELATIVE, }, deps) local mockElement = { children = {}, padding = { left = 0, top = 0, right = 0, bottom = 0 }, } layout:initialize(mockElement) -- Should handle relative positioning (doesn't layout children, just applies offsets) layout:layoutChildren() end -- Edge cases TestLayoutEngineEdgeCases = {} function TestLayoutEngineEdgeCases:testAutoWidthWithZeroGap() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, gap = 0, }, deps) local mockChild1 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 50 end, } local mockChild2 = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 60 end, } local mockElement = { children = { mockChild1, mockChild2 }, calculateTextWidth = function() return 0 end, } layout:initialize(mockElement) local width = layout:calculateAutoWidth() luaunit.assertEquals(width, 110) -- 50 + 60, no gaps end function TestLayoutEngineEdgeCases:testAutoHeightWithSingleChild() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.VERTICAL, gap = 10, }, deps) local mockChild = { _explicitlyAbsolute = false, getBorderBoxHeight = function() return 100 end, } local mockElement = { children = { mockChild }, calculateTextHeight = function() return 0 end, } layout:initialize(mockElement) local height = layout:calculateAutoHeight() luaunit.assertEquals(height, 100) -- No gaps with single child end function TestLayoutEngineEdgeCases:testAutoWidthWithTextAndChildren() local layout = LayoutEngine.new({ flexDirection = utils.enums.FlexDirection.HORIZONTAL, gap = 10, }, deps) local mockChild = { _explicitlyAbsolute = false, getBorderBoxWidth = function() return 50 end, } local mockElement = { children = { mockChild }, calculateTextWidth = function() return 100 end, -- Has text } layout:initialize(mockElement) local width = layout:calculateAutoWidth() -- Text width (100) + child width (50) = 150 luaunit.assertEquals(width, 150) end -- Run tests if not running as part of a suite if not _G.RUNNING_ALL_TESTS then os.exit(luaunit.LuaUnit.run()) end