1680 lines
44 KiB
Lua
1680 lines
44 KiB
Lua
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
|
local originalSearchers = package.searchers or package.loaders
|
|
table.insert(originalSearchers, 2, function(modname)
|
|
if modname:match("^FlexLove%.modules%.") then
|
|
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
|
|
return function()
|
|
return require("modules." .. moduleName)
|
|
end
|
|
end
|
|
end)
|
|
|
|
require("testing.loveStub")
|
|
|
|
local luaunit = require("testing.luaunit")
|
|
local LayoutEngine = require("modules.LayoutEngine")
|
|
local Units = require("modules.Units")
|
|
local utils = require("modules.utils")
|
|
local ErrorHandler = require("modules.ErrorHandler")
|
|
local Animation = require("modules.Animation")
|
|
local FlexLove = require("FlexLove")
|
|
local Transform = Animation.Transform
|
|
|
|
-- ============================================================================
|
|
-- 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,
|
|
}
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 1: LayoutEngine Initialization and Constructor
|
|
-- ============================================================================
|
|
|
|
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 2: LayoutEngine Initialization
|
|
-- ============================================================================
|
|
|
|
TestLayoutEngineInitialize = {}
|
|
|
|
function TestLayoutEngineInitialize:testInitialize()
|
|
local layout = LayoutEngine.new({}, deps)
|
|
local mockElement = { id = "test" }
|
|
|
|
layout:initialize(mockElement)
|
|
luaunit.assertEquals(layout.element, mockElement)
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 3: Auto Width Calculation
|
|
-- ============================================================================
|
|
|
|
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
|
|
|
|
function TestLayoutEngineAutoWidth: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 TestLayoutEngineAutoWidth: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
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 4: Auto Height Calculation
|
|
-- ============================================================================
|
|
|
|
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
|
|
|
|
function TestLayoutEngineAutoHeight: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
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 5: CSS Positioning Offsets
|
|
-- ============================================================================
|
|
|
|
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 6: Layout Children
|
|
-- ============================================================================
|
|
|
|
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
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 7: Layout Edge Cases and CSS Positioning (Immediate Mode)
|
|
-- ============================================================================
|
|
|
|
TestLayoutEdgeCases = {}
|
|
|
|
function TestLayoutEdgeCases:setUp()
|
|
FlexLove.init()
|
|
FlexLove.setMode("immediate")
|
|
FlexLove.beginFrame()
|
|
-- Capture warnings
|
|
self.warnings = {}
|
|
self.originalWarn = ErrorHandler.warn
|
|
ErrorHandler.warn = function(module, message)
|
|
table.insert(self.warnings, { module = module, message = message })
|
|
end
|
|
end
|
|
|
|
function TestLayoutEdgeCases:tearDown()
|
|
-- Restore original warn function
|
|
ErrorHandler.warn = self.originalWarn
|
|
FlexLove.endFrame()
|
|
end
|
|
|
|
-- Percentage sizing warnings (placeholders for future implementation)
|
|
function TestLayoutEdgeCases:test_percentage_width_with_auto_parent_warns()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
-- width not specified - auto-sizing width
|
|
height = 200,
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
})
|
|
|
|
FlexLove.new({
|
|
id = "child_with_percentage",
|
|
parent = container,
|
|
width = "50%", -- Percentage width with auto-sizing parent - should warn
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Check that a warning was issued
|
|
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage width with auto-sizing parent")
|
|
|
|
-- Note: This warning feature is not yet implemented
|
|
luaunit.assertTrue(true, "Placeholder - percentage width warning not implemented yet")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_percentage_height_with_auto_parent_warns()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
-- height not specified - auto-sizing height
|
|
positioning = "flex",
|
|
flexDirection = "vertical",
|
|
})
|
|
|
|
FlexLove.new({
|
|
id = "child_with_percentage",
|
|
parent = container,
|
|
width = 100,
|
|
height = "50%", -- Percentage height with auto-sizing parent - should warn
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Check that a warning was issued
|
|
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage height with auto-sizing parent")
|
|
|
|
-- Note: This warning feature is not yet implemented
|
|
luaunit.assertTrue(true, "Placeholder - percentage height warning not implemented yet")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_pixel_width_with_auto_parent_no_warn()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
-- width not specified - auto-sizing
|
|
height = 200,
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
})
|
|
|
|
FlexLove.new({
|
|
id = "child_with_pixels",
|
|
parent = container,
|
|
width = 100, -- Pixel width - should NOT warn
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Check that NO warning was issued about percentage sizing
|
|
for _, warning in ipairs(self.warnings) do
|
|
local hasPercentageWarning = warning.message:match("percentage") and warning.message:match("auto%-sizing")
|
|
luaunit.assertFalse(hasPercentageWarning, "Should not warn for pixel-sized children")
|
|
end
|
|
end
|
|
|
|
-- CSS positioning tests
|
|
function TestLayoutEdgeCases:test_css_positioning_top_offset()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 50, -- 50px from top
|
|
left = 0,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
-- Trigger layout by ending and restarting frame
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Child should be positioned 50px from container's top edge (accounting for padding)
|
|
local expectedY = container.y + container.padding.top + 50
|
|
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with top offset")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_bottom_offset()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
bottom = 50, -- 50px from bottom
|
|
left = 0,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Child should be positioned 50px from container's bottom edge
|
|
local expectedY = container.y + container.padding.top + container.height - 50 - child:getBorderBoxHeight()
|
|
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with bottom offset")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_left_offset()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 0,
|
|
left = 50, -- 50px from left
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Child should be positioned 50px from container's left edge
|
|
local expectedX = container.x + container.padding.left + 50
|
|
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with left offset")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_right_offset()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 0,
|
|
right = 50, -- 50px from right
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Child should be positioned 50px from container's right edge
|
|
local expectedX = container.x + container.padding.left + container.width - 50 - child:getBorderBoxWidth()
|
|
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with right offset")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_top_and_bottom()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 10,
|
|
bottom = 20, -- Both specified - last one wins in current implementation
|
|
left = 0,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Bottom should override top
|
|
local expectedY = container.y + container.padding.top + container.height - 20 - child:getBorderBoxHeight()
|
|
luaunit.assertEquals(child.y, expectedY, "Bottom offset should override top when both specified")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_left_and_right()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 0,
|
|
left = 10,
|
|
right = 20, -- Both specified - last one wins in current implementation
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Right should override left
|
|
local expectedX = container.x + container.padding.left + container.width - 20 - child:getBorderBoxWidth()
|
|
luaunit.assertEquals(child.x, expectedX, "Right offset should override left when both specified")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_with_padding()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
|
positioning = "absolute",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 10,
|
|
left = 10,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Offsets should be relative to content area (after padding)
|
|
local expectedX = container.x + container.padding.left + 10
|
|
local expectedY = container.y + container.padding.top + 10
|
|
|
|
luaunit.assertEquals(child.x, expectedX, "Left offset should account for container padding")
|
|
luaunit.assertEquals(child.y, expectedY, "Top offset should account for container padding")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_ignored_in_flex()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
top = 100, -- This should be IGNORED in flex layout
|
|
left = 100, -- This should be IGNORED in flex layout
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- In flex layout, child should be positioned by flex rules, not CSS offsets
|
|
-- Child should be at (0, 0) relative to container content area
|
|
luaunit.assertEquals(child.x, 0, "CSS offsets should be ignored in flex layout")
|
|
luaunit.assertEquals(child.y, 0, "CSS offsets should be ignored in flex layout")
|
|
end
|
|
|
|
function TestLayoutEdgeCases:test_css_positioning_in_relative_container()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 100,
|
|
y = 100,
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "relative",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 30,
|
|
left = 30,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame()
|
|
|
|
-- Should work the same as absolute container
|
|
local expectedX = container.x + container.padding.left + 30
|
|
local expectedY = container.y + container.padding.top + 30
|
|
|
|
luaunit.assertEquals(child.x, expectedX, "CSS positioning should work in relative containers")
|
|
luaunit.assertEquals(child.y, expectedY, "CSS positioning should work in relative containers")
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 8: Overflow Detection and Scrolling
|
|
-- ============================================================================
|
|
|
|
TestOverflowDetection = {}
|
|
|
|
function TestOverflowDetection:setUp()
|
|
FlexLove.init()
|
|
FlexLove.beginFrame()
|
|
end
|
|
|
|
function TestOverflowDetection:tearDown()
|
|
FlexLove.endFrame()
|
|
end
|
|
|
|
function TestOverflowDetection:test_vertical_overflow_detected()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 100,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Add child that exceeds container height
|
|
FlexLove.new({
|
|
id = "tall_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 200, -- Taller than container (100)
|
|
})
|
|
|
|
-- Force layout to trigger detectOverflow
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
-- Check if overflow was detected
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
|
|
luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_horizontal_overflow_detected()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Add child that exceeds container width
|
|
FlexLove.new({
|
|
id = "wide_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 300, -- Wider than container (100)
|
|
height = 50,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
|
|
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_both_axes_overflow()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 100,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Add child that exceeds both dimensions
|
|
FlexLove.new({
|
|
id = "large_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
|
|
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_no_overflow_when_content_fits()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Add child that fits within container
|
|
FlexLove.new({
|
|
id = "small_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow")
|
|
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_overflow_with_multiple_children()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
positioning = "flex",
|
|
flexDirection = "vertical",
|
|
})
|
|
|
|
-- Add multiple children that together exceed container
|
|
for i = 1, 5 do
|
|
FlexLove.new({
|
|
id = "child_" .. i,
|
|
parent = container,
|
|
width = 150,
|
|
height = 60, -- 5 * 60 = 300, exceeds container height of 200
|
|
})
|
|
end
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollY > 0, "Should detect overflow from multiple children")
|
|
end
|
|
|
|
function TestOverflowDetection:test_overflow_with_padding()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
padding = { top = 10, right = 10, bottom = 10, left = 10 },
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Child that fits in container but exceeds available content area (200 - 20 = 180)
|
|
FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 190, -- Exceeds content width (180)
|
|
height = 100,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollX > 0, "Should detect overflow accounting for padding")
|
|
end
|
|
|
|
function TestOverflowDetection:test_overflow_with_margins()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Child with margins that contribute to overflow
|
|
-- In flex layout, margins are properly accounted for in positioning
|
|
FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
width = 180,
|
|
height = 180,
|
|
margin = { top = 5, right = 20, bottom = 5, left = 5 }, -- Total width: 5+180+20=205, overflows 200px container
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertTrue(maxScrollX > 0, "Should include child margins in overflow calculation")
|
|
end
|
|
|
|
function TestOverflowDetection:test_visible_overflow_skips_detection()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 100,
|
|
overflow = "visible", -- Should not clip or calculate overflow
|
|
})
|
|
|
|
-- Add oversized child
|
|
FlexLove.new({
|
|
id = "large_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 300,
|
|
height = 300,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
-- With overflow="visible", maxScroll should be 0 (no scrolling)
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertEquals(maxScrollX, 0, "visible overflow should not enable scrolling")
|
|
luaunit.assertEquals(maxScrollY, 0, "visible overflow should not enable scrolling")
|
|
end
|
|
|
|
function TestOverflowDetection:test_empty_container_no_overflow()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
-- No children
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
luaunit.assertEquals(maxScrollX, 0, "Empty container should have no overflow")
|
|
luaunit.assertEquals(maxScrollY, 0, "Empty container should have no overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_absolute_children_ignored_in_overflow()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
-- Regular child that fits
|
|
FlexLove.new({
|
|
id = "normal_child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 150,
|
|
height = 150,
|
|
})
|
|
|
|
-- Absolutely positioned child that extends beyond (should NOT cause overflow)
|
|
FlexLove.new({
|
|
id = "absolute_child",
|
|
parent = container,
|
|
positioning = "absolute",
|
|
top = 0,
|
|
left = 0,
|
|
width = 400,
|
|
height = 400,
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
-- Should not have overflow because absolute children are ignored
|
|
luaunit.assertEquals(maxScrollX, 0, "Absolute children should not cause overflow")
|
|
luaunit.assertEquals(maxScrollY, 0, "Absolute children should not cause overflow")
|
|
end
|
|
|
|
function TestOverflowDetection:test_scroll_clamped_to_max()
|
|
local container = FlexLove.new({
|
|
id = "container",
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 100,
|
|
overflow = "scroll",
|
|
})
|
|
|
|
FlexLove.new({
|
|
id = "child",
|
|
parent = container,
|
|
x = 0,
|
|
y = 0,
|
|
width = 100,
|
|
height = 300, -- Creates 200px of vertical overflow
|
|
})
|
|
|
|
FlexLove.endFrame()
|
|
FlexLove.beginFrame(1920, 1080)
|
|
|
|
-- Try to scroll beyond max
|
|
container:setScrollPosition(0, 999999)
|
|
local scrollX, scrollY = container:getScrollPosition()
|
|
local maxScrollX, maxScrollY = container:getMaxScroll()
|
|
|
|
luaunit.assertEquals(scrollY, maxScrollY, "Scroll should be clamped to maximum")
|
|
luaunit.assertTrue(scrollY < 999999, "Should not scroll beyond content")
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Test Suite 9: Transform (from Animation module)
|
|
-- ============================================================================
|
|
|
|
TestTransform = {}
|
|
|
|
function TestTransform:setUp()
|
|
-- Reset state before each test
|
|
end
|
|
|
|
-- Transform.new() tests
|
|
function TestTransform:testNew_DefaultValues()
|
|
local transform = Transform.new()
|
|
|
|
luaunit.assertNotNil(transform)
|
|
luaunit.assertEquals(transform.rotate, 0)
|
|
luaunit.assertEquals(transform.scaleX, 1)
|
|
luaunit.assertEquals(transform.scaleY, 1)
|
|
luaunit.assertEquals(transform.translateX, 0)
|
|
luaunit.assertEquals(transform.translateY, 0)
|
|
luaunit.assertEquals(transform.skewX, 0)
|
|
luaunit.assertEquals(transform.skewY, 0)
|
|
luaunit.assertEquals(transform.originX, 0.5)
|
|
luaunit.assertEquals(transform.originY, 0.5)
|
|
end
|
|
|
|
function TestTransform:testNew_CustomValues()
|
|
local transform = Transform.new({
|
|
rotate = math.pi / 4,
|
|
scaleX = 2,
|
|
scaleY = 3,
|
|
translateX = 100,
|
|
translateY = 200,
|
|
skewX = 0.1,
|
|
skewY = 0.2,
|
|
originX = 0,
|
|
originY = 1,
|
|
})
|
|
|
|
luaunit.assertAlmostEquals(transform.rotate, math.pi / 4, 0.01)
|
|
luaunit.assertEquals(transform.scaleX, 2)
|
|
luaunit.assertEquals(transform.scaleY, 3)
|
|
luaunit.assertEquals(transform.translateX, 100)
|
|
luaunit.assertEquals(transform.translateY, 200)
|
|
luaunit.assertAlmostEquals(transform.skewX, 0.1, 0.01)
|
|
luaunit.assertAlmostEquals(transform.skewY, 0.2, 0.01)
|
|
luaunit.assertEquals(transform.originX, 0)
|
|
luaunit.assertEquals(transform.originY, 1)
|
|
end
|
|
|
|
function TestTransform:testNew_PartialValues()
|
|
local transform = Transform.new({
|
|
rotate = math.pi,
|
|
scaleX = 2,
|
|
})
|
|
|
|
luaunit.assertAlmostEquals(transform.rotate, math.pi, 0.01)
|
|
luaunit.assertEquals(transform.scaleX, 2)
|
|
luaunit.assertEquals(transform.scaleY, 1) -- default
|
|
luaunit.assertEquals(transform.translateX, 0) -- default
|
|
end
|
|
|
|
function TestTransform:testNew_EmptyProps()
|
|
local transform = Transform.new({})
|
|
|
|
-- Should use all defaults
|
|
luaunit.assertEquals(transform.rotate, 0)
|
|
luaunit.assertEquals(transform.scaleX, 1)
|
|
luaunit.assertEquals(transform.originX, 0.5)
|
|
end
|
|
|
|
function TestTransform:testNew_NilProps()
|
|
local transform = Transform.new(nil)
|
|
|
|
-- Should use all defaults
|
|
luaunit.assertEquals(transform.rotate, 0)
|
|
luaunit.assertEquals(transform.scaleX, 1)
|
|
end
|
|
|
|
-- Transform.lerp() tests
|
|
function TestTransform:testLerp_MidPoint()
|
|
local from = Transform.new({ rotate = 0, scaleX = 1, scaleY = 1 })
|
|
local to = Transform.new({ rotate = math.pi, scaleX = 2, scaleY = 3 })
|
|
|
|
local result = Transform.lerp(from, to, 0.5)
|
|
|
|
luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
|
|
end
|
|
|
|
function TestTransform:testLerp_StartPoint()
|
|
local from = Transform.new({ rotate = 0, scaleX = 1 })
|
|
local to = Transform.new({ rotate = math.pi, scaleX = 2 })
|
|
|
|
local result = Transform.lerp(from, to, 0)
|
|
|
|
luaunit.assertAlmostEquals(result.rotate, 0, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleX, 1, 0.01)
|
|
end
|
|
|
|
function TestTransform:testLerp_EndPoint()
|
|
local from = Transform.new({ rotate = 0, scaleX = 1 })
|
|
local to = Transform.new({ rotate = math.pi, scaleX = 2 })
|
|
|
|
local result = Transform.lerp(from, to, 1)
|
|
|
|
luaunit.assertAlmostEquals(result.rotate, math.pi, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleX, 2, 0.01)
|
|
end
|
|
|
|
function TestTransform:testLerp_AllProperties()
|
|
local from = Transform.new({
|
|
rotate = 0,
|
|
scaleX = 1,
|
|
scaleY = 1,
|
|
translateX = 0,
|
|
translateY = 0,
|
|
skewX = 0,
|
|
skewY = 0,
|
|
originX = 0,
|
|
originY = 0,
|
|
})
|
|
|
|
local to = Transform.new({
|
|
rotate = math.pi,
|
|
scaleX = 2,
|
|
scaleY = 3,
|
|
translateX = 100,
|
|
translateY = 200,
|
|
skewX = 0.2,
|
|
skewY = 0.4,
|
|
originX = 1,
|
|
originY = 1,
|
|
})
|
|
|
|
local result = Transform.lerp(from, to, 0.5)
|
|
|
|
luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
|
|
luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
|
|
luaunit.assertAlmostEquals(result.translateX, 50, 0.01)
|
|
luaunit.assertAlmostEquals(result.translateY, 100, 0.01)
|
|
luaunit.assertAlmostEquals(result.skewX, 0.1, 0.01)
|
|
luaunit.assertAlmostEquals(result.skewY, 0.2, 0.01)
|
|
luaunit.assertAlmostEquals(result.originX, 0.5, 0.01)
|
|
luaunit.assertAlmostEquals(result.originY, 0.5, 0.01)
|
|
end
|
|
|
|
function TestTransform:testLerp_InvalidInputs()
|
|
-- Should handle nil gracefully
|
|
local result = Transform.lerp(nil, nil, 0.5)
|
|
|
|
luaunit.assertNotNil(result)
|
|
luaunit.assertEquals(result.rotate, 0)
|
|
luaunit.assertEquals(result.scaleX, 1)
|
|
end
|
|
|
|
function TestTransform:testLerp_ClampT()
|
|
local from = Transform.new({ scaleX = 1 })
|
|
local to = Transform.new({ scaleX = 2 })
|
|
|
|
-- Test t > 1
|
|
local result1 = Transform.lerp(from, to, 1.5)
|
|
luaunit.assertAlmostEquals(result1.scaleX, 2, 0.01)
|
|
|
|
-- Test t < 0
|
|
local result2 = Transform.lerp(from, to, -0.5)
|
|
luaunit.assertAlmostEquals(result2.scaleX, 1, 0.01)
|
|
end
|
|
|
|
function TestTransform:testLerp_InvalidT()
|
|
local from = Transform.new({ scaleX = 1 })
|
|
local to = Transform.new({ scaleX = 2 })
|
|
|
|
-- Test NaN
|
|
local result1 = Transform.lerp(from, to, 0 / 0)
|
|
luaunit.assertAlmostEquals(result1.scaleX, 1, 0.01) -- Should default to 0
|
|
|
|
-- Test Infinity
|
|
local result2 = Transform.lerp(from, to, math.huge)
|
|
luaunit.assertAlmostEquals(result2.scaleX, 2, 0.01) -- Should clamp to 1
|
|
end
|
|
|
|
-- Transform.isIdentity() tests
|
|
function TestTransform:testIsIdentity_True()
|
|
local transform = Transform.new()
|
|
luaunit.assertTrue(Transform.isIdentity(transform))
|
|
end
|
|
|
|
function TestTransform:testIsIdentity_Nil()
|
|
luaunit.assertTrue(Transform.isIdentity(nil))
|
|
end
|
|
|
|
function TestTransform:testIsIdentity_FalseRotate()
|
|
local transform = Transform.new({ rotate = 0.1 })
|
|
luaunit.assertFalse(Transform.isIdentity(transform))
|
|
end
|
|
|
|
function TestTransform:testIsIdentity_FalseScale()
|
|
local transform = Transform.new({ scaleX = 2 })
|
|
luaunit.assertFalse(Transform.isIdentity(transform))
|
|
end
|
|
|
|
function TestTransform:testIsIdentity_FalseTranslate()
|
|
local transform = Transform.new({ translateX = 10 })
|
|
luaunit.assertFalse(Transform.isIdentity(transform))
|
|
end
|
|
|
|
function TestTransform:testIsIdentity_FalseSkew()
|
|
local transform = Transform.new({ skewX = 0.1 })
|
|
luaunit.assertFalse(Transform.isIdentity(transform))
|
|
end
|
|
|
|
-- Transform.clone() tests
|
|
function TestTransform:testClone_AllProperties()
|
|
local original = Transform.new({
|
|
rotate = math.pi / 4,
|
|
scaleX = 2,
|
|
scaleY = 3,
|
|
translateX = 100,
|
|
translateY = 200,
|
|
skewX = 0.1,
|
|
skewY = 0.2,
|
|
originX = 0.25,
|
|
originY = 0.75,
|
|
})
|
|
|
|
local clone = Transform.clone(original)
|
|
|
|
luaunit.assertAlmostEquals(clone.rotate, math.pi / 4, 0.01)
|
|
luaunit.assertEquals(clone.scaleX, 2)
|
|
luaunit.assertEquals(clone.scaleY, 3)
|
|
luaunit.assertEquals(clone.translateX, 100)
|
|
luaunit.assertEquals(clone.translateY, 200)
|
|
luaunit.assertAlmostEquals(clone.skewX, 0.1, 0.01)
|
|
luaunit.assertAlmostEquals(clone.skewY, 0.2, 0.01)
|
|
luaunit.assertAlmostEquals(clone.originX, 0.25, 0.01)
|
|
luaunit.assertAlmostEquals(clone.originY, 0.75, 0.01)
|
|
|
|
-- Ensure it's a different object (use raw comparison)
|
|
luaunit.assertFalse(rawequal(clone, original), "Clone should be a different table instance")
|
|
end
|
|
|
|
function TestTransform:testClone_Nil()
|
|
local clone = Transform.clone(nil)
|
|
|
|
luaunit.assertNotNil(clone)
|
|
luaunit.assertEquals(clone.rotate, 0)
|
|
luaunit.assertEquals(clone.scaleX, 1)
|
|
end
|
|
|
|
function TestTransform:testClone_Mutation()
|
|
local original = Transform.new({ rotate = 0 })
|
|
local clone = Transform.clone(original)
|
|
|
|
-- Mutate clone
|
|
clone.rotate = math.pi
|
|
|
|
-- Original should be unchanged
|
|
luaunit.assertEquals(original.rotate, 0)
|
|
luaunit.assertAlmostEquals(clone.rotate, math.pi, 0.01)
|
|
end
|
|
|
|
-- Integration tests
|
|
function TestTransform:testTransformAnimation()
|
|
local anim = Animation.new({
|
|
duration = 1,
|
|
start = { transform = Transform.new({ rotate = 0, scaleX = 1 }) },
|
|
final = { transform = Transform.new({ rotate = math.pi, scaleX = 2 }) },
|
|
})
|
|
|
|
anim:update(0.5)
|
|
|
|
local result = anim:interpolate()
|
|
|
|
luaunit.assertNotNil(result.transform)
|
|
luaunit.assertAlmostEquals(result.transform.rotate, math.pi / 2, 0.01)
|
|
luaunit.assertAlmostEquals(result.transform.scaleX, 1.5, 0.01)
|
|
end
|
|
|
|
-- ============================================================================
|
|
-- Run Tests
|
|
-- ============================================================================
|
|
|
|
if not _G.RUNNING_ALL_TESTS then
|
|
os.exit(luaunit.LuaUnit.run())
|
|
end
|