Files
FlexLove/testing/__tests__/layout_engine_test.lua
Michael Freno 472bf358f4 more testing
2025-11-15 02:02:02 -05:00

723 lines
18 KiB
Lua

-- 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
local Units = require("modules.Units")
local utils = require("modules.utils")
-- Mock dependencies
local mockContext = {
getScaleFactors = function()
return 1, 1
end,
baseScale = 1,
_cachedViewport = { width = 1920, height = 1080 },
}
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,
}
-- Helper function to create mock element
local function createMockElement(props)
return {
id = props.id or "mock",
x = props.x or 0,
y = props.y or 0,
width = props.width or 100,
height = props.height or 100,
absoluteX = props.absoluteX or 0,
absoluteY = props.absoluteY or 0,
marginLeft = props.marginLeft or 0,
marginTop = props.marginTop or 0,
marginRight = props.marginRight or 0,
marginBottom = props.marginBottom or 0,
children = props.children or {},
parent = props.parent,
isHidden = props.isHidden or false,
flexGrow = props.flexGrow or 0,
flexShrink = props.flexShrink or 1,
flexBasis = props.flexBasis or "auto",
alignSelf = props.alignSelf,
minWidth = props.minWidth,
maxWidth = props.maxWidth,
minHeight = props.minHeight,
maxHeight = props.maxHeight,
text = props.text,
_layout = nil,
recalculateUnits = function() end,
layoutChildren = function() end,
}
end
-- Run tests if this file is executed directly
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end