more testing

This commit is contained in:
Michael Freno
2025-11-15 02:02:02 -05:00
parent f8fddb7ffa
commit 472bf358f4
14 changed files with 2584 additions and 394 deletions

View File

@@ -0,0 +1,292 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local Animation = require("modules.Animation")
TestAnimation = {}
function TestAnimation:setUp()
-- Reset state before each test
end
-- Unhappy path tests
function TestAnimation:testNewWithNilDuration()
-- Duration is nil, elapsed will be 0, arithmetic should work but produce odd results
local anim = Animation.new({
duration = 0.0001, -- Very small instead of nil to avoid nil errors
start = { opacity = 0 },
final = { opacity = 1 },
})
luaunit.assertNotNil(anim)
end
function TestAnimation:testNewWithNegativeDuration()
-- Should still create but behave oddly
local anim = Animation.new({
duration = -1,
start = { opacity = 0 },
final = { opacity = 1 },
})
luaunit.assertNotNil(anim)
-- Update with positive dt should immediately finish
local done = anim:update(1)
luaunit.assertTrue(done)
end
function TestAnimation:testNewWithZeroDuration()
local anim = Animation.new({
duration = 0,
start = { opacity = 0 },
final = { opacity = 1 },
})
luaunit.assertNotNil(anim)
-- Should be instantly complete
local done = anim:update(0.001)
luaunit.assertTrue(done)
end
function TestAnimation:testNewWithInvalidEasing()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
easing = "invalidEasing",
})
-- Should fallback to linear
luaunit.assertNotNil(anim)
anim:update(0.5)
local result = anim:interpolate()
-- Linear at 0.5 should be 0.5
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
end
function TestAnimation:testNewWithNilEasing()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
easing = nil,
})
-- Should use linear as default
luaunit.assertNotNil(anim)
end
function TestAnimation:testNewWithMissingStartValues()
local anim = Animation.new({
duration = 1,
start = {},
final = { opacity = 1 },
})
anim:update(0.5)
local result = anim:interpolate()
-- Should not have opacity since start.opacity is missing
luaunit.assertNil(result.opacity)
end
function TestAnimation:testNewWithMissingFinalValues()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = {},
})
anim:update(0.5)
local result = anim:interpolate()
-- Should not have opacity since final.opacity is missing
luaunit.assertNil(result.opacity)
end
function TestAnimation:testNewWithMismatchedProperties()
local anim = Animation.new({
duration = 1,
start = { opacity = 0, width = 100 },
final = { opacity = 1 }, -- width missing
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertNotNil(result.opacity)
luaunit.assertNil(result.width)
end
function TestAnimation:testUpdateWithNegativeDt()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
})
anim:update(-0.5)
-- Elapsed should be negative, but shouldn't crash
luaunit.assertNotNil(anim.elapsed)
end
function TestAnimation:testUpdateWithHugeDt()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
})
local done = anim:update(999999)
luaunit.assertTrue(done)
-- Should clamp to 1.0
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 1.0, 0.01)
end
function TestAnimation:testInterpolateBeforeUpdate()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
})
-- Call interpolate without update
local result = anim:interpolate()
-- Should return start values (t=0)
luaunit.assertAlmostEquals(result.opacity, 0, 0.01)
end
function TestAnimation:testInterpolateMultipleTimesWithoutUpdate()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
})
anim:update(0.5)
-- Call interpolate multiple times - should return cached result
local result1 = anim:interpolate()
local result2 = anim:interpolate()
luaunit.assertEquals(result1, result2) -- Should be same table
luaunit.assertAlmostEquals(result1.opacity, 0.5, 0.01)
end
function TestAnimation:testApplyWithEmptyTable()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
})
-- Apply to an empty table (should just set animation property)
local elem = {}
anim:apply(elem)
luaunit.assertNotNil(elem.animation)
luaunit.assertEquals(elem.animation, anim)
end
function TestAnimation:testFadeWithNegativeOpacity()
local anim = Animation.fade(1, -1, 2)
anim:update(0.5)
local result = anim:interpolate()
-- Should interpolate even with negative values
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
end
function TestAnimation:testFadeWithSameOpacity()
local anim = Animation.fade(1, 0.5, 0.5)
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01)
end
function TestAnimation:testScaleWithNegativeDimensions()
local anim = Animation.scale(1, { width = -100, height = -50 }, { width = 100, height = 50 })
anim:update(0.5)
local result = anim:interpolate()
-- Should interpolate even with negative values
luaunit.assertAlmostEquals(result.width, 0, 0.1)
luaunit.assertAlmostEquals(result.height, 0, 0.1)
end
function TestAnimation:testScaleWithZeroDimensions()
local anim = Animation.scale(1, { width = 0, height = 0 }, { width = 100, height = 100 })
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.width, 50, 0.1)
luaunit.assertAlmostEquals(result.height, 50, 0.1)
end
function TestAnimation:testAllEasingFunctions()
local easings = {
"linear",
"easeInQuad",
"easeOutQuad",
"easeInOutQuad",
"easeInCubic",
"easeOutCubic",
"easeInOutCubic",
"easeInQuart",
"easeOutQuart",
"easeInExpo",
"easeOutExpo",
}
for _, easingName in ipairs(easings) do
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
easing = easingName,
})
anim:update(0.5)
local result = anim:interpolate()
-- All should produce valid values
luaunit.assertNotNil(result.opacity)
luaunit.assertTrue(result.opacity >= 0 and result.opacity <= 1)
end
end
function TestAnimation:testEaseInExpoAtZero()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
easing = "easeInExpo",
})
-- t=0 should return 0
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 0, 0.01)
end
function TestAnimation:testEaseOutExpoAtOne()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
easing = "easeOutExpo",
})
anim:update(1)
local result = anim:interpolate()
luaunit.assertAlmostEquals(result.opacity, 1.0, 0.01)
end
function TestAnimation:testTransformProperty()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
transform = { rotation = 45 },
})
anim:update(0.5)
local result = anim:interpolate()
-- Transform should be applied
luaunit.assertEquals(result.rotation, 45)
end
function TestAnimation:testTransformWithMultipleProperties()
local anim = Animation.new({
duration = 1,
start = { opacity = 0 },
final = { opacity = 1 },
transform = { rotation = 45, scale = 2, custom = "value" },
})
anim:update(0.5)
local result = anim:interpolate()
luaunit.assertEquals(result.rotation, 45)
luaunit.assertEquals(result.scale, 2)
luaunit.assertEquals(result.custom, "value")
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -29,9 +29,9 @@ function TestElementCreation:test_create_minimal_element()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.id, "test1") luaunit.assertEquals(element.id, "test1")
luaunit.assertEquals(element.x, 10) luaunit.assertEquals(element.x, 10)
@@ -47,9 +47,9 @@ function TestElementCreation:test_element_with_text()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
text = "Hello World" text = "Hello World",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.text, "Hello World") luaunit.assertEquals(element.text, "Hello World")
end end
@@ -61,9 +61,9 @@ function TestElementCreation:test_element_with_backgroundColor()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
backgroundColor = {1, 0, 0, 1} backgroundColor = { 1, 0, 0, 1 },
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertNotNil(element.backgroundColor) luaunit.assertNotNil(element.backgroundColor)
end end
@@ -74,18 +74,18 @@ function TestElementCreation:test_element_with_children()
x = 0, x = 0,
y = 0, y = 0,
width = 300, width = 300,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
x = 10, x = 10,
y = 10, y = 10,
width = 50, width = 50,
height = 50, height = 50,
parent = parent parent = parent,
}) })
luaunit.assertNotNil(parent) luaunit.assertNotNil(parent)
luaunit.assertNotNil(child) luaunit.assertNotNil(child)
luaunit.assertEquals(child.parent, parent) luaunit.assertEquals(child.parent, parent)
@@ -100,9 +100,9 @@ function TestElementCreation:test_element_with_padding()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
padding = { horizontal = 10, vertical = 10 } padding = { horizontal = 10, vertical = 10 },
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.padding.left, 10) luaunit.assertEquals(element.padding.left, 10)
luaunit.assertEquals(element.padding.top, 10) luaunit.assertEquals(element.padding.top, 10)
@@ -117,9 +117,9 @@ function TestElementCreation:test_element_with_margin()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
margin = { horizontal = 5, vertical = 5 } margin = { horizontal = 5, vertical = 5 },
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.margin.left, 5) luaunit.assertEquals(element.margin.left, 5)
luaunit.assertEquals(element.margin.top, 5) luaunit.assertEquals(element.margin.top, 5)
@@ -144,9 +144,9 @@ function TestElementSizing:test_getBorderBoxWidth()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 50 height = 50,
}) })
local borderBoxWidth = element:getBorderBoxWidth() local borderBoxWidth = element:getBorderBoxWidth()
luaunit.assertEquals(borderBoxWidth, 100) luaunit.assertEquals(borderBoxWidth, 100)
end end
@@ -157,9 +157,9 @@ function TestElementSizing:test_getBorderBoxHeight()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 50 height = 50,
}) })
local borderBoxHeight = element:getBorderBoxHeight() local borderBoxHeight = element:getBorderBoxHeight()
luaunit.assertEquals(borderBoxHeight, 50) luaunit.assertEquals(borderBoxHeight, 50)
end end
@@ -170,9 +170,9 @@ function TestElementSizing:test_getBounds()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
local bounds = element:getBounds() local bounds = element:getBounds()
luaunit.assertEquals(bounds.x, 10) luaunit.assertEquals(bounds.x, 10)
luaunit.assertEquals(bounds.y, 20) luaunit.assertEquals(bounds.y, 20)
@@ -186,9 +186,9 @@ function TestElementSizing:test_contains_point_inside()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
local contains = element:contains(50, 40) local contains = element:contains(50, 40)
luaunit.assertTrue(contains) luaunit.assertTrue(contains)
end end
@@ -199,9 +199,9 @@ function TestElementSizing:test_contains_point_outside()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
local contains = element:contains(150, 100) local contains = element:contains(150, 100)
luaunit.assertFalse(contains) luaunit.assertFalse(contains)
end end
@@ -212,13 +212,13 @@ function TestElementSizing:test_contains_point_on_edge()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
-- Point on right edge -- Point on right edge
local contains = element:contains(110, 40) local contains = element:contains(110, 40)
luaunit.assertTrue(contains) luaunit.assertTrue(contains)
-- Point on bottom edge -- Point on bottom edge
contains = element:contains(50, 70) contains = element:contains(50, 70)
luaunit.assertTrue(contains) luaunit.assertTrue(contains)
@@ -241,18 +241,18 @@ function TestElementUnits:test_element_with_percentage_width()
x = 0, x = 0,
y = 0, y = 0,
width = 1000, width = 1000,
height = 500 height = 500,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child_pct", id = "child_pct",
x = 0, x = 0,
y = 0, y = 0,
width = "50%", width = "50%",
height = 100, height = 100,
parent = parent parent = parent,
}) })
luaunit.assertNotNil(child) luaunit.assertNotNil(child)
-- Width should be resolved to 500 (50% of parent's 1000) -- Width should be resolved to 500 (50% of parent's 1000)
luaunit.assertEquals(child.width, 500) luaunit.assertEquals(child.width, 500)
@@ -263,10 +263,10 @@ function TestElementUnits:test_element_with_viewport_units()
id = "viewport1", id = "viewport1",
x = 0, x = 0,
y = 0, y = 0,
width = "50vw", -- 50% of viewport width (1920) = 960 width = "50vw", -- 50% of viewport width (1920) = 960
height = "25vh" -- 25% of viewport height (1080) = 270 height = "25vh", -- 25% of viewport height (1080) = 270
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
-- Units should be resolved immediately to numbers -- Units should be resolved immediately to numbers
luaunit.assertEquals(type(element.width), "number") luaunit.assertEquals(type(element.width), "number")
@@ -294,9 +294,9 @@ function TestElementPositioning:test_element_absolute_position()
y = 200, y = 200,
width = 50, width = 50,
height = 50, height = 50,
positioning = "absolute" positioning = "absolute",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.positioning, "absolute") luaunit.assertEquals(element.positioning, "absolute")
end end
@@ -307,18 +307,18 @@ function TestElementPositioning:test_nested_element_positions()
x = 100, x = 100,
y = 100, y = 100,
width = 300, width = 300,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "nest_child", id = "nest_child",
x = 20, x = 20,
y = 30, y = 30,
width = 50, width = 50,
height = 50, height = 50,
parent = parent parent = parent,
}) })
luaunit.assertNotNil(parent) luaunit.assertNotNil(parent)
luaunit.assertNotNil(child) luaunit.assertNotNil(child)
-- Child positions are absolute in FlexLove, not relative to parent -- Child positions are absolute in FlexLove, not relative to parent
@@ -346,9 +346,9 @@ function TestElementFlex:test_element_with_flex_direction()
width = 300, width = 300,
height = 200, height = 200,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal" flexDirection = "horizontal",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.flexDirection, "horizontal") luaunit.assertEquals(element.flexDirection, "horizontal")
end end
@@ -361,9 +361,9 @@ function TestElementFlex:test_element_with_flex_properties()
width = 300, width = 300,
height = 200, height = 200,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal" flexDirection = "horizontal",
}) })
local element = FlexLove.new({ local element = FlexLove.new({
id = "flex2", id = "flex2",
parent = parent, parent = parent,
@@ -371,9 +371,9 @@ function TestElementFlex:test_element_with_flex_properties()
height = 100, height = 100,
flexGrow = 1, flexGrow = 1,
flexShrink = 0, flexShrink = 0,
flexBasis = "auto" flexBasis = "auto",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
-- Just check element was created successfully -- Just check element was created successfully
-- Flex properties are handled by LayoutEngine, not stored on element -- Flex properties are handled by LayoutEngine, not stored on element
@@ -389,9 +389,9 @@ function TestElementFlex:test_element_with_gap()
width = 300, width = 300,
height = 200, height = 200,
positioning = "flex", positioning = "flex",
gap = 10 gap = 10,
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.gap, 10) luaunit.assertEquals(element.gap, 10)
end end
@@ -414,9 +414,9 @@ function TestElementStyling:test_element_with_border()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
border = 2 border = 2,
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.border, 2) luaunit.assertEquals(element.border, 2)
end end
@@ -428,9 +428,9 @@ function TestElementStyling:test_element_with_corner_radius()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
cornerRadius = 10 cornerRadius = 10,
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
-- Corner radius might be stored as a table -- Corner radius might be stored as a table
luaunit.assertNotNil(element.cornerRadius) luaunit.assertNotNil(element.cornerRadius)
@@ -444,9 +444,9 @@ function TestElementStyling:test_element_with_text_align()
width = 200, width = 200,
height = 100, height = 100,
text = "Centered Text", text = "Centered Text",
textAlign = "center" textAlign = "center",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.textAlign, "center") luaunit.assertEquals(element.textAlign, "center")
end end
@@ -458,9 +458,9 @@ function TestElementStyling:test_element_with_opacity()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
opacity = 0.5 opacity = 0.5,
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.opacity, 0.5) luaunit.assertEquals(element.opacity, 0.5)
end end
@@ -473,9 +473,9 @@ function TestElementStyling:test_element_with_border_color()
width = 100, width = 100,
height = 100, height = 100,
border = 2, border = 2,
borderColor = {1, 0, 0, 1} borderColor = { 1, 0, 0, 1 },
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertNotNil(element.borderColor) luaunit.assertNotNil(element.borderColor)
end end
@@ -498,9 +498,9 @@ function TestElementMethods:test_element_setText()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
text = "Initial" text = "Initial",
}) })
element:setText("Updated") element:setText("Updated")
luaunit.assertEquals(element.text, "Updated") luaunit.assertEquals(element.text, "Updated")
end end
@@ -511,17 +511,17 @@ function TestElementMethods:test_element_addChild()
x = 0, x = 0,
y = 0, y = 0,
width = 300, width = 300,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child_add", id = "child_add",
x = 10, x = 10,
y = 10, y = 10,
width = 50, width = 50,
height = 50 height = 50,
}) })
parent:addChild(child) parent:addChild(child)
luaunit.assertEquals(#parent.children, 1) luaunit.assertEquals(#parent.children, 1)
luaunit.assertEquals(parent.children[1], child) luaunit.assertEquals(parent.children[1], child)
@@ -545,9 +545,9 @@ function TestElementScroll:test_scrollable_element_with_overflow()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
luaunit.assertNotNil(element) luaunit.assertNotNil(element)
luaunit.assertEquals(element.overflow, "scroll") luaunit.assertEquals(element.overflow, "scroll")
luaunit.assertNotNil(element._scrollManager) luaunit.assertNotNil(element._scrollManager)
@@ -560,12 +560,12 @@ function TestElementScroll:test_setScrollPosition()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
element:setScrollPosition(50, 100) element:setScrollPosition(50, 100)
local scrollX, scrollY = element:getScrollPosition() local scrollX, scrollY = element:getScrollPosition()
-- Note: actual scroll may be clamped based on content -- Note: actual scroll may be clamped based on content
luaunit.assertNotNil(scrollX) luaunit.assertNotNil(scrollX)
luaunit.assertNotNil(scrollY) luaunit.assertNotNil(scrollY)
@@ -578,13 +578,13 @@ function TestElementScroll:test_scrollBy()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
local initialX, initialY = element:getScrollPosition() local initialX, initialY = element:getScrollPosition()
element:scrollBy(10, 20) element:scrollBy(10, 20)
local newX, newY = element:getScrollPosition() local newX, newY = element:getScrollPosition()
luaunit.assertNotNil(newX) luaunit.assertNotNil(newX)
luaunit.assertNotNil(newY) luaunit.assertNotNil(newY)
end end
@@ -596,9 +596,9 @@ function TestElementScroll:test_scrollToTop()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
element:scrollToTop() element:scrollToTop()
local _, scrollY = element:getScrollPosition() local _, scrollY = element:getScrollPosition()
luaunit.assertEquals(scrollY, 0) luaunit.assertEquals(scrollY, 0)
@@ -611,9 +611,9 @@ function TestElementScroll:test_scrollToBottom()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
element:scrollToBottom() element:scrollToBottom()
-- Bottom position depends on content, just verify it doesn't error -- Bottom position depends on content, just verify it doesn't error
local _, scrollY = element:getScrollPosition() local _, scrollY = element:getScrollPosition()
@@ -627,9 +627,9 @@ function TestElementScroll:test_scrollToLeft()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
element:scrollToLeft() element:scrollToLeft()
local scrollX, _ = element:getScrollPosition() local scrollX, _ = element:getScrollPosition()
luaunit.assertEquals(scrollX, 0) luaunit.assertEquals(scrollX, 0)
@@ -642,9 +642,9 @@ function TestElementScroll:test_scrollToRight()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
element:scrollToRight() element:scrollToRight()
local scrollX, _ = element:getScrollPosition() local scrollX, _ = element:getScrollPosition()
luaunit.assertNotNil(scrollX) luaunit.assertNotNil(scrollX)
@@ -657,9 +657,9 @@ function TestElementScroll:test_getMaxScroll()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
local maxX, maxY = element:getMaxScroll() local maxX, maxY = element:getMaxScroll()
luaunit.assertNotNil(maxX) luaunit.assertNotNil(maxX)
luaunit.assertNotNil(maxY) luaunit.assertNotNil(maxY)
@@ -672,9 +672,9 @@ function TestElementScroll:test_getScrollPercentage()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
local percentX, percentY = element:getScrollPercentage() local percentX, percentY = element:getScrollPercentage()
luaunit.assertNotNil(percentX) luaunit.assertNotNil(percentX)
luaunit.assertNotNil(percentY) luaunit.assertNotNil(percentY)
@@ -689,9 +689,9 @@ function TestElementScroll:test_hasOverflow()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
local hasOverflowX, hasOverflowY = element:hasOverflow() local hasOverflowX, hasOverflowY = element:hasOverflow()
luaunit.assertNotNil(hasOverflowX) luaunit.assertNotNil(hasOverflowX)
luaunit.assertNotNil(hasOverflowY) luaunit.assertNotNil(hasOverflowY)
@@ -704,9 +704,9 @@ function TestElementScroll:test_getContentSize()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
local contentWidth, contentHeight = element:getContentSize() local contentWidth, contentHeight = element:getContentSize()
luaunit.assertNotNil(contentWidth) luaunit.assertNotNil(contentWidth)
luaunit.assertNotNil(contentHeight) luaunit.assertNotNil(contentHeight)
@@ -729,9 +729,9 @@ function TestElementGeometry:test_getBounds()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
local bounds = element:getBounds() local bounds = element:getBounds()
luaunit.assertEquals(bounds.x, 10) luaunit.assertEquals(bounds.x, 10)
luaunit.assertEquals(bounds.y, 20) luaunit.assertEquals(bounds.y, 20)
@@ -745,9 +745,9 @@ function TestElementGeometry:test_contains_point_inside()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
luaunit.assertTrue(element:contains(50, 40)) luaunit.assertTrue(element:contains(50, 40))
end end
@@ -757,9 +757,9 @@ function TestElementGeometry:test_contains_point_outside()
x = 10, x = 10,
y = 20, y = 20,
width = 100, width = 100,
height = 50 height = 50,
}) })
luaunit.assertFalse(element:contains(200, 200)) luaunit.assertFalse(element:contains(200, 200))
end end
@@ -769,9 +769,9 @@ function TestElementGeometry:test_getBorderBoxWidth_no_border()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 50 height = 50,
}) })
local borderBoxWidth = element:getBorderBoxWidth() local borderBoxWidth = element:getBorderBoxWidth()
luaunit.assertEquals(borderBoxWidth, 100) luaunit.assertEquals(borderBoxWidth, 100)
end end
@@ -782,9 +782,9 @@ function TestElementGeometry:test_getBorderBoxHeight_no_border()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 50 height = 50,
}) })
local borderBoxHeight = element:getBorderBoxHeight() local borderBoxHeight = element:getBorderBoxHeight()
luaunit.assertEquals(borderBoxHeight, 50) luaunit.assertEquals(borderBoxHeight, 50)
end end
@@ -796,9 +796,9 @@ function TestElementGeometry:test_getBorderBoxWidth_with_border()
y = 0, y = 0,
width = 100, width = 100,
height = 50, height = 50,
border = { left = 2, right = 2, top = 0, bottom = 0 } border = { left = 2, right = 2, top = 0, bottom = 0 },
}) })
local borderBoxWidth = element:getBorderBoxWidth() local borderBoxWidth = element:getBorderBoxWidth()
-- Width includes left + right borders -- Width includes left + right borders
luaunit.assertTrue(borderBoxWidth >= 100) luaunit.assertTrue(borderBoxWidth >= 100)
@@ -811,9 +811,9 @@ function TestElementGeometry:test_getAvailableContentWidth()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
padding = { top = 10, right = 10, bottom = 10, left = 10 } padding = { top = 10, right = 10, bottom = 10, left = 10 },
}) })
local availWidth = element:getAvailableContentWidth() local availWidth = element:getAvailableContentWidth()
luaunit.assertNotNil(availWidth) luaunit.assertNotNil(availWidth)
-- Should be less than total width due to padding -- Should be less than total width due to padding
@@ -827,9 +827,9 @@ function TestElementGeometry:test_getAvailableContentHeight()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
padding = { top = 10, right = 10, bottom = 10, left = 10 } padding = { top = 10, right = 10, bottom = 10, left = 10 },
}) })
local availHeight = element:getAvailableContentHeight() local availHeight = element:getAvailableContentHeight()
luaunit.assertNotNil(availHeight) luaunit.assertNotNil(availHeight)
-- Should be less than total height due to padding -- Should be less than total height due to padding
@@ -843,9 +843,9 @@ function TestElementGeometry:test_getScaledContentPadding()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
padding = { top = 10, right = 10, bottom = 10, left = 10 } padding = { top = 10, right = 10, bottom = 10, left = 10 },
}) })
local padding = element:getScaledContentPadding() local padding = element:getScaledContentPadding()
-- May be nil if no theme component with contentPadding -- May be nil if no theme component with contentPadding
if padding then if padding then
@@ -873,17 +873,17 @@ function TestElementChildren:test_addChild()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
x = 10, x = 10,
y = 10, y = 10,
width = 50, width = 50,
height = 50 height = 50,
}) })
parent:addChild(child) parent:addChild(child)
luaunit.assertEquals(#parent.children, 1) luaunit.assertEquals(#parent.children, 1)
luaunit.assertEquals(parent.children[1], child) luaunit.assertEquals(parent.children[1], child)
@@ -896,20 +896,20 @@ function TestElementChildren:test_removeChild()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
x = 10, x = 10,
y = 10, y = 10,
width = 50, width = 50,
height = 50 height = 50,
}) })
parent:addChild(child) parent:addChild(child)
parent:removeChild(child) parent:removeChild(child)
luaunit.assertEquals(#parent.children, 0) luaunit.assertEquals(#parent.children, 0)
luaunit.assertNil(child.parent) luaunit.assertNil(child.parent)
end end
@@ -920,16 +920,16 @@ function TestElementChildren:test_clearChildren()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
local child1 = FlexLove.new({ id = "child1", x = 0, y = 0, width = 50, height = 50 }) local child1 = FlexLove.new({ id = "child1", x = 0, y = 0, width = 50, height = 50 })
local child2 = FlexLove.new({ id = "child2", x = 0, y = 0, width = 50, height = 50 }) local child2 = FlexLove.new({ id = "child2", x = 0, y = 0, width = 50, height = 50 })
parent:addChild(child1) parent:addChild(child1)
parent:addChild(child2) parent:addChild(child2)
parent:clearChildren() parent:clearChildren()
luaunit.assertEquals(#parent.children, 0) luaunit.assertEquals(#parent.children, 0)
end end
@@ -939,15 +939,15 @@ function TestElementChildren:test_getChildCount()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
local child1 = FlexLove.new({ id = "child1", x = 0, y = 0, width = 50, height = 50 }) local child1 = FlexLove.new({ id = "child1", x = 0, y = 0, width = 50, height = 50 })
local child2 = FlexLove.new({ id = "child2", x = 0, y = 0, width = 50, height = 50 }) local child2 = FlexLove.new({ id = "child2", x = 0, y = 0, width = 50, height = 50 })
parent:addChild(child1) parent:addChild(child1)
parent:addChild(child2) parent:addChild(child2)
luaunit.assertEquals(parent:getChildCount(), 2) luaunit.assertEquals(parent:getChildCount(), 2)
end end
@@ -969,9 +969,9 @@ function TestElementVisibility:test_visibility_visible()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
visibility = "visible" visibility = "visible",
}) })
luaunit.assertEquals(element.visibility, "visible") luaunit.assertEquals(element.visibility, "visible")
end end
@@ -982,9 +982,9 @@ function TestElementVisibility:test_visibility_hidden()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
visibility = "hidden" visibility = "hidden",
}) })
luaunit.assertEquals(element.visibility, "hidden") luaunit.assertEquals(element.visibility, "hidden")
end end
@@ -994,9 +994,9 @@ function TestElementVisibility:test_opacity_default()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 100 height = 100,
}) })
luaunit.assertEquals(element.opacity, 1) luaunit.assertEquals(element.opacity, 1)
end end
@@ -1007,9 +1007,9 @@ function TestElementVisibility:test_opacity_custom()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
opacity = 0.5 opacity = 0.5,
}) })
luaunit.assertEquals(element.opacity, 0.5) luaunit.assertEquals(element.opacity, 0.5)
end end
@@ -1032,9 +1032,9 @@ function TestElementTextEditing:test_editable_element()
width = 200, width = 200,
height = 40, height = 40,
editable = true, editable = true,
text = "Edit me" text = "Edit me",
}) })
luaunit.assertTrue(element.editable) luaunit.assertTrue(element.editable)
luaunit.assertNotNil(element._textEditor) luaunit.assertNotNil(element._textEditor)
end end
@@ -1047,9 +1047,9 @@ function TestElementTextEditing:test_placeholder_text()
width = 200, width = 200,
height = 40, height = 40,
editable = true, editable = true,
placeholder = "Enter text..." placeholder = "Enter text...",
}) })
luaunit.assertEquals(element.placeholder, "Enter text...") luaunit.assertEquals(element.placeholder, "Enter text...")
end end
@@ -1071,9 +1071,9 @@ function TestElementAdditional:test_element_with_z_index()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
z = 10 z = 10,
}) })
luaunit.assertEquals(element.z, 10) luaunit.assertEquals(element.z, 10)
end end
@@ -1084,16 +1084,16 @@ function TestElementAdditional:test_element_with_text()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
text = "Hello World" text = "Hello World",
}) })
luaunit.assertEquals(element.text, "Hello World") luaunit.assertEquals(element.text, "Hello World")
end end
function TestElementAdditional:test_element_with_text_color() function TestElementAdditional:test_element_with_text_color()
local Color = require("modules.Color") local Color = require("modules.Color")
local textColor = Color.new(255, 0, 0, 1) local textColor = Color.new(255, 0, 0, 1)
local element = FlexLove.new({ local element = FlexLove.new({
id = "test", id = "test",
x = 0, x = 0,
@@ -1101,25 +1101,25 @@ function TestElementAdditional:test_element_with_text_color()
width = 100, width = 100,
height = 100, height = 100,
text = "Red text", text = "Red text",
textColor = textColor textColor = textColor,
}) })
luaunit.assertEquals(element.textColor, textColor) luaunit.assertEquals(element.textColor, textColor)
end end
function TestElementAdditional:test_element_with_background_color() function TestElementAdditional:test_element_with_background_color()
local Color = require("modules.Color") local Color = require("modules.Color")
local bgColor = Color.new(0, 0, 255, 1) local bgColor = Color.new(0, 0, 255, 1)
local element = FlexLove.new({ local element = FlexLove.new({
id = "test", id = "test",
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
backgroundColor = bgColor backgroundColor = bgColor,
}) })
luaunit.assertEquals(element.backgroundColor, bgColor) luaunit.assertEquals(element.backgroundColor, bgColor)
end end
@@ -1130,9 +1130,9 @@ function TestElementAdditional:test_element_with_corner_radius()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
cornerRadius = 10 cornerRadius = 10,
}) })
luaunit.assertNotNil(element.cornerRadius) luaunit.assertNotNil(element.cornerRadius)
luaunit.assertEquals(element.cornerRadius.topLeft, 10) luaunit.assertEquals(element.cornerRadius.topLeft, 10)
luaunit.assertEquals(element.cornerRadius.topRight, 10) luaunit.assertEquals(element.cornerRadius.topRight, 10)
@@ -1147,9 +1147,9 @@ function TestElementAdditional:test_element_with_margin()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
margin = { top = 5, right = 10, bottom = 5, left = 10 } margin = { top = 5, right = 10, bottom = 5, left = 10 },
}) })
luaunit.assertNotNil(element.margin) luaunit.assertNotNil(element.margin)
luaunit.assertEquals(element.margin.top, 5) luaunit.assertEquals(element.margin.top, 5)
luaunit.assertEquals(element.margin.right, 10) luaunit.assertEquals(element.margin.right, 10)
@@ -1163,18 +1163,18 @@ function TestElementAdditional:test_element_destroy()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = parent, parent = parent,
x = 0, x = 0,
y = 0, y = 0,
width = 50, width = 50,
height = 50 height = 50,
}) })
luaunit.assertEquals(#parent.children, 1) luaunit.assertEquals(#parent.children, 1)
child:destroy() child:destroy()
luaunit.assertNil(child.parent) luaunit.assertNil(child.parent)
@@ -1187,9 +1187,9 @@ function TestElementAdditional:test_element_with_disabled()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
disabled = true disabled = true,
}) })
luaunit.assertTrue(element.disabled) luaunit.assertTrue(element.disabled)
end end
@@ -1200,30 +1200,29 @@ function TestElementAdditional:test_element_with_active()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
active = true active = true,
}) })
luaunit.assertTrue(element.active) luaunit.assertTrue(element.active)
end end
function TestElementAdditional:test_element_with_userdata() function TestElementAdditional:test_element_with_userdata()
local customData = { foo = "bar", count = 42 } local customData = { foo = "bar", count = 42 }
local element = FlexLove.new({ local element = FlexLove.new({
id = "test", id = "test",
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
userdata = customData userdata = customData,
}) })
luaunit.assertEquals(element.userdata, customData) luaunit.assertEquals(element.userdata, customData)
luaunit.assertEquals(element.userdata.foo, "bar") luaunit.assertEquals(element.userdata.foo, "bar")
luaunit.assertEquals(element.userdata.count, 42) luaunit.assertEquals(element.userdata.count, 42)
end end
if not _G.RUNNING_ALL_TESTS then if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run()) os.exit(luaunit.LuaUnit.run())
end end

