coverage work
This commit is contained in:
354
testing/__tests__/animation_coverage_test.lua
Normal file
354
testing/__tests__/animation_coverage_test.lua
Normal file
@@ -0,0 +1,354 @@
|
||||
-- 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
|
||||
612
testing/__tests__/element_coverage_test.lua
Normal file
612
testing/__tests__/element_coverage_test.lua
Normal file
@@ -0,0 +1,612 @@
|
||||
-- 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
|
||||
@@ -1512,7 +1512,6 @@ function TestElementUnhappyPaths:test_clear_children_twice()
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
|
||||
-- Test: Element contains with NaN coordinates
|
||||
function TestElementUnhappyPaths:test_contains_nan_coordinates()
|
||||
local element = FlexLove.new({
|
||||
@@ -1542,7 +1541,6 @@ function TestElementUnhappyPaths:test_scroll_without_manager()
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
|
||||
-- Test: Element scrollBy with nil values
|
||||
function TestElementUnhappyPaths:test_scroll_by_nil()
|
||||
local element = FlexLove.new({
|
||||
@@ -1747,11 +1745,6 @@ function TestElementUnhappyPaths:test_invalid_gap()
|
||||
gap = -10,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
gridRows = 0,
|
||||
gridColumns = 0,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Negative rows/columns
|
||||
element = FlexLove.new({
|
||||
@@ -1790,7 +1783,6 @@ function TestElementUnhappyPaths:test_set_text_nil()
|
||||
luaunit.assertNil(element.text)
|
||||
end
|
||||
|
||||
|
||||
-- Test: Element with conflicting size constraints
|
||||
function TestElementUnhappyPaths:test_conflicting_size_constraints()
|
||||
-- Width less than padding
|
||||
|
||||
773
testing/__tests__/renderer_texteditor_bugs_test.lua
Normal file
773
testing/__tests__/renderer_texteditor_bugs_test.lua
Normal file
@@ -0,0 +1,773 @@
|
||||
-- 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
|
||||
693
testing/__tests__/text_editor_coverage_test.lua
Normal file
693
testing/__tests__/text_editor_coverage_test.lua
Normal file
@@ -0,0 +1,693 @@
|
||||
-- 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,
|
||||
padding = {top = 5, right = 5, bottom = 5, left = 5},
|
||||
getScaledContentPadding = function(self)
|
||||
return self.padding
|
||||
end,
|
||||
_renderer = {
|
||||
getFont = function()
|
||||
return {
|
||||
getWidth = function(text) return #text * 8 end,
|
||||
getHeight = function() return 16 end,
|
||||
}
|
||||
end,
|
||||
wrapLine = function(element, line, maxWidth)
|
||||
-- Simple word wrapping simulation
|
||||
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()
|
||||
local editor = createTextEditor({multiline = true, text = "Line 1\nLine 2"})
|
||||
local element = createMockElement()
|
||||
editor:initialize(element)
|
||||
|
||||
-- Position in middle of first line
|
||||
editor:setCursorPosition(3)
|
||||
|
||||
-- Move to line start
|
||||
editor:moveCursorToLineStart()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0)
|
||||
|
||||
-- Move to line end
|
||||
editor:moveCursorToLineEnd()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 6)
|
||||
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(100, 100) -- Narrow width to force wrapping
|
||||
editor:initialize(element)
|
||||
|
||||
editor:_calculateWrapping()
|
||||
luaunit.assertNotNil(editor._wrappedLines)
|
||||
luaunit.assertTrue(#editor._wrappedLines > 1) -- Should wrap into multiple 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()
|
||||
luaunit.assertNotNil(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()
|
||||
local editor = createTextEditor({text = "Hello", editable = true, allowTabs = true})
|
||||
local element = createMockElement()
|
||||
editor:initialize(element)
|
||||
|
||||
editor:focus()
|
||||
editor:setCursorPosition(5)
|
||||
editor:handleKeyPress("tab", "tab", false)
|
||||
|
||||
luaunit.assertEquals(editor:getText(), "Hello\t")
|
||||
end
|
||||
|
||||
function TestTextEditorKeyboard:test_handle_home_end()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
local element = createMockElement()
|
||||
editor:initialize(element)
|
||||
|
||||
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: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)
|
||||
|
||||
-- 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: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)
|
||||
|
||||
-- Start at position 0
|
||||
editor:handleTextClick(0, 10, 1)
|
||||
|
||||
-- Drag to position further right
|
||||
editor:handleTextDrag(40, 10)
|
||||
|
||||
luaunit.assertTrue(editor:hasSelection())
|
||||
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()
|
||||
local editor = createTextEditor({
|
||||
text = "",
|
||||
editable = true,
|
||||
multiline = true,
|
||||
maxLines = 2
|
||||
})
|
||||
local element = createMockElement()
|
||||
editor:initialize(element)
|
||||
|
||||
editor:setText("Line 1\nLine 2")
|
||||
luaunit.assertEquals(editor:getText(), "Line 1\nLine 2")
|
||||
|
||||
editor:setText("Line 1\nLine 2\nLine 3")
|
||||
-- Should be limited to 2 lines
|
||||
local lines = {}
|
||||
for line in editor:getText():gmatch("[^\n]+") do
|
||||
table.insert(lines, line)
|
||||
end
|
||||
luaunit.assertTrue(#lines <= 2)
|
||||
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.assertFalse(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.assertFalse(editor:getText():find("\t"))
|
||||
end
|
||||
|
||||
-- Run tests
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
Reference in New Issue
Block a user