Replacing errors with warns in non-critical areas
This commit is contained in:
789
testing/__tests__/critical_failures_test.lua
Normal file
789
testing/__tests__/critical_failures_test.lua
Normal file
@@ -0,0 +1,789 @@
|
||||
-- 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()
|
||||
-- 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
|
||||
@@ -1223,6 +1223,771 @@ function TestElementAdditional:test_element_with_userdata()
|
||||
luaunit.assertEquals(element.userdata.count, 42)
|
||||
end
|
||||
|
||||
-- ==========================================
|
||||
-- UNHAPPY PATH TESTS
|
||||
-- ==========================================
|
||||
|
||||
TestElementUnhappyPaths = {}
|
||||
|
||||
function TestElementUnhappyPaths:setUp()
|
||||
FlexLove.beginFrame(1920, 1080)
|
||||
end
|
||||
|
||||
function TestElementUnhappyPaths:tearDown()
|
||||
FlexLove.endFrame()
|
||||
end
|
||||
|
||||
-- Test: Element with missing deps parameter
|
||||
function TestElementUnhappyPaths:test_element_without_deps()
|
||||
local Element = require("modules.Element")
|
||||
local success = pcall(function()
|
||||
Element.new({}, nil)
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error without deps
|
||||
end
|
||||
|
||||
-- Test: Element with negative dimensions
|
||||
function TestElementUnhappyPaths:test_element_negative_dimensions()
|
||||
local element = FlexLove.new({
|
||||
id = "negative",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = -100,
|
||||
height = -50,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
-- Element should still be created (negative values handled)
|
||||
end
|
||||
|
||||
-- Test: Element with zero dimensions
|
||||
function TestElementUnhappyPaths:test_element_zero_dimensions()
|
||||
local element = FlexLove.new({
|
||||
id = "zero",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 0,
|
||||
height = 0,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with extreme dimensions
|
||||
function TestElementUnhappyPaths:test_element_extreme_dimensions()
|
||||
local element = FlexLove.new({
|
||||
id = "huge",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 1000000,
|
||||
height = 1000000,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid opacity values
|
||||
function TestElementUnhappyPaths:test_element_invalid_opacity()
|
||||
-- Opacity > 1
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "high_opacity",
|
||||
width = 100,
|
||||
height = 100,
|
||||
opacity = 2.5,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateRange)
|
||||
|
||||
-- Negative opacity
|
||||
success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "negative_opacity",
|
||||
width = 100,
|
||||
height = 100,
|
||||
opacity = -0.5,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateRange)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid imageOpacity values
|
||||
function TestElementUnhappyPaths:test_element_invalid_image_opacity()
|
||||
-- imageOpacity > 1
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "high_img_opacity",
|
||||
width = 100,
|
||||
height = 100,
|
||||
imageOpacity = 3.0,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success)
|
||||
|
||||
-- Negative imageOpacity
|
||||
success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "negative_img_opacity",
|
||||
width = 100,
|
||||
height = 100,
|
||||
imageOpacity = -1.0,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid textSize
|
||||
function TestElementUnhappyPaths:test_element_invalid_text_size()
|
||||
-- Zero textSize
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "zero_text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
textSize = 0,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success)
|
||||
|
||||
-- Negative textSize
|
||||
success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "negative_text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
textSize = -12,
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid textAlign enum
|
||||
function TestElementUnhappyPaths:test_element_invalid_text_align()
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "invalid_align",
|
||||
width = 100,
|
||||
height = 100,
|
||||
textAlign = "invalid_value",
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateEnum)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid positioning enum
|
||||
function TestElementUnhappyPaths:test_element_invalid_positioning()
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "invalid_pos",
|
||||
width = 100,
|
||||
height = 100,
|
||||
positioning = "invalid_positioning",
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateEnum)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid flexDirection enum
|
||||
function TestElementUnhappyPaths:test_element_invalid_flex_direction()
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "invalid_flex",
|
||||
width = 100,
|
||||
height = 100,
|
||||
positioning = "flex",
|
||||
flexDirection = "diagonal",
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateEnum)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid objectFit enum
|
||||
function TestElementUnhappyPaths:test_element_invalid_object_fit()
|
||||
local success = pcall(function()
|
||||
FlexLove.new({
|
||||
id = "invalid_fit",
|
||||
width = 100,
|
||||
height = 100,
|
||||
objectFit = "stretch",
|
||||
})
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error (validateEnum)
|
||||
end
|
||||
|
||||
-- Test: Element with nonexistent image path
|
||||
function TestElementUnhappyPaths:test_element_nonexistent_image()
|
||||
local element = FlexLove.new({
|
||||
id = "no_image",
|
||||
width = 100,
|
||||
height = 100,
|
||||
imagePath = "/nonexistent/path/to/image.png",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertNil(element._loadedImage) -- Image should fail to load silently
|
||||
end
|
||||
|
||||
-- Test: Element with passwordMode and multiline (conflicting)
|
||||
function TestElementUnhappyPaths:test_element_password_multiline_conflict()
|
||||
local element = FlexLove.new({
|
||||
id = "conflict",
|
||||
width = 200,
|
||||
height = 100,
|
||||
editable = true,
|
||||
passwordMode = true,
|
||||
multiline = true, -- Should be disabled by passwordMode
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertFalse(element.multiline) -- multiline should be forced to false
|
||||
end
|
||||
|
||||
-- Test: Element addChild with nil child
|
||||
function TestElementUnhappyPaths:test_add_nil_child()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
local success = pcall(function()
|
||||
parent:addChild(nil)
|
||||
end)
|
||||
luaunit.assertFalse(success) -- Should error
|
||||
end
|
||||
|
||||
-- Test: Element removeChild that doesn't exist
|
||||
function TestElementUnhappyPaths:test_remove_nonexistent_child()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
local notAChild = FlexLove.new({
|
||||
id = "orphan",
|
||||
width = 50,
|
||||
height = 50,
|
||||
})
|
||||
|
||||
parent:removeChild(notAChild) -- Should not crash
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
-- Test: Element removeChild with nil
|
||||
function TestElementUnhappyPaths:test_remove_nil_child()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
parent:removeChild(nil) -- Should not crash
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element clearChildren on empty parent
|
||||
function TestElementUnhappyPaths:test_clear_children_empty()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
parent:clearChildren() -- Should not crash
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
-- Test: Element clearChildren called twice
|
||||
function TestElementUnhappyPaths:test_clear_children_twice()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
local child = FlexLove.new({
|
||||
id = "child",
|
||||
width = 50,
|
||||
height = 50,
|
||||
parent = parent,
|
||||
})
|
||||
|
||||
parent:clearChildren()
|
||||
parent:clearChildren() -- Call again
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
-- Test: Element contains with extreme coordinates
|
||||
function TestElementUnhappyPaths:test_contains_extreme_coordinates()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
x = 10,
|
||||
y = 20,
|
||||
width = 100,
|
||||
height = 50,
|
||||
})
|
||||
|
||||
luaunit.assertFalse(element:contains(math.huge, math.huge))
|
||||
luaunit.assertFalse(element:contains(-math.huge, -math.huge))
|
||||
end
|
||||
|
||||
-- Test: Element contains with NaN coordinates
|
||||
function TestElementUnhappyPaths:test_contains_nan_coordinates()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
x = 10,
|
||||
y = 20,
|
||||
width = 100,
|
||||
height = 50,
|
||||
})
|
||||
|
||||
local nan = 0 / 0
|
||||
local result = element:contains(nan, nan)
|
||||
-- NaN comparisons return false, so this should be false
|
||||
luaunit.assertFalse(result)
|
||||
end
|
||||
|
||||
-- Test: Element setScrollPosition without ScrollManager
|
||||
function TestElementUnhappyPaths:test_scroll_without_manager()
|
||||
local element = FlexLove.new({
|
||||
id = "no_scroll",
|
||||
width = 100,
|
||||
height = 100,
|
||||
-- No overflow property, so no ScrollManager
|
||||
})
|
||||
|
||||
element:setScrollPosition(50, 50) -- Should not crash
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element setScrollPosition with extreme values
|
||||
function TestElementUnhappyPaths:test_scroll_extreme_values()
|
||||
local element = FlexLove.new({
|
||||
id = "scrollable",
|
||||
width = 200,
|
||||
height = 200,
|
||||
overflow = "scroll",
|
||||
})
|
||||
|
||||
element:setScrollPosition(1000000, 1000000) -- Should clamp
|
||||
luaunit.assertTrue(true)
|
||||
|
||||
element:setScrollPosition(-1000000, -1000000) -- Should clamp to 0
|
||||
local scrollX, scrollY = element:getScrollPosition()
|
||||
luaunit.assertEquals(scrollX, 0)
|
||||
luaunit.assertEquals(scrollY, 0)
|
||||
end
|
||||
|
||||
-- Test: Element scrollBy with nil values
|
||||
function TestElementUnhappyPaths:test_scroll_by_nil()
|
||||
local element = FlexLove.new({
|
||||
id = "scrollable",
|
||||
width = 200,
|
||||
height = 200,
|
||||
overflow = "scroll",
|
||||
})
|
||||
|
||||
element:scrollBy(nil, nil) -- Should use current position
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element destroy on already destroyed element
|
||||
function TestElementUnhappyPaths:test_destroy_twice()
|
||||
local element = FlexLove.new({
|
||||
id = "destroyable",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
element:destroy()
|
||||
element:destroy() -- Call again - should not crash
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element destroy with circular reference (parent-child)
|
||||
function TestElementUnhappyPaths:test_destroy_with_children()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 200,
|
||||
height = 200,
|
||||
})
|
||||
|
||||
local child = FlexLove.new({
|
||||
id = "child",
|
||||
width = 50,
|
||||
height = 50,
|
||||
parent = parent,
|
||||
})
|
||||
|
||||
parent:destroy() -- Should destroy all children too
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
-- Test: Element update with nil dt
|
||||
function TestElementUnhappyPaths:test_update_nil_dt()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
local success = pcall(function()
|
||||
element:update(nil)
|
||||
end)
|
||||
-- May or may not error depending on implementation
|
||||
end
|
||||
|
||||
-- Test: Element update with negative dt
|
||||
function TestElementUnhappyPaths:test_update_negative_dt()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
element:update(-0.016) -- Should not crash
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element draw with nil backdropCanvas
|
||||
function TestElementUnhappyPaths:test_draw_nil_backdrop()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
element:draw(nil) -- Should not crash
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid cornerRadius types
|
||||
function TestElementUnhappyPaths:test_invalid_corner_radius()
|
||||
-- String cornerRadius
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
cornerRadius = "invalid",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Negative cornerRadius
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
cornerRadius = -10,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with partial cornerRadius table
|
||||
function TestElementUnhappyPaths:test_partial_corner_radius()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
cornerRadius = {
|
||||
topLeft = 10,
|
||||
-- Missing other corners
|
||||
},
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.cornerRadius.topLeft, 10)
|
||||
luaunit.assertEquals(element.cornerRadius.topRight, 0)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid border types
|
||||
function TestElementUnhappyPaths:test_invalid_border()
|
||||
-- String border
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
border = "invalid",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Negative border
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
border = -5,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with partial border table
|
||||
function TestElementUnhappyPaths:test_partial_border()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
border = {
|
||||
top = 2,
|
||||
left = 3,
|
||||
-- Missing right and bottom
|
||||
},
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.border.top, 2)
|
||||
luaunit.assertEquals(element.border.left, 3)
|
||||
luaunit.assertFalse(element.border.right)
|
||||
luaunit.assertFalse(element.border.bottom)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid padding types
|
||||
function TestElementUnhappyPaths:test_invalid_padding()
|
||||
-- String padding
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
padding = "invalid",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Negative padding
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
padding = { top = -10, left = -10, right = -10, bottom = -10 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid margin types
|
||||
function TestElementUnhappyPaths:test_invalid_margin()
|
||||
-- String margin
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = "invalid",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Huge margin
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = { top = 1000000, left = 1000000, right = 1000000, bottom = 1000000 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid gap value
|
||||
function TestElementUnhappyPaths:test_invalid_gap()
|
||||
-- Negative gap
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
gap = -10,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Huge gap
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
gap = 1000000,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid grid properties
|
||||
function TestElementUnhappyPaths:test_invalid_grid_properties()
|
||||
-- Zero rows/columns
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "grid",
|
||||
gridRows = 0,
|
||||
gridColumns = 0,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Negative rows/columns
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "grid",
|
||||
gridRows = -5,
|
||||
gridColumns = -5,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element setText on non-text element
|
||||
function TestElementUnhappyPaths:test_set_text_on_non_text()
|
||||
local element = FlexLove.new({
|
||||
id = "no_text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
element:setText("New text") -- Should not crash
|
||||
luaunit.assertEquals(element.text, "New text")
|
||||
end
|
||||
|
||||
-- Test: Element setText with nil
|
||||
function TestElementUnhappyPaths:test_set_text_nil()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
text = "Initial",
|
||||
})
|
||||
|
||||
element:setText(nil)
|
||||
luaunit.assertNil(element.text)
|
||||
end
|
||||
|
||||
-- Test: Element setText with extreme length
|
||||
function TestElementUnhappyPaths:test_set_text_extreme_length()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
text = "Initial",
|
||||
})
|
||||
|
||||
local longText = string.rep("a", 100000)
|
||||
element:setText(longText)
|
||||
luaunit.assertEquals(element.text, longText)
|
||||
end
|
||||
|
||||
-- Test: Element with conflicting size constraints
|
||||
function TestElementUnhappyPaths:test_conflicting_size_constraints()
|
||||
-- Width less than padding
|
||||
local element = FlexLove.new({
|
||||
id = "conflict",
|
||||
width = 10,
|
||||
height = 10,
|
||||
padding = { top = 20, left = 20, right = 20, bottom = 20 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
-- Content width should be clamped to 0 or handled gracefully
|
||||
end
|
||||
|
||||
-- Test: Element textinput on non-editable element
|
||||
function TestElementUnhappyPaths:test_textinput_non_editable()
|
||||
local element = FlexLove.new({
|
||||
id = "not_editable",
|
||||
width = 100,
|
||||
height = 100,
|
||||
editable = false,
|
||||
})
|
||||
|
||||
local success = pcall(function()
|
||||
element:textinput("a")
|
||||
end)
|
||||
-- Should either do nothing or handle gracefully
|
||||
end
|
||||
|
||||
-- Test: Element keypressed on non-editable element
|
||||
function TestElementUnhappyPaths:test_keypressed_non_editable()
|
||||
local element = FlexLove.new({
|
||||
id = "not_editable",
|
||||
width = 100,
|
||||
height = 100,
|
||||
editable = false,
|
||||
})
|
||||
|
||||
local success = pcall(function()
|
||||
element:keypressed("return", "return", false)
|
||||
end)
|
||||
-- Should either do nothing or handle gracefully
|
||||
end
|
||||
|
||||
-- Test: Element with invalid blur configuration
|
||||
function TestElementUnhappyPaths:test_invalid_blur_config()
|
||||
-- Negative intensity
|
||||
local element = FlexLove.new({
|
||||
id = "blur",
|
||||
width = 100,
|
||||
height = 100,
|
||||
contentBlur = { intensity = -10, quality = 5 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Intensity > 100
|
||||
element = FlexLove.new({
|
||||
id = "blur2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
backdropBlur = { intensity = 150, quality = 5 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Invalid quality
|
||||
element = FlexLove.new({
|
||||
id = "blur3",
|
||||
width = 100,
|
||||
height = 100,
|
||||
contentBlur = { intensity = 50, quality = 0 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element getAvailableContentWidth/Height on element with no padding
|
||||
function TestElementUnhappyPaths:test_available_content_no_padding()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 100,
|
||||
height = 100,
|
||||
})
|
||||
|
||||
local availWidth = element:getAvailableContentWidth()
|
||||
local availHeight = element:getAvailableContentHeight()
|
||||
|
||||
luaunit.assertEquals(availWidth, 100)
|
||||
luaunit.assertEquals(availHeight, 100)
|
||||
end
|
||||
|
||||
-- Test: Element with maxLines but no multiline
|
||||
function TestElementUnhappyPaths:test_max_lines_without_multiline()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 200,
|
||||
height = 100,
|
||||
editable = true,
|
||||
multiline = false,
|
||||
maxLines = 5, -- Should be ignored for single-line
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with maxLength 0
|
||||
function TestElementUnhappyPaths:test_max_length_zero()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 200,
|
||||
height = 40,
|
||||
editable = true,
|
||||
maxLength = 0,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with negative maxLength
|
||||
function TestElementUnhappyPaths:test_max_length_negative()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 200,
|
||||
height = 40,
|
||||
editable = true,
|
||||
maxLength = -10,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
|
||||
@@ -4,10 +4,25 @@ package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||
require("testing.loveStub")
|
||||
local luaunit = require("testing.luaunit")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
local ErrorCodes = require("modules.ErrorCodes")
|
||||
|
||||
TestErrorHandler = {}
|
||||
|
||||
-- Test: error() throws with correct format
|
||||
function TestErrorHandler:setUp()
|
||||
-- Reset debug mode and logging before each test
|
||||
ErrorHandler.setDebugMode(false)
|
||||
ErrorHandler.setLogTarget("none") -- Disable logging during tests
|
||||
end
|
||||
|
||||
function TestErrorHandler:tearDown()
|
||||
-- Clean up any test log files
|
||||
os.remove("test-errors.log")
|
||||
for i = 1, 5 do
|
||||
os.remove("test-errors.log." .. i)
|
||||
end
|
||||
end
|
||||
|
||||
-- Test: error() throws with correct format (backward compatibility)
|
||||
function TestErrorHandler:test_error_throws_with_format()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "Something went wrong")
|
||||
@@ -17,7 +32,65 @@ function TestErrorHandler:test_error_throws_with_format()
|
||||
luaunit.assertStrContains(err, "[FlexLove - TestModule] Error: Something went wrong")
|
||||
end
|
||||
|
||||
-- Test: warn() prints with correct format
|
||||
-- Test: error() with error code
|
||||
function TestErrorHandler:test_error_with_code()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Invalid property type")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "[FlexLove - TestModule] Error [FLEXLOVE_VAL_001]")
|
||||
luaunit.assertStrContains(err, "Invalid property type")
|
||||
end
|
||||
|
||||
-- Test: error() with error code and details
|
||||
function TestErrorHandler:test_error_with_code_and_details()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Invalid property type", {
|
||||
property = "width",
|
||||
expected = "number",
|
||||
got = "string",
|
||||
})
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "[FLEXLOVE_VAL_001]")
|
||||
luaunit.assertStrContains(err, "Details:")
|
||||
luaunit.assertStrContains(err, "Property: width")
|
||||
luaunit.assertStrContains(err, "Expected: number")
|
||||
luaunit.assertStrContains(err, "Got: string")
|
||||
end
|
||||
|
||||
-- Test: error() with error code, details, and custom suggestion
|
||||
function TestErrorHandler:test_error_with_code_details_and_suggestion()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Invalid property type", {
|
||||
property = "width",
|
||||
expected = "number",
|
||||
got = "string",
|
||||
}, "Use a number like width = 100")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "Suggestion: Use a number like width = 100")
|
||||
end
|
||||
|
||||
-- Test: error() with code uses automatic suggestion
|
||||
function TestErrorHandler:test_error_with_code_uses_auto_suggestion()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Invalid property type", {
|
||||
property = "width",
|
||||
})
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "Suggestion:")
|
||||
-- Should contain suggestion from ErrorCodes
|
||||
local suggestion = ErrorCodes.getSuggestion("VAL_001")
|
||||
luaunit.assertStrContains(err, suggestion)
|
||||
end
|
||||
|
||||
-- Test: warn() prints with correct format (backward compatibility)
|
||||
function TestErrorHandler:test_warn_prints_with_format()
|
||||
-- Capture print output by mocking print
|
||||
local captured = nil
|
||||
@@ -34,20 +107,60 @@ function TestErrorHandler:test_warn_prints_with_format()
|
||||
luaunit.assertEquals(captured, "[FlexLove - TestModule] Warning: This is a warning")
|
||||
end
|
||||
|
||||
-- Test: warn() with error code
|
||||
function TestErrorHandler:test_warn_with_code()
|
||||
local captured = nil
|
||||
local originalPrint = print
|
||||
print = function(msg)
|
||||
captured = msg
|
||||
end
|
||||
|
||||
ErrorHandler.warn("TestModule", "VAL_001", "Potentially invalid property")
|
||||
|
||||
print = originalPrint
|
||||
|
||||
luaunit.assertNotNil(captured, "warn() should print")
|
||||
luaunit.assertStrContains(captured, "[FlexLove - TestModule] Warning [FLEXLOVE_VAL_001]")
|
||||
luaunit.assertStrContains(captured, "Potentially invalid property")
|
||||
end
|
||||
|
||||
-- Test: warn() with details
|
||||
function TestErrorHandler:test_warn_with_details()
|
||||
local captured = nil
|
||||
local originalPrint = print
|
||||
print = function(msg)
|
||||
captured = msg
|
||||
end
|
||||
|
||||
ErrorHandler.warn("TestModule", "VAL_001", "Check this property", {
|
||||
property = "height",
|
||||
value = "auto",
|
||||
})
|
||||
|
||||
print = originalPrint
|
||||
|
||||
luaunit.assertNotNil(captured, "warn() should print")
|
||||
luaunit.assertStrContains(captured, "Details:")
|
||||
luaunit.assertStrContains(captured, "Property: height")
|
||||
luaunit.assertStrContains(captured, "Value: auto")
|
||||
end
|
||||
|
||||
-- Test: assertNotNil returns true for non-nil value
|
||||
function TestErrorHandler:test_assertNotNil_returns_true_for_valid()
|
||||
local result = ErrorHandler.assertNotNil("TestModule", "some value", "testParam")
|
||||
luaunit.assertTrue(result, "assertNotNil should return true for non-nil value")
|
||||
end
|
||||
|
||||
-- Test: assertNotNil throws for nil value
|
||||
-- Test: assertNotNil throws for nil value (now uses error codes)
|
||||
function TestErrorHandler:test_assertNotNil_throws_for_nil()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.assertNotNil("TestModule", nil, "testParam")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "assertNotNil should throw for nil")
|
||||
luaunit.assertStrContains(err, "Parameter 'testParam' cannot be nil")
|
||||
luaunit.assertStrContains(err, "[FLEXLOVE_VAL_003]")
|
||||
luaunit.assertStrContains(err, "Required parameter missing")
|
||||
luaunit.assertStrContains(err, "testParam")
|
||||
end
|
||||
|
||||
-- Test: assertType returns true for correct type
|
||||
@@ -62,14 +175,16 @@ function TestErrorHandler:test_assertType_returns_true_for_valid()
|
||||
luaunit.assertTrue(result, "assertType should return true for table")
|
||||
end
|
||||
|
||||
-- Test: assertType throws for wrong type
|
||||
-- Test: assertType throws for wrong type (now uses error codes)
|
||||
function TestErrorHandler:test_assertType_throws_for_wrong_type()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.assertType("TestModule", 123, "string", "testParam")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "assertType should throw for wrong type")
|
||||
luaunit.assertStrContains(err, "Parameter 'testParam' must be string, got number")
|
||||
luaunit.assertStrContains(err, "[FLEXLOVE_VAL_001]")
|
||||
luaunit.assertStrContains(err, "Invalid property type")
|
||||
luaunit.assertStrContains(err, "testParam")
|
||||
end
|
||||
|
||||
-- Test: assertRange returns true for value in range
|
||||
@@ -84,24 +199,27 @@ function TestErrorHandler:test_assertRange_returns_true_for_valid()
|
||||
luaunit.assertTrue(result, "assertRange should accept max boundary")
|
||||
end
|
||||
|
||||
-- Test: assertRange throws for value below min
|
||||
-- Test: assertRange throws for value below min (now uses error codes)
|
||||
function TestErrorHandler:test_assertRange_throws_for_below_min()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.assertRange("TestModule", -1, 0, 10, "testParam")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "assertRange should throw for value below min")
|
||||
luaunit.assertStrContains(err, "Parameter 'testParam' must be between 0 and 10, got -1")
|
||||
luaunit.assertStrContains(err, "[FLEXLOVE_VAL_002]")
|
||||
luaunit.assertStrContains(err, "Property value out of range")
|
||||
luaunit.assertStrContains(err, "testParam")
|
||||
end
|
||||
|
||||
-- Test: assertRange throws for value above max
|
||||
-- Test: assertRange throws for value above max (now uses error codes)
|
||||
function TestErrorHandler:test_assertRange_throws_for_above_max()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.assertRange("TestModule", 11, 0, 10, "testParam")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "assertRange should throw for value above max")
|
||||
luaunit.assertStrContains(err, "Parameter 'testParam' must be between 0 and 10, got 11")
|
||||
luaunit.assertStrContains(err, "[FLEXLOVE_VAL_002]")
|
||||
luaunit.assertStrContains(err, "Property value out of range")
|
||||
end
|
||||
|
||||
-- Test: warnDeprecated prints deprecation warning
|
||||
@@ -136,6 +254,181 @@ function TestErrorHandler:test_warnCommonMistake_prints_message()
|
||||
luaunit.assertStrContains(captured, "Width is zero. Suggestion: Set width to positive value")
|
||||
end
|
||||
|
||||
-- Test: debug mode enables stack traces
|
||||
function TestErrorHandler:test_debug_mode_enables_stack_trace()
|
||||
ErrorHandler.setDebugMode(true)
|
||||
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test error")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "Stack trace:")
|
||||
|
||||
ErrorHandler.setDebugMode(false)
|
||||
end
|
||||
|
||||
-- Test: setStackTrace independently
|
||||
function TestErrorHandler:test_set_stack_trace()
|
||||
ErrorHandler.setStackTrace(true)
|
||||
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test error")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "Stack trace:")
|
||||
|
||||
ErrorHandler.setStackTrace(false)
|
||||
end
|
||||
|
||||
-- Test: error code validation
|
||||
function TestErrorHandler:test_invalid_error_code_fallback()
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "INVALID_CODE", "This is a message")
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
-- Should treat as message (backward compatibility)
|
||||
luaunit.assertStrContains(err, "INVALID_CODE")
|
||||
luaunit.assertStrContains(err, "This is a message")
|
||||
end
|
||||
|
||||
-- Test: details formatting with long values
|
||||
function TestErrorHandler:test_details_with_long_values()
|
||||
local longValue = string.rep("x", 150)
|
||||
local success, err = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test", {
|
||||
shortValue = "short",
|
||||
longValue = longValue,
|
||||
})
|
||||
end)
|
||||
|
||||
luaunit.assertFalse(success, "error() should throw")
|
||||
luaunit.assertStrContains(err, "ShortValue: short")
|
||||
-- Long value should be truncated
|
||||
luaunit.assertStrContains(err, "...")
|
||||
end
|
||||
|
||||
-- Test: file logging
|
||||
function TestErrorHandler:test_file_logging()
|
||||
ErrorHandler.setLogTarget("file")
|
||||
ErrorHandler.setLogFile("test-errors.log")
|
||||
|
||||
-- Trigger an error (will be caught)
|
||||
local success = pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test file logging")
|
||||
end)
|
||||
|
||||
-- Check file was created and contains log
|
||||
local file = io.open("test-errors.log", "r")
|
||||
luaunit.assertNotNil(file, "Log file should be created")
|
||||
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
luaunit.assertStrContains(content, "ERROR")
|
||||
luaunit.assertStrContains(content, "TestModule")
|
||||
luaunit.assertStrContains(content, "Test file logging")
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
ErrorHandler.setLogTarget("none")
|
||||
os.remove("test-errors.log")
|
||||
end
|
||||
|
||||
-- Test: log level filtering
|
||||
function TestErrorHandler:test_log_level_filtering()
|
||||
ErrorHandler.setLogTarget("file")
|
||||
ErrorHandler.setLogFile("test-errors.log")
|
||||
ErrorHandler.setLogLevel("ERROR") -- Only log errors, not warnings
|
||||
|
||||
-- Trigger a warning (should not be logged)
|
||||
ErrorHandler.warn("TestModule", "VAL_001", "Test warning")
|
||||
|
||||
-- Trigger an error (should be logged)
|
||||
pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test error")
|
||||
end)
|
||||
|
||||
-- Check file
|
||||
local file = io.open("test-errors.log", "r")
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
luaunit.assertStrContains(content, "Test error")
|
||||
luaunit.assertFalse(content:find("Test warning") ~= nil, "Warning should not be logged")
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
ErrorHandler.setLogTarget("none")
|
||||
ErrorHandler.setLogLevel("WARNING")
|
||||
os.remove("test-errors.log")
|
||||
end
|
||||
|
||||
-- Test: JSON format
|
||||
function TestErrorHandler:test_json_format()
|
||||
ErrorHandler.setLogTarget("file")
|
||||
ErrorHandler.setLogFile("test-errors.log")
|
||||
ErrorHandler.setLogFormat("json")
|
||||
|
||||
pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test JSON", {
|
||||
property = "width",
|
||||
})
|
||||
end)
|
||||
|
||||
local file = io.open("test-errors.log", "r")
|
||||
if file then
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
|
||||
-- Should be valid JSON-like
|
||||
luaunit.assertStrContains(content, '"level":"ERROR"')
|
||||
luaunit.assertStrContains(content, '"module":"TestModule"')
|
||||
luaunit.assertStrContains(content, '"message":"Test JSON"')
|
||||
luaunit.assertStrContains(content, '"details":')
|
||||
end
|
||||
|
||||
-- Cleanup
|
||||
ErrorHandler.setLogTarget("none")
|
||||
ErrorHandler.setLogFormat("human")
|
||||
os.remove("test-errors.log")
|
||||
end
|
||||
|
||||
-- Test: log rotation
|
||||
function TestErrorHandler:test_log_rotation()
|
||||
ErrorHandler.setLogTarget("file")
|
||||
ErrorHandler.setLogFile("test-errors.log")
|
||||
ErrorHandler.enableLogRotation({ maxSize = 100, maxFiles = 2 }) -- Very small for testing
|
||||
|
||||
-- Write multiple errors to trigger rotation
|
||||
for i = 1, 10 do
|
||||
pcall(function()
|
||||
ErrorHandler.error("TestModule", "VAL_001", "Test rotation error number " .. i)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Check that rotation occurred (main file should exist)
|
||||
local file = io.open("test-errors.log", "r")
|
||||
luaunit.assertNotNil(file, "Main log file should exist")
|
||||
if file then
|
||||
file:close()
|
||||
end
|
||||
|
||||
-- Check that rotated files might exist (depending on log size)
|
||||
-- We won't assert this as it depends on exact message size
|
||||
|
||||
-- Cleanup
|
||||
ErrorHandler.setLogTarget("none")
|
||||
ErrorHandler.enableLogRotation(true) -- Reset to defaults
|
||||
os.remove("test-errors.log")
|
||||
os.remove("test-errors.log.1")
|
||||
os.remove("test-errors.log.2")
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -65,9 +65,12 @@ function TestImageRenderer:testCalculateFitWithNegativeBoundsHeight()
|
||||
end
|
||||
|
||||
function TestImageRenderer:testCalculateFitWithInvalidFitMode()
|
||||
luaunit.assertError(function()
|
||||
ImageRenderer.calculateFit(100, 100, 200, 200, "invalid-mode")
|
||||
end)
|
||||
-- Now uses 'fill' fallback with warning instead of error
|
||||
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "invalid-mode")
|
||||
luaunit.assertNotNil(result)
|
||||
-- Should fall back to 'fill' mode behavior (scales to fill bounds)
|
||||
luaunit.assertEquals(result.scaleX, 2)
|
||||
luaunit.assertEquals(result.scaleY, 2)
|
||||
end
|
||||
|
||||
function TestImageRenderer:testCalculateFitWithNilFitMode()
|
||||
@@ -208,9 +211,10 @@ function TestImageRenderer:testDrawWithOpacityGreaterThanOne()
|
||||
end
|
||||
|
||||
function TestImageRenderer:testDrawWithInvalidFitMode()
|
||||
luaunit.assertError(function()
|
||||
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "invalid")
|
||||
end)
|
||||
-- Now uses 'fill' fallback with warning instead of error
|
||||
-- Should not throw an error, just use fill mode
|
||||
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "invalid")
|
||||
luaunit.assertTrue(true) -- If we reach here, no error was thrown
|
||||
end
|
||||
|
||||
function TestImageRenderer:testCalculateFitWithVerySmallBounds()
|
||||
|
||||
@@ -24,51 +24,67 @@ function TestImageScaler:testScaleNearestWithNilSource()
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithZeroSourceWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 0, 10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 0, 10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithZeroSourceHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 0, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 0, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithNegativeSourceWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, -10, 10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, -10, 10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithNegativeSourceHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, -10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, -10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithZeroDestWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 0, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 0, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithZeroDestHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, 0)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, 0)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithNegativeDestWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, -20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, -20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleNearestWithNegativeDestHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, -20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, -20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
-- Unhappy path tests for scaleBilinear
|
||||
@@ -80,51 +96,67 @@ function TestImageScaler:testScaleBilinearWithNilSource()
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithZeroSourceWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 0, 10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 0, 10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithZeroSourceHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 0, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 0, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithNegativeSourceWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, -10, 10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, -10, 10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithNegativeSourceHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, -10, 20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, -10, 20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithZeroDestWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 0, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 0, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithZeroDestHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, 0)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, 0)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithNegativeDestWidth()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, -20, 20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, -20, 20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
function TestImageScaler:testScaleBilinearWithNegativeDestHeight()
|
||||
luaunit.assertError(function()
|
||||
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, -20)
|
||||
end)
|
||||
-- Now returns 1x1 transparent fallback with warning instead of error
|
||||
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, -20)
|
||||
luaunit.assertNotNil(result)
|
||||
luaunit.assertEquals(result:getWidth(), 1)
|
||||
luaunit.assertEquals(result:getHeight(), 1)
|
||||
end
|
||||
|
||||
-- Edge case tests
|
||||
|
||||
@@ -161,22 +161,24 @@ end
|
||||
-- Test: new() with imagePath (successful load via cache)
|
||||
function TestRenderer:testNewWithImagePathSuccessfulLoad()
|
||||
local mockImage = {
|
||||
getDimensions = function() return 50, 50 end
|
||||
getDimensions = function()
|
||||
return 50, 50
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
-- Pre-populate the cache so load succeeds
|
||||
ImageCache._cache["test/image.png"] = {
|
||||
image = mockImage,
|
||||
imageData = nil
|
||||
imageData = nil,
|
||||
}
|
||||
|
||||
|
||||
local renderer = Renderer.new({
|
||||
imagePath = "test/image.png",
|
||||
}, createDeps())
|
||||
|
||||
luaunit.assertEquals(renderer.imagePath, "test/image.png")
|
||||
luaunit.assertEquals(renderer._loadedImage, mockImage)
|
||||
|
||||
|
||||
-- Clean up cache
|
||||
ImageCache._cache["test/image.png"] = nil
|
||||
end
|
||||
|
||||
@@ -89,20 +89,33 @@ function TestUnitsParse:testParseZero()
|
||||
end
|
||||
|
||||
function TestUnitsParse:testParseInvalidType()
|
||||
luaunit.assertErrorMsgContains("Invalid unit value type", Units.parse, nil)
|
||||
-- Now returns fallback value (0, "px") with warning instead of error
|
||||
local value, unit = Units.parse(nil)
|
||||
luaunit.assertEquals(value, 0)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
function TestUnitsParse:testParseInvalidString()
|
||||
luaunit.assertErrorMsgContains("Invalid unit format", Units.parse, "abc")
|
||||
-- Now returns fallback value (0, "px") with warning instead of error
|
||||
local value, unit = Units.parse("abc")
|
||||
luaunit.assertEquals(value, 0)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
function TestUnitsParse:testParseInvalidUnit()
|
||||
luaunit.assertErrorMsgContains("Unknown unit", Units.parse, "100xyz")
|
||||
-- Now extracts the number and treats as pixels with warning instead of error
|
||||
-- "100xyz" -> extracts 100, ignores invalid unit "xyz", treats as "100px"
|
||||
local value, unit = Units.parse("100xyz")
|
||||
luaunit.assertEquals(value, 100)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
function TestUnitsParse:testParseWithSpace()
|
||||
-- Spaces between number and unit should be invalid
|
||||
luaunit.assertErrorMsgContains("contains space", Units.parse, "100 px")
|
||||
-- Now returns fallback value (0, "px") with warning instead of error
|
||||
local value, unit = Units.parse("100 px")
|
||||
luaunit.assertEquals(value, 0)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
-- Test suite for Units.resolve()
|
||||
@@ -369,11 +382,17 @@ function TestUnitsEdgeCases:testResolveZeroParentSize()
|
||||
end
|
||||
|
||||
function TestUnitsEdgeCases:testParseEmptyString()
|
||||
luaunit.assertErrorMsgContains("Invalid unit format", Units.parse, "")
|
||||
-- Now returns fallback value (0, "px") with warning instead of error
|
||||
local value, unit = Units.parse("")
|
||||
luaunit.assertEquals(value, 0)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
function TestUnitsEdgeCases:testParseOnlyUnit()
|
||||
luaunit.assertErrorMsgContains("Missing numeric value before unit", Units.parse, "px")
|
||||
-- Now returns fallback value (0, "px") with warning instead of error
|
||||
local value, unit = Units.parse("px")
|
||||
luaunit.assertEquals(value, 0)
|
||||
luaunit.assertEquals(unit, "px")
|
||||
end
|
||||
|
||||
function TestUnitsEdgeCases:testResolveNegativePercentage()
|
||||
|
||||
Reference in New Issue
Block a user