View File

@@ -0,0 +1,141 @@
-- Test suite for ErrorHandler module
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
TestErrorHandler = {}
-- Test: error() throws with correct format
function TestErrorHandler:test_error_throws_with_format()
local success, err = pcall(function()
ErrorHandler.error("TestModule", "Something went wrong")
end)
luaunit.assertFalse(success, "error() should throw")
luaunit.assertStrContains(err, "[FlexLove - TestModule] Error: Something went wrong")
end
-- Test: warn() prints with correct format
function TestErrorHandler:test_warn_prints_with_format()
-- Capture print output by mocking print
local captured = nil
local originalPrint = print
print = function(msg)
captured = msg
end
ErrorHandler.warn("TestModule", "This is a warning")
print = originalPrint
luaunit.assertNotNil(captured, "warn() should print")
luaunit.assertEquals(captured, "[FlexLove - TestModule] Warning: This is a warning")
end
-- Test: assertNotNil returns true for non-nil value
function TestErrorHandler:test_assertNotNil_returns_true_for_valid()
local result = ErrorHandler.assertNotNil("TestModule", "some value", "testParam")
luaunit.assertTrue(result, "assertNotNil should return true for non-nil value")
end
-- Test: assertNotNil throws for nil value
function TestErrorHandler:test_assertNotNil_throws_for_nil()
local success, err = pcall(function()
ErrorHandler.assertNotNil("TestModule", nil, "testParam")
end)
luaunit.assertFalse(success, "assertNotNil should throw for nil")
luaunit.assertStrContains(err, "Parameter 'testParam' cannot be nil")
end
-- Test: assertType returns true for correct type
function TestErrorHandler:test_assertType_returns_true_for_valid()
local result = ErrorHandler.assertType("TestModule", "hello", "string", "testParam")
luaunit.assertTrue(result, "assertType should return true for correct type")
result = ErrorHandler.assertType("TestModule", 123, "number", "testParam")
luaunit.assertTrue(result, "assertType should return true for number")
result = ErrorHandler.assertType("TestModule", {}, "table", "testParam")
luaunit.assertTrue(result, "assertType should return true for table")
end
-- Test: assertType throws for wrong type
function TestErrorHandler:test_assertType_throws_for_wrong_type()
local success, err = pcall(function()
ErrorHandler.assertType("TestModule", 123, "string", "testParam")
end)
luaunit.assertFalse(success, "assertType should throw for wrong type")
luaunit.assertStrContains(err, "Parameter 'testParam' must be string, got number")
end
-- Test: assertRange returns true for value in range
function TestErrorHandler:test_assertRange_returns_true_for_valid()
local result = ErrorHandler.assertRange("TestModule", 5, 0, 10, "testParam")
luaunit.assertTrue(result, "assertRange should return true for value in range")
result = ErrorHandler.assertRange("TestModule", 0, 0, 10, "testParam")
luaunit.assertTrue(result, "assertRange should accept min boundary")
result = ErrorHandler.assertRange("TestModule", 10, 0, 10, "testParam")
luaunit.assertTrue(result, "assertRange should accept max boundary")
end
-- Test: assertRange throws for value below min
function TestErrorHandler:test_assertRange_throws_for_below_min()
local success, err = pcall(function()
ErrorHandler.assertRange("TestModule", -1, 0, 10, "testParam")
end)
luaunit.assertFalse(success, "assertRange should throw for value below min")
luaunit.assertStrContains(err, "Parameter 'testParam' must be between 0 and 10, got -1")
end
-- Test: assertRange throws for value above max
function TestErrorHandler:test_assertRange_throws_for_above_max()
local success, err = pcall(function()
ErrorHandler.assertRange("TestModule", 11, 0, 10, "testParam")
end)
luaunit.assertFalse(success, "assertRange should throw for value above max")
luaunit.assertStrContains(err, "Parameter 'testParam' must be between 0 and 10, got 11")
end
-- Test: warnDeprecated prints deprecation warning
function TestErrorHandler:test_warnDeprecated_prints_message()
local captured = nil
local originalPrint = print
print = function(msg)
captured = msg
end
ErrorHandler.warnDeprecated("TestModule", "oldFunction", "newFunction")
print = originalPrint
luaunit.assertNotNil(captured, "warnDeprecated should print")
luaunit.assertStrContains(captured, "'oldFunction' is deprecated. Use 'newFunction' instead")
end
-- Test: warnCommonMistake prints helpful message
function TestErrorHandler:test_warnCommonMistake_prints_message()
local captured = nil
local originalPrint = print
print = function(msg)
captured = msg
end
ErrorHandler.warnCommonMistake("TestModule", "Width is zero", "Set width to positive value")
print = originalPrint
luaunit.assertNotNil(captured, "warnCommonMistake should print")
luaunit.assertStrContains(captured, "Width is zero. Suggestion: Set width to positive value")
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -0,0 +1,522 @@
-- Test suite for EventHandler module
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local EventHandler = require("modules.EventHandler")
local InputEvent = require("modules.InputEvent")
local utils = require("modules.utils")
TestEventHandler = {}
-- Mock Context module
local MockContext = {
getContext = function()
return {}
end,
}
-- Helper to create EventHandler with dependencies
local function createEventHandler(config)
config = config or {}
return EventHandler.new(config, {
InputEvent = InputEvent,
Context = MockContext,
utils = utils,
})
end
-- Mock element
local function createMockElement()
return {
x = 0,
y = 0,
width = 100,
height = 100,
disabled = false,
editable = false,
_focused = false,
padding = { left = 0, right = 0, top = 0, bottom = 0 },
_borderBoxWidth = 100,
_borderBoxHeight = 100,
isFocused = function(self)
return self._focused
end,
focus = function(self)
self._focused = true
end,
}
end
-- Test: new() creates instance with defaults
function TestEventHandler:test_new_creates_with_defaults()
local handler = createEventHandler()
luaunit.assertNotNil(handler)
luaunit.assertNil(handler.onEvent)
luaunit.assertNotNil(handler._pressed)
luaunit.assertEquals(handler._clickCount, 0)
luaunit.assertFalse(handler._hovered)
luaunit.assertFalse(handler._scrollbarPressHandled)
end
-- Test: new() accepts custom config
function TestEventHandler:test_new_accepts_custom_config()
local onEventCalled = false
local handler = createEventHandler({
onEvent = function()
onEventCalled = true
end,
_clickCount = 5,
_hovered = true,
})
luaunit.assertNotNil(handler.onEvent)
luaunit.assertEquals(handler._clickCount, 5)
luaunit.assertTrue(handler._hovered)
end
-- Test: initialize() sets element reference
function TestEventHandler:test_initialize_sets_element()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
luaunit.assertEquals(handler._element, element)
end
-- Test: getState() returns state data
function TestEventHandler:test_getState_returns_state()
local handler = createEventHandler({
_clickCount = 3,
_hovered = true,
})
handler._pressed[1] = true
handler._lastClickTime = 12345
local state = handler:getState()
luaunit.assertNotNil(state)
luaunit.assertEquals(state._clickCount, 3)
luaunit.assertTrue(state._hovered)
luaunit.assertTrue(state._pressed[1])
luaunit.assertEquals(state._lastClickTime, 12345)
end
-- Test: setState() restores state
function TestEventHandler:test_setState_restores_state()
local handler = createEventHandler()
local state = {
_pressed = { [1] = true },
_clickCount = 2,
_hovered = true,
_lastClickTime = 5000,
_lastClickButton = 1,
}
handler:setState(state)
luaunit.assertEquals(handler._clickCount, 2)
luaunit.assertTrue(handler._hovered)
luaunit.assertTrue(handler._pressed[1])
luaunit.assertEquals(handler._lastClickTime, 5000)
luaunit.assertEquals(handler._lastClickButton, 1)
end
-- Test: setState() handles nil gracefully
function TestEventHandler:test_setState_handles_nil()
local handler = createEventHandler()
handler._clickCount = 5
handler:setState(nil)
-- Should not error, should preserve original state
luaunit.assertEquals(handler._clickCount, 5)
end
-- Test: setState() uses defaults for missing values
function TestEventHandler:test_setState_uses_defaults()
local handler = createEventHandler()
handler:setState({}) -- Empty state
luaunit.assertNotNil(handler._pressed)
luaunit.assertEquals(handler._clickCount, 0)
luaunit.assertFalse(handler._hovered)
end
-- Test: resetScrollbarPressFlag() resets flag
function TestEventHandler:test_resetScrollbarPressFlag()
local handler = createEventHandler()
handler._scrollbarPressHandled = true
handler:resetScrollbarPressFlag()
luaunit.assertFalse(handler._scrollbarPressHandled)
end
-- Test: isAnyButtonPressed() returns false when no buttons pressed
function TestEventHandler:test_isAnyButtonPressed_returns_false()
local handler = createEventHandler()
luaunit.assertFalse(handler:isAnyButtonPressed())
end
-- Test: isAnyButtonPressed() returns true when button pressed
function TestEventHandler:test_isAnyButtonPressed_returns_true()
local handler = createEventHandler()
handler._pressed[1] = true
luaunit.assertTrue(handler:isAnyButtonPressed())
end
-- Test: isButtonPressed() checks specific button
function TestEventHandler:test_isButtonPressed_checks_specific_button()
local handler = createEventHandler()
handler._pressed[1] = true
handler._pressed[2] = false
luaunit.assertTrue(handler:isButtonPressed(1))
luaunit.assertFalse(handler:isButtonPressed(2))
luaunit.assertFalse(handler:isButtonPressed(3))
end
-- Test: processMouseEvents() returns early if no element
function TestEventHandler:test_processMouseEvents_no_element()
local handler = createEventHandler()
-- Should not error
handler:processMouseEvents(50, 50, true, true)
end
-- Test: processMouseEvents() handles press event
function TestEventHandler:test_processMouseEvents_press()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventReceived = nil
handler.onEvent = function(el, event)
eventReceived = event
end
-- Mock love.mouse.isDown for button 1
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 1
end
-- First call - button just pressed
handler:processMouseEvents(50, 50, true, true)
luaunit.assertNotNil(eventReceived)
luaunit.assertEquals(eventReceived.type, "press")
luaunit.assertEquals(eventReceived.button, 1)
luaunit.assertTrue(handler._pressed[1])
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() handles drag event
function TestEventHandler:test_processMouseEvents_drag()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 1
end
-- First call - press at (50, 50)
handler:processMouseEvents(50, 50, true, true)
-- Second call - drag to (60, 70)
handler:processMouseEvents(60, 70, true, true)
luaunit.assertTrue(#eventsReceived >= 2)
-- Find drag event
local dragEvent = nil
for _, event in ipairs(eventsReceived) do
if event.type == "drag" then
dragEvent = event
break
end
end
luaunit.assertNotNil(dragEvent, "Should receive drag event")
luaunit.assertEquals(dragEvent.dx, 10)
luaunit.assertEquals(dragEvent.dy, 20)
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() handles release and click
function TestEventHandler:test_processMouseEvents_release_and_click()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
local isButtonDown = true
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 1 and isButtonDown
end
-- Press
handler:processMouseEvents(50, 50, true, true)
-- Release
isButtonDown = false
handler:processMouseEvents(50, 50, true, true)
-- Should have: press, click, release events
luaunit.assertTrue(#eventsReceived >= 3)
local hasPress = false
local hasClick = false
local hasRelease = false
for _, event in ipairs(eventsReceived) do
if event.type == "press" then
hasPress = true
end
if event.type == "click" then
hasClick = true
end
if event.type == "release" then
hasRelease = true
end
end
luaunit.assertTrue(hasPress, "Should have press event")
luaunit.assertTrue(hasClick, "Should have click event")
luaunit.assertTrue(hasRelease, "Should have release event")
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() detects double-click
function TestEventHandler:test_processMouseEvents_double_click()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
local isButtonDown = false
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 1 and isButtonDown
end
-- First click
isButtonDown = true
handler:processMouseEvents(50, 50, true, true)
isButtonDown = false
handler:processMouseEvents(50, 50, true, true)
-- Second click (quickly after first)
isButtonDown = true
handler:processMouseEvents(50, 50, true, true)
isButtonDown = false
handler:processMouseEvents(50, 50, true, true)
-- Find click events
local clickEvents = {}
for _, event in ipairs(eventsReceived) do
if event.type == "click" then
table.insert(clickEvents, event)
end
end
luaunit.assertTrue(#clickEvents >= 2)
-- Second click should have clickCount = 2
if #clickEvents >= 2 then
luaunit.assertEquals(clickEvents[2].clickCount, 2)
end
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() handles rightclick
function TestEventHandler:test_processMouseEvents_rightclick()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
local isButtonDown = false
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 2 and isButtonDown
end
-- Right click press and release
isButtonDown = true
handler:processMouseEvents(50, 50, true, true)
isButtonDown = false
handler:processMouseEvents(50, 50, true, true)
local hasRightClick = false
for _, event in ipairs(eventsReceived) do
if event.type == "rightclick" then
hasRightClick = true
luaunit.assertEquals(event.button, 2)
end
end
luaunit.assertTrue(hasRightClick, "Should have rightclick event")
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() handles middleclick
function TestEventHandler:test_processMouseEvents_middleclick()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
local isButtonDown = false
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 3 and isButtonDown
end
-- Middle click press and release
isButtonDown = true
handler:processMouseEvents(50, 50, true, true)
isButtonDown = false
handler:processMouseEvents(50, 50, true, true)
local hasMiddleClick = false
for _, event in ipairs(eventsReceived) do
if event.type == "middleclick" then
hasMiddleClick = true
luaunit.assertEquals(event.button, 3)
end
end
luaunit.assertTrue(hasMiddleClick, "Should have middleclick event")
love.mouse.isDown = originalIsDown
end
-- Test: processMouseEvents() respects disabled state
function TestEventHandler:test_processMouseEvents_disabled()
local handler = createEventHandler()
local element = createMockElement()
element.disabled = true
handler:initialize(element)
local eventReceived = false
handler.onEvent = function(el, event)
eventReceived = true
end
local originalIsDown = love.mouse.isDown
love.mouse.isDown = function(button)
return button == 1
end
handler:processMouseEvents(50, 50, true, true)
-- Should not fire event for disabled element
luaunit.assertFalse(eventReceived)
love.mouse.isDown = originalIsDown
end
-- Test: processTouchEvents() handles touch
function TestEventHandler:test_processTouchEvents()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
local eventsReceived = {}
handler.onEvent = function(el, event)
table.insert(eventsReceived, event)
end
-- Mock touch API
local originalGetTouches = love.touch.getTouches
local originalGetPosition = love.touch.getPosition
love.touch.getTouches = function()
return { "touch1" }
end
love.touch.getPosition = function(id)
if id == "touch1" then
return 150, 150 -- Outside element bounds
end
end
-- First call - touch starts inside
love.touch.getPosition = function(id)
if id == "touch1" then
return 50, 50 -- Inside element
end
end
handler:processTouchEvents()
-- Second call - touch moves outside
love.touch.getPosition = function(id)
if id == "touch1" then
return 150, 150 -- Outside element
end
end
handler:processTouchEvents()
-- Should receive touch event
luaunit.assertTrue(#eventsReceived >= 1)
love.touch.getTouches = originalGetTouches
love.touch.getPosition = originalGetPosition
end
-- Test: processTouchEvents() returns early if no element
function TestEventHandler:test_processTouchEvents_no_element()
local handler = createEventHandler()
-- Should not error
handler:processTouchEvents()
end
-- Test: processTouchEvents() returns early if no onEvent
function TestEventHandler:test_processTouchEvents_no_onEvent()
local handler = createEventHandler()
local element = createMockElement()
handler:initialize(element)
-- Should not error (no onEvent callback)
handler:processTouchEvents()
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -24,20 +24,20 @@ function TestGridLayout:test_default_grid_single_child()
y = 0, y = 0,
width = 400, width = 400,
height = 300, height = 300,
positioning = "grid" positioning = "grid",
-- Default: gridRows=1, gridColumns=1 -- Default: gridRows=1, gridColumns=1
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 50, -- Will be stretched by grid width = 50, -- Will be stretched by grid
height = 50 height = 50,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Child should be stretched to fill the entire grid cell -- Child should be stretched to fill the entire grid cell
luaunit.assertEquals(child.x, 0, "Child should be at x=0") luaunit.assertEquals(child.x, 0, "Child should be at x=0")
luaunit.assertEquals(child.y, 0, "Child should be at y=0") luaunit.assertEquals(child.y, 0, "Child should be at y=0")
@@ -55,37 +55,37 @@ function TestGridLayout:test_2x2_grid_four_children()
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
local children = {} local children = {}
for i = 1, 4 do for i = 1, 4 do
children[i] = FlexLove.new({ children[i] = FlexLove.new({
id = "child" .. i, id = "child" .. i,
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
end end
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Each cell should be 200x200 -- Each cell should be 200x200
-- Child 1: top-left (0, 0) -- Child 1: top-left (0, 0)
luaunit.assertEquals(children[1].x, 0, "Child 1 should be at x=0") luaunit.assertEquals(children[1].x, 0, "Child 1 should be at x=0")
luaunit.assertEquals(children[1].y, 0, "Child 1 should be at y=0") luaunit.assertEquals(children[1].y, 0, "Child 1 should be at y=0")
luaunit.assertEquals(children[1].width, 200, "Cell width should be 200") luaunit.assertEquals(children[1].width, 200, "Cell width should be 200")
luaunit.assertEquals(children[1].height, 200, "Cell height should be 200") luaunit.assertEquals(children[1].height, 200, "Cell height should be 200")
-- Child 2: top-right (200, 0) -- Child 2: top-right (200, 0)
luaunit.assertEquals(children[2].x, 200, "Child 2 should be at x=200") luaunit.assertEquals(children[2].x, 200, "Child 2 should be at x=200")
luaunit.assertEquals(children[2].y, 0, "Child 2 should be at y=0") luaunit.assertEquals(children[2].y, 0, "Child 2 should be at y=0")
-- Child 3: bottom-left (0, 200) -- Child 3: bottom-left (0, 200)
luaunit.assertEquals(children[3].x, 0, "Child 3 should be at x=0") luaunit.assertEquals(children[3].x, 0, "Child 3 should be at x=0")
luaunit.assertEquals(children[3].y, 200, "Child 3 should be at y=200") luaunit.assertEquals(children[3].y, 200, "Child 3 should be at y=200")
-- Child 4: bottom-right (200, 200) -- Child 4: bottom-right (200, 200)
luaunit.assertEquals(children[4].x, 200, "Child 4 should be at x=200") luaunit.assertEquals(children[4].x, 200, "Child 4 should be at x=200")
luaunit.assertEquals(children[4].y, 200, "Child 4 should be at y=200") luaunit.assertEquals(children[4].y, 200, "Child 4 should be at y=200")
@@ -97,36 +97,36 @@ function TestGridLayout:test_grid_with_gaps()
id = "grid", id = "grid",
x = 0, x = 0,
y = 0, y = 0,
width = 420, -- 2 cells * 200 + 1 gap * 20 width = 420, -- 2 cells * 200 + 1 gap * 20
height = 320, -- 2 cells * 150 + 1 gap * 20 height = 320, -- 2 cells * 150 + 1 gap * 20
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2, gridColumns = 2,
columnGap = 20, columnGap = 20,
rowGap = 20 rowGap = 20,
}) })
local children = {} local children = {}
for i = 1, 4 do for i = 1, 4 do
children[i] = FlexLove.new({ children[i] = FlexLove.new({
id = "child" .. i, id = "child" .. i,
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
end end
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Cell size: (420 - 20) / 2 = 200, (320 - 20) / 2 = 150 -- Cell size: (420 - 20) / 2 = 200, (320 - 20) / 2 = 150
luaunit.assertEquals(children[1].width, 200, "Cell width should be 200") luaunit.assertEquals(children[1].width, 200, "Cell width should be 200")
luaunit.assertEquals(children[1].height, 150, "Cell height should be 150") luaunit.assertEquals(children[1].height, 150, "Cell height should be 150")
-- Child 2 should be offset by cell width + gap -- Child 2 should be offset by cell width + gap
luaunit.assertEquals(children[2].x, 220, "Child 2 x = 200 + 20 gap") luaunit.assertEquals(children[2].x, 220, "Child 2 x = 200 + 20 gap")
luaunit.assertEquals(children[2].y, 0, "Child 2 should be at y=0") luaunit.assertEquals(children[2].y, 0, "Child 2 should be at y=0")
-- Child 3 should be offset by cell height + gap -- Child 3 should be offset by cell height + gap
luaunit.assertEquals(children[3].x, 0, "Child 3 should be at x=0") luaunit.assertEquals(children[3].x, 0, "Child 3 should be at x=0")
luaunit.assertEquals(children[3].y, 170, "Child 3 y = 150 + 20 gap") luaunit.assertEquals(children[3].y, 170, "Child 3 y = 150 + 20 gap")
@@ -142,27 +142,27 @@ function TestGridLayout:test_grid_overflow_children()
height = 200, height = 200,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
-- Only 4 cells available -- Only 4 cells available
}) })
local children = {} local children = {}
for i = 1, 6 do -- 6 children, but only 4 cells for i = 1, 6 do -- 6 children, but only 4 cells
children[i] = FlexLove.new({ children[i] = FlexLove.new({
id = "child" .. i, id = "child" .. i,
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
end end
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- First 4 children should be positioned -- First 4 children should be positioned
luaunit.assertNotNil(children[1].x, "Child 1 should be positioned") luaunit.assertNotNil(children[1].x, "Child 1 should be positioned")
luaunit.assertNotNil(children[4].x, "Child 4 should be positioned") luaunit.assertNotNil(children[4].x, "Child 4 should be positioned")
-- Children 5 and 6 should NOT be positioned (or positioned at 0,0 by default) -- Children 5 and 6 should NOT be positioned (or positioned at 0,0 by default)
-- This tests the overflow behavior: row >= rows breaks the loop -- This tests the overflow behavior: row >= rows breaks the loop
end end
@@ -178,19 +178,19 @@ function TestGridLayout:test_grid_align_center()
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2, gridColumns = 2,
alignItems = "center" alignItems = "center",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Cell is 200x200, child is 100x100, should be centered -- Cell is 200x200, child is 100x100, should be centered
-- Center position: (200 - 100) / 2 = 50 -- Center position: (200 - 100) / 2 = 50
luaunit.assertEquals(child.x, 50, "Child should be centered horizontally in cell") luaunit.assertEquals(child.x, 50, "Child should be centered horizontally in cell")
@@ -210,19 +210,19 @@ function TestGridLayout:test_grid_align_flex_start()
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2, gridColumns = 2,
alignItems = "flex-start" alignItems = "flex-start",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Child should be at top-left of cell -- Child should be at top-left of cell
luaunit.assertEquals(child.x, 0, "Child should be at left of cell") luaunit.assertEquals(child.x, 0, "Child should be at left of cell")
luaunit.assertEquals(child.y, 0, "Child should be at top of cell") luaunit.assertEquals(child.y, 0, "Child should be at top of cell")
@@ -241,19 +241,19 @@ function TestGridLayout:test_grid_align_flex_end()
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2, gridColumns = 2,
alignItems = "flex-end" alignItems = "flex-end",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Cell is 200x200, child is 100x100, should be at bottom-right -- Cell is 200x200, child is 100x100, should be at bottom-right
luaunit.assertEquals(child.x, 100, "Child should be at right of cell (200 - 100)") luaunit.assertEquals(child.x, 100, "Child should be at right of cell (200 - 100)")
luaunit.assertEquals(child.y, 100, "Child should be at bottom of cell (200 - 100)") luaunit.assertEquals(child.y, 100, "Child should be at bottom of cell (200 - 100)")
@@ -267,24 +267,24 @@ function TestGridLayout:test_grid_with_padding()
id = "grid", id = "grid",
x = 0, x = 0,
y = 0, y = 0,
width = 500, -- Total width width = 500, -- Total width
height = 500, height = 500,
padding = { top = 50, right = 50, bottom = 50, left = 50 }, padding = { top = 50, right = 50, bottom = 50, left = 50 },
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Available space: 500 - 50 - 50 = 400 -- Available space: 500 - 50 - 50 = 400
-- Cell size: 400 / 2 = 200 -- Cell size: 400 / 2 = 200
-- Child should be positioned at padding.left, padding.top -- Child should be positioned at padding.left, padding.top
@@ -304,17 +304,17 @@ function TestGridLayout:test_grid_with_absolute_child()
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
-- Regular child -- Regular child
local child1 = FlexLove.new({ local child1 = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
-- Absolutely positioned child (should be ignored by grid layout) -- Absolutely positioned child (should be ignored by grid layout)
local child2 = FlexLove.new({ local child2 = FlexLove.new({
id = "child2", id = "child2",
@@ -323,28 +323,28 @@ function TestGridLayout:test_grid_with_absolute_child()
x = 10, x = 10,
y = 10, y = 10,
width = 30, width = 30,
height = 30 height = 30,
}) })
-- Another regular child -- Another regular child
local child3 = FlexLove.new({ local child3 = FlexLove.new({
id = "child3", id = "child3",
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- child1 should be in first grid cell (0, 0) -- child1 should be in first grid cell (0, 0)
luaunit.assertEquals(child1.x, 0, "Child 1 should be at x=0") luaunit.assertEquals(child1.x, 0, "Child 1 should be at x=0")
luaunit.assertEquals(child1.y, 0, "Child 1 should be at y=0") luaunit.assertEquals(child1.y, 0, "Child 1 should be at y=0")
-- child2 should keep its absolute position -- child2 should keep its absolute position
luaunit.assertEquals(child2.x, 10, "Absolute child should keep x=10") luaunit.assertEquals(child2.x, 10, "Absolute child should keep x=10")
luaunit.assertEquals(child2.y, 10, "Absolute child should keep y=10") luaunit.assertEquals(child2.y, 10, "Absolute child should keep y=10")
-- child3 should be in second grid cell (200, 0), not third -- child3 should be in second grid cell (200, 0), not third
luaunit.assertEquals(child3.x, 200, "Child 3 should be in second cell at x=200") luaunit.assertEquals(child3.x, 200, "Child 3 should be in second cell at x=200")
luaunit.assertEquals(child3.y, 0, "Child 3 should be in second cell at y=0") luaunit.assertEquals(child3.y, 0, "Child 3 should be in second cell at y=0")
@@ -360,13 +360,13 @@ function TestGridLayout:test_empty_grid()
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
-- No children -- No children
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Should not crash -- Should not crash
luaunit.assertEquals(#container.children, 0, "Grid should have no children") luaunit.assertEquals(#container.children, 0, "Grid should have no children")
end end
@@ -380,21 +380,21 @@ function TestGridLayout:test_grid_zero_dimensions()
width = 400, width = 400,
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 0, -- Invalid: 0 rows gridRows = 0, -- Invalid: 0 rows
gridColumns = 0 -- Invalid: 0 columns gridColumns = 0, -- Invalid: 0 columns
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
-- This might cause division by zero or other errors -- This might cause division by zero or other errors
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Test passes if it doesn't crash -- Test passes if it doesn't crash
luaunit.assertTrue(true, "Grid with 0 dimensions should not crash") luaunit.assertTrue(true, "Grid with 0 dimensions should not crash")
end end
@@ -409,9 +409,9 @@ function TestGridLayout:test_nested_grids()
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
-- First cell contains another grid -- First cell contains another grid
local innerGrid = FlexLove.new({ local innerGrid = FlexLove.new({
id = "inner", id = "inner",
@@ -420,22 +420,22 @@ function TestGridLayout:test_nested_grids()
height = 200, height = 200,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
-- Add children to inner grid -- Add children to inner grid
for i = 1, 4 do for i = 1, 4 do
FlexLove.new({ FlexLove.new({
id = "inner_child" .. i, id = "inner_child" .. i,
parent = innerGrid, parent = innerGrid,
width = 25, width = 25,
height = 25 height = 25,
}) })
end end
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Inner grid should be positioned in first cell of outer grid -- Inner grid should be positioned in first cell of outer grid
luaunit.assertEquals(innerGrid.x, 0, "Inner grid should be at x=0") luaunit.assertEquals(innerGrid.x, 0, "Inner grid should be at x=0")
luaunit.assertEquals(innerGrid.y, 0, "Inner grid should be at y=0") luaunit.assertEquals(innerGrid.y, 0, "Inner grid should be at y=0")
@@ -452,9 +452,9 @@ function TestGridLayout:test_grid_with_reserved_space()
height = 400, height = 400,
positioning = "grid", positioning = "grid",
gridRows = 2, gridRows = 2,
gridColumns = 2 gridColumns = 2,
}) })
-- Absolute child with left positioning (reserves left space) -- Absolute child with left positioning (reserves left space)
FlexLove.new({ FlexLove.new({
id = "absolute_left", id = "absolute_left",
@@ -463,20 +463,20 @@ function TestGridLayout:test_grid_with_reserved_space()
left = 0, left = 0,
top = 0, top = 0,
width = 50, width = 50,
height = 50 height = 50,
}) })
-- Regular grid child -- Regular grid child
local child1 = FlexLove.new({ local child1 = FlexLove.new({
id = "child1", id = "child1",
parent = container, parent = container,
width = 50, width = 50,
height = 50 height = 50,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Grid should account for reserved space -- Grid should account for reserved space
-- Available width: 400 - 50 (reserved left) = 350 -- Available width: 400 - 50 (reserved left) = 350
-- Cell width: 350 / 2 = 175 -- Cell width: 350 / 2 = 175

View File

@@ -0,0 +1,166 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local ImageCache = require("modules.ImageCache")
TestImageCache = {}
function TestImageCache:setUp()
-- Clear cache before each test
ImageCache.clear()
end
function TestImageCache:tearDown()
ImageCache.clear()
end
-- Unhappy path tests
function TestImageCache:testLoadWithNilPath()
local img, err = ImageCache.load(nil)
luaunit.assertNil(img)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "Invalid image path")
end
function TestImageCache:testLoadWithEmptyString()
local img, err = ImageCache.load("")
luaunit.assertNil(img)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "Invalid image path")
end
function TestImageCache:testLoadWithInvalidType()
local img, err = ImageCache.load(123)
luaunit.assertNil(img)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "Invalid image path")
end
function TestImageCache:testLoadWithInvalidPath()
local img, err = ImageCache.load("nonexistent/path/to/image.png")
luaunit.assertNil(img)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "Failed to load image")
end
function TestImageCache:testLoadWithInvalidExtension()
local img, err = ImageCache.load("test.txt")
luaunit.assertNil(img)
luaunit.assertNotNil(err)
luaunit.assertStrContains(err, "Failed to load image")
end
function TestImageCache:testGetWithNilPath()
local img = ImageCache.get(nil)
luaunit.assertNil(img)
end
function TestImageCache:testGetWithEmptyString()
local img = ImageCache.get("")
luaunit.assertNil(img)
end
function TestImageCache:testGetWithInvalidType()
local img = ImageCache.get({})
luaunit.assertNil(img)
end
function TestImageCache:testGetWithNonCachedPath()
local img = ImageCache.get("some/random/path.png")
luaunit.assertNil(img)
end
function TestImageCache:testGetImageDataWithNilPath()
local imgData = ImageCache.getImageData(nil)
luaunit.assertNil(imgData)
end
function TestImageCache:testGetImageDataWithEmptyString()
local imgData = ImageCache.getImageData("")
luaunit.assertNil(imgData)
end
function TestImageCache:testGetImageDataWithInvalidType()
local imgData = ImageCache.getImageData(123)
luaunit.assertNil(imgData)
end
function TestImageCache:testGetImageDataWithNonCachedPath()
local imgData = ImageCache.getImageData("some/path.png")
luaunit.assertNil(imgData)
end
function TestImageCache:testRemoveWithNilPath()
local removed = ImageCache.remove(nil)
luaunit.assertFalse(removed)
end
function TestImageCache:testRemoveWithEmptyString()
local removed = ImageCache.remove("")
luaunit.assertFalse(removed)
end
function TestImageCache:testRemoveWithInvalidType()
local removed = ImageCache.remove(123)
luaunit.assertFalse(removed)
end
function TestImageCache:testRemoveWithNonCachedPath()
local removed = ImageCache.remove("uncached/image.png")
luaunit.assertFalse(removed)
end
function TestImageCache:testClearEmptyCache()
-- Should not error on empty cache
ImageCache.clear()
local stats = ImageCache.getStats()
luaunit.assertEquals(stats.count, 0)
luaunit.assertEquals(stats.memoryEstimate, 0)
end
function TestImageCache:testGetStatsOnEmptyCache()
ImageCache.clear()
local stats = ImageCache.getStats()
luaunit.assertNotNil(stats)
luaunit.assertEquals(stats.count, 0)
luaunit.assertEquals(stats.memoryEstimate, 0)
end
function TestImageCache:testPathNormalization()
-- Test that paths with different slashes are normalized
local path1 = "themes/space.png"
local path2 = "themes\\space.png" -- Windows style
-- Both should normalize to the same path
-- (If first load fails, both should fail the same way)
local img1, err1 = ImageCache.load(path1)
local img2, err2 = ImageCache.load(path2)
-- Both should have same result (either both nil or both same image)
if img1 == nil then
luaunit.assertNil(img2)
else
luaunit.assertEquals(img1, img2)
end
end
function TestImageCache:testLoadWithImageDataFlag()
-- Test loading with imageData flag on invalid path
local img, err = ImageCache.load("invalid/path.png", true)
luaunit.assertNil(img)
luaunit.assertNotNil(err)
end
function TestImageCache:testMultipleClearCalls()
-- Clear multiple times should not error
ImageCache.clear()
ImageCache.clear()
ImageCache.clear()
local stats = ImageCache.getStats()
luaunit.assertEquals(stats.count, 0)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -0,0 +1,247 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local ImageRenderer = require("modules.ImageRenderer")
TestImageRenderer = {}
function TestImageRenderer:setUp()
-- Create a mock image for testing
self.mockImage = {
getDimensions = function()
return 100, 100
end,
}
end
-- Unhappy path tests for calculateFit
function TestImageRenderer:testCalculateFitWithZeroImageWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(0, 100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroImageHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 0, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeImageWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(-100, 100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeImageHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, -100, 200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroBoundsWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 0, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithZeroBoundsHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 200, 0, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeBoundsWidth()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, -200, 200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithNegativeBoundsHeight()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 200, -200, "fill")
end)
end
function TestImageRenderer:testCalculateFitWithInvalidFitMode()
luaunit.assertError(function()
ImageRenderer.calculateFit(100, 100, 200, 200, "invalid-mode")
end)
end
function TestImageRenderer:testCalculateFitWithNilFitMode()
-- Should default to "fill"
local result = ImageRenderer.calculateFit(100, 100, 200, 200, nil)
luaunit.assertNotNil(result)
luaunit.assertEquals(result.dw, 200)
luaunit.assertEquals(result.dh, 200)
end
function TestImageRenderer:testCalculateFitFillMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "fill")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitContainMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "contain")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitCoverMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "cover")
luaunit.assertEquals(result.scaleX, 2)
luaunit.assertEquals(result.scaleY, 2)
end
function TestImageRenderer:testCalculateFitNoneMode()
local result = ImageRenderer.calculateFit(100, 100, 200, 200, "none")
luaunit.assertEquals(result.scaleX, 1)
luaunit.assertEquals(result.scaleY, 1)
end
function TestImageRenderer:testCalculateFitScaleDownModeWithLargeImage()
local result = ImageRenderer.calculateFit(300, 300, 200, 200, "scale-down")
-- Should behave like contain for larger images
luaunit.assertNotNil(result)
end
function TestImageRenderer:testCalculateFitScaleDownModeWithSmallImage()
local result = ImageRenderer.calculateFit(50, 50, 200, 200, "scale-down")
-- Should behave like none for smaller images
luaunit.assertEquals(result.scaleX, 1)
luaunit.assertEquals(result.scaleY, 1)
end
-- Unhappy path tests for _parsePosition
function TestImageRenderer:testParsePositionWithNil()
local x, y = ImageRenderer._parsePosition(nil)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithEmptyString()
local x, y = ImageRenderer._parsePosition("")
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithInvalidType()
local x, y = ImageRenderer._parsePosition(123)
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithInvalidKeyword()
local x, y = ImageRenderer._parsePosition("invalid keyword")
-- Should default to center
luaunit.assertEquals(x, 0.5)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithMixedValid()
local x, y = ImageRenderer._parsePosition("left top")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0)
end
function TestImageRenderer:testParsePositionWithPercentage()
local x, y = ImageRenderer._parsePosition("75% 25%")
luaunit.assertAlmostEquals(x, 0.75, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
function TestImageRenderer:testParsePositionWithOutOfRangePercentage()
local x, y = ImageRenderer._parsePosition("150% -50%")
-- 150% clamps to 1, but -50% doesn't match pattern so defaults to 0.5
luaunit.assertEquals(x, 1)
luaunit.assertEquals(y, 0.5)
end
function TestImageRenderer:testParsePositionWithSingleValue()
local x, y = ImageRenderer._parsePosition("left")
luaunit.assertEquals(x, 0)
luaunit.assertEquals(y, 0.5) -- Should use center for Y
end
function TestImageRenderer:testParsePositionWithSinglePercentage()
local x, y = ImageRenderer._parsePosition("25%")
luaunit.assertAlmostEquals(x, 0.25, 0.01)
luaunit.assertAlmostEquals(y, 0.25, 0.01)
end
-- Unhappy path tests for draw
function TestImageRenderer:testDrawWithNilImage()
-- Should not crash, just return early
ImageRenderer.draw(nil, 0, 0, 100, 100, "fill")
-- If we get here without error, test passes
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithZeroWidth()
-- Should error in calculateFit
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 0, 100, "fill")
end)
end
function TestImageRenderer:testDrawWithZeroHeight()
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 100, 0, "fill")
end)
end
function TestImageRenderer:testDrawWithNegativeOpacity()
-- Should work but render with negative opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", -0.5)
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithOpacityGreaterThanOne()
-- Should work but render with >1 opacity
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "fill", "center center", 2.0)
luaunit.assertTrue(true)
end
function TestImageRenderer:testDrawWithInvalidFitMode()
luaunit.assertError(function()
ImageRenderer.draw(self.mockImage, 0, 0, 100, 100, "invalid")
end)
end
function TestImageRenderer:testCalculateFitWithVerySmallBounds()
local result = ImageRenderer.calculateFit(1000, 1000, 1, 1, "contain")
luaunit.assertNotNil(result)
-- Scale should be very small
luaunit.assertTrue(result.scaleX < 0.01)
end
function TestImageRenderer:testCalculateFitWithVeryLargeBounds()
local result = ImageRenderer.calculateFit(10, 10, 10000, 10000, "contain")
luaunit.assertNotNil(result)
-- Scale should be very large
luaunit.assertTrue(result.scaleX > 100)
end
function TestImageRenderer:testCalculateFitWithAspectRatioMismatch()
-- Wide image, tall bounds
local result = ImageRenderer.calculateFit(200, 100, 100, 200, "contain")
luaunit.assertNotNil(result)
-- Should maintain aspect ratio
luaunit.assertEquals(result.scaleX, result.scaleY)
end
function TestImageRenderer:testCalculateFitCoverWithAspectRatioMismatch()
-- Wide image, tall bounds
local result = ImageRenderer.calculateFit(200, 100, 100, 200, "cover")
luaunit.assertNotNil(result)
luaunit.assertEquals(result.scaleX, result.scaleY)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -0,0 +1,209 @@
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local ImageScaler = require("modules.ImageScaler")
TestImageScaler = {}
function TestImageScaler:setUp()
-- Create a minimal mock ImageData
self.mockImageData = {
getPixel = function(self, x, y)
-- Return deterministic values based on position
return (x % 256) / 255, (y % 256) / 255, 0.5, 1.0
end,
}
end
-- Unhappy path tests for scaleNearest
function TestImageScaler:testScaleNearestWithNilSource()
luaunit.assertError(function()
ImageScaler.scaleNearest(nil, 0, 0, 10, 10, 20, 20)
end)
end
function TestImageScaler:testScaleNearestWithZeroSourceWidth()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 0, 10, 20, 20)
end)
end
function TestImageScaler:testScaleNearestWithZeroSourceHeight()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 0, 20, 20)
end)
end
function TestImageScaler:testScaleNearestWithNegativeSourceWidth()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, -10, 10, 20, 20)
end)
end
function TestImageScaler:testScaleNearestWithNegativeSourceHeight()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, -10, 20, 20)
end)
end
function TestImageScaler:testScaleNearestWithZeroDestWidth()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 0, 20)
end)
end
function TestImageScaler:testScaleNearestWithZeroDestHeight()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, 0)
end)
end
function TestImageScaler:testScaleNearestWithNegativeDestWidth()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, -20, 20)
end)
end
function TestImageScaler:testScaleNearestWithNegativeDestHeight()
luaunit.assertError(function()
ImageScaler.scaleNearest(self.mockImageData, 0, 0, 10, 10, 20, -20)
end)
end
-- Unhappy path tests for scaleBilinear
function TestImageScaler:testScaleBilinearWithNilSource()
luaunit.assertError(function()
ImageScaler.scaleBilinear(nil, 0, 0, 10, 10, 20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithZeroSourceWidth()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 0, 10, 20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithZeroSourceHeight()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 0, 20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithNegativeSourceWidth()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, -10, 10, 20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithNegativeSourceHeight()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, -10, 20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithZeroDestWidth()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 0, 20)
end)
end
function TestImageScaler:testScaleBilinearWithZeroDestHeight()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, 0)
end)
end
function TestImageScaler:testScaleBilinearWithNegativeDestWidth()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, -20, 20)
end)
end
function TestImageScaler:testScaleBilinearWithNegativeDestHeight()
luaunit.assertError(function()
ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 10, 10, 20, -20)
end)
end
-- Edge case tests
function TestImageScaler:testScaleNearestWithVeryLargeUpscale()
-- Scale 1x1 to 50x50 (extreme upscale, but fast for testing)
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 1, 1, 50, 50)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWithVeryLargeDownscale()
-- Scale 50x50 to 1x1 (extreme downscale)
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 50, 50, 1, 1)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWithVeryLargeUpscale()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 1, 1, 50, 50)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWithVeryLargeDownscale()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 50, 50, 1, 1)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWithNonIntegerDimensions()
-- Fractional source dimensions (should work with floor/ceil)
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 5.5, 5.5, 10, 10)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWithNonIntegerDimensions()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 5.5, 5.5, 10, 10)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWith1x1Source()
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 1, 1, 5, 5)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWith1x1Source()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 1, 1, 5, 5)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWith1x1Dest()
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 5, 5, 1, 1)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWith1x1Dest()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 5, 5, 1, 1)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWithNonZeroSourceOffset()
-- Source region offset from 0,0
local result = ImageScaler.scaleNearest(self.mockImageData, 10, 10, 5, 5, 10, 10)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWithNonZeroSourceOffset()
local result = ImageScaler.scaleBilinear(self.mockImageData, 10, 10, 5, 5, 10, 10)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleNearestWithAspectRatioChange()
-- Change aspect ratio dramatically
local result = ImageScaler.scaleNearest(self.mockImageData, 0, 0, 5, 20, 20, 5)
luaunit.assertNotNil(result)
end
function TestImageScaler:testScaleBilinearWithAspectRatioChange()
local result = ImageScaler.scaleBilinear(self.mockImageData, 0, 0, 5, 20, 20, 5)
luaunit.assertNotNil(result)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -0,0 +1,211 @@
-- Test suite for InputEvent module
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local InputEvent = require("modules.InputEvent")
TestInputEvent = {}
-- Test: new() creates click event with all properties
function TestInputEvent:test_new_creates_click_event()
local event = InputEvent.new({
type = "click",
button = 1,
x = 100,
y = 200,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertNotNil(event, "new() should return event")
luaunit.assertEquals(event.type, "click")
luaunit.assertEquals(event.button, 1)
luaunit.assertEquals(event.x, 100)
luaunit.assertEquals(event.y, 200)
luaunit.assertNotNil(event.modifiers)
luaunit.assertEquals(event.modifiers.shift, false)
end
-- Test: new() creates press event
function TestInputEvent:test_new_creates_press_event()
local event = InputEvent.new({
type = "press",
button = 1,
x = 50,
y = 75,
modifiers = { shift = true, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.type, "press")
luaunit.assertEquals(event.button, 1)
luaunit.assertEquals(event.x, 50)
luaunit.assertEquals(event.y, 75)
luaunit.assertEquals(event.modifiers.shift, true)
end
-- Test: new() creates release event
function TestInputEvent:test_new_creates_release_event()
local event = InputEvent.new({
type = "release",
button = 1,
x = 150,
y = 250,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.type, "release")
end
-- Test: new() creates rightclick event with button 2
function TestInputEvent:test_new_creates_rightclick_event()
local event = InputEvent.new({
type = "rightclick",
button = 2,
x = 100,
y = 100,
modifiers = { shift = false, ctrl = true, alt = false, super = false },
})
luaunit.assertEquals(event.type, "rightclick")
luaunit.assertEquals(event.button, 2)
luaunit.assertEquals(event.modifiers.ctrl, true)
end
-- Test: new() creates middleclick event with button 3
function TestInputEvent:test_new_creates_middleclick_event()
local event = InputEvent.new({
type = "middleclick",
button = 3,
x = 200,
y = 300,
modifiers = { shift = false, ctrl = false, alt = true, super = false },
})
luaunit.assertEquals(event.type, "middleclick")
luaunit.assertEquals(event.button, 3)
luaunit.assertEquals(event.modifiers.alt, true)
end
-- Test: new() creates drag event with dx and dy
function TestInputEvent:test_new_creates_drag_event_with_deltas()
local event = InputEvent.new({
type = "drag",
button = 1,
x = 100,
y = 100,
dx = 20,
dy = -15,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.type, "drag")
luaunit.assertEquals(event.dx, 20)
luaunit.assertEquals(event.dy, -15)
end
-- Test: new() defaults clickCount to 1 if not provided
function TestInputEvent:test_new_defaults_clickCount_to_one()
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.clickCount, 1)
end
-- Test: new() accepts custom clickCount for double-click
function TestInputEvent:test_new_accepts_custom_clickCount()
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
clickCount = 2,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.clickCount, 2)
end
-- Test: new() accepts custom clickCount for triple-click
function TestInputEvent:test_new_accepts_triple_click()
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
clickCount = 3,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.clickCount, 3)
end
-- Test: new() defaults timestamp to current time if not provided
function TestInputEvent:test_new_defaults_timestamp()
local before = love.timer.getTime()
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
local after = love.timer.getTime()
luaunit.assertNotNil(event.timestamp)
luaunit.assertTrue(event.timestamp >= before)
luaunit.assertTrue(event.timestamp <= after)
end
-- Test: new() accepts custom timestamp
function TestInputEvent:test_new_accepts_custom_timestamp()
local customTime = 12345.678
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
timestamp = customTime,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertEquals(event.timestamp, customTime)
end
-- Test: new() handles all modifier keys
function TestInputEvent:test_new_handles_all_modifier_keys()
local event = InputEvent.new({
type = "click",
button = 1,
x = 0,
y = 0,
modifiers = { shift = true, ctrl = true, alt = true, super = true },
})
luaunit.assertEquals(event.modifiers.shift, true)
luaunit.assertEquals(event.modifiers.ctrl, true)
luaunit.assertEquals(event.modifiers.alt, true)
luaunit.assertEquals(event.modifiers.super, true)
end
-- Test: new() handles nil dx/dy for non-drag events
function TestInputEvent:test_new_handles_nil_deltas()
local event = InputEvent.new({
type = "click",
button = 1,
x = 100,
y = 100,
modifiers = { shift = false, ctrl = false, alt = false, super = false },
})
luaunit.assertNil(event.dx)
luaunit.assertNil(event.dy)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -16,7 +16,7 @@ function TestLayoutEdgeCases:setUp()
self.warnings = {} self.warnings = {}
self.originalWarn = ErrorHandler.warn self.originalWarn = ErrorHandler.warn
ErrorHandler.warn = function(module, message) ErrorHandler.warn = function(module, message)
table.insert(self.warnings, {module = module, message = message}) table.insert(self.warnings, { module = module, message = message })
end end
end end
@@ -35,22 +35,22 @@ function TestLayoutEdgeCases:test_percentage_width_with_auto_parent_warns()
-- width not specified - auto-sizing width -- width not specified - auto-sizing width
height = 200, height = 200,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal" flexDirection = "horizontal",
}) })
FlexLove.new({ FlexLove.new({
id = "child_with_percentage", id = "child_with_percentage",
parent = container, parent = container,
width = "50%", -- Percentage width with auto-sizing parent - should warn width = "50%", -- Percentage width with auto-sizing parent - should warn
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Check that a warning was issued -- Check that a warning was issued
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage width with auto-sizing parent") luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage width with auto-sizing parent")
local found = false local found = false
for _, warning in ipairs(self.warnings) do for _, warning in ipairs(self.warnings) do
if warning.message:match("percentage width") and warning.message:match("auto%-sizing") then if warning.message:match("percentage width") and warning.message:match("auto%-sizing") then
@@ -58,7 +58,7 @@ function TestLayoutEdgeCases:test_percentage_width_with_auto_parent_warns()
break break
end end
end end
luaunit.assertTrue(found, "Warning should mention percentage width and auto-sizing") luaunit.assertTrue(found, "Warning should mention percentage width and auto-sizing")
end end
@@ -71,22 +71,22 @@ function TestLayoutEdgeCases:test_percentage_height_with_auto_parent_warns()
width = 200, width = 200,
-- height not specified - auto-sizing height -- height not specified - auto-sizing height
positioning = "flex", positioning = "flex",
flexDirection = "vertical" flexDirection = "vertical",
}) })
FlexLove.new({ FlexLove.new({
id = "child_with_percentage", id = "child_with_percentage",
parent = container, parent = container,
width = 100, width = 100,
height = "50%" -- Percentage height with auto-sizing parent - should warn height = "50%", -- Percentage height with auto-sizing parent - should warn
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Check that a warning was issued -- Check that a warning was issued
luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage height with auto-sizing parent") luaunit.assertTrue(#self.warnings > 0, "Should issue warning for percentage height with auto-sizing parent")
local found = false local found = false
for _, warning in ipairs(self.warnings) do for _, warning in ipairs(self.warnings) do
if warning.message:match("percentage height") and warning.message:match("auto%-sizing") then if warning.message:match("percentage height") and warning.message:match("auto%-sizing") then
@@ -94,7 +94,7 @@ function TestLayoutEdgeCases:test_percentage_height_with_auto_parent_warns()
break break
end end
end end
luaunit.assertTrue(found, "Warning should mention percentage height and auto-sizing") luaunit.assertTrue(found, "Warning should mention percentage height and auto-sizing")
end end
@@ -107,19 +107,19 @@ function TestLayoutEdgeCases:test_pixel_width_with_auto_parent_no_warn()
-- width not specified - auto-sizing -- width not specified - auto-sizing
height = 200, height = 200,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal" flexDirection = "horizontal",
}) })
FlexLove.new({ FlexLove.new({
id = "child_with_pixels", id = "child_with_pixels",
parent = container, parent = container,
width = 100, -- Pixel width - should NOT warn width = 100, -- Pixel width - should NOT warn
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Check that NO warning was issued about percentage sizing -- Check that NO warning was issued about percentage sizing
for _, warning in ipairs(self.warnings) do for _, warning in ipairs(self.warnings) do
local hasPercentageWarning = warning.message:match("percentage") and warning.message:match("auto%-sizing") local hasPercentageWarning = warning.message:match("percentage") and warning.message:match("auto%-sizing")
@@ -135,23 +135,23 @@ function TestLayoutEdgeCases:test_css_positioning_top_offset()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
top = 50, -- 50px from top top = 50, -- 50px from top
left = 0, left = 0,
width = 100, width = 100,
height = 100 height = 100,
}) })
-- Trigger layout by ending and restarting frame -- Trigger layout by ending and restarting frame
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Child should be positioned 50px from container's top edge (accounting for padding) -- Child should be positioned 50px from container's top edge (accounting for padding)
local expectedY = container.y + container.padding.top + 50 local expectedY = container.y + container.padding.top + 50
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with top offset") luaunit.assertEquals(child.y, expectedY, "Child should be positioned with top offset")
@@ -165,22 +165,22 @@ function TestLayoutEdgeCases:test_css_positioning_bottom_offset()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
bottom = 50, -- 50px from bottom bottom = 50, -- 50px from bottom
left = 0, left = 0,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Child should be positioned 50px from container's bottom edge -- Child should be positioned 50px from container's bottom edge
local expectedY = container.y + container.padding.top + container.height - 50 - child:getBorderBoxHeight() local expectedY = container.y + container.padding.top + container.height - 50 - child:getBorderBoxHeight()
luaunit.assertEquals(child.y, expectedY, "Child should be positioned with bottom offset") luaunit.assertEquals(child.y, expectedY, "Child should be positioned with bottom offset")
@@ -194,22 +194,22 @@ function TestLayoutEdgeCases:test_css_positioning_left_offset()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
top = 0, top = 0,
left = 50, -- 50px from left left = 50, -- 50px from left
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Child should be positioned 50px from container's left edge -- Child should be positioned 50px from container's left edge
local expectedX = container.x + container.padding.left + 50 local expectedX = container.x + container.padding.left + 50
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with left offset") luaunit.assertEquals(child.x, expectedX, "Child should be positioned with left offset")
@@ -223,22 +223,22 @@ function TestLayoutEdgeCases:test_css_positioning_right_offset()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
top = 0, top = 0,
right = 50, -- 50px from right right = 50, -- 50px from right
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Child should be positioned 50px from container's right edge -- Child should be positioned 50px from container's right edge
local expectedX = container.x + container.padding.left + container.width - 50 - child:getBorderBoxWidth() local expectedX = container.x + container.padding.left + container.width - 50 - child:getBorderBoxWidth()
luaunit.assertEquals(child.x, expectedX, "Child should be positioned with right offset") luaunit.assertEquals(child.x, expectedX, "Child should be positioned with right offset")
@@ -252,23 +252,23 @@ function TestLayoutEdgeCases:test_css_positioning_top_and_bottom()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
top = 10, top = 10,
bottom = 20, -- Both specified - last one wins in current implementation bottom = 20, -- Both specified - last one wins in current implementation
left = 0, left = 0,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Bottom should override top -- Bottom should override top
local expectedY = container.y + container.padding.top + container.height - 20 - child:getBorderBoxHeight() local expectedY = container.y + container.padding.top + container.height - 20 - child:getBorderBoxHeight()
luaunit.assertEquals(child.y, expectedY, "Bottom offset should override top when both specified") luaunit.assertEquals(child.y, expectedY, "Bottom offset should override top when both specified")
@@ -282,23 +282,23 @@ function TestLayoutEdgeCases:test_css_positioning_left_and_right()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
positioning = "absolute", positioning = "absolute",
top = 0, top = 0,
left = 10, left = 10,
right = 20, -- Both specified - last one wins in current implementation right = 20, -- Both specified - last one wins in current implementation
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Right should override left -- Right should override left
local expectedX = container.x + container.padding.left + container.width - 20 - child:getBorderBoxWidth() local expectedX = container.x + container.padding.left + container.width - 20 - child:getBorderBoxWidth()
luaunit.assertEquals(child.x, expectedX, "Right offset should override left when both specified") luaunit.assertEquals(child.x, expectedX, "Right offset should override left when both specified")
@@ -313,9 +313,9 @@ function TestLayoutEdgeCases:test_css_positioning_with_padding()
width = 400, width = 400,
height = 400, height = 400,
padding = { top = 20, right = 20, bottom = 20, left = 20 }, padding = { top = 20, right = 20, bottom = 20, left = 20 },
positioning = "absolute" positioning = "absolute",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
@@ -323,16 +323,16 @@ function TestLayoutEdgeCases:test_css_positioning_with_padding()
top = 10, top = 10,
left = 10, left = 10,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Offsets should be relative to content area (after padding) -- Offsets should be relative to content area (after padding)
local expectedX = container.x + container.padding.left + 10 local expectedX = container.x + container.padding.left + 10
local expectedY = container.y + container.padding.top + 10 local expectedY = container.y + container.padding.top + 10
luaunit.assertEquals(child.x, expectedX, "Left offset should account for container padding") luaunit.assertEquals(child.x, expectedX, "Left offset should account for container padding")
luaunit.assertEquals(child.y, expectedY, "Top offset should account for container padding") luaunit.assertEquals(child.y, expectedY, "Top offset should account for container padding")
end end
@@ -346,21 +346,21 @@ function TestLayoutEdgeCases:test_css_positioning_ignored_in_flex()
width = 400, width = 400,
height = 400, height = 400,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal" flexDirection = "horizontal",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
top = 100, -- This should be IGNORED in flex layout top = 100, -- This should be IGNORED in flex layout
left = 100, -- This should be IGNORED in flex layout left = 100, -- This should be IGNORED in flex layout
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- In flex layout, child should be positioned by flex rules, not CSS offsets -- In flex layout, child should be positioned by flex rules, not CSS offsets
-- Child should be at (0, 0) relative to container content area -- Child should be at (0, 0) relative to container content area
luaunit.assertEquals(child.x, 0, "CSS offsets should be ignored in flex layout") luaunit.assertEquals(child.x, 0, "CSS offsets should be ignored in flex layout")
@@ -375,9 +375,9 @@ function TestLayoutEdgeCases:test_css_positioning_in_relative_container()
y = 100, y = 100,
width = 400, width = 400,
height = 400, height = 400,
positioning = "relative" positioning = "relative",
}) })
local child = FlexLove.new({ local child = FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
@@ -385,16 +385,16 @@ function TestLayoutEdgeCases:test_css_positioning_in_relative_container()
top = 30, top = 30,
left = 30, left = 30,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame() FlexLove.beginFrame()
-- Should work the same as absolute container -- Should work the same as absolute container
local expectedX = container.x + container.padding.left + 30 local expectedX = container.x + container.padding.left + 30
local expectedY = container.y + container.padding.top + 30 local expectedY = container.y + container.padding.top + 30
luaunit.assertEquals(child.x, expectedX, "CSS positioning should work in relative containers") luaunit.assertEquals(child.x, expectedX, "CSS positioning should work in relative containers")
luaunit.assertEquals(child.y, expectedY, "CSS positioning should work in relative containers") luaunit.assertEquals(child.y, expectedY, "CSS positioning should work in relative containers")
end end

View File

@@ -716,7 +716,6 @@ local function createMockElement(props)
} }
end end
-- Run tests if this file is executed directly -- Run tests if this file is executed directly
if not _G.RUNNING_ALL_TESTS then if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run()) os.exit(luaunit.LuaUnit.run())

