coverage work

This commit is contained in:
Michael Freno
2025-11-20 16:28:34 -05:00
parent aafb8e2af6
commit f59c594480
7 changed files with 2437 additions and 8 deletions

View 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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -37,11 +37,13 @@ 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_test.lua",
"testing/__tests__/event_handler_test.lua",
"testing/__tests__/flexlove_test.lua",
@@ -62,8 +64,10 @@ local testFiles = {
"testing/__tests__/performance_instrumentation_test.lua",
"testing/__tests__/performance_warnings_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__/text_editor_test.lua",
"testing/__tests__/theme_test.lua",
"testing/__tests__/touch_events_test.lua",