trying to get coverage analysis to reasonable time
This commit is contained in:
12
.luacov
12
.luacov
@@ -13,6 +13,13 @@ return {
|
||||
"tasks",
|
||||
"themes",
|
||||
"luarocks",
|
||||
"loveStub", -- Exclude LÖVE stub from coverage
|
||||
},
|
||||
|
||||
-- Include patterns - focus coverage on core modules
|
||||
include = {
|
||||
"modules/",
|
||||
"FlexLove%.lua",
|
||||
},
|
||||
|
||||
-- Run reporter by default
|
||||
@@ -21,6 +28,7 @@ return {
|
||||
-- Delete stats file after reporting
|
||||
deletestats = false,
|
||||
|
||||
-- Tick options
|
||||
tick = true
|
||||
-- Tick options - enable for better line-by-line tracking
|
||||
-- Note: With cluacov this is faster than pure Lua luacov
|
||||
tick = true,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
-- Lua 5.2+ compatibility for unpack
|
||||
local unpack = table.unpack or unpack
|
||||
|
||||
local Cache = {
|
||||
canvases = {},
|
||||
quads = {},
|
||||
@@ -368,7 +371,9 @@ end
|
||||
--- Initialize Blur module with dependencies
|
||||
---@param deps table Dependencies: { ErrorHandler = ErrorHandler? }
|
||||
function Blur.init(deps)
|
||||
Blur._ErrorHandler = deps.ErrorHandler
|
||||
if type(deps) == "table" then
|
||||
Blur._ErrorHandler = deps.ErrorHandler
|
||||
end
|
||||
end
|
||||
|
||||
Blur.Cache = Cache
|
||||
|
||||
@@ -225,6 +225,35 @@ function Element.new(props)
|
||||
Color = Element._Color,
|
||||
}
|
||||
|
||||
-- Normalize flexDirection: convert "row"→"horizontal", "column"→"vertical"
|
||||
if props.flexDirection == "row" then
|
||||
props.flexDirection = "horizontal"
|
||||
elseif props.flexDirection == "column" then
|
||||
props.flexDirection = "vertical"
|
||||
end
|
||||
|
||||
-- Normalize padding: convert single value to table with all sides
|
||||
if props.padding ~= nil and type(props.padding) ~= "table" then
|
||||
local singleValue = props.padding
|
||||
props.padding = {
|
||||
top = singleValue,
|
||||
right = singleValue,
|
||||
bottom = singleValue,
|
||||
left = singleValue,
|
||||
}
|
||||
end
|
||||
|
||||
-- Normalize margin: convert single value to table with all sides
|
||||
if props.margin ~= nil and type(props.margin) ~= "table" then
|
||||
local singleValue = props.margin
|
||||
props.margin = {
|
||||
top = singleValue,
|
||||
right = singleValue,
|
||||
bottom = singleValue,
|
||||
left = singleValue,
|
||||
}
|
||||
end
|
||||
|
||||
self.children = {}
|
||||
self.onEvent = props.onEvent
|
||||
|
||||
|
||||
@@ -737,7 +737,7 @@ end
|
||||
---@param suggestion string|nil Suggestion
|
||||
function ErrorHandler:_writeLog(level, levelNum, module, code, message, details, suggestion)
|
||||
-- Check if we should log this level
|
||||
if levelNum > self.logLevel then
|
||||
if not levelNum or not self.logLevel or levelNum > self.logLevel then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ local AnimationProps = {}
|
||||
---@field backgroundColor Color? -- Background color (default: transparent)
|
||||
---@field cornerRadius number|{topLeft:number?, topRight:number?, bottomLeft:number?, bottomRight:number?}? -- Corner radius: number (all corners) or table for individual corners (default: 0)
|
||||
---@field gap number|string? -- Space between children elements (default: 0)
|
||||
---@field padding {top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Padding around children (default: {top=0, right=0, bottom=0, left=0})
|
||||
---@field margin {top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Margin around element (default: {top=0, right=0, bottom=0, left=0})
|
||||
---@field padding number|string|{top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Padding around children: single value for all sides or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
|
||||
---@field margin number|string|{top:number|string?, right:number|string?, bottom:number|string?, left:number|string?, horizontal:number|string?, vertical:number|string?}? -- Margin around element: single value for all sides or table for individual sides (default: {top=0, right=0, bottom=0, left=0})
|
||||
---@field text string? -- Text content to display (default: nil)
|
||||
---@field textAlign TextAlign? -- Alignment of the text content (default: START)
|
||||
---@field textColor Color? -- Color of the text content (default: black or theme text color)
|
||||
@@ -61,7 +61,7 @@ local AnimationProps = {}
|
||||
---@field fontFamily string? -- Font family name from theme or path to font file (default: theme default or system default, inherits from parent)
|
||||
---@field autoScaleText boolean? -- Whether text should auto-scale with window size (default: true)
|
||||
---@field positioning Positioning? -- Layout positioning mode: "absolute"|"relative"|"flex"|"grid" (default: RELATIVE)
|
||||
---@field flexDirection FlexDirection? -- Direction of flex layout: "horizontal"|"vertical" (default: HORIZONTAL)
|
||||
---@field flexDirection FlexDirection? -- Direction of flex layout: "horizontal"|"vertical"|"row"|"column" (row→horizontal, column→vertical, default: HORIZONTAL)
|
||||
---@field justifyContent JustifyContent? -- Alignment of items along main axis (default: FLEX_START)
|
||||
---@field alignItems AlignItems? -- Alignment of items along cross axis (default: STRETCH)
|
||||
---@field alignContent AlignContent? -- Alignment of lines in multi-line flex containers (default: STRETCH)
|
||||
|
||||
@@ -194,11 +194,29 @@ function profile.draw()
|
||||
love.graphics.print("Press - to remove 10 animated elements", 10, love.graphics.getHeight() - 45)
|
||||
end
|
||||
|
||||
function profile.keypressed(key)
|
||||
function profile.keypressed(key, profiler)
|
||||
if key == "=" or key == "+" then
|
||||
-- Create snapshot before changing animation count
|
||||
if profiler then
|
||||
local label = string.format("%d animations", profile.animationCount)
|
||||
profiler:createSnapshot(label, {
|
||||
animationCount = profile.animationCount,
|
||||
activeAnimations = #profile.animations
|
||||
})
|
||||
end
|
||||
|
||||
profile.animationCount = math.min(profile.maxAnimations, profile.animationCount + 10)
|
||||
profile.buildLayout()
|
||||
elseif key == "-" or key == "_" then
|
||||
-- Create snapshot before changing animation count
|
||||
if profiler then
|
||||
local label = string.format("%d animations", profile.animationCount)
|
||||
profiler:createSnapshot(label, {
|
||||
animationCount = profile.animationCount,
|
||||
activeAnimations = #profile.animations
|
||||
})
|
||||
end
|
||||
|
||||
profile.animationCount = math.max(profile.minAnimations, profile.animationCount - 10)
|
||||
profile.buildLayout()
|
||||
end
|
||||
|
||||
@@ -127,11 +127,29 @@ function profile.draw()
|
||||
love.graphics.print("Press - to remove 50 elements", 10, love.graphics.getHeight() - 45)
|
||||
end
|
||||
|
||||
function profile.keypressed(key)
|
||||
function profile.keypressed(key, profiler)
|
||||
if key == "=" or key == "+" then
|
||||
-- Create snapshot before changing element count
|
||||
if profiler then
|
||||
local label = string.format("%d elements", profile.elementCount)
|
||||
profiler:createSnapshot(label, {
|
||||
elementCount = profile.elementCount,
|
||||
nestingDepth = profile.nestingDepth
|
||||
})
|
||||
end
|
||||
|
||||
profile.elementCount = math.min(profile.maxElements, profile.elementCount + 50)
|
||||
profile.buildLayout()
|
||||
elseif key == "-" or key == "_" then
|
||||
-- Create snapshot before changing element count
|
||||
if profiler then
|
||||
local label = string.format("%d elements", profile.elementCount)
|
||||
profiler:createSnapshot(label, {
|
||||
elementCount = profile.elementCount,
|
||||
nestingDepth = profile.nestingDepth
|
||||
})
|
||||
end
|
||||
|
||||
profile.elementCount = math.max(10, profile.elementCount - 50)
|
||||
profile.buildLayout()
|
||||
end
|
||||
|
||||
@@ -152,11 +152,41 @@ function profile.draw()
|
||||
love.graphics.print("Press R/T/L to toggle features", 10, love.graphics.getHeight() - 50)
|
||||
end
|
||||
|
||||
function profile.keypressed(key)
|
||||
function profile.keypressed(key, profiler)
|
||||
if key == "=" or key == "+" then
|
||||
-- Create snapshot before changing element count
|
||||
if profiler then
|
||||
local label = string.format("%d elements (R:%s T:%s L:%s)",
|
||||
profile.elementCount,
|
||||
profile.showRounded and "on" or "off",
|
||||
profile.showText and "on" or "off",
|
||||
profile.showLayering and "on" or "off")
|
||||
profiler:createSnapshot(label, {
|
||||
elementCount = profile.elementCount,
|
||||
showRounded = profile.showRounded,
|
||||
showText = profile.showText,
|
||||
showLayering = profile.showLayering
|
||||
})
|
||||
end
|
||||
|
||||
profile.elementCount = math.min(profile.maxElements, profile.elementCount + 50)
|
||||
profile.buildLayout()
|
||||
elseif key == "-" or key == "_" then
|
||||
-- Create snapshot before changing element count
|
||||
if profiler then
|
||||
local label = string.format("%d elements (R:%s T:%s L:%s)",
|
||||
profile.elementCount,
|
||||
profile.showRounded and "on" or "off",
|
||||
profile.showText and "on" or "off",
|
||||
profile.showLayering and "on" or "off")
|
||||
profiler:createSnapshot(label, {
|
||||
elementCount = profile.elementCount,
|
||||
showRounded = profile.showRounded,
|
||||
showText = profile.showText,
|
||||
showLayering = profile.showLayering
|
||||
})
|
||||
end
|
||||
|
||||
profile.elementCount = math.max(profile.minElements, profile.elementCount - 50)
|
||||
profile.buildLayout()
|
||||
elseif key == "r" then
|
||||
|
||||
@@ -335,7 +335,7 @@ function love.keypressed(key)
|
||||
|
||||
if state.currentProfile and type(state.currentProfile.keypressed) == "function" then
|
||||
pcall(function()
|
||||
state.currentProfile.keypressed(key)
|
||||
state.currentProfile.keypressed(key, state.profiler)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
---@field _currentFrameStart number?
|
||||
---@field _maxHistorySize number
|
||||
---@field _lastGcCount number
|
||||
---@field _snapshots table
|
||||
---@field _currentSnapshot table?
|
||||
local PerformanceProfiler = {}
|
||||
PerformanceProfiler.__index = PerformanceProfiler
|
||||
|
||||
@@ -29,6 +31,8 @@ function PerformanceProfiler.new(config)
|
||||
self._markers = {}
|
||||
self._currentFrameStart = nil
|
||||
self._lastGcCount = collectgarbage("count")
|
||||
self._snapshots = {}
|
||||
self._currentSnapshot = nil
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -379,6 +383,42 @@ function PerformanceProfiler:reset()
|
||||
self._markers = {}
|
||||
self._currentFrameStart = nil
|
||||
self._lastGcCount = collectgarbage("count")
|
||||
-- Don't reset snapshots - they persist across resets
|
||||
end
|
||||
|
||||
--- Create a snapshot of current metrics with a label
|
||||
---@param label string Label for this snapshot (e.g., "100 elements", "500 elements")
|
||||
---@param metadata table? Additional metadata to store with snapshot
|
||||
---@return nil
|
||||
function PerformanceProfiler:createSnapshot(label, metadata)
|
||||
local report = self:getReport()
|
||||
|
||||
table.insert(self._snapshots, {
|
||||
label = label,
|
||||
timestamp = os.date("%Y-%m-%d %H:%M:%S"),
|
||||
metadata = metadata or {},
|
||||
report = report,
|
||||
})
|
||||
|
||||
-- Reset current metrics for next snapshot period
|
||||
self._frameCount = 0
|
||||
self._startTime = love.timer.getTime()
|
||||
self._frameTimes = {}
|
||||
self._fpsHistory = {}
|
||||
self._memoryHistory = {}
|
||||
self._currentFrameStart = nil
|
||||
end
|
||||
|
||||
--- Get all snapshots
|
||||
---@return table
|
||||
function PerformanceProfiler:getSnapshots()
|
||||
return self._snapshots
|
||||
end
|
||||
|
||||
--- Clear all snapshots
|
||||
---@return nil
|
||||
function PerformanceProfiler:clearSnapshots()
|
||||
self._snapshots = {}
|
||||
end
|
||||
|
||||
---@return string
|
||||
@@ -571,6 +611,45 @@ function PerformanceProfiler:_saveWithIO(filepath, profileName)
|
||||
end
|
||||
end
|
||||
|
||||
-- Snapshots (if any)
|
||||
if #self._snapshots > 0 then
|
||||
table.insert(lines, "## Snapshots")
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "> Performance metrics captured at different configuration points")
|
||||
table.insert(lines, "")
|
||||
|
||||
for i, snapshot in ipairs(self._snapshots) do
|
||||
table.insert(lines, string.format("### %d. %s", i, snapshot.label))
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, string.format("**Captured:** %s", snapshot.timestamp))
|
||||
|
||||
-- Show metadata if present
|
||||
if next(snapshot.metadata) then
|
||||
table.insert(lines, "")
|
||||
table.insert(lines, "**Configuration:**")
|
||||
for key, value in pairs(snapshot.metadata) do
|
||||
table.insert(lines, string.format("- %s: `%s`", key, tostring(value)))
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(lines, "")
|
||||
|
||||
local r = snapshot.report
|
||||
|
||||
-- Compact FPS/Frame Time table
|
||||
table.insert(lines, "| FPS | Frame Time (ms) | Memory (MB) | Frames |")
|
||||
table.insert(lines, "|-----|-----------------|-------------|--------|")
|
||||
table.insert(lines, string.format("| Avg: %.1f | Avg: %.2f | Avg: %.2f | %d |",
|
||||
r.fps.average, r.frameTime.average, r.memory.average, r.frameCount))
|
||||
table.insert(lines, string.format("| 1%% Worst: **%.1f** | P99: %.2f | Peak: %.2f | Duration: %.1fs |",
|
||||
r.fps.worst_1_percent, r.frameTime.p99, r.memory.peak, r.totalTime))
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
table.insert(lines, "---")
|
||||
table.insert(lines, "")
|
||||
end
|
||||
|
||||
table.insert(lines, "---")
|
||||
table.insert(lines, "")
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
local luaunit = require("testing.luaunit")
|
||||
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||
|
||||
require("testing.loveStub")
|
||||
local luaunit = require("testing.luaunit")
|
||||
|
||||
local Blur = require("modules.Blur")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
|
||||
-- Initialize ErrorHandler
|
||||
ErrorHandler.init({})
|
||||
Blur.init({ ErrorHandler = ErrorHandler })
|
||||
|
||||
TestBlur = {}
|
||||
|
||||
@@ -10,9 +17,11 @@ function TestBlur:setUp()
|
||||
Blur.clearCache()
|
||||
end
|
||||
|
||||
-- Unhappy path tests for Blur.new({quality = )
|
||||
-- ============================================================================
|
||||
-- Constructor Tests: Blur.new()
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testNewWithNilQuality(})
|
||||
function TestBlur:testNewWithNilQuality()
|
||||
-- Should default to quality 5
|
||||
local blur = Blur.new({quality = nil})
|
||||
luaunit.assertNotNil(blur)
|
||||
@@ -62,19 +71,35 @@ function TestBlur:testNewEnsuresOddTaps()
|
||||
end
|
||||
end
|
||||
|
||||
-- Unhappy path tests for Blur.applyToRegion()
|
||||
|
||||
function TestBlur:testApplyToRegionWithNilBlurInstance()
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
luaunit.assertError(function()
|
||||
Blur.applyToRegion(nil, 50, 0, 0, 100, 100, drawFunc)
|
||||
end)
|
||||
function TestBlur:testNewWithEmptyProps()
|
||||
-- Should work with no props table
|
||||
local blur = Blur.new()
|
||||
luaunit.assertNotNil(blur)
|
||||
luaunit.assertEquals(blur.quality, 5)
|
||||
end
|
||||
|
||||
function TestBlur:testNewWithNilProps()
|
||||
-- Should work with explicit nil
|
||||
local blur = Blur.new(nil)
|
||||
luaunit.assertNotNil(blur)
|
||||
luaunit.assertEquals(blur.quality, 5)
|
||||
end
|
||||
|
||||
function TestBlur:testNewCreatesUniqueShaders()
|
||||
-- Each instance should have its own shader
|
||||
local blur1 = Blur.new({quality = 5})
|
||||
local blur2 = Blur.new({quality = 5})
|
||||
|
||||
luaunit.assertNotNil(blur1.shader)
|
||||
luaunit.assertNotNil(blur2.shader)
|
||||
-- Shaders should be different objects even if same quality
|
||||
luaunit.assertNotEquals(blur1.shader, blur2.shader)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- applyToRegion() Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testApplyToRegionWithZeroIntensity()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
@@ -83,7 +108,7 @@ function TestBlur:testApplyToRegionWithZeroIntensity()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, 0, 0, 0, 100, 100, drawFunc)
|
||||
blur:applyToRegion(0, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
@@ -95,7 +120,7 @@ function TestBlur:testApplyToRegionWithNegativeIntensity()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, -10, 0, 0, 100, 100, drawFunc)
|
||||
blur:applyToRegion(-10, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
@@ -107,7 +132,7 @@ function TestBlur:testApplyToRegionWithZeroWidth()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, 50, 0, 0, 0, 100, drawFunc)
|
||||
blur:applyToRegion(50, 0, 0, 0, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
@@ -119,7 +144,7 @@ function TestBlur:testApplyToRegionWithZeroHeight()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, 50, 0, 0, 100, 0, drawFunc)
|
||||
blur:applyToRegion(50, 0, 0, 100, 0, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
@@ -131,7 +156,7 @@ function TestBlur:testApplyToRegionWithNegativeWidth()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, 50, 0, 0, -100, 100, drawFunc)
|
||||
blur:applyToRegion(50, 0, 0, -100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
@@ -143,54 +168,111 @@ function TestBlur:testApplyToRegionWithNegativeHeight()
|
||||
end
|
||||
|
||||
-- Should just call drawFunc and return early
|
||||
Blur.applyToRegion(blur, 50, 0, 0, 100, -100, drawFunc)
|
||||
blur:applyToRegion(50, 0, 0, 100, -100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithIntensityOver100()
|
||||
local blur = Blur.new({quality = 5})
|
||||
|
||||
-- We can't fully test rendering without complete LÖVE graphics
|
||||
-- But we can verify the blur instance was created
|
||||
luaunit.assertNotNil(blur)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithSmallDimensions()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
-- For small dimensions, we test that it doesn't error
|
||||
-- We can't fully test the rendering without full LÖVE graphics
|
||||
luaunit.assertNotNil(blur)
|
||||
luaunit.assertTrue(true)
|
||||
-- Should clamp intensity to 100
|
||||
blur:applyToRegion(150, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithNonFunctionDrawFunc()
|
||||
local blur = Blur.new({quality = 5})
|
||||
|
||||
-- Should not error but warn through ErrorHandler
|
||||
blur:applyToRegion(50, 0, 0, 100, 100, "not a function")
|
||||
luaunit.assertTrue(true) -- Should reach here without crash
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithNilDrawFunc()
|
||||
local blur = Blur.new({quality = 5})
|
||||
|
||||
luaunit.assertError(function()
|
||||
Blur.applyToRegion(blur, 50, 0, 0, 100, 100, nil)
|
||||
end)
|
||||
-- Should not error but warn through ErrorHandler
|
||||
blur:applyToRegion(50, 0, 0, 100, 100, nil)
|
||||
luaunit.assertTrue(true) -- Should reach here without crash
|
||||
end
|
||||
|
||||
-- Unhappy path tests for Blur.applyBackdrop()
|
||||
function TestBlur:testApplyToRegionWithNegativeCoordinates()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
function TestBlur:testApplyBackdropWithNilBlurInstance()
|
||||
local mockCanvas = {
|
||||
getDimensions = function()
|
||||
return 100, 100
|
||||
end,
|
||||
}
|
||||
|
||||
luaunit.assertError(function()
|
||||
Blur.applyBackdrop(nil, 50, 0, 0, 100, 100, mockCanvas)
|
||||
end)
|
||||
-- Negative coordinates should work (off-screen rendering)
|
||||
blur:applyToRegion(50, -100, -100, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithVerySmallDimensions()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
-- Very small dimensions (1x1)
|
||||
blur:applyToRegion(50, 0, 0, 1, 1, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionWithVeryLargeDimensions()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
-- Very large dimensions (might stress cache)
|
||||
blur:applyToRegion(50, 0, 0, 4096, 4096, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyToRegionIntensityBoundaries()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local called = false
|
||||
local drawFunc = function()
|
||||
called = true
|
||||
end
|
||||
|
||||
-- Test boundary values that affect passes calculation
|
||||
-- intensity 20 = 1 pass
|
||||
blur:applyToRegion(20, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
|
||||
called = false
|
||||
-- intensity 40 = 2 passes
|
||||
blur:applyToRegion(40, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
|
||||
called = false
|
||||
-- intensity 60 = 3 passes
|
||||
blur:applyToRegion(60, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
|
||||
called = false
|
||||
-- intensity 80 = 4 passes
|
||||
blur:applyToRegion(80, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
|
||||
called = false
|
||||
-- intensity 100 = 5 passes
|
||||
blur:applyToRegion(100, 0, 0, 100, 100, drawFunc)
|
||||
luaunit.assertTrue(called)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- applyBackdrop() Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testApplyBackdropWithZeroIntensity()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local mockCanvas = {
|
||||
@@ -200,7 +282,7 @@ function TestBlur:testApplyBackdropWithZeroIntensity()
|
||||
}
|
||||
|
||||
-- Should return early without error
|
||||
Blur.applyBackdrop(blur, 0, 0, 0, 100, 100, mockCanvas)
|
||||
blur:applyBackdrop(0, 0, 0, 100, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
@@ -213,7 +295,7 @@ function TestBlur:testApplyBackdropWithNegativeIntensity()
|
||||
}
|
||||
|
||||
-- Should return early without error
|
||||
Blur.applyBackdrop(blur, -10, 0, 0, 100, 100, mockCanvas)
|
||||
blur:applyBackdrop(-10, 0, 0, 100, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
@@ -226,7 +308,7 @@ function TestBlur:testApplyBackdropWithZeroWidth()
|
||||
}
|
||||
|
||||
-- Should return early without error
|
||||
Blur.applyBackdrop(blur, 50, 0, 0, 0, 100, mockCanvas)
|
||||
blur:applyBackdrop(50, 0, 0, 0, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
@@ -239,16 +321,16 @@ function TestBlur:testApplyBackdropWithZeroHeight()
|
||||
}
|
||||
|
||||
-- Should return early without error
|
||||
Blur.applyBackdrop(blur, 50, 0, 0, 100, 0, mockCanvas)
|
||||
blur:applyBackdrop(50, 0, 0, 100, 0, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyBackdropWithNilCanvas()
|
||||
local blur = Blur.new({quality = 5})
|
||||
|
||||
luaunit.assertError(function()
|
||||
Blur.applyBackdrop(blur, 50, 0, 0, 100, 100, nil)
|
||||
end)
|
||||
-- Should not error but warn through ErrorHandler
|
||||
blur:applyBackdrop(50, 0, 0, 100, 100, nil)
|
||||
luaunit.assertTrue(true) -- Should reach here without crash
|
||||
end
|
||||
|
||||
function TestBlur:testApplyBackdropWithIntensityOver100()
|
||||
@@ -259,13 +341,22 @@ function TestBlur:testApplyBackdropWithIntensityOver100()
|
||||
end,
|
||||
}
|
||||
|
||||
-- We can't fully test rendering without complete LÖVE graphics
|
||||
luaunit.assertNotNil(blur)
|
||||
luaunit.assertNotNil(mockCanvas)
|
||||
-- Should clamp intensity to 100
|
||||
blur:applyBackdrop(200, 0, 0, 100, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyBackdropWithSmallDimensions()
|
||||
function TestBlur:testApplyBackdropWithInvalidCanvas()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local invalidCanvas = "not a canvas"
|
||||
|
||||
-- Should error when trying to call getDimensions
|
||||
luaunit.assertErrorMsgContains("attempt", function()
|
||||
blur:applyBackdrop(50, 0, 0, 100, 100, invalidCanvas)
|
||||
end)
|
||||
end
|
||||
|
||||
function TestBlur:testApplyBackdropRegionBeyondCanvas()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local mockCanvas = {
|
||||
getDimensions = function()
|
||||
@@ -273,12 +364,43 @@ function TestBlur:testApplyBackdropWithSmallDimensions()
|
||||
end,
|
||||
}
|
||||
|
||||
-- We can't fully test rendering without complete LÖVE graphics
|
||||
luaunit.assertNotNil(blur)
|
||||
-- Region starts beyond canvas bounds
|
||||
blur:applyBackdrop(50, 150, 150, 100, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Tests for Blur.clearCache()
|
||||
function TestBlur:testApplyBackdropWithNegativeCoordinates()
|
||||
local blur = Blur.new({quality = 5})
|
||||
local mockCanvas = {
|
||||
getDimensions = function()
|
||||
return 100, 100
|
||||
end,
|
||||
}
|
||||
|
||||
-- Negative coordinates (region partially off-screen)
|
||||
blur:applyBackdrop(50, -50, -50, 100, 100, mockCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Getter Methods
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testGetQuality()
|
||||
local blur = Blur.new({quality = 7})
|
||||
luaunit.assertEquals(blur:getQuality(), 7)
|
||||
end
|
||||
|
||||
function TestBlur:testGetTaps()
|
||||
local blur = Blur.new({quality = 5})
|
||||
luaunit.assertIsNumber(blur:getTaps())
|
||||
luaunit.assertTrue(blur:getTaps() > 0)
|
||||
luaunit.assertTrue(blur:getTaps() % 2 == 1) -- Must be odd
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Cache Tests
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testClearCacheDoesNotError()
|
||||
-- Create some blur instances to populate cache
|
||||
@@ -297,22 +419,109 @@ function TestBlur:testClearCacheMultipleTimes()
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Edge case: intensity boundaries
|
||||
|
||||
function TestBlur:testIntensityBoundaries()
|
||||
local blur = Blur.new({quality = 5})
|
||||
|
||||
-- Test that various quality levels create valid blur instances
|
||||
for quality = 1, 10 do
|
||||
local b = Blur.new({quality = quality})
|
||||
luaunit.assertNotNil(b)
|
||||
luaunit.assertNotNil(b.shader)
|
||||
luaunit.assertTrue(b.taps % 2 == 1) -- Taps must be odd
|
||||
end
|
||||
function TestBlur:testCacheAccessMethods()
|
||||
-- Test that Cache is accessible
|
||||
luaunit.assertNotNil(Blur.Cache)
|
||||
luaunit.assertNotNil(Blur.Cache.getCanvas)
|
||||
luaunit.assertNotNil(Blur.Cache.releaseCanvas)
|
||||
luaunit.assertNotNil(Blur.Cache.getQuad)
|
||||
luaunit.assertNotNil(Blur.Cache.releaseQuad)
|
||||
luaunit.assertNotNil(Blur.Cache.clear)
|
||||
end
|
||||
|
||||
function TestBlur:testReleaseNonExistentCanvas()
|
||||
-- Should not error when releasing canvas that's not in cache
|
||||
local fakeCanvas = {}
|
||||
Blur.Cache.releaseCanvas(fakeCanvas)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestBlur:testReleaseNonExistentQuad()
|
||||
-- Should not error when releasing quad that's not in cache
|
||||
local fakeQuad = {}
|
||||
Blur.Cache.releaseQuad(fakeQuad)
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- ShaderBuilder Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testShaderBuilderAccessible()
|
||||
luaunit.assertNotNil(Blur.ShaderBuilder)
|
||||
luaunit.assertNotNil(Blur.ShaderBuilder.build)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithMinimalTaps()
|
||||
-- Should work with minimum taps (3)
|
||||
local shader = Blur.ShaderBuilder.build(3, 1.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithFractionalTaps()
|
||||
-- Should floor fractional taps to nearest odd number
|
||||
local shader = Blur.ShaderBuilder.build(4.7, 1.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithCenterOffset()
|
||||
-- Should work with center offset type
|
||||
local shader = Blur.ShaderBuilder.build(7, 1.0, "center", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithZeroSigma()
|
||||
-- Should clamp sigma to minimum 1
|
||||
local shader = Blur.ShaderBuilder.build(7, 1.0, "weighted", 0)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithNegativeSigma()
|
||||
-- Should auto-calculate sigma when negative
|
||||
local shader = Blur.ShaderBuilder.build(7, 1.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithLargeTaps()
|
||||
-- Should work with large tap count
|
||||
local shader = Blur.ShaderBuilder.build(21, 1.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithZeroOffset()
|
||||
-- Should work with zero offset
|
||||
local shader = Blur.ShaderBuilder.build(7, 0.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
function TestBlur:testShaderBuilderWithLargeOffset()
|
||||
-- Should work with large offset
|
||||
local shader = Blur.ShaderBuilder.build(7, 10.0, "weighted", -1)
|
||||
luaunit.assertNotNil(shader)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Initialization Tests
|
||||
-- ============================================================================
|
||||
|
||||
function TestBlur:testInitWithErrorHandler()
|
||||
-- Should accept ErrorHandler dependency
|
||||
Blur.init({ ErrorHandler = ErrorHandler })
|
||||
luaunit.assertNotNil(Blur._ErrorHandler)
|
||||
end
|
||||
|
||||
function TestBlur:testInitWithNilDeps()
|
||||
-- Should handle nil deps gracefully
|
||||
Blur.init(nil)
|
||||
luaunit.assertTrue(true) -- Should not crash
|
||||
end
|
||||
|
||||
function TestBlur:testInitWithEmptyTable()
|
||||
-- Should handle empty deps table
|
||||
Blur.init({})
|
||||
luaunit.assertTrue(true) -- Should not crash
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
|
||||
@@ -1282,18 +1282,6 @@ function TestElementUnhappyPaths:test_element_zero_dimensions()
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with extreme dimensions
|
||||
function TestElementUnhappyPaths:test_element_extreme_dimensions()
|
||||
local element = FlexLove.new({
|
||||
id = "huge",
|
||||
x = 0,
|
||||
y = 0,
|
||||
width = 1000000,
|
||||
height = 1000000,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid opacity values
|
||||
function TestElementUnhappyPaths:test_element_invalid_opacity()
|
||||
-- Opacity > 1
|
||||
@@ -1524,19 +1512,6 @@ function TestElementUnhappyPaths:test_clear_children_twice()
|
||||
luaunit.assertEquals(#parent.children, 0)
|
||||
end
|
||||
|
||||
-- Test: Element contains with extreme coordinates
|
||||
function TestElementUnhappyPaths:test_contains_extreme_coordinates()
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
x = 10,
|
||||
y = 20,
|
||||
width = 100,
|
||||
height = 50,
|
||||
})
|
||||
|
||||
luaunit.assertFalse(element:contains(math.huge, math.huge))
|
||||
luaunit.assertFalse(element:contains(-math.huge, -math.huge))
|
||||
end
|
||||
|
||||
-- Test: Element contains with NaN coordinates
|
||||
function TestElementUnhappyPaths:test_contains_nan_coordinates()
|
||||
@@ -1567,23 +1542,6 @@ function TestElementUnhappyPaths:test_scroll_without_manager()
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- Test: Element setScrollPosition with extreme values
|
||||
function TestElementUnhappyPaths:test_scroll_extreme_values()
|
||||
local element = FlexLove.new({
|
||||
id = "scrollable",
|
||||
width = 200,
|
||||
height = 200,
|
||||
overflow = "scroll",
|
||||
})
|
||||
|
||||
element:setScrollPosition(1000000, 1000000) -- Should clamp
|
||||
luaunit.assertTrue(true)
|
||||
|
||||
element:setScrollPosition(-1000000, -1000000) -- Should clamp to 0
|
||||
local scrollX, scrollY = element:getScrollPosition()
|
||||
luaunit.assertEquals(scrollX, 0)
|
||||
luaunit.assertEquals(scrollY, 0)
|
||||
end
|
||||
|
||||
-- Test: Element scrollBy with nil values
|
||||
function TestElementUnhappyPaths:test_scroll_by_nil()
|
||||
@@ -1776,15 +1734,6 @@ function TestElementUnhappyPaths:test_invalid_margin()
|
||||
margin = "invalid",
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Huge margin
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = { top = 1000000, left = 1000000, right = 1000000, bottom = 1000000 },
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid gap value
|
||||
@@ -1798,26 +1747,7 @@ function TestElementUnhappyPaths:test_invalid_gap()
|
||||
gap = -10,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Huge gap
|
||||
element = FlexLove.new({
|
||||
id = "test2",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
gap = 1000000,
|
||||
})
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: Element with invalid grid properties
|
||||
function TestElementUnhappyPaths:test_invalid_grid_properties()
|
||||
-- Zero rows/columns
|
||||
local element = FlexLove.new({
|
||||
id = "test",
|
||||
width = 300,
|
||||
height = 200,
|
||||
positioning = "grid",
|
||||
gridRows = 0,
|
||||
gridColumns = 0,
|
||||
})
|
||||
@@ -1860,19 +1790,6 @@ function TestElementUnhappyPaths:test_set_text_nil()
|
||||
luaunit.assertNil(element.text)
|
||||
end
|
||||
|
||||
-- Test: Element setText with extreme length
|
||||
function TestElementUnhappyPaths:test_set_text_extreme_length()
|
||||
local element = FlexLove.new({
|
||||
id = "text",
|
||||
width = 100,
|
||||
height = 100,
|
||||
text = "Initial",
|
||||
})
|
||||
|
||||
local longText = string.rep("a", 100000)
|
||||
element:setText(longText)
|
||||
luaunit.assertEquals(element.text, longText)
|
||||
end
|
||||
|
||||
-- Test: Element with conflicting size constraints
|
||||
function TestElementUnhappyPaths:test_conflicting_size_constraints()
|
||||
@@ -1999,6 +1916,166 @@ function TestElementUnhappyPaths:test_max_length_negative()
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test suite for convenience API features
|
||||
TestConvenienceAPI = {}
|
||||
|
||||
function TestConvenienceAPI:setUp()
|
||||
FlexLove.beginFrame(1920, 1080)
|
||||
end
|
||||
|
||||
function TestConvenienceAPI:tearDown()
|
||||
FlexLove.endFrame()
|
||||
end
|
||||
|
||||
-- Test: flexDirection "row" converts to "horizontal"
|
||||
function TestConvenienceAPI:test_flexDirection_row_converts()
|
||||
local element = FlexLove.new({
|
||||
id = "test_row",
|
||||
width = 200,
|
||||
height = 100,
|
||||
positioning = "flex",
|
||||
flexDirection = "row",
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.flexDirection, "horizontal")
|
||||
end
|
||||
|
||||
-- Test: flexDirection "column" converts to "vertical"
|
||||
function TestConvenienceAPI:test_flexDirection_column_converts()
|
||||
local element = FlexLove.new({
|
||||
id = "test_column",
|
||||
width = 200,
|
||||
height = 100,
|
||||
positioning = "flex",
|
||||
flexDirection = "column",
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.flexDirection, "vertical")
|
||||
end
|
||||
|
||||
-- Test: Single number padding expands to all sides
|
||||
function TestConvenienceAPI:test_padding_single_number()
|
||||
local element = FlexLove.new({
|
||||
id = "test_padding_num",
|
||||
width = 200,
|
||||
height = 100,
|
||||
padding = 10,
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.padding.top, 10)
|
||||
luaunit.assertEquals(element.padding.right, 10)
|
||||
luaunit.assertEquals(element.padding.bottom, 10)
|
||||
luaunit.assertEquals(element.padding.left, 10)
|
||||
end
|
||||
|
||||
-- Test: Single string padding expands to all sides
|
||||
function TestConvenienceAPI:test_padding_single_string()
|
||||
local element = FlexLove.new({
|
||||
id = "test_padding_str",
|
||||
width = 200,
|
||||
height = 100,
|
||||
padding = "5%",
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
-- All sides should be 5% of the element's dimensions
|
||||
-- For width: 5% of 200 = 10, for height: 5% of 100 = 5
|
||||
luaunit.assertEquals(element.padding.left, 10)
|
||||
luaunit.assertEquals(element.padding.right, 10)
|
||||
luaunit.assertEquals(element.padding.top, 5)
|
||||
luaunit.assertEquals(element.padding.bottom, 5)
|
||||
end
|
||||
|
||||
-- Test: Single number margin expands to all sides
|
||||
function TestConvenienceAPI:test_margin_single_number()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 400,
|
||||
height = 300,
|
||||
})
|
||||
|
||||
local element = FlexLove.new({
|
||||
id = "test_margin_num",
|
||||
parent = parent,
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 15,
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.margin.top, 15)
|
||||
luaunit.assertEquals(element.margin.right, 15)
|
||||
luaunit.assertEquals(element.margin.bottom, 15)
|
||||
luaunit.assertEquals(element.margin.left, 15)
|
||||
end
|
||||
|
||||
-- Test: Single string margin expands to all sides
|
||||
function TestConvenienceAPI:test_margin_single_string()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 400,
|
||||
height = 300,
|
||||
})
|
||||
|
||||
local element = FlexLove.new({
|
||||
id = "test_margin_str",
|
||||
parent = parent,
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = "10%",
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
-- Margin percentages are relative to parent dimensions
|
||||
-- 10% of parent width 400 = 40, 10% of parent height 300 = 30
|
||||
luaunit.assertEquals(element.margin.left, 40)
|
||||
luaunit.assertEquals(element.margin.right, 40)
|
||||
luaunit.assertEquals(element.margin.top, 30)
|
||||
luaunit.assertEquals(element.margin.bottom, 30)
|
||||
end
|
||||
|
||||
-- Test: Table padding still works (backward compatibility)
|
||||
function TestConvenienceAPI:test_padding_table_still_works()
|
||||
local element = FlexLove.new({
|
||||
id = "test_padding_table",
|
||||
width = 200,
|
||||
height = 100,
|
||||
padding = { top = 5, right = 10, bottom = 15, left = 20 },
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.padding.top, 5)
|
||||
luaunit.assertEquals(element.padding.right, 10)
|
||||
luaunit.assertEquals(element.padding.bottom, 15)
|
||||
luaunit.assertEquals(element.padding.left, 20)
|
||||
end
|
||||
|
||||
-- Test: Table margin still works (backward compatibility)
|
||||
function TestConvenienceAPI:test_margin_table_still_works()
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 400,
|
||||
height = 300,
|
||||
})
|
||||
|
||||
local element = FlexLove.new({
|
||||
id = "test_margin_table",
|
||||
parent = parent,
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = { top = 5, right = 10, bottom = 15, left = 20 },
|
||||
})
|
||||
|
||||
luaunit.assertNotNil(element)
|
||||
luaunit.assertEquals(element.margin.top, 5)
|
||||
luaunit.assertEquals(element.margin.right, 10)
|
||||
luaunit.assertEquals(element.margin.bottom, 15)
|
||||
luaunit.assertEquals(element.margin.left, 20)
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
|
||||
@@ -997,9 +997,6 @@ function TestFlexLoveUnhappyPaths:testNewWithInvalidPosition()
|
||||
local element = FlexLove.new({ x = -1000, y = -1000, width = 100, height = 100 })
|
||||
luaunit.assertNotNil(element)
|
||||
|
||||
-- Extreme positions
|
||||
element = FlexLove.new({ x = 1000000, y = 1000000, width = 100, height = 100 })
|
||||
luaunit.assertNotNil(element)
|
||||
end
|
||||
|
||||
-- Test: new() with circular parent reference
|
||||
@@ -1131,12 +1128,6 @@ end
|
||||
function TestFlexLoveUnhappyPaths:testWheelMovedWithInvalidValues()
|
||||
FlexLove.setMode("retained")
|
||||
|
||||
-- Extreme values
|
||||
FlexLove.wheelmoved(1000000, 1000000)
|
||||
luaunit.assertTrue(true)
|
||||
|
||||
FlexLove.wheelmoved(-1000000, -1000000)
|
||||
luaunit.assertTrue(true)
|
||||
|
||||
-- nil values
|
||||
local success = pcall(function()
|
||||
@@ -1207,9 +1198,6 @@ function TestFlexLoveUnhappyPaths:testGetElementAtPositionWithInvalidCoords()
|
||||
local element = FlexLove.getElementAtPosition(-100, -100)
|
||||
luaunit.assertNil(element)
|
||||
|
||||
-- Extreme coordinates
|
||||
element = FlexLove.getElementAtPosition(1000000, 1000000)
|
||||
luaunit.assertNil(element)
|
||||
|
||||
-- nil coordinates
|
||||
local success = pcall(function()
|
||||
@@ -1278,17 +1266,6 @@ function TestFlexLoveUnhappyPaths:testStateOperationsInRetainedMode()
|
||||
end
|
||||
|
||||
-- Test: Extreme z-index values
|
||||
function TestFlexLoveUnhappyPaths:testExtremeZIndexValues()
|
||||
FlexLove.setMode("retained")
|
||||
|
||||
local element1 = FlexLove.new({ width = 100, height = 100, z = -1000000 })
|
||||
local element2 = FlexLove.new({ width = 100, height = 100, z = 1000000 })
|
||||
|
||||
luaunit.assertNotNil(element1)
|
||||
luaunit.assertNotNil(element2)
|
||||
|
||||
FlexLove.draw() -- Should not crash during z-index sorting
|
||||
end
|
||||
|
||||
-- Test: Creating deeply nested element hierarchy
|
||||
function TestFlexLoveUnhappyPaths:testDeeplyNestedHierarchy()
|
||||
|
||||
1031
testing/__tests__/scroll_manager_edge_cases_test.lua
Normal file
1031
testing/__tests__/scroll_manager_edge_cases_test.lua
Normal file
File diff suppressed because it is too large
Load Diff
730
testing/__tests__/shorthand_syntax_test.lua
Normal file
730
testing/__tests__/shorthand_syntax_test.lua
Normal file
@@ -0,0 +1,730 @@
|
||||
-- Tests for shorthand syntax features (flexDirection aliases, margin/padding shortcuts)
|
||||
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||
|
||||
require("testing.loveStub")
|
||||
local luaunit = require("testing.luaunit")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
|
||||
-- Initialize ErrorHandler
|
||||
ErrorHandler.init({})
|
||||
local FlexLove = require("FlexLove")
|
||||
|
||||
TestShorthandSyntax = {}
|
||||
|
||||
function TestShorthandSyntax:setUp()
|
||||
FlexLove.init()
|
||||
FlexLove.setMode("immediate")
|
||||
FlexLove.beginFrame()
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:tearDown()
|
||||
FlexLove.endFrame()
|
||||
FlexLove.destroy()
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- FlexDirection Aliases Tests
|
||||
-- ============================================================================
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionRowEqualsHorizontal()
|
||||
-- Create two containers: one with "row", one with "horizontal"
|
||||
local containerRow = FlexLove.new({
|
||||
id = "container-row",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "row",
|
||||
})
|
||||
|
||||
local containerHorizontal = FlexLove.new({
|
||||
id = "container-horizontal",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "horizontal",
|
||||
})
|
||||
|
||||
-- Both should have the same internal flexDirection value
|
||||
luaunit.assertEquals(containerRow.flexDirection, "horizontal")
|
||||
luaunit.assertEquals(containerHorizontal.flexDirection, "horizontal")
|
||||
luaunit.assertEquals(containerRow.flexDirection, containerHorizontal.flexDirection)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionColumnEqualsVertical()
|
||||
-- Create two containers: one with "column", one with "vertical"
|
||||
local containerColumn = FlexLove.new({
|
||||
id = "container-column",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "column",
|
||||
})
|
||||
|
||||
local containerVertical = FlexLove.new({
|
||||
id = "container-vertical",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "vertical",
|
||||
})
|
||||
|
||||
-- Both should have the same internal flexDirection value
|
||||
luaunit.assertEquals(containerColumn.flexDirection, "vertical")
|
||||
luaunit.assertEquals(containerVertical.flexDirection, "vertical")
|
||||
luaunit.assertEquals(containerColumn.flexDirection, containerVertical.flexDirection)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionRowLayoutMatchesHorizontal()
|
||||
-- Create two containers with children: one with "row", one with "horizontal"
|
||||
local containerRow = FlexLove.new({
|
||||
id = "container-row",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "row",
|
||||
})
|
||||
|
||||
local containerHorizontal = FlexLove.new({
|
||||
id = "container-horizontal",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "horizontal",
|
||||
})
|
||||
|
||||
-- Add identical children to both
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-row-" .. i,
|
||||
width = 100,
|
||||
height = 50,
|
||||
parent = containerRow,
|
||||
})
|
||||
|
||||
FlexLove.new({
|
||||
id = "child-horizontal-" .. i,
|
||||
width = 100,
|
||||
height = 50,
|
||||
parent = containerHorizontal,
|
||||
})
|
||||
end
|
||||
|
||||
-- Trigger layout
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Children should be laid out identically
|
||||
for i = 1, 3 do
|
||||
local childRow = containerRow.children[i]
|
||||
local childHorizontal = containerHorizontal.children[i]
|
||||
|
||||
luaunit.assertEquals(childRow.x, childHorizontal.x, "Child " .. i .. " x position should match")
|
||||
luaunit.assertEquals(childRow.y, childHorizontal.y, "Child " .. i .. " y position should match")
|
||||
luaunit.assertEquals(childRow.width, childHorizontal.width, "Child " .. i .. " width should match")
|
||||
luaunit.assertEquals(childRow.height, childHorizontal.height, "Child " .. i .. " height should match")
|
||||
end
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionColumnLayoutMatchesVertical()
|
||||
-- Create two containers with children: one with "column", one with "vertical"
|
||||
local containerColumn = FlexLove.new({
|
||||
id = "container-column",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "column",
|
||||
})
|
||||
|
||||
local containerVertical = FlexLove.new({
|
||||
id = "container-vertical",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "vertical",
|
||||
})
|
||||
|
||||
-- Add identical children to both
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-column-" .. i,
|
||||
width = 100,
|
||||
height = 50,
|
||||
parent = containerColumn,
|
||||
})
|
||||
|
||||
FlexLove.new({
|
||||
id = "child-vertical-" .. i,
|
||||
width = 100,
|
||||
height = 50,
|
||||
parent = containerVertical,
|
||||
})
|
||||
end
|
||||
|
||||
-- Trigger layout
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Children should be laid out identically
|
||||
for i = 1, 3 do
|
||||
local childColumn = containerColumn.children[i]
|
||||
local childVertical = containerVertical.children[i]
|
||||
|
||||
luaunit.assertEquals(childColumn.x, childVertical.x, "Child " .. i .. " x position should match")
|
||||
luaunit.assertEquals(childColumn.y, childVertical.y, "Child " .. i .. " y position should match")
|
||||
luaunit.assertEquals(childColumn.width, childVertical.width, "Child " .. i .. " width should match")
|
||||
luaunit.assertEquals(childColumn.height, childVertical.height, "Child " .. i .. " height should match")
|
||||
end
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionRowWithJustifyContent()
|
||||
-- Test that "row" works with justifyContent like "horizontal" does
|
||||
local containerRow = FlexLove.new({
|
||||
id = "container-row",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "row",
|
||||
justifyContent = "space-between",
|
||||
})
|
||||
|
||||
local containerHorizontal = FlexLove.new({
|
||||
id = "container-horizontal",
|
||||
width = 400,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "horizontal",
|
||||
justifyContent = "space-between",
|
||||
})
|
||||
|
||||
-- Add children
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-row-" .. i,
|
||||
width = 80,
|
||||
height = 50,
|
||||
parent = containerRow,
|
||||
})
|
||||
|
||||
FlexLove.new({
|
||||
id = "child-horizontal-" .. i,
|
||||
width = 80,
|
||||
height = 50,
|
||||
parent = containerHorizontal,
|
||||
})
|
||||
end
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Verify space-between worked the same way
|
||||
for i = 1, 3 do
|
||||
local childRow = containerRow.children[i]
|
||||
local childHorizontal = containerHorizontal.children[i]
|
||||
luaunit.assertEquals(childRow.x, childHorizontal.x, "space-between should work identically")
|
||||
end
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionColumnWithAlignItems()
|
||||
-- Test that "column" works with alignItems like "vertical" does
|
||||
local containerColumn = FlexLove.new({
|
||||
id = "container-column",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "column",
|
||||
alignItems = "center",
|
||||
})
|
||||
|
||||
local containerVertical = FlexLove.new({
|
||||
id = "container-vertical",
|
||||
width = 200,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "vertical",
|
||||
alignItems = "center",
|
||||
})
|
||||
|
||||
-- Add children
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-column-" .. i,
|
||||
width = 80,
|
||||
height = 50,
|
||||
parent = containerColumn,
|
||||
})
|
||||
|
||||
FlexLove.new({
|
||||
id = "child-vertical-" .. i,
|
||||
width = 80,
|
||||
height = 50,
|
||||
parent = containerVertical,
|
||||
})
|
||||
end
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Verify center alignment worked the same way
|
||||
for i = 1, 3 do
|
||||
local childColumn = containerColumn.children[i]
|
||||
local childVertical = containerVertical.children[i]
|
||||
luaunit.assertEquals(childColumn.x, childVertical.x, "center alignment should work identically")
|
||||
end
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Margin Shorthand Tests
|
||||
-- ============================================================================
|
||||
|
||||
function TestShorthandSyntax:testMarginNumberEqualsMarginTable()
|
||||
-- Create two elements: one with margin=10, one with margin={top=10,right=10,bottom=10,left=10}
|
||||
local parent = FlexLove.new({
|
||||
id = "parent",
|
||||
width = 400,
|
||||
height = 400,
|
||||
})
|
||||
|
||||
local elementShorthand = FlexLove.new({
|
||||
id = "element-shorthand",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 10,
|
||||
parent = parent,
|
||||
})
|
||||
|
||||
local elementExplicit = FlexLove.new({
|
||||
id = "element-explicit",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = { top = 10, right = 10, bottom = 10, left = 10 },
|
||||
parent = parent,
|
||||
})
|
||||
|
||||
-- Both should have the same margin values
|
||||
luaunit.assertEquals(elementShorthand.margin.top, 10)
|
||||
luaunit.assertEquals(elementShorthand.margin.right, 10)
|
||||
luaunit.assertEquals(elementShorthand.margin.bottom, 10)
|
||||
luaunit.assertEquals(elementShorthand.margin.left, 10)
|
||||
|
||||
luaunit.assertEquals(elementShorthand.margin.top, elementExplicit.margin.top)
|
||||
luaunit.assertEquals(elementShorthand.margin.right, elementExplicit.margin.right)
|
||||
luaunit.assertEquals(elementShorthand.margin.bottom, elementExplicit.margin.bottom)
|
||||
luaunit.assertEquals(elementShorthand.margin.left, elementExplicit.margin.left)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testMarginShorthandLayoutMatchesExplicit()
|
||||
-- Create container with two children in column layout
|
||||
local container = FlexLove.new({
|
||||
id = "container",
|
||||
width = 400,
|
||||
height = 400,
|
||||
positioning = "flex",
|
||||
flexDirection = "column",
|
||||
})
|
||||
|
||||
local elementShorthand = FlexLove.new({
|
||||
id = "element-shorthand",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 20,
|
||||
parent = container,
|
||||
})
|
||||
|
||||
local elementExplicit = FlexLove.new({
|
||||
id = "element-explicit",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||
parent = container,
|
||||
})
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- The explicit element should be positioned 20px below the shorthand element
|
||||
-- shorthand: y=20 (top margin), height=100, bottom margin=20 → next starts at 140
|
||||
-- explicit: y=140+20=160
|
||||
luaunit.assertEquals(elementShorthand.y, 20, "Shorthand element should have top margin applied")
|
||||
luaunit.assertEquals(elementExplicit.y, 160, "Explicit element should be positioned after shorthand's bottom margin")
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testMarginZeroShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 0,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.margin.top, 0)
|
||||
luaunit.assertEquals(element.margin.right, 0)
|
||||
luaunit.assertEquals(element.margin.bottom, 0)
|
||||
luaunit.assertEquals(element.margin.left, 0)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testMarginLargeValueShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 100,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.margin.top, 100)
|
||||
luaunit.assertEquals(element.margin.right, 100)
|
||||
luaunit.assertEquals(element.margin.bottom, 100)
|
||||
luaunit.assertEquals(element.margin.left, 100)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testMarginDecimalShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 15.5,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.margin.top, 15.5)
|
||||
luaunit.assertEquals(element.margin.right, 15.5)
|
||||
luaunit.assertEquals(element.margin.bottom, 15.5)
|
||||
luaunit.assertEquals(element.margin.left, 15.5)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Padding Shorthand Tests
|
||||
-- ============================================================================
|
||||
|
||||
function TestShorthandSyntax:testPaddingNumberEqualsPaddingTable()
|
||||
-- Create two elements: one with padding=20, one with padding={top=20,right=20,bottom=20,left=20}
|
||||
local elementShorthand = FlexLove.new({
|
||||
id = "element-shorthand",
|
||||
width = 200,
|
||||
height = 200,
|
||||
padding = 20,
|
||||
})
|
||||
|
||||
local elementExplicit = FlexLove.new({
|
||||
id = "element-explicit",
|
||||
width = 200,
|
||||
height = 200,
|
||||
padding = { top = 20, right = 20, bottom = 20, left = 20 },
|
||||
})
|
||||
|
||||
-- Both should have the same padding values
|
||||
luaunit.assertEquals(elementShorthand.padding.top, 20)
|
||||
luaunit.assertEquals(elementShorthand.padding.right, 20)
|
||||
luaunit.assertEquals(elementShorthand.padding.bottom, 20)
|
||||
luaunit.assertEquals(elementShorthand.padding.left, 20)
|
||||
|
||||
luaunit.assertEquals(elementShorthand.padding.top, elementExplicit.padding.top)
|
||||
luaunit.assertEquals(elementShorthand.padding.right, elementExplicit.padding.right)
|
||||
luaunit.assertEquals(elementShorthand.padding.bottom, elementExplicit.padding.bottom)
|
||||
luaunit.assertEquals(elementShorthand.padding.left, elementExplicit.padding.left)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testPaddingShorthandAffectsContentArea()
|
||||
-- Create container with padding and a child
|
||||
local containerShorthand = FlexLove.new({
|
||||
id = "container-shorthand",
|
||||
width = 200,
|
||||
height = 200,
|
||||
padding = 30,
|
||||
})
|
||||
|
||||
local containerExplicit = FlexLove.new({
|
||||
id = "container-explicit",
|
||||
width = 200,
|
||||
height = 200,
|
||||
padding = { top = 30, right = 30, bottom = 30, left = 30 },
|
||||
})
|
||||
|
||||
-- Add children
|
||||
local childShorthand = FlexLove.new({
|
||||
id = "child-shorthand",
|
||||
width = "100%",
|
||||
height = "100%",
|
||||
parent = containerShorthand,
|
||||
})
|
||||
|
||||
local childExplicit = FlexLove.new({
|
||||
id = "child-explicit",
|
||||
width = "100%",
|
||||
height = "100%",
|
||||
parent = containerExplicit,
|
||||
})
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Children should have the same dimensions (200 - 30*2 = 140)
|
||||
luaunit.assertEquals(childShorthand.width, 140)
|
||||
luaunit.assertEquals(childShorthand.height, 140)
|
||||
luaunit.assertEquals(childExplicit.width, 140)
|
||||
luaunit.assertEquals(childExplicit.height, 140)
|
||||
|
||||
luaunit.assertEquals(childShorthand.width, childExplicit.width)
|
||||
luaunit.assertEquals(childShorthand.height, childExplicit.height)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testPaddingZeroShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
padding = 0,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.padding.top, 0)
|
||||
luaunit.assertEquals(element.padding.right, 0)
|
||||
luaunit.assertEquals(element.padding.bottom, 0)
|
||||
luaunit.assertEquals(element.padding.left, 0)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testPaddingLargeValueShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 300,
|
||||
height = 300,
|
||||
padding = 50,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.padding.top, 50)
|
||||
luaunit.assertEquals(element.padding.right, 50)
|
||||
luaunit.assertEquals(element.padding.bottom, 50)
|
||||
luaunit.assertEquals(element.padding.left, 50)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testPaddingDecimalShorthand()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
padding = 12.5,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.padding.top, 12.5)
|
||||
luaunit.assertEquals(element.padding.right, 12.5)
|
||||
luaunit.assertEquals(element.padding.bottom, 12.5)
|
||||
luaunit.assertEquals(element.padding.left, 12.5)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Combined Tests (FlexDirection + Margin/Padding)
|
||||
-- ============================================================================
|
||||
|
||||
function TestShorthandSyntax:testRowWithMarginShorthand()
|
||||
local container = FlexLove.new({
|
||||
id = "container",
|
||||
width = 500,
|
||||
height = 200,
|
||||
flexDirection = "row", -- Alias for "horizontal"
|
||||
})
|
||||
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-" .. i,
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 10, -- Shorthand
|
||||
parent = container,
|
||||
})
|
||||
end
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- First child: x=10 (left margin)
|
||||
-- Second child: x=10+100+10 (first child's margin-right) + 10 (own margin-left) = 130
|
||||
-- Third child: x=130+100+10+10 = 250
|
||||
luaunit.assertEquals(container.children[1].x, 10)
|
||||
luaunit.assertEquals(container.children[2].x, 130)
|
||||
luaunit.assertEquals(container.children[3].x, 250)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testColumnWithPaddingShorthand()
|
||||
local container = FlexLove.new({
|
||||
id = "container",
|
||||
width = 200,
|
||||
height = 500,
|
||||
flexDirection = "column", -- Alias for "vertical"
|
||||
padding = 15, -- Shorthand
|
||||
})
|
||||
|
||||
for i = 1, 3 do
|
||||
FlexLove.new({
|
||||
id = "child-" .. i,
|
||||
width = 100,
|
||||
height = 50,
|
||||
parent = container,
|
||||
})
|
||||
end
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Children should start at y=15 (top padding)
|
||||
-- First child: y=15
|
||||
-- Second child: y=15+50=65
|
||||
-- Third child: y=65+50=115
|
||||
luaunit.assertEquals(container.children[1].y, 15)
|
||||
luaunit.assertEquals(container.children[2].y, 65)
|
||||
luaunit.assertEquals(container.children[3].y, 115)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testRowAndColumnAliasesWithAllShorthands()
|
||||
-- Complex test: use all shorthands together
|
||||
local container = FlexLove.new({
|
||||
id = "container",
|
||||
width = 600,
|
||||
height = 400,
|
||||
flexDirection = "row", -- Alias
|
||||
padding = 20, -- Shorthand
|
||||
})
|
||||
|
||||
for i = 1, 2 do
|
||||
FlexLove.new({
|
||||
id = "child-" .. i,
|
||||
width = 150,
|
||||
height = 100,
|
||||
margin = 10, -- Shorthand
|
||||
parent = container,
|
||||
})
|
||||
end
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- First child: x=20 (container padding) + 10 (own margin) = 30
|
||||
-- Second child: x=30 + 150 + 10 (first child's margin-right) + 10 (own margin-left) = 200
|
||||
luaunit.assertEquals(container.children[1].x, 30)
|
||||
luaunit.assertEquals(container.children[2].x, 200)
|
||||
|
||||
-- Both children should be at y=20 (container padding) + 10 (own margin) = 30
|
||||
luaunit.assertEquals(container.children[1].y, 30)
|
||||
luaunit.assertEquals(container.children[2].y, 30)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testNestedContainersWithShorthands()
|
||||
-- Test nested containers with multiple shorthand usages
|
||||
local outerContainer = FlexLove.new({
|
||||
id = "outer",
|
||||
width = 500,
|
||||
height = 500,
|
||||
flexDirection = "column", -- Alias
|
||||
padding = 25, -- Shorthand
|
||||
})
|
||||
|
||||
local innerContainer = FlexLove.new({
|
||||
id = "inner",
|
||||
width = 400,
|
||||
height = 200,
|
||||
flexDirection = "row", -- Alias
|
||||
margin = 15, -- Shorthand
|
||||
padding = 10, -- Shorthand
|
||||
parent = outerContainer,
|
||||
})
|
||||
|
||||
local child = FlexLove.new({
|
||||
id = "child",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = 5, -- Shorthand
|
||||
parent = innerContainer,
|
||||
})
|
||||
|
||||
FlexLove.resize(800, 600)
|
||||
|
||||
-- Inner container position: y=25 (outer padding) + 15 (own margin) = 40
|
||||
luaunit.assertEquals(innerContainer.y, 40)
|
||||
|
||||
-- Child position within inner:
|
||||
-- x relative to inner = 10 (inner padding) + 5 (own margin) = 15
|
||||
-- y relative to inner = 10 (inner padding) + 5 (own margin) = 15
|
||||
local expectedChildX = innerContainer.x + 15
|
||||
local expectedChildY = innerContainer.y + 15
|
||||
luaunit.assertEquals(child.x, expectedChildX)
|
||||
luaunit.assertEquals(child.y, expectedChildY)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestShorthandSyntax:testFlexDirectionAliasDoesNotAffectOtherValues()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 200,
|
||||
height = 200,
|
||||
positioning = "flex",
|
||||
flexDirection = "row",
|
||||
justifyContent = "center",
|
||||
alignItems = "center",
|
||||
})
|
||||
|
||||
-- Using alias shouldn't affect other properties
|
||||
luaunit.assertEquals(element.justifyContent, "center")
|
||||
luaunit.assertEquals(element.alignItems, "center")
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testMarginShorthandDoesNotAffectPadding()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 200,
|
||||
height = 200,
|
||||
margin = 10,
|
||||
padding = { top = 5, right = 5, bottom = 5, left = 5 },
|
||||
})
|
||||
|
||||
-- Margin shorthand shouldn't affect padding
|
||||
luaunit.assertEquals(element.padding.top, 5)
|
||||
luaunit.assertEquals(element.padding.right, 5)
|
||||
luaunit.assertEquals(element.padding.bottom, 5)
|
||||
luaunit.assertEquals(element.padding.left, 5)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testPaddingShorthandDoesNotAffectMargin()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 200,
|
||||
height = 200,
|
||||
padding = 20,
|
||||
margin = { top = 10, right = 10, bottom = 10, left = 10 },
|
||||
})
|
||||
|
||||
-- Padding shorthand shouldn't affect margin
|
||||
luaunit.assertEquals(element.margin.top, 10)
|
||||
luaunit.assertEquals(element.margin.right, 10)
|
||||
luaunit.assertEquals(element.margin.bottom, 10)
|
||||
luaunit.assertEquals(element.margin.left, 10)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testBothMarginAndPaddingShorthands()
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 200,
|
||||
height = 200,
|
||||
margin = 15,
|
||||
padding = 25,
|
||||
})
|
||||
|
||||
-- Both should be expanded correctly
|
||||
luaunit.assertEquals(element.margin.top, 15)
|
||||
luaunit.assertEquals(element.margin.right, 15)
|
||||
luaunit.assertEquals(element.margin.bottom, 15)
|
||||
luaunit.assertEquals(element.margin.left, 15)
|
||||
|
||||
luaunit.assertEquals(element.padding.top, 25)
|
||||
luaunit.assertEquals(element.padding.right, 25)
|
||||
luaunit.assertEquals(element.padding.bottom, 25)
|
||||
luaunit.assertEquals(element.padding.left, 25)
|
||||
end
|
||||
|
||||
function TestShorthandSyntax:testNegativeMarginShorthand()
|
||||
-- Negative margins should work
|
||||
local element = FlexLove.new({
|
||||
id = "element",
|
||||
width = 100,
|
||||
height = 100,
|
||||
margin = -5,
|
||||
})
|
||||
|
||||
luaunit.assertEquals(element.margin.top, -5)
|
||||
luaunit.assertEquals(element.margin.right, -5)
|
||||
luaunit.assertEquals(element.margin.bottom, -5)
|
||||
luaunit.assertEquals(element.margin.left, -5)
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
610
testing/__tests__/text_editor_edge_cases_test.lua
Normal file
610
testing/__tests__/text_editor_edge_cases_test.lua
Normal file
@@ -0,0 +1,610 @@
|
||||
-- Edge case and unhappy path tests for TextEditor module
|
||||
package.path = package.path .. ";./?.lua;./modules/?.lua"
|
||||
|
||||
require("testing.loveStub")
|
||||
local luaunit = require("testing.luaunit")
|
||||
local ErrorHandler = require("modules.ErrorHandler")
|
||||
|
||||
-- Initialize ErrorHandler
|
||||
ErrorHandler.init({})
|
||||
local TextEditor = require("modules.TextEditor")
|
||||
local Color = require("modules.Color")
|
||||
local utils = require("modules.utils")
|
||||
|
||||
TestTextEditorEdgeCases = {}
|
||||
|
||||
-- Mock dependencies
|
||||
local MockContext = {
|
||||
_immediateMode = false,
|
||||
_focusedElement = nil,
|
||||
}
|
||||
|
||||
local MockStateManager = {
|
||||
getState = function(id)
|
||||
return nil
|
||||
end,
|
||||
updateState = function(id, state) end,
|
||||
}
|
||||
|
||||
-- Helper to create TextEditor with dependencies
|
||||
local function createTextEditor(config)
|
||||
config = config or {}
|
||||
return TextEditor.new(config, {
|
||||
Context = MockContext,
|
||||
StateManager = MockStateManager,
|
||||
Color = Color,
|
||||
utils = utils,
|
||||
})
|
||||
end
|
||||
|
||||
-- Helper to create mock element
|
||||
local function createMockElement()
|
||||
return {
|
||||
_stateId = "test-element-1",
|
||||
width = 200,
|
||||
height = 30,
|
||||
padding = {top = 0, right = 0, bottom = 0, left = 0},
|
||||
_renderer = {
|
||||
getFont = function()
|
||||
return {
|
||||
getWidth = function(text) return #text * 8 end,
|
||||
getHeight = function() return 16 end,
|
||||
}
|
||||
end,
|
||||
wrapLine = function(element, line, maxWidth)
|
||||
return {{text = line, startIdx = 0, endIdx = #line}}
|
||||
end,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Constructor Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithInvalidCursorBlinkRate()
|
||||
-- Negative blink rate
|
||||
local editor = createTextEditor({cursorBlinkRate = -1})
|
||||
luaunit.assertEquals(editor.cursorBlinkRate, -1) -- Should accept any value
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithZeroCursorBlinkRate()
|
||||
-- Zero blink rate (would cause rapid blinking)
|
||||
local editor = createTextEditor({cursorBlinkRate = 0})
|
||||
luaunit.assertEquals(editor.cursorBlinkRate, 0)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithVeryLargeCursorBlinkRate()
|
||||
-- Very large blink rate
|
||||
local editor = createTextEditor({cursorBlinkRate = 1000})
|
||||
luaunit.assertEquals(editor.cursorBlinkRate, 1000)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithNegativeMaxLength()
|
||||
-- Negative maxLength should be ignored
|
||||
local editor = createTextEditor({maxLength = -10})
|
||||
luaunit.assertEquals(editor.maxLength, -10) -- Module doesn't validate, just stores
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithZeroMaxLength()
|
||||
-- Zero maxLength (no text allowed)
|
||||
local editor = createTextEditor({maxLength = 0})
|
||||
editor:setText("test")
|
||||
luaunit.assertEquals(editor:getText(), "") -- Should be empty
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithInvalidInputType()
|
||||
-- Invalid input type (not validated by constructor)
|
||||
local editor = createTextEditor({inputType = "invalid"})
|
||||
luaunit.assertEquals(editor.inputType, "invalid")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithCustomSanitizerReturnsNil()
|
||||
-- Custom sanitizer that returns nil
|
||||
local editor = createTextEditor({
|
||||
customSanitizer = function(text)
|
||||
return nil
|
||||
end,
|
||||
})
|
||||
|
||||
editor:setText("test")
|
||||
-- Should fallback to original text when sanitizer returns nil
|
||||
luaunit.assertEquals(editor:getText(), "test")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testNewWithCustomSanitizerThrowsError()
|
||||
-- Custom sanitizer that throws error
|
||||
local editor = createTextEditor({
|
||||
customSanitizer = function(text)
|
||||
error("Intentional error")
|
||||
end,
|
||||
})
|
||||
|
||||
-- Should error when setting text
|
||||
luaunit.assertErrorMsgContains("Intentional error", function()
|
||||
editor:setText("test")
|
||||
end)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Text Buffer Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testSetTextWithEmptyString()
|
||||
local editor = createTextEditor()
|
||||
editor:setText("")
|
||||
luaunit.assertEquals(editor:getText(), "")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetTextWithNil()
|
||||
local editor = createTextEditor({text = "initial"})
|
||||
editor:setText(nil)
|
||||
luaunit.assertEquals(editor:getText(), "") -- Should default to empty string
|
||||
end
|
||||
|
||||
|
||||
function TestTextEditorEdgeCases:testInsertTextAtInvalidPosition()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
|
||||
-- Insert at negative position (should treat as 0)
|
||||
editor:insertText("X", -10)
|
||||
luaunit.assertStrContains(editor:getText(), "X")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testInsertTextBeyondLength()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
|
||||
-- Insert beyond text length
|
||||
editor:insertText("X", 1000)
|
||||
luaunit.assertStrContains(editor:getText(), "X")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testInsertTextWithEmptyString()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:insertText("", 2)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should remain unchanged
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testInsertTextWhenAtMaxLength()
|
||||
local editor = createTextEditor({text = "Hello", maxLength = 5})
|
||||
editor:insertText("X", 5)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testDeleteTextWithInvertedRange()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:deleteText(10, 2) -- End before start
|
||||
-- Should swap and delete
|
||||
luaunit.assertEquals(#editor:getText(), 3) -- Deleted 8 characters
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testDeleteTextBeyondBounds()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:deleteText(10, 20) -- Beyond text length
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should clamp to bounds
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testDeleteTextWithNegativePositions()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:deleteText(-5, -1) -- Negative positions
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should clamp to 0
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testReplaceTextWithEmptyString()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:replaceText(0, 5, "")
|
||||
luaunit.assertEquals(editor:getText(), " World") -- Should just delete
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testReplaceTextBeyondBounds()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:replaceText(10, 20, "X")
|
||||
luaunit.assertStrContains(editor:getText(), "X")
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- UTF-8 Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testSetTextWithUTF8Emoji()
|
||||
local editor = createTextEditor()
|
||||
editor:setText("Hello 👋 World 🌍")
|
||||
luaunit.assertStrContains(editor:getText(), "👋")
|
||||
luaunit.assertStrContains(editor:getText(), "🌍")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testInsertTextWithUTF8Characters()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:insertText("世界", 5) -- Chinese characters
|
||||
luaunit.assertStrContains(editor:getText(), "世界")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testCursorPositionWithUTF8()
|
||||
local editor = createTextEditor({text = "Hello👋World"})
|
||||
-- Cursor positions should be in characters, not bytes
|
||||
editor:setCursorPosition(6) -- After emoji
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 6)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testDeleteTextWithUTF8()
|
||||
local editor = createTextEditor({text = "Hello👋World"})
|
||||
editor:deleteText(5, 6) -- Delete emoji
|
||||
luaunit.assertEquals(editor:getText(), "HelloWorld")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMaxLengthWithUTF8()
|
||||
local editor = createTextEditor({maxLength = 10})
|
||||
editor:setText("Hello👋👋👋👋👋") -- 10 characters including emojis
|
||||
luaunit.assertTrue(utf8.len(editor:getText()) <= 10)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Cursor Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testSetCursorPositionNegative()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setCursorPosition(-10)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should clamp to 0
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetCursorPositionBeyondLength()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setCursorPosition(1000)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 5) -- Should clamp to length
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetCursorPositionWithNonNumber()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor._cursorPosition = "invalid" -- Corrupt state
|
||||
editor:setCursorPosition(3)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 3) -- Should validate and fix
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorByZero()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setCursorPosition(2)
|
||||
editor:moveCursorBy(0)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 2) -- Should stay same
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorByLargeNegative()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setCursorPosition(2)
|
||||
editor:moveCursorBy(-1000)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should clamp to 0
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorByLargePositive()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setCursorPosition(2)
|
||||
editor:moveCursorBy(1000)
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 5) -- Should clamp to length
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorToPreviousWordAtStart()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:moveCursorToStart()
|
||||
editor:moveCursorToPreviousWord()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0) -- Should stay at start
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorToNextWordAtEnd()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:moveCursorToEnd()
|
||||
editor:moveCursorToNextWord()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 11) -- Should stay at end
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMoveCursorWithEmptyBuffer()
|
||||
local editor = createTextEditor({text = ""})
|
||||
editor:moveCursorToStart()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0)
|
||||
editor:moveCursorToEnd()
|
||||
luaunit.assertEquals(editor:getCursorPosition(), 0)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Selection Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testSetSelectionWithInvertedRange()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:setSelection(10, 2) -- End before start
|
||||
local start, endPos = editor:getSelection()
|
||||
luaunit.assertTrue(start <= endPos) -- Should be swapped
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetSelectionBeyondBounds()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setSelection(0, 1000)
|
||||
local start, endPos = editor:getSelection()
|
||||
luaunit.assertEquals(endPos, 5) -- Should clamp to length
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetSelectionWithNegativePositions()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setSelection(-5, -1)
|
||||
local start, endPos = editor:getSelection()
|
||||
luaunit.assertEquals(start, 0) -- Should clamp to 0
|
||||
luaunit.assertEquals(endPos, 0)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSetSelectionWithSameStartEnd()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:setSelection(2, 2) -- Same position
|
||||
luaunit.assertFalse(editor:hasSelection()) -- Should be no selection
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testGetSelectedTextWithNoSelection()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
luaunit.assertNil(editor:getSelectedText())
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testDeleteSelectionWithNoSelection()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
local deleted = editor:deleteSelection()
|
||||
luaunit.assertFalse(deleted) -- Should return false
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Text unchanged
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSelectAllWithEmptyBuffer()
|
||||
local editor = createTextEditor({text = ""})
|
||||
editor:selectAll()
|
||||
luaunit.assertFalse(editor:hasSelection()) -- No selection on empty text
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testGetSelectionRectsWithEmptyBuffer()
|
||||
local editor = createTextEditor({text = ""})
|
||||
local mockElement = createMockElement()
|
||||
editor:initialize(mockElement)
|
||||
|
||||
editor:setSelection(0, 0)
|
||||
local rects = editor:_getSelectionRects(0, 0)
|
||||
luaunit.assertEquals(#rects, 0) -- No rects for empty selection
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Focus Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testFocusWithoutElement()
|
||||
local editor = createTextEditor()
|
||||
-- Should not error
|
||||
editor:focus()
|
||||
luaunit.assertTrue(editor:isFocused())
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testBlurWithoutElement()
|
||||
local editor = createTextEditor()
|
||||
editor:focus()
|
||||
editor:blur()
|
||||
luaunit.assertFalse(editor:isFocused())
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testFocusTwice()
|
||||
local editor = createTextEditor()
|
||||
editor:focus()
|
||||
editor:focus() -- Focus again
|
||||
luaunit.assertTrue(editor:isFocused()) -- Should remain focused
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testBlurTwice()
|
||||
local editor = createTextEditor()
|
||||
editor:focus()
|
||||
editor:blur()
|
||||
editor:blur() -- Blur again
|
||||
luaunit.assertFalse(editor:isFocused()) -- Should remain blurred
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Mouse Input Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testMouseToTextPositionWithoutElement()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
local pos = editor:mouseToTextPosition(10, 10)
|
||||
luaunit.assertEquals(pos, 0) -- Should return 0 without element
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMouseToTextPositionWithNilBuffer()
|
||||
local editor = createTextEditor()
|
||||
local mockElement = createMockElement()
|
||||
mockElement.x = 0
|
||||
mockElement.y = 0
|
||||
editor:initialize(mockElement)
|
||||
editor._textBuffer = nil
|
||||
|
||||
local pos = editor:mouseToTextPosition(10, 10)
|
||||
luaunit.assertEquals(pos, 0) -- Should handle nil buffer
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMouseToTextPositionWithNegativeCoords()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
local mockElement = createMockElement()
|
||||
mockElement.x = 100
|
||||
mockElement.y = 100
|
||||
editor:initialize(mockElement)
|
||||
|
||||
local pos = editor:mouseToTextPosition(-10, -10)
|
||||
luaunit.assertTrue(pos >= 0) -- Should clamp to valid position
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextClickWithoutFocus()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:handleTextClick(10, 10, 1)
|
||||
-- Should not error, but also won't do anything without focus
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextDragWithoutMouseDown()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:handleTextDrag(20, 10) -- Drag without mouseDownPosition
|
||||
-- Should not error
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextClickWithZeroClickCount()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:handleTextClick(10, 10, 0)
|
||||
-- Should not error
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Update Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testUpdateWithoutFocus()
|
||||
local editor = createTextEditor()
|
||||
editor:update(1.0) -- Should not update cursor blink
|
||||
luaunit.assertTrue(true) -- Should not error
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testUpdateWithNegativeDt()
|
||||
local editor = createTextEditor()
|
||||
editor:focus()
|
||||
editor:update(-1.0) -- Negative delta time
|
||||
-- Should not error
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testUpdateWithZeroDt()
|
||||
local editor = createTextEditor()
|
||||
editor:focus()
|
||||
editor:update(0) -- Zero delta time
|
||||
-- Should not error
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
-- Key Press Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleKeyPressWithoutFocus()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:handleKeyPress("backspace", "backspace", false)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not modify
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleKeyPressBackspaceAtStart()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:moveCursorToStart()
|
||||
editor:handleKeyPress("backspace", "backspace", false)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not delete
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleKeyPressDeleteAtEnd()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:moveCursorToEnd()
|
||||
editor:handleKeyPress("delete", "delete", false)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not delete
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleKeyPressWithUnknownKey()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:handleKeyPress("unknownkey", "unknownkey", false)
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should ignore
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Text Input Edge Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextInputWithoutFocus()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:handleTextInput("X")
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextInputWithEmptyString()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
editor:focus()
|
||||
editor:handleTextInput("")
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not modify
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextInputWithNewlineInSingleLine()
|
||||
local editor = createTextEditor({text = "Hello", multiline = false, allowNewlines = false})
|
||||
editor:focus()
|
||||
editor:handleTextInput("\n")
|
||||
-- Should sanitize newline in single-line mode
|
||||
luaunit.assertFalse(editor:getText():find("\n") ~= nil)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testHandleTextInputCallbackReturnsFalse()
|
||||
local editor = createTextEditor({
|
||||
text = "Hello",
|
||||
onTextInput = function(element, text)
|
||||
return false -- Reject input
|
||||
end,
|
||||
})
|
||||
local mockElement = createMockElement()
|
||||
editor:initialize(mockElement)
|
||||
editor:focus()
|
||||
editor:handleTextInput("X")
|
||||
luaunit.assertEquals(editor:getText(), "Hello") -- Should not insert
|
||||
end
|
||||
|
||||
-- ============================================================================
|
||||
-- Special Cases
|
||||
-- ============================================================================
|
||||
|
||||
function TestTextEditorEdgeCases:testPasswordModeWithEmptyText()
|
||||
local editor = createTextEditor({passwordMode = true, text = ""})
|
||||
luaunit.assertEquals(editor:getText(), "")
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testMultilineWithMaxLines()
|
||||
local editor = createTextEditor({multiline = true, maxLines = 2})
|
||||
editor:setText("Line1\nLine2\nLine3\nLine4")
|
||||
-- MaxLines might not be enforced by setText, depends on implementation
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testTextWrapWithZeroWidth()
|
||||
local editor = createTextEditor({textWrap = true})
|
||||
local mockElement = createMockElement()
|
||||
mockElement.width = 0
|
||||
editor:initialize(mockElement)
|
||||
editor:setText("Hello World")
|
||||
-- Should handle zero width gracefully
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testAutoGrowWithoutElement()
|
||||
local editor = createTextEditor({autoGrow = true, multiline = true})
|
||||
editor:updateAutoGrowHeight()
|
||||
-- Should not error without element
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testGetCursorScreenPositionWithoutElement()
|
||||
local editor = createTextEditor({text = "Hello"})
|
||||
local x, y = editor:_getCursorScreenPosition()
|
||||
luaunit.assertEquals(x, 0)
|
||||
luaunit.assertEquals(y, 0)
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSelectWordAtPositionWithEmptyText()
|
||||
local editor = createTextEditor({text = ""})
|
||||
editor:_selectWordAtPosition(0)
|
||||
luaunit.assertFalse(editor:hasSelection())
|
||||
end
|
||||
|
||||
function TestTextEditorEdgeCases:testSelectWordAtPositionOnWhitespace()
|
||||
local editor = createTextEditor({text = "Hello World"})
|
||||
editor:_selectWordAtPosition(7) -- In whitespace
|
||||
-- Behavior depends on implementation
|
||||
luaunit.assertTrue(true)
|
||||
end
|
||||
|
||||
if not _G.RUNNING_ALL_TESTS then
|
||||
os.exit(luaunit.LuaUnit.run())
|
||||
end
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Parallel Test Runner for FlexLove
|
||||
# Runs tests in parallel to speed up execution
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR/.."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Create temp directory for test results
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap "rm -rf $TEMP_DIR" EXIT
|
||||
|
||||
echo "========================================"
|
||||
echo "Running tests in parallel..."
|
||||
echo "========================================"
|
||||
|
||||
# Get all test files
|
||||
TEST_FILES=(
|
||||
"testing/__tests__/animation_test.lua"
|
||||
"testing/__tests__/animation_properties_test.lua"
|
||||
"testing/__tests__/blur_test.lua"
|
||||
"testing/__tests__/critical_failures_test.lua"
|
||||
"testing/__tests__/easing_test.lua"
|
||||
"testing/__tests__/element_test.lua"
|
||||
"testing/__tests__/event_handler_test.lua"
|
||||
"testing/__tests__/flexlove_test.lua"
|
||||
"testing/__tests__/font_cache_test.lua"
|
||||
"testing/__tests__/grid_test.lua"
|
||||
"testing/__tests__/image_cache_test.lua"
|
||||
"testing/__tests__/image_renderer_test.lua"
|
||||
"testing/__tests__/image_scaler_test.lua"
|
||||
"testing/__tests__/image_tiling_test.lua"
|
||||
"testing/__tests__/input_event_test.lua"
|
||||
"testing/__tests__/keyframe_animation_test.lua"
|
||||
"testing/__tests__/layout_edge_cases_test.lua"
|
||||
"testing/__tests__/layout_engine_test.lua"
|
||||
"testing/__tests__/ninepatch_parser_test.lua"
|
||||
"testing/__tests__/ninepatch_test.lua"
|
||||
"testing/__tests__/overflow_test.lua"
|
||||
"testing/__tests__/path_validation_test.lua"
|
||||
"testing/__tests__/performance_instrumentation_test.lua"
|
||||
"testing/__tests__/performance_warnings_test.lua"
|
||||
"testing/__tests__/renderer_test.lua"
|
||||
"testing/__tests__/roundedrect_test.lua"
|
||||
"testing/__tests__/sanitization_test.lua"
|
||||
"testing/__tests__/text_editor_test.lua"
|
||||
"testing/__tests__/theme_test.lua"
|
||||
"testing/__tests__/touch_events_test.lua"
|
||||
"testing/__tests__/transform_test.lua"
|
||||
"testing/__tests__/units_test.lua"
|
||||
"testing/__tests__/utils_test.lua"
|
||||
)
|
||||
|
||||
# Number of parallel jobs (adjust based on CPU cores)
|
||||
MAX_JOBS=${MAX_JOBS:-8}
|
||||
|
||||
# Function to run a single test file
|
||||
run_test() {
|
||||
local test_file=$1
|
||||
local test_name=$(basename "$test_file" .lua)
|
||||
local output_file="$TEMP_DIR/${test_name}.out"
|
||||
local status_file="$TEMP_DIR/${test_name}.status"
|
||||
|
||||
# Create a wrapper script that runs the test
|
||||
cat > "$TEMP_DIR/${test_name}_runner.lua" << 'EOF'
|
||||
package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua"
|
||||
_G.RUNNING_ALL_TESTS = true
|
||||
local luaunit = require("testing.luaunit")
|
||||
EOF
|
||||
|
||||
echo "dofile('$test_file')" >> "$TEMP_DIR/${test_name}_runner.lua"
|
||||
echo "os.exit(luaunit.LuaUnit.run())" >> "$TEMP_DIR/${test_name}_runner.lua"
|
||||
|
||||
# Run the test and capture output
|
||||
if lua "$TEMP_DIR/${test_name}_runner.lua" > "$output_file" 2>&1; then
|
||||
echo "0" > "$status_file"
|
||||
else
|
||||
echo "1" > "$status_file"
|
||||
fi
|
||||
}
|
||||
|
||||
export -f run_test
|
||||
export TEMP_DIR
|
||||
|
||||
# Run tests in parallel
|
||||
printf '%s\n' "${TEST_FILES[@]}" | xargs -P $MAX_JOBS -I {} bash -c 'run_test "{}"'
|
||||
|
||||
# Collect results
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Test Results Summary"
|
||||
echo "========================================"
|
||||
|
||||
total_tests=0
|
||||
passed_tests=0
|
||||
failed_tests=0
|
||||
total_successes=0
|
||||
total_failures=0
|
||||
total_errors=0
|
||||
|
||||
for test_file in "${TEST_FILES[@]}"; do
|
||||
test_name=$(basename "$test_file" .lua)
|
||||
output_file="$TEMP_DIR/${test_name}.out"
|
||||
status_file="$TEMP_DIR/${test_name}.status"
|
||||
|
||||
if [ -f "$status_file" ]; then
|
||||
status=$(cat "$status_file")
|
||||
|
||||
# Extract test counts from output
|
||||
if grep -q "Ran.*tests" "$output_file"; then
|
||||
test_line=$(grep "Ran.*tests" "$output_file")
|
||||
|
||||
# Parse: "Ran X tests in Y seconds, A successes, B failures, C errors"
|
||||
if [[ $test_line =~ Ran\ ([0-9]+)\ tests.*,\ ([0-9]+)\ successes.*,\ ([0-9]+)\ failures.*,\ ([0-9]+)\ errors ]]; then
|
||||
tests="${BASH_REMATCH[1]}"
|
||||
successes="${BASH_REMATCH[2]}"
|
||||
failures="${BASH_REMATCH[3]}"
|
||||
errors="${BASH_REMATCH[4]}"
|
||||
|
||||
total_tests=$((total_tests + tests))
|
||||
total_successes=$((total_successes + successes))
|
||||
total_failures=$((total_failures + failures))
|
||||
total_errors=$((total_errors + errors))
|
||||
|
||||
if [ "$status" = "0" ] && [ "$failures" = "0" ] && [ "$errors" = "0" ]; then
|
||||
echo -e "${GREEN}✓${NC} $test_name: $tests tests, $successes passed"
|
||||
passed_tests=$((passed_tests + 1))
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name: $tests tests, $successes passed, $failures failures, $errors errors"
|
||||
failed_tests=$((failed_tests + 1))
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name: Failed to run"
|
||||
failed_tests=$((failed_tests + 1))
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}✗${NC} $test_name: No results"
|
||||
failed_tests=$((failed_tests + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Overall Summary"
|
||||
echo "========================================"
|
||||
echo "Total test files: ${#TEST_FILES[@]}"
|
||||
echo -e "${GREEN}Passed: $passed_tests${NC}"
|
||||
echo -e "${RED}Failed: $failed_tests${NC}"
|
||||
echo ""
|
||||
echo "Total tests run: $total_tests"
|
||||
echo -e "${GREEN}Successes: $total_successes${NC}"
|
||||
echo -e "${YELLOW}Failures: $total_failures${NC}"
|
||||
echo -e "${RED}Errors: $total_errors${NC}"
|
||||
echo ""
|
||||
|
||||
# Show detailed output for failed tests
|
||||
if [ $failed_tests -gt 0 ]; then
|
||||
echo "========================================"
|
||||
echo "Failed Test Details"
|
||||
echo "========================================"
|
||||
|
||||
for test_file in "${TEST_FILES[@]}"; do
|
||||
test_name=$(basename "$test_file" .lua)
|
||||
output_file="$TEMP_DIR/${test_name}.out"
|
||||
status_file="$TEMP_DIR/${test_name}.status"
|
||||
|
||||
if [ -f "$status_file" ] && [ "$(cat "$status_file")" != "0" ]; then
|
||||
echo ""
|
||||
echo "--- $test_name ---"
|
||||
# Show last 20 lines of output
|
||||
tail -20 "$output_file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Exit with error if any tests failed
|
||||
if [ $failed_tests -gt 0 ] || [ $total_errors -gt 0 ]; then
|
||||
exit 1
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
Reference in New Issue
Block a user