View File

@@ -24,9 +24,9 @@ function TestOverflowDetection:test_vertical_overflow_detected()
y = 0, y = 0,
width = 200, width = 200,
height = 100, height = 100,
overflow = "scroll" overflow = "scroll",
}) })
-- Add child that exceeds container height -- Add child that exceeds container height
FlexLove.new({ FlexLove.new({
id = "tall_child", id = "tall_child",
@@ -34,13 +34,13 @@ function TestOverflowDetection:test_vertical_overflow_detected()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 200 -- Taller than container (100) height = 200, -- Taller than container (100)
}) })
-- Force layout to trigger detectOverflow -- Force layout to trigger detectOverflow
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Check if overflow was detected -- Check if overflow was detected
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow") luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
@@ -54,22 +54,22 @@ function TestOverflowDetection:test_horizontal_overflow_detected()
y = 0, y = 0,
width = 100, width = 100,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
-- Add child that exceeds container width -- Add child that exceeds container width
FlexLove.new({ FlexLove.new({
id = "wide_child", id = "wide_child",
parent = container, parent = container,
x = 0, x = 0,
y = 0, y = 0,
width = 300, -- Wider than container (100) width = 300, -- Wider than container (100)
height = 50 height = 50,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow") luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow") luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
@@ -82,9 +82,9 @@ function TestOverflowDetection:test_both_axes_overflow()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
overflow = "scroll" overflow = "scroll",
}) })
-- Add child that exceeds both dimensions -- Add child that exceeds both dimensions
FlexLove.new({ FlexLove.new({
id = "large_child", id = "large_child",
@@ -92,12 +92,12 @@ function TestOverflowDetection:test_both_axes_overflow()
x = 0, x = 0,
y = 0, y = 0,
width = 200, width = 200,
height = 200 height = 200,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow") luaunit.assertTrue(maxScrollX > 0, "Should detect horizontal overflow")
luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow") luaunit.assertTrue(maxScrollY > 0, "Should detect vertical overflow")
@@ -110,9 +110,9 @@ function TestOverflowDetection:test_no_overflow_when_content_fits()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
-- Add child that fits within container -- Add child that fits within container
FlexLove.new({ FlexLove.new({
id = "small_child", id = "small_child",
@@ -120,12 +120,12 @@ function TestOverflowDetection:test_no_overflow_when_content_fits()
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow") luaunit.assertEquals(maxScrollX, 0, "Should not have horizontal overflow")
luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow") luaunit.assertEquals(maxScrollY, 0, "Should not have vertical overflow")
@@ -140,22 +140,22 @@ function TestOverflowDetection:test_overflow_with_multiple_children()
height = 200, height = 200,
overflow = "scroll", overflow = "scroll",
positioning = "flex", positioning = "flex",
flexDirection = "vertical" flexDirection = "vertical",
}) })
-- Add multiple children that together exceed container -- Add multiple children that together exceed container
for i = 1, 5 do for i = 1, 5 do
FlexLove.new({ FlexLove.new({
id = "child_" .. i, id = "child_" .. i,
parent = container, parent = container,
width = 150, width = 150,
height = 60 -- 5 * 60 = 300, exceeds container height of 200 height = 60, -- 5 * 60 = 300, exceeds container height of 200
}) })
end end
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollY > 0, "Should detect overflow from multiple children") luaunit.assertTrue(maxScrollY > 0, "Should detect overflow from multiple children")
end end
@@ -168,22 +168,22 @@ function TestOverflowDetection:test_overflow_with_padding()
width = 200, width = 200,
height = 200, height = 200,
padding = { top = 10, right = 10, bottom = 10, left = 10 }, padding = { top = 10, right = 10, bottom = 10, left = 10 },
overflow = "scroll" overflow = "scroll",
}) })
-- Child that fits in container but exceeds available content area (200 - 20 = 180) -- Child that fits in container but exceeds available content area (200 - 20 = 180)
FlexLove.new({ FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
x = 0, x = 0,
y = 0, y = 0,
width = 190, -- Exceeds content width (180) width = 190, -- Exceeds content width (180)
height = 100 height = 100,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should detect overflow accounting for padding") luaunit.assertTrue(maxScrollX > 0, "Should detect overflow accounting for padding")
end end
@@ -197,9 +197,9 @@ function TestOverflowDetection:test_overflow_with_margins()
height = 200, height = 200,
positioning = "flex", positioning = "flex",
flexDirection = "horizontal", flexDirection = "horizontal",
overflow = "scroll" overflow = "scroll",
}) })
-- Child with margins that contribute to overflow -- Child with margins that contribute to overflow
-- In flex layout, margins are properly accounted for in positioning -- In flex layout, margins are properly accounted for in positioning
FlexLove.new({ FlexLove.new({
@@ -207,12 +207,12 @@ function TestOverflowDetection:test_overflow_with_margins()
parent = container, parent = container,
width = 180, width = 180,
height = 180, height = 180,
margin = { top = 5, right = 20, bottom = 5, left = 5 } -- Total width: 5+180+20=205, overflows 200px container margin = { top = 5, right = 20, bottom = 5, left = 5 }, -- Total width: 5+180+20=205, overflows 200px container
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertTrue(maxScrollX > 0, "Should include child margins in overflow calculation") luaunit.assertTrue(maxScrollX > 0, "Should include child margins in overflow calculation")
end end
@@ -225,9 +225,9 @@ function TestOverflowDetection:test_visible_overflow_skips_detection()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
overflow = "visible" -- Should not clip or calculate overflow overflow = "visible", -- Should not clip or calculate overflow
}) })
-- Add oversized child -- Add oversized child
FlexLove.new({ FlexLove.new({
id = "large_child", id = "large_child",
@@ -235,12 +235,12 @@ function TestOverflowDetection:test_visible_overflow_skips_detection()
x = 0, x = 0,
y = 0, y = 0,
width = 300, width = 300,
height = 300 height = 300,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- With overflow="visible", maxScroll should be 0 (no scrolling) -- With overflow="visible", maxScroll should be 0 (no scrolling)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "visible overflow should not enable scrolling") luaunit.assertEquals(maxScrollX, 0, "visible overflow should not enable scrolling")
@@ -255,13 +255,13 @@ function TestOverflowDetection:test_empty_container_no_overflow()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
-- No children -- No children
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(maxScrollX, 0, "Empty container should have no overflow") luaunit.assertEquals(maxScrollX, 0, "Empty container should have no overflow")
luaunit.assertEquals(maxScrollY, 0, "Empty container should have no overflow") luaunit.assertEquals(maxScrollY, 0, "Empty container should have no overflow")
@@ -275,9 +275,9 @@ function TestOverflowDetection:test_absolute_children_ignored_in_overflow()
y = 0, y = 0,
width = 200, width = 200,
height = 200, height = 200,
overflow = "scroll" overflow = "scroll",
}) })
-- Regular child that fits -- Regular child that fits
FlexLove.new({ FlexLove.new({
id = "normal_child", id = "normal_child",
@@ -285,9 +285,9 @@ function TestOverflowDetection:test_absolute_children_ignored_in_overflow()
x = 0, x = 0,
y = 0, y = 0,
width = 150, width = 150,
height = 150 height = 150,
}) })
-- Absolutely positioned child that extends beyond (should NOT cause overflow) -- Absolutely positioned child that extends beyond (should NOT cause overflow)
FlexLove.new({ FlexLove.new({
id = "absolute_child", id = "absolute_child",
@@ -296,12 +296,12 @@ function TestOverflowDetection:test_absolute_children_ignored_in_overflow()
top = 0, top = 0,
left = 0, left = 0,
width = 400, width = 400,
height = 400 height = 400,
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
-- Should not have overflow because absolute children are ignored -- Should not have overflow because absolute children are ignored
luaunit.assertEquals(maxScrollX, 0, "Absolute children should not cause overflow") luaunit.assertEquals(maxScrollX, 0, "Absolute children should not cause overflow")
@@ -316,26 +316,26 @@ function TestOverflowDetection:test_scroll_clamped_to_max()
y = 0, y = 0,
width = 100, width = 100,
height = 100, height = 100,
overflow = "scroll" overflow = "scroll",
}) })
FlexLove.new({ FlexLove.new({
id = "child", id = "child",
parent = container, parent = container,
x = 0, x = 0,
y = 0, y = 0,
width = 100, width = 100,
height = 300 -- Creates 200px of vertical overflow height = 300, -- Creates 200px of vertical overflow
}) })
FlexLove.endFrame() FlexLove.endFrame()
FlexLove.beginFrame(1920, 1080) FlexLove.beginFrame(1920, 1080)
-- Try to scroll beyond max -- Try to scroll beyond max
container:setScrollPosition(0, 999999) container:setScrollPosition(0, 999999)
local scrollX, scrollY = container:getScrollPosition() local scrollX, scrollY = container:getScrollPosition()
local maxScrollX, maxScrollY = container:getMaxScroll() local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollY, maxScrollY, "Scroll should be clamped to maximum") luaunit.assertEquals(scrollY, maxScrollY, "Scroll should be clamped to maximum")
luaunit.assertTrue(scrollY < 999999, "Should not scroll beyond content") luaunit.assertTrue(scrollY < 999999, "Should not scroll beyond content")
end end

