792 lines
23 KiB
Lua
792 lines
23 KiB
Lua
-- Critical Failure Tests for FlexLove
|
|
-- These tests are designed to find ACTUAL BUGS:
|
|
-- 1. Memory leaks / garbage creation without cleanup
|
|
-- 2. Layout calculation bugs causing incorrect positioning
|
|
-- 3. Unsafe input access (nil dereference, division by zero, etc.)
|
|
|
|
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
|
require("testing.loveStub")
|
|
local luaunit = require("testing.luaunit")
|
|
local FlexLove = require("FlexLove")
|
|
|
|
TestCriticalFailures = {}
|
|
|
|
function TestCriticalFailures:setUp()
|
|
collectgarbage("collect")
|
|
FlexLove.destroy()
|
|
FlexLove.setMode("retained")
|
|
end
|
|
|
|
function TestCriticalFailures:tearDown()
|
|
FlexLove.destroy()
|
|
collectgarbage("collect")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- MEMORY LEAK TESTS - Find garbage that's not cleaned up
|
|
-- ============================================================
|
|
|
|
-- Test: Canvas objects should be cleaned up on resize
|
|
function TestCriticalFailures:test_canvas_cleanup_on_resize()
|
|
FlexLove.init()
|
|
|
|
-- Create initial canvases
|
|
FlexLove.draw(function() end)
|
|
local canvas1 = FlexLove._gameCanvas
|
|
|
|
-- Resize should invalidate old canvases
|
|
FlexLove.resize()
|
|
|
|
-- Draw again to create new canvases
|
|
FlexLove.draw(function() end)
|
|
local canvas2 = FlexLove._gameCanvas
|
|
|
|
-- Old canvas should be replaced
|
|
luaunit.assertNotEquals(canvas1, canvas2)
|
|
|
|
-- Check canvas is actually nil after resize (before draw)
|
|
FlexLove.resize()
|
|
luaunit.assertNil(FlexLove._gameCanvas)
|
|
end
|
|
|
|
-- Test: Elements should be cleaned up from topElements on destroy
|
|
function TestCriticalFailures:test_element_cleanup_from_top_elements()
|
|
local element1 = FlexLove.new({ width = 100, height = 100 })
|
|
local element2 = FlexLove.new({ width = 100, height = 100 })
|
|
|
|
luaunit.assertEquals(#FlexLove.topElements, 2)
|
|
|
|
element1:destroy()
|
|
luaunit.assertEquals(#FlexLove.topElements, 1)
|
|
|
|
element2:destroy()
|
|
luaunit.assertEquals(#FlexLove.topElements, 0)
|
|
end
|
|
|
|
-- Test: Child elements should be destroyed when parent is destroyed
|
|
function TestCriticalFailures:test_child_cleanup_on_parent_destroy()
|
|
local parent = FlexLove.new({ width = 200, height = 200 })
|
|
local child = FlexLove.new({ width = 50, height = 50, parent = parent })
|
|
|
|
luaunit.assertEquals(#parent.children, 1)
|
|
|
|
-- Destroy parent should also clear children
|
|
parent:destroy()
|
|
|
|
-- Child should have no parent reference (potential memory leak if not cleared)
|
|
luaunit.assertNil(child.parent)
|
|
luaunit.assertEquals(#parent.children, 0)
|
|
end
|
|
|
|
-- Test: Event handlers should be cleared on destroy (closure leak)
|
|
function TestCriticalFailures:test_event_handler_cleanup()
|
|
local captured_data = { large_array = {} }
|
|
for i = 1, 1000 do
|
|
captured_data.large_array[i] = i
|
|
end
|
|
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
onEvent = function(el, event)
|
|
-- This closure captures captured_data
|
|
print(captured_data.large_array[1])
|
|
end,
|
|
})
|
|
|
|
element:destroy()
|
|
|
|
-- onEvent should be nil after destroy (prevent closure leak)
|
|
luaunit.assertNil(element.onEvent)
|
|
end
|
|
|
|
-- Test: Immediate mode state should not grow unbounded
|
|
function TestCriticalFailures:test_immediate_mode_state_cleanup()
|
|
FlexLove.setMode("immediate")
|
|
FlexLove.init({ stateRetentionFrames = 2 })
|
|
|
|
-- Create elements for multiple frames
|
|
for frame = 1, 10 do
|
|
FlexLove.beginFrame()
|
|
FlexLove.new({ id = "element_" .. frame, width = 100, height = 100 })
|
|
FlexLove.endFrame()
|
|
end
|
|
|
|
-- State count should be limited by stateRetentionFrames
|
|
local stateCount = FlexLove.getStateCount()
|
|
-- Should be much less than 10 due to cleanup
|
|
luaunit.assertTrue(stateCount < 10, "State count: " .. stateCount .. " (should be cleaned up)")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- LAYOUT CALCULATION BUGS - Find incorrect positioning
|
|
-- ============================================================
|
|
|
|
-- Test: Flex layout with overflow should not position children outside container
|
|
function TestCriticalFailures:test_flex_overflow_positioning()
|
|
local parent = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
flexWrap = "nowrap",
|
|
})
|
|
|
|
-- Add children that exceed parent width
|
|
local child1 = FlexLove.new({ width = 80, height = 50, parent = parent })
|
|
local child2 = FlexLove.new({ width = 80, height = 50, parent = parent })
|
|
|
|
-- Children should be positioned, even if they overflow
|
|
-- Check that x positions are at least valid numbers
|
|
luaunit.assertNotNil(child1.x)
|
|
luaunit.assertNotNil(child2.x)
|
|
luaunit.assertTrue(child1.x >= 0)
|
|
luaunit.assertTrue(child2.x > child1.x)
|
|
end
|
|
|
|
-- Test: Percentage width with zero parent width (division by zero)
|
|
function TestCriticalFailures:test_percentage_width_zero_parent()
|
|
local parent = FlexLove.new({ width = 0, height = 100 })
|
|
|
|
-- This should not crash (division by zero in percentage calculation)
|
|
local success, child = pcall(function()
|
|
return FlexLove.new({ width = "50%", height = 50, parent = parent })
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should not crash with zero parent width")
|
|
if success then
|
|
-- Width should be 0 or handled gracefully
|
|
luaunit.assertTrue(child.width >= 0)
|
|
end
|
|
end
|
|
|
|
-- Test: Auto-sizing with circular dependency
|
|
function TestCriticalFailures:test_autosizing_circular_dependency()
|
|
FlexLove.init()
|
|
FlexLove.init()
|
|
-- Parent auto-sizes to child, child uses percentage of parent
|
|
local parent = FlexLove.new({ height = 100 }) -- No width = auto
|
|
|
|
-- Child width is percentage of parent, but parent width depends on child
|
|
local success, child = pcall(function()
|
|
return FlexLove.new({ width = "50%", height = 50, parent = parent })
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should not crash with circular sizing")
|
|
|
|
-- Check that we don't get NaN or negative values
|
|
if success then
|
|
luaunit.assertFalse(parent.width ~= parent.width, "Parent width should not be NaN")
|
|
luaunit.assertFalse(child.width ~= child.width, "Child width should not be NaN")
|
|
luaunit.assertTrue(parent.width >= 0, "Parent width should be non-negative")
|
|
luaunit.assertTrue(child.width >= 0, "Child width should be non-negative")
|
|
end
|
|
end
|
|
|
|
-- Test: Negative padding should not cause negative content dimensions
|
|
function TestCriticalFailures:test_negative_padding_content_dimensions()
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
padding = { top = -50, left = -50, right = -50, bottom = -50 },
|
|
})
|
|
|
|
-- Content width/height should never be negative
|
|
luaunit.assertTrue(element.width >= 0, "Content width should be non-negative: " .. element.width)
|
|
luaunit.assertTrue(element.height >= 0, "Content height should be non-negative: " .. element.height)
|
|
end
|
|
|
|
-- Test: Grid layout with zero rows/columns (division by zero)
|
|
function TestCriticalFailures:test_grid_zero_dimensions()
|
|
local parent = FlexLove.new({
|
|
width = 300,
|
|
height = 200,
|
|
positioning = "grid",
|
|
gridRows = 0,
|
|
gridColumns = 0,
|
|
})
|
|
|
|
-- This should not crash when adding children
|
|
local success = pcall(function()
|
|
FlexLove.new({ width = 50, height = 50, parent = parent })
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should not crash with zero grid dimensions")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- UNSAFE INPUT ACCESS - Find nil dereference and type errors
|
|
-- ============================================================
|
|
|
|
-- Test: setText with number should not crash (type coercion)
|
|
function TestCriticalFailures:test_set_text_with_number()
|
|
local element = FlexLove.new({ width = 100, height = 100, text = "initial" })
|
|
|
|
-- Many Lua APIs expect string but get number
|
|
local success = pcall(function()
|
|
element:setText(12345)
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle number text gracefully")
|
|
end
|
|
|
|
-- Test: Image path with special characters should not crash file system
|
|
function TestCriticalFailures:test_image_path_special_characters()
|
|
local success = pcall(function()
|
|
FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
imagePath = "../../../etc/passwd", -- Path traversal attempt
|
|
})
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle malicious paths gracefully")
|
|
end
|
|
|
|
-- Test: onEvent callback that errors should not crash the system
|
|
function TestCriticalFailures:test_on_event_error_handling()
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
onEvent = function(el, event)
|
|
error("Intentional error in callback")
|
|
end,
|
|
})
|
|
|
|
-- Simulate mouse click
|
|
local success = pcall(function()
|
|
local InputEvent = require("modules.InputEvent")
|
|
local event = InputEvent.new({ type = "pressed", button = 1 })
|
|
if element.onEvent then
|
|
element.onEvent(element, event)
|
|
end
|
|
end)
|
|
|
|
-- Should error (no protection), but shouldn't leave system in bad state
|
|
luaunit.assertFalse(success)
|
|
|
|
-- Element should still be valid
|
|
luaunit.assertNotNil(element)
|
|
end
|
|
|
|
-- Test: Text with null bytes should not cause buffer issues
|
|
function TestCriticalFailures:test_text_with_null_bytes()
|
|
local success = pcall(function()
|
|
FlexLove.new({
|
|
width = 200,
|
|
height = 100,
|
|
text = "Hello\0World\0\0\0",
|
|
})
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle null bytes in text")
|
|
end
|
|
|
|
-- Test: Extremely deep nesting should not cause stack overflow
|
|
function TestCriticalFailures:test_extreme_nesting_stack_overflow()
|
|
local parent = FlexLove.new({ width = 500, height = 500 })
|
|
local current = parent
|
|
|
|
-- Try to create 1000 levels of nesting
|
|
local success = pcall(function()
|
|
for i = 1, 1000 do
|
|
local child = FlexLove.new({
|
|
width = 10,
|
|
height = 10,
|
|
parent = current,
|
|
})
|
|
current = child
|
|
end
|
|
end)
|
|
|
|
-- This might fail due to legitimate recursion limits
|
|
-- But it should fail gracefully, not segfault
|
|
if not success then
|
|
print("Deep nesting failed (expected for extreme depth)")
|
|
end
|
|
|
|
luaunit.assertTrue(true) -- If we get here, no segfault
|
|
end
|
|
|
|
-- Test: Gap with NaN value
|
|
function TestCriticalFailures:test_gap_nan_value()
|
|
local success = pcall(function()
|
|
FlexLove.new({
|
|
width = 300,
|
|
height = 200,
|
|
positioning = "flex",
|
|
gap = 0 / 0, -- NaN
|
|
})
|
|
end)
|
|
|
|
-- NaN in calculations can propagate and cause issues
|
|
luaunit.assertTrue(success, "Should handle NaN gap")
|
|
end
|
|
|
|
-- Test: Border-box model with huge padding
|
|
function TestCriticalFailures:test_huge_padding_overflow()
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
padding = { top = 1000000, left = 1000000, right = 1000000, bottom = 1000000 },
|
|
})
|
|
|
|
-- Content dimensions should be clamped to 0, not underflow
|
|
luaunit.assertTrue(element.width >= 0, "Width should not underflow: " .. element.width)
|
|
luaunit.assertTrue(element.height >= 0, "Height should not underflow: " .. element.height)
|
|
end
|
|
|
|
-- Test: Scroll position race condition in immediate mode
|
|
function TestCriticalFailures:test_scroll_position_race_immediate_mode()
|
|
FlexLove.setMode("immediate")
|
|
|
|
-- Create scrollable element
|
|
FlexLove.beginFrame()
|
|
local element = FlexLove.new({
|
|
id = "scroll_test",
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
})
|
|
FlexLove.endFrame()
|
|
|
|
-- Set scroll position
|
|
element:setScrollPosition(50, 50)
|
|
|
|
-- Create same element next frame (state should restore)
|
|
FlexLove.beginFrame()
|
|
local element2 = FlexLove.new({
|
|
id = "scroll_test",
|
|
width = 200,
|
|
height = 200,
|
|
overflow = "scroll",
|
|
})
|
|
FlexLove.endFrame()
|
|
|
|
-- Scroll position should persist (or at least not crash)
|
|
local scrollX, scrollY = element2:getScrollPosition()
|
|
luaunit.assertNotNil(scrollX)
|
|
luaunit.assertNotNil(scrollY)
|
|
end
|
|
|
|
-- Test: Theme with missing required properties
|
|
function TestCriticalFailures:test_theme_missing_properties()
|
|
local Theme = require("modules.Theme")
|
|
|
|
-- Create theme with minimal properties
|
|
local success = pcall(function()
|
|
local theme = Theme.new({
|
|
name = "broken",
|
|
-- Missing components table
|
|
})
|
|
|
|
FlexLove.init({ theme = theme })
|
|
end)
|
|
|
|
-- Should handle gracefully or error clearly
|
|
luaunit.assertTrue(true) -- If we get here, no segfault
|
|
end
|
|
|
|
-- Test: Blur with zero or negative quality
|
|
function TestCriticalFailures:test_blur_invalid_quality()
|
|
local success = pcall(function()
|
|
FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
contentBlur = { intensity = 50, quality = 0 },
|
|
})
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle zero blur quality")
|
|
|
|
success = pcall(function()
|
|
FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
contentBlur = { intensity = 50, quality = -5 },
|
|
})
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle negative blur quality")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- NIL DEREFERENCE BUGS - Target specific LSP warnings
|
|
-- ============================================================
|
|
|
|
-- Test: 9-patch padding with corrupted theme state (Element.lua:752-755)
|
|
function TestCriticalFailures:test_ninepatch_padding_nil_dereference()
|
|
local Theme = require("modules.Theme")
|
|
|
|
-- Create a theme with 9-patch data
|
|
local theme = Theme.new({
|
|
name = "test_theme",
|
|
components = {
|
|
container = {
|
|
ninePatch = {
|
|
imagePath = "themes/metal.lua", -- Invalid path to trigger edge case
|
|
contentPadding = { top = 10, left = 10, right = 10, bottom = 10 }
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
FlexLove.init({ theme = theme })
|
|
|
|
-- Try to create element that uses 9-patch padding
|
|
-- If ninePatchContentPadding becomes nil but use9PatchPadding is true, this will crash
|
|
local success, err = pcall(function()
|
|
return FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
component = "container",
|
|
-- No explicit padding, should use 9-patch padding
|
|
})
|
|
end)
|
|
|
|
if not success then
|
|
print("ERROR: " .. tostring(err))
|
|
end
|
|
|
|
luaunit.assertTrue(success, "Should handle 9-patch padding gracefully")
|
|
end
|
|
|
|
-- Test: Theme with malformed 9-patch data
|
|
function TestCriticalFailures:test_malformed_ninepatch_data()
|
|
local Theme = require("modules.Theme")
|
|
|
|
-- Create theme with incomplete 9-patch data
|
|
local success = pcall(function()
|
|
local theme = Theme.new({
|
|
name = "broken_nine_patch",
|
|
components = {
|
|
container = {
|
|
ninePatch = {
|
|
-- Missing imagePath
|
|
contentPadding = { top = 10, left = 10 }, -- Incomplete padding
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
FlexLove.init({ theme = theme })
|
|
|
|
FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
component = "container",
|
|
})
|
|
end)
|
|
|
|
-- Should either succeed or fail with clear error (not nil dereference)
|
|
luaunit.assertTrue(true) -- If we get here, no segfault
|
|
end
|
|
|
|
-- ============================================================
|
|
-- INTEGRATION TESTS - Combine features in unexpected ways
|
|
-- ============================================================
|
|
|
|
-- Test: Scrollable element with overflow content + immediate mode + state restoration
|
|
function TestCriticalFailures:test_scroll_overflow_immediate_mode_integration()
|
|
FlexLove.setMode("immediate")
|
|
|
|
for frame = 1, 3 do
|
|
FlexLove.beginFrame()
|
|
|
|
local scrollContainer = FlexLove.new({
|
|
id = "scroll_container",
|
|
width = 200,
|
|
height = 150,
|
|
overflow = "scroll",
|
|
positioning = "flex",
|
|
flexDirection = "vertical",
|
|
})
|
|
|
|
-- Add children that exceed container height
|
|
for i = 1, 10 do
|
|
FlexLove.new({
|
|
id = "child_" .. i,
|
|
width = 180,
|
|
height = 50,
|
|
parent = scrollContainer,
|
|
})
|
|
end
|
|
|
|
FlexLove.endFrame()
|
|
|
|
-- Scroll on second frame
|
|
if frame == 2 then
|
|
scrollContainer:setScrollPosition(0, 100)
|
|
end
|
|
|
|
-- Check scroll position restored on third frame
|
|
if frame == 3 then
|
|
local scrollX, scrollY = scrollContainer:getScrollPosition()
|
|
luaunit.assertNotNil(scrollX, "Scroll X should be preserved")
|
|
luaunit.assertNotNil(scrollY, "Scroll Y should be preserved")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Test: Grid layout with auto-sized children and percentage gaps
|
|
function TestCriticalFailures:test_grid_autosized_children_percentage_gap()
|
|
local grid = FlexLove.new({
|
|
width = 300,
|
|
height = 300,
|
|
positioning = "grid",
|
|
gridRows = 3,
|
|
gridColumns = 3,
|
|
gap = "5%", -- Percentage gap
|
|
})
|
|
|
|
-- Add auto-sized children (no explicit dimensions)
|
|
for i = 1, 9 do
|
|
local child = FlexLove.new({
|
|
parent = grid,
|
|
text = "Cell " .. i,
|
|
-- Auto-sizing based on text
|
|
})
|
|
|
|
-- Verify child dimensions are valid
|
|
luaunit.assertNotNil(child.width)
|
|
luaunit.assertNotNil(child.height)
|
|
luaunit.assertTrue(child.width >= 0)
|
|
luaunit.assertTrue(child.height >= 0)
|
|
end
|
|
end
|
|
|
|
-- Test: Nested flex containers with conflicting alignment
|
|
function TestCriticalFailures:test_nested_flex_conflicting_alignment()
|
|
local outer = FlexLove.new({
|
|
width = 400,
|
|
height = 400,
|
|
positioning = "flex",
|
|
flexDirection = "vertical",
|
|
alignItems = "stretch",
|
|
justifyContent = "center",
|
|
})
|
|
|
|
local middle = FlexLove.new({
|
|
parent = outer,
|
|
height = 200,
|
|
-- Auto width (should stretch)
|
|
positioning = "flex",
|
|
flexDirection = "horizontal",
|
|
alignItems = "flex-end",
|
|
justifyContent = "space-between",
|
|
})
|
|
|
|
local inner1 = FlexLove.new({
|
|
parent = middle,
|
|
width = 50,
|
|
-- Auto height
|
|
text = "A",
|
|
})
|
|
|
|
local inner2 = FlexLove.new({
|
|
parent = middle,
|
|
width = 50,
|
|
height = 100,
|
|
text = "B",
|
|
})
|
|
|
|
-- Verify all elements have valid dimensions and positions
|
|
luaunit.assertTrue(outer.width > 0)
|
|
luaunit.assertTrue(middle.width > 0)
|
|
luaunit.assertTrue(inner1.height > 0)
|
|
luaunit.assertNotNil(inner1.x)
|
|
luaunit.assertNotNil(inner2.x)
|
|
end
|
|
|
|
-- Test: Element with multiple conflicting size sources
|
|
function TestCriticalFailures:test_conflicting_size_sources()
|
|
-- Element with explicit size + auto-sizing content + parent constraints
|
|
local parent = FlexLove.new({
|
|
width = 200,
|
|
height = 200,
|
|
positioning = "flex",
|
|
alignItems = "stretch",
|
|
})
|
|
|
|
local success = pcall(function()
|
|
FlexLove.new({
|
|
parent = parent,
|
|
width = 300, -- Exceeds parent width
|
|
height = "50%", -- Percentage height
|
|
text = "Very long text that should cause auto-sizing",
|
|
padding = { top = 50, left = 50, right = 50, bottom = 50 },
|
|
})
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle conflicting size sources")
|
|
end
|
|
|
|
-- Test: Image element with resize during load
|
|
function TestCriticalFailures:test_image_resize_during_load()
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
imagePath = "nonexistent.png", -- Won't load, but should handle gracefully
|
|
})
|
|
|
|
-- Simulate resize while "loading"
|
|
FlexLove.resize(1920, 1080)
|
|
|
|
-- Element should still be valid
|
|
luaunit.assertNotNil(element.width)
|
|
luaunit.assertNotNil(element.height)
|
|
luaunit.assertTrue(element.width > 0)
|
|
luaunit.assertTrue(element.height > 0)
|
|
end
|
|
|
|
-- Test: Rapid theme switching with active elements
|
|
function TestCriticalFailures:test_rapid_theme_switching()
|
|
local Theme = require("modules.Theme")
|
|
|
|
local theme1 = Theme.new({ name = "theme1", components = {} })
|
|
local theme2 = Theme.new({ name = "theme2", components = {} })
|
|
|
|
FlexLove.init({ theme = theme1 })
|
|
|
|
-- Create elements with theme1
|
|
local element1 = FlexLove.new({ width = 100, height = 100 })
|
|
local element2 = FlexLove.new({ width = 100, height = 100 })
|
|
|
|
-- Switch theme
|
|
FlexLove.destroy()
|
|
FlexLove.init({ theme = theme2 })
|
|
|
|
-- Old elements should be invalidated (accessing them might crash)
|
|
local success = pcall(function()
|
|
element1:setText("test")
|
|
end)
|
|
|
|
-- It's OK if this fails (element destroyed), but shouldn't segfault
|
|
luaunit.assertTrue(true)
|
|
end
|
|
|
|
-- Test: Update properties during layout calculation
|
|
function TestCriticalFailures:test_update_during_layout()
|
|
local parent = FlexLove.new({
|
|
width = 300,
|
|
height = 300,
|
|
positioning = "flex",
|
|
})
|
|
|
|
local child = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
parent = parent,
|
|
})
|
|
|
|
-- Modify child properties immediately after creation (during layout)
|
|
child:setText("Modified during layout")
|
|
child.backgroundColor = { r = 1, g = 0, b = 0, a = 1 }
|
|
|
|
-- Trigger another layout
|
|
parent:resize(400, 400)
|
|
|
|
-- Everything should still be valid
|
|
luaunit.assertNotNil(child.text)
|
|
luaunit.assertEquals(child.text, "Modified during layout")
|
|
end
|
|
|
|
-- ============================================================
|
|
-- STATE CORRUPTION SCENARIOS
|
|
-- ============================================================
|
|
|
|
-- Test: Destroy element with active event listeners
|
|
function TestCriticalFailures:test_destroy_with_active_listeners()
|
|
local eventFired = false
|
|
|
|
local element = FlexLove.new({
|
|
width = 100,
|
|
height = 100,
|
|
onEvent = function(el, event)
|
|
eventFired = true
|
|
end,
|
|
})
|
|
|
|
-- Simulate an event via InputEvent
|
|
local InputEvent = require("modules.InputEvent")
|
|
local event = InputEvent.new({ type = "pressed", button = 1, x = 50, y = 50 })
|
|
|
|
if element.onEvent and element:contains(50, 50) then
|
|
element.onEvent(element, event)
|
|
end
|
|
|
|
luaunit.assertTrue(eventFired, "Event should fire before destroy")
|
|
|
|
-- Destroy element
|
|
element:destroy()
|
|
|
|
-- onEvent should be nil after destroy
|
|
luaunit.assertNil(element.onEvent, "onEvent should be cleared after destroy")
|
|
end
|
|
|
|
-- Test: Double destroy should be safe
|
|
function TestCriticalFailures:test_double_destroy_safety()
|
|
local element = FlexLove.new({ width = 100, height = 100 })
|
|
|
|
element:destroy()
|
|
|
|
-- Second destroy should be safe (idempotent)
|
|
local success = pcall(function()
|
|
element:destroy()
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Double destroy should be safe")
|
|
end
|
|
|
|
-- Test: Circular parent-child reference (should never happen, but test safety)
|
|
function TestCriticalFailures:test_circular_parent_child_reference()
|
|
local parent = FlexLove.new({ width = 200, height = 200 })
|
|
local child = FlexLove.new({ width = 100, height = 100, parent = parent })
|
|
|
|
-- Try to create circular reference (should be prevented)
|
|
local success = pcall(function()
|
|
parent.parent = child -- This should never be allowed
|
|
parent:layoutChildren() -- This would cause infinite recursion
|
|
end)
|
|
|
|
-- Even if we set circular reference, layout should not crash
|
|
luaunit.assertTrue(true) -- If we get here, no stack overflow
|
|
end
|
|
|
|
-- Test: Modify children array during iteration
|
|
function TestCriticalFailures:test_modify_children_during_iteration()
|
|
local parent = FlexLove.new({
|
|
width = 300,
|
|
height = 300,
|
|
positioning = "flex",
|
|
})
|
|
|
|
-- Add several children
|
|
local children = {}
|
|
for i = 1, 5 do
|
|
children[i] = FlexLove.new({
|
|
width = 50,
|
|
height = 50,
|
|
parent = parent,
|
|
})
|
|
end
|
|
|
|
-- Remove child during layout (simulates user code modifying structure)
|
|
local success = pcall(function()
|
|
-- Trigger layout
|
|
parent:layoutChildren()
|
|
|
|
-- Remove a child (modifies children array)
|
|
children[3]:destroy()
|
|
|
|
-- Trigger layout again
|
|
parent:layoutChildren()
|
|
end)
|
|
|
|
luaunit.assertTrue(success, "Should handle children modification during layout")
|
|
end
|
|
|
|
if not _G.RUNNING_ALL_TESTS then
|
|
os.exit(luaunit.LuaUnit.run())
|
|
end
|