consolidation
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
316
testing/__tests__/performance_test.lua
Normal file
316
testing/__tests__/performance_test.lua
Normal 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
|
||||
@@ -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
@@ -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
|
||||
@@ -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, "<script>alert('xss')</script>")
|
||||
end
|
||||
|
||||
function TestEscapeHtml:testEscapeHtml_Ampersand()
|
||||
local result = utils.escapeHtml("Tom & Jerry")
|
||||
luaunit.assertEquals(result, "Tom & Jerry")
|
||||
end
|
||||
|
||||
function TestEscapeHtml:testEscapeHtml_Quotes()
|
||||
local result = utils.escapeHtml('Hello "World"')
|
||||
luaunit.assertEquals(result, "Hello "World"")
|
||||
|
||||
result = utils.escapeHtml("It's fine")
|
||||
luaunit.assertEquals(result, "It'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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user