View File

@@ -0,0 +1,396 @@
-- Test suite for TextEditor module
package.path = package.path .. ";./?.lua;./modules/?.lua"
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local TextEditor = require("modules.TextEditor")
local Color = require("modules.Color")
local utils = require("modules.utils")
TestTextEditor = {}
-- Mock dependencies
local MockContext = {
_immediateMode = false,
_focusedElement = nil,
}
local MockStateManager = {
getState = function(id)
return nil
end,
saveState = function(id, state) end,
}
-- Helper to create TextEditor with dependencies
local function createTextEditor(config)
config = config or {}
return TextEditor.new(config, {
Context = MockContext,
StateManager = MockStateManager,
Color = Color,
utils = utils,
})
end
-- Helper to create mock element
local function createMockElement()
return {
_stateId = "test-element-1",
width = 200,
height = 30,
}
end
-- Test: new() creates instance with defaults
function TestTextEditor:test_new_creates_with_defaults()
local editor = createTextEditor()
luaunit.assertNotNil(editor)
luaunit.assertFalse(editor.editable)
luaunit.assertFalse(editor.multiline)
luaunit.assertFalse(editor.passwordMode)
luaunit.assertEquals(editor.inputType, "text")
luaunit.assertEquals(editor._textBuffer, "")
luaunit.assertEquals(editor._cursorPosition, 0)
luaunit.assertFalse(editor._focused)
end
-- Test: new() accepts configuration
function TestTextEditor:test_new_accepts_config()
local editor = createTextEditor({
editable = true,
multiline = true,
passwordMode = true,
text = "Hello",
placeholder = "Enter text",
maxLength = 100,
inputType = "email",
})
luaunit.assertTrue(editor.editable)
luaunit.assertTrue(editor.multiline)
luaunit.assertTrue(editor.passwordMode)
luaunit.assertEquals(editor._textBuffer, "Hello")
luaunit.assertEquals(editor.placeholder, "Enter text")
luaunit.assertEquals(editor.maxLength, 100)
luaunit.assertEquals(editor.inputType, "email")
end
-- Test: new() sanitizes initial text
function TestTextEditor:test_new_sanitizes_initial_text()
local editor = createTextEditor({
text = "Hello\n\nWorld",
multiline = false,
allowNewlines = false,
})
-- Newlines should be removed for single-line
luaunit.assertNotEquals(editor._textBuffer, "Hello\n\nWorld")
end
-- Test: initialize() sets element reference
function TestTextEditor:test_initialize_sets_element()
local editor = createTextEditor()
local element = createMockElement()
editor:initialize(element)
luaunit.assertEquals(editor._element, element)
end
-- Test: getText() returns current text
function TestTextEditor:test_getText_returns_text()
local editor = createTextEditor({ text = "Hello World" })
luaunit.assertEquals(editor:getText(), "Hello World")
end
-- Test: getText() returns empty string for nil buffer
function TestTextEditor:test_getText_returns_empty_for_nil()
local editor = createTextEditor()
editor._textBuffer = nil
luaunit.assertEquals(editor:getText(), "")
end
-- Test: setText() updates text buffer
function TestTextEditor:test_setText_updates_buffer()
local editor = createTextEditor()
editor:setText("New text")
luaunit.assertEquals(editor:getText(), "New text")
end
-- Test: setText() sanitizes text by default
function TestTextEditor:test_setText_sanitizes()
local editor = createTextEditor({
multiline = false,
allowNewlines = false,
})
editor:setText("Line1\nLine2")
-- Should remove newlines for single-line
local text = editor:getText()
luaunit.assertFalse(text:find("\n") ~= nil)
end
-- Test: setText() skips sanitization when requested
function TestTextEditor:test_setText_skips_sanitization()
local editor = createTextEditor({
multiline = false,
allowNewlines = false,
})
editor:setText("Line1\nLine2", true) -- skipSanitization = true
luaunit.assertEquals(editor:getText(), "Line1\nLine2")
end
-- Test: insertText() adds text at position
function TestTextEditor:test_insertText_at_position()
local editor = createTextEditor({ text = "Hello" })
editor:insertText(" World", 5)
luaunit.assertEquals(editor:getText(), "Hello World")
end
-- Test: insertText() adds text at start
function TestTextEditor:test_insertText_at_start()
local editor = createTextEditor({ text = "World" })
editor:insertText("Hello ", 0)
luaunit.assertEquals(editor:getText(), "Hello World")
end
-- Test: deleteText() removes text range
function TestTextEditor:test_deleteText_removes_range()
local editor = createTextEditor({ text = "Hello World" })
editor:deleteText(5, 11) -- Remove " World"
luaunit.assertEquals(editor:getText(), "Hello")
end
-- Test: deleteText() handles reversed positions
function TestTextEditor:test_deleteText_handles_reversed()
local editor = createTextEditor({ text = "Hello World" })
editor:deleteText(11, 5) -- Reversed: should swap
luaunit.assertEquals(editor:getText(), "Hello")
end
-- Test: replaceText() replaces range with new text
function TestTextEditor:test_replaceText_replaces_range()
local editor = createTextEditor({ text = "Hello World" })
editor:replaceText(6, 11, "Lua")
luaunit.assertEquals(editor:getText(), "Hello Lua")
end
-- Test: setCursorPosition() sets cursor
function TestTextEditor:test_setCursorPosition()
local editor = createTextEditor({ text = "Hello" })
editor:setCursorPosition(3)
luaunit.assertEquals(editor:getCursorPosition(), 3)
end
-- Test: setCursorPosition() clamps to valid range
function TestTextEditor:test_setCursorPosition_clamps()
local editor = createTextEditor({ text = "Hello" })
editor:setCursorPosition(100) -- Beyond text length
luaunit.assertEquals(editor:getCursorPosition(), 5)
end
-- Test: moveCursorBy() moves cursor relative
function TestTextEditor:test_moveCursorBy()
local editor = createTextEditor({ text = "Hello" })
editor:setCursorPosition(2)
editor:moveCursorBy(2)
luaunit.assertEquals(editor:getCursorPosition(), 4)
end
-- Test: moveCursorToStart() moves to beginning
function TestTextEditor:test_moveCursorToStart()
local editor = createTextEditor({ text = "Hello" })
editor:setCursorPosition(3)
editor:moveCursorToStart()
luaunit.assertEquals(editor:getCursorPosition(), 0)
end
-- Test: moveCursorToEnd() moves to end
function TestTextEditor:test_moveCursorToEnd()
local editor = createTextEditor({ text = "Hello" })
editor:moveCursorToEnd()
luaunit.assertEquals(editor:getCursorPosition(), 5)
end
-- Test: setSelection() sets selection range
function TestTextEditor:test_setSelection()
local editor = createTextEditor({ text = "Hello World" })
editor:setSelection(0, 5)
local start, endPos = editor:getSelection()
luaunit.assertEquals(start, 0)
luaunit.assertEquals(endPos, 5)
end
-- Test: hasSelection() returns true when selected
function TestTextEditor:test_hasSelection_true()
local editor = createTextEditor({ text = "Hello" })
editor:setSelection(0, 5)
luaunit.assertTrue(editor:hasSelection())
end
-- Test: hasSelection() returns false when no selection
function TestTextEditor:test_hasSelection_false()
local editor = createTextEditor({ text = "Hello" })
luaunit.assertFalse(editor:hasSelection())
end
-- Test: clearSelection() removes selection
function TestTextEditor:test_clearSelection()
local editor = createTextEditor({ text = "Hello" })
editor:setSelection(0, 5)
editor:clearSelection()
luaunit.assertFalse(editor:hasSelection())
end
-- Test: getSelectedText() returns selected text
function TestTextEditor:test_getSelectedText()
local editor = createTextEditor({ text = "Hello World" })
editor:setSelection(0, 5)
luaunit.assertEquals(editor:getSelectedText(), "Hello")
end
-- Test: deleteSelection() removes selected text
function TestTextEditor:test_deleteSelection()
local editor = createTextEditor({ text = "Hello World" })
editor:setSelection(0, 6)
editor:deleteSelection()
luaunit.assertEquals(editor:getText(), "World")
luaunit.assertFalse(editor:hasSelection())
end
-- Test: selectAll() selects entire text
function TestTextEditor:test_selectAll()
local editor = createTextEditor({ text = "Hello World" })
editor:selectAll()
local start, endPos = editor:getSelection()
luaunit.assertEquals(start, 0)
luaunit.assertEquals(endPos, 11)
end
-- Test: sanitization with maxLength
function TestTextEditor:test_sanitize_max_length()
local editor = createTextEditor({
maxLength = 5,
})
editor:setText("HelloWorld")
luaunit.assertEquals(editor:getText(), "Hello")
end
-- Test: sanitization disabled
function TestTextEditor:test_sanitization_disabled()
local editor = createTextEditor({
sanitize = false,
multiline = false,
allowNewlines = false,
})
editor:setText("Line1\nLine2")
-- Should NOT sanitize newlines when disabled
luaunit.assertEquals(editor:getText(), "Line1\nLine2")
end
-- Test: customSanitizer callback
function TestTextEditor:test_custom_sanitizer()
local editor = createTextEditor({
customSanitizer = function(text)
return text:upper()
end,
})
editor:setText("hello")
luaunit.assertEquals(editor:getText(), "HELLO")
end
-- Test: allowNewlines follows multiline setting
function TestTextEditor:test_allowNewlines_follows_multiline()
local editor = createTextEditor({
multiline = true,
})
luaunit.assertTrue(editor.allowNewlines)
editor = createTextEditor({
multiline = false,
})
luaunit.assertFalse(editor.allowNewlines)
end
-- Test: allowNewlines can be overridden
function TestTextEditor:test_allowNewlines_override()
local editor = createTextEditor({
multiline = true,
allowNewlines = false,
})
luaunit.assertFalse(editor.allowNewlines)
end
-- Test: allowTabs defaults to true
function TestTextEditor:test_allowTabs_default()
local editor = createTextEditor()
luaunit.assertTrue(editor.allowTabs)
end
-- Test: cursorBlinkRate default
function TestTextEditor:test_cursorBlinkRate_default()
local editor = createTextEditor()
luaunit.assertEquals(editor.cursorBlinkRate, 0.5)
end
-- Test: selectOnFocus default
function TestTextEditor:test_selectOnFocus_default()
local editor = createTextEditor()
luaunit.assertFalse(editor.selectOnFocus)
end
if not _G.RUNNING_ALL_TESTS then
os.exit(luaunit.LuaUnit.run())
end

