consolidation

This commit is contained in:
Michael Freno
2025-11-20 23:17:03 -05:00
parent a19352bc9e
commit b4420a2e21
31 changed files with 7318 additions and 10642 deletions

View File

@@ -1,356 +0,0 @@
-- Advanced test suite for Animation.lua to increase coverage
-- Focuses on uncovered edge cases, error handling, and complex scenarios
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
-- Load FlexLove which properly initializes all dependencies
local FlexLove = require("FlexLove")
-- Initialize FlexLove
FlexLove.init()
local Animation = FlexLove.Animation
-- Test suite for Animation error handling and validation
TestAnimationValidation = {}
function TestAnimationValidation:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestAnimationValidation:tearDown()
FlexLove.endFrame()
end
function TestAnimationValidation:test_new_with_invalid_props()
-- Should handle non-table props gracefully
local anim = Animation.new(nil)
luaunit.assertNotNil(anim)
luaunit.assertEquals(anim.duration, 1)
local anim2 = Animation.new("invalid")
luaunit.assertNotNil(anim2)
luaunit.assertEquals(anim2.duration, 1)
end
function TestAnimationValidation:test_new_with_invalid_duration()
-- Negative duration
local anim = Animation.new({
duration = -1,
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(anim.duration, 1) -- Should default to 1
-- Zero duration
local anim2 = Animation.new({
duration = 0,
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(anim2.duration, 1)
-- Non-number duration
local anim3 = Animation.new({
duration = "invalid",
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(anim3.duration, 1)
end
function TestAnimationValidation:test_new_with_invalid_start_final()
-- Invalid start table
local anim = Animation.new({
duration = 1,
start = "invalid",
final = { x = 100 },
})
luaunit.assertEquals(type(anim.start), "table")
-- Invalid final table
local anim2 = Animation.new({
duration = 1,
start = { x = 0 },
final = "invalid",
})
luaunit.assertEquals(type(anim2.final), "table")
end
function TestAnimationValidation:test_easing_string_and_function()
-- Valid easing string
local anim = Animation.new({
duration = 1,
easing = "easeInQuad",
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(type(anim.easing), "function")
-- Invalid easing string (should default to linear)
local anim2 = Animation.new({
duration = 1,
easing = "invalidEasing",
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(type(anim2.easing), "function")
-- Custom easing function
local customEasing = function(t)
return t * t
end
local anim3 = Animation.new({
duration = 1,
easing = customEasing,
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertEquals(anim3.easing, customEasing)
end
-- Test suite for Animation update with edge cases
TestAnimationUpdate = {}
function TestAnimationUpdate:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestAnimationUpdate:tearDown()
FlexLove.endFrame()
end
function TestAnimationUpdate:test_update_with_invalid_dt()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
-- Negative dt
anim:update(-1)
luaunit.assertEquals(anim.elapsed, 0)
-- NaN dt
anim:update(0 / 0)
luaunit.assertEquals(anim.elapsed, 0)
-- Infinite dt
anim:update(math.huge)
luaunit.assertEquals(anim.elapsed, 0)
-- String dt (non-number)
anim:update("invalid")
luaunit.assertEquals(anim.elapsed, 0)
end
function TestAnimationUpdate:test_update_while_paused()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:pause()
local complete = anim:update(0.5)
luaunit.assertFalse(complete)
luaunit.assertEquals(anim.elapsed, 0)
end
function TestAnimationUpdate:test_callbacks()
local onStartCalled = false
local onUpdateCalled = false
local onCompleteCalled = false
local anim = Animation.new({
duration = 0.1,
start = { x = 0 },
final = { x = 100 },
onStart = function()
onStartCalled = true
end,
onUpdate = function()
onUpdateCalled = true
end,
onComplete = function()
onCompleteCalled = true
end,
})
-- First update should trigger onStart
anim:update(0.05)
luaunit.assertTrue(onStartCalled)
luaunit.assertTrue(onUpdateCalled)
luaunit.assertFalse(onCompleteCalled)
-- Complete the animation
anim:update(0.1)
luaunit.assertTrue(onCompleteCalled)
end
function TestAnimationUpdate:test_onCancel_callback()
local onCancelCalled = false
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
onCancel = function()
onCancelCalled = true
end,
})
anim:update(0.5)
anim:cancel()
luaunit.assertTrue(onCancelCalled)
end
-- Test suite for Animation state control
TestAnimationStateControl = {}
function TestAnimationStateControl:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestAnimationStateControl:tearDown()
FlexLove.endFrame()
end
function TestAnimationStateControl:test_pause_resume()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.5)
local elapsed1 = anim.elapsed
anim:pause()
anim:update(0.5)
luaunit.assertEquals(anim.elapsed, elapsed1) -- Should not advance
anim:resume()
anim:update(0.1)
luaunit.assertTrue(anim.elapsed > elapsed1) -- Should advance
end
function TestAnimationStateControl:test_reverse()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.5)
anim:reverse()
luaunit.assertTrue(anim._reversed)
-- Continue updating - it should go backwards
anim:update(0.3)
luaunit.assertTrue(anim.elapsed < 0.5)
end
function TestAnimationStateControl:test_setSpeed()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:setSpeed(2.0)
luaunit.assertEquals(anim._speed, 2.0)
-- Update with 0.1 seconds at 2x speed should advance 0.2 seconds
anim:update(0.1)
luaunit.assertAlmostEquals(anim.elapsed, 0.2, 0.01)
end
function TestAnimationStateControl:test_reset()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.7)
luaunit.assertTrue(anim.elapsed > 0)
anim:reset()
luaunit.assertEquals(anim.elapsed, 0)
luaunit.assertFalse(anim._hasStarted)
end
function TestAnimationStateControl:test_isPaused_isComplete()
local anim = Animation.new({
duration = 0.5,
start = { x = 0 },
final = { x = 100 },
})
luaunit.assertFalse(anim:isPaused())
anim:pause()
luaunit.assertTrue(anim:isPaused())
anim:resume()
luaunit.assertFalse(anim:isPaused())
local complete = anim:update(1.0) -- Complete it
luaunit.assertTrue(complete)
luaunit.assertEquals(anim:getState(), "completed")
end
-- Test suite for delay functionality
TestAnimationDelay = {}
function TestAnimationDelay:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestAnimationDelay:tearDown()
FlexLove.endFrame()
end
function TestAnimationDelay:test_delay()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:delay(0.5)
-- Update during delay - animation should not start yet
local result = anim:update(0.3)
luaunit.assertFalse(result)
luaunit.assertEquals(anim:getState(), "pending")
-- Update past delay - animation should be ready to start
anim:update(0.3) -- Now delay elapsed is > 0.5
luaunit.assertEquals(anim:getState(), "pending") -- Still pending until next update
-- One more update to actually start
anim:update(0.01)
luaunit.assertEquals(anim:getState(), "playing")
end
-- Run all tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,551 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local Animation = require("modules.Animation")
local Easing = Animation.Easing
local Transform = Animation.Transform
local Color = require("modules.Color")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize modules
ErrorHandler.init({})
Color.init({ ErrorHandler = ErrorHandler })
Animation.init({ ErrorHandler = ErrorHandler, Color = Color })
TestAnimationProperties = {}
function TestAnimationProperties:setUp()
-- Reset state before each test
end
-- Test Color.lerp() method
function TestAnimationProperties:testColorLerp_MidPoint()
local colorA = Color.new(0, 0, 0, 1) -- Black
local colorB = Color.new(1, 1, 1, 1) -- White
local result = Color.lerp(colorA, colorB, 0.5)
luaunit.assertAlmostEquals(result.r, 0.5, 0.01)
luaunit.assertAlmostEquals(result.g, 0.5, 0.01)
luaunit.assertAlmostEquals(result.b, 0.5, 0.01)
luaunit.assertAlmostEquals(result.a, 1, 0.01)
end
function TestAnimationProperties:testColorLerp_StartPoint()
local colorA = Color.new(1, 0, 0, 1) -- Red
local colorB = Color.new(0, 0, 1, 1) -- Blue
local result = Color.lerp(colorA, colorB, 0)
luaunit.assertAlmostEquals(result.r, 1, 0.01)
luaunit.assertAlmostEquals(result.g, 0, 0.01)
luaunit.assertAlmostEquals(result.b, 0, 0.01)
end
function TestAnimationProperties:testColorLerp_EndPoint()
local colorA = Color.new(1, 0, 0, 1) -- Red
local colorB = Color.new(0, 0, 1, 1) -- Blue
local result = Color.lerp(colorA, colorB, 1)
luaunit.assertAlmostEquals(result.r, 0, 0.01)
luaunit.assertAlmostEquals(result.g, 0, 0.01)
luaunit.assertAlmostEquals(result.b, 1, 0.01)
end
function TestAnimationProperties:testColorLerp_Alpha()
local colorA = Color.new(1, 1, 1, 0) -- Transparent white
local colorB = Color.new(1, 1, 1, 1) -- Opaque white
local result = Color.lerp(colorA, colorB, 0.5)
luaunit.assertAlmostEquals(result.a, 0.5, 0.01)
end
function TestAnimationProperties:testColorLerp_InvalidInputs()
-- Should handle invalid inputs gracefully
local result = Color.lerp("invalid", "invalid", 0.5)
luaunit.assertNotNil(result)
luaunit.assertEquals(getmetatable(result), Color)
end
function TestAnimationProperties:testColorLerp_ClampT()
local colorA = Color.new(0, 0, 0, 1)
local colorB = Color.new(1, 1, 1, 1)
-- Test t > 1
local result1 = Color.lerp(colorA, colorB, 1.5)
luaunit.assertAlmostEquals(result1.r, 1, 0.01)
-- Test t < 0
local result2 = Color.lerp(colorA, colorB, -0.5)
luaunit.assertAlmostEquals(result2.r, 0, 0.01)
end
-- Test Position Animation (x, y)
function TestAnimationProperties:testPositionAnimation_XProperty()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 50, 0.01)
end
function TestAnimationProperties:testPositionAnimation_YProperty()
local anim = Animation.new({
duration = 1,
start = { y = 0 },
final = { y = 200 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.y, 100, 0.01)
end
function TestAnimationProperties:testPositionAnimation_XY()
local anim = Animation.new({
duration = 1,
start = { x = 10, y = 20 },
final = { x = 110, y = 220 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 60, 0.01)
luaunit.assertAlmostEquals(result.y, 120, 0.01)
end
-- Test Color Property Animation
function TestAnimationProperties:testColorAnimation_BackgroundColor()
local anim = Animation.new({
duration = 1,
start = { backgroundColor = Color.new(1, 0, 0, 1) }, -- Red
final = { backgroundColor = Color.new(0, 0, 1, 1) }, -- Blue
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.5, 0.01)
luaunit.assertAlmostEquals(result.backgroundColor.b, 0.5, 0.01)
end
function TestAnimationProperties:testColorAnimation_MultipleColors()
local anim = Animation.new({
duration = 1,
start = {
backgroundColor = Color.new(1, 0, 0, 1),
borderColor = Color.new(0, 1, 0, 1),
textColor = Color.new(0, 0, 1, 1),
},
final = {
backgroundColor = Color.new(0, 1, 0, 1),
borderColor = Color.new(0, 0, 1, 1),
textColor = Color.new(1, 0, 0, 1),
},
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertNotNil(result.borderColor)
luaunit.assertNotNil(result.textColor)
-- Mid-point should be (0.5, 0.5, 0.5) for backgroundColor
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.5, 0.01)
luaunit.assertAlmostEquals(result.backgroundColor.g, 0.5, 0.01)
end
function TestAnimationProperties:testColorAnimation_WithColorModule()
-- Should interpolate colors when Color module is set
local anim = Animation.new({
duration = 1,
start = { backgroundColor = Color.new(1, 0, 0, 1) },
final = { backgroundColor = Color.new(0, 0, 1, 1) },
})
-- Color module is set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.5, 0.01)
luaunit.assertAlmostEquals(result.backgroundColor.g, 0, 0.01)
luaunit.assertAlmostEquals(result.backgroundColor.b, 0.5, 0.01)
end
function TestAnimationProperties:testColorAnimation_HexColors()
local anim = Animation.new({
duration = 1,
start = { backgroundColor = "#FF0000" }, -- Red
final = { backgroundColor = "#0000FF" }, -- Blue
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.5, 0.01)
end
function TestAnimationProperties:testColorAnimation_NamedColors()
-- Note: Named colors like "red" and "blue" are not supported
-- Use hex colors or Color objects instead
local anim = Animation.new({
duration = 1,
start = { backgroundColor = "#FF0000" }, -- red
final = { backgroundColor = "#0000FF" }, -- blue
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.5, 0.01)
end
-- Test Numeric Property Animation
function TestAnimationProperties:testNumericAnimation_Gap()
local anim = Animation.new({
duration = 1,
start = { gap = 0 },
final = { gap = 20 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.gap, 10, 0.01)
end
function TestAnimationProperties:testNumericAnimation_ImageOpacity()
local anim = Animation.new({
duration = 1,
start = { imageOpacity = 0 },
final = { imageOpacity = 1 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.imageOpacity, 0.5, 0.01)
end
function TestAnimationProperties:testNumericAnimation_BorderWidth()
local anim = Animation.new({
duration = 1,
start = { borderWidth = 1 },
final = { borderWidth = 10 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.borderWidth, 5.5, 0.01)
end
function TestAnimationProperties:testNumericAnimation_FontSize()
local anim = Animation.new({
duration = 1,
start = { fontSize = 12 },
final = { fontSize = 24 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.fontSize, 18, 0.01)
end
function TestAnimationProperties:testNumericAnimation_MultipleProperties()
local anim = Animation.new({
duration = 1,
start = { gap = 0, imageOpacity = 0, borderWidth = 1 },
final = { gap = 20, imageOpacity = 1, borderWidth = 5 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.gap, 10, 0.01)
luaunit.assertAlmostEquals(result.imageOpacity, 0.5, 0.01)
luaunit.assertAlmostEquals(result.borderWidth, 3, 0.01)
end
-- Test Table Property Animation (padding, margin, cornerRadius)
function TestAnimationProperties:testTableAnimation_Padding()
local anim = Animation.new({
duration = 1,
start = { padding = { top = 0, right = 0, bottom = 0, left = 0 } },
final = { padding = { top = 10, right = 20, bottom = 10, left = 20 } },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.padding)
luaunit.assertAlmostEquals(result.padding.top, 5, 0.01)
luaunit.assertAlmostEquals(result.padding.right, 10, 0.01)
luaunit.assertAlmostEquals(result.padding.bottom, 5, 0.01)
luaunit.assertAlmostEquals(result.padding.left, 10, 0.01)
end
function TestAnimationProperties:testTableAnimation_Margin()
local anim = Animation.new({
duration = 1,
start = { margin = { top = 0, right = 0, bottom = 0, left = 0 } },
final = { margin = { top = 20, right = 20, bottom = 20, left = 20 } },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.margin)
luaunit.assertAlmostEquals(result.margin.top, 10, 0.01)
luaunit.assertAlmostEquals(result.margin.right, 10, 0.01)
end
function TestAnimationProperties:testTableAnimation_CornerRadius()
local anim = Animation.new({
duration = 1,
start = { cornerRadius = { topLeft = 0, topRight = 0, bottomLeft = 0, bottomRight = 0 } },
final = { cornerRadius = { topLeft = 10, topRight = 10, bottomLeft = 10, bottomRight = 10 } },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.cornerRadius)
luaunit.assertAlmostEquals(result.cornerRadius.topLeft, 5, 0.01)
luaunit.assertAlmostEquals(result.cornerRadius.topRight, 5, 0.01)
end
function TestAnimationProperties:testTableAnimation_PartialKeys()
-- Test when start and final have different keys
local anim = Animation.new({
duration = 1,
start = { padding = { top = 0, left = 0 } },
final = { padding = { top = 10, right = 20, left = 10 } },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.padding)
luaunit.assertAlmostEquals(result.padding.top, 5, 0.01)
luaunit.assertAlmostEquals(result.padding.left, 5, 0.01)
luaunit.assertNotNil(result.padding.right)
end
function TestAnimationProperties:testTableAnimation_NonNumericValues()
-- Should skip non-numeric values in tables
local anim = Animation.new({
duration = 1,
start = { padding = { top = 0, special = "value" } },
final = { padding = { top = 10, special = "value" } },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.padding)
luaunit.assertAlmostEquals(result.padding.top, 5, 0.01)
end
-- Test Combined Animations
function TestAnimationProperties:testCombinedAnimation_AllTypes()
local anim = Animation.new({
duration = 1,
start = {
width = 100,
height = 100,
x = 0,
y = 0,
opacity = 0,
backgroundColor = Color.new(1, 0, 0, 1),
gap = 0,
padding = { top = 0, left = 0 },
},
final = {
width = 200,
height = 200,
x = 100,
y = 100,
opacity = 1,
backgroundColor = Color.new(0, 0, 1, 1),
gap = 20,
padding = { top = 10, left = 10 },
},
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
-- Check all properties interpolated correctly
luaunit.assertAlmostEquals(result.width, 150, 0.01)
luaunit.assertAlmostEquals(result.height, 150, 0.01)
luaunit.assertAlmostEquals(result.x, 50, 0.01)
luaunit.assertAlmostEquals(result.y, 50, 0.01)
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
luaunit.assertAlmostEquals(result.gap, 10, 0.01)
luaunit.assertNotNil(result.backgroundColor)
luaunit.assertNotNil(result.padding)
end
function TestAnimationProperties:testCombinedAnimation_WithEasing()
local anim = Animation.new({
duration = 1,
start = { x = 0, backgroundColor = Color.new(0, 0, 0, 1) },
final = { x = 100, backgroundColor = Color.new(1, 1, 1, 1) },
easing = "easeInQuad",
})
-- Color module already set via Animation.init()
anim:update(0.5)
local result = anim:interpolate()
-- With easeInQuad, at t=0.5, eased value should be 0.25
luaunit.assertAlmostEquals(result.x, 25, 0.01)
luaunit.assertAlmostEquals(result.backgroundColor.r, 0.25, 0.01)
end
-- Test Backward Compatibility
function TestAnimationProperties:testBackwardCompatibility_WidthHeightOpacity()
-- Ensure old animations still work
local anim = Animation.new({
duration = 1,
start = { width = 100, height = 100, opacity = 0 },
final = { width = 200, height = 200, opacity = 1 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.width, 150, 0.01)
luaunit.assertAlmostEquals(result.height, 150, 0.01)
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
end
function TestAnimationProperties:testBackwardCompatibility_FadeHelper()
local anim = Animation.fade(1, 0, 1)
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
end
function TestAnimationProperties:testBackwardCompatibility_ScaleHelper()
local anim = Animation.scale(1, { width = 100, height = 100 }, { width = 200, height = 200 })
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.width, 150, 0.01)
luaunit.assertAlmostEquals(result.height, 150, 0.01)
end
-- Test Edge Cases
function TestAnimationProperties:testEdgeCase_MissingStartValue()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100, y = 100 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 50, 0.01)
luaunit.assertNil(result.y) -- Should be nil since start.y is missing
end
function TestAnimationProperties:testEdgeCase_MissingFinalValue()
local anim = Animation.new({
duration = 1,
start = { x = 0, y = 0 },
final = { x = 100 },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 50, 0.01)
luaunit.assertNil(result.y) -- Should be nil since final.y is missing
end
function TestAnimationProperties:testEdgeCase_EmptyTables()
local anim = Animation.new({
duration = 1,
start = {},
final = {},
})
anim:update(0.5)
local result = anim:interpolate()
-- Should not error, just return empty result
luaunit.assertNotNil(result)
end
function TestAnimationProperties:testEdgeCase_CachedResult()
-- Test that cached results work correctly
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.5)
local result1 = anim:interpolate()
local result2 = anim:interpolate() -- Should use cached result
luaunit.assertEquals(result1, result2) -- Same table reference
luaunit.assertAlmostEquals(result1.x, 50, 0.01)
end
function TestAnimationProperties:testEdgeCase_ResultInvalidatedOnUpdate()
local anim = Animation.new({
duration = 1,
start = { x = 0 },
final = { x = 100 },
})
anim:update(0.5)
local result1 = anim:interpolate()
local x1 = result1.x -- Store value, not reference
anim:update(0.25) -- Update again
local result2 = anim:interpolate()
local x2 = result2.x
-- Should recalculate
-- Note: result1 and result2 are the same cached table, but values should be updated
luaunit.assertAlmostEquals(x1, 50, 0.01)
luaunit.assertAlmostEquals(x2, 75, 0.01)
-- result1.x will actually be 75 now since it's the same table reference
luaunit.assertAlmostEquals(result1.x, 75, 0.01)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,326 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local Animation = require("modules.Animation")
local Easing = Animation.Easing
TestEasing = {}
function TestEasing:setUp()
-- Reset state before each test
end
-- Test that all easing functions exist
function TestEasing:testAllEasingFunctionsExist()
local easings = {
-- Linear
"linear",
-- Quad
"easeInQuad",
"easeOutQuad",
"easeInOutQuad",
-- Cubic
"easeInCubic",
"easeOutCubic",
"easeInOutCubic",
-- Quart
"easeInQuart",
"easeOutQuart",
"easeInOutQuart",
-- Quint
"easeInQuint",
"easeOutQuint",
"easeInOutQuint",
-- Expo
"easeInExpo",
"easeOutExpo",
"easeInOutExpo",
-- Sine
"easeInSine",
"easeOutSine",
"easeInOutSine",
-- Circ
"easeInCirc",
"easeOutCirc",
"easeInOutCirc",
-- Back
"easeInBack",
"easeOutBack",
"easeInOutBack",
-- Elastic
"easeInElastic",
"easeOutElastic",
"easeInOutElastic",
-- Bounce
"easeInBounce",
"easeOutBounce",
"easeInOutBounce",
}
for _, name in ipairs(easings) do
luaunit.assertNotNil(Easing[name], "Easing function " .. name .. " should exist")
luaunit.assertEquals(type(Easing[name]), "function", name .. " should be a function")
end
end
-- Test that all easing functions accept t parameter (0-1)
function TestEasing:testEasingFunctionsAcceptParameter()
local result = Easing.linear(0.5)
luaunit.assertNotNil(result)
luaunit.assertEquals(type(result), "number")
end
-- Test linear easing
function TestEasing:testLinear()
luaunit.assertEquals(Easing.linear(0), 0)
luaunit.assertEquals(Easing.linear(0.5), 0.5)
luaunit.assertEquals(Easing.linear(1), 1)
end
-- Test easeInQuad
function TestEasing:testEaseInQuad()
luaunit.assertEquals(Easing.easeInQuad(0), 0)
luaunit.assertAlmostEquals(Easing.easeInQuad(0.5), 0.25, 0.01)
luaunit.assertEquals(Easing.easeInQuad(1), 1)
end
-- Test easeOutQuad
function TestEasing:testEaseOutQuad()
luaunit.assertEquals(Easing.easeOutQuad(0), 0)
luaunit.assertAlmostEquals(Easing.easeOutQuad(0.5), 0.75, 0.01)
luaunit.assertEquals(Easing.easeOutQuad(1), 1)
end
-- Test easeInOutQuad
function TestEasing:testEaseInOutQuad()
luaunit.assertEquals(Easing.easeInOutQuad(0), 0)
luaunit.assertAlmostEquals(Easing.easeInOutQuad(0.5), 0.5, 0.01)
luaunit.assertEquals(Easing.easeInOutQuad(1), 1)
end
-- Test easeInSine
function TestEasing:testEaseInSine()
luaunit.assertEquals(Easing.easeInSine(0), 0)
local mid = Easing.easeInSine(0.5)
luaunit.assertTrue(mid > 0 and mid < 1, "easeInSine(0.5) should be between 0 and 1")
luaunit.assertAlmostEquals(Easing.easeInSine(1), 1, 0.01)
end
-- Test easeOutSine
function TestEasing:testEaseOutSine()
luaunit.assertEquals(Easing.easeOutSine(0), 0)
local mid = Easing.easeOutSine(0.5)
luaunit.assertTrue(mid > 0 and mid < 1, "easeOutSine(0.5) should be between 0 and 1")
luaunit.assertAlmostEquals(Easing.easeOutSine(1), 1, 0.01)
end
-- Test easeInOutSine
function TestEasing:testEaseInOutSine()
luaunit.assertEquals(Easing.easeInOutSine(0), 0)
luaunit.assertAlmostEquals(Easing.easeInOutSine(0.5), 0.5, 0.01)
luaunit.assertAlmostEquals(Easing.easeInOutSine(1), 1, 0.01)
end
-- Test easeInQuint
function TestEasing:testEaseInQuint()
luaunit.assertEquals(Easing.easeInQuint(0), 0)
luaunit.assertAlmostEquals(Easing.easeInQuint(0.5), 0.03125, 0.01)
luaunit.assertEquals(Easing.easeInQuint(1), 1)
end
-- Test easeOutQuint
function TestEasing:testEaseOutQuint()
luaunit.assertEquals(Easing.easeOutQuint(0), 0)
luaunit.assertAlmostEquals(Easing.easeOutQuint(0.5), 0.96875, 0.01)
luaunit.assertEquals(Easing.easeOutQuint(1), 1)
end
-- Test easeInCirc
function TestEasing:testEaseInCirc()
luaunit.assertEquals(Easing.easeInCirc(0), 0)
local mid = Easing.easeInCirc(0.5)
luaunit.assertTrue(mid > 0 and mid < 1, "easeInCirc(0.5) should be between 0 and 1")
luaunit.assertAlmostEquals(Easing.easeInCirc(1), 1, 0.01)
end
-- Test easeOutCirc
function TestEasing:testEaseOutCirc()
luaunit.assertEquals(Easing.easeOutCirc(0), 0)
local mid = Easing.easeOutCirc(0.5)
luaunit.assertTrue(mid > 0 and mid < 1, "easeOutCirc(0.5) should be between 0 and 1")
luaunit.assertAlmostEquals(Easing.easeOutCirc(1), 1, 0.01)
end
-- Test easeInOutCirc
function TestEasing:testEaseInOutCirc()
luaunit.assertEquals(Easing.easeInOutCirc(0), 0)
luaunit.assertAlmostEquals(Easing.easeInOutCirc(0.5), 0.5, 0.01)
luaunit.assertAlmostEquals(Easing.easeInOutCirc(1), 1, 0.01)
end
-- Test easeInBack (should overshoot at start)
function TestEasing:testEaseInBack()
luaunit.assertEquals(Easing.easeInBack(0), 0)
local early = Easing.easeInBack(0.3)
luaunit.assertTrue(early < 0, "easeInBack should go negative (overshoot) early on")
luaunit.assertAlmostEquals(Easing.easeInBack(1), 1, 0.001)
end
-- Test easeOutBack (should overshoot at end)
function TestEasing:testEaseOutBack()
luaunit.assertAlmostEquals(Easing.easeOutBack(0), 0, 0.001)
local late = Easing.easeOutBack(0.7)
luaunit.assertTrue(late > 0.7, "easeOutBack should overshoot at the end")
luaunit.assertAlmostEquals(Easing.easeOutBack(1), 1, 0.01)
end
-- Test easeInElastic (should oscillate)
function TestEasing:testEaseInElastic()
luaunit.assertEquals(Easing.easeInElastic(0), 0)
luaunit.assertAlmostEquals(Easing.easeInElastic(1), 1, 0.01)
-- Elastic should go negative at some point
local hasNegative = false
for i = 1, 9 do
local t = i / 10
if Easing.easeInElastic(t) < 0 then
hasNegative = true
break
end
end
luaunit.assertTrue(hasNegative, "easeInElastic should have negative values (oscillation)")
end
-- Test easeOutElastic (should oscillate)
function TestEasing:testEaseOutElastic()
luaunit.assertEquals(Easing.easeOutElastic(0), 0)
luaunit.assertAlmostEquals(Easing.easeOutElastic(1), 1, 0.01)
-- Elastic should go above 1 at some point
local hasOvershoot = false
for i = 1, 9 do
local t = i / 10
if Easing.easeOutElastic(t) > 1 then
hasOvershoot = true
break
end
end
luaunit.assertTrue(hasOvershoot, "easeOutElastic should overshoot 1 (oscillation)")
end
-- Test easeInBounce
function TestEasing:testEaseInBounce()
luaunit.assertEquals(Easing.easeInBounce(0), 0)
luaunit.assertAlmostEquals(Easing.easeInBounce(1), 1, 0.01)
-- Bounce should have multiple "bounces" (local minima)
local result = Easing.easeInBounce(0.5)
luaunit.assertTrue(result >= 0 and result <= 1, "easeInBounce should stay within 0-1 range")
end
-- Test easeOutBounce
function TestEasing:testEaseOutBounce()
luaunit.assertEquals(Easing.easeOutBounce(0), 0)
luaunit.assertAlmostEquals(Easing.easeOutBounce(1), 1, 0.01)
-- Bounce should have bounces
local result = Easing.easeOutBounce(0.8)
luaunit.assertTrue(result >= 0 and result <= 1, "easeOutBounce should stay within 0-1 range")
end
-- Test easeInOutBounce
function TestEasing:testEaseInOutBounce()
luaunit.assertEquals(Easing.easeInOutBounce(0), 0)
luaunit.assertAlmostEquals(Easing.easeInOutBounce(0.5), 0.5, 0.01)
luaunit.assertAlmostEquals(Easing.easeInOutBounce(1), 1, 0.01)
end
-- Test configurable back() factory
function TestEasing:testBackFactory()
local customBack = Easing.back(2.5)
luaunit.assertEquals(type(customBack), "function")
luaunit.assertEquals(customBack(0), 0)
luaunit.assertEquals(customBack(1), 1)
-- Should overshoot with custom amount
local mid = customBack(0.3)
luaunit.assertTrue(mid < 0, "Custom back easing should overshoot")
end
-- Test configurable elastic() factory
function TestEasing:testElasticFactory()
local customElastic = Easing.elastic(1.5, 0.4)
luaunit.assertEquals(type(customElastic), "function")
luaunit.assertEquals(customElastic(0), 0)
luaunit.assertAlmostEquals(customElastic(1), 1, 0.01)
end
-- Test that all InOut easings are symmetric around 0.5
function TestEasing:testInOutSymmetry()
local inOutEasings = {
"easeInOutQuad",
"easeInOutCubic",
"easeInOutQuart",
"easeInOutQuint",
"easeInOutExpo",
"easeInOutSine",
"easeInOutCirc",
"easeInOutBack",
"easeInOutElastic",
"easeInOutBounce",
}
for _, name in ipairs(inOutEasings) do
local easing = Easing[name]
-- At t=0.5, all InOut easings should be close to 0.5
local mid = easing(0.5)
luaunit.assertAlmostEquals(mid, 0.5, 0.1, name .. " should be close to 0.5 at t=0.5")
end
end
-- Test boundary conditions for all easings
function TestEasing:testBoundaryConditions()
local easings = {
"linear",
"easeInQuad",
"easeOutQuad",
"easeInOutQuad",
"easeInCubic",
"easeOutCubic",
"easeInOutCubic",
"easeInQuart",
"easeOutQuart",
"easeInOutQuart",
"easeInQuint",
"easeOutQuint",
"easeInOutQuint",
"easeInExpo",
"easeOutExpo",
"easeInOutExpo",
"easeInSine",
"easeOutSine",
"easeInOutSine",
"easeInCirc",
"easeOutCirc",
"easeInOutCirc",
"easeInBack",
"easeOutBack",
"easeInOutBack",
"easeInElastic",
"easeOutElastic",
"easeInOutElastic",
"easeInBounce",
"easeOutBounce",
"easeInOutBounce",
}
for _, name in ipairs(easings) do
local easing = Easing[name]
-- All easings should start at 0
local start = easing(0)
luaunit.assertAlmostEquals(start, 0, 0.01, name .. " should start at 0")
-- All easings should end at 1
local finish = easing(1)
luaunit.assertAlmostEquals(finish, 1, 0.01, name .. " should end at 1")
end
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,612 +0,0 @@
-- Advanced test suite for Element.lua to increase coverage
-- Focuses on uncovered edge cases and complex scenarios
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local FlexLove = require("FlexLove")
local Color = require("modules.Color")
-- Initialize FlexLove
FlexLove.init()
-- Test suite for resize behavior with different unit types
TestElementResize = {}
function TestElementResize:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementResize:tearDown()
FlexLove.endFrame()
end
function TestElementResize:test_resize_with_percentage_units()
-- Test that percentage units calculate correctly initially
local parent = FlexLove.new({
id = "resize_parent",
x = 0,
y = 0,
width = 1000,
height = 500,
})
local child = FlexLove.new({
id = "resize_child",
width = "50%",
height = "50%",
parent = parent,
})
-- Initial calculation should be 50% of parent
luaunit.assertEquals(child.width, 500)
luaunit.assertEquals(child.height, 250)
-- Verify units are stored correctly
luaunit.assertEquals(child.units.width.unit, "%")
luaunit.assertEquals(child.units.height.unit, "%")
end
function TestElementResize:test_resize_with_viewport_units()
-- Test that viewport units calculate correctly
local element = FlexLove.new({
id = "vp_resize",
x = 0,
y = 0,
width = "50vw",
height = "50vh",
})
-- Should be 50% of viewport (1920x1080)
luaunit.assertEquals(element.width, 960)
luaunit.assertEquals(element.height, 540)
-- Verify units are stored correctly
luaunit.assertEquals(element.units.width.unit, "vw")
luaunit.assertEquals(element.units.height.unit, "vh")
end
function TestElementResize:test_resize_with_textSize_scaling()
-- Test that textSize with viewport units calculates correctly
local element = FlexLove.new({
id = "text_resize",
x = 0,
y = 0,
width = 200,
height = 100,
text = "Test",
textSize = "2vh",
autoScaleText = true,
})
-- 2vh of 1080 = 21.6
luaunit.assertAlmostEquals(element.textSize, 21.6, 0.1)
-- Verify unit is stored
luaunit.assertEquals(element.units.textSize.unit, "vh")
end
-- Test suite for positioning offset application (top/right/bottom/left)
TestElementPositioningOffsets = {}
function TestElementPositioningOffsets:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementPositioningOffsets:tearDown()
FlexLove.endFrame()
end
function TestElementPositioningOffsets:test_applyPositioningOffsets_with_absolute()
local parent = FlexLove.new({
id = "offset_parent",
x = 0,
y = 0,
width = 500,
height = 500,
positioning = "absolute",
})
local child = FlexLove.new({
id = "offset_child",
width = 100,
height = 100,
positioning = "absolute",
top = 50,
left = 50,
parent = parent,
})
-- Apply positioning offsets
parent:applyPositioningOffsets(child)
-- Child should be offset from parent
luaunit.assertTrue(child.y >= parent.y + 50)
luaunit.assertTrue(child.x >= parent.x + 50)
end
function TestElementPositioningOffsets:test_applyPositioningOffsets_with_right_bottom()
local parent = FlexLove.new({
id = "rb_parent",
x = 0,
y = 0,
width = 500,
height = 500,
positioning = "relative",
})
local child = FlexLove.new({
id = "rb_child",
width = 100,
height = 100,
positioning = "absolute",
right = 50,
bottom = 50,
parent = parent,
})
parent:applyPositioningOffsets(child)
-- Child should be positioned from right/bottom
luaunit.assertNotNil(child.x)
luaunit.assertNotNil(child.y)
end
-- Test suite for scroll-related methods
TestElementScrollMethods = {}
function TestElementScrollMethods:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementScrollMethods:tearDown()
FlexLove.endFrame()
end
function TestElementScrollMethods:test_scrollToTop()
local container = FlexLove.new({
id = "scroll_container",
x = 0,
y = 0,
width = 300,
height = 200,
overflow = "scroll",
positioning = "flex",
flexDirection = "vertical",
})
-- Add content that overflows
for i = 1, 10 do
FlexLove.new({
id = "item_" .. i,
width = 280,
height = 50,
parent = container,
})
end
-- Scroll down first
container:setScrollPosition(nil, 100)
local _, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollY, 100)
-- Scroll to top
container:scrollToTop()
_, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollY, 0)
end
function TestElementScrollMethods:test_scrollToBottom()
local container = FlexLove.new({
id = "scroll_bottom",
x = 0,
y = 0,
width = 300,
height = 200,
overflow = "scroll",
positioning = "flex",
flexDirection = "vertical",
})
-- Add overflowing content
for i = 1, 10 do
FlexLove.new({
id = "item_" .. i,
width = 280,
height = 50,
parent = container,
})
end
container:scrollToBottom()
local _, scrollY = container:getScrollPosition()
local _, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollY, maxScrollY)
end
function TestElementScrollMethods:test_scrollBy()
local container = FlexLove.new({
id = "scroll_by",
x = 0,
y = 0,
width = 300,
height = 200,
overflow = "scroll",
positioning = "flex",
flexDirection = "vertical",
})
for i = 1, 10 do
FlexLove.new({
id = "item_" .. i,
width = 280,
height = 50,
parent = container,
})
end
container:scrollBy(nil, 50)
local _, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollY, 50)
container:scrollBy(nil, 25)
_, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollY, 75)
end
function TestElementScrollMethods:test_getScrollPercentage()
local container = FlexLove.new({
id = "scroll_pct",
x = 0,
y = 0,
width = 300,
height = 200,
overflow = "scroll",
positioning = "flex",
flexDirection = "vertical",
})
for i = 1, 10 do
FlexLove.new({
id = "item_" .. i,
width = 280,
height = 50,
parent = container,
})
end
-- At top
local _, percentY = container:getScrollPercentage()
luaunit.assertEquals(percentY, 0)
-- Scroll halfway
local _, maxScrollY = container:getMaxScroll()
container:setScrollPosition(nil, maxScrollY / 2)
_, percentY = container:getScrollPercentage()
luaunit.assertAlmostEquals(percentY, 0.5, 0.01)
end
-- Test suite for auto-sizing with complex scenarios
TestElementComplexAutoSizing = {}
function TestElementComplexAutoSizing:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementComplexAutoSizing:tearDown()
FlexLove.endFrame()
end
function TestElementComplexAutoSizing:test_autosize_with_nested_flex()
local root = FlexLove.new({
id = "root",
x = 0,
y = 0,
positioning = "flex",
flexDirection = "vertical",
})
local row1 = FlexLove.new({
id = "row1",
positioning = "flex",
flexDirection = "horizontal",
parent = root,
})
FlexLove.new({
id = "item1",
width = 100,
height = 50,
parent = row1,
})
FlexLove.new({
id = "item2",
width = 100,
height = 50,
parent = row1,
})
-- Root should auto-size to contain row
luaunit.assertTrue(root.width >= 200)
luaunit.assertTrue(root.height >= 50)
end
function TestElementComplexAutoSizing:test_autosize_with_absolutely_positioned_child()
local parent = FlexLove.new({
id = "abs_parent",
x = 0,
y = 0,
positioning = "flex",
})
-- Regular child affects size
FlexLove.new({
id = "regular",
width = 100,
height = 100,
parent = parent,
})
-- Absolutely positioned child should NOT affect parent size
FlexLove.new({
id = "absolute",
width = 200,
height = 200,
positioning = "absolute",
parent = parent,
})
-- Parent should only size to regular child
luaunit.assertTrue(parent.width < 150)
luaunit.assertTrue(parent.height < 150)
end
function TestElementComplexAutoSizing:test_autosize_with_margin()
local parent = FlexLove.new({
id = "margin_parent",
x = 0,
y = 0,
positioning = "flex",
flexDirection = "horizontal",
})
-- Add two children with margins to test margin collapsing
FlexLove.new({
id = "margin_child1",
width = 100,
height = 100,
margin = { right = 20 },
parent = parent,
})
FlexLove.new({
id = "margin_child2",
width = 100,
height = 100,
margin = { left = 20 },
parent = parent,
})
-- Parent should size to children (margins don't add to content size in flex layout)
luaunit.assertEquals(parent.width, 200)
luaunit.assertEquals(parent.height, 100)
end
-- Test suite for theme integration
TestElementThemeIntegration = {}
function TestElementThemeIntegration:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementThemeIntegration:tearDown()
FlexLove.endFrame()
end
function TestElementThemeIntegration:test_getScaledContentPadding()
local element = FlexLove.new({
id = "themed",
x = 0,
y = 0,
width = 200,
height = 100,
})
local padding = element:getScaledContentPadding()
-- Should return nil if no theme component
luaunit.assertNil(padding)
end
function TestElementThemeIntegration:test_getAvailableContentWidth_with_padding()
local element = FlexLove.new({
id = "content_width",
x = 0,
y = 0,
width = 200,
height = 100,
padding = 10,
})
local availableWidth = element:getAvailableContentWidth()
-- Should be width minus padding
luaunit.assertEquals(availableWidth, 180) -- 200 - 10*2
end
function TestElementThemeIntegration:test_getAvailableContentHeight_with_padding()
local element = FlexLove.new({
id = "content_height",
x = 0,
y = 0,
width = 200,
height = 100,
padding = 10,
})
local availableHeight = element:getAvailableContentHeight()
luaunit.assertEquals(availableHeight, 80) -- 100 - 10*2
end
-- Test suite for child management edge cases
TestElementChildManagementEdgeCases = {}
function TestElementChildManagementEdgeCases:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementChildManagementEdgeCases:tearDown()
FlexLove.endFrame()
end
function TestElementChildManagementEdgeCases:test_addChild_triggers_autosize_recalc()
local parent = FlexLove.new({
id = "dynamic_parent",
x = 0,
y = 0,
positioning = "flex",
})
local initialWidth = parent.width
local initialHeight = parent.height
-- Add child dynamically
local child = FlexLove.new({
id = "dynamic_child",
width = 150,
height = 150,
})
parent:addChild(child)
-- Parent should have resized
luaunit.assertTrue(parent.width >= initialWidth)
luaunit.assertTrue(parent.height >= initialHeight)
end
function TestElementChildManagementEdgeCases:test_removeChild_triggers_autosize_recalc()
local parent = FlexLove.new({
id = "shrink_parent",
x = 0,
y = 0,
positioning = "flex",
})
local child1 = FlexLove.new({
id = "child1",
width = 100,
height = 100,
parent = parent,
})
local child2 = FlexLove.new({
id = "child2",
width = 100,
height = 100,
parent = parent,
})
local widthWithTwo = parent.width
parent:removeChild(child2)
-- Parent should shrink
luaunit.assertTrue(parent.width < widthWithTwo)
end
function TestElementChildManagementEdgeCases:test_clearChildren_resets_autosize()
local parent = FlexLove.new({
id = "clear_parent",
x = 0,
y = 0,
positioning = "flex",
})
for i = 1, 5 do
FlexLove.new({
id = "child_" .. i,
width = 50,
height = 50,
parent = parent,
})
end
local widthWithChildren = parent.width
parent:clearChildren()
-- Parent should shrink to minimal size
luaunit.assertTrue(parent.width < widthWithChildren)
luaunit.assertEquals(#parent.children, 0)
end
-- Test suite for grid layout edge cases
TestElementGridEdgeCases = {}
function TestElementGridEdgeCases:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestElementGridEdgeCases:tearDown()
FlexLove.endFrame()
end
function TestElementGridEdgeCases:test_grid_with_uneven_children()
local grid = FlexLove.new({
id = "uneven_grid",
x = 0,
y = 0,
width = 300,
height = 300,
positioning = "grid",
gridRows = 2,
gridColumns = 2,
})
-- Add only 3 children to a 2x2 grid
for i = 1, 3 do
FlexLove.new({
id = "grid_item_" .. i,
width = 50,
height = 50,
parent = grid,
})
end
luaunit.assertEquals(#grid.children, 3)
end
function TestElementGridEdgeCases:test_grid_with_percentage_gaps()
local grid = FlexLove.new({
id = "pct_gap_grid",
x = 0,
y = 0,
width = 400,
height = 400,
positioning = "grid",
gridRows = 2,
gridColumns = 2,
columnGap = "5%",
rowGap = "5%",
})
luaunit.assertNotNil(grid.columnGap)
luaunit.assertNotNil(grid.rowGap)
luaunit.assertTrue(grid.columnGap > 0)
luaunit.assertTrue(grid.rowGap > 0)
end
-- Run tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,718 +0,0 @@
-- Extended coverage tests for Element module
-- Focuses on uncovered paths like image loading, blur, animations, transforms, and edge cases
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local FlexLove = require("FlexLove")
FlexLove.init()
local Element = require("modules.Element")
local Color = require("modules.Color")
-- ============================================================================
-- Helper Functions
-- ============================================================================
local function createBasicElement(props)
props = props or {}
props.width = props.width or 100
props.height = props.height or 100
return Element.new(props)
end
-- ============================================================================
-- Image Loading and Callbacks
-- ============================================================================
TestElementImageLoading = {}
function TestElementImageLoading:test_image_loading_deferred_callback()
local callbackCalled = false
local element = createBasicElement({
image = "test.png",
onImageLoad = function(img)
callbackCalled = true
end,
})
-- Callback should be stored
luaunit.assertNotNil(element._imageLoadCallback)
-- Simulate image loaded
if element._imageLoadCallback then
element._imageLoadCallback({})
end
luaunit.assertTrue(callbackCalled)
end
function TestElementImageLoading:test_image_with_tint()
local element = createBasicElement({
image = "test.png",
})
local tintColor = Color.new(1, 0, 0, 1)
element:setImageTint(tintColor)
luaunit.assertEquals(element.imageTint, tintColor)
end
function TestElementImageLoading:test_image_with_opacity()
local element = createBasicElement({
image = "test.png",
})
element:setImageOpacity(0.5)
luaunit.assertEquals(element.imageOpacity, 0.5)
end
function TestElementImageLoading:test_image_with_repeat()
local element = createBasicElement({
image = "test.png",
})
element:setImageRepeat("repeat")
luaunit.assertEquals(element.imageRepeat, "repeat")
end
-- ============================================================================
-- Blur Instance Management
-- ============================================================================
TestElementBlur = {}
function TestElementBlur:test_getBlurInstance_no_blur()
local element = createBasicElement({})
local blur = element:getBlurInstance()
luaunit.assertNil(blur)
end
function TestElementBlur:test_getBlurInstance_with_blur()
local element = createBasicElement({
backdropBlur = 5,
})
-- Blur instance should be created when backdropBlur is set
local blur = element:getBlurInstance()
-- May be nil if Blur module isn't initialized, but shouldn't error
luaunit.assertTrue(blur == nil or type(blur) == "table")
end
-- ============================================================================
-- Element Update and Animations
-- ============================================================================
TestElementUpdate = {}
function TestElementUpdate:test_update_without_animations()
local element = createBasicElement({})
-- Should not error
element:update(0.016)
luaunit.assertTrue(true)
end
function TestElementUpdate:test_update_with_transition()
local element = createBasicElement({
opacity = 1,
})
element:setTransition("opacity", {
duration = 1.0,
easing = "linear",
})
-- Change opacity to trigger transition
element:setProperty("opacity", 0)
-- Update should process transition
element:update(0.5)
-- Opacity should be between 0 and 1
luaunit.assertTrue(element.opacity >= 0 and element.opacity <= 1)
end
function TestElementUpdate:test_countActiveAnimations()
local element = createBasicElement({})
local count = element:_countActiveAnimations()
luaunit.assertEquals(count, 0)
end
-- ============================================================================
-- Element Draw Method
-- ============================================================================
TestElementDraw = {}
function TestElementDraw:test_draw_basic_element()
local element = createBasicElement({
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Should not error
element:draw()
luaunit.assertTrue(true)
end
function TestElementDraw:test_draw_with_opacity_zero()
local element = createBasicElement({
backgroundColor = Color.new(1, 0, 0, 1),
opacity = 0,
})
-- Should not draw but not error
element:draw()
luaunit.assertTrue(true)
end
function TestElementDraw:test_draw_with_transform()
local element = createBasicElement({})
element:rotate(45)
element:scale(1.5, 1.5)
-- Should apply transforms
element:draw()
luaunit.assertTrue(true)
end
function TestElementDraw:test_draw_with_blur()
local element = createBasicElement({
backdropBlur = 5,
backgroundColor = Color.new(1, 1, 1, 0.5),
})
-- Should handle blur
element:draw()
luaunit.assertTrue(true)
end
-- ============================================================================
-- Element Resize
-- ============================================================================
TestElementResize = {}
function TestElementResize:test_resize_updates_dimensions()
local element = createBasicElement({
width = 100,
height = 100,
})
element:resize(200, 200)
luaunit.assertEquals(element.width, 200)
luaunit.assertEquals(element.height, 200)
end
function TestElementResize:test_resize_with_percentage_units()
local element = createBasicElement({
width = "50%",
height = "50%",
})
-- Should handle percentage units (recalculation)
element:resize(400, 400)
luaunit.assertTrue(true)
end
-- ============================================================================
-- Layout Children with Performance
-- ============================================================================
TestElementLayout = {}
function TestElementLayout:test_layoutChildren_empty()
local element = createBasicElement({})
-- Should not error with no children
element:layoutChildren()
luaunit.assertTrue(true)
end
function TestElementLayout:test_layoutChildren_with_children()
local parent = createBasicElement({
width = 200,
height = 200,
})
local child1 = createBasicElement({ width = 50, height = 50 })
local child2 = createBasicElement({ width = 50, height = 50 })
parent:addChild(child1)
parent:addChild(child2)
parent:layoutChildren()
-- Children should have positions
luaunit.assertNotNil(child1.x)
luaunit.assertNotNil(child2.x)
end
function TestElementLayout:test_checkPerformanceWarnings()
local parent = createBasicElement({})
-- Add many children to trigger warnings
for i = 1, 150 do
parent:addChild(createBasicElement({ width = 10, height = 10 }))
end
-- Should check performance
parent:_checkPerformanceWarnings()
luaunit.assertTrue(true)
end
-- ============================================================================
-- Absolute Positioning with CSS Offsets
-- ============================================================================
TestElementPositioning = {}
function TestElementPositioning:test_absolute_positioning_with_top_left()
local element = createBasicElement({
positioning = "absolute",
top = 10,
left = 20,
})
luaunit.assertEquals(element.positioning, "absolute")
luaunit.assertEquals(element.top, 10)
luaunit.assertEquals(element.left, 20)
end
function TestElementPositioning:test_absolute_positioning_with_bottom_right()
local element = createBasicElement({
positioning = "absolute",
bottom = 10,
right = 20,
})
luaunit.assertEquals(element.positioning, "absolute")
luaunit.assertEquals(element.bottom, 10)
luaunit.assertEquals(element.right, 20)
end
function TestElementPositioning:test_relative_positioning()
local element = createBasicElement({
positioning = "relative",
top = 10,
left = 10,
})
luaunit.assertEquals(element.positioning, "relative")
end
-- ============================================================================
-- Theme State Management
-- ============================================================================
TestElementTheme = {}
function TestElementTheme:test_element_with_hover_state()
local element = createBasicElement({
backgroundColor = Color.new(1, 0, 0, 1),
hover = {
backgroundColor = Color.new(0, 1, 0, 1),
},
})
luaunit.assertNotNil(element.hover)
luaunit.assertNotNil(element.hover.backgroundColor)
end
function TestElementTheme:test_element_with_active_state()
local element = createBasicElement({
backgroundColor = Color.new(1, 0, 0, 1),
active = {
backgroundColor = Color.new(0, 0, 1, 1),
},
})
luaunit.assertNotNil(element.active)
end
function TestElementTheme:test_element_with_disabled_state()
local element = createBasicElement({
disabled = true,
})
luaunit.assertTrue(element.disabled)
end
-- ============================================================================
-- Transform Application
-- ============================================================================
TestElementTransform = {}
function TestElementTransform:test_rotate_transform()
local element = createBasicElement({})
element:rotate(90)
luaunit.assertNotNil(element._transform)
luaunit.assertEquals(element._transform.rotation, 90)
end
function TestElementTransform:test_scale_transform()
local element = createBasicElement({})
element:scale(2, 2)
luaunit.assertNotNil(element._transform)
luaunit.assertEquals(element._transform.scaleX, 2)
luaunit.assertEquals(element._transform.scaleY, 2)
end
function TestElementTransform:test_translate_transform()
local element = createBasicElement({})
element:translate(10, 20)
luaunit.assertNotNil(element._transform)
luaunit.assertEquals(element._transform.translateX, 10)
luaunit.assertEquals(element._transform.translateY, 20)
end
function TestElementTransform:test_setTransformOrigin()
local element = createBasicElement({})
element:setTransformOrigin(0.5, 0.5)
luaunit.assertNotNil(element._transform)
luaunit.assertEquals(element._transform.originX, 0.5)
luaunit.assertEquals(element._transform.originY, 0.5)
end
function TestElementTransform:test_combined_transforms()
local element = createBasicElement({})
element:rotate(45)
element:scale(1.5, 1.5)
element:translate(10, 10)
luaunit.assertEquals(element._transform.rotation, 45)
luaunit.assertEquals(element._transform.scaleX, 1.5)
luaunit.assertEquals(element._transform.translateX, 10)
end
-- ============================================================================
-- Grid Layout
-- ============================================================================
TestElementGrid = {}
function TestElementGrid:test_grid_layout()
local element = createBasicElement({
display = "grid",
gridTemplateColumns = "1fr 1fr",
gridTemplateRows = "auto auto",
})
luaunit.assertEquals(element.display, "grid")
luaunit.assertNotNil(element.gridTemplateColumns)
end
function TestElementGrid:test_grid_gap()
local element = createBasicElement({
display = "grid",
gridGap = 10,
})
luaunit.assertEquals(element.gridGap, 10)
end
-- ============================================================================
-- Editable Element Text Operations
-- ============================================================================
TestElementTextOps = {}
function TestElementTextOps:test_insertText()
local element = createBasicElement({
editable = true,
text = "Hello",
})
element:insertText(" World", 5)
luaunit.assertEquals(element:getText(), "Hello World")
end
function TestElementTextOps:test_deleteText()
local element = createBasicElement({
editable = true,
text = "Hello World",
})
element:deleteText(5, 11)
luaunit.assertEquals(element:getText(), "Hello")
end
function TestElementTextOps:test_replaceText()
local element = createBasicElement({
editable = true,
text = "Hello World",
})
element:replaceText(6, 11, "Lua")
luaunit.assertEquals(element:getText(), "Hello Lua")
end
function TestElementTextOps:test_getText_non_editable()
local element = createBasicElement({
text = "Test",
})
luaunit.assertEquals(element:getText(), "Test")
end
-- ============================================================================
-- Focus Management
-- ============================================================================
TestElementFocus = {}
function TestElementFocus:test_focus_non_editable()
local element = createBasicElement({})
element:focus()
-- Should not create editor for non-editable element
luaunit.assertNil(element._textEditor)
end
function TestElementFocus:test_focus_editable()
local element = createBasicElement({
editable = true,
text = "Test",
})
element:focus()
-- Should create editor
luaunit.assertNotNil(element._textEditor)
luaunit.assertTrue(element:isFocused())
end
function TestElementFocus:test_blur()
local element = createBasicElement({
editable = true,
text = "Test",
})
element:focus()
element:blur()
luaunit.assertFalse(element:isFocused())
end
-- ============================================================================
-- Hierarchy Methods
-- ============================================================================
TestElementHierarchy = {}
function TestElementHierarchy:test_getHierarchyDepth_root()
local element = createBasicElement({})
local depth = element:getHierarchyDepth()
luaunit.assertEquals(depth, 0)
end
function TestElementHierarchy:test_getHierarchyDepth_nested()
local root = createBasicElement({})
local child = createBasicElement({})
local grandchild = createBasicElement({})
root:addChild(child)
child:addChild(grandchild)
luaunit.assertEquals(grandchild:getHierarchyDepth(), 2)
end
function TestElementHierarchy:test_countElements()
local root = createBasicElement({})
local child1 = createBasicElement({})
local child2 = createBasicElement({})
root:addChild(child1)
root:addChild(child2)
local count = root:countElements()
luaunit.assertEquals(count, 3) -- root + 2 children
end
-- ============================================================================
-- Scroll Methods Edge Cases
-- ============================================================================
TestElementScrollEdgeCases = {}
function TestElementScrollEdgeCases:test_scrollBy_non_scrollable()
local element = createBasicElement({})
-- Should not error
element:scrollBy(10, 10)
luaunit.assertTrue(true)
end
function TestElementScrollEdgeCases:test_getScrollPosition_no_scroll()
local element = createBasicElement({})
local x, y = element:getScrollPosition()
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0)
end
function TestElementScrollEdgeCases:test_hasOverflow_no_overflow()
local element = createBasicElement({
width = 100,
height = 100,
})
local hasX, hasY = element:hasOverflow()
luaunit.assertFalse(hasX)
luaunit.assertFalse(hasY)
end
function TestElementScrollEdgeCases:test_getContentSize()
local element = createBasicElement({})
local w, h = element:getContentSize()
luaunit.assertNotNil(w)
luaunit.assertNotNil(h)
end
-- ============================================================================
-- Child Management Edge Cases
-- ============================================================================
TestElementChildManagement = {}
function TestElementChildManagement:test_addChild_nil()
local element = createBasicElement({})
-- Should not error or should handle gracefully
pcall(function()
element:addChild(nil)
end)
luaunit.assertTrue(true)
end
function TestElementChildManagement:test_removeChild_not_found()
local parent = createBasicElement({})
local child = createBasicElement({})
-- Removing child that was never added
parent:removeChild(child)
luaunit.assertTrue(true)
end
function TestElementChildManagement:test_clearChildren_empty()
local element = createBasicElement({})
element:clearChildren()
luaunit.assertEquals(element:getChildCount(), 0)
end
function TestElementChildManagement:test_getChildCount()
local parent = createBasicElement({})
luaunit.assertEquals(parent:getChildCount(), 0)
parent:addChild(createBasicElement({}))
parent:addChild(createBasicElement({}))
luaunit.assertEquals(parent:getChildCount(), 2)
end
-- ============================================================================
-- Property Setting
-- ============================================================================
TestElementProperty = {}
function TestElementProperty:test_setProperty_valid()
local element = createBasicElement({})
element:setProperty("opacity", 0.5)
luaunit.assertEquals(element.opacity, 0.5)
end
function TestElementProperty:test_setProperty_with_transition()
local element = createBasicElement({
opacity = 1,
})
element:setTransition("opacity", { duration = 1.0 })
element:setProperty("opacity", 0)
-- Transition should be created
luaunit.assertNotNil(element._transitions)
end
-- ============================================================================
-- Transition Management
-- ============================================================================
TestElementTransitions = {}
function TestElementTransitions:test_removeTransition()
local element = createBasicElement({
opacity = 1,
})
element:setTransition("opacity", { duration = 1.0 })
element:removeTransition("opacity")
-- Transition should be removed
luaunit.assertTrue(true)
end
function TestElementTransitions:test_setTransitionGroup()
local element = createBasicElement({})
element:setTransitionGroup("fade", { duration = 1.0 }, { "opacity", "scale" })
luaunit.assertTrue(true)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,196 +0,0 @@
-- Test font cache optimizations
package.path = package.path .. ";./?.lua;./modules/?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
-- Set up stub before requiring modules
_G.love = loveStub
local utils = require("modules.utils")
TestFontCache = {}
function TestFontCache:setUp()
utils.clearFontCache()
utils.resetFontCacheStats()
love.timer.setTime(0) -- Reset timer for consistent timestamps
end
function TestFontCache:tearDown()
utils.clearFontCache()
utils.resetFontCacheStats()
utils.setFontCacheSize(50) -- Reset to default
end
function TestFontCache:testCacheHitOnRepeatedAccess()
-- First access should be a miss
utils.FONT_CACHE.get(16, nil)
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.misses, 1)
luaunit.assertEquals(stats1.hits, 0)
-- Second access should be a hit
utils.FONT_CACHE.get(16, nil)
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.hits, 1)
luaunit.assertEquals(stats2.misses, 1)
-- Third access should also be a hit
utils.FONT_CACHE.get(16, nil)
local stats3 = utils.getFontCacheStats()
luaunit.assertEquals(stats3.hits, 2)
luaunit.assertEquals(stats3.misses, 1)
end
function TestFontCache:testCacheMissOnFirstAccess()
utils.clearFontCache()
utils.resetFontCacheStats()
utils.FONT_CACHE.get(24, nil)
local stats = utils.getFontCacheStats()
luaunit.assertEquals(stats.misses, 1)
luaunit.assertEquals(stats.hits, 0)
end
function TestFontCache:testLRUEviction()
utils.setFontCacheSize(3)
-- Load 3 fonts (fills cache) with time steps to ensure different timestamps
utils.FONT_CACHE.get(10, nil)
love.timer.step(0.001)
utils.FONT_CACHE.get(12, nil)
love.timer.step(0.001)
utils.FONT_CACHE.get(14, nil)
love.timer.step(0.001)
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.size, 3)
luaunit.assertEquals(stats1.evictions, 0)
-- Load 4th font (triggers eviction of font 10 - the oldest)
utils.FONT_CACHE.get(16, nil)
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.size, 3)
luaunit.assertEquals(stats2.evictions, 1)
-- Access first font again - it should have been evicted (miss)
local initialMisses = stats2.misses
utils.FONT_CACHE.get(10, nil)
local stats3 = utils.getFontCacheStats()
luaunit.assertEquals(stats3.misses, initialMisses + 1) -- Should be a miss
end
function TestFontCache:testCacheSizeLimitEnforced()
utils.setFontCacheSize(5)
-- Load 10 fonts
for i = 1, 10 do
utils.FONT_CACHE.get(10 + i, nil)
end
local stats = utils.getFontCacheStats()
luaunit.assertEquals(stats.size, 5)
luaunit.assertTrue(stats.evictions >= 5)
end
function TestFontCache:testFontRounding()
-- Sizes should be rounded: 14.5 and 14.7 should map to same cache entry (15)
utils.FONT_CACHE.get(14.5, nil)
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.misses, 1)
utils.FONT_CACHE.get(14.7, nil)
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.hits, 1) -- Should be a hit because both round to 15
luaunit.assertEquals(stats2.misses, 1)
end
function TestFontCache:testCacheClear()
utils.FONT_CACHE.get(16, nil)
utils.FONT_CACHE.get(18, nil)
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.size, 2)
utils.clearFontCache()
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.size, 0)
end
function TestFontCache:testCacheKeyWithPath()
-- Different cache keys for same size, different paths
utils.FONT_CACHE.get(16, nil)
utils.FONT_CACHE.get(16, "fonts/custom.ttf")
local stats = utils.getFontCacheStats()
luaunit.assertEquals(stats.misses, 2) -- Both should be misses
luaunit.assertEquals(stats.size, 2)
end
function TestFontCache:testPreloadFont()
utils.clearFontCache()
utils.resetFontCacheStats()
-- Preload multiple sizes
utils.preloadFont(nil, { 12, 14, 16, 18 })
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.size, 4)
luaunit.assertEquals(stats1.misses, 4) -- All preloads are misses
-- Now access one - should be a hit
utils.FONT_CACHE.get(16, nil)
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.hits, 1)
end
function TestFontCache:testCacheHitRate()
utils.clearFontCache()
utils.resetFontCacheStats()
-- 1 miss, 9 hits = 90% hit rate
utils.FONT_CACHE.get(16, nil)
for i = 1, 9 do
utils.FONT_CACHE.get(16, nil)
end
local stats = utils.getFontCacheStats()
luaunit.assertEquals(stats.hitRate, 0.9)
end
function TestFontCache:testSetCacheSizeEvictsExcess()
utils.setFontCacheSize(10)
-- Load 10 fonts
for i = 1, 10 do
utils.FONT_CACHE.get(10 + i, nil)
end
local stats1 = utils.getFontCacheStats()
luaunit.assertEquals(stats1.size, 10)
-- Reduce cache size - should trigger evictions
utils.setFontCacheSize(5)
local stats2 = utils.getFontCacheStats()
luaunit.assertEquals(stats2.size, 5)
luaunit.assertTrue(stats2.evictions >= 5)
end
function TestFontCache:testMinimalCacheSize()
-- Minimum cache size is 1
utils.setFontCacheSize(0)
utils.FONT_CACHE.get(16, nil)
local stats = utils.getFontCacheStats()
luaunit.assertEquals(stats.size, 1)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,3 +1,6 @@
-- ImageRenderer Comprehensive Test Suite
-- Tests for ImageRenderer functionality including fit modes, positioning, tiling, and edge cases
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
@@ -6,15 +9,18 @@ ErrorHandler.init({})
require("testing.loveStub")
local ImageRenderer = require("modules.ImageRenderer")
local ErrorHandler = require("modules.ErrorHandler")
local Color = require("modules.Color")
local utils = require("modules.utils")
-- Initialize ErrorHandler
ErrorHandler.init({})
-- Initialize ImageRenderer with dependencies
ImageRenderer.init({ ErrorHandler = ErrorHandler, utils = utils })
TestImageRenderer = {}
-- ============================================================================
-- Test Suite 1: calculateFit - Input Validation
-- ============================================================================
TestImageRendererInputValidation = {}
function TestImageRenderer:setUp()
-- Create a mock image for testing
function TestImageRendererInputValidation:setUp()
self.mockImage = {
getDimensions = function()
return 100, 100
@@ -22,57 +28,55 @@ function TestImageRenderer:setUp()
}
end
-- Unhappy path tests for calculateFit
function TestImageRenderer:testCalculateFitWithZeroImageWidth()
function TestImageRendererInputValidation:testCalculateFitWithZeroImageWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(0, 100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroImageHeight()
function TestImageRendererInputValidation:testCalculateFitWithZeroImageHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 0, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeImageWidth()
function TestImageRendererInputValidation:testCalculateFitWithNegativeImageWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(-100, 100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeImageHeight()
function TestImageRendererInputValidation:testCalculateFitWithNegativeImageHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, -100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroBoundsWidth()
function TestImageRendererInputValidation:testCalculateFitWithZeroBoundsWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 0, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroBoundsHeight()
function TestImageRendererInputValidation:testCalculateFitWithZeroBoundsHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 200, 0, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeBoundsWidth()
function TestImageRendererInputValidation:testCalculateFitWithNegativeBoundsWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, -200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeBoundsHeight()
function TestImageRendererInputValidation:testCalculateFitWithNegativeBoundsHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 200, -200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithInvalidFitMode()
function TestImageRendererInputValidation:testCalculateFitWithInvalidFitMode()
-- Now uses 'fill' fallback with warning instead of error
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "invalid-mode")
luaunit.assertNotNil(result)
@@ -81,7 +85,7 @@ function TestImageRenderer:testCalculateFitWithInvalidFitMode()
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitWithNilFitMode()
function TestImageRendererInputValidation:testCalculateFitWithNilFitMode()
-- Should default to "fill"
local result = ImageRenderer.calculateFit(100, 100, 200, 200, nil)
luaunit.assertNotNil(result)
@@ -89,157 +93,68 @@ function TestImageRenderer:testCalculateFitWithNilFitMode()
luaunit.assertEquals(result.dh, 200)
end
function TestImageRenderer:testCalculateFitFillMode()
-- ============================================================================
-- Test Suite 2: calculateFit - Fit Modes
-- ============================================================================
TestImageRendererFitModes = {}
function TestImageRendererFitModes:testCalculateFitFillMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "fill")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitContainMode()
function TestImageRendererFitModes:testCalculateFitContainMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "contain")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitCoverMode()
function TestImageRendererFitModes:testCalculateFitCoverMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "cover")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitNoneMode()
function TestImageRendererFitModes:testCalculateFitNoneMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "none")
luaunit.assertEquals(result.scaleX, 1)
luaunit.assertEquals(result.scaleY, 1)
end
function TestImageRenderer:testCalculateFitScaleDownModeWithLargeImage()
function TestImageRendererFitModes:testCalculateFitScaleDownModeWithLargeImage()
local result = ImageRenderer.calculateFit(300, 300, 200, 200, "scale-down")
-- Should behave like contain for larger images
luaunit.assertNotNil(result)
end
function TestImageRenderer:testCalculateFitScaleDownModeWithSmallImage()
function TestImageRendererFitModes:testCalculateFitScaleDownModeWithSmallImage()
local result = ImageRenderer.calculateFit(50, 50, 200, 200, "scale-down")
-- Should behave like none for smaller images
luaunit.assertEquals(result.scaleX, 1)
luaunit.assertEquals(result.scaleY, 1)
end
-- Unhappy path tests for _parsePosition
-- ============================================================================
-- Test Suite 3: calculateFit - Edge Cases
-- ============================================================================
TestImageRendererEdgeCases = {}
function TestImageRenderer:testParsePositionWithNil()
local x, y = ImageRenderer._parsePosition(nil)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithEmptyString()
local x, y = ImageRenderer._parsePosition("")
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithInvalidType()
local x, y = ImageRenderer._parsePosition(123)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithInvalidKeyword()
local x, y = ImageRenderer._parsePosition("invalid keyword")
-- Should default to center
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithMixedValid()
local x, y = ImageRenderer._parsePosition("left top")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0)
end
function TestImageRenderer:testParsePositionWithPercentage()
local x, y = ImageRenderer._parsePosition("75% 25%")
luaunit.assertAlmostEquals(x, 0.75, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
function TestImageRenderer:testParsePositionWithOutOfRangePercentage()
local x, y = ImageRenderer._parsePosition("150% -50%")
-- 150% clamps to 1, but -50% doesn't match pattern so defaults to 0.5
luaunit.assertEquals(x, 1)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithSingleValue()
local x, y = ImageRenderer._parsePosition("left")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0.5) -- Should use center for Y
end
function TestImageRenderer:testParsePositionWithSinglePercentage()
local x, y = ImageRenderer._parsePosition("25%")
luaunit.assertAlmostEquals(x, 0.25, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
-- Unhappy path tests for draw
function TestImageRenderer:testDrawWithNilImage()
-- Should not crash, just return early
ImageRenderer.draw(nil, 0, 0, 100, 100, "fill")
-- If we get here without error, test passes
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithZeroWidth()
-- Should error in calculateFit
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 0, 100, "fill")
end)
end
function TestImageRenderer:testDrawWithZeroHeight()
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 100, 0, "fill")
end)
end
function TestImageRenderer:testDrawWithNegativeOpacity()
-- Should work but render with negative opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", -0.5)
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithOpacityGreaterThanOne()
-- Should work but render with >1 opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", 2.0)
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithInvalidFitMode()
-- 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()
function TestImageRendererEdgeCases:testCalculateFitWithVerySmallBounds()
local result = ImageRenderer.calculateFit(1000, 1000, 1, 1, "contain")
luaunit.assertNotNil(result)
-- Scale should be very small
luaunit.assertTrue(result.scaleX < 0.01)
end
function TestImageRenderer:testCalculateFitWithVeryLargeBounds()
function TestImageRendererEdgeCases:testCalculateFitWithVeryLargeBounds()
local result = ImageRenderer.calculateFit(10, 10, 10000, 10000, "contain")
luaunit.assertNotNil(result)
-- Scale should be very large
luaunit.assertTrue(result.scaleX > 100)
end
function TestImageRenderer:testCalculateFitWithAspectRatioMismatch()
function TestImageRendererEdgeCases:testCalculateFitWithAspectRatioMismatch()
-- Wide image, tall bounds
local result = ImageRenderer.calculateFit(200, 100, 100, 200, "contain")
luaunit.assertNotNil(result)
@@ -247,13 +162,445 @@ function TestImageRenderer:testCalculateFitWithAspectRatioMismatch()
luaunit.assertEquals(result.scaleX, result.scaleY)
end
function TestImageRenderer:testCalculateFitCoverWithAspectRatioMismatch()
function TestImageRendererEdgeCases:testCalculateFitCoverWithAspectRatioMismatch()
-- Wide image, tall bounds
local result = ImageRenderer.calculateFit(200, 100, 100, 200, "cover")
luaunit.assertNotNil(result)
luaunit.assertEquals(result.scaleX, result.scaleY)
end
-- ============================================================================
-- Test Suite 4: Position Parsing
-- ============================================================================
TestImageRendererPositionParsing = {}
function TestImageRendererPositionParsing:testParsePositionWithNil()
local x, y = ImageRenderer._parsePosition(nil)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRendererPositionParsing:testParsePositionWithEmptyString()
local x, y = ImageRenderer._parsePosition("")
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRendererPositionParsing:testParsePositionWithInvalidType()
local x, y = ImageRenderer._parsePosition(123)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRendererPositionParsing:testParsePositionWithInvalidKeyword()
local x, y = ImageRenderer._parsePosition("invalid keyword")
-- Should default to center
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRendererPositionParsing:testParsePositionWithMixedValid()
local x, y = ImageRenderer._parsePosition("left top")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0)
end
function TestImageRendererPositionParsing:testParsePositionWithPercentage()
local x, y = ImageRenderer._parsePosition("75% 25%")
luaunit.assertAlmostEquals(x, 0.75, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
function TestImageRendererPositionParsing:testParsePositionWithOutOfRangePercentage()
local x, y = ImageRenderer._parsePosition("150% -50%")
-- 150% clamps to 1, but -50% doesn't match pattern so defaults to 0.5
luaunit.assertEquals(x, 1)
luaunit.assertEquals(y, 0.5)
end
function TestImageRendererPositionParsing:testParsePositionWithSingleValue()
local x, y = ImageRenderer._parsePosition("left")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0.5) -- Should use center for Y
end
function TestImageRendererPositionParsing:testParsePositionWithSinglePercentage()
local x, y = ImageRenderer._parsePosition("25%")
luaunit.assertAlmostEquals(x, 0.25, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
-- ============================================================================
-- Test Suite 5: Draw Function
-- ============================================================================
TestImageRendererDraw = {}
function TestImageRendererDraw:setUp()
self.mockImage = {
getDimensions = function()
return 100, 100
end,
}
end
function TestImageRendererDraw:testDrawWithNilImage()
-- Should not crash, just return early
ImageRenderer.draw(nil, 0, 0, 100, 100, "fill")
-- If we get here without error, test passes
luaunit.assertTrue(true)
end
function TestImageRendererDraw:testDrawWithZeroWidth()
-- Should error in calculateFit
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 0, 100, "fill")
end)
end
function TestImageRendererDraw:testDrawWithZeroHeight()
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 100, 0, "fill")
end)
end
function TestImageRendererDraw:testDrawWithNegativeOpacity()
-- Should work but render with negative opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", -0.5)
luaunit.assertTrue(true)
end
function TestImageRendererDraw:testDrawWithOpacityGreaterThanOne()
-- Should work but render with >1 opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", 2.0)
luaunit.assertTrue(true)
end
function TestImageRendererDraw:testDrawWithInvalidFitMode()
-- 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
-- ============================================================================
-- Test Suite 6: Tiling - Basic Modes
-- ============================================================================
TestImageRendererTiling = {}
function TestImageRendererTiling:setUp()
self.mockImage = {
getDimensions = function()
return 64, 64
end,
type = function()
return "Image"
end,
}
end
function TestImageRendererTiling:tearDown()
self.mockImage = nil
end
function TestImageRendererTiling:testDrawTiledNoRepeat()
-- Test no-repeat mode (single image)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, nil)
-- Should draw once
luaunit.assertEquals(#drawCalls, 1)
luaunit.assertEquals(drawCalls[1][1], self.mockImage)
luaunit.assertEquals(drawCalls[1][2], 100)
luaunit.assertEquals(drawCalls[1][3], 100)
love.graphics.draw = originalDraw
end
function TestImageRendererTiling:testDrawTiledRepeat()
-- Test repeat mode (tiles in both directions)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 200x200
-- Should tile 4 times (4 tiles total: 2x2 with partials)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "repeat", 1, nil)
-- 4 tiles: (0,0), (64,0), (0,64), (64,64)
-- 2 full tiles + 2 partial tiles = 4 draws
luaunit.assertTrue(#drawCalls >= 4)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageRendererTiling:testDrawTiledRepeatX()
-- Test repeat-x mode (tiles horizontally only)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 200x64
-- Should tile 4 times horizontally: (0), (64), (128), (192)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 64, "repeat-x", 1, nil)
-- 3 full tiles + 1 partial tile = 4 draws
luaunit.assertTrue(#drawCalls >= 3)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageRendererTiling:testDrawTiledRepeatY()
-- Test repeat-y mode (tiles vertically only)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 64x200
-- Should tile 4 times vertically
ImageRenderer.drawTiled(self.mockImage, 100, 100, 64, 200, "repeat-y", 1, nil)
-- 3 full tiles + 1 partial tile = 4 draws
luaunit.assertTrue(#drawCalls >= 3)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageRendererTiling:testDrawTiledSpace()
-- Test space mode (distributes tiles with even spacing)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
-- Image is 64x64, bounds are 200x200
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "space", 1, nil)
-- Should draw multiple tiles with spacing
luaunit.assertTrue(#drawCalls > 1)
love.graphics.draw = originalDraw
end
function TestImageRendererTiling:testDrawTiledRound()
-- Test round mode (scales tiles to fit exactly)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
-- Image is 64x64, bounds are 200x200
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "round", 1, nil)
-- Should draw tiles with scaling
luaunit.assertTrue(#drawCalls > 1)
love.graphics.draw = originalDraw
end
-- ============================================================================
-- Test Suite 7: Tiling - Opacity and Tint
-- ============================================================================
TestImageRendererTilingEffects = {}
function TestImageRendererTilingEffects:setUp()
self.mockImage = {
getDimensions = function()
return 64, 64
end,
type = function()
return "Image"
end,
}
end
function TestImageRendererTilingEffects:tearDown()
self.mockImage = nil
end
function TestImageRendererTilingEffects:testDrawTiledWithOpacity()
-- Test tiling with opacity
local setColorCalls = {}
local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...)
table.insert(setColorCalls, { ... })
end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 0.5, nil)
-- Should set color with opacity
luaunit.assertTrue(#setColorCalls > 0)
-- Check that opacity 0.5 was used
local found = false
for _, call in ipairs(setColorCalls) do
if call[4] == 0.5 then
found = true
break
end
end
luaunit.assertTrue(found)
love.graphics.setColor = originalSetColor
end
function TestImageRendererTilingEffects:testDrawTiledWithTint()
-- Test tiling with tint color
local setColorCalls = {}
local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...)
table.insert(setColorCalls, { ... })
end
local redTint = Color.new(1, 0, 0, 1)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, redTint)
-- Should set color with tint
luaunit.assertTrue(#setColorCalls > 0)
-- Check that red tint was used (r=1, g=0, b=0)
local found = false
for _, call in ipairs(setColorCalls) do
if call[1] == 1 and call[2] == 0 and call[3] == 0 then
found = true
break
end
end
luaunit.assertTrue(found)
love.graphics.setColor = originalSetColor
end
-- ============================================================================
-- Test Suite 8: Element Integration
-- ============================================================================
TestImageRendererElementIntegration = {}
function TestImageRendererElementIntegration:setUp()
local Element = require("modules.Element")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
self.deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
self.Element = Element
end
function TestImageRendererElementIntegration:testElementImageRepeatProperty()
-- Test that Element accepts imageRepeat property
local element = self.Element.new({
width = 200,
height = 200,
imageRepeat = "repeat",
}, self.deps)
luaunit.assertEquals(element.imageRepeat, "repeat")
end
function TestImageRendererElementIntegration:testElementImageRepeatDefault()
-- Test that imageRepeat defaults to "no-repeat"
local element = self.Element.new({
width = 200,
height = 200,
}, self.deps)
luaunit.assertEquals(element.imageRepeat, "no-repeat")
end
function TestImageRendererElementIntegration:testElementSetImageRepeat()
-- Test setImageRepeat method
local element = self.Element.new({
width = 200,
height = 200,
}, self.deps)
element:setImageRepeat("repeat-x")
luaunit.assertEquals(element.imageRepeat, "repeat-x")
end
function TestImageRendererElementIntegration:testElementImageTintProperty()
-- Test that Element accepts imageTint property
local redTint = Color.new(1, 0, 0, 1)
local element = self.Element.new({
width = 200,
height = 200,
imageTint = redTint,
}, self.deps)
luaunit.assertEquals(element.imageTint, redTint)
end
function TestImageRendererElementIntegration:testElementSetImageTint()
-- Test setImageTint method
local element = self.Element.new({
width = 200,
height = 200,
}, self.deps)
local blueTint = Color.new(0, 0, 1, 1)
element:setImageTint(blueTint)
luaunit.assertEquals(element.imageTint, blueTint)
end
function TestImageRendererElementIntegration:testElementSetImageOpacity()
-- Test setImageOpacity method
local element = self.Element.new({
width = 200,
height = 200,
}, self.deps)
element:setImageOpacity(0.7)
luaunit.assertEquals(element.imageOpacity, 0.7)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,411 +0,0 @@
-- Image Tiling Tests
-- Tests for ImageRenderer tiling functionality
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local ImageRenderer = require("modules.ImageRenderer")
local ErrorHandler = require("modules.ErrorHandler")
local Color = require("modules.Color")
local utils = require("modules.utils")
-- Initialize ImageRenderer with ErrorHandler and utils
ImageRenderer.init({ ErrorHandler = ErrorHandler, utils = utils })
TestImageTiling = {}
function TestImageTiling:setUp()
-- Create a mock image
self.mockImage = {
getDimensions = function()
return 64, 64
end,
type = function()
return "Image"
end,
}
end
function TestImageTiling:tearDown()
self.mockImage = nil
end
function TestImageTiling:testDrawTiledNoRepeat()
-- Test no-repeat mode (single image)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, nil)
-- Should draw once
luaunit.assertEquals(#drawCalls, 1)
luaunit.assertEquals(drawCalls[1][1], self.mockImage)
luaunit.assertEquals(drawCalls[1][2], 100)
luaunit.assertEquals(drawCalls[1][3], 100)
love.graphics.draw = originalDraw
end
function TestImageTiling:testDrawTiledRepeat()
-- Test repeat mode (tiles in both directions)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 200x200
-- Should tile 4 times (4 tiles total: 2x2 with partials)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "repeat", 1, nil)
-- 4 tiles: (0,0), (64,0), (0,64), (64,64)
-- 2 full tiles + 2 partial tiles = 4 draws
luaunit.assertTrue(#drawCalls >= 4)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageTiling:testDrawTiledRepeatX()
-- Test repeat-x mode (tiles horizontally only)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 200x64
-- Should tile 4 times horizontally: (0), (64), (128), (192)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 64, "repeat-x", 1, nil)
-- 3 full tiles + 1 partial tile = 4 draws
luaunit.assertTrue(#drawCalls >= 3)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageTiling:testDrawTiledRepeatY()
-- Test repeat-y mode (tiles vertically only)
local drawCalls = {}
local originalDraw = love.graphics.draw
local originalNewQuad = love.graphics.newQuad
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
love.graphics.newQuad = function(...)
return { type = "quad", ... }
end
-- Image is 64x64, bounds are 64x200
-- Should tile 4 times vertically
ImageRenderer.drawTiled(self.mockImage, 100, 100, 64, 200, "repeat-y", 1, nil)
-- 3 full tiles + 1 partial tile = 4 draws
luaunit.assertTrue(#drawCalls >= 3)
love.graphics.draw = originalDraw
love.graphics.newQuad = originalNewQuad
end
function TestImageTiling:testDrawTiledSpace()
-- Test space mode (distributes tiles with even spacing)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
-- Image is 64x64, bounds are 200x200
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "space", 1, nil)
-- Should draw multiple tiles with spacing
luaunit.assertTrue(#drawCalls > 1)
love.graphics.draw = originalDraw
end
function TestImageTiling:testDrawTiledRound()
-- Test round mode (scales tiles to fit exactly)
local drawCalls = {}
local originalDraw = love.graphics.draw
love.graphics.draw = function(...)
table.insert(drawCalls, { ... })
end
-- Image is 64x64, bounds are 200x200
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "round", 1, nil)
-- Should draw tiles with scaling
luaunit.assertTrue(#drawCalls > 1)
love.graphics.draw = originalDraw
end
function TestImageTiling:testDrawTiledWithOpacity()
-- Test tiling with opacity
local setColorCalls = {}
local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...)
table.insert(setColorCalls, { ... })
end
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 0.5, nil)
-- Should set color with opacity
luaunit.assertTrue(#setColorCalls > 0)
-- Check that opacity 0.5 was used
local found = false
for _, call in ipairs(setColorCalls) do
if call[4] == 0.5 then
found = true
break
end
end
luaunit.assertTrue(found)
love.graphics.setColor = originalSetColor
end
function TestImageTiling:testDrawTiledWithTint()
-- Test tiling with tint color
local setColorCalls = {}
local originalSetColor = love.graphics.setColor
love.graphics.setColor = function(...)
table.insert(setColorCalls, { ... })
end
local redTint = Color.new(1, 0, 0, 1)
ImageRenderer.drawTiled(self.mockImage, 100, 100, 200, 200, "no-repeat", 1, redTint)
-- Should set color with tint
luaunit.assertTrue(#setColorCalls > 0)
-- Check that red tint was used (r=1, g=0, b=0)
local found = false
for _, call in ipairs(setColorCalls) do
if call[1] == 1 and call[2] == 0 and call[3] == 0 then
found = true
break
end
end
luaunit.assertTrue(found)
love.graphics.setColor = originalSetColor
end
function TestImageTiling:testElementImageRepeatProperty()
-- Test that Element accepts imageRepeat property
local Element = require("modules.Element")
local utils = require("modules.utils")
local Color = require("modules.Color")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
imageRepeat = "repeat",
}, deps)
luaunit.assertEquals(element.imageRepeat, "repeat")
end
function TestImageTiling:testElementImageRepeatDefault()
-- Test that imageRepeat defaults to "no-repeat"
local Element = require("modules.Element")
local utils = require("modules.utils")
local Color = require("modules.Color")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
}, deps)
luaunit.assertEquals(element.imageRepeat, "no-repeat")
end
function TestImageTiling:testElementSetImageRepeat()
-- Test setImageRepeat method
local Element = require("modules.Element")
local utils = require("modules.utils")
local Color = require("modules.Color")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
}, deps)
element:setImageRepeat("repeat-x")
luaunit.assertEquals(element.imageRepeat, "repeat-x")
end
function TestImageTiling:testElementImageTintProperty()
-- Test that Element accepts imageTint property
local Element = require("modules.Element")
local utils = require("modules.utils")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local redTint = Color.new(1, 0, 0, 1)
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
imageTint = redTint,
}, deps)
luaunit.assertEquals(element.imageTint, redTint)
end
function TestImageTiling:testElementSetImageTint()
-- Test setImageTint method
local Element = require("modules.Element")
local utils = require("modules.utils")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
}, deps)
local blueTint = Color.new(0, 0, 1, 1)
element:setImageTint(blueTint)
luaunit.assertEquals(element.imageTint, blueTint)
end
function TestImageTiling:testElementSetImageOpacity()
-- Test setImageOpacity method
local Element = require("modules.Element")
local utils = require("modules.utils")
local Color = require("modules.Color")
local Units = require("modules.Units")
local LayoutEngine = require("modules.LayoutEngine")
local Renderer = require("modules.Renderer")
local EventHandler = require("modules.EventHandler")
local ImageCache = require("modules.ImageCache")
local deps = {
utils = utils,
Color = Color,
Units = Units,
LayoutEngine = LayoutEngine,
Renderer = Renderer,
EventHandler = EventHandler,
ImageCache = ImageCache,
ImageRenderer = ImageRenderer,
ErrorHandler = ErrorHandler,
}
local element = Element.new({
width = 200,
height = 200,
}, deps)
element:setImageOpacity(0.7)
luaunit.assertEquals(element.imageOpacity, 0.7)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,364 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local Animation = require("modules.Animation")
local Easing = Animation.Easing
local ErrorHandler = require("modules.ErrorHandler")
local Color = require("modules.Color")
-- Initialize modules
ErrorHandler.init({})
Animation.init({ ErrorHandler = ErrorHandler, Color = Color })
TestKeyframeAnimation = {}
function TestKeyframeAnimation:setUp()
-- Reset state before each test
end
-- Test basic keyframe animation creation
function TestKeyframeAnimation:testCreateKeyframeAnimation()
local anim = Animation.keyframes({
duration = 2,
keyframes = {
{ at = 0, values = { x = 0, opacity = 0 } },
{ at = 1, values = { x = 100, opacity = 1 } },
},
})
luaunit.assertNotNil(anim)
luaunit.assertEquals(type(anim), "table")
luaunit.assertEquals(anim.duration, 2)
luaunit.assertNotNil(anim.keyframes)
luaunit.assertEquals(#anim.keyframes, 2)
end
-- Test keyframe animation with multiple waypoints
function TestKeyframeAnimation:testMultipleWaypoints()
local anim = Animation.keyframes({
duration = 3,
keyframes = {
{ at = 0, values = { x = 0, opacity = 0 } },
{ at = 0.25, values = { x = 50, opacity = 1 } },
{ at = 0.75, values = { x = 150, opacity = 1 } },
{ at = 1, values = { x = 200, opacity = 0 } },
},
})
luaunit.assertEquals(#anim.keyframes, 4)
luaunit.assertEquals(anim.keyframes[1].at, 0)
luaunit.assertEquals(anim.keyframes[2].at, 0.25)
luaunit.assertEquals(anim.keyframes[3].at, 0.75)
luaunit.assertEquals(anim.keyframes[4].at, 1)
end
-- Test keyframe sorting
function TestKeyframeAnimation:testKeyframeSorting()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 1, values = { x = 100 } },
{ at = 0, values = { x = 0 } },
{ at = 0.5, values = { x = 50 } },
},
})
-- Should be sorted by 'at' position
luaunit.assertEquals(anim.keyframes[1].at, 0)
luaunit.assertEquals(anim.keyframes[2].at, 0.5)
luaunit.assertEquals(anim.keyframes[3].at, 1)
end
-- Test keyframe interpolation at start
function TestKeyframeAnimation:testInterpolationAtStart()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0, opacity = 0 } },
{ at = 1, values = { x = 100, opacity = 1 } },
},
})
anim.elapsed = 0
local result = anim:interpolate()
luaunit.assertNotNil(result.x)
luaunit.assertNotNil(result.opacity)
luaunit.assertAlmostEquals(result.x, 0, 0.01)
luaunit.assertAlmostEquals(result.opacity, 0, 0.01)
end
-- Test keyframe interpolation at end
function TestKeyframeAnimation:testInterpolationAtEnd()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0, opacity = 0 } },
{ at = 1, values = { x = 100, opacity = 1 } },
},
})
anim.elapsed = 1
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 100, 0.01)
luaunit.assertAlmostEquals(result.opacity, 1, 0.01)
end
-- Test keyframe interpolation at midpoint
function TestKeyframeAnimation:testInterpolationAtMidpoint()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 } },
{ at = 1, values = { x = 100 } },
},
})
anim.elapsed = 0.5
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.x, 50, 0.01)
end
-- Test per-keyframe easing
function TestKeyframeAnimation:testPerKeyframeEasing()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 }, easing = "easeInQuad" },
{ at = 0.5, values = { x = 50 }, easing = "linear" },
{ at = 1, values = { x = 100 } },
},
})
-- At t=0.25 (middle of first segment with easeInQuad)
anim.elapsed = 0.25
anim._resultDirty = true -- Mark dirty to force recalculation
local result1 = anim:interpolate()
-- easeInQuad at 0.5 should give 0.25, so x = 0 + (50-0) * 0.25 = 12.5
luaunit.assertTrue(result1.x < 25, "easeInQuad should slow start")
-- At t=0.75 (middle of second segment with linear)
anim.elapsed = 0.75
anim._resultDirty = true -- Mark dirty to force recalculation
local result2 = anim:interpolate()
-- linear at 0.5 should give 0.5, so x = 50 + (100-50) * 0.5 = 75
luaunit.assertAlmostEquals(result2.x, 75, 1)
end
-- Test findKeyframes method
function TestKeyframeAnimation:testFindKeyframes()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 } },
{ at = 0.25, values = { x = 25 } },
{ at = 0.75, values = { x = 75 } },
{ at = 1, values = { x = 100 } },
},
})
-- Test finding keyframes at different progress values
local prev1, next1 = anim:findKeyframes(0.1)
luaunit.assertEquals(prev1.at, 0)
luaunit.assertEquals(next1.at, 0.25)
local prev2, next2 = anim:findKeyframes(0.5)
luaunit.assertEquals(prev2.at, 0.25)
luaunit.assertEquals(next2.at, 0.75)
local prev3, next3 = anim:findKeyframes(0.9)
luaunit.assertEquals(prev3.at, 0.75)
luaunit.assertEquals(next3.at, 1)
end
-- Test keyframe animation with update
function TestKeyframeAnimation:testKeyframeAnimationUpdate()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { opacity = 0 } },
{ at = 1, values = { opacity = 1 } },
},
})
-- Update halfway through
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
luaunit.assertFalse(anim:update(0)) -- Not complete yet
-- Update to completion
luaunit.assertTrue(anim:update(0.6)) -- Should complete
luaunit.assertEquals(anim:getState(), "completed")
end
-- Test keyframe animation with callbacks
function TestKeyframeAnimation:testKeyframeAnimationCallbacks()
local startCalled = false
local updateCalled = false
local completeCalled = false
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 } },
{ at = 1, values = { x = 100 } },
},
onStart = function()
startCalled = true
end,
onUpdate = function()
updateCalled = true
end,
onComplete = function()
completeCalled = true
end,
})
anim:update(0.5)
luaunit.assertTrue(startCalled)
luaunit.assertTrue(updateCalled)
luaunit.assertFalse(completeCalled)
anim:update(0.6)
luaunit.assertTrue(completeCalled)
end
-- Test missing keyframes (error handling)
function TestKeyframeAnimation:testMissingKeyframes()
-- Should create default keyframes with warning
local anim = Animation.keyframes({
duration = 1,
keyframes = {},
})
luaunit.assertNotNil(anim)
luaunit.assertEquals(#anim.keyframes, 2) -- Should have default start and end
end
-- Test single keyframe (error handling)
function TestKeyframeAnimation:testSingleKeyframe()
-- Should create default keyframes with warning
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0.5, values = { x = 50 } },
},
})
luaunit.assertNotNil(anim)
luaunit.assertTrue(#anim.keyframes >= 2) -- Should have at least 2 keyframes
end
-- Test keyframes without start (at=0)
function TestKeyframeAnimation:testKeyframesWithoutStart()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0.5, values = { x = 50 } },
{ at = 1, values = { x = 100 } },
},
})
-- Should auto-add keyframe at 0
luaunit.assertEquals(anim.keyframes[1].at, 0)
luaunit.assertEquals(anim.keyframes[1].values.x, 50) -- Should copy first keyframe values
end
-- Test keyframes without end (at=1)
function TestKeyframeAnimation:testKeyframesWithoutEnd()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 } },
{ at = 0.5, values = { x = 50 } },
},
})
-- Should auto-add keyframe at 1
luaunit.assertEquals(anim.keyframes[#anim.keyframes].at, 1)
luaunit.assertEquals(anim.keyframes[#anim.keyframes].values.x, 50) -- Should copy last keyframe values
end
-- Test keyframe with invalid props
function TestKeyframeAnimation:testInvalidKeyframeProps()
-- Should handle gracefully with warnings
local anim = Animation.keyframes({
duration = 0, -- Invalid
keyframes = "not a table", -- Invalid
})
luaunit.assertNotNil(anim)
luaunit.assertEquals(anim.duration, 1) -- Should use default
end
-- Test complex multi-property keyframes
function TestKeyframeAnimation:testMultiPropertyKeyframes()
local anim = Animation.keyframes({
duration = 2,
keyframes = {
{ at = 0, values = { x = 0, y = 0, opacity = 0, width = 50 } },
{ at = 0.33, values = { x = 100, y = 50, opacity = 1, width = 100 } },
{ at = 0.66, values = { x = 200, y = 100, opacity = 1, width = 150 } },
{ at = 1, values = { x = 300, y = 150, opacity = 0, width = 200 } },
},
})
-- Test interpolation at 0.5 (middle of second segment)
anim.elapsed = 1.0 -- t = 0.5
local result = anim:interpolate()
luaunit.assertNotNil(result.x)
luaunit.assertNotNil(result.y)
luaunit.assertNotNil(result.opacity)
luaunit.assertNotNil(result.width)
-- Should be interpolating between keyframes at 0.33 and 0.66
luaunit.assertTrue(result.x > 100 and result.x < 200)
luaunit.assertTrue(result.y > 50 and result.y < 100)
end
-- Test keyframe with easing function (not string)
function TestKeyframeAnimation:testKeyframeWithEasingFunction()
local customEasing = function(t)
return t * t
end
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 }, easing = customEasing },
{ at = 1, values = { x = 100 } },
},
})
anim.elapsed = 0.5
local result = anim:interpolate()
-- At t=0.5, easing(0.5) = 0.25, so x = 0 + 100 * 0.25 = 25
luaunit.assertAlmostEquals(result.x, 25, 1)
end
-- Test caching behavior with keyframes
function TestKeyframeAnimation:testKeyframeCaching()
local anim = Animation.keyframes({
duration = 1,
keyframes = {
{ at = 0, values = { x = 0 } },
{ at = 1, values = { x = 100 } },
},
})
anim.elapsed = 0.5
local result1 = anim:interpolate()
local result2 = anim:interpolate() -- Should return cached result
luaunit.assertEquals(result1, result2) -- Should be same table
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,408 +0,0 @@
-- Test suite for layout edge cases and warnings
-- Tests untested code paths in LayoutEngine
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local FlexLove = require("FlexLove")
local ErrorHandler = require("modules.ErrorHandler")
TestLayoutEdgeCases = {}
function TestLayoutEdgeCases:setUp()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Capture warnings
self.warnings = {}
self.originalWarn = ErrorHandler.warn
ErrorHandler.warn = function(module, message)
table.insert(self.warnings, { module = module, message = message })
end
end
function TestLayoutEdgeCases:tearDown()
-- Restore original warn function
ErrorHandler.warn = self.originalWarn
FlexLove.endFrame()
end
-- Test: Child with percentage width in auto-sizing parent should trigger warning
function TestLayoutEdgeCases:test_percentage_width_with_auto_parent_warns()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
-- width not specified - auto-sizing width
height = 200,
positioning = "flex",
flexDirection = "horizontal",
})
FlexLove.new({
id = "child_with_percentage",
parent = container,
width = "50%", -- Percentage width with auto-sizing parent - should warn
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Check that a warning was issued
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage width with auto-sizing parent")
local found = false
for _, warning in ipairs(self.warnings) do
if warning.message:match("percentage width") and warning.message:match("auto%-sizing") then
found = true
break
end
end
-- Note: This warning feature is not yet implemented
-- luaunit.assertTrue(found, "Warning should mention percentage width and auto-sizing")
luaunit.assertTrue(true, "Placeholder - percentage width warning not implemented yet")
end
-- Test: Child with percentage height in auto-sizing parent should trigger warning
function TestLayoutEdgeCases:test_percentage_height_with_auto_parent_warns()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
-- height not specified - auto-sizing height
positioning = "flex",
flexDirection = "vertical",
})
FlexLove.new({
id = "child_with_percentage",
parent = container,
width = 100,
height = "50%", -- Percentage height with auto-sizing parent - should warn
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Check that a warning was issued
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage height with auto-sizing parent")
local found = false
for _, warning in ipairs(self.warnings) do
if warning.message:match("percentage height") and warning.message:match("auto%-sizing") then
found = true
break
end
end
-- Note: This warning feature is not yet implemented
-- luaunit.assertTrue(found, "Warning should mention percentage height and auto-sizing")
luaunit.assertTrue(true, "Placeholder - percentage height warning not implemented yet")
end
-- Test: Pixel-sized children in auto-sizing parent should NOT warn
function TestLayoutEdgeCases:test_pixel_width_with_auto_parent_no_warn()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
-- width not specified - auto-sizing
height = 200,
positioning = "flex",
flexDirection = "horizontal",
})
FlexLove.new({
id = "child_with_pixels",
parent = container,
width = 100, -- Pixel width - should NOT warn
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Check that NO warning was issued about percentage sizing
for _, warning in ipairs(self.warnings) do
local hasPercentageWarning = warning.message:match("percentage") and warning.message:match("auto%-sizing")
luaunit.assertFalse(hasPercentageWarning, "Should not warn for pixel-sized children")
end
end
-- Test: CSS positioning - top offset in absolute container
function TestLayoutEdgeCases:test_css_positioning_top_offset()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 50, -- 50px from top
left = 0,
width = 100,
height = 100,
})
-- Trigger layout by ending and restarting frame
FlexLove.endFrame()
FlexLove.beginFrame()
-- Child should be positioned 50px from container's top edge (accounting for padding)
local expectedY = container.y + container.padding.top + 50
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with top offset")
end
-- Test: CSS positioning - bottom offset in absolute container
function TestLayoutEdgeCases:test_css_positioning_bottom_offset()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
bottom = 50, -- 50px from bottom
left = 0,
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Child should be positioned 50px from container's bottom edge
local expectedY = container.y + container.padding.top + container.height - 50 - child:getBorderBoxHeight()
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with bottom offset")
end
-- Test: CSS positioning - left offset in absolute container
function TestLayoutEdgeCases:test_css_positioning_left_offset()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 0,
left = 50, -- 50px from left
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Child should be positioned 50px from container's left edge
local expectedX = container.x + container.padding.left + 50
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with left offset")
end
-- Test: CSS positioning - right offset in absolute container
function TestLayoutEdgeCases:test_css_positioning_right_offset()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 0,
right = 50, -- 50px from right
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Child should be positioned 50px from container's right edge
local expectedX = container.x + container.padding.left + container.width - 50 - child:getBorderBoxWidth()
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with right offset")
end
-- Test: CSS positioning - combined top and bottom (bottom should take precedence or be ignored)
function TestLayoutEdgeCases:test_css_positioning_top_and_bottom()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 10,
bottom = 20, -- Both specified - last one wins in current implementation
left = 0,
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Bottom should override top
local expectedY = container.y + container.padding.top + container.height - 20 - child:getBorderBoxHeight()
luaunit.assertEquals(child.y, expectedY, "Bottom offset should override top when both specified")
end
-- Test: CSS positioning - combined left and right (right should take precedence or be ignored)
function TestLayoutEdgeCases:test_css_positioning_left_and_right()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 0,
left = 10,
right = 20, -- Both specified - last one wins in current implementation
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Right should override left
local expectedX = container.x + container.padding.left + container.width - 20 - child:getBorderBoxWidth()
luaunit.assertEquals(child.x, expectedX, "Right offset should override left when both specified")
end
-- Test: CSS positioning with padding in container
function TestLayoutEdgeCases:test_css_positioning_with_padding()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
positioning = "absolute",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 10,
left = 10,
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Offsets should be relative to content area (after padding)
local expectedX = container.x + container.padding.left + 10
local expectedY = container.y + container.padding.top + 10
luaunit.assertEquals(child.x, expectedX, "Left offset should account for container padding")
luaunit.assertEquals(child.y, expectedY, "Top offset should account for container padding")
end
-- Test: CSS positioning should NOT affect flex children
function TestLayoutEdgeCases:test_css_positioning_ignored_in_flex()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 400,
height = 400,
positioning = "flex",
flexDirection = "horizontal",
})
local child = FlexLove.new({
id = "child",
parent = container,
top = 100, -- This should be IGNORED in flex layout
left = 100, -- This should be IGNORED in flex layout
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- In flex layout, child should be positioned by flex rules, not CSS offsets
-- Child should be at (0, 0) relative to container content area
luaunit.assertEquals(child.x, 0, "CSS offsets should be ignored in flex layout")
luaunit.assertEquals(child.y, 0, "CSS offsets should be ignored in flex layout")
end
-- Test: CSS positioning in relative container
function TestLayoutEdgeCases:test_css_positioning_in_relative_container()
local container = FlexLove.new({
id = "container",
x = 100,
y = 100,
width = 400,
height = 400,
positioning = "relative",
})
local child = FlexLove.new({
id = "child",
parent = container,
positioning = "absolute",
top = 30,
left = 30,
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame()
-- Should work the same as absolute container
local expectedX = container.x + container.padding.left + 30
local expectedY = container.y + container.padding.top + 30
luaunit.assertEquals(child.x, expectedX, "CSS positioning should work in relative containers")
luaunit.assertEquals(child.y, expectedY, "CSS positioning should work in relative containers")
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
-- Note: NinePatchParser and ImageDataReader modules were folded into the NinePatch module
-- This test file is kept for backwards compatibility but effectively disabled
-- The parsing logic is now covered by ninepatch_test.lua which tests the public API
TestNinePatchParser = {}
-- Single stub test to indicate the module was refactored
function TestNinePatchParser:testModuleWasRefactored()
luaunit.assertTrue(true, "NinePatchParser was folded into NinePatch module - see ninepatch_test.lua")
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -3,9 +3,13 @@ require("testing.loveStub")
local NinePatch = require("modules.NinePatch")
TestNinePatch = {}
-- =============================================================================
-- Test Suite 1: Basic Initialization and Setup
-- =============================================================================
function TestNinePatch:setUp()
TestNinePatchBasics = {}
function TestNinePatchBasics:setUp()
-- Create a minimal mock component with regions
self.mockComponent = {
regions = {
@@ -28,115 +32,285 @@ function TestNinePatch:setUp()
}
end
-- Unhappy path tests for NinePatch.draw()
-- =============================================================================
-- Test Suite 2: Nil and Invalid Input Handling
-- =============================================================================
function TestNinePatch:testDrawWithNilComponent()
TestNinePatchNilInputs = {}
function TestNinePatchNilInputs:setUp()
self.mockComponent = {
regions = {
topLeft = { x = 0, y = 0, w = 10, h = 10 },
topCenter = { x = 10, y = 0, w = 20, h = 10 },
topRight = { x = 30, y = 0, w = 10, h = 10 },
middleLeft = { x = 0, y = 10, w = 10, h = 20 },
middleCenter = { x = 10, y = 10, w = 20, h = 20 },
middleRight = { x = 30, y = 10, w = 10, h = 20 },
bottomLeft = { x = 0, y = 30, w = 10, h = 10 },
bottomCenter = { x = 10, y = 30, w = 20, h = 10 },
bottomRight = { x = 30, y = 30, w = 10, h = 10 },
},
}
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatchNilInputs:testDrawWithNilComponent()
-- Should return early without error
NinePatch.draw(nil, self.mockAtlas, 0, 0, 100, 100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNilAtlas()
function TestNinePatchNilInputs:testDrawWithNilAtlas()
-- Should return early without error
NinePatch.draw(self.mockComponent, nil, 0, 0, 100, 100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithBothNil()
function TestNinePatchNilInputs:testDrawWithBothNil()
-- Should return early without error
NinePatch.draw(nil, nil, 0, 0, 100, 100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithZeroWidth()
-- =============================================================================
-- Test Suite 3: Zero and Negative Dimensions
-- =============================================================================
TestNinePatchDimensions = {}
function TestNinePatchDimensions:setUp()
self.mockComponent = {
regions = {
topLeft = { x = 0, y = 0, w = 10, h = 10 },
topCenter = { x = 10, y = 0, w = 20, h = 10 },
topRight = { x = 30, y = 0, w = 10, h = 10 },
middleLeft = { x = 0, y = 10, w = 10, h = 20 },
middleCenter = { x = 10, y = 10, w = 20, h = 20 },
middleRight = { x = 30, y = 10, w = 10, h = 20 },
bottomLeft = { x = 0, y = 30, w = 10, h = 10 },
bottomCenter = { x = 10, y = 30, w = 20, h = 10 },
bottomRight = { x = 30, y = 30, w = 10, h = 10 },
},
}
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatchDimensions:testDrawWithZeroWidth()
-- Should handle zero width gracefully
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 0, 100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithZeroHeight()
function TestNinePatchDimensions:testDrawWithZeroHeight()
-- Should handle zero height gracefully
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 0)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNegativeWidth()
function TestNinePatchDimensions:testDrawWithNegativeWidth()
-- Should handle negative width
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, -100, 100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNegativeHeight()
function TestNinePatchDimensions:testDrawWithNegativeHeight()
-- Should handle negative height
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, -100)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithSmallDimensions()
function TestNinePatchDimensions:testDrawWithSmallDimensions()
-- Dimensions smaller than borders - should clamp
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 5, 5)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithVeryLargeDimensions()
function TestNinePatchDimensions:testDrawWithVeryLargeDimensions()
-- Very large dimensions
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 10000, 10000)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNegativePosition()
function TestNinePatchDimensions:testDrawWithWidthEqualToBorders()
-- Width exactly equals left + right borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 20, 100)
luaunit.assertTrue(true)
end
function TestNinePatchDimensions:testDrawWithHeightEqualToBorders()
-- Height exactly equals top + bottom borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 20)
luaunit.assertTrue(true)
end
function TestNinePatchDimensions:testDrawWithExactBorderDimensions()
-- Both width and height equal borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 20, 20)
luaunit.assertTrue(true)
end
function TestNinePatchDimensions:testDrawWithOneLessThanBorders()
-- Dimensions one pixel less than borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 19, 19)
luaunit.assertTrue(true)
end
function TestNinePatchDimensions:testDrawWithFractionalDimensions()
-- Non-integer dimensions
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100.5, 100.7)
luaunit.assertTrue(true)
end
-- =============================================================================
-- Test Suite 4: Position and Coordinate Edge Cases
-- =============================================================================
TestNinePatchPositioning = {}
function TestNinePatchPositioning:setUp()
self.mockComponent = {
regions = {
topLeft = { x = 0, y = 0, w = 10, h = 10 },
topCenter = { x = 10, y = 0, w = 20, h = 10 },
topRight = { x = 30, y = 0, w = 10, h = 10 },
middleLeft = { x = 0, y = 10, w = 10, h = 20 },
middleCenter = { x = 10, y = 10, w = 20, h = 20 },
middleRight = { x = 30, y = 10, w = 10, h = 20 },
bottomLeft = { x = 0, y = 30, w = 10, h = 10 },
bottomCenter = { x = 10, y = 30, w = 20, h = 10 },
bottomRight = { x = 30, y = 30, w = 10, h = 10 },
},
}
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatchPositioning:testDrawWithNegativePosition()
-- Negative x, y positions
NinePatch.draw(self.mockComponent, self.mockAtlas, -100, -100, 200, 200)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithDefaultOpacity()
function TestNinePatchPositioning:testDrawWithFractionalPosition()
-- Non-integer position
NinePatch.draw(self.mockComponent, self.mockAtlas, 10.3, 20.7, 100, 100)
luaunit.assertTrue(true)
end
-- =============================================================================
-- Test Suite 5: Opacity Parameter Handling
-- =============================================================================
TestNinePatchOpacity = {}
function TestNinePatchOpacity:setUp()
self.mockComponent = {
regions = {
topLeft = { x = 0, y = 0, w = 10, h = 10 },
topCenter = { x = 10, y = 0, w = 20, h = 10 },
topRight = { x = 30, y = 0, w = 10, h = 10 },
middleLeft = { x = 0, y = 10, w = 10, h = 20 },
middleCenter = { x = 10, y = 10, w = 20, h = 20 },
middleRight = { x = 30, y = 10, w = 10, h = 20 },
bottomLeft = { x = 0, y = 30, w = 10, h = 10 },
bottomCenter = { x = 10, y = 30, w = 20, h = 10 },
bottomRight = { x = 30, y = 30, w = 10, h = 10 },
},
}
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatchOpacity:testDrawWithDefaultOpacity()
-- Opacity defaults to 1
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, nil)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithZeroOpacity()
function TestNinePatchOpacity:testDrawWithZeroOpacity()
-- Zero opacity
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, 0)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNegativeOpacity()
function TestNinePatchOpacity:testDrawWithNegativeOpacity()
-- Negative opacity
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, -0.5)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithOpacityGreaterThanOne()
function TestNinePatchOpacity:testDrawWithOpacityGreaterThanOne()
-- Opacity > 1
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, 2.0)
luaunit.assertTrue(true)
end
-- Test with missing regions
-- =============================================================================
-- Test Suite 6: ScaleCorners Parameter Handling
-- =============================================================================
-- Test with scaleCorners = 0 (no scaling, just stretching)
TestNinePatchScaling = {}
function TestNinePatch:testDrawWithZeroScaleCorners()
function TestNinePatchScaling:setUp()
self.mockComponent = {
regions = {
topLeft = { x = 0, y = 0, w = 10, h = 10 },
topCenter = { x = 10, y = 0, w = 20, h = 10 },
topRight = { x = 30, y = 0, w = 10, h = 10 },
middleLeft = { x = 0, y = 10, w = 10, h = 20 },
middleCenter = { x = 10, y = 10, w = 20, h = 20 },
middleRight = { x = 30, y = 10, w = 10, h = 20 },
bottomLeft = { x = 0, y = 30, w = 10, h = 10 },
bottomCenter = { x = 10, y = 30, w = 20, h = 10 },
bottomRight = { x = 30, y = 30, w = 10, h = 10 },
},
}
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatchScaling:testDrawWithZeroScaleCorners()
-- Zero should not trigger scaling path
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, 1, 0)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNegativeScaleCorners()
function TestNinePatchScaling:testDrawWithNegativeScaleCorners()
-- Negative should not trigger scaling path
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, 1, -1)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithNilScaleCorners()
function TestNinePatchScaling:testDrawWithNilScaleCorners()
-- Nil should use component setting or default (no scaling)
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 100, 1, nil)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithComponentScalingAlgorithm()
function TestNinePatchScaling:testDrawWithComponentScalingAlgorithm()
-- Test that scalingAlgorithm property exists but don't trigger scaling path
local componentWithAlgorithm = {
regions = self.mockComponent.regions,
@@ -147,47 +321,21 @@ function TestNinePatch:testDrawWithComponentScalingAlgorithm()
luaunit.assertTrue(true)
end
-- Edge cases with specific dimensions
-- =============================================================================
-- Test Suite 7: Unusual Region Configurations
-- =============================================================================
function TestNinePatch:testDrawWithWidthEqualToBorders()
-- Width exactly equals left + right borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 20, 100)
luaunit.assertTrue(true)
TestNinePatchRegions = {}
function TestNinePatchRegions:setUp()
self.mockAtlas = {
getDimensions = function()
return 100, 100
end,
}
end
function TestNinePatch:testDrawWithHeightEqualToBorders()
-- Height exactly equals top + bottom borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100, 20)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithExactBorderDimensions()
-- Both width and height equal borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 20, 20)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithOneLessThanBorders()
-- Dimensions one pixel less than borders
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 19, 19)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithFractionalDimensions()
-- Non-integer dimensions
NinePatch.draw(self.mockComponent, self.mockAtlas, 0, 0, 100.5, 100.7)
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithFractionalPosition()
-- Non-integer position
NinePatch.draw(self.mockComponent, self.mockAtlas, 10.3, 20.7, 100, 100)
luaunit.assertTrue(true)
end
-- Test with unusual region sizes
function TestNinePatch:testDrawWithAsymmetricBorders()
function TestNinePatchRegions:testDrawWithAsymmetricBorders()
local asymmetric = {
regions = {
topLeft = { x = 0, y = 0, w = 5, h = 5 },
@@ -205,7 +353,7 @@ function TestNinePatch:testDrawWithAsymmetricBorders()
luaunit.assertTrue(true)
end
function TestNinePatch:testDrawWithVerySmallRegions()
function TestNinePatchRegions:testDrawWithVerySmallRegions()
local tiny = {
regions = {
topLeft = { x = 0, y = 0, w = 1, h = 1 },

View File

@@ -1,345 +0,0 @@
-- Test suite for overflow detection and scroll behavior
-- This tests the critical ScrollManager.detectOverflow() path which is currently 0% covered
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local FlexLove = require("FlexLove")
TestOverflowDetection = {}
function TestOverflowDetection:setUp()
FlexLove.beginFrame(1920, 1080)
end
function TestOverflowDetection:tearDown()
FlexLove.endFrame()
end
-- Test basic overflow detection when content exceeds container
function TestOverflowDetection:test_vertical_overflow_detected()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 100,
overflow = "scroll",
})
-- Add child that exceeds container height
FlexLove.new({
id = "tall_child",
parent = container,
x = 0,
y = 0,
width = 100,
height = 200, -- Taller than container (100)
})
-- Force layout to trigger detectOverflow
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
-- Check if overflow was detected
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow")
end
function TestOverflowDetection:test_horizontal_overflow_detected()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 100,
height = 200,
overflow = "scroll",
})
-- Add child that exceeds container width
FlexLove.new({
id = "wide_child",
parent = container,
x = 0,
y = 0,
width = 300, -- Wider than container (100)
height = 50,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
end
function TestOverflowDetection:test_both_axes_overflow()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 100,
height = 100,
overflow = "scroll",
})
-- Add child that exceeds both dimensions
FlexLove.new({
id = "large_child",
parent = container,
x = 0,
y = 0,
width = 200,
height = 200,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
end
function TestOverflowDetection:test_no_overflow_when_content_fits()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
})
-- Add child that fits within container
FlexLove.new({
id = "small_child",
parent = container,
x = 0,
y = 0,
width = 100,
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow")
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
end
function TestOverflowDetection:test_overflow_with_multiple_children()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = "flex",
flexDirection = "vertical",
})
-- Add multiple children that together exceed container
for i = 1, 5 do
FlexLove.new({
id = "child_" .. i,
parent = container,
width = 150,
height = 60, -- 5 * 60 = 300, exceeds container height of 200
})
end
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollY > 0, "Should detect overflow from multiple children")
end
function TestOverflowDetection:test_overflow_with_padding()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
padding = { top = 10, right = 10, bottom = 10, left = 10 },
overflow = "scroll",
})
-- Child that fits in container but exceeds available content area (200 - 20 = 180)
FlexLove.new({
id = "child",
parent = container,
x = 0,
y = 0,
width = 190, -- Exceeds content width (180)
height = 100,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect overflow accounting for padding")
end
function TestOverflowDetection:test_overflow_with_margins()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
overflow = "scroll",
})
-- Child with margins that contribute to overflow
-- In flex layout, margins are properly accounted for in positioning
FlexLove.new({
id = "child",
parent = container,
width = 180,
height = 180,
margin = { top = 5, right = 20, bottom = 5, left = 5 }, -- Total width: 5+180+20=205, overflows 200px container
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should include child margins in overflow calculation")
end
-- Test edge case: overflow = "visible" should skip detection
function TestOverflowDetection:test_visible_overflow_skips_detection()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 100,
height = 100,
overflow = "visible", -- Should not clip or calculate overflow
})
-- Add oversized child
FlexLove.new({
id = "large_child",
parent = container,
x = 0,
y = 0,
width = 300,
height = 300,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
-- With overflow="visible", maxScroll should be 0 (no scrolling)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "visible overflow should not enable scrolling")
luaunit.assertEquals(maxScrollY, 0, "visible overflow should not enable scrolling")
end
-- Test edge case: empty container
function TestOverflowDetection:test_empty_container_no_overflow()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
-- No children
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "Empty container should have no overflow")
luaunit.assertEquals(maxScrollY, 0, "Empty container should have no overflow")
end
-- Test overflow with absolutely positioned children (should be ignored)
function TestOverflowDetection:test_absolute_children_ignored_in_overflow()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
})
-- Regular child that fits
FlexLove.new({
id = "normal_child",
parent = container,
x = 0,
y = 0,
width = 150,
height = 150,
})
-- Absolutely positioned child that extends beyond (should NOT cause overflow)
FlexLove.new({
id = "absolute_child",
parent = container,
positioning = "absolute",
top = 0,
left = 0,
width = 400,
height = 400,
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll()
-- Should not have overflow because absolute children are ignored
luaunit.assertEquals(maxScrollX, 0, "Absolute children should not cause overflow")
luaunit.assertEquals(maxScrollY, 0, "Absolute children should not cause overflow")
end
-- Test scroll clamping with overflow
function TestOverflowDetection:test_scroll_clamped_to_max()
local container = FlexLove.new({
id = "container",
x = 0,
y = 0,
width = 100,
height = 100,
overflow = "scroll",
})
FlexLove.new({
id = "child",
parent = container,
x = 0,
y = 0,
width = 100,
height = 300, -- Creates 200px of vertical overflow
})
FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080)
-- Try to scroll beyond max
container:setScrollPosition(0, 999999)
local scrollX, scrollY = container:getScrollPosition()
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollY, maxScrollY, "Scroll should be clamped to maximum")
luaunit.assertTrue(scrollY < 999999, "Should not scroll beyond content")
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,281 +0,0 @@
-- Test suite for path validation functions
-- Tests sanitizePath, isPathSafe, validatePath, getFileExtension, hasAllowedExtension
package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Load love stub before anything else
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local utils = require("modules.utils")
-- Test suite for sanitizePath
TestSanitizePath = {}
function TestSanitizePath:testSanitizePath_NilInput()
local result = utils.sanitizePath(nil)
luaunit.assertEquals(result, "")
end
function TestSanitizePath:testSanitizePath_EmptyString()
local result = utils.sanitizePath("")
luaunit.assertEquals(result, "")
end
function TestSanitizePath:testSanitizePath_Whitespace()
local result = utils.sanitizePath(" /path/to/file ")
luaunit.assertEquals(result, "/path/to/file")
end
function TestSanitizePath:testSanitizePath_Backslashes()
local result = utils.sanitizePath("C:\\path\\to\\file")
luaunit.assertEquals(result, "C:/path/to/file")
end
function TestSanitizePath:testSanitizePath_DuplicateSlashes()
local result = utils.sanitizePath("/path//to///file")
luaunit.assertEquals(result, "/path/to/file")
end
function TestSanitizePath:testSanitizePath_TrailingSlash()
local result = utils.sanitizePath("/path/to/dir/")
luaunit.assertEquals(result, "/path/to/dir")
-- Root should keep trailing slash
result = utils.sanitizePath("/")
luaunit.assertEquals(result, "/")
end
function TestSanitizePath:testSanitizePath_MixedIssues()
local result = utils.sanitizePath(" C:\\path\\\\to///file.txt ")
luaunit.assertEquals(result, "C:/path/to/file.txt")
end
-- Test suite for isPathSafe
TestIsPathSafe = {}
function TestIsPathSafe:testIsPathSafe_EmptyPath()
local safe, reason = utils.isPathSafe("")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "empty")
end
function TestIsPathSafe:testIsPathSafe_NilPath()
local safe, reason = utils.isPathSafe(nil)
luaunit.assertFalse(safe)
luaunit.assertNotNil(reason)
end
function TestIsPathSafe:testIsPathSafe_ParentDirectory()
local safe, reason = utils.isPathSafe("../etc/passwd")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "..")
end
function TestIsPathSafe:testIsPathSafe_MultipleParentDirectories()
local safe, reason = utils.isPathSafe("../../../../../../etc/passwd")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "..")
end
function TestIsPathSafe:testIsPathSafe_HiddenParentDirectory()
local safe, reason = utils.isPathSafe("/path/to/../../../etc/passwd")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "..")
end
function TestIsPathSafe:testIsPathSafe_NullBytes()
local safe, reason = utils.isPathSafe("/path/to/file\0.txt")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "null")
end
function TestIsPathSafe:testIsPathSafe_EncodedTraversal()
local safe, reason = utils.isPathSafe("/path/%2e%2e/file")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "encoded")
end
function TestIsPathSafe:testIsPathSafe_LegitimatePathNoBaseDir()
local safe, reason = utils.isPathSafe("/themes/default.lua")
luaunit.assertTrue(safe)
luaunit.assertNil(reason)
end
function TestIsPathSafe:testIsPathSafe_LegitimatePathWithBaseDir()
local safe, reason = utils.isPathSafe("/allowed/themes/default.lua", "/allowed")
luaunit.assertTrue(safe)
luaunit.assertNil(reason)
end
function TestIsPathSafe:testIsPathSafe_RelativePathWithBaseDir()
local safe, reason = utils.isPathSafe("themes/default.lua", "/allowed")
luaunit.assertTrue(safe)
luaunit.assertNil(reason)
end
function TestIsPathSafe:testIsPathSafe_OutsideBaseDir()
local safe, reason = utils.isPathSafe("/other/themes/default.lua", "/allowed")
luaunit.assertFalse(safe)
luaunit.assertStrContains(reason, "outside")
end
-- Test suite for validatePath
TestValidatePath = {}
function TestValidatePath:testValidatePath_EmptyPath()
local valid, err = utils.validatePath("")
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "empty")
end
function TestValidatePath:testValidatePath_NilPath()
local valid, err = utils.validatePath(nil)
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "empty")
end
function TestValidatePath:testValidatePath_TooLong()
local longPath = string.rep("a", 5000)
local valid, err = utils.validatePath(longPath, { maxLength = 100 })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "maximum length")
end
function TestValidatePath:testValidatePath_TraversalAttack()
local valid, err = utils.validatePath("../../../etc/passwd")
luaunit.assertFalse(valid)
luaunit.assertNotNil(err)
end
function TestValidatePath:testValidatePath_AllowedExtension()
local valid, err = utils.validatePath("theme.lua", { allowedExtensions = { "lua", "txt" } })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
end
function TestValidatePath:testValidatePath_DisallowedExtension()
local valid, err = utils.validatePath("script.exe", { allowedExtensions = { "lua", "txt" } })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "not allowed")
end
function TestValidatePath:testValidatePath_NoExtension()
local valid, err = utils.validatePath("README", { allowedExtensions = { "lua", "txt" } })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "no file extension")
end
function TestValidatePath:testValidatePath_CaseInsensitiveExtension()
local valid, err = utils.validatePath("Theme.LUA", { allowedExtensions = { "lua" } })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
end
function TestValidatePath:testValidatePath_WithBaseDir()
local valid, err = utils.validatePath("themes/default.lua", { baseDir = "/allowed" })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
end
function TestValidatePath:testValidatePath_OutsideBaseDir()
local valid, err = utils.validatePath("/other/theme.lua", { baseDir = "/allowed" })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "outside")
end
-- Test suite for getFileExtension
TestGetFileExtension = {}
function TestGetFileExtension:testGetFileExtension_SimpleExtension()
local ext = utils.getFileExtension("file.txt")
luaunit.assertEquals(ext, "txt")
end
function TestGetFileExtension:testGetFileExtension_MultipleDotsInPath()
local ext = utils.getFileExtension("/path/to/file.name.txt")
luaunit.assertEquals(ext, "txt")
end
function TestGetFileExtension:testGetFileExtension_NoExtension()
local ext = utils.getFileExtension("README")
luaunit.assertNil(ext)
end
function TestGetFileExtension:testGetFileExtension_NilPath()
local ext = utils.getFileExtension(nil)
luaunit.assertNil(ext)
end
function TestGetFileExtension:testGetFileExtension_CaseSensitive()
local ext = utils.getFileExtension("File.TXT")
luaunit.assertEquals(ext, "txt") -- Should be lowercase
end
function TestGetFileExtension:testGetFileExtension_LongExtension()
local ext = utils.getFileExtension("archive.tar.gz")
luaunit.assertEquals(ext, "gz")
end
-- Test suite for hasAllowedExtension
TestHasAllowedExtension = {}
function TestHasAllowedExtension:testHasAllowedExtension_Allowed()
local allowed = utils.hasAllowedExtension("file.txt", { "txt", "lua" })
luaunit.assertTrue(allowed)
end
function TestHasAllowedExtension:testHasAllowedExtension_NotAllowed()
local allowed = utils.hasAllowedExtension("file.exe", { "txt", "lua" })
luaunit.assertFalse(allowed)
end
function TestHasAllowedExtension:testHasAllowedExtension_CaseInsensitive()
local allowed = utils.hasAllowedExtension("File.TXT", { "txt", "lua" })
luaunit.assertTrue(allowed)
end
function TestHasAllowedExtension:testHasAllowedExtension_NoExtension()
local allowed = utils.hasAllowedExtension("README", { "txt", "lua" })
luaunit.assertFalse(allowed)
end
function TestHasAllowedExtension:testHasAllowedExtension_EmptyArray()
local allowed = utils.hasAllowedExtension("file.txt", {})
luaunit.assertFalse(allowed)
end
-- Test suite for security scenarios
TestPathSecurity = {}
function TestPathSecurity:testPathSecurity_WindowsTraversal()
local safe = utils.isPathSafe("..\\..\\..\\windows\\system32")
luaunit.assertFalse(safe)
end
function TestPathSecurity:testPathSecurity_MixedSeparators()
local safe = utils.isPathSafe("../path\\to/../file")
luaunit.assertFalse(safe)
end
function TestPathSecurity:testPathSecurity_DoubleEncodedTraversal()
local safe = utils.isPathSafe("%252e%252e%252f")
luaunit.assertFalse(safe)
end
function TestPathSecurity:testPathSecurity_LegitimateFileWithDots()
-- Files with dots in name should be OK (not ..)
local safe = utils.isPathSafe("/path/to/file.backup.txt")
luaunit.assertTrue(safe)
end
function TestPathSecurity:testPathSecurity_HiddenFiles()
-- Hidden files (starting with .) should be OK
local safe = utils.isPathSafe("/path/to/.hidden")
luaunit.assertTrue(safe)
end
-- Run tests if this file is executed directly
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,153 +0,0 @@
-- Test Performance Instrumentation
package.path = package.path .. ";./?.lua;./modules/?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
-- Set up stub before requiring modules
_G.love = loveStub
local Performance = require("modules.Performance")
TestPerformanceInstrumentation = {}
local perf
function TestPerformanceInstrumentation:setUp()
-- Get Performance instance and ensure it's enabled
perf = Performance.init({ enabled = true }, {})
perf.enabled = true -- Explicitly set enabled in case singleton was already created
end
function TestPerformanceInstrumentation:tearDown()
-- No cleanup needed - instance will be recreated in setUp
end
function TestPerformanceInstrumentation:testTimerStartStop()
perf:startTimer("test_operation")
-- Simulate some work
local sum = 0
for i = 1, 1000 do
sum = sum + i
end
local elapsed = perf:stopTimer("test_operation")
luaunit.assertNotNil(elapsed)
luaunit.assertTrue(elapsed >= 0)
end
function TestPerformanceInstrumentation:testMultipleTimers()
-- Start multiple timers
perf:startTimer("layout")
perf:startTimer("render")
local sum = 0
for i = 1, 100 do
sum = sum + i
end
local layoutTime = perf:stopTimer("layout")
local renderTime = perf:stopTimer("render")
luaunit.assertNotNil(layoutTime)
luaunit.assertNotNil(renderTime)
end
function TestPerformanceInstrumentation:testFrameTiming()
perf:startFrame()
-- Simulate frame work
local sum = 0
for i = 1, 1000 do
sum = sum + i
end
perf:endFrame()
luaunit.assertNotNil(perf._frameMetrics)
luaunit.assertTrue(perf._frameMetrics.frameCount >= 1)
luaunit.assertTrue(perf._frameMetrics.lastFrameTime >= 0)
end
function TestPerformanceInstrumentation:testDrawCallCounting()
perf:incrementCounter("draw_calls", 1)
perf:incrementCounter("draw_calls", 1)
perf:incrementCounter("draw_calls", 1)
luaunit.assertNotNil(perf._metrics.draw_calls)
luaunit.assertTrue(perf._metrics.draw_calls.frameValue >= 3)
-- Reset and check
perf:resetFrameCounters()
luaunit.assertEquals(perf._metrics.draw_calls.frameValue, 0)
end
function TestPerformanceInstrumentation:testHUDToggle()
luaunit.assertFalse(perf.hudEnabled)
perf:toggleHUD()
luaunit.assertTrue(perf.hudEnabled)
perf:toggleHUD()
luaunit.assertFalse(perf.hudEnabled)
end
function TestPerformanceInstrumentation:testEnableDisable()
perf.enabled = true
luaunit.assertTrue(perf.enabled)
perf.enabled = false
luaunit.assertFalse(perf.enabled)
-- Timers should not record when disabled
perf:startTimer("disabled_test")
local elapsed = perf:stopTimer("disabled_test")
luaunit.assertNil(elapsed)
end
function TestPerformanceInstrumentation:testMeasureFunction()
local function expensiveOperation(n)
local sum = 0
for i = 1, n do
sum = sum + i
end
return sum
end
-- Test that the function works (Performance module doesn't have measure wrapper)
perf:startTimer("expensive_op")
local result = expensiveOperation(1000)
perf:stopTimer("expensive_op")
luaunit.assertEquals(result, 500500) -- sum of 1 to 1000
end
function TestPerformanceInstrumentation:testMemoryTracking()
perf:_updateMemory()
luaunit.assertNotNil(perf._memoryMetrics)
luaunit.assertTrue(perf._memoryMetrics.current > 0)
luaunit.assertTrue(perf._memoryMetrics.peak >= perf._memoryMetrics.current)
end
function TestPerformanceInstrumentation:testExportJSON()
perf:startTimer("test_op")
perf:stopTimer("test_op")
-- Performance module doesn't have exportJSON, just verify timers work
luaunit.assertNotNil(perf._timers)
end
function TestPerformanceInstrumentation:testExportCSV()
perf:startTimer("test_op")
perf:stopTimer("test_op")
-- Performance module doesn't have exportCSV, just verify timers work
luaunit.assertNotNil(perf._timers)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -0,0 +1,316 @@
-- Test Performance Module (Consolidated)
package.path = package.path .. ";./?.lua;./modules/?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
-- Set up stub before requiring modules
_G.love = loveStub
local FlexLove = require("FlexLove")
local Performance = require("modules.Performance")
local Element = require('modules.Element')
-- Initialize FlexLove to ensure all modules are properly set up
FlexLove.init()
-- ============================================================================
-- Test Suite 1: Performance Instrumentation
-- ============================================================================
TestPerformanceInstrumentation = {}
local perf
function TestPerformanceInstrumentation:setUp()
-- Get Performance instance and ensure it's enabled
perf = Performance.init({ enabled = true }, {})
perf.enabled = true -- Explicitly set enabled in case singleton was already created
end
function TestPerformanceInstrumentation:tearDown()
-- No cleanup needed - instance will be recreated in setUp
end
function TestPerformanceInstrumentation:testTimerStartStop()
perf:startTimer("test_operation")
-- Simulate some work
local sum = 0
for i = 1, 1000 do
sum = sum + i
end
local elapsed = perf:stopTimer("test_operation")
luaunit.assertNotNil(elapsed)
luaunit.assertTrue(elapsed >= 0)
end
function TestPerformanceInstrumentation:testMultipleTimers()
-- Start multiple timers
perf:startTimer("layout")
perf:startTimer("render")
local sum = 0
for i = 1, 100 do
sum = sum + i
end
local layoutTime = perf:stopTimer("layout")
local renderTime = perf:stopTimer("render")
luaunit.assertNotNil(layoutTime)
luaunit.assertNotNil(renderTime)
end
function TestPerformanceInstrumentation:testFrameTiming()
perf:startFrame()
-- Simulate frame work
local sum = 0
for i = 1, 1000 do
sum = sum + i
end
perf:endFrame()
luaunit.assertNotNil(perf._frameMetrics)
luaunit.assertTrue(perf._frameMetrics.frameCount >= 1)
luaunit.assertTrue(perf._frameMetrics.lastFrameTime >= 0)
end
function TestPerformanceInstrumentation:testDrawCallCounting()
perf:incrementCounter("draw_calls", 1)
perf:incrementCounter("draw_calls", 1)
perf:incrementCounter("draw_calls", 1)
luaunit.assertNotNil(perf._metrics.draw_calls)
luaunit.assertTrue(perf._metrics.draw_calls.frameValue >= 3)
-- Reset and check
perf:resetFrameCounters()
luaunit.assertEquals(perf._metrics.draw_calls.frameValue, 0)
end
function TestPerformanceInstrumentation:testHUDToggle()
luaunit.assertFalse(perf.hudEnabled)
perf:toggleHUD()
luaunit.assertTrue(perf.hudEnabled)
perf:toggleHUD()
luaunit.assertFalse(perf.hudEnabled)
end
function TestPerformanceInstrumentation:testEnableDisable()
perf.enabled = true
luaunit.assertTrue(perf.enabled)
perf.enabled = false
luaunit.assertFalse(perf.enabled)
-- Timers should not record when disabled
perf:startTimer("disabled_test")
local elapsed = perf:stopTimer("disabled_test")
luaunit.assertNil(elapsed)
end
function TestPerformanceInstrumentation:testMeasureFunction()
local function expensiveOperation(n)
local sum = 0
for i = 1, n do
sum = sum + i
end
return sum
end
-- Test that the function works (Performance module doesn't have measure wrapper)
perf:startTimer("expensive_op")
local result = expensiveOperation(1000)
perf:stopTimer("expensive_op")
luaunit.assertEquals(result, 500500) -- sum of 1 to 1000
end
function TestPerformanceInstrumentation:testMemoryTracking()
perf:_updateMemory()
luaunit.assertNotNil(perf._memoryMetrics)
luaunit.assertTrue(perf._memoryMetrics.current > 0)
luaunit.assertTrue(perf._memoryMetrics.peak >= perf._memoryMetrics.current)
end
function TestPerformanceInstrumentation:testExportJSON()
perf:startTimer("test_op")
perf:stopTimer("test_op")
-- Performance module doesn't have exportJSON, just verify timers work
luaunit.assertNotNil(perf._timers)
end
function TestPerformanceInstrumentation:testExportCSV()
perf:startTimer("test_op")
perf:stopTimer("test_op")
-- Performance module doesn't have exportCSV, just verify timers work
luaunit.assertNotNil(perf._timers)
end
-- ============================================================================
-- Test Suite 2: Performance Warnings
-- ============================================================================
TestPerformanceWarnings = {}
local perfWarn
function TestPerformanceWarnings:setUp()
-- Recreate Performance instance with warnings enabled
perfWarn = Performance.init({ enabled = true, warningsEnabled = true }, {})
end
function TestPerformanceWarnings:tearDown()
-- No cleanup needed - instance will be recreated in setUp
end
-- Test hierarchy depth warning
function TestPerformanceWarnings:testHierarchyDepthWarning()
-- Create a deep hierarchy (20 levels)
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
local current = root
for i = 1, 20 do
local child = Element.new({
id = "child_" .. i,
width = 50,
height = 50,
parent = current,
}, Element.defaultDependencies)
table.insert(current.children, child)
current = child
end
-- This should trigger a hierarchy depth warning
root:layoutChildren()
-- Check that element was created successfully despite warning
luaunit.assertNotNil(current)
luaunit.assertEquals(current:getHierarchyDepth(), 20)
end
-- Test element count warning
function TestPerformanceWarnings:testElementCountWarning()
-- Create a container with many children (simulating 1000+ elements)
local root = Element.new({
id = "root",
width = 1000,
height = 1000,
}, Element.defaultDependencies)
-- Add many child elements
for i = 1, 50 do -- Keep test fast, just verify the counting logic works
local child = Element.new({
id = "child_" .. i,
width = 20,
height = 20,
parent = root,
}, Element.defaultDependencies)
table.insert(root.children, child)
end
local count = root:countElements()
-- Note: Due to test isolation issues with shared state, count may be doubled
luaunit.assertTrue(count >= 51, "Should count at least 51 elements (root + 50 children), got " .. count)
end
-- Test animation count warning
function TestPerformanceWarnings:testAnimationTracking()
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
-- Add some animated children
for i = 1, 3 do
local child = Element.new({
id = "animated_child_" .. i,
width = 20,
height = 20,
parent = root,
}, Element.defaultDependencies)
-- Add mock animation
child.animation = {
update = function()
return false
end,
interpolate = function()
return { width = 20, height = 20 }
end,
}
table.insert(root.children, child)
end
local animCount = root:_countActiveAnimations()
-- Note: Due to test isolation issues with shared state, count may be doubled
luaunit.assertTrue(animCount >= 3, "Should count at least 3 animations, got " .. animCount)
end
-- Test warnings can be disabled
function TestPerformanceWarnings:testWarningsCanBeDisabled()
perfWarn.warningsEnabled = false
-- Create deep hierarchy
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
local current = root
for i = 1, 20 do
local child = Element.new({
id = "child_" .. i,
width = 50,
height = 50,
parent = current,
}, Element.defaultDependencies)
table.insert(current.children, child)
current = child
end
-- Should not trigger warning (but should still create elements)
root:layoutChildren()
luaunit.assertEquals(current:getHierarchyDepth(), 20)
-- Re-enable for other tests
perfWarn.warningsEnabled = true
end
-- Test layout recalculation tracking
function TestPerformanceWarnings:testLayoutRecalculationTracking()
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
-- Layout multiple times (simulating layout thrashing)
for i = 1, 5 do
root:layoutChildren()
end
-- Should complete without crashing
luaunit.assertNotNil(root)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,163 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
local Performance = require("modules.Performance")
local Element = require('modules.Element')
-- Initialize FlexLove to ensure all modules are properly set up
FlexLove.init()
TestPerformanceWarnings = {}
local perf
function TestPerformanceWarnings:setUp()
-- Recreate Performance instance with warnings enabled
perf = Performance.init({ enabled = true, warningsEnabled = true }, {})
end
function TestPerformanceWarnings:tearDown()
-- No cleanup needed - instance will be recreated in setUp
end
-- Test hierarchy depth warning
function TestPerformanceWarnings:testHierarchyDepthWarning()
-- Create a deep hierarchy (20 levels)
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
local current = root
for i = 1, 20 do
local child = Element.new({
id = "child_" .. i,
width = 50,
height = 50,
parent = current,
}, Element.defaultDependencies)
table.insert(current.children, child)
current = child
end
-- This should trigger a hierarchy depth warning
root:layoutChildren()
-- Check that element was created successfully despite warning
luaunit.assertNotNil(current)
luaunit.assertEquals(current:getHierarchyDepth(), 20)
end
-- Test element count warning
function TestPerformanceWarnings:testElementCountWarning()
-- Create a container with many children (simulating 1000+ elements)
local root = Element.new({
id = "root",
width = 1000,
height = 1000,
}, Element.defaultDependencies)
-- Add many child elements
for i = 1, 50 do -- Keep test fast, just verify the counting logic works
local child = Element.new({
id = "child_" .. i,
width = 20,
height = 20,
parent = root,
}, Element.defaultDependencies)
table.insert(root.children, child)
end
local count = root:countElements()
-- Note: Due to test isolation issues with shared state, count may be doubled
luaunit.assertTrue(count >= 51, "Should count at least 51 elements (root + 50 children), got " .. count)
end
-- Test animation count warning
function TestPerformanceWarnings:testAnimationTracking()
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
-- Add some animated children
for i = 1, 3 do
local child = Element.new({
id = "animated_child_" .. i,
width = 20,
height = 20,
parent = root,
}, Element.defaultDependencies)
-- Add mock animation
child.animation = {
update = function()
return false
end,
interpolate = function()
return { width = 20, height = 20 }
end,
}
table.insert(root.children, child)
end
local animCount = root:_countActiveAnimations()
-- Note: Due to test isolation issues with shared state, count may be doubled
luaunit.assertTrue(animCount >= 3, "Should count at least 3 animations, got " .. animCount)
end
-- Test warnings can be disabled
function TestPerformanceWarnings:testWarningsCanBeDisabled()
perf.warningsEnabled = false
-- Create deep hierarchy
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
local current = root
for i = 1, 20 do
local child = Element.new({
id = "child_" .. i,
width = 50,
height = 50,
parent = current,
}, Element.defaultDependencies)
table.insert(current.children, child)
current = child
end
-- Should not trigger warning (but should still create elements)
root:layoutChildren()
luaunit.assertEquals(current:getHierarchyDepth(), 20)
-- Re-enable for other tests
perf.warningsEnabled = true
end
-- Test layout recalculation tracking
function TestPerformanceWarnings:testLayoutRecalculationTracking()
local root = Element.new({
id = "root",
width = 100,
height = 100,
}, Element.defaultDependencies)
-- Layout multiple times (simulating layout thrashing)
for i = 1, 5 do
root:layoutChildren()
end
-- Should complete without crashing
luaunit.assertNotNil(root)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,773 +0,0 @@
-- Bug-finding and error handling tests for Renderer and TextEditor
-- Tests edge cases, nil handling, division by zero, invalid inputs, etc.
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local FlexLove = require("FlexLove")
FlexLove.init()
-- ============================================================================
-- Renderer Bug Tests
-- ============================================================================
TestRendererBugs = {}
function TestRendererBugs:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestRendererBugs:tearDown()
FlexLove.endFrame()
end
function TestRendererBugs:test_nil_background_color()
-- Should handle nil backgroundColor gracefully
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
backgroundColor = nil,
})
luaunit.assertNotNil(element)
luaunit.assertNotNil(element.backgroundColor)
end
function TestRendererBugs:test_invalid_opacity()
-- Opacity > 1
local element = FlexLove.new({
id = "test1",
width = 100,
height = 100,
opacity = 5,
})
luaunit.assertNotNil(element)
-- Negative opacity
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
opacity = -1,
})
luaunit.assertNotNil(element2)
-- NaN opacity
local element3 = FlexLove.new({
id = "test3",
width = 100,
height = 100,
opacity = 0 / 0,
})
luaunit.assertNotNil(element3)
end
function TestRendererBugs:test_invalid_corner_radius()
-- Negative corner radius
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
cornerRadius = -10,
})
luaunit.assertNotNil(element)
-- Huge corner radius (larger than element)
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
cornerRadius = 1000,
})
luaunit.assertNotNil(element2)
end
function TestRendererBugs:test_invalid_border_config()
-- Non-boolean border values
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
border = {
top = "yes",
right = 1,
bottom = nil,
left = {},
},
})
luaunit.assertNotNil(element)
end
function TestRendererBugs:test_missing_image_path()
-- Non-existent image path
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
imagePath = "/nonexistent/path/to/image.png",
})
luaunit.assertNotNil(element)
end
function TestRendererBugs:test_invalid_object_fit()
-- Invalid objectFit value
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
imagePath = "test.png",
objectFit = "invalid-value",
})
luaunit.assertNotNil(element)
luaunit.assertEquals(element.objectFit, "invalid-value") -- Should store but might break rendering
end
function TestRendererBugs:test_zero_dimensions()
-- Zero width
local element = FlexLove.new({
id = "test1",
width = 0,
height = 100,
})
luaunit.assertNotNil(element)
-- Zero height
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 0,
})
luaunit.assertNotNil(element2)
-- Both zero
local element3 = FlexLove.new({
id = "test3",
width = 0,
height = 0,
})
luaunit.assertNotNil(element3)
end
function TestRendererBugs:test_negative_dimensions()
-- Negative width
local element = FlexLove.new({
id = "test1",
width = -100,
height = 100,
})
luaunit.assertNotNil(element)
-- Negative height
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = -100,
})
luaunit.assertNotNil(element2)
end
function TestRendererBugs:test_text_rendering_with_nil_text()
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
text = nil,
})
luaunit.assertNotNil(element)
end
function TestRendererBugs:test_text_rendering_with_empty_string()
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
text = "",
})
luaunit.assertNotNil(element)
luaunit.assertEquals(element.text, "")
end
function TestRendererBugs:test_text_rendering_with_very_long_text()
local longText = string.rep("A", 10000)
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
text = longText,
})
luaunit.assertNotNil(element)
end
function TestRendererBugs:test_text_rendering_with_special_characters()
-- Newlines
local element1 = FlexLove.new({
id = "test1",
width = 100,
height = 100,
text = "Line1\nLine2\nLine3",
})
luaunit.assertNotNil(element1)
-- Tabs
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
text = "Col1\tCol2\tCol3",
})
luaunit.assertNotNil(element2)
-- Unicode
local element3 = FlexLove.new({
id = "test3",
width = 100,
height = 100,
text = "Hello 世界 🌍",
})
luaunit.assertNotNil(element3)
end
function TestRendererBugs:test_invalid_text_align()
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
text = "Test",
textAlign = "invalid-alignment",
})
luaunit.assertNotNil(element)
end
function TestRendererBugs:test_invalid_text_size()
-- Zero text size
local element1 = FlexLove.new({
id = "test1",
width = 100,
height = 100,
text = "Test",
textSize = 0,
})
luaunit.assertNotNil(element1)
-- Negative text size
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
text = "Test",
textSize = -10,
})
luaunit.assertNotNil(element2)
-- Huge text size
local element3 = FlexLove.new({
id = "test3",
width = 100,
height = 100,
text = "Test",
textSize = 10000,
})
luaunit.assertNotNil(element3)
end
function TestRendererBugs:test_blur_with_invalid_intensity()
-- Negative intensity
local element1 = FlexLove.new({
id = "test1",
width = 100,
height = 100,
contentBlur = { intensity = -10, quality = 5 },
})
luaunit.assertNotNil(element1)
-- Intensity > 100
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
backdropBlur = { intensity = 200, quality = 5 },
})
luaunit.assertNotNil(element2)
end
function TestRendererBugs:test_blur_with_invalid_quality()
-- Quality < 1
local element1 = FlexLove.new({
id = "test1",
width = 100,
height = 100,
contentBlur = { intensity = 10, quality = 0 },
})
luaunit.assertNotNil(element1)
-- Quality > 10
local element2 = FlexLove.new({
id = "test2",
width = 100,
height = 100,
contentBlur = { intensity = 10, quality = 100 },
})
luaunit.assertNotNil(element2)
end
function TestRendererBugs:test_theme_with_invalid_component()
local element = FlexLove.new({
id = "test",
width = 100,
height = 100,
theme = "nonexistent-theme",
themeComponent = "nonexistent-component",
})
luaunit.assertNotNil(element)
end
-- ============================================================================
-- TextEditor Bug Tests
-- ============================================================================
TestTextEditorBugs = {}
function TestTextEditorBugs:setUp()
love.window.setMode(1920, 1080)
FlexLove.beginFrame()
end
function TestTextEditorBugs:tearDown()
FlexLove.endFrame()
end
function TestTextEditorBugs:test_editable_without_text()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
})
luaunit.assertNotNil(element)
luaunit.assertEquals(element.text, "")
end
function TestTextEditorBugs:test_editable_with_nil_text()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = nil,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_cursor_position_beyond_text_length()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello",
})
-- Try to set cursor beyond text length
if element._textEditor then
element._textEditor:setCursorPosition(1000)
-- Should clamp to text length
luaunit.assertTrue(element._textEditor:getCursorPosition() <= 5)
end
end
function TestTextEditorBugs:test_cursor_position_negative()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello",
})
if element._textEditor then
element._textEditor:setCursorPosition(-10)
-- Should clamp to 0
luaunit.assertEquals(element._textEditor:getCursorPosition(), 0)
end
end
function TestTextEditorBugs:test_selection_with_invalid_range()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello World",
})
if element._textEditor then
-- Start > end
element._textEditor:setSelection(10, 2)
luaunit.assertNotNil(element._textEditor:getSelection())
-- Both beyond text length
element._textEditor:setSelection(100, 200)
luaunit.assertNotNil(element._textEditor:getSelection())
-- Negative values
element._textEditor:setSelection(-5, -1)
luaunit.assertNotNil(element._textEditor:getSelection())
end
end
function TestTextEditorBugs:test_insert_text_at_invalid_position()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello",
})
if element._textEditor then
-- Insert beyond text length
element._textEditor:insertText(" World", 1000)
luaunit.assertNotNil(element._textEditor:getText())
-- Insert at negative position
element._textEditor:insertText("X", -10)
luaunit.assertNotNil(element._textEditor:getText())
end
end
function TestTextEditorBugs:test_delete_text_with_invalid_range()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello World",
})
if element._textEditor then
local originalText = element._textEditor:getText()
-- Delete beyond text length
element._textEditor:deleteText(5, 1000)
luaunit.assertNotNil(element._textEditor:getText())
-- Delete with negative positions
element._textEditor:deleteText(-10, -5)
luaunit.assertNotNil(element._textEditor:getText())
-- Delete with start > end
element._textEditor:deleteText(10, 5)
luaunit.assertNotNil(element._textEditor:getText())
end
end
function TestTextEditorBugs:test_max_length_zero()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "",
maxLength = 0,
})
if element._textEditor then
element._textEditor:setText("Should not appear")
luaunit.assertEquals(element._textEditor:getText(), "")
end
end
function TestTextEditorBugs:test_max_length_negative()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Test",
maxLength = -10,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_max_lines_zero()
local element = FlexLove.new({
id = "test",
width = 200,
height = 100,
editable = true,
multiline = true,
text = "",
maxLines = 0,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_multiline_with_very_long_lines()
local longLine = string.rep("A", 10000)
local element = FlexLove.new({
id = "test",
width = 200,
height = 100,
editable = true,
multiline = true,
text = longLine,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_text_wrap_with_zero_width()
local element = FlexLove.new({
id = "test",
width = 0,
height = 100,
editable = true,
multiline = true,
textWrap = "word",
text = "This should wrap",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_password_mode_with_empty_text()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
passwordMode = true,
text = "",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_input_type_number_with_non_numeric()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
inputType = "number",
text = "abc123def",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_cursor_blink_rate_zero()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
cursorBlinkRate = 0,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_cursor_blink_rate_negative()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
cursorBlinkRate = -1,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_text_editor_update_with_invalid_dt()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Test",
})
if element._textEditor then
-- Negative dt
element._textEditor:update(-1)
-- NaN dt
element._textEditor:update(0 / 0)
-- Infinite dt
element._textEditor:update(math.huge)
-- All should handle gracefully
luaunit.assertNotNil(element._textEditor)
end
end
function TestTextEditorBugs:test_placeholder_with_text()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Actual text",
placeholder = "Placeholder",
})
luaunit.assertNotNil(element)
luaunit.assertEquals(element.text, "Actual text")
end
function TestTextEditorBugs:test_sanitization_with_malicious_input()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "<script>alert('xss')</script>",
sanitize = true,
})
luaunit.assertNotNil(element)
-- Text should be sanitized
luaunit.assertNotNil(element.text)
end
function TestTextEditorBugs:test_text_overflow_with_no_scrollable()
local element = FlexLove.new({
id = "test",
width = 50,
height = 30,
editable = true,
text = "This is a very long text that will overflow",
textOverflow = "ellipsis",
scrollable = false,
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_auto_grow_with_fixed_height()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
multiline = true,
autoGrow = true,
text = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_select_on_focus_with_empty_text()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
selectOnFocus = true,
text = "",
})
luaunit.assertNotNil(element)
if element._textEditor then
element._textEditor:focus()
-- Should not crash with empty text
luaunit.assertNotNil(element._textEditor)
end
end
function TestTextEditorBugs:test_word_navigation_with_no_words()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = " ", -- Only spaces
})
if element._textEditor then
element._textEditor:moveCursorToNextWord()
luaunit.assertNotNil(element._textEditor:getCursorPosition())
element._textEditor:moveCursorToPreviousWord()
luaunit.assertNotNil(element._textEditor:getCursorPosition())
end
end
function TestTextEditorBugs:test_word_navigation_with_single_character()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "A",
})
if element._textEditor then
element._textEditor:moveCursorToNextWord()
luaunit.assertNotNil(element._textEditor:getCursorPosition())
end
end
function TestTextEditorBugs:test_multiline_with_only_newlines()
local element = FlexLove.new({
id = "test",
width = 200,
height = 100,
editable = true,
multiline = true,
text = "\n\n\n\n",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_text_with_null_bytes()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Hello\0World",
})
luaunit.assertNotNil(element)
end
function TestTextEditorBugs:test_concurrent_focus_blur()
local element = FlexLove.new({
id = "test",
width = 200,
height = 30,
editable = true,
text = "Test",
})
if element._textEditor then
element._textEditor:focus()
element._textEditor:blur()
element._textEditor:focus()
element._textEditor:blur()
luaunit.assertNotNil(element._textEditor)
end
end
-- Run tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,537 +0,0 @@
-- Test suite for text sanitization functions
-- Tests sanitizeText, validateTextInput, escapeHtml, escapeLuaPattern, stripNonPrintable
package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Load love stub before anything else
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local utils = require("modules.utils")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
-- Test suite for sanitizeText
TestSanitizeText = {}
function TestSanitizeText:testSanitizeText_NilInput()
local result = utils.sanitizeText(nil)
luaunit.assertEquals(result, "")
end
function TestSanitizeText:testSanitizeText_NonStringInput()
local result = utils.sanitizeText(123)
luaunit.assertEquals(result, "123")
result = utils.sanitizeText(true)
luaunit.assertEquals(result, "true")
end
function TestSanitizeText:testSanitizeText_NullBytes()
local result = utils.sanitizeText("Hello\0World")
luaunit.assertEquals(result, "HelloWorld")
end
function TestSanitizeText:testSanitizeText_ControlCharacters()
-- Test removal of various control characters
local result = utils.sanitizeText("Hello\1\2\3World")
luaunit.assertEquals(result, "HelloWorld")
end
function TestSanitizeText:testSanitizeText_AllowNewlines()
local result = utils.sanitizeText("Hello\nWorld", { allowNewlines = true })
luaunit.assertEquals(result, "Hello\nWorld")
result = utils.sanitizeText("Hello\nWorld", { allowNewlines = false })
luaunit.assertEquals(result, "HelloWorld")
end
function TestSanitizeText:testSanitizeText_AllowTabs()
local result = utils.sanitizeText("Hello\tWorld", { allowTabs = true })
luaunit.assertEquals(result, "Hello\tWorld")
result = utils.sanitizeText("Hello\tWorld", { allowTabs = false })
luaunit.assertEquals(result, "HelloWorld")
end
function TestSanitizeText:testSanitizeText_TrimWhitespace()
local result = utils.sanitizeText(" Hello World ", { trimWhitespace = true })
luaunit.assertEquals(result, "Hello World")
result = utils.sanitizeText(" Hello World ", { trimWhitespace = false })
luaunit.assertEquals(result, " Hello World ")
end
function TestSanitizeText:testSanitizeText_MaxLength()
local longText = string.rep("a", 100)
local result = utils.sanitizeText(longText, { maxLength = 50 })
luaunit.assertEquals(#result, 50)
luaunit.assertEquals(result, string.rep("a", 50))
end
function TestSanitizeText:testSanitizeText_DefaultOptions()
-- Test with default options
local result = utils.sanitizeText(" Hello\nWorld\t ")
luaunit.assertEquals(result, "Hello\nWorld")
end
function TestSanitizeText:testSanitizeText_EmptyString()
local result = utils.sanitizeText("")
luaunit.assertEquals(result, "")
end
function TestSanitizeText:testSanitizeText_OnlyWhitespace()
local result = utils.sanitizeText(" \n \t ", { trimWhitespace = true })
luaunit.assertEquals(result, "")
end
-- Test suite for validateTextInput
TestValidateTextInput = {}
function TestValidateTextInput:testValidateTextInput_MinLength()
local valid, err = utils.validateTextInput("abc", { minLength = 3 })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
valid, err = utils.validateTextInput("ab", { minLength = 3 })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "at least")
end
function TestValidateTextInput:testValidateTextInput_MaxLength()
local valid, err = utils.validateTextInput("abc", { maxLength = 5 })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
valid, err = utils.validateTextInput("abcdef", { maxLength = 5 })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "at most")
end
function TestValidateTextInput:testValidateTextInput_Pattern()
local valid, err = utils.validateTextInput("123", { pattern = "^%d+$" })
luaunit.assertTrue(valid)
luaunit.assertNil(err)
valid, err = utils.validateTextInput("abc", { pattern = "^%d+$" })
luaunit.assertFalse(valid)
luaunit.assertNotNil(err)
end
function TestValidateTextInput:testValidateTextInput_AllowedChars()
local valid, err = utils.validateTextInput("abc123", { allowedChars = "a-z0-9" })
luaunit.assertTrue(valid)
valid, err = utils.validateTextInput("abc123!", { allowedChars = "a-z0-9" })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "invalid characters")
end
function TestValidateTextInput:testValidateTextInput_ForbiddenChars()
local valid, err = utils.validateTextInput("hello world", { forbiddenChars = "@#$%%" })
luaunit.assertTrue(valid)
valid, err = utils.validateTextInput("hello@world", { forbiddenChars = "@#$%%" })
luaunit.assertFalse(valid)
luaunit.assertStrContains(err, "forbidden characters")
end
function TestValidateTextInput:testValidateTextInput_NoRules()
local valid, err = utils.validateTextInput("anything goes")
luaunit.assertTrue(valid)
luaunit.assertNil(err)
end
-- Test suite for escapeHtml
TestEscapeHtml = {}
function TestEscapeHtml:testEscapeHtml_BasicChars()
local result = utils.escapeHtml("<script>alert('xss')</script>")
luaunit.assertEquals(result, "&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;")
end
function TestEscapeHtml:testEscapeHtml_Ampersand()
local result = utils.escapeHtml("Tom & Jerry")
luaunit.assertEquals(result, "Tom &amp; Jerry")
end
function TestEscapeHtml:testEscapeHtml_Quotes()
local result = utils.escapeHtml('Hello "World"')
luaunit.assertEquals(result, "Hello &quot;World&quot;")
result = utils.escapeHtml("It's fine")
luaunit.assertEquals(result, "It&#39;s fine")
end
function TestEscapeHtml:testEscapeHtml_NilInput()
local result = utils.escapeHtml(nil)
luaunit.assertEquals(result, "")
end
function TestEscapeHtml:testEscapeHtml_EmptyString()
local result = utils.escapeHtml("")
luaunit.assertEquals(result, "")
end
-- Test suite for escapeLuaPattern
TestEscapeLuaPattern = {}
function TestEscapeLuaPattern:testEscapeLuaPattern_SpecialChars()
local result = utils.escapeLuaPattern("^$()%.[]*+-?")
luaunit.assertEquals(result, "%^%$%(%)%%%.%[%]%*%+%-%?")
end
function TestEscapeLuaPattern:testEscapeLuaPattern_NormalText()
local result = utils.escapeLuaPattern("Hello World")
luaunit.assertEquals(result, "Hello World")
end
function TestEscapeLuaPattern:testEscapeLuaPattern_NilInput()
local result = utils.escapeLuaPattern(nil)
luaunit.assertEquals(result, "")
end
function TestEscapeLuaPattern:testEscapeLuaPattern_UsageInMatch()
-- Test that escaped pattern can be used safely
local text = "The price is $10.50"
local escaped = utils.escapeLuaPattern("$10.50")
local found = text:match(escaped)
luaunit.assertEquals(found, "$10.50")
end
-- Test suite for stripNonPrintable
TestStripNonPrintable = {}
function TestStripNonPrintable:testStripNonPrintable_BasicText()
local result = utils.stripNonPrintable("Hello World")
luaunit.assertEquals(result, "Hello World")
end
function TestStripNonPrintable:testStripNonPrintable_KeepNewlines()
local result = utils.stripNonPrintable("Hello\nWorld")
luaunit.assertEquals(result, "Hello\nWorld")
end
function TestStripNonPrintable:testStripNonPrintable_KeepTabs()
local result = utils.stripNonPrintable("Hello\tWorld")
luaunit.assertEquals(result, "Hello\tWorld")
end
function TestStripNonPrintable:testStripNonPrintable_RemoveControlChars()
local result = utils.stripNonPrintable("Hello\1\2\3World")
luaunit.assertEquals(result, "HelloWorld")
end
function TestStripNonPrintable:testStripNonPrintable_NilInput()
local result = utils.stripNonPrintable(nil)
luaunit.assertEquals(result, "")
end
function TestStripNonPrintable:testStripNonPrintable_EmptyString()
local result = utils.stripNonPrintable("")
luaunit.assertEquals(result, "")
end
-- Mock dependencies
local mockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local mockStateManager = {
getState = function()
return nil
end,
setState = function() end,
}
-- Test Suite for TextEditor Sanitization
TestTextEditorSanitization = {}
---Helper to create a TextEditor instance
function TestTextEditorSanitization:_createEditor(config)
local TextEditor = require("modules.TextEditor")
config = config or {}
local deps = {
Context = mockContext,
StateManager = mockStateManager,
Color = Color,
utils = utils,
}
return TextEditor.new(config, deps)
end
-- === Sanitization Enabled Tests ===
function TestTextEditorSanitization:test_sanitization_enabled_by_default()
local editor = self:_createEditor({ editable = true })
luaunit.assertTrue(editor.sanitize)
end
function TestTextEditorSanitization:test_sanitization_can_be_disabled()
local editor = self:_createEditor({ editable = true, sanitize = false })
luaunit.assertFalse(editor.sanitize)
end
function TestTextEditorSanitization:test_setText_removes_control_characters()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello\x00World\x01Test")
luaunit.assertEquals(editor:getText(), "HelloWorldTest")
end
function TestTextEditorSanitization:test_setText_preserves_valid_text()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello World! 123")
luaunit.assertEquals(editor:getText(), "Hello World! 123")
end
function TestTextEditorSanitization:test_setText_removes_multiple_control_chars()
local editor = self:_createEditor({ editable = true })
editor:setText("Test\x00\x01\x02\x03\x04Data")
luaunit.assertEquals(editor:getText(), "TestData")
end
function TestTextEditorSanitization:test_setText_with_sanitization_disabled()
local editor = self:_createEditor({ editable = true, sanitize = false })
editor:setText("Hello\x00World")
luaunit.assertEquals(editor:getText(), "Hello\x00World")
end
function TestTextEditorSanitization:test_setText_skip_sanitization_parameter()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello\x00World", true) -- skipSanitization = true
luaunit.assertEquals(editor:getText(), "Hello\x00World")
end
-- === Initial Text Sanitization ===
function TestTextEditorSanitization:test_initial_text_is_sanitized()
local editor = self:_createEditor({
editable = true,
text = "Initial\x00Text\x01",
})
luaunit.assertEquals(editor:getText(), "InitialText")
end
function TestTextEditorSanitization:test_initial_text_preserved_when_disabled()
local editor = self:_createEditor({
editable = true,
sanitize = false,
text = "Initial\x00Text",
})
luaunit.assertEquals(editor:getText(), "Initial\x00Text")
end
-- === insertText Sanitization ===
function TestTextEditorSanitization:test_insertText_sanitizes_input()
local editor = self:_createEditor({ editable = true, text = "Hello" })
editor:insertText("\x00World", 5)
luaunit.assertEquals(editor:getText(), "HelloWorld")
end
function TestTextEditorSanitization:test_insertText_with_valid_text()
local editor = self:_createEditor({ editable = true, text = "Hello" })
editor:insertText(" World", 5)
luaunit.assertEquals(editor:getText(), "Hello World")
end
function TestTextEditorSanitization:test_insertText_empty_after_sanitization()
local editor = self:_createEditor({ editable = true, text = "Hello" })
editor:insertText("\x00\x01\x02", 5) -- Only control chars
luaunit.assertEquals(editor:getText(), "Hello") -- Should remain unchanged
end
-- === Length Limiting ===
function TestTextEditorSanitization:test_maxLength_enforced_on_setText()
local editor = self:_createEditor({ editable = true, maxLength = 10 })
editor:setText("This is a very long text")
luaunit.assertEquals(#editor:getText(), 10)
end
function TestTextEditorSanitization:test_maxLength_enforced_on_insertText()
local editor = self:_createEditor({ editable = true, text = "12345", maxLength = 10 })
editor:insertText("67890", 5) -- This would make it exactly 10
luaunit.assertEquals(editor:getText(), "1234567890")
end
function TestTextEditorSanitization:test_maxLength_truncates_excess()
local editor = self:_createEditor({ editable = true, text = "12345", maxLength = 10 })
editor:insertText("67890EXTRA", 5) -- Would exceed limit
luaunit.assertEquals(editor:getText(), "1234567890")
end
function TestTextEditorSanitization:test_maxLength_prevents_insert_when_full()
local editor = self:_createEditor({ editable = true, text = "1234567890", maxLength = 10 })
editor:insertText("X", 10)
luaunit.assertEquals(editor:getText(), "1234567890") -- Should not change
end
-- === Newline Handling ===
function TestTextEditorSanitization:test_newlines_allowed_in_multiline()
local editor = self:_createEditor({ editable = true, multiline = true })
editor:setText("Line1\nLine2")
luaunit.assertEquals(editor:getText(), "Line1\nLine2")
end
function TestTextEditorSanitization:test_newlines_removed_in_singleline()
local editor = self:_createEditor({ editable = true, multiline = false })
editor:setText("Line1\nLine2")
luaunit.assertEquals(editor:getText(), "Line1Line2")
end
function TestTextEditorSanitization:test_allowNewlines_explicit_false()
local editor = self:_createEditor({
editable = true,
multiline = true,
allowNewlines = false,
})
editor:setText("Line1\nLine2")
luaunit.assertEquals(editor:getText(), "Line1Line2")
end
-- === Tab Handling ===
function TestTextEditorSanitization:test_tabs_allowed_by_default()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello\tWorld")
luaunit.assertEquals(editor:getText(), "Hello\tWorld")
end
function TestTextEditorSanitization:test_tabs_removed_when_disabled()
local editor = self:_createEditor({
editable = true,
allowTabs = false,
})
editor:setText("Hello\tWorld")
luaunit.assertEquals(editor:getText(), "HelloWorld")
end
-- === Custom Sanitizer ===
function TestTextEditorSanitization:test_custom_sanitizer_used()
local customSanitizer = function(text)
return text:upper()
end
local editor = self:_createEditor({
editable = true,
customSanitizer = customSanitizer,
})
editor:setText("hello world")
luaunit.assertEquals(editor:getText(), "HELLO WORLD")
end
function TestTextEditorSanitization:test_custom_sanitizer_with_control_chars()
local customSanitizer = function(text)
-- Custom sanitizer that replaces control chars with *
return text:gsub("[\x00-\x1F]", "*")
end
local editor = self:_createEditor({
editable = true,
customSanitizer = customSanitizer,
})
editor:setText("Hello\x00World\x01")
luaunit.assertEquals(editor:getText(), "Hello*World*")
end
-- === Unicode and Special Characters ===
function TestTextEditorSanitization:test_unicode_preserved()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello 世界 🌍")
luaunit.assertEquals(editor:getText(), "Hello 世界 🌍")
end
function TestTextEditorSanitization:test_emoji_preserved()
local editor = self:_createEditor({ editable = true })
editor:setText("😀😃😄😁")
luaunit.assertEquals(editor:getText(), "😀😃😄😁")
end
function TestTextEditorSanitization:test_special_chars_preserved()
local editor = self:_createEditor({ editable = true })
editor:setText("!@#$%^&*()_+-=[]{}|;':\",./<>?")
luaunit.assertEquals(editor:getText(), "!@#$%^&*()_+-=[]{}|;':\",./<>?")
end
-- === Edge Cases ===
function TestTextEditorSanitization:test_empty_string()
local editor = self:_createEditor({ editable = true })
editor:setText("")
luaunit.assertEquals(editor:getText(), "")
end
function TestTextEditorSanitization:test_only_control_characters()
local editor = self:_createEditor({ editable = true })
editor:setText("\x00\x01\x02\x03")
luaunit.assertEquals(editor:getText(), "")
end
function TestTextEditorSanitization:test_nil_text()
local editor = self:_createEditor({ editable = true })
editor:setText(nil)
luaunit.assertEquals(editor:getText(), "")
end
function TestTextEditorSanitization:test_very_long_text_with_control_chars()
local editor = self:_createEditor({ editable = true })
local longText = string.rep("Hello\x00World", 100)
editor:setText(longText)
luaunit.assertStrContains(editor:getText(), "Hello")
luaunit.assertStrContains(editor:getText(), "World")
luaunit.assertNotStrContains(editor:getText(), "\x00")
end
function TestTextEditorSanitization:test_mixed_valid_and_invalid()
local editor = self:_createEditor({ editable = true })
editor:setText("Valid\x00Text\x01With\x02Control\x03Chars")
luaunit.assertEquals(editor:getText(), "ValidTextWithControlChars")
end
-- === Whitespace Handling ===
function TestTextEditorSanitization:test_spaces_preserved()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello World")
luaunit.assertEquals(editor:getText(), "Hello World")
end
function TestTextEditorSanitization:test_leading_trailing_spaces_preserved()
local editor = self:_createEditor({ editable = true })
editor:setText(" Hello World ")
luaunit.assertEquals(editor:getText(), " Hello World ")
end
-- === Integration Tests ===
function TestTextEditorSanitization:test_cursor_position_after_sanitization()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello")
editor:insertText("\x00World", 5)
-- Cursor should be at end of "HelloWorld" = position 10
luaunit.assertEquals(editor._cursorPosition, 10)
end
function TestTextEditorSanitization:test_multiple_operations()
local editor = self:_createEditor({ editable = true })
editor:setText("Hello")
editor:insertText(" ", 5)
editor:insertText("World\x00", 6)
luaunit.assertEquals(editor:getText(), "Hello World")
end
-- Run tests if this file is executed directly
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,730 +0,0 @@
-- Tests for shorthand syntax features (flexDirection aliases, margin/padding shortcuts)
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local FlexLove = require("FlexLove")
TestShorthandSyntax = {}
function TestShorthandSyntax:setUp()
FlexLove.init()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
end
function TestShorthandSyntax:tearDown()
FlexLove.endFrame()
FlexLove.destroy()
end
-- ============================================================================
-- FlexDirection Aliases Tests
-- ============================================================================
function TestShorthandSyntax:testFlexDirectionRowEqualsHorizontal()
-- Create two containers: one with "row", one with "horizontal"
local containerRow = FlexLove.new({
id = "container-row",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "row",
})
local containerHorizontal = FlexLove.new({
id = "container-horizontal",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
})
-- Both should have the same internal flexDirection value
luaunit.assertEquals(containerRow.flexDirection, "horizontal")
luaunit.assertEquals(containerHorizontal.flexDirection, "horizontal")
luaunit.assertEquals(containerRow.flexDirection, containerHorizontal.flexDirection)
end
function TestShorthandSyntax:testFlexDirectionColumnEqualsVertical()
-- Create two containers: one with "column", one with "vertical"
local containerColumn = FlexLove.new({
id = "container-column",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "column",
})
local containerVertical = FlexLove.new({
id = "container-vertical",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "vertical",
})
-- Both should have the same internal flexDirection value
luaunit.assertEquals(containerColumn.flexDirection, "vertical")
luaunit.assertEquals(containerVertical.flexDirection, "vertical")
luaunit.assertEquals(containerColumn.flexDirection, containerVertical.flexDirection)
end
function TestShorthandSyntax:testFlexDirectionRowLayoutMatchesHorizontal()
-- Create two containers with children: one with "row", one with "horizontal"
local containerRow = FlexLove.new({
id = "container-row",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "row",
})
local containerHorizontal = FlexLove.new({
id = "container-horizontal",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
})
-- Add identical children to both
for i = 1, 3 do
FlexLove.new({
id = "child-row-" .. i,
width = 100,
height = 50,
parent = containerRow,
})
FlexLove.new({
id = "child-horizontal-" .. i,
width = 100,
height = 50,
parent = containerHorizontal,
})
end
-- Trigger layout
FlexLove.resize(800, 600)
-- Children should be laid out identically
for i = 1, 3 do
local childRow = containerRow.children[i]
local childHorizontal = containerHorizontal.children[i]
luaunit.assertEquals(childRow.x, childHorizontal.x, "Child " .. i .. " x position should match")
luaunit.assertEquals(childRow.y, childHorizontal.y, "Child " .. i .. " y position should match")
luaunit.assertEquals(childRow.width, childHorizontal.width, "Child " .. i .. " width should match")
luaunit.assertEquals(childRow.height, childHorizontal.height, "Child " .. i .. " height should match")
end
end
function TestShorthandSyntax:testFlexDirectionColumnLayoutMatchesVertical()
-- Create two containers with children: one with "column", one with "vertical"
local containerColumn = FlexLove.new({
id = "container-column",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "column",
})
local containerVertical = FlexLove.new({
id = "container-vertical",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "vertical",
})
-- Add identical children to both
for i = 1, 3 do
FlexLove.new({
id = "child-column-" .. i,
width = 100,
height = 50,
parent = containerColumn,
})
FlexLove.new({
id = "child-vertical-" .. i,
width = 100,
height = 50,
parent = containerVertical,
})
end
-- Trigger layout
FlexLove.resize(800, 600)
-- Children should be laid out identically
for i = 1, 3 do
local childColumn = containerColumn.children[i]
local childVertical = containerVertical.children[i]
luaunit.assertEquals(childColumn.x, childVertical.x, "Child " .. i .. " x position should match")
luaunit.assertEquals(childColumn.y, childVertical.y, "Child " .. i .. " y position should match")
luaunit.assertEquals(childColumn.width, childVertical.width, "Child " .. i .. " width should match")
luaunit.assertEquals(childColumn.height, childVertical.height, "Child " .. i .. " height should match")
end
end
function TestShorthandSyntax:testFlexDirectionRowWithJustifyContent()
-- Test that "row" works with justifyContent like "horizontal" does
local containerRow = FlexLove.new({
id = "container-row",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "row",
justifyContent = "space-between",
})
local containerHorizontal = FlexLove.new({
id = "container-horizontal",
width = 400,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "space-between",
})
-- Add children
for i = 1, 3 do
FlexLove.new({
id = "child-row-" .. i,
width = 80,
height = 50,
parent = containerRow,
})
FlexLove.new({
id = "child-horizontal-" .. i,
width = 80,
height = 50,
parent = containerHorizontal,
})
end
FlexLove.resize(800, 600)
-- Verify space-between worked the same way
for i = 1, 3 do
local childRow = containerRow.children[i]
local childHorizontal = containerHorizontal.children[i]
luaunit.assertEquals(childRow.x, childHorizontal.x, "space-between should work identically")
end
end
function TestShorthandSyntax:testFlexDirectionColumnWithAlignItems()
-- Test that "column" works with alignItems like "vertical" does
local containerColumn = FlexLove.new({
id = "container-column",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "column",
alignItems = "center",
})
local containerVertical = FlexLove.new({
id = "container-vertical",
width = 200,
height = 400,
positioning = "flex",
flexDirection = "vertical",
alignItems = "center",
})
-- Add children
for i = 1, 3 do
FlexLove.new({
id = "child-column-" .. i,
width = 80,
height = 50,
parent = containerColumn,
})
FlexLove.new({
id = "child-vertical-" .. i,
width = 80,
height = 50,
parent = containerVertical,
})
end
FlexLove.resize(800, 600)
-- Verify center alignment worked the same way
for i = 1, 3 do
local childColumn = containerColumn.children[i]
local childVertical = containerVertical.children[i]
luaunit.assertEquals(childColumn.x, childVertical.x, "center alignment should work identically")
end
end
-- ============================================================================
-- Margin Shorthand Tests
-- ============================================================================
function TestShorthandSyntax:testMarginNumberEqualsMarginTable()
-- Create two elements: one with margin=10, one with margin={top=10,right=10,bottom=10,left=10}
local parent = FlexLove.new({
id = "parent",
width = 400,
height = 400,
})
local elementShorthand = FlexLove.new({
id = "element-shorthand",
width = 100,
height = 100,
margin = 10,
parent = parent,
})
local elementExplicit = FlexLove.new({
id = "element-explicit",
width = 100,
height = 100,
margin = { top = 10, right = 10, bottom = 10, left = 10 },
parent = parent,
})
-- Both should have the same margin values
luaunit.assertEquals(elementShorthand.margin.top, 10)
luaunit.assertEquals(elementShorthand.margin.right, 10)
luaunit.assertEquals(elementShorthand.margin.bottom, 10)
luaunit.assertEquals(elementShorthand.margin.left, 10)
luaunit.assertEquals(elementShorthand.margin.top, elementExplicit.margin.top)
luaunit.assertEquals(elementShorthand.margin.right, elementExplicit.margin.right)
luaunit.assertEquals(elementShorthand.margin.bottom, elementExplicit.margin.bottom)
luaunit.assertEquals(elementShorthand.margin.left, elementExplicit.margin.left)
end
function TestShorthandSyntax:testMarginShorthandLayoutMatchesExplicit()
-- Create container with two children in column layout
local container = FlexLove.new({
id = "container",
width = 400,
height = 400,
positioning = "flex",
flexDirection = "column",
})
local elementShorthand = FlexLove.new({
id = "element-shorthand",
width = 100,
height = 100,
margin = 20,
parent = container,
})
local elementExplicit = FlexLove.new({
id = "element-explicit",
width = 100,
height = 100,
margin = { top = 20, right = 20, bottom = 20, left = 20 },
parent = container,
})
FlexLove.resize(800, 600)
-- The explicit element should be positioned 20px below the shorthand element
-- shorthand: y=20 (top margin), height=100, bottom margin=20 → next starts at 140
-- explicit: y=140+20=160
luaunit.assertEquals(elementShorthand.y, 20, "Shorthand element should have top margin applied")
luaunit.assertEquals(elementExplicit.y, 160, "Explicit element should be positioned after shorthand's bottom margin")
end
function TestShorthandSyntax:testMarginZeroShorthand()
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
margin = 0,
})
luaunit.assertEquals(element.margin.top, 0)
luaunit.assertEquals(element.margin.right, 0)
luaunit.assertEquals(element.margin.bottom, 0)
luaunit.assertEquals(element.margin.left, 0)
end
function TestShorthandSyntax:testMarginLargeValueShorthand()
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
margin = 100,
})
luaunit.assertEquals(element.margin.top, 100)
luaunit.assertEquals(element.margin.right, 100)
luaunit.assertEquals(element.margin.bottom, 100)
luaunit.assertEquals(element.margin.left, 100)
end
function TestShorthandSyntax:testMarginDecimalShorthand()
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
margin = 15.5,
})
luaunit.assertEquals(element.margin.top, 15.5)
luaunit.assertEquals(element.margin.right, 15.5)
luaunit.assertEquals(element.margin.bottom, 15.5)
luaunit.assertEquals(element.margin.left, 15.5)
end
-- ============================================================================
-- Padding Shorthand Tests
-- ============================================================================
function TestShorthandSyntax:testPaddingNumberEqualsPaddingTable()
-- Create two elements: one with padding=20, one with padding={top=20,right=20,bottom=20,left=20}
local elementShorthand = FlexLove.new({
id = "element-shorthand",
width = 200,
height = 200,
padding = 20,
})
local elementExplicit = FlexLove.new({
id = "element-explicit",
width = 200,
height = 200,
padding = { top = 20, right = 20, bottom = 20, left = 20 },
})
-- Both should have the same padding values
luaunit.assertEquals(elementShorthand.padding.top, 20)
luaunit.assertEquals(elementShorthand.padding.right, 20)
luaunit.assertEquals(elementShorthand.padding.bottom, 20)
luaunit.assertEquals(elementShorthand.padding.left, 20)
luaunit.assertEquals(elementShorthand.padding.top, elementExplicit.padding.top)
luaunit.assertEquals(elementShorthand.padding.right, elementExplicit.padding.right)
luaunit.assertEquals(elementShorthand.padding.bottom, elementExplicit.padding.bottom)
luaunit.assertEquals(elementShorthand.padding.left, elementExplicit.padding.left)
end
function TestShorthandSyntax:testPaddingShorthandAffectsContentArea()
-- Create container with padding and a child
local containerShorthand = FlexLove.new({
id = "container-shorthand",
width = 200,
height = 200,
padding = 30,
})
local containerExplicit = FlexLove.new({
id = "container-explicit",
width = 200,
height = 200,
padding = { top = 30, right = 30, bottom = 30, left = 30 },
})
-- Add children
local childShorthand = FlexLove.new({
id = "child-shorthand",
width = "100%",
height = "100%",
parent = containerShorthand,
})
local childExplicit = FlexLove.new({
id = "child-explicit",
width = "100%",
height = "100%",
parent = containerExplicit,
})
FlexLove.resize(800, 600)
-- Children should have the same dimensions (200 - 30*2 = 140)
luaunit.assertEquals(childShorthand.width, 140)
luaunit.assertEquals(childShorthand.height, 140)
luaunit.assertEquals(childExplicit.width, 140)
luaunit.assertEquals(childExplicit.height, 140)
luaunit.assertEquals(childShorthand.width, childExplicit.width)
luaunit.assertEquals(childShorthand.height, childExplicit.height)
end
function TestShorthandSyntax:testPaddingZeroShorthand()
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
padding = 0,
})
luaunit.assertEquals(element.padding.top, 0)
luaunit.assertEquals(element.padding.right, 0)
luaunit.assertEquals(element.padding.bottom, 0)
luaunit.assertEquals(element.padding.left, 0)
end
function TestShorthandSyntax:testPaddingLargeValueShorthand()
local element = FlexLove.new({
id = "element",
width = 300,
height = 300,
padding = 50,
})
luaunit.assertEquals(element.padding.top, 50)
luaunit.assertEquals(element.padding.right, 50)
luaunit.assertEquals(element.padding.bottom, 50)
luaunit.assertEquals(element.padding.left, 50)
end
function TestShorthandSyntax:testPaddingDecimalShorthand()
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
padding = 12.5,
})
luaunit.assertEquals(element.padding.top, 12.5)
luaunit.assertEquals(element.padding.right, 12.5)
luaunit.assertEquals(element.padding.bottom, 12.5)
luaunit.assertEquals(element.padding.left, 12.5)
end
-- ============================================================================
-- Combined Tests (FlexDirection + Margin/Padding)
-- ============================================================================
function TestShorthandSyntax:testRowWithMarginShorthand()
local container = FlexLove.new({
id = "container",
width = 500,
height = 200,
flexDirection = "row", -- Alias for "horizontal"
})
for i = 1, 3 do
FlexLove.new({
id = "child-" .. i,
width = 100,
height = 100,
margin = 10, -- Shorthand
parent = container,
})
end
FlexLove.resize(800, 600)
-- First child: x=10 (left margin)
-- Second child: x=10+100+10 (first child's margin-right) + 10 (own margin-left) = 130
-- Third child: x=130+100+10+10 = 250
luaunit.assertEquals(container.children[1].x, 10)
luaunit.assertEquals(container.children[2].x, 130)
luaunit.assertEquals(container.children[3].x, 250)
end
function TestShorthandSyntax:testColumnWithPaddingShorthand()
local container = FlexLove.new({
id = "container",
width = 200,
height = 500,
flexDirection = "column", -- Alias for "vertical"
padding = 15, -- Shorthand
})
for i = 1, 3 do
FlexLove.new({
id = "child-" .. i,
width = 100,
height = 50,
parent = container,
})
end
FlexLove.resize(800, 600)
-- Children should start at y=15 (top padding)
-- First child: y=15
-- Second child: y=15+50=65
-- Third child: y=65+50=115
luaunit.assertEquals(container.children[1].y, 15)
luaunit.assertEquals(container.children[2].y, 65)
luaunit.assertEquals(container.children[3].y, 115)
end
function TestShorthandSyntax:testRowAndColumnAliasesWithAllShorthands()
-- Complex test: use all shorthands together
local container = FlexLove.new({
id = "container",
width = 600,
height = 400,
flexDirection = "row", -- Alias
padding = 20, -- Shorthand
})
for i = 1, 2 do
FlexLove.new({
id = "child-" .. i,
width = 150,
height = 100,
margin = 10, -- Shorthand
parent = container,
})
end
FlexLove.resize(800, 600)
-- First child: x=20 (container padding) + 10 (own margin) = 30
-- Second child: x=30 + 150 + 10 (first child's margin-right) + 10 (own margin-left) = 200
luaunit.assertEquals(container.children[1].x, 30)
luaunit.assertEquals(container.children[2].x, 200)
-- Both children should be at y=20 (container padding) + 10 (own margin) = 30
luaunit.assertEquals(container.children[1].y, 30)
luaunit.assertEquals(container.children[2].y, 30)
end
function TestShorthandSyntax:testNestedContainersWithShorthands()
-- Test nested containers with multiple shorthand usages
local outerContainer = FlexLove.new({
id = "outer",
width = 500,
height = 500,
flexDirection = "column", -- Alias
padding = 25, -- Shorthand
})
local innerContainer = FlexLove.new({
id = "inner",
width = 400,
height = 200,
flexDirection = "row", -- Alias
margin = 15, -- Shorthand
padding = 10, -- Shorthand
parent = outerContainer,
})
local child = FlexLove.new({
id = "child",
width = 100,
height = 100,
margin = 5, -- Shorthand
parent = innerContainer,
})
FlexLove.resize(800, 600)
-- Inner container position: y=25 (outer padding) + 15 (own margin) = 40
luaunit.assertEquals(innerContainer.y, 40)
-- Child position within inner:
-- x relative to inner = 10 (inner padding) + 5 (own margin) = 15
-- y relative to inner = 10 (inner padding) + 5 (own margin) = 15
local expectedChildX = innerContainer.x + 15
local expectedChildY = innerContainer.y + 15
luaunit.assertEquals(child.x, expectedChildX)
luaunit.assertEquals(child.y, expectedChildY)
end
-- ============================================================================
-- Edge Cases
-- ============================================================================
function TestShorthandSyntax:testFlexDirectionAliasDoesNotAffectOtherValues()
local element = FlexLove.new({
id = "element",
width = 200,
height = 200,
positioning = "flex",
flexDirection = "row",
justifyContent = "center",
alignItems = "center",
})
-- Using alias shouldn't affect other properties
luaunit.assertEquals(element.justifyContent, "center")
luaunit.assertEquals(element.alignItems, "center")
end
function TestShorthandSyntax:testMarginShorthandDoesNotAffectPadding()
local element = FlexLove.new({
id = "element",
width = 200,
height = 200,
margin = 10,
padding = { top = 5, right = 5, bottom = 5, left = 5 },
})
-- Margin shorthand shouldn't affect padding
luaunit.assertEquals(element.padding.top, 5)
luaunit.assertEquals(element.padding.right, 5)
luaunit.assertEquals(element.padding.bottom, 5)
luaunit.assertEquals(element.padding.left, 5)
end
function TestShorthandSyntax:testPaddingShorthandDoesNotAffectMargin()
local element = FlexLove.new({
id = "element",
width = 200,
height = 200,
padding = 20,
margin = { top = 10, right = 10, bottom = 10, left = 10 },
})
-- Padding shorthand shouldn't affect margin
luaunit.assertEquals(element.margin.top, 10)
luaunit.assertEquals(element.margin.right, 10)
luaunit.assertEquals(element.margin.bottom, 10)
luaunit.assertEquals(element.margin.left, 10)
end
function TestShorthandSyntax:testBothMarginAndPaddingShorthands()
local element = FlexLove.new({
id = "element",
width = 200,
height = 200,
margin = 15,
padding = 25,
})
-- Both should be expanded correctly
luaunit.assertEquals(element.margin.top, 15)
luaunit.assertEquals(element.margin.right, 15)
luaunit.assertEquals(element.margin.bottom, 15)
luaunit.assertEquals(element.margin.left, 15)
luaunit.assertEquals(element.padding.top, 25)
luaunit.assertEquals(element.padding.right, 25)
luaunit.assertEquals(element.padding.bottom, 25)
luaunit.assertEquals(element.padding.left, 25)
end
function TestShorthandSyntax:testNegativeMarginShorthand()
-- Negative margins should work
local element = FlexLove.new({
id = "element",
width = 100,
height = 100,
margin = -5,
})
luaunit.assertEquals(element.margin.top, -5)
luaunit.assertEquals(element.margin.right, -5)
luaunit.assertEquals(element.margin.bottom, -5)
luaunit.assertEquals(element.margin.left, -5)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,679 +0,0 @@
-- Comprehensive coverage tests for TextEditor module
-- Focuses on multiline, wrapping, keyboard/mouse interactions, and advanced features
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local FlexLove = require("FlexLove")
FlexLove.init()
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")
-- Mock dependencies
local MockContext = {
_immediateMode = false,
_focusedElement = nil,
setFocusedElement = function(self, element)
self._focusedElement = element
end,
}
local MockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state) end,
}
-- Helper to create TextEditor
local function createTextEditor(config)
config = config or {}
return TextEditor.new(config, {
Context = MockContext,
StateManager = MockStateManager,
Color = Color,
utils = utils,
})
end
-- Helper to create mock element
local function createMockElement(width, height)
return {
_stateId = "test-element",
width = width or 200,
height = height or 100,
x = 10,
y = 10,
_absoluteX = 10,
_absoluteY = 10,
padding = {top = 5, right = 5, bottom = 5, left = 5},
_borderBoxWidth = (width or 200) + 10,
_borderBoxHeight = (height or 100) + 10,
getScaledContentPadding = function(self)
return self.padding
end,
_renderer = {
getFont = function(self, element)
return {
getWidth = function(text) return #text * 8 end,
getHeight = function() return 16 end,
}
end,
wrapLine = function(element, line, maxWidth)
-- Simple word wrapping simulation
line = tostring(line or "")
maxWidth = tonumber(maxWidth) or 1000
local words = {}
for word in line:gmatch("%S+") do
table.insert(words, word)
end
local wrapped = {}
local currentLine = ""
local startIdx = 0
for i, word in ipairs(words) do
local testLine = currentLine == "" and word or (currentLine .. " " .. word)
if #testLine * 8 <= maxWidth then
currentLine = testLine
else
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
startIdx = startIdx + #currentLine + 1
end
currentLine = word
end
end
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
end
return #wrapped > 0 and wrapped or {{text = line, startIdx = 0, endIdx = #line}}
end,
},
}
end
-- ============================================================================
-- Multiline Text Tests
-- ============================================================================
TestTextEditorMultiline = {}
function TestTextEditorMultiline:test_multiline_split_lines()
local editor = createTextEditor({multiline = true, text = "Line 1\nLine 2\nLine 3"})
local element = createMockElement()
editor:initialize(element)
editor:_splitLines()
luaunit.assertNotNil(editor._lines)
luaunit.assertEquals(#editor._lines, 3)
luaunit.assertEquals(editor._lines[1], "Line 1")
luaunit.assertEquals(editor._lines[2], "Line 2")
luaunit.assertEquals(editor._lines[3], "Line 3")
end
function TestTextEditorMultiline:test_multiline_cursor_movement()
local editor = createTextEditor({multiline = true, text = "Line 1\nLine 2"})
local element = createMockElement()
editor:initialize(element)
-- Move to end
editor:moveCursorToEnd()
luaunit.assertEquals(editor:getCursorPosition(), 13) -- "Line 1\nLine 2" = 13 chars
-- Move to start
editor:moveCursorToStart()
luaunit.assertEquals(editor:getCursorPosition(), 0)
end
function TestTextEditorMultiline:test_multiline_line_start_end()
-- TODO: moveCursorToLineStart/End not yet implemented for multiline
-- Currently just moves to start/end of entire text
luaunit.skip("Multiline line start/end not implemented")
end
function TestTextEditorMultiline:test_multiline_insert_newline()
local editor = createTextEditor({multiline = true, text = "Hello"})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(5)
editor:insertText("\n", 5)
editor:insertText("World", 6)
luaunit.assertEquals(editor:getText(), "Hello\nWorld")
end
-- ============================================================================
-- Text Wrapping Tests
-- ============================================================================
TestTextEditorWrapping = {}
function TestTextEditorWrapping:test_word_wrapping()
local editor = createTextEditor({
multiline = true,
textWrap = "word",
text = "This is a long line that should wrap"
})
local element = createMockElement(50, 100) -- Very narrow width to force wrapping
editor:initialize(element)
editor._textDirty = true
editor:_updateTextIfDirty()
luaunit.assertNotNil(editor._wrappedLines)
luaunit.assertTrue(#editor._wrappedLines >= 1) -- Should have wrapped lines
end
function TestTextEditorWrapping:test_char_wrapping()
local editor = createTextEditor({
multiline = true,
textWrap = "char",
text = "Verylongwordwithoutspaces"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:_calculateWrapping()
luaunit.assertNotNil(editor._wrappedLines)
end
function TestTextEditorWrapping:test_no_wrapping()
local editor = createTextEditor({
multiline = true,
textWrap = false,
text = "This is a long line that should not wrap"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:_calculateWrapping()
-- With textWrap = false, _wrappedLines should be nil
luaunit.assertNil(editor._wrappedLines)
end
-- ============================================================================
-- Selection Tests
-- ============================================================================
TestTextEditorSelection = {}
function TestTextEditorSelection:test_select_all()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:selectAll()
luaunit.assertTrue(editor:hasSelection())
luaunit.assertEquals(editor:getSelectedText(), "Hello World")
end
function TestTextEditorSelection:test_get_selected_text()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 5)
luaunit.assertEquals(editor:getSelectedText(), "Hello")
end
function TestTextEditorSelection:test_delete_selection()
local editor = createTextEditor({text = "Hello World", editable = true})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 5)
editor:deleteSelection()
luaunit.assertEquals(editor:getText(), " World")
end
function TestTextEditorSelection:test_clear_selection()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 5)
luaunit.assertTrue(editor:hasSelection())
editor:clearSelection()
luaunit.assertFalse(editor:hasSelection())
end
function TestTextEditorSelection:test_selection_reversed()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
-- Set selection in reverse order
editor:setSelection(5, 0)
local start, endPos = editor:getSelection()
luaunit.assertEquals(start, 0)
luaunit.assertEquals(endPos, 5)
end
-- ============================================================================
-- Focus and Blur Tests
-- ============================================================================
TestTextEditorFocus = {}
function TestTextEditorFocus:test_focus()
local focusCalled = false
local editor = createTextEditor({
text = "Test",
onFocus = function() focusCalled = true end
})
local element = createMockElement()
editor:initialize(element)
editor:focus()
luaunit.assertTrue(editor:isFocused())
luaunit.assertTrue(focusCalled)
end
function TestTextEditorFocus:test_blur()
local blurCalled = false
local editor = createTextEditor({
text = "Test",
onBlur = function() blurCalled = true end
})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:blur()
luaunit.assertFalse(editor:isFocused())
luaunit.assertTrue(blurCalled)
end
function TestTextEditorFocus:test_select_on_focus()
local editor = createTextEditor({
text = "Hello World",
selectOnFocus = true
})
local element = createMockElement()
editor:initialize(element)
editor:focus()
luaunit.assertTrue(editor:hasSelection())
luaunit.assertEquals(editor:getSelectedText(), "Hello World")
end
-- ============================================================================
-- Keyboard Input Tests
-- ============================================================================
TestTextEditorKeyboard = {}
function TestTextEditorKeyboard:test_handle_text_input()
local editor = createTextEditor({text = "", editable = true})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleTextInput("H")
editor:handleTextInput("i")
luaunit.assertEquals(editor:getText(), "Hi")
end
function TestTextEditorKeyboard:test_handle_backspace()
local editor = createTextEditor({text = "Hello", editable = true})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(5)
editor:handleKeyPress("backspace", "backspace", false)
luaunit.assertEquals(editor:getText(), "Hell")
end
function TestTextEditorKeyboard:test_handle_delete()
local editor = createTextEditor({text = "Hello", editable = true})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(0)
editor:handleKeyPress("delete", "delete", false)
luaunit.assertEquals(editor:getText(), "ello")
end
function TestTextEditorKeyboard:test_handle_return_multiline()
local editor = createTextEditor({text = "Hello", editable = true, multiline = true})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(5)
editor:handleKeyPress("return", "return", false)
editor:handleTextInput("World")
luaunit.assertEquals(editor:getText(), "Hello\nWorld")
end
function TestTextEditorKeyboard:test_handle_return_singleline()
local onEnterCalled = false
local editor = createTextEditor({
text = "Hello",
editable = true,
multiline = false,
onEnter = function() onEnterCalled = true end
})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleKeyPress("return", "return", false)
luaunit.assertTrue(onEnterCalled)
luaunit.assertEquals(editor:getText(), "Hello") -- Should not add newline
end
function TestTextEditorKeyboard:test_handle_tab()
-- TODO: Tab key insertion not yet implemented via handleKeyPress
-- Tab characters are allowed via handleTextInput but not triggered by tab key
luaunit.skip("Tab key insertion not implemented")
end
function TestTextEditorKeyboard:test_handle_home_end()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(5)
-- Home key
editor:handleKeyPress("home", "home", false)
luaunit.assertEquals(editor:getCursorPosition(), 0)
-- End key
editor:handleKeyPress("end", "end", false)
luaunit.assertEquals(editor:getCursorPosition(), 11)
end
function TestTextEditorKeyboard:test_handle_arrow_keys()
local editor = createTextEditor({text = "Hello"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(2)
-- Right arrow
editor:handleKeyPress("right", "right", false)
luaunit.assertEquals(editor:getCursorPosition(), 3)
-- Left arrow
editor:handleKeyPress("left", "left", false)
luaunit.assertEquals(editor:getCursorPosition(), 2)
end
-- ============================================================================
-- Mouse Interaction Tests
-- ============================================================================
TestTextEditorMouse = {}
function TestTextEditorMouse:test_mouse_to_text_position()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
-- Click in middle of text (approximate)
local pos = editor:mouseToTextPosition(40, 10)
luaunit.assertNotNil(pos)
luaunit.assertTrue(pos >= 0 and pos <= 11)
end
function TestTextEditorMouse:test_handle_single_click()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:handleTextClick(40, 10, 1)
luaunit.assertTrue(editor:getCursorPosition() >= 0)
end
function TestTextEditorMouse:test_handle_double_click_selects_word()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
-- Double click on first word
editor:handleTextClick(20, 10, 2)
luaunit.assertTrue(editor:hasSelection())
local selected = editor:getSelectedText()
luaunit.assertTrue(selected == "Hello" or selected == "World")
end
function TestTextEditorMouse:test_handle_triple_click_selects_all()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleTextClick(20, 10, 3)
luaunit.assertTrue(editor:hasSelection())
luaunit.assertEquals(editor:getSelectedText(), "Hello World")
end
function TestTextEditorMouse:test_handle_text_drag()
local editor = createTextEditor({text = "Hello World"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
-- Start at text beginning (element x=10 + padding left=5 = 15)
editor:handleTextClick(15, 15, 1)
-- Verify mouseDownPosition was set
luaunit.assertNotNil(editor._mouseDownPosition)
-- Drag to position much further right (should be different position)
editor:handleTextDrag(100, 15)
-- If still no selection, the positions might be the same - just verify drag was called
luaunit.assertTrue(editor:hasSelection() or editor._mouseDownPosition ~= nil)
end
-- ============================================================================
-- Password Mode Tests
-- ============================================================================
TestTextEditorPassword = {}
function TestTextEditorPassword:test_password_mode_masks_text()
local editor = createTextEditor({text = "secret123", passwordMode = true})
local element = createMockElement()
editor:initialize(element)
-- Password mode should be enabled
luaunit.assertTrue(editor.passwordMode)
-- The actual text should still be stored
luaunit.assertEquals(editor:getText(), "secret123")
end
-- ============================================================================
-- Input Validation Tests
-- ============================================================================
TestTextEditorValidation = {}
function TestTextEditorValidation:test_number_input_type()
local editor = createTextEditor({text = "", editable = true, inputType = "number"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleTextInput("123")
luaunit.assertEquals(editor:getText(), "123")
-- Non-numeric input should be sanitized
editor:handleTextInput("abc")
-- Sanitization behavior depends on implementation
end
function TestTextEditorValidation:test_max_length()
local editor = createTextEditor({text = "", editable = true, maxLength = 5})
local element = createMockElement()
editor:initialize(element)
editor:setText("12345")
luaunit.assertEquals(editor:getText(), "12345")
editor:setText("123456789")
luaunit.assertEquals(editor:getText(), "12345") -- Should be truncated
end
function TestTextEditorValidation:test_max_lines()
-- TODO: maxLines validation not yet enforced
-- Property exists but setText doesn't validate against it
luaunit.skip("maxLines validation not implemented")
end
-- ============================================================================
-- Cursor Blink and Update Tests
-- ============================================================================
TestTextEditorUpdate = {}
function TestTextEditorUpdate:test_update_cursor_blink()
local editor = createTextEditor({text = "Test", cursorBlinkRate = 0.5})
local element = createMockElement()
editor:initialize(element)
editor:focus()
-- Initial state
local initialVisible = editor._cursorVisible
-- Update for half the blink rate
editor:update(0.25)
luaunit.assertEquals(editor._cursorVisible, initialVisible)
-- Update to complete blink cycle
editor:update(0.26)
luaunit.assertNotEquals(editor._cursorVisible, initialVisible)
end
function TestTextEditorUpdate:test_cursor_blink_pause()
local editor = createTextEditor({text = "Test", cursorBlinkRate = 0.5})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:_resetCursorBlink(true) -- Pause blink
luaunit.assertTrue(editor._cursorBlinkPaused)
luaunit.assertTrue(editor._cursorVisible)
end
-- ============================================================================
-- Word Navigation Tests
-- ============================================================================
TestTextEditorWordNav = {}
function TestTextEditorWordNav:test_move_to_next_word()
local editor = createTextEditor({text = "Hello World Test"})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(0)
editor:moveCursorToNextWord()
luaunit.assertTrue(editor:getCursorPosition() > 0)
end
function TestTextEditorWordNav:test_move_to_previous_word()
local editor = createTextEditor({text = "Hello World Test"})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(16)
editor:moveCursorToPreviousWord()
luaunit.assertTrue(editor:getCursorPosition() < 16)
end
-- ============================================================================
-- Sanitization Tests
-- ============================================================================
TestTextEditorSanitization = {}
function TestTextEditorSanitization:test_sanitize_disabled()
local editor = createTextEditor({text = "", editable = true, sanitize = false})
local element = createMockElement()
editor:initialize(element)
editor:setText("<script>alert('xss')</script>", true) -- Skip sanitization
-- With sanitization disabled, text should be preserved
luaunit.assertNotNil(editor:getText())
end
function TestTextEditorSanitization:test_custom_sanitizer()
local customCalled = false
local editor = createTextEditor({
text = "",
editable = true,
customSanitizer = function(text)
customCalled = true
return text:upper()
end
})
local element = createMockElement()
editor:initialize(element)
editor:setText("hello")
luaunit.assertTrue(customCalled)
luaunit.assertEquals(editor:getText(), "HELLO")
end
function TestTextEditorSanitization:test_disallow_newlines()
local editor = createTextEditor({
text = "",
editable = true,
multiline = false,
allowNewlines = false
})
local element = createMockElement()
editor:initialize(element)
editor:setText("Hello\nWorld")
-- Newlines should be removed or replaced
luaunit.assertNil(editor:getText():find("\n"))
end
function TestTextEditorSanitization:test_disallow_tabs()
local editor = createTextEditor({
text = "",
editable = true,
allowTabs = false
})
local element = createMockElement()
editor:initialize(element)
editor:setText("Hello\tWorld")
-- Tabs should be removed or replaced
luaunit.assertNil(editor:getText():find("\t"))
end
-- Run tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,610 +0,0 @@
-- Edge case and unhappy path tests for TextEditor module
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")
TestTextEditorEdgeCases = {}
-- Mock dependencies
local MockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local MockStateManager = {
getState = function(id)
return nil
end,
updateState = function(id, state) end,
}
-- Helper to create TextEditor with dependencies
local function createTextEditor(config)
config = config or {}
return TextEditor.new(config, {
Context = MockContext,
StateManager = MockStateManager,
Color = Color,
utils = utils,
})
end
-- Helper to create mock element
local function createMockElement()
return {
_stateId = "test-element-1",
width = 200,
height = 30,
padding = {top = 0, right = 0, bottom = 0, left = 0},
_renderer = {
getFont = function()
return {
getWidth = function(text) return #text * 8 end,
getHeight = function() return 16 end,
}
end,
wrapLine = function(element, line, maxWidth)
return {{text = line, startIdx = 0, endIdx = #line}}
end,
},
}
end
-- ============================================================================
-- Constructor Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testNewWithInvalidCursorBlinkRate()
-- Negative blink rate
local editor = createTextEditor({cursorBlinkRate = -1})
luaunit.assertEquals(editor.cursorBlinkRate, -1) -- Should accept any value
end
function TestTextEditorEdgeCases:testNewWithZeroCursorBlinkRate()
-- Zero blink rate (would cause rapid blinking)
local editor = createTextEditor({cursorBlinkRate = 0})
luaunit.assertEquals(editor.cursorBlinkRate, 0)
end
function TestTextEditorEdgeCases:testNewWithVeryLargeCursorBlinkRate()
-- Very large blink rate
local editor = createTextEditor({cursorBlinkRate = 1000})
luaunit.assertEquals(editor.cursorBlinkRate, 1000)
end
function TestTextEditorEdgeCases:testNewWithNegativeMaxLength()
-- Negative maxLength should be ignored
local editor = createTextEditor({maxLength = -10})
luaunit.assertEquals(editor.maxLength, -10) -- Module doesn't validate, just stores
end
function TestTextEditorEdgeCases:testNewWithZeroMaxLength()
-- Zero maxLength (no text allowed)
local editor = createTextEditor({maxLength = 0})
editor:setText("test")
luaunit.assertEquals(editor:getText(), "") -- Should be empty
end
function TestTextEditorEdgeCases:testNewWithInvalidInputType()
-- Invalid input type (not validated by constructor)
local editor = createTextEditor({inputType = "invalid"})
luaunit.assertEquals(editor.inputType, "invalid")
end
function TestTextEditorEdgeCases:testNewWithCustomSanitizerReturnsNil()
-- Custom sanitizer that returns nil
local editor = createTextEditor({
customSanitizer = function(text)
return nil
end,
})
editor:setText("test")
-- Should fallback to original text when sanitizer returns nil
luaunit.assertEquals(editor:getText(), "test")
end
function TestTextEditorEdgeCases:testNewWithCustomSanitizerThrowsError()
-- Custom sanitizer that throws error
local editor = createTextEditor({
customSanitizer = function(text)
error("Intentional error")
end,
})
-- Should error when setting text
luaunit.assertErrorMsgContains("Intentional error", function()
editor:setText("test")
end)
end
-- ============================================================================
-- Text Buffer Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testSetTextWithEmptyString()
local editor = createTextEditor()
editor:setText("")
luaunit.assertEquals(editor:getText(), "")
end
function TestTextEditorEdgeCases:testSetTextWithNil()
local editor = createTextEditor({text = "initial"})
editor:setText(nil)
luaunit.assertEquals(editor:getText(), "") -- Should default to empty string
end
function TestTextEditorEdgeCases:testInsertTextAtInvalidPosition()
local editor = createTextEditor({text = "Hello"})
-- Insert at negative position (should treat as 0)
editor:insertText("X", -10)
luaunit.assertStrContains(editor:getText(), "X")
end
function TestTextEditorEdgeCases:testInsertTextBeyondLength()
local editor = createTextEditor({text = "Hello"})
-- Insert beyond text length
editor:insertText("X", 1000)
luaunit.assertStrContains(editor:getText(), "X")
end
function TestTextEditorEdgeCases:testInsertTextWithEmptyString()
local editor = createTextEditor({text = "Hello"})
editor:insertText("", 2)
luaunit.assertEquals(editor:getText(), "Hello") -- Should remain unchanged
end
function TestTextEditorEdgeCases:testInsertTextWhenAtMaxLength()
local editor = createTextEditor({text = "Hello", maxLength = 5})
editor:insertText("X", 5)
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
end
function TestTextEditorEdgeCases:testDeleteTextWithInvertedRange()
local editor = createTextEditor({text = "Hello World"})
editor:deleteText(10, 2) -- End before start
-- Should swap and delete
luaunit.assertEquals(#editor:getText(), 3) -- Deleted 8 characters
end
function TestTextEditorEdgeCases:testDeleteTextBeyondBounds()
local editor = createTextEditor({text = "Hello"})
editor:deleteText(10, 20) -- Beyond text length
luaunit.assertEquals(editor:getText(), "Hello") -- Should clamp to bounds
end
function TestTextEditorEdgeCases:testDeleteTextWithNegativePositions()
local editor = createTextEditor({text = "Hello"})
editor:deleteText(-5, -1) -- Negative positions
luaunit.assertEquals(editor:getText(), "Hello") -- Should clamp to 0
end
function TestTextEditorEdgeCases:testReplaceTextWithEmptyString()
local editor = createTextEditor({text = "Hello World"})
editor:replaceText(0, 5, "")
luaunit.assertEquals(editor:getText(), " World") -- Should just delete
end
function TestTextEditorEdgeCases:testReplaceTextBeyondBounds()
local editor = createTextEditor({text = "Hello"})
editor:replaceText(10, 20, "X")
luaunit.assertStrContains(editor:getText(), "X")
end
-- ============================================================================
-- UTF-8 Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testSetTextWithUTF8Emoji()
local editor = createTextEditor()
editor:setText("Hello 👋 World 🌍")
luaunit.assertStrContains(editor:getText(), "👋")
luaunit.assertStrContains(editor:getText(), "🌍")
end
function TestTextEditorEdgeCases:testInsertTextWithUTF8Characters()
local editor = createTextEditor({text = "Hello"})
editor:insertText("世界", 5) -- Chinese characters
luaunit.assertStrContains(editor:getText(), "世界")
end
function TestTextEditorEdgeCases:testCursorPositionWithUTF8()
local editor = createTextEditor({text = "Hello👋World"})
-- Cursor positions should be in characters, not bytes
editor:setCursorPosition(6) -- After emoji
luaunit.assertEquals(editor:getCursorPosition(), 6)
end
function TestTextEditorEdgeCases:testDeleteTextWithUTF8()
local editor = createTextEditor({text = "Hello👋World"})
editor:deleteText(5, 6) -- Delete emoji
luaunit.assertEquals(editor:getText(), "HelloWorld")
end
function TestTextEditorEdgeCases:testMaxLengthWithUTF8()
local editor = createTextEditor({maxLength = 10})
editor:setText("Hello👋👋👋👋👋") -- 10 characters including emojis
luaunit.assertTrue(utf8.len(editor:getText()) <= 10)
end
-- ============================================================================
-- Cursor Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testSetCursorPositionNegative()
local editor = createTextEditor({text = "Hello"})
editor:setCursorPosition(-10)
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should clamp to 0
end
function TestTextEditorEdgeCases:testSetCursorPositionBeyondLength()
local editor = createTextEditor({text = "Hello"})
editor:setCursorPosition(1000)
luaunit.assertEquals(editor:getCursorPosition(), 5) -- Should clamp to length
end
function TestTextEditorEdgeCases:testSetCursorPositionWithNonNumber()
local editor = createTextEditor({text = "Hello"})
editor._cursorPosition = "invalid" -- Corrupt state
editor:setCursorPosition(3)
luaunit.assertEquals(editor:getCursorPosition(), 3) -- Should validate and fix
end
function TestTextEditorEdgeCases:testMoveCursorByZero()
local editor = createTextEditor({text = "Hello"})
editor:setCursorPosition(2)
editor:moveCursorBy(0)
luaunit.assertEquals(editor:getCursorPosition(), 2) -- Should stay same
end
function TestTextEditorEdgeCases:testMoveCursorByLargeNegative()
local editor = createTextEditor({text = "Hello"})
editor:setCursorPosition(2)
editor:moveCursorBy(-1000)
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should clamp to 0
end
function TestTextEditorEdgeCases:testMoveCursorByLargePositive()
local editor = createTextEditor({text = "Hello"})
editor:setCursorPosition(2)
editor:moveCursorBy(1000)
luaunit.assertEquals(editor:getCursorPosition(), 5) -- Should clamp to length
end
function TestTextEditorEdgeCases:testMoveCursorToPreviousWordAtStart()
local editor = createTextEditor({text = "Hello World"})
editor:moveCursorToStart()
editor:moveCursorToPreviousWord()
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should stay at start
end
function TestTextEditorEdgeCases:testMoveCursorToNextWordAtEnd()
local editor = createTextEditor({text = "Hello World"})
editor:moveCursorToEnd()
editor:moveCursorToNextWord()
luaunit.assertEquals(editor:getCursorPosition(), 11) -- Should stay at end
end
function TestTextEditorEdgeCases:testMoveCursorWithEmptyBuffer()
local editor = createTextEditor({text = ""})
editor:moveCursorToStart()
luaunit.assertEquals(editor:getCursorPosition(), 0)
editor:moveCursorToEnd()
luaunit.assertEquals(editor:getCursorPosition(), 0)
end
-- ============================================================================
-- Selection Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testSetSelectionWithInvertedRange()
local editor = createTextEditor({text = "Hello World"})
editor:setSelection(10, 2) -- End before start
local start, endPos = editor:getSelection()
luaunit.assertTrue(start <= endPos) -- Should be swapped
end
function TestTextEditorEdgeCases:testSetSelectionBeyondBounds()
local editor = createTextEditor({text = "Hello"})
editor:setSelection(0, 1000)
local start, endPos = editor:getSelection()
luaunit.assertEquals(endPos, 5) -- Should clamp to length
end
function TestTextEditorEdgeCases:testSetSelectionWithNegativePositions()
local editor = createTextEditor({text = "Hello"})
editor:setSelection(-5, -1)
local start, endPos = editor:getSelection()
luaunit.assertEquals(start, 0) -- Should clamp to 0
luaunit.assertEquals(endPos, 0)
end
function TestTextEditorEdgeCases:testSetSelectionWithSameStartEnd()
local editor = createTextEditor({text = "Hello"})
editor:setSelection(2, 2) -- Same position
luaunit.assertFalse(editor:hasSelection()) -- Should be no selection
end
function TestTextEditorEdgeCases:testGetSelectedTextWithNoSelection()
local editor = createTextEditor({text = "Hello"})
luaunit.assertNil(editor:getSelectedText())
end
function TestTextEditorEdgeCases:testDeleteSelectionWithNoSelection()
local editor = createTextEditor({text = "Hello"})
local deleted = editor:deleteSelection()
luaunit.assertFalse(deleted) -- Should return false
luaunit.assertEquals(editor:getText(), "Hello") -- Text unchanged
end
function TestTextEditorEdgeCases:testSelectAllWithEmptyBuffer()
local editor = createTextEditor({text = ""})
editor:selectAll()
luaunit.assertFalse(editor:hasSelection()) -- No selection on empty text
end
function TestTextEditorEdgeCases:testGetSelectionRectsWithEmptyBuffer()
local editor = createTextEditor({text = ""})
local mockElement = createMockElement()
editor:initialize(mockElement)
editor:setSelection(0, 0)
local rects = editor:_getSelectionRects(0, 0)
luaunit.assertEquals(#rects, 0) -- No rects for empty selection
end
-- ============================================================================
-- Focus Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testFocusWithoutElement()
local editor = createTextEditor()
-- Should not error
editor:focus()
luaunit.assertTrue(editor:isFocused())
end
function TestTextEditorEdgeCases:testBlurWithoutElement()
local editor = createTextEditor()
editor:focus()
editor:blur()
luaunit.assertFalse(editor:isFocused())
end
function TestTextEditorEdgeCases:testFocusTwice()
local editor = createTextEditor()
editor:focus()
editor:focus() -- Focus again
luaunit.assertTrue(editor:isFocused()) -- Should remain focused
end
function TestTextEditorEdgeCases:testBlurTwice()
local editor = createTextEditor()
editor:focus()
editor:blur()
editor:blur() -- Blur again
luaunit.assertFalse(editor:isFocused()) -- Should remain blurred
end
-- ============================================================================
-- Mouse Input Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testMouseToTextPositionWithoutElement()
local editor = createTextEditor({text = "Hello"})
local pos = editor:mouseToTextPosition(10, 10)
luaunit.assertEquals(pos, 0) -- Should return 0 without element
end
function TestTextEditorEdgeCases:testMouseToTextPositionWithNilBuffer()
local editor = createTextEditor()
local mockElement = createMockElement()
mockElement.x = 0
mockElement.y = 0
editor:initialize(mockElement)
editor._textBuffer = nil
local pos = editor:mouseToTextPosition(10, 10)
luaunit.assertEquals(pos, 0) -- Should handle nil buffer
end
function TestTextEditorEdgeCases:testMouseToTextPositionWithNegativeCoords()
local editor = createTextEditor({text = "Hello"})
local mockElement = createMockElement()
mockElement.x = 100
mockElement.y = 100
editor:initialize(mockElement)
local pos = editor:mouseToTextPosition(-10, -10)
luaunit.assertTrue(pos >= 0) -- Should clamp to valid position
end
function TestTextEditorEdgeCases:testHandleTextClickWithoutFocus()
local editor = createTextEditor({text = "Hello"})
editor:handleTextClick(10, 10, 1)
-- Should not error, but also won't do anything without focus
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testHandleTextDragWithoutMouseDown()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:handleTextDrag(20, 10) -- Drag without mouseDownPosition
-- Should not error
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testHandleTextClickWithZeroClickCount()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:handleTextClick(10, 10, 0)
-- Should not error
luaunit.assertTrue(true)
end
-- ============================================================================
-- Update Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testUpdateWithoutFocus()
local editor = createTextEditor()
editor:update(1.0) -- Should not update cursor blink
luaunit.assertTrue(true) -- Should not error
end
function TestTextEditorEdgeCases:testUpdateWithNegativeDt()
local editor = createTextEditor()
editor:focus()
editor:update(-1.0) -- Negative delta time
-- Should not error
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testUpdateWithZeroDt()
local editor = createTextEditor()
editor:focus()
editor:update(0) -- Zero delta time
-- Should not error
luaunit.assertTrue(true)
end
-- ============================================================================
-- Key Press Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testHandleKeyPressWithoutFocus()
local editor = createTextEditor({text = "Hello"})
editor:handleKeyPress("backspace", "backspace", false)
luaunit.assertEquals(editor:getText(), "Hello") -- Should not modify
end
function TestTextEditorEdgeCases:testHandleKeyPressBackspaceAtStart()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:moveCursorToStart()
editor:handleKeyPress("backspace", "backspace", false)
luaunit.assertEquals(editor:getText(), "Hello") -- Should not delete
end
function TestTextEditorEdgeCases:testHandleKeyPressDeleteAtEnd()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:moveCursorToEnd()
editor:handleKeyPress("delete", "delete", false)
luaunit.assertEquals(editor:getText(), "Hello") -- Should not delete
end
function TestTextEditorEdgeCases:testHandleKeyPressWithUnknownKey()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:handleKeyPress("unknownkey", "unknownkey", false)
luaunit.assertEquals(editor:getText(), "Hello") -- Should ignore
end
-- ============================================================================
-- Text Input Edge Cases
-- ============================================================================
function TestTextEditorEdgeCases:testHandleTextInputWithoutFocus()
local editor = createTextEditor({text = "Hello"})
editor:handleTextInput("X")
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
end
function TestTextEditorEdgeCases:testHandleTextInputWithEmptyString()
local editor = createTextEditor({text = "Hello"})
editor:focus()
editor:handleTextInput("")
luaunit.assertEquals(editor:getText(), "Hello") -- Should not modify
end
function TestTextEditorEdgeCases:testHandleTextInputWithNewlineInSingleLine()
local editor = createTextEditor({text = "Hello", multiline = false, allowNewlines = false})
editor:focus()
editor:handleTextInput("\n")
-- Should sanitize newline in single-line mode
luaunit.assertFalse(editor:getText():find("\n") ~= nil)
end
function TestTextEditorEdgeCases:testHandleTextInputCallbackReturnsFalse()
local editor = createTextEditor({
text = "Hello",
onTextInput = function(element, text)
return false -- Reject input
end,
})
local mockElement = createMockElement()
editor:initialize(mockElement)
editor:focus()
editor:handleTextInput("X")
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
end
-- ============================================================================
-- Special Cases
-- ============================================================================
function TestTextEditorEdgeCases:testPasswordModeWithEmptyText()
local editor = createTextEditor({passwordMode = true, text = ""})
luaunit.assertEquals(editor:getText(), "")
end
function TestTextEditorEdgeCases:testMultilineWithMaxLines()
local editor = createTextEditor({multiline = true, maxLines = 2})
editor:setText("Line1\nLine2\nLine3\nLine4")
-- MaxLines might not be enforced by setText, depends on implementation
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testTextWrapWithZeroWidth()
local editor = createTextEditor({textWrap = true})
local mockElement = createMockElement()
mockElement.width = 0
editor:initialize(mockElement)
editor:setText("Hello World")
-- Should handle zero width gracefully
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testAutoGrowWithoutElement()
local editor = createTextEditor({autoGrow = true, multiline = true})
editor:updateAutoGrowHeight()
-- Should not error without element
luaunit.assertTrue(true)
end
function TestTextEditorEdgeCases:testGetCursorScreenPositionWithoutElement()
local editor = createTextEditor({text = "Hello"})
local x, y = editor:_getCursorScreenPosition()
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0)
end
function TestTextEditorEdgeCases:testSelectWordAtPositionWithEmptyText()
local editor = createTextEditor({text = ""})
editor:_selectWordAtPosition(0)
luaunit.assertFalse(editor:hasSelection())
end
function TestTextEditorEdgeCases:testSelectWordAtPositionOnWhitespace()
local editor = createTextEditor({text = "Hello World"})
editor:_selectWordAtPosition(7) -- In whitespace
-- Behavior depends on implementation
luaunit.assertTrue(true)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,673 +0,0 @@
-- Extended coverage tests for TextEditor module
-- Focuses on uncovered code paths to increase coverage
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")
-- Mock dependencies
local MockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local MockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state) end,
}
-- Helper to create TextEditor
local function createTextEditor(config)
config = config or {}
return TextEditor.new(config, {
Context = MockContext,
StateManager = MockStateManager,
Color = Color,
utils = utils,
})
end
-- Helper to create mock element with renderer
local function createMockElement(width, height)
return {
_stateId = "test-element",
width = width or 200,
height = height or 100,
padding = {top = 5, right = 5, bottom = 5, left = 5},
x = 10,
y = 10,
_absoluteX = 10,
_absoluteY = 10,
_borderBoxWidth = (width or 200) + 10,
_borderBoxHeight = (height or 100) + 10,
getScaledContentPadding = function(self)
return self.padding
end,
_renderer = {
getFont = function(self, element)
return {
getWidth = function(text) return #text * 8 end,
getHeight = function() return 16 end,
}
end,
wrapLine = function(element, line, maxWidth)
-- Simple word wrapping
line = tostring(line or "")
maxWidth = tonumber(maxWidth) or 1000
local words = {}
for word in line:gmatch("%S+") do
table.insert(words, word)
end
local wrapped = {}
local currentLine = ""
local startIdx = 0
for i, word in ipairs(words) do
local testLine = currentLine == "" and word or (currentLine .. " " .. word)
if #testLine * 8 <= maxWidth then
currentLine = testLine
else
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
startIdx = startIdx + #currentLine + 1
end
currentLine = word
end
end
if currentLine ~= "" then
table.insert(wrapped, {text = currentLine, startIdx = startIdx, endIdx = startIdx + #currentLine})
end
return #wrapped > 0 and wrapped or {{text = line, startIdx = 0, endIdx = #line}}
end,
},
}
end
-- ============================================================================
-- Auto-grow Height Tests
-- ============================================================================
TestTextEditorAutoGrow = {}
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_single_line()
local editor = createTextEditor({
multiline = false,
autoGrow = true,
text = "Single line"
})
local element = createMockElement()
editor:initialize(element)
editor:updateAutoGrowHeight()
-- Single line should not trigger height change
luaunit.assertNotNil(element.height)
end
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_multiline()
local editor = createTextEditor({
multiline = true,
autoGrow = true,
text = "Line 1\nLine 2\nLine 3"
})
local element = createMockElement(200, 50)
editor:initialize(element)
local initialHeight = element.height
editor:updateAutoGrowHeight()
-- Height should be updated based on line count
luaunit.assertNotNil(element.height)
end
function TestTextEditorAutoGrow:test_updateAutoGrowHeight_with_wrapping()
local editor = createTextEditor({
multiline = true,
autoGrow = true,
textWrap = "word",
text = "This is a very long line that will wrap multiple times when displayed"
})
local element = createMockElement(100, 50)
editor:initialize(element)
editor:updateAutoGrowHeight()
-- Should account for wrapped lines
luaunit.assertNotNil(element.height)
end
-- ============================================================================
-- Update and Cursor Blink Tests
-- ============================================================================
TestTextEditorUpdate = {}
function TestTextEditorUpdate:test_update_cursor_blink_pause_resume()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:_resetCursorBlink(true) -- Pause
luaunit.assertTrue(editor._cursorBlinkPaused)
-- Update to resume blink
editor:update(0.6) -- More than 0.5 second pause
luaunit.assertFalse(editor._cursorBlinkPaused)
end
function TestTextEditorUpdate:test_update_not_focused()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
-- Not focused - update should exit early
editor:update(0.1)
luaunit.assertTrue(true) -- Should not crash
end
function TestTextEditorUpdate:test_cursor_blink_cycle()
local editor = createTextEditor({text = "Test", cursorBlinkRate = 0.5})
local element = createMockElement()
editor:initialize(element)
editor:focus()
local initialVisible = editor._cursorVisible
-- Complete a full blink cycle
editor:update(0.5)
luaunit.assertNotEquals(editor._cursorVisible, initialVisible)
end
-- ============================================================================
-- Selection Rectangle Calculation Tests
-- ============================================================================
TestTextEditorSelectionRects = {}
function TestTextEditorSelectionRects:test_getSelectionRects_single_line()
local editor = createTextEditor({text = "Hello World", multiline = false})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 5)
local rects = editor:_getSelectionRects(0, 5)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
luaunit.assertNotNil(rects[1].x)
luaunit.assertNotNil(rects[1].y)
luaunit.assertNotNil(rects[1].width)
luaunit.assertNotNil(rects[1].height)
end
function TestTextEditorSelectionRects:test_getSelectionRects_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2\nLine 3", multiline = true})
local element = createMockElement()
editor:initialize(element)
-- Select across lines
editor:setSelection(0, 14) -- "Line 1\nLine 2"
local rects = editor:_getSelectionRects(0, 14)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
end
function TestTextEditorSelectionRects:test_getSelectionRects_with_wrapping()
local editor = createTextEditor({
text = "This is a long line that wraps",
multiline = true,
textWrap = "word"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:setSelection(0, 20)
local rects = editor:_getSelectionRects(0, 20)
luaunit.assertNotNil(rects)
end
function TestTextEditorSelectionRects:test_getSelectionRects_password_mode()
local editor = createTextEditor({
text = "secret",
passwordMode = true,
multiline = false
})
local element = createMockElement()
editor:initialize(element)
editor:setSelection(0, 6)
local rects = editor:_getSelectionRects(0, 6)
luaunit.assertNotNil(rects)
luaunit.assertTrue(#rects > 0)
end
-- ============================================================================
-- Cursor Screen Position Tests
-- ============================================================================
TestTextEditorCursorPosition = {}
function TestTextEditorCursorPosition:test_getCursorScreenPosition_single_line()
local editor = createTextEditor({text = "Hello", multiline = false})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(3)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
luaunit.assertTrue(x >= 0)
luaunit.assertEquals(y, 0)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2", multiline = true})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(10) -- Second line
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_with_wrapping()
local editor = createTextEditor({
text = "Very long text that will wrap",
multiline = true,
textWrap = "word"
})
local element = createMockElement(100, 100)
editor:initialize(element)
editor:setCursorPosition(15)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertNotNil(y)
end
function TestTextEditorCursorPosition:test_getCursorScreenPosition_password_mode()
local editor = createTextEditor({
text = "password123",
passwordMode = true
})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(8)
local x, y = editor:_getCursorScreenPosition()
luaunit.assertNotNil(x)
luaunit.assertEquals(y, 0)
end
-- ============================================================================
-- Text Scroll Tests
-- ============================================================================
TestTextEditorScroll = {}
function TestTextEditorScroll:test_updateTextScroll()
local editor = createTextEditor({text = "This is very long text that needs scrolling"})
local element = createMockElement(100, 30)
editor:initialize(element)
editor:focus()
editor:moveCursorToEnd()
editor:_updateTextScroll()
-- Scroll should be updated
luaunit.assertTrue(editor._textScrollX >= 0)
end
function TestTextEditorScroll:test_updateTextScroll_keeps_cursor_visible()
local editor = createTextEditor({text = "Long text here"})
local element = createMockElement(50, 30)
editor:initialize(element)
editor:focus()
editor:setCursorPosition(10)
editor:_updateTextScroll()
local scrollX = editor._textScrollX
luaunit.assertTrue(scrollX >= 0)
end
-- ============================================================================
-- Mouse Interaction Edge Cases
-- ============================================================================
TestTextEditorMouseEdgeCases = {}
function TestTextEditorMouseEdgeCases:test_handleTextDrag_sets_flag()
local editor = createTextEditor({text = "Drag me"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleTextClick(10, 10, 1)
editor:handleTextDrag(50, 10)
luaunit.assertTrue(editor._textDragOccurred or not editor:hasSelection())
end
function TestTextEditorMouseEdgeCases:test_mouseToTextPosition_multiline()
local editor = createTextEditor({text = "Line 1\nLine 2\nLine 3", multiline = true})
local element = createMockElement()
editor:initialize(element)
-- Click on second line
local pos = editor:mouseToTextPosition(20, 25)
luaunit.assertNotNil(pos)
luaunit.assertTrue(pos >= 0) -- Valid position
end
function TestTextEditorMouseEdgeCases:test_mouseToTextPosition_with_scroll()
local editor = createTextEditor({text = "Very long scrolling text"})
local element = createMockElement(100, 30)
editor:initialize(element)
editor:focus()
editor._textScrollX = 50
local pos = editor:mouseToTextPosition(30, 15)
luaunit.assertNotNil(pos)
end
-- ============================================================================
-- Word Selection Tests
-- ============================================================================
TestTextEditorWordSelection = {}
function TestTextEditorWordSelection:test_selectWordAtPosition()
local editor = createTextEditor({text = "Hello World Test"})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(7) -- "World"
luaunit.assertTrue(editor:hasSelection())
local selected = editor:getSelectedText()
luaunit.assertEquals(selected, "World")
end
function TestTextEditorWordSelection:test_selectWordAtPosition_with_punctuation()
local editor = createTextEditor({text = "Hello, World!"})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(7) -- "World"
local selected = editor:getSelectedText()
luaunit.assertEquals(selected, "World")
end
function TestTextEditorWordSelection:test_selectWordAtPosition_empty()
local editor = createTextEditor({text = ""})
local element = createMockElement()
editor:initialize(element)
editor:_selectWordAtPosition(0)
-- Should not crash
luaunit.assertFalse(editor:hasSelection())
end
-- ============================================================================
-- State Saving Tests
-- ============================================================================
TestTextEditorStateSaving = {}
function TestTextEditorStateSaving:test_saveState_immediate_mode()
local savedState = nil
local mockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state)
savedState = state
end,
}
local mockContext = {
_immediateMode = true,
_focusedElement = nil,
}
local editor = TextEditor.new({text = "Test"}, {
Context = mockContext,
StateManager = mockStateManager,
Color = Color,
utils = utils,
})
local element = createMockElement()
element._stateId = "test-state-id"
editor:initialize(element)
editor:setText("New text")
luaunit.assertNotNil(savedState)
luaunit.assertEquals(savedState._textBuffer, "New text")
end
function TestTextEditorStateSaving:test_saveState_not_immediate_mode()
local saveCalled = false
local mockStateManager = {
getState = function(id) return nil end,
updateState = function(id, state)
saveCalled = true
end,
}
local mockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local editor = TextEditor.new({text = "Test"}, {
Context = mockContext,
StateManager = mockStateManager,
Color = Color,
utils = utils,
})
local element = createMockElement()
editor:initialize(element)
editor:_saveState()
-- Should not save in retained mode
luaunit.assertFalse(saveCalled)
end
-- ============================================================================
-- Text Wrapping Edge Cases
-- ============================================================================
TestTextEditorWrappingEdgeCases = {}
function TestTextEditorWrappingEdgeCases:test_wrapLine_empty_line()
local editor = createTextEditor({multiline = true, textWrap = "word"})
local element = createMockElement()
editor:initialize(element)
local wrapped = editor:_wrapLine("", 100)
luaunit.assertNotNil(wrapped)
luaunit.assertTrue(#wrapped > 0)
end
function TestTextEditorWrappingEdgeCases:test_calculateWrapping_empty_lines()
local editor = createTextEditor({
multiline = true,
textWrap = "word",
text = "Line 1\n\nLine 3"
})
local element = createMockElement()
editor:initialize(element)
editor:_calculateWrapping()
luaunit.assertNotNil(editor._wrappedLines)
end
function TestTextEditorWrappingEdgeCases:test_calculateWrapping_no_element()
local editor = createTextEditor({
multiline = true,
textWrap = "word",
text = "Test"
})
-- No element initialized
editor:_calculateWrapping()
luaunit.assertNil(editor._wrappedLines)
end
-- ============================================================================
-- Insert Text Edge Cases
-- ============================================================================
TestTextEditorInsertEdgeCases = {}
function TestTextEditorInsertEdgeCases:test_insertText_empty_after_sanitization()
local editor = createTextEditor({
maxLength = 5,
text = "12345"
})
local element = createMockElement()
editor:initialize(element)
-- Try to insert when at max length
editor:insertText("67890")
-- Should not insert anything
luaunit.assertEquals(editor:getText(), "12345")
end
function TestTextEditorInsertEdgeCases:test_insertText_updates_cursor()
local editor = createTextEditor({text = "Hello"})
local element = createMockElement()
editor:initialize(element)
editor:setCursorPosition(5)
editor:insertText(" World")
luaunit.assertEquals(editor:getCursorPosition(), 11)
end
-- ============================================================================
-- Keyboard Input Edge Cases
-- ============================================================================
TestTextEditorKeyboardEdgeCases = {}
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_escape_with_selection()
local editor = createTextEditor({text = "Select me"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:selectAll()
editor:handleKeyPress("escape", "escape", false)
luaunit.assertFalse(editor:hasSelection())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_escape_without_selection()
local editor = createTextEditor({text = "Test"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:handleKeyPress("escape", "escape", false)
luaunit.assertFalse(editor:isFocused())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_arrow_with_shift()
local editor = createTextEditor({text = "Select this"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(0)
-- Simulate shift+right arrow
love.keyboard.setDown("lshift", true)
editor:handleKeyPress("right", "right", false)
love.keyboard.setDown("lshift", false)
luaunit.assertTrue(editor:hasSelection())
end
function TestTextEditorKeyboardEdgeCases:test_handleKeyPress_ctrl_backspace()
local editor = createTextEditor({text = "Delete this"})
local element = createMockElement()
editor:initialize(element)
editor:focus()
editor:setCursorPosition(11)
-- Simulate ctrl+backspace
love.keyboard.setDown("lctrl", true)
editor:handleKeyPress("backspace", "backspace", false)
love.keyboard.setDown("lctrl", false)
luaunit.assertEquals(editor:getText(), "")
end
-- ============================================================================
-- Focus Edge Cases
-- ============================================================================
TestTextEditorFocusEdgeCases = {}
function TestTextEditorFocusEdgeCases:test_focus_blurs_previous()
local editor1 = createTextEditor({text = "Editor 1"})
local editor2 = createTextEditor({text = "Editor 2"})
local element1 = createMockElement()
local element2 = createMockElement()
element1._textEditor = editor1
element2._textEditor = editor2
editor1:initialize(element1)
editor2:initialize(element2)
MockContext._focusedElement = element1
editor1:focus()
-- Focus second editor
editor2:focus()
luaunit.assertFalse(editor1:isFocused())
luaunit.assertTrue(editor2:isFocused())
end
-- Run tests
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -1,291 +0,0 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local Animation = require("modules.Animation")
local Transform = Animation.Transform
TestTransform = {}
function TestTransform:setUp()
-- Reset state before each test
end
-- Test Transform.new()
function TestTransform:testNew_DefaultValues()
local transform = Transform.new()
luaunit.assertNotNil(transform)
luaunit.assertEquals(transform.rotate, 0)
luaunit.assertEquals(transform.scaleX, 1)
luaunit.assertEquals(transform.scaleY, 1)
luaunit.assertEquals(transform.translateX, 0)
luaunit.assertEquals(transform.translateY, 0)
luaunit.assertEquals(transform.skewX, 0)
luaunit.assertEquals(transform.skewY, 0)
luaunit.assertEquals(transform.originX, 0.5)
luaunit.assertEquals(transform.originY, 0.5)
end
function TestTransform:testNew_CustomValues()
local transform = Transform.new({
rotate = math.pi / 4,
scaleX = 2,
scaleY = 3,
translateX = 100,
translateY = 200,
skewX = 0.1,
skewY = 0.2,
originX = 0,
originY = 1,
})
luaunit.assertAlmostEquals(transform.rotate, math.pi / 4, 0.01)
luaunit.assertEquals(transform.scaleX, 2)
luaunit.assertEquals(transform.scaleY, 3)
luaunit.assertEquals(transform.translateX, 100)
luaunit.assertEquals(transform.translateY, 200)
luaunit.assertAlmostEquals(transform.skewX, 0.1, 0.01)
luaunit.assertAlmostEquals(transform.skewY, 0.2, 0.01)
luaunit.assertEquals(transform.originX, 0)
luaunit.assertEquals(transform.originY, 1)
end
function TestTransform:testNew_PartialValues()
local transform = Transform.new({
rotate = math.pi,
scaleX = 2,
})
luaunit.assertAlmostEquals(transform.rotate, math.pi, 0.01)
luaunit.assertEquals(transform.scaleX, 2)
luaunit.assertEquals(transform.scaleY, 1) -- default
luaunit.assertEquals(transform.translateX, 0) -- default
end
function TestTransform:testNew_EmptyProps()
local transform = Transform.new({})
-- Should use all defaults
luaunit.assertEquals(transform.rotate, 0)
luaunit.assertEquals(transform.scaleX, 1)
luaunit.assertEquals(transform.originX, 0.5)
end
function TestTransform:testNew_NilProps()
local transform = Transform.new(nil)
-- Should use all defaults
luaunit.assertEquals(transform.rotate, 0)
luaunit.assertEquals(transform.scaleX, 1)
end
-- Test Transform.lerp()
function TestTransform:testLerp_MidPoint()
local from = Transform.new({ rotate = 0, scaleX = 1, scaleY = 1 })
local to = Transform.new({ rotate = math.pi, scaleX = 2, scaleY = 3 })
local result = Transform.lerp(from, to, 0.5)
luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
end
function TestTransform:testLerp_StartPoint()
local from = Transform.new({ rotate = 0, scaleX = 1 })
local to = Transform.new({ rotate = math.pi, scaleX = 2 })
local result = Transform.lerp(from, to, 0)
luaunit.assertAlmostEquals(result.rotate, 0, 0.01)
luaunit.assertAlmostEquals(result.scaleX, 1, 0.01)
end
function TestTransform:testLerp_EndPoint()
local from = Transform.new({ rotate = 0, scaleX = 1 })
local to = Transform.new({ rotate = math.pi, scaleX = 2 })
local result = Transform.lerp(from, to, 1)
luaunit.assertAlmostEquals(result.rotate, math.pi, 0.01)
luaunit.assertAlmostEquals(result.scaleX, 2, 0.01)
end
function TestTransform:testLerp_AllProperties()
local from = Transform.new({
rotate = 0,
scaleX = 1,
scaleY = 1,
translateX = 0,
translateY = 0,
skewX = 0,
skewY = 0,
originX = 0,
originY = 0,
})
local to = Transform.new({
rotate = math.pi,
scaleX = 2,
scaleY = 3,
translateX = 100,
translateY = 200,
skewX = 0.2,
skewY = 0.4,
originX = 1,
originY = 1,
})
local result = Transform.lerp(from, to, 0.5)
luaunit.assertAlmostEquals(result.rotate, math.pi / 2, 0.01)
luaunit.assertAlmostEquals(result.scaleX, 1.5, 0.01)
luaunit.assertAlmostEquals(result.scaleY, 2, 0.01)
luaunit.assertAlmostEquals(result.translateX, 50, 0.01)
luaunit.assertAlmostEquals(result.translateY, 100, 0.01)
luaunit.assertAlmostEquals(result.skewX, 0.1, 0.01)
luaunit.assertAlmostEquals(result.skewY, 0.2, 0.01)
luaunit.assertAlmostEquals(result.originX, 0.5, 0.01)
luaunit.assertAlmostEquals(result.originY, 0.5, 0.01)
end
function TestTransform:testLerp_InvalidInputs()
-- Should handle nil gracefully
local result = Transform.lerp(nil, nil, 0.5)
luaunit.assertNotNil(result)
luaunit.assertEquals(result.rotate, 0)
luaunit.assertEquals(result.scaleX, 1)
end
function TestTransform:testLerp_ClampT()
local from = Transform.new({ scaleX = 1 })
local to = Transform.new({ scaleX = 2 })
-- Test t > 1
local result1 = Transform.lerp(from, to, 1.5)
luaunit.assertAlmostEquals(result1.scaleX, 2, 0.01)
-- Test t < 0
local result2 = Transform.lerp(from, to, -0.5)
luaunit.assertAlmostEquals(result2.scaleX, 1, 0.01)
end
function TestTransform:testLerp_InvalidT()
local from = Transform.new({ scaleX = 1 })
local to = Transform.new({ scaleX = 2 })
-- Test NaN
local result1 = Transform.lerp(from, to, 0 / 0)
luaunit.assertAlmostEquals(result1.scaleX, 1, 0.01) -- Should default to 0
-- Test Infinity
local result2 = Transform.lerp(from, to, math.huge)
luaunit.assertAlmostEquals(result2.scaleX, 2, 0.01) -- Should clamp to 1
end
-- Test Transform.isIdentity()
function TestTransform:testIsIdentity_True()
local transform = Transform.new()
luaunit.assertTrue(Transform.isIdentity(transform))
end
function TestTransform:testIsIdentity_Nil()
luaunit.assertTrue(Transform.isIdentity(nil))
end
function TestTransform:testIsIdentity_FalseRotate()
local transform = Transform.new({ rotate = 0.1 })
luaunit.assertFalse(Transform.isIdentity(transform))
end
function TestTransform:testIsIdentity_FalseScale()
local transform = Transform.new({ scaleX = 2 })
luaunit.assertFalse(Transform.isIdentity(transform))
end
function TestTransform:testIsIdentity_FalseTranslate()
local transform = Transform.new({ translateX = 10 })
luaunit.assertFalse(Transform.isIdentity(transform))
end
function TestTransform:testIsIdentity_FalseSkew()
local transform = Transform.new({ skewX = 0.1 })
luaunit.assertFalse(Transform.isIdentity(transform))
end
-- Test Transform.clone()
function TestTransform:testClone_AllProperties()
local original = Transform.new({
rotate = math.pi / 4,
scaleX = 2,
scaleY = 3,
translateX = 100,
translateY = 200,
skewX = 0.1,
skewY = 0.2,
originX = 0.25,
originY = 0.75,
})
local clone = Transform.clone(original)
luaunit.assertAlmostEquals(clone.rotate, math.pi / 4, 0.01)
luaunit.assertEquals(clone.scaleX, 2)
luaunit.assertEquals(clone.scaleY, 3)
luaunit.assertEquals(clone.translateX, 100)
luaunit.assertEquals(clone.translateY, 200)
luaunit.assertAlmostEquals(clone.skewX, 0.1, 0.01)
luaunit.assertAlmostEquals(clone.skewY, 0.2, 0.01)
luaunit.assertAlmostEquals(clone.originX, 0.25, 0.01)
luaunit.assertAlmostEquals(clone.originY, 0.75, 0.01)
-- Ensure it's a different object (use raw comparison)
luaunit.assertFalse(rawequal(clone, original), "Clone should be a different table instance")
end
function TestTransform:testClone_Nil()
local clone = Transform.clone(nil)
luaunit.assertNotNil(clone)
luaunit.assertEquals(clone.rotate, 0)
luaunit.assertEquals(clone.scaleX, 1)
end
function TestTransform:testClone_Mutation()
local original = Transform.new({ rotate = 0 })
local clone = Transform.clone(original)
-- Mutate clone
clone.rotate = math.pi
-- Original should be unchanged
luaunit.assertEquals(original.rotate, 0)
luaunit.assertAlmostEquals(clone.rotate, math.pi, 0.01)
end
-- Integration Tests
function TestTransform:testTransformAnimation()
local anim = Animation.new({
duration = 1,
start = { transform = Transform.new({ rotate = 0, scaleX = 1 }) },
final = { transform = Transform.new({ rotate = math.pi, scaleX = 2 }) },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.transform)
luaunit.assertAlmostEquals(result.transform.rotate, math.pi / 2, 0.01)
luaunit.assertAlmostEquals(result.transform.scaleX, 1.5, 0.01)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -35,47 +35,30 @@ _G.RUNNING_ALL_TESTS = true
local luaunit = require("testing.luaunit")
-- Run all tests in the __tests__ directory
local testFiles = {
"testing/__tests__/animation_coverage_test.lua",
"testing/__tests__/animation_test.lua",
"testing/__tests__/animation_properties_test.lua",
"testing/__tests__/blur_test.lua",
"testing/__tests__/critical_failures_test.lua",
"testing/__tests__/easing_test.lua",
"testing/__tests__/element_coverage_test.lua",
"testing/__tests__/element_extended_coverage_test.lua",
"testing/__tests__/element_test.lua",
"testing/__tests__/event_handler_test.lua",
"testing/__tests__/flexlove_test.lua",
"testing/__tests__/font_cache_test.lua",
"testing/__tests__/grid_test.lua",
"testing/__tests__/image_cache_test.lua",
"testing/__tests__/image_renderer_test.lua",
"testing/__tests__/image_scaler_test.lua",
"testing/__tests__/image_tiling_test.lua",
"testing/__tests__/input_event_test.lua",
"testing/__tests__/keyframe_animation_test.lua",
"testing/__tests__/layout_edge_cases_test.lua",
"testing/__tests__/layout_engine_test.lua",
"testing/__tests__/ninepatch_parser_test.lua",
"testing/__tests__/ninepatch_test.lua",
"testing/__tests__/overflow_test.lua",
"testing/__tests__/path_validation_test.lua",
"testing/__tests__/performance_instrumentation_test.lua",
"testing/__tests__/performance_warnings_test.lua",
"testing/__tests__/performance_test.lua",
"testing/__tests__/renderer_test.lua",
"testing/__tests__/renderer_texteditor_bugs_test.lua",
"testing/__tests__/roundedrect_test.lua",
"testing/__tests__/sanitization_test.lua",
"testing/__tests__/text_editor_coverage_test.lua",
"testing/__tests__/scroll_manager_test.lua",
"testing/__tests__/text_editor_test.lua",
"testing/__tests__/texteditor_extended_coverage_test.lua",
"testing/__tests__/theme_test.lua",
"testing/__tests__/touch_events_test.lua",
"testing/__tests__/transform_test.lua",
"testing/__tests__/units_test.lua",
"testing/__tests__/utils_test.lua",
-- Feature/Integration tests
"testing/__tests__/critical_failures_test.lua",
"testing/__tests__/flexlove_test.lua",
"testing/__tests__/touch_events_test.lua",
}
local success = true