Files
FlexLove/testing/__tests__/09_layout_validation_tests.lua
2025-10-14 01:58:03 -04:00

1259 lines
40 KiB
Lua

package.path = package.path .. ";?.lua"
local luaunit = require("testing/luaunit")
require("testing/loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui, enums = FlexLove.GUI, FlexLove.enums
local Color = FlexLove.Color
local Positioning = enums.Positioning
local FlexDirection = enums.FlexDirection
local JustifyContent = enums.JustifyContent
local AlignItems = enums.AlignItems
local FlexWrap = enums.FlexWrap
-- Create test cases for layout validation
TestLayoutValidation = {}
function TestLayoutValidation:setUp()
-- Clean up before each test
Gui.destroy()
end
function TestLayoutValidation:tearDown()
-- Clean up after each test
Gui.destroy()
end
-- Helper function to capture errors without crashing
local function captureError(func)
local success, error_msg = pcall(func)
return success, error_msg
end
-- Helper function to create test containers
local function createTestContainer(props)
props = props or {}
local defaults = {
x = 0,
y = 0,
width = 200,
height = 150,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
flexWrap = FlexWrap.NOWRAP,
gap = 0,
}
-- Merge props with defaults
for key, value in pairs(props) do
defaults[key] = value
end
return Gui.new(defaults)
end
-- Test 1: Invalid Color Hex Strings
function TestLayoutValidation:testInvalidColorHexStrings()
-- Test completely invalid hex string
local success, error_msg = captureError(function()
Color.fromHex("invalid")
end)
luaunit.assertFalse(success)
luaunit.assertTrue(string.find(error_msg, "Invalid hex string") ~= nil)
-- Test wrong length hex string
local success2, error_msg2 = captureError(function()
Color.fromHex("#ABC")
end)
luaunit.assertFalse(success2)
luaunit.assertTrue(string.find(error_msg2, "Invalid hex string") ~= nil)
-- Test valid hex strings (should not error)
local success3, color3 = captureError(function()
return Color.fromHex("#FF0000")
end)
luaunit.assertTrue(success3)
luaunit.assertIsTable(color3)
local success4, color4 = captureError(function()
return Color.fromHex("#FF0000AA")
end)
luaunit.assertTrue(success4)
luaunit.assertIsTable(color4)
end
-- Test 2: Invalid Enum Values (Graceful Degradation)
function TestLayoutValidation:testInvalidEnumValuesGracefulDegradation()
-- Test with invalid flexDirection - should not crash, use default
local success, container = captureError(function()
return Gui.new({
x = 0,
y = 0,
width = 100,
height = 100,
positioning = Positioning.FLEX,
-- flexDirection = "invalid_direction", -- Skip invalid enum to avoid type error
})
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(container.flexDirection, FlexDirection.HORIZONTAL) -- Should use default
-- Test with invalid justifyContent
local success2, container2 = captureError(function()
return Gui.new({
x = 0,
y = 0,
width = 100,
height = 100,
positioning = Positioning.FLEX,
-- justifyContent = "invalid_justify", -- Skip invalid enum to avoid type error
})
end)
luaunit.assertTrue(success2) -- Should not crash
luaunit.assertEquals(container2.justifyContent, JustifyContent.FLEX_START) -- Should use default
end
-- Test 3: Missing Required Properties (Graceful Defaults)
function TestLayoutValidation:testMissingRequiredPropertiesDefaults()
-- Test element creation with minimal properties
local success, element = captureError(function()
return Gui.new({}) -- Completely empty props
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertIsNumber(element.x)
luaunit.assertIsNumber(element.y)
luaunit.assertIsNumber(element.width)
luaunit.assertIsNumber(element.height)
luaunit.assertEquals(element.positioning, Positioning.RELATIVE) -- Default positioning
-- Test flex container with minimal properties
local success2, flex_element = captureError(function()
return Gui.new({
positioning = Positioning.FLEX, -- Only positioning specified
})
end)
luaunit.assertTrue(success2) -- Should not crash
luaunit.assertEquals(flex_element.flexDirection, FlexDirection.HORIZONTAL) -- Default
luaunit.assertEquals(flex_element.justifyContent, JustifyContent.FLEX_START) -- Default
luaunit.assertEquals(flex_element.alignItems, AlignItems.STRETCH) -- Default
end
-- Test 4: Invalid Property Combinations
function TestLayoutValidation:testInvalidPropertyCombinations()
-- Test absolute positioned element with flex properties (should be ignored)
local success, absolute_element = captureError(function()
return Gui.new({
x = 10,
y = 10,
width = 100,
height = 100,
positioning = Positioning.ABSOLUTE,
flexDirection = FlexDirection.VERTICAL, -- Should be ignored
justifyContent = JustifyContent.CENTER, -- Should be ignored
})
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(absolute_element.positioning, Positioning.ABSOLUTE)
-- Note: FlexLove might still store these properties even for absolute elements
-- Test flex element can have both flex and position properties
local success2, flex_element = captureError(function()
return Gui.new({
x = 10, -- Should work with flex
y = 10, -- Should work with flex
width = 100,
height = 100,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
end)
luaunit.assertTrue(success2) -- Should not crash
luaunit.assertEquals(flex_element.positioning, Positioning.FLEX)
luaunit.assertEquals(flex_element.flexDirection, FlexDirection.VERTICAL)
end
-- Test 5: Negative Dimensions and Positions
function TestLayoutValidation:testNegativeDimensionsAndPositions()
-- Test negative width and height (should work)
local success, element = captureError(function()
return Gui.new({
x = -10,
y = -20,
width = -50,
height = -30,
})
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(element.x, -10) -- Negative positions should work
luaunit.assertEquals(element.y, -20)
luaunit.assertEquals(element.width, 0) -- Negative dimensions are clamped to 0
luaunit.assertEquals(element.height, 0) -- Negative dimensions are clamped to 0
end
-- Test 6: Extremely Large Values
function TestLayoutValidation:testExtremelyLargeValues()
local success, element = captureError(function()
return Gui.new({
x = 999999,
y = 999999,
width = 999999,
height = 999999,
})
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(element.x, 999999)
luaunit.assertEquals(element.y, 999999)
luaunit.assertEquals(element.width, 999999)
luaunit.assertEquals(element.height, 999999)
end
-- Test 7: Invalid Child-Parent Relationships
function TestLayoutValidation:testInvalidChildParentRelationships()
local parent = createTestContainer()
-- Test adding child with conflicting positioning
local success, child = captureError(function()
local child = Gui.new({
x = 10,
y = 10,
width = 50,
height = 30,
positioning = Positioning.FLEX, -- Child tries to be flex container
})
child.parent = parent
table.insert(parent.children, child)
return child
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(child.positioning, Positioning.FLEX) -- Should respect explicit positioning
luaunit.assertEquals(child.parent, parent)
luaunit.assertEquals(#parent.children, 1)
end
-- Test 8: Layout After Property Changes
function TestLayoutValidation:testLayoutAfterPropertyChanges()
local container = createTestContainer()
local child1 = Gui.new({
width = 50,
height = 30,
})
child1.parent = container
table.insert(container.children, child1)
local child2 = Gui.new({
width = 60,
height = 35,
})
child2.parent = container
table.insert(container.children, child2)
-- Change container properties and verify layout still works
local success = captureError(function()
container.flexDirection = FlexDirection.VERTICAL
container:layoutChildren()
end)
luaunit.assertTrue(success) -- Should not crash
-- Verify positions changed appropriately
local new_pos1 = { x = child1.x, y = child1.y }
local new_pos2 = { x = child2.x, y = child2.y }
-- In vertical layout, child2 should be below child1
luaunit.assertTrue(new_pos2.y >= new_pos1.y) -- child2 should be at or below child1
end
-- Test 9: Autosizing Edge Cases
function TestLayoutValidation:testAutosizingEdgeCases()
-- Test element with autosizing width/height
local success, element = captureError(function()
return Gui.new({
x = 0,
y = 0,
-- No w or h specified - should autosize
})
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertIsNumber(element.width) -- Should have calculated width
luaunit.assertIsNumber(element.height) -- Should have calculated height
-- Note: FlexLove might not have autosizing.width/height fields
end
-- Test 10: Complex Nested Validation
function TestLayoutValidation:testComplexNestedValidation()
-- Create deeply nested structure with mixed positioning
local success, root = captureError(function()
local root = Gui.new({
x = 0,
y = 0,
width = 200,
height = 150,
positioning = Positioning.FLEX,
})
local flex_child = Gui.new({
width = 100,
height = 75,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
flex_child.parent = root
table.insert(root.children, flex_child)
local absolute_grandchild = Gui.new({
x = 10,
y = 10,
width = 30,
height = 20,
positioning = Positioning.ABSOLUTE,
})
absolute_grandchild.parent = flex_child
table.insert(flex_child.children, absolute_grandchild)
local flex_grandchild = Gui.new({
width = 40,
height = 25,
-- No positioning - should inherit behavior
})
flex_grandchild.parent = flex_child
table.insert(flex_child.children, flex_grandchild)
return root
end)
luaunit.assertTrue(success) -- Should not crash
luaunit.assertEquals(#root.children, 1)
luaunit.assertEquals(#root.children[1].children, 2)
-- Verify positioning was handled correctly
local flex_child = root.children[1]
luaunit.assertEquals(flex_child.positioning, Positioning.FLEX)
local absolute_grandchild = flex_child.children[1]
local flex_grandchild = flex_child.children[2]
luaunit.assertEquals(absolute_grandchild.positioning, Positioning.ABSOLUTE)
-- flex_grandchild positioning depends on FlexLove's behavior
end
-- ===================================
-- COMPLEX VALIDATION STRUCTURE TESTS
-- ===================================
-- Test 11: Complex Multi-Level Layout Validation
function TestLayoutValidation:testComplexMultiLevelLayoutValidation()
-- Test complex application-like structure with validation at every level
local success, app = captureError(function()
-- Main app container
local app = Gui.new({
x = 0,
y = 0,
width = 1200,
height = 800,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 0,
})
-- Header with complex validation scenarios
local header = Gui.new({
height = 60,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.SPACE_BETWEEN,
alignItems = AlignItems.CENTER,
gap = 20,
padding = { top = 10, right = 20, bottom = 10, left = 20 },
})
header.parent = app
table.insert(app.children, header)
-- Header navigation with potential edge cases
local nav = Gui.new({
width = 400,
height = 40,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.CENTER,
gap = 16,
})
nav.parent = header
table.insert(header.children, nav)
-- Create nav items with extreme values
for i = 1, 5 do
local navItem = Gui.new({
width = i == 3 and 0 or 80, -- One item with zero width
height = i == 4 and -10 or 24, -- One item with negative height
positioning = i == 5 and Positioning.ABSOLUTE or nil, -- Mixed positioning
})
navItem.parent = nav
table.insert(nav.children, navItem)
end
-- Header actions with validation edge cases
local actions = Gui.new({
width = 200,
height = 40,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.FLEX_END,
alignItems = AlignItems.CENTER,
gap = 12,
})
actions.parent = header
table.insert(header.children, actions)
-- Actions with extreme dimensions
for i = 1, 3 do
local action = Gui.new({
width = i == 1 and 999999 or 32, -- Extremely large width
height = i == 2 and 0.1 or 32, -- Fractional height
x = i == 3 and -1000 or nil, -- Extreme negative position
y = i == 3 and -1000 or nil,
})
action.parent = actions
table.insert(actions.children, action)
end
-- Main content with nested validation challenges
local main = Gui.new({
height = 740,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 0,
})
main.parent = app
table.insert(app.children, main)
-- Sidebar with deep nesting and edge cases
local sidebar = Gui.new({
width = 250,
height = 740,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 20,
padding = { top = 20, right = 0, bottom = 20, left = 20 },
})
sidebar.parent = main
table.insert(main.children, sidebar)
-- Sidebar sections with validation challenges
for section = 1, 3 do
local sideSection = Gui.new({
height = section == 2 and -100 or 200, -- Negative height test
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 8,
})
sideSection.parent = sidebar
table.insert(sidebar.children, sideSection)
-- Section items with extreme properties
for item = 1, 4 do
local sectionItem = Gui.new({
height = 24,
width = item == 2 and 0 or nil, -- Zero width test
positioning = item == 4 and Positioning.ABSOLUTE or nil,
x = item == 4 and 50 or nil,
y = item == 4 and 50 or nil,
gap = item == 3 and -5 or 0, -- Negative gap test
})
sectionItem.parent = sideSection
table.insert(sideSection.children, sectionItem)
-- Nested items for deep validation
if item <= 2 then
for nested = 1, 2 do
local nestedItem = Gui.new({
width = nested == 1 and 999999 or 20, -- Extreme width
height = 12,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.CENTER,
alignItems = AlignItems.CENTER,
})
nestedItem.parent = sectionItem
table.insert(sectionItem.children, nestedItem)
end
end
end
end
-- Content area with complex validation scenarios
local content = Gui.new({
width = 950,
height = 740,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 0,
})
content.parent = main
table.insert(main.children, content)
-- Content grid with wrapping and validation challenges
local contentGrid = Gui.new({
height = 600,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
flexWrap = FlexWrap.WRAP,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.FLEX_START,
gap = 20,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
contentGrid.parent = content
table.insert(content.children, contentGrid)
-- Grid items with validation edge cases
for i = 1, 12 do
local gridItem = Gui.new({
width = i % 4 == 0 and 0 or 200, -- Some zero width items
height = i % 3 == 0 and -50 or 150, -- Some negative height items
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.SPACE_BETWEEN,
alignItems = AlignItems.STRETCH,
gap = i % 5 == 0 and -10 or 12, -- Some negative gaps
})
gridItem.parent = contentGrid
table.insert(contentGrid.children, gridItem)
-- Grid item content with extreme values
for j = 1, 3 do
local itemContent = Gui.new({
height = j == 1 and 999999 or 40, -- Extreme height
width = j == 2 and -100 or nil, -- Negative width
positioning = j == 3 and Positioning.ABSOLUTE or nil,
x = j == 3 and -500 or nil, -- Extreme negative position
y = j == 3 and 1000000 or nil, -- Extreme positive position
})
itemContent.parent = gridItem
table.insert(gridItem.children, itemContent)
end
end
-- Footer with final validation tests
local footer = Gui.new({
height = 140,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.SPACE_AROUND,
alignItems = AlignItems.CENTER,
gap = 0,
padding = { top = 999999, right = -100, bottom = 0, left = 50 }, -- Extreme padding
})
footer.parent = content
table.insert(content.children, footer)
-- Footer sections with final edge cases
for i = 1, 4 do
local footerSection = Gui.new({
width = i == 1 and 0 or 200,
height = i == 2 and -1000 or 100,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.CENTER,
alignItems = AlignItems.CENTER,
gap = i == 4 and 999999 or 8,
})
footerSection.parent = footer
table.insert(footer.children, footerSection)
end
return app
end)
-- Should not crash despite extreme values
luaunit.assertTrue(success)
luaunit.assertIsTable(app)
luaunit.assertEquals(#app.children, 2) -- header and main
-- Test layout calculation with extreme values
local layoutSuccess = captureError(function()
app:layoutChildren()
end)
luaunit.assertTrue(layoutSuccess) -- Layout should not crash
-- Verify structure integrity after layout
luaunit.assertEquals(app.positioning, Positioning.FLEX)
luaunit.assertEquals(#app.children, 2)
luaunit.assertEquals(#app.children[1].children, 2) -- header nav and actions
luaunit.assertEquals(#app.children[2].children, 2) -- sidebar and content
-- Test that extreme values are preserved but handled gracefully
local nav = app.children[1].children[1]
luaunit.assertEquals(#nav.children, 5) -- All nav items created
local actions = app.children[1].children[2]
luaunit.assertEquals(actions.children[1].width, 999999) -- Extreme width preserved
local sidebar = app.children[2].children[1]
luaunit.assertEquals(#sidebar.children, 3) -- All sidebar sections created
local content = app.children[2].children[2]
luaunit.assertEquals(#content.children, 2) -- contentGrid and footer
local contentGrid = content.children[1]
luaunit.assertEquals(#contentGrid.children, 12) -- All grid items created
end
-- Test 12: Validation of Dynamic Property Changes in Complex Layouts
function TestLayoutValidation:testComplexDynamicPropertyValidation()
local success, result = captureError(function()
-- Create complex dashboard layout
local dashboard = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 600,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 0,
})
-- Metrics row that will be modified
local metricsRow = Gui.new({
height = 120,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
flexWrap = FlexWrap.WRAP,
justifyContent = JustifyContent.SPACE_BETWEEN,
alignItems = AlignItems.STRETCH,
gap = 20,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
metricsRow.parent = dashboard
table.insert(dashboard.children, metricsRow)
-- Create initial metrics
local metrics = {}
for i = 1, 6 do
local metric = Gui.new({
width = 150,
height = 80,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.SPACE_BETWEEN,
alignItems = AlignItems.CENTER,
gap = 8,
})
metric.parent = metricsRow
table.insert(metricsRow.children, metric)
metrics[i] = metric
-- Metric content
for j = 1, 3 do
local content = Gui.new({
width = 100,
height = 20,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.CENTER,
alignItems = AlignItems.CENTER,
})
content.parent = metric
table.insert(metric.children, content)
end
end
-- Content area that will receive dynamic changes
local contentArea = Gui.new({
height = 480,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 20,
padding = { top = 0, right = 20, bottom = 20, left = 20 },
})
contentArea.parent = dashboard
table.insert(dashboard.children, contentArea)
-- Left panel for modifications
local leftPanel = Gui.new({
width = 300,
height = 460,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 16,
})
leftPanel.parent = contentArea
table.insert(contentArea.children, leftPanel)
-- Right panel with nested complexity
local rightPanel = Gui.new({
width = 640,
height = 460,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 12,
})
rightPanel.parent = contentArea
table.insert(contentArea.children, rightPanel)
-- Create nested content for validation testing
for i = 1, 3 do
local section = Gui.new({
height = 140,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
flexWrap = FlexWrap.WRAP,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.FLEX_START,
gap = 12,
padding = { top = 12, right = 12, bottom = 12, left = 12 },
})
section.parent = rightPanel
table.insert(rightPanel.children, section)
-- Section items for modification testing
for j = 1, 8 do
local item = Gui.new({
width = 80,
height = 60,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.CENTER,
alignItems = AlignItems.CENTER,
gap = 4,
})
item.parent = section
table.insert(section.children, item)
end
end
-- Initial layout
dashboard:layoutChildren()
-- Test 1: Change flexDirection on main container
dashboard.flexDirection = FlexDirection.HORIZONTAL
dashboard:layoutChildren() -- Should not crash
-- Test 2: Modify metrics with extreme values
metrics[1].width = 0
metrics[2].width = -100
metrics[3].width = 999999
metrics[4].height = 0
metrics[5].height = -200
metrics[6].height = 1000000
dashboard:layoutChildren() -- Should not crash
-- Test 3: Change flex wrap and justify properties
metricsRow.flexWrap = FlexWrap.WRAP_REVERSE
metricsRow.justifyContent = JustifyContent.CENTER
metricsRow.alignItems = AlignItems.FLEX_END
dashboard:layoutChildren() -- Should not crash
-- Test 4: Modify gap values with extremes
metricsRow.gap = -50
contentArea.gap = 999999
dashboard:layoutChildren() -- Should not crash
-- Test 5: Change positioning types dynamically
leftPanel.positioning = Positioning.ABSOLUTE
leftPanel.x = -500
leftPanel.y = 1000
dashboard:layoutChildren() -- Should not crash
-- Test 6: Modify padding with extreme values
rightPanel.padding = { top = -100, right = 999999, bottom = 0, left = -50 }
dashboard:layoutChildren() -- Should not crash
-- Test 7: Change nested item properties
local firstSection = rightPanel.children[1]
firstSection.flexDirection = FlexDirection.VERTICAL
firstSection.flexWrap = FlexWrap.WRAP_REVERSE
firstSection.justifyContent = JustifyContent.SPACE_EVENLY
dashboard:layoutChildren() -- Should not crash
-- Test 8: Modify individual items with extreme values
local items = firstSection.children
for i = 1, #items do
items[i].width = i % 2 == 0 and 0 or 999999
items[i].height = i % 3 == 0 and -100 or 200
end
dashboard:layoutChildren() -- Should not crash
-- Test 9: Add/remove children dynamically
local newMetric = Gui.new({
width = 0,
height = -50,
positioning = Positioning.ABSOLUTE,
x = -1000,
y = -1000,
})
newMetric.parent = metricsRow
table.insert(metricsRow.children, newMetric)
dashboard:layoutChildren() -- Should not crash
-- Test 10: Remove children
table.remove(metricsRow.children, 1)
if metricsRow.children[1] then
metricsRow.children[1].parent = nil
end
dashboard:layoutChildren() -- Should not crash
return {
dashboard = dashboard,
metricsRow = metricsRow,
contentArea = contentArea,
leftPanel = leftPanel,
rightPanel = rightPanel,
finalChildCount = #metricsRow.children,
}
end)
luaunit.assertTrue(success) -- Should not crash during any modifications
luaunit.assertIsTable(result)
luaunit.assertIsTable(result.dashboard)
-- Verify structure integrity after all modifications
luaunit.assertEquals(result.dashboard.flexDirection, FlexDirection.HORIZONTAL)
luaunit.assertEquals(result.metricsRow.flexWrap, FlexWrap.WRAP_REVERSE)
luaunit.assertEquals(result.leftPanel.positioning, Positioning.ABSOLUTE)
luaunit.assertEquals(result.finalChildCount, 6) -- 7 added - 1 removed = 6 remaining
-- Test final layout one more time
local finalLayoutSuccess = captureError(function()
result.dashboard:layoutChildren()
end)
luaunit.assertTrue(finalLayoutSuccess)
end
-- Test 13: Validation of Circular Reference Prevention
function TestLayoutValidation:testCircularReferenceValidation()
local success, result = captureError(function()
-- Create containers that could form circular references
local container1 = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
local container2 = Gui.new({
width = 180,
height = 180,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
})
local container3 = Gui.new({
width = 160,
height = 160,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
-- Establish normal parent-child relationship
container2.parent = container1
table.insert(container1.children, container2)
container3.parent = container2
table.insert(container2.children, container3)
-- Test layout works normally
container1:layoutChildren()
-- Attempt to create circular reference (should be prevented or handled)
-- Note: FlexLove should handle this gracefully or the test framework should catch it
-- Test case 1: Try to make parent a child of its own child
local attemptSuccess1 = captureError(function()
container1.parent = container3
table.insert(container3.children, container1)
container1:layoutChildren() -- This should either work or fail gracefully
end)
-- Clean up potential circular reference
container1.parent = nil
if container3.children and #container3.children > 0 then
for i = #container3.children, 1, -1 do
if container3.children[i] == container1 then
table.remove(container3.children, i)
end
end
end
-- Test case 2: Complex nested structure with potential circular refs
local container4 = Gui.new({
width = 140,
height = 140,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
})
local container5 = Gui.new({
width = 120,
height = 120,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
-- Normal nesting first
container4.parent = container3
table.insert(container3.children, container4)
container5.parent = container4
table.insert(container4.children, container5)
-- Test normal layout
container1:layoutChildren()
-- Try to create deeper circular reference
local attemptSuccess2 = captureError(function()
container2.parent = container5
table.insert(container5.children, container2)
container1:layoutChildren()
end)
-- Clean up the circular reference to restore valid structure
-- Remove container2 from container5's children
if container5.children and #container5.children > 0 then
for i = #container5.children, 1, -1 do
if container5.children[i] == container2 then
table.remove(container5.children, i)
end
end
end
-- Restore container2's original parent
container2.parent = container1
return {
container1 = container1,
container2 = container2,
container3 = container3,
container4 = container4,
container5 = container5,
attempt1 = attemptSuccess1,
attempt2 = attemptSuccess2,
}
end)
luaunit.assertTrue(success) -- Should not crash the test framework
luaunit.assertIsTable(result)
-- Verify basic structure is maintained
luaunit.assertIsTable(result.container1)
luaunit.assertIsTable(result.container2)
luaunit.assertIsTable(result.container3)
-- Test that final layout still works after cleanup
local finalLayoutSuccess = captureError(function()
result.container1:layoutChildren()
end)
luaunit.assertTrue(finalLayoutSuccess)
end
-- Test 14: Memory and Performance Validation with Large Structures
function TestLayoutValidation:testLargeStructureValidation()
local success, result = captureError(function()
-- Create a large, complex structure to test memory handling
local root = Gui.new({
x = 0,
y = 0,
width = 2000,
height = 1500,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = 5,
})
local itemCount = 0
local maxDepth = 5
local itemsPerLevel = 10
-- Recursive function to create deep, wide structure
local function createLevel(parent, depth, items)
if depth >= maxDepth then
return
end
for i = 1, items do
local container = Gui.new({
width = depth == 1 and 400 or 100,
height = depth == 1 and 200 or 50,
positioning = Positioning.FLEX,
flexDirection = i % 2 == 0 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL,
flexWrap = i % 3 == 0 and FlexWrap.WRAP or FlexWrap.NOWRAP,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.STRETCH,
gap = depth,
})
container.parent = parent
table.insert(parent.children, container)
itemCount = itemCount + 1
-- Add leaf elements to some containers
if depth >= 3 then
for j = 1, 3 do
local leaf = Gui.new({
width = 20 + j * 5,
height = 15 + j * 3,
positioning = j == 3 and Positioning.ABSOLUTE or nil,
x = j == 3 and j * 10 or nil,
y = j == 3 and j * 10 or nil,
})
leaf.parent = container
table.insert(container.children, leaf)
itemCount = itemCount + 1
end
end
-- Recurse to next level
createLevel(container, depth + 1, math.max(1, items - 2))
end
end
-- Create the large structure
createLevel(root, 1, itemsPerLevel)
-- Test initial layout
root:layoutChildren()
-- Modify properties across the structure
local function modifyRandomly(container, depth)
if depth > maxDepth then
return
end
-- Randomly modify properties
if math.random() > 0.7 then
container.gap = math.random(-10, 50)
end
if math.random() > 0.8 then
container.flexDirection = math.random() > 0.5 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL
end
if math.random() > 0.9 then
container.width = math.random(10, 500)
container.height = math.random(10, 300)
end
-- Recurse to children
if container.children then
for _, child in ipairs(container.children) do
modifyRandomly(child, depth + 1)
end
end
end
-- Apply random modifications
for iteration = 1, 3 do
modifyRandomly(root, 1)
root:layoutChildren() -- Should handle large structure
end
-- Test memory cleanup simulation
local function clearSubtree(container)
if container.children then
for i = #container.children, 1, -1 do
clearSubtree(container.children[i])
container.children[i].parent = nil
table.remove(container.children, i)
end
end
end
-- Clear half the structure
if root.children then
for i = math.ceil(#root.children / 2), #root.children do
if root.children[i] then
clearSubtree(root.children[i])
root.children[i].parent = nil
table.remove(root.children, i)
end
end
end
-- Test layout after cleanup
root:layoutChildren()
return {
root = root,
itemCount = itemCount,
finalChildCount = #root.children,
}
end)
luaunit.assertTrue(success) -- Should handle large structures without crashing
luaunit.assertIsTable(result)
luaunit.assertTrue(result.itemCount > 100) -- Should have created many items
luaunit.assertTrue(result.finalChildCount > 0) -- Should have remaining children after cleanup
-- Test final layout works
local finalLayoutSuccess = captureError(function()
result.root:layoutChildren()
end)
luaunit.assertTrue(finalLayoutSuccess)
end
-- Test 15: Validation of Edge Cases in Complex Flex Combinations
function TestLayoutValidation:testComplexFlexCombinationValidation()
local success, result = captureError(function()
-- Test every possible combination of flex properties with edge cases
local combinations = {}
local flexDirections = { FlexDirection.HORIZONTAL, FlexDirection.VERTICAL }
local justifyContents = {
JustifyContent.FLEX_START,
JustifyContent.FLEX_END,
JustifyContent.CENTER,
JustifyContent.SPACE_BETWEEN,
JustifyContent.SPACE_AROUND,
JustifyContent.SPACE_EVENLY,
}
local alignItems = {
AlignItems.FLEX_START,
AlignItems.FLEX_END,
AlignItems.CENTER,
AlignItems.STRETCH,
}
local flexWraps = { FlexWrap.NOWRAP, FlexWrap.WRAP, FlexWrap.WRAP_REVERSE }
-- Main container for all combinations
local mainContainer = Gui.new({
x = 0,
y = 0,
width = 2400,
height = 1800,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
flexWrap = FlexWrap.WRAP,
justifyContent = JustifyContent.FLEX_START,
alignItems = AlignItems.FLEX_START,
gap = 20,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
local combinationCount = 0
-- Test each combination
for _, flexDir in ipairs(flexDirections) do
for _, justify in ipairs(justifyContents) do
for _, align in ipairs(alignItems) do
for _, wrap in ipairs(flexWraps) do
combinationCount = combinationCount + 1
local testContainer = Gui.new({
width = 200,
height = 150,
positioning = Positioning.FLEX,
flexDirection = flexDir,
justifyContent = justify,
alignItems = align,
flexWrap = wrap,
gap = 5,
padding = { top = 5, right = 5, bottom = 5, left = 5 },
})
testContainer.parent = mainContainer
table.insert(mainContainer.children, testContainer)
-- Add children with edge case properties
for i = 1, 6 do
local child = Gui.new({
width = i == 1 and 0 or (i == 2 and -10 or (i == 6 and 999999 or 30)),
height = i == 3 and 0 or (i == 4 and -5 or (i == 5 and 1000000 or 20)),
positioning = i == 6 and Positioning.ABSOLUTE or nil,
x = i == 6 and -100 or nil,
y = i == 6 and 200 or nil,
})
child.parent = testContainer
table.insert(testContainer.children, child)
-- Add nested content to some children
if i <= 3 then
local nested = Gui.new({
width = 15,
height = 10,
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
justifyContent = JustifyContent.CENTER,
alignItems = AlignItems.CENTER,
})
nested.parent = child
table.insert(child.children, nested)
end
end
end
end
end
end
-- Test layout with all combinations
mainContainer:layoutChildren()
-- Test dynamic property changes on all combinations
for _, container in ipairs(mainContainer.children) do
-- Change gap to extreme values
container.gap = math.random() > 0.5 and -20 or 100
-- Change dimensions
if math.random() > 0.7 then
container.width = math.random() > 0.5 and 0 or 500
container.height = math.random() > 0.5 and -50 or 300
end
-- Modify children
if container.children then
for i, child in ipairs(container.children) do
if math.random() > 0.8 then
child.width = math.random() > 0.5 and 0 or 999999
child.height = math.random() > 0.5 and -100 or 1000000
end
end
end
end
-- Test layout after modifications
mainContainer:layoutChildren()
return {
mainContainer = mainContainer,
combinationCount = combinationCount,
finalContainerCount = #mainContainer.children,
}
end)
luaunit.assertTrue(success) -- Should handle all combinations without crashing
luaunit.assertIsTable(result)
-- Should have tested many combinations
local expectedCombinations = 2 * 6 * 4 * 3 -- 144 combinations
luaunit.assertEquals(result.combinationCount, expectedCombinations)
luaunit.assertEquals(result.finalContainerCount, expectedCombinations)
-- Test final layout works
local finalLayoutSuccess = captureError(function()
result.mainContainer:layoutChildren()
end)
luaunit.assertTrue(finalLayoutSuccess)
end
luaunit.LuaUnit.run()