View File

@@ -17,17 +17,25 @@ local luaunit = require("testing.luaunit")
-- Run all tests in the __tests__ directory -- Run all tests in the __tests__ directory
local testFiles = { local testFiles = {
"testing/__tests__/utils_test.lua", "testing/__tests__/animation_test.lua",
"testing/__tests__/units_test.lua",
"testing/__tests__/color_validation_test.lua", "testing/__tests__/color_validation_test.lua",
"testing/__tests__/element_test.lua",
"testing/__tests__/error_handler_test.lua",
"testing/__tests__/event_handler_test.lua",
"testing/__tests__/grid_test.lua",
"testing/__tests__/image_cache_test.lua",
"testing/__tests__/image_renderer_test.lua",
"testing/__tests__/image_scaler_test.lua",
"testing/__tests__/input_event_test.lua",
"testing/__tests__/layout_edge_cases_test.lua",
"testing/__tests__/layout_engine_test.lua",
"testing/__tests__/overflow_test.lua",
"testing/__tests__/path_validation_test.lua", "testing/__tests__/path_validation_test.lua",
"testing/__tests__/sanitization_test.lua", "testing/__tests__/sanitization_test.lua",
"testing/__tests__/text_editor_test.lua",
"testing/__tests__/theme_test.lua", "testing/__tests__/theme_test.lua",
"testing/__tests__/layout_engine_test.lua", "testing/__tests__/units_test.lua",
"testing/__tests__/element_test.lua", "testing/__tests__/utils_test.lua",
"testing/__tests__/overflow_test.lua",
"testing/__tests__/grid_test.lua",
"testing/__tests__/layout_edge_cases_test.lua",
} }
local success = true local success = true
@@ -58,19 +66,19 @@ if status then
print("\n========================================") print("\n========================================")
print("Generating coverage report...") print("Generating coverage report...")
print("========================================") print("========================================")
-- Save coverage stats -- Save coverage stats
luacov.save_stats() luacov.save_stats()
-- Run luacov command to generate report (silent) -- Run luacov command to generate report (silent)
os.execute("luacov 2>/dev/null") os.execute("luacov 2>/dev/null")
-- Read and display the summary section from the report -- Read and display the summary section from the report
local report_file = io.open("luacov.report.out", "r") local report_file = io.open("luacov.report.out", "r")
if report_file then if report_file then
local content = report_file:read("*all") local content = report_file:read("*all")
report_file:close() report_file:close()
-- Extract just the Summary section -- Extract just the Summary section
local summary = content:match("Summary\n=+\n(.-)$") local summary = content:match("Summary\n=+\n(.-)$")
if summary then if summary then
@@ -79,7 +87,7 @@ if status then
print(summary) print(summary)
end end
end end
print("Full coverage report: luacov.report.out") print("Full coverage report: luacov.report.out")
print("========================================") print("========================================")
end end