remove subpar tests, update examples

This commit is contained in:
Michael Freno
2025-11-13 09:23:31 -05:00
parent b173ab7354
commit 07278aac96
52 changed files with 269 additions and 27943 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,365 +0,0 @@
package.path = package.path .. ";?.lua"
local luaunit = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
local Gui, enums = FlexLove.Gui, FlexLove.enums
local Positioning = enums.Positioning
local FlexDirection = enums.FlexDirection
-- Test the Units system functionality
TestUnitsSystem = {}
function TestUnitsSystem:setUp()
-- Clear any existing GUI elements and reset viewport
Gui.destroy()
-- Set a consistent viewport size for testing
love.window.setMode(1200, 800)
end
function TestUnitsSystem:tearDown()
Gui.destroy()
-- Restore original viewport size
love.window.setMode(800, 600)
end
-- ============================================
-- Units Parsing Tests
-- ============================================
function TestUnitsSystem:testUnitsParsePx()
-- Test pixel unit parsing
local container = Gui.new({
id = "container",
width = "100px",
height = "200px",
x = "50px",
y = "75px",
})
luaunit.assertEquals(container.width, 100)
luaunit.assertEquals(container.height, 200)
luaunit.assertEquals(container.x, 50)
luaunit.assertEquals(container.y, 75)
luaunit.assertEquals(container.units.width.unit, "px")
luaunit.assertEquals(container.units.height.unit, "px")
luaunit.assertEquals(container.units.x.unit, "px")
luaunit.assertEquals(container.units.y.unit, "px")
end
function TestUnitsSystem:testUnitsParsePercentage()
-- Test percentage unit parsing
local parent = Gui.new({
id = "parent",
width = 400,
height = 300,
})
local child = Gui.new({
id = "child",
width = "50%",
height = "25%",
parent = parent,
})
luaunit.assertEquals(child.width, 200) -- 50% of 400
luaunit.assertEquals(child.height, 75) -- 25% of 300
luaunit.assertEquals(child.units.width.unit, "%")
luaunit.assertEquals(child.units.height.unit, "%")
luaunit.assertEquals(child.units.width.value, 50)
luaunit.assertEquals(child.units.height.value, 25)
end
function TestUnitsSystem:testUnitsParseViewportWidth()
-- Test viewport width units (1200px viewport)
local container = Gui.new({
id = "container",
width = "50vw",
height = "100px",
})
luaunit.assertEquals(container.width, 600) -- 50% of 1200
luaunit.assertEquals(container.units.width.unit, "vw")
luaunit.assertEquals(container.units.width.value, 50)
end
function TestUnitsSystem:testUnitsParseViewportHeight()
-- Test viewport height units (800px viewport)
local container = Gui.new({
id = "container",
width = "100px",
height = "25vh",
})
luaunit.assertEquals(container.height, 200) -- 25% of 800
luaunit.assertEquals(container.units.height.unit, "vh")
luaunit.assertEquals(container.units.height.value, 25)
end
function TestUnitsSystem:testUnitsAutoSizing()
-- Test that auto-sized elements use "auto" unit
local autoContainer = Gui.new({
id = "autoContainer",
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
})
luaunit.assertEquals(autoContainer.units.width.unit, "auto")
luaunit.assertEquals(autoContainer.units.height.unit, "auto")
luaunit.assertTrue(autoContainer.autosizing.width)
luaunit.assertTrue(autoContainer.autosizing.height)
end
function TestUnitsSystem:testMixedUnits()
-- Test elements with different unit types
local container = Gui.new({
id = "container",
width = "80vw", -- viewport width
height = "400px", -- pixels
x = "10%", -- percentage of viewport
y = "5vh", -- viewport height
gap = "2vw", -- viewport width for gap
textSize = "16px", -- pixel font size
})
luaunit.assertEquals(container.width, 960) -- 80% of 1200
luaunit.assertEquals(container.height, 400) -- 400px
luaunit.assertEquals(container.x, 120) -- 10% of 1200
luaunit.assertEquals(container.y, 40) -- 5% of 800
luaunit.assertEquals(container.gap, 24) -- 2% of 1200
luaunit.assertEquals(container.textSize, 16) -- 16px
end
-- ============================================
-- Resize and Unit Recalculation Tests
-- ============================================
function TestUnitsSystem:testResizeViewportUnits()
-- Test that viewport units recalculate on resize
local container = Gui.new({
id = "container",
width = "50vw",
height = "25vh",
})
luaunit.assertEquals(container.width, 600) -- 50% of 1200
luaunit.assertEquals(container.height, 200) -- 25% of 800
-- Simulate viewport resize using setMode
love.window.setMode(1600, 1000)
container:resize(1600, 1000)
luaunit.assertEquals(container.width, 800) -- 50% of 1600
luaunit.assertEquals(container.height, 250) -- 25% of 1000
-- Restore viewport
love.window.setMode(1200, 800)
end
function TestUnitsSystem:testResizePercentageUnits()
-- Test percentage units during parent resize
local parent = Gui.new({
id = "parent",
width = 400,
height = 300,
})
local child = Gui.new({
id = "child",
width = "75%",
height = "50%",
parent = parent,
})
luaunit.assertEquals(child.width, 300) -- 75% of 400
luaunit.assertEquals(child.height, 150) -- 50% of 300
-- Resize parent
parent.width = 600
parent.height = 500
child:resize(1200, 800)
luaunit.assertEquals(child.width, 450) -- 75% of 600
luaunit.assertEquals(child.height, 250) -- 50% of 500
end
function TestUnitsSystem:testResizePixelUnitsNoChange()
-- Test that pixel units don't change during resize
local container = Gui.new({
id = "container",
width = "300px",
height = "200px",
})
luaunit.assertEquals(container.width, 300)
luaunit.assertEquals(container.height, 200)
-- Resize viewport - pixel values should stay the same
container:resize(1600, 1000)
luaunit.assertEquals(container.width, 300)
luaunit.assertEquals(container.height, 200)
end
-- ============================================
-- Spacing (Padding/Margin) Units Tests
-- ============================================
function TestUnitsSystem:testPaddingUnits()
-- Test different unit types for padding
local container = Gui.new({
id = "container",
width = 400,
height = 300,
padding = {
top = "10px",
right = "5%",
bottom = "2vh",
left = "1vw",
},
})
luaunit.assertEquals(container.padding.top, 10) -- 10px
luaunit.assertEquals(container.padding.right, 20) -- 5% of 400
luaunit.assertEquals(container.padding.bottom, 16) -- 2% of 800
luaunit.assertEquals(container.padding.left, 12) -- 1% of 1200
luaunit.assertEquals(container.units.padding.top.unit, "px")
luaunit.assertEquals(container.units.padding.right.unit, "%")
luaunit.assertEquals(container.units.padding.bottom.unit, "vh")
luaunit.assertEquals(container.units.padding.left.unit, "vw")
end
function TestUnitsSystem:testMarginUnits()
-- Test different unit types for margin
local container = Gui.new({
id = "container",
width = 400,
height = 300,
margin = {
top = "8px",
right = "3%",
bottom = "1vh",
left = "2vw",
},
})
luaunit.assertEquals(container.margin.top, 8) -- 8px
luaunit.assertEquals(container.margin.right, 36) -- 3% of viewport width (1200) - CSS spec: % margins relative to containing block
luaunit.assertEquals(container.margin.bottom, 8) -- 1% of 800
luaunit.assertEquals(container.margin.left, 24) -- 2% of 1200
luaunit.assertEquals(container.units.margin.top.unit, "px")
luaunit.assertEquals(container.units.margin.right.unit, "%")
luaunit.assertEquals(container.units.margin.bottom.unit, "vh")
luaunit.assertEquals(container.units.margin.left.unit, "vw")
end
-- ============================================
-- Gap and TextSize Units Tests
-- ============================================
function TestUnitsSystem:testGapUnits()
-- Test gap with different unit types
local flexContainer = Gui.new({
id = "flexContainer",
positioning = Positioning.FLEX,
flexDirection = FlexDirection.HORIZONTAL,
width = 600,
height = 400,
gap = "2%", -- 2% of container width
})
luaunit.assertEquals(flexContainer.gap, 12) -- 2% of 600
luaunit.assertEquals(flexContainer.units.gap.unit, "%")
luaunit.assertEquals(flexContainer.units.gap.value, 2)
-- Test with viewport units
local viewportGapContainer = Gui.new({
id = "viewportGapContainer",
positioning = Positioning.FLEX,
flexDirection = FlexDirection.VERTICAL,
width = 400,
height = 300,
gap = "1vw",
})
luaunit.assertEquals(viewportGapContainer.gap, 12) -- 1% of 1200 viewport width
luaunit.assertEquals(viewportGapContainer.units.gap.unit, "vw")
end
function TestUnitsSystem:testTextSizeUnits()
-- Test textSize with different units
local textElement = Gui.new({
id = "textElement",
width = 200,
height = 100,
textSize = "16px",
})
luaunit.assertEquals(textElement.textSize, 16)
luaunit.assertEquals(textElement.units.textSize.unit, "px")
luaunit.assertEquals(textElement.units.textSize.value, 16)
-- Test with viewport units
local viewportTextElement = Gui.new({
id = "viewportTextElement",
width = 200,
height = 100,
textSize = "2vw",
})
luaunit.assertEquals(viewportTextElement.textSize, 24) -- 2% of 1200
luaunit.assertEquals(viewportTextElement.units.textSize.unit, "vw")
end
-- ============================================
-- Error Handling and Edge Cases
-- ============================================
function TestUnitsSystem:testInvalidUnits()
-- Test handling of invalid unit specifications (should default to pixels)
local container = Gui.new({
id = "container",
width = "100invalid", -- Should be treated as 100px
height = "50badunit", -- Should be treated as 50px
})
-- Should fallback to pixel values
luaunit.assertEquals(container.width, 100)
luaunit.assertEquals(container.height, 50)
luaunit.assertEquals(container.units.width.unit, "px")
luaunit.assertEquals(container.units.height.unit, "px")
end
function TestUnitsSystem:testZeroAndNegativeValues()
-- Test zero and negative values with units
local container = Gui.new({
id = "container",
width = "0px",
height = "0vh",
x = "-10px",
y = "-5%",
})
luaunit.assertEquals(container.width, 0)
luaunit.assertEquals(container.height, 0)
luaunit.assertEquals(container.x, -10)
luaunit.assertEquals(container.y, -40) -- -5% of 800 viewport height for y positioning
end
function TestUnitsSystem:testVeryLargeValues()
-- Test very large percentage values
local container = Gui.new({
id = "container",
width = "200%", -- 200% of viewport
height = "150vh", -- 150% of viewport height
})
luaunit.assertEquals(container.width, 2400) -- 200% of 1200
luaunit.assertEquals(container.height, 1200) -- 150% of 800
end
-- Run the tests
luaunit.LuaUnit.run()

View File

@@ -1,199 +0,0 @@
-- Test relative positioning functionality
package.path = package.path .. ";?.lua"
require("testing.loveStub")
local FlexLove = require("FlexLove")
local luaunit = require("testing.luaunit")
local Gui, enums = FlexLove.Gui, FlexLove.enums
local Color = FlexLove.Color
local Positioning = enums.Positioning
TestRelativePositioning = {}
-- Test 1: Basic relative positioning with pixel values
function TestRelativePositioning.testBasicRelativePositioning()
local parent = Gui.new({
x = 100,
y = 50,
width = 200,
height = 150,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0),
})
local child = Gui.new({
parent = parent,
x = 20,
y = 30,
width = 50,
height = 40,
positioning = "relative",
backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0),
})
-- Child should be positioned relative to parent
luaunit.assertEquals(child.positioning, Positioning.RELATIVE)
luaunit.assertEquals(child.x, 120) -- parent.x (100) + offset (20)
luaunit.assertEquals(child.y, 80) -- parent.y (50) + offset (30)
end
-- Test 2: Relative positioning with percentage values
function TestRelativePositioning.testRelativePositioningPercentages()
local parent = Gui.new({
x = 50,
y = 100,
width = 200,
height = 100,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0),
})
local child = Gui.new({
parent = parent,
x = "10%", -- 10% of parent width = 20px
y = "20%", -- 20% of parent height = 20px
width = 30,
height = 20,
positioning = "relative",
backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0),
})
-- Child should be positioned relative to parent with percentage offsets
luaunit.assertEquals(child.positioning, Positioning.RELATIVE)
luaunit.assertEquals(child.x, 70.0) -- parent.x (50) + 10% of width (20)
luaunit.assertEquals(child.y, 120.0) -- parent.y (100) + 20% of height (20)
end
-- Test 3: Relative positioning with no offset (default to parent position)
function TestRelativePositioning.testRelativePositioningNoOffset()
local parent = Gui.new({
x = 75,
y = 125,
width = 150,
height = 200,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0),
})
local child = Gui.new({
parent = parent,
width = 40,
height = 30,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.8, 0.2, 1.0),
})
-- Child should be positioned at parent's position with no offset
luaunit.assertEquals(child.positioning, Positioning.RELATIVE)
luaunit.assertEquals(child.x, 75) -- same as parent.x
luaunit.assertEquals(child.y, 125) -- same as parent.y
end
-- Test 4: Multiple relative positioned children
function TestRelativePositioning.testMultipleRelativeChildren()
local parent = Gui.new({
x = 200,
y = 300,
width = 100,
height = 100,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0),
})
local child1 = Gui.new({
parent = parent,
x = 10,
y = 15,
width = 20,
height = 20,
positioning = "relative",
backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0),
})
local child2 = Gui.new({
parent = parent,
x = 30,
y = 45,
width = 25,
height = 25,
positioning = "relative",
backgroundColor = Color.new(0.2, 0.8, 0.2, 1.0),
})
-- Both children should be positioned relative to parent
luaunit.assertEquals(child1.x, 210) -- parent.x (200) + offset (10)
luaunit.assertEquals(child1.y, 315) -- parent.y (300) + offset (15)
luaunit.assertEquals(child2.x, 230) -- parent.x (200) + offset (30)
luaunit.assertEquals(child2.y, 345) -- parent.y (300) + offset (45)
end
-- Test 5: Nested relative positioning
function TestRelativePositioning.testNestedRelativePositioning()
local grandparent = Gui.new({
x = 50,
y = 60,
width = 300,
height = 250,
positioning = "relative",
backgroundColor = Color.new(0.1, 0.1, 0.1, 1.0),
})
local parent = Gui.new({
parent = grandparent,
x = 25,
y = 35,
width = 200,
height = 150,
positioning = "relative",
backgroundColor = Color.new(0.3, 0.3, 0.3, 1.0),
})
local child = Gui.new({
parent = parent,
x = 15,
y = 20,
width = 50,
height = 40,
positioning = "relative",
backgroundColor = Color.new(0.8, 0.8, 0.8, 1.0),
})
-- Each level should be positioned relative to its parent
luaunit.assertEquals(parent.x, 75) -- grandparent.x (50) + offset (25)
luaunit.assertEquals(parent.y, 95) -- grandparent.y (60) + offset (35)
luaunit.assertEquals(child.x, 90) -- parent.x (75) + offset (15)
luaunit.assertEquals(child.y, 115) -- parent.y (95) + offset (20)
end
-- Test 6: Mixed positioning types (relative child in absolute parent)
function TestRelativePositioning.testMixedPositioning()
local parent = Gui.new({
x = 100,
y = 200,
width = 180,
height = 120,
positioning = "absolute",
backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0),
})
local child = Gui.new({
parent = parent,
x = 40,
y = 25,
width = 60,
height = 35,
positioning = "relative",
backgroundColor = Color.new(0.8, 0.8, 0.2, 1.0),
})
-- Relative child should still be positioned relative to absolute parent
luaunit.assertEquals(parent.positioning, Positioning.ABSOLUTE)
luaunit.assertEquals(child.positioning, Positioning.RELATIVE)
luaunit.assertEquals(child.x, 140) -- parent.x (100) + offset (40)
luaunit.assertEquals(child.y, 225) -- parent.y (200) + offset (25)
end
-- Run all tests
luaunit.LuaUnit.run()

View File

@@ -1,503 +0,0 @@
-- Test file for comprehensive text scaling functionality
-- This tests all text scaling scenarios including edge cases and multiple resize events
package.path = package.path .. ";?.lua"
local luaunit = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
-- Test suite for comprehensive text scaling
TestTextScaling = {}
function TestTextScaling:setUp()
-- Reset viewport to default before each test
love.window.setMode(800, 600)
Gui.destroy()
end
function TestTextScaling:tearDown()
Gui.destroy()
end
-- Basic functionality tests
function TestTextScaling.testFixedTextSize()
-- Create an element with fixed textSize in pixels (auto-scaling disabled)
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = 16, -- Fixed size in pixels
autoScaleText = false, -- Disable auto-scaling for truly fixed size
text = "Hello World",
})
-- Check initial state
luaunit.assertEquals(element.textSize, 16)
luaunit.assertEquals(element.units.textSize.unit, "px")
-- Simulate multiple resizes
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 16) -- Should remain unchanged
element:resize(400, 300)
luaunit.assertEquals(element.textSize, 16) -- Should remain unchanged
end
function TestTextScaling.testPercentageTextSize()
-- Create an element with percentage textSize (relative to viewport height)
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "5%", -- Percentage of viewport height
text = "Hello World",
})
-- Check initial state (5% of 600px = 30px)
luaunit.assertEquals(element.units.textSize.unit, "%")
luaunit.assertEquals(element.units.textSize.value, 5)
luaunit.assertEquals(element.textSize, 30.0)
-- Simulate resize to larger viewport (5% of 1200px = 60px)
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 60.0)
-- Simulate resize to smaller viewport (5% of 300px = 15px)
element:resize(400, 300)
luaunit.assertEquals(element.textSize, 15.0)
end
function TestTextScaling.testVwTextSize()
-- Create an element with vw textSize
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "2vw", -- 2% of viewport width
text = "Hello World",
})
-- Check initial state (2% of 800px = 16px)
luaunit.assertEquals(element.units.textSize.unit, "vw")
luaunit.assertEquals(element.units.textSize.value, 2)
luaunit.assertEquals(element.textSize, 16.0)
-- Simulate resize to larger viewport (2% of 1600px = 32px)
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 32.0)
-- Simulate resize to smaller viewport (2% of 400px = 8px)
element:resize(400, 300)
luaunit.assertEquals(element.textSize, 8.0)
end
function TestTextScaling.testVhTextSize()
-- Create an element with vh textSize
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "3vh", -- 3% of viewport height
text = "Hello World",
})
-- Check initial state (3% of 600px = 18px)
luaunit.assertEquals(element.units.textSize.unit, "vh")
luaunit.assertEquals(element.units.textSize.value, 3)
luaunit.assertEquals(element.textSize, 18.0)
-- Simulate resize to larger viewport (3% of 1200px = 36px)
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 36.0)
-- Simulate resize to smaller viewport (3% of 300px = 9px)
element:resize(400, 300)
luaunit.assertEquals(element.textSize, 9.0)
end
function TestTextScaling.testNoTextSize()
-- Create an element without textSize specified (auto-scaling enabled by default)
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
text = "Hello World",
})
-- Check initial state - should auto-scale by default (1.5vh)
luaunit.assertEquals(element.autoScaleText, true)
luaunit.assertEquals(element.units.textSize.value, 1.5)
luaunit.assertEquals(element.units.textSize.unit, "vh")
luaunit.assertEquals(element.textSize, 9.0) -- 1.5% of 600px
-- Resize should scale the text
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 18.0) -- 1.5% of 1200px
-- Test with auto-scaling disabled
local elementNoScale = Gui.new({
id = "testElementNoScale",
width = 100,
height = 50,
text = "Hello World",
autoScaleText = false,
})
luaunit.assertEquals(elementNoScale.autoScaleText, false)
luaunit.assertEquals(elementNoScale.units.textSize.value, nil)
luaunit.assertEquals(elementNoScale.textSize, 12) -- Fixed 12px
-- Resize should not affect textSize when auto-scaling is disabled
elementNoScale:resize(1600, 1200)
luaunit.assertEquals(elementNoScale.textSize, 12)
end
-- Edge case tests
function TestTextScaling.testZeroPercentageTextSize()
-- Create an element with 0% textSize (protected to minimum 1px)
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "0%",
text = "Hello World",
})
luaunit.assertEquals(element.textSize, 1) -- Protected to minimum 1px
-- Should remain at minimum after resize
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 1) -- Protected to minimum 1px
end
function TestTextScaling.testVerySmallTextSize()
-- Create an element with very small textSize (protected to minimum 1px)
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "0.1vh",
text = "Hello World",
})
-- Check initial state (0.1% of 600px = 0.6px, protected to 1px)
luaunit.assertEquals(element.textSize, 1)
-- Should scale proportionally when above minimum
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 1.2) -- 0.1% of 1200px = 1.2px
end
function TestTextScaling.testVeryLargeTextSize()
-- Create an element with very large textSize
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "50vh",
text = "Hello World",
})
-- Check initial state (50% of 600px = 300px)
luaunit.assertEquals(element.textSize, 300.0)
-- Should scale proportionally
element:resize(1600, 1200)
luaunit.assertEquals(element.textSize, 600.0) -- 50% of 1200px = 600px
end
function TestTextScaling.testDecimalUnits()
-- Create an element with decimal units
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "2.5vw",
text = "Hello World",
})
-- Check initial state (2.5% of 800px = 20px)
luaunit.assertEquals(element.textSize, 20.0)
-- Should handle decimal precision
element:resize(1000, 800)
luaunit.assertEquals(element.textSize, 25.0) -- 2.5% of 1000px = 25px
end
-- Multiple resize tests
function TestTextScaling.testMultipleResizes()
-- Create an element and perform multiple resize operations
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "4vh",
text = "Hello World",
})
-- Initial: 4% of 600px = 24px
luaunit.assertEquals(element.textSize, 24.0)
-- First resize: 4% of 800px = 32px
element:resize(1000, 800)
luaunit.assertEquals(element.textSize, 32.0)
-- Second resize: 4% of 400px = 16px
element:resize(500, 400)
luaunit.assertEquals(element.textSize, 16.0)
-- Third resize: 4% of 1000px = 40px
element:resize(1200, 1000)
luaunit.assertEquals(element.textSize, 40.0)
-- Return to original: 4% of 600px = 24px
element:resize(800, 600)
luaunit.assertEquals(element.textSize, 24.0)
end
-- Mixed unit tests
function TestTextScaling.testMixedUnitsInDifferentElements()
-- Create multiple elements with different unit types
local elements = {
Gui.new({ id = "px", textSize = 20, autoScaleText = false, text = "Fixed" }),
Gui.new({ id = "percent", textSize = "5%", text = "Percent" }),
Gui.new({ id = "vw", textSize = "3vw", text = "ViewWidth" }),
Gui.new({ id = "vh", textSize = "4vh", text = "ViewHeight" }),
}
-- Check initial states
luaunit.assertEquals(elements[1].textSize, 20) -- Fixed
luaunit.assertEquals(elements[2].textSize, 30.0) -- 5% of 600px
luaunit.assertEquals(elements[3].textSize, 24.0) -- 3% of 800px
luaunit.assertEquals(elements[4].textSize, 24.0) -- 4% of 600px
-- Resize all elements
for _, element in ipairs(elements) do
element:resize(1200, 900)
end
-- Check after resize
luaunit.assertEquals(elements[1].textSize, 20) -- Fixed (unchanged)
luaunit.assertEquals(elements[2].textSize, 45.0) -- 5% of 900px
luaunit.assertEquals(elements[3].textSize, 36.0) -- 3% of 1200px
luaunit.assertEquals(elements[4].textSize, 36.0) -- 4% of 900px
end
-- Test invalid units handling
function TestTextScaling.testInvalidUnits()
-- Test that invalid units are handled gracefully
local success, err = pcall(function()
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "5invalidunit",
text = "Hello World",
})
end)
-- Should handle invalid units gracefully (might error or default)
-- The exact behavior depends on implementation, but shouldn't crash
luaunit.assertTrue(success or string.find(tostring(err), "Unknown unit"))
end
-- Performance test for many resizes
function TestTextScaling.testPerformanceWithManyResizes()
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "2vh",
text = "Hello World",
})
-- Perform many resize operations
local startTime = os.clock()
for i = 1, 100 do
local width = 800 + (i * 2)
local height = 600 + (i * 2)
element:resize(width, height)
-- Verify the calculation is still correct
local expected = (2 / 100) * height
luaunit.assertEquals(element.textSize, expected)
end
local endTime = os.clock()
-- Should complete in reasonable time (less than 1 second for 100 resizes)
local duration = endTime - startTime
luaunit.assertTrue(duration < 1.0, "Performance test took too long: " .. duration .. " seconds")
end
-- Element-relative unit tests
function TestTextScaling.testElementWidthUnits()
-- Create an element with textSize relative to element width
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = "10ew", -- 10% of element width
text = "Hello World",
})
-- Check initial state (10% of 200px = 20px)
luaunit.assertEquals(element.units.textSize.unit, "ew")
luaunit.assertEquals(element.units.textSize.value, 10)
luaunit.assertEquals(element.textSize, 20.0)
luaunit.assertEquals(element.width, 200)
-- Change element width and recalculate
element.width = 300
element:resize(800, 600)
luaunit.assertEquals(element.textSize, 30.0) -- 10% of 300px = 30px
end
function TestTextScaling.testElementHeightUnits()
-- Create an element with textSize relative to element height
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = "15eh", -- 15% of element height
text = "Hello World",
})
-- Check initial state (15% of 100px = 15px)
luaunit.assertEquals(element.units.textSize.unit, "eh")
luaunit.assertEquals(element.units.textSize.value, 15)
luaunit.assertEquals(element.textSize, 15.0)
luaunit.assertEquals(element.height, 100)
-- Change element height and recalculate
element.height = 200
element:resize(800, 600)
luaunit.assertEquals(element.textSize, 30.0) -- 15% of 200px = 30px
end
function TestTextScaling.testElementRelativeWithViewportUnits()
-- Create an element with viewport-based size and element-relative textSize
local element = Gui.new({
id = "testElement",
width = "25%", -- 25% of viewport width = 200px (800px * 0.25)
height = "20%", -- 20% of viewport height = 120px (600px * 0.20)
textSize = "8ew", -- 8% of element width
text = "Hello World",
})
-- Check initial state
luaunit.assertEquals(element.width, 200.0) -- 25% of 800px
luaunit.assertEquals(element.height, 120.0) -- 20% of 600px
luaunit.assertEquals(element.textSize, 16.0) -- 8% of 200px
-- Resize viewport
element:resize(1600, 1200)
-- Element size should update with viewport, textSize should update with element size
luaunit.assertEquals(element.width, 400.0) -- 25% of 1600px
luaunit.assertEquals(element.height, 240.0) -- 20% of 1200px
luaunit.assertEquals(element.textSize, 32.0) -- 8% of 400px
end
-- Min/Max constraint tests
function TestTextScaling.testMinTextSizeConstraint()
-- Create element with textSize that would be smaller than minimum
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = "2vh", -- 2% of 600px = 12px
minTextSize = 16, -- Minimum 16px
text = "Hello World",
})
-- Should be clamped to minimum
luaunit.assertEquals(element.textSize, 16)
-- Test with very small viewport
element:resize(400, 300) -- 2% of 300px = 6px, should stay at 16px
luaunit.assertEquals(element.textSize, 16)
end
function TestTextScaling.testMaxTextSizeConstraint()
-- Create element with textSize that would be larger than maximum
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = "4vh", -- 4% of 600px = 24px
maxTextSize = 20, -- Maximum 20px
text = "Hello World",
})
-- Should be clamped to maximum
luaunit.assertEquals(element.textSize, 20)
-- Test with very large viewport
element:resize(1600, 1200) -- 4% of 1200px = 48px, should stay at 20px
luaunit.assertEquals(element.textSize, 20)
end
function TestTextScaling.testBothMinMaxConstraints()
-- Create element with both min and max constraints
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = "3vh", -- 3% of 600px = 18px (within bounds)
minTextSize = 12,
maxTextSize = 24,
text = "Hello World",
})
-- Should be within bounds
luaunit.assertEquals(element.textSize, 18.0)
-- Test small viewport (should hit min)
element:resize(400, 300) -- 3% of 300px = 9px, should be clamped to 12px
luaunit.assertEquals(element.textSize, 12)
-- Test large viewport (should hit max)
element:resize(1600, 1200) -- 3% of 1200px = 36px, should be clamped to 24px
luaunit.assertEquals(element.textSize, 24)
end
function TestTextScaling.testConstraintsWithElementUnits()
-- Test constraints with element-relative units
local element = Gui.new({
id = "testElement",
width = 100,
height = 50,
textSize = "20ew", -- 20% of 100px = 20px
minTextSize = 8,
maxTextSize = 15,
text = "Hello World",
})
-- Should be clamped to maximum
luaunit.assertEquals(element.textSize, 15)
-- Change width to trigger minimum
element.width = 30 -- 20% of 30px = 6px, should be clamped to 8px
element:resize(800, 600)
luaunit.assertEquals(element.textSize, 8)
end
function TestTextScaling.testConstraintsWithFixedTextSize()
-- Test that constraints work with fixed pixel textSize too
local element = Gui.new({
id = "testElement",
width = 200,
height = 100,
textSize = 25, -- Fixed 25px
minTextSize = 12,
maxTextSize = 20,
text = "Hello World",
})
-- Should be clamped to maximum even for fixed sizes
luaunit.assertEquals(element.textSize, 20)
end
luaunit.LuaUnit.run()

View File

@@ -1,344 +0,0 @@
-- Grid Layout Tests
-- Tests for simplified grid layout functionality
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local enums = FlexLove.enums
TestGridLayout = {}
function TestGridLayout:setUp()
-- Reset GUI before each test
Gui.destroy()
Gui.init({})
end
function TestGridLayout:tearDown()
Gui.destroy()
end
-- ====================
-- Basic Grid Layout Tests
-- ====================
function TestGridLayout:test_simple_grid_creation()
local grid = Gui.new({
x = 0,
y = 0,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 3,
})
lu.assertEquals(grid.positioning, enums.Positioning.GRID)
lu.assertEquals(grid.gridRows, 2)
lu.assertEquals(grid.gridColumns, 3)
end
function TestGridLayout:test_grid_with_gaps()
local grid = Gui.new({
x = 0,
y = 0,
width = 600,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 10,
rowGap = 20,
})
lu.assertEquals(grid.columnGap, 10)
lu.assertEquals(grid.rowGap, 20)
end
function TestGridLayout:test_grid_auto_placement()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 3,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
-- Add 6 items that should auto-place in a 3x2 grid
local items = {}
for i = 1, 6 do
items[i] = Gui.new({
parent = grid,
width = 50,
height = 50,
})
end
-- Check first item (top-left)
lu.assertAlmostEquals(items[1].x, 0, 1)
lu.assertAlmostEquals(items[1].y, 0, 1)
-- Check second item (top-middle)
lu.assertAlmostEquals(items[2].x, 100, 1)
lu.assertAlmostEquals(items[2].y, 0, 1)
-- Check fourth item (bottom-left)
lu.assertAlmostEquals(items[4].x, 0, 1)
lu.assertAlmostEquals(items[4].y, 100, 1)
end
function TestGridLayout:test_grid_equal_distribution()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item1 = Gui.new({
parent = grid,
width = 50,
height = 50,
})
local item2 = Gui.new({
parent = grid,
width = 50,
height = 50,
})
-- Each cell should be 150x100 (300/2 x 200/2)
lu.assertAlmostEquals(item1.width, 150, 1)
lu.assertAlmostEquals(item1.height, 100, 1)
lu.assertAlmostEquals(item2.x, 150, 1)
lu.assertAlmostEquals(item2.width, 150, 1)
end
function TestGridLayout:test_grid_stretch_behavior()
local grid = Gui.new({
x = 0,
y = 0,
width = 400,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 3,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- Each cell should be ~133.33px wide (400/3)
-- Items should stretch to fill cells
lu.assertAlmostEquals(item1.width, 133.33, 1)
lu.assertAlmostEquals(item2.width, 133.33, 1)
lu.assertAlmostEquals(item3.width, 133.33, 1)
end
-- ====================
-- Alignment Tests
-- ====================
function TestGridLayout:test_align_items_stretch()
local grid = Gui.new({
x = 0,
y = 0,
width = 300,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 1,
alignItems = enums.AlignItems.STRETCH,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item = Gui.new({
parent = grid,
width = 50,
})
-- Item should stretch to fill cell height (200/2 = 100)
lu.assertAlmostEquals(item.height, 100, 1)
end
-- ====================
-- Gap Tests
-- ====================
function TestGridLayout:test_column_gap()
local grid = Gui.new({
x = 0,
y = 0,
width = 320,
height = 100,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 3,
columnGap = 10,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- Total width: 320, gaps: 2*10=20, available: 300, per cell: 100
lu.assertAlmostEquals(item1.x, 0, 1)
lu.assertAlmostEquals(item2.x, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.x, 220, 1) -- 100 + 10 + 100 + 10
end
function TestGridLayout:test_row_gap()
local grid = Gui.new({
x = 0,
y = 0,
width = 100,
height = 320,
positioning = enums.Positioning.GRID,
gridRows = 3,
gridColumns = 1,
columnGap = 0,
rowGap = 10,
padding = { horizontal = 0, vertical = 0 },
})
local item1 = Gui.new({ parent = grid, width = 50, height = 50 })
local item2 = Gui.new({ parent = grid, width = 50, height = 50 })
local item3 = Gui.new({ parent = grid, width = 50, height = 50 })
-- Total height: 320, gaps: 2*10=20, available: 300, per cell: 100
lu.assertAlmostEquals(item1.y, 0, 1)
lu.assertAlmostEquals(item2.y, 110, 1) -- 100 + 10 gap
lu.assertAlmostEquals(item3.y, 220, 1) -- 100 + 10 + 100 + 10
end
-- ====================
-- Nested Grid Tests
-- ====================
function TestGridLayout:test_nested_grids()
local outerGrid = Gui.new({
x = 0,
y = 0,
width = 400,
height = 400,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local innerGrid = Gui.new({
parent = outerGrid,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
-- Add items to inner grid
local item1 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
local item2 = Gui.new({ parent = innerGrid, width = 50, height = 50 })
-- Inner grid should be stretched to fill outer grid cell (200x200)
lu.assertAlmostEquals(innerGrid.width, 200, 1)
lu.assertAlmostEquals(innerGrid.height, 200, 1)
-- Items in inner grid should be positioned correctly
-- Each cell in inner grid is 100x100
lu.assertAlmostEquals(item1.x, 0, 1)
lu.assertAlmostEquals(item1.y, 0, 1)
lu.assertAlmostEquals(item2.x, 100, 1)
lu.assertAlmostEquals(item2.y, 0, 1)
end
-- ====================
-- Edge Cases
-- ====================
function TestGridLayout:test_more_items_than_cells()
local grid = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
positioning = enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local items = {}
for i = 1, 6 do
items[i] = Gui.new({
parent = grid,
width = 50,
height = 50,
})
end
-- First 4 items should be positioned
lu.assertAlmostEquals(items[1].x, 0, 1)
lu.assertAlmostEquals(items[4].x, 100, 1)
lu.assertAlmostEquals(items[4].y, 100, 1)
-- Items 5 and 6 should not be laid out (remain at parent position)
-- This is acceptable behavior - they're just not visible in the grid
end
function TestGridLayout:test_single_cell_grid()
local grid = Gui.new({
x = 0,
y = 0,
width = 100,
height = 100,
positioning = enums.Positioning.GRID,
gridRows = 1,
gridColumns = 1,
columnGap = 0,
rowGap = 0,
padding = { horizontal = 0, vertical = 0 },
})
local item = Gui.new({
parent = grid,
width = 50,
height = 50,
})
-- Item should stretch to fill the entire grid
lu.assertAlmostEquals(item.x, 0, 1)
lu.assertAlmostEquals(item.y, 0, 1)
lu.assertAlmostEquals(item.width, 100, 1)
lu.assertAlmostEquals(item.height, 100, 1)
end
print("Running Simplified Grid Layout Tests...")
lu.LuaUnit.run()

View File

@@ -1,369 +0,0 @@
-- Event System Tests
-- Tests for the enhanced callback system with InputEvent objects
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
TestEventSystem = {}
function TestEventSystem:setUp()
-- Clear all keyboard modifier states at start of each test
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
Gui.init({ baseScale = { width = 1920, height = 1080 } })
love.window.setMode(1920, 1080)
Gui.resize(1920, 1080) -- Recalculate scale factors after setMode
end
function TestEventSystem:tearDown()
-- Clean up after each test
Gui.destroy()
-- Reset keyboard state
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
end
-- Test 1: Event object structure
function TestEventSystem:test_event_object_has_required_fields()
local eventReceived = nil
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
eventReceived = event
end,
})
-- Simulate mouse press and release
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
-- Verify event object structure
lu.assertNotNil(eventReceived, "Event should be received")
lu.assertNotNil(eventReceived.type, "Event should have type field")
lu.assertNotNil(eventReceived.button, "Event should have button field")
lu.assertNotNil(eventReceived.x, "Event should have x field")
lu.assertNotNil(eventReceived.y, "Event should have y field")
lu.assertNotNil(eventReceived.modifiers, "Event should have modifiers field")
lu.assertNotNil(eventReceived.clickCount, "Event should have clickCount field")
lu.assertNotNil(eventReceived.timestamp, "Event should have timestamp field")
end
-- Test 2: Left click event
function TestEventSystem:test_left_click_generates_click_event()
local eventsReceived = {}
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
table.insert(eventsReceived, { type = event.type, button = event.button })
end,
})
-- Simulate left click
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
-- Should receive press, click, and release events
lu.assertTrue(#eventsReceived >= 2, "Should receive at least 2 events")
-- Check for click event
local hasClickEvent = false
for _, evt in ipairs(eventsReceived) do
if evt.type == "click" and evt.button == 1 then
hasClickEvent = true
break
end
end
lu.assertTrue(hasClickEvent, "Should receive click event for left button")
end
-- Test 3: Right click event
function TestEventSystem:test_right_click_generates_rightclick_event()
local eventsReceived = {}
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
table.insert(eventsReceived, { type = event.type, button = event.button })
end,
})
-- Simulate right click
love.mouse.setPosition(150, 150)
love.mouse.setDown(2, true)
button:update(0.016)
love.mouse.setDown(2, false)
button:update(0.016)
-- Check for rightclick event
local hasRightClickEvent = false
for _, evt in ipairs(eventsReceived) do
if evt.type == "rightclick" and evt.button == 2 then
hasRightClickEvent = true
break
end
end
lu.assertTrue(hasRightClickEvent, "Should receive rightclick event for right button")
end
-- Test 4: Middle click event
function TestEventSystem:test_middle_click_generates_middleclick_event()
local eventsReceived = {}
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
table.insert(eventsReceived, { type = event.type, button = event.button })
end,
})
-- Simulate middle click
love.mouse.setPosition(150, 150)
love.mouse.setDown(3, true)
button:update(0.016)
love.mouse.setDown(3, false)
button:update(0.016)
-- Check for middleclick event
local hasMiddleClickEvent = false
for _, evt in ipairs(eventsReceived) do
if evt.type == "middleclick" and evt.button == 3 then
hasMiddleClickEvent = true
break
end
end
lu.assertTrue(hasMiddleClickEvent, "Should receive middleclick event for middle button")
end
-- Test 5: Modifier keys detection
function TestEventSystem:test_modifier_keys_are_detected()
local eventReceived = nil
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
if event.type == "click" then
eventReceived = event
end
end,
})
-- Simulate shift + click
love.keyboard.setDown("lshift", true)
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
lu.assertNotNil(eventReceived, "Should receive click event")
lu.assertTrue(eventReceived.modifiers.shift, "Shift modifier should be detected")
end
-- Test 6: Double click detection
function TestEventSystem:test_double_click_increments_click_count()
local clickEvents = {}
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
if event.type == "click" then
table.insert(clickEvents, event.clickCount)
end
end,
})
-- Simulate first click
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
-- Simulate second click quickly (double-click)
love.timer.setTime(love.timer.getTime() + 0.1) -- 100ms later
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
lu.assertEquals(#clickEvents, 2, "Should receive 2 click events")
lu.assertEquals(clickEvents[1], 1, "First click should have clickCount = 1")
lu.assertEquals(clickEvents[2], 2, "Second click should have clickCount = 2")
end
-- Test 7: Press and release events
function TestEventSystem:test_press_and_release_events_are_fired()
local eventsReceived = {}
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
table.insert(eventsReceived, event.type)
end,
})
-- Simulate click
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
-- Should receive press, click, and release
lu.assertTrue(#eventsReceived >= 2, "Should receive multiple events")
local hasPress = false
local hasRelease = false
for _, eventType in ipairs(eventsReceived) do
if eventType == "press" then
hasPress = true
end
if eventType == "release" then
hasRelease = true
end
end
lu.assertTrue(hasPress, "Should receive press event")
lu.assertTrue(hasRelease, "Should receive release event")
end
-- Test 8: Mouse position in event
function TestEventSystem:test_event_contains_mouse_position()
local eventReceived = nil
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
if event.type == "click" then
eventReceived = event
end
end,
})
-- Simulate click at specific position
local mouseX, mouseY = 175, 125
love.mouse.setPosition(mouseX, mouseY)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
lu.assertNotNil(eventReceived, "Should receive click event")
lu.assertEquals(eventReceived.x, mouseX, "Event should contain correct mouse X position")
lu.assertEquals(eventReceived.y, mouseY, "Event should contain correct mouse Y position")
end
-- Test 9: No callback when mouse outside element
function TestEventSystem:test_no_callback_when_clicking_outside_element()
local callbackCalled = false
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
callbackCalled = true
end,
})
-- Click outside element
love.mouse.setPosition(50, 50)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
lu.assertFalse(callbackCalled, "Callback should not be called when clicking outside element")
end
-- Test 10: Multiple modifiers
function TestEventSystem:test_multiple_modifiers_detected()
local eventReceived = nil
local button = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(element, event)
if event.type == "click" then
eventReceived = event
end
end,
})
-- Simulate shift + ctrl + click
love.keyboard.setDown("lshift", true)
love.keyboard.setDown("lctrl", true)
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
button:update(0.016)
love.mouse.setDown(1, false)
button:update(0.016)
lu.assertNotNil(eventReceived, "Should receive click event")
lu.assertTrue(eventReceived.modifiers.shift, "Shift modifier should be detected")
lu.assertTrue(eventReceived.modifiers.ctrl, "Ctrl modifier should be detected")
end
print("Running Event System Tests...")
lu.LuaUnit.run()

View File

@@ -1,437 +0,0 @@
-- Test: Sibling Space Reservation in Flex and Grid Layouts
-- Purpose: Verify that absolutely positioned siblings with explicit positioning
-- properly reserve space in flex and grid containers
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local Color = FlexLove.Color
TestSiblingSpaceReservation = {}
function TestSiblingSpaceReservation:setUp()
-- Reset GUI state before each test
Gui.destroy()
-- Set up a standard viewport
love.window.setMode(1920, 1080)
end
function TestSiblingSpaceReservation:tearDown()
Gui.destroy()
end
-- ====================
-- Flex Layout Tests
-- ====================
function TestSiblingSpaceReservation:test_flex_horizontal_left_positioned_sibling_reserves_space()
-- Create a flex container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "flex-start",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add an absolutely positioned sibling with left positioning
local absoluteSibling = Gui.new({
parent = container,
positioning = "absolute",
left = 10, -- 10px from left edge
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Add a flex child that should start after the absolutely positioned sibling
local flexChild = Gui.new({
parent = container,
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- The absolutely positioned sibling reserves: left (10) + width (50) + padding (0) = 60px
-- The flex child should start at x = container.x + padding.left + reservedLeft
-- = 0 + 0 + 60 = 60
lu.assertEquals(flexChild.x, 60, "Flex child should start after absolutely positioned sibling")
end
function TestSiblingSpaceReservation:test_flex_horizontal_right_positioned_sibling_reserves_space()
-- Create a flex container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "flex-start",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add an absolutely positioned sibling with right positioning
local absoluteSibling = Gui.new({
parent = container,
positioning = "absolute",
right = 10, -- 10px from right edge
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Add a flex child
local flexChild = Gui.new({
parent = container,
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- The absolutely positioned sibling reserves: right (10) + width (50) + padding (0) = 60px
-- Available space = 1000 - 0 (padding) - 0 (reservedLeft) - 60 (reservedRight) = 940px
-- The flex child (width 100) should fit within this space
-- Child should start at x = 0
lu.assertEquals(flexChild.x, 0, "Flex child should start at container left edge")
-- The absolutely positioned sibling should be at the right edge
-- x = container.x + container.width + padding.left - right - (width + padding)
-- = 0 + 1000 + 0 - 10 - 50 = 940
lu.assertEquals(absoluteSibling.x, 940, "Absolutely positioned sibling should be at right edge")
end
function TestSiblingSpaceReservation:test_flex_vertical_top_positioned_sibling_reserves_space()
-- Create a vertical flex container
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 1000,
positioning = "flex",
flexDirection = "vertical",
justifyContent = "flex-start",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add an absolutely positioned sibling with top positioning
local absoluteSibling = Gui.new({
parent = container,
positioning = "absolute",
top = 10, -- 10px from top edge
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Add a flex child that should start after the absolutely positioned sibling
local flexChild = Gui.new({
parent = container,
width = 50,
height = 100,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- The absolutely positioned sibling reserves: top (10) + height (50) + padding (0) = 60px
-- The flex child should start at y = container.y + padding.top + reservedTop
-- = 0 + 0 + 60 = 60
lu.assertEquals(flexChild.y, 60, "Flex child should start after absolutely positioned sibling")
end
function TestSiblingSpaceReservation:test_flex_horizontal_multiple_positioned_siblings()
-- Create a flex container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
justifyContent = "flex-start",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add two absolutely positioned siblings (left and right)
local leftSibling = Gui.new({
parent = container,
positioning = "absolute",
left = 5,
width = 40,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
local rightSibling = Gui.new({
parent = container,
positioning = "absolute",
right = 5,
width = 40,
height = 50,
backgroundColor = Color.new(0, 0, 1, 1),
})
-- Add flex children
local flexChild1 = Gui.new({
parent = container,
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 0, 1),
})
local flexChild2 = Gui.new({
parent = container,
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 1, 1),
})
-- Layout children
container:layoutChildren()
-- Reserved left: 5 + 40 = 45px
-- Reserved right: 5 + 40 = 45px
-- Available space: 1000 - 45 - 45 = 910px
-- First flex child should start at x = 0 + 0 + 45 = 45
lu.assertEquals(flexChild1.x, 45, "First flex child should start after left sibling")
-- Second flex child should start at x = 45 + 100 + gap = 145 (assuming gap=10)
lu.assertIsTrue(flexChild2.x >= 145, "Second flex child should be positioned after first")
end
-- ====================
-- Grid Layout Tests
-- ====================
function TestSiblingSpaceReservation:test_grid_left_positioned_sibling_reserves_space()
-- Create a grid container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 500,
positioning = "grid",
gridRows = 2,
gridColumns = 3,
columnGap = 10,
rowGap = 10,
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add an absolutely positioned sibling with left positioning
local absoluteSibling = Gui.new({
parent = container,
positioning = "absolute",
left = 10,
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Add grid children
local gridChild1 = Gui.new({
parent = container,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- Reserved left: 10 + 50 = 60px
-- Available width: 1000 - 60 = 940px
-- Column gaps: 2 * 10 = 20px
-- Cell width: (940 - 20) / 3 = 306.67px
-- First grid child should start at x = 0 + 0 + 60 = 60
lu.assertEquals(gridChild1.x, 60, "Grid child should start after absolutely positioned sibling")
end
function TestSiblingSpaceReservation:test_grid_top_positioned_sibling_reserves_space()
-- Create a grid container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 500,
positioning = "grid",
gridRows = 2,
gridColumns = 3,
columnGap = 10,
rowGap = 10,
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add an absolutely positioned sibling with top positioning
local absoluteSibling = Gui.new({
parent = container,
positioning = "absolute",
top = 10,
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Add grid children
local gridChild1 = Gui.new({
parent = container,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- Reserved top: 10 + 50 = 60px
-- Available height: 500 - 60 = 440px
-- Row gaps: 1 * 10 = 10px
-- Cell height: (440 - 10) / 2 = 215px
-- First grid child should start at y = 0 + 0 + 60 = 60
lu.assertEquals(gridChild1.y, 60, "Grid child should start after absolutely positioned sibling")
end
function TestSiblingSpaceReservation:test_grid_multiple_positioned_siblings()
-- Create a grid container
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 500,
positioning = "grid",
gridRows = 2,
gridColumns = 2,
columnGap = 0,
rowGap = 0,
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Add absolutely positioned siblings at all corners
local topLeftSibling = Gui.new({
parent = container,
positioning = "absolute",
left = 10,
top = 10,
width = 40,
height = 40,
backgroundColor = Color.new(1, 0, 0, 1),
})
local bottomRightSibling = Gui.new({
parent = container,
positioning = "absolute",
right = 10,
bottom = 10,
width = 40,
height = 40,
backgroundColor = Color.new(0, 0, 1, 1),
})
-- Add grid children
local gridChild1 = Gui.new({
parent = container,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- Reserved left: 10 + 40 = 50px
-- Reserved right: 10 + 40 = 50px
-- Reserved top: 10 + 40 = 50px
-- Reserved bottom: 10 + 40 = 50px
-- Available width: 1000 - 50 - 50 = 900px
-- Available height: 500 - 50 - 50 = 400px
-- Cell width: 900 / 2 = 450px
-- Cell height: 400 / 2 = 200px
-- First grid child should start at (50, 50)
lu.assertEquals(gridChild1.x, 50, "Grid child X should account for left sibling")
lu.assertEquals(gridChild1.y, 50, "Grid child Y should account for top sibling")
lu.assertEquals(gridChild1.width, 450, "Grid cell width should account for reserved space")
lu.assertEquals(gridChild1.height, 200, "Grid cell height should account for reserved space")
end
-- ====================
-- Edge Cases
-- ====================
function TestSiblingSpaceReservation:test_non_explicitly_absolute_children_dont_reserve_space()
-- Children that default to absolute positioning (not explicitly set)
-- should NOT reserve space in flex layouts
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- This child has positioning="flex" so it participates in layout
local flexChild = Gui.new({
parent = container,
positioning = "flex",
left = 10, -- This should be ignored since it's a flex child
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- Flex child should start at x = 0 (no reserved space)
lu.assertEquals(flexChild.x, 0, "Flex children with positioning offsets should not reserve space")
end
function TestSiblingSpaceReservation:test_absolute_without_positioning_offsets_doesnt_reserve_space()
-- Absolutely positioned children without left/right/top/bottom
-- should NOT reserve space
local container = Gui.new({
x = 0,
y = 0,
width = 1000,
height = 200,
positioning = "flex",
flexDirection = "horizontal",
padding = { top = 0, right = 0, bottom = 0, left = 0 },
})
-- Absolutely positioned but no positioning offsets
local absoluteChild = Gui.new({
parent = container,
positioning = "absolute",
x = 50,
y = 50,
width = 50,
height = 50,
backgroundColor = Color.new(1, 0, 0, 1),
})
-- Flex child
local flexChild = Gui.new({
parent = container,
width = 100,
height = 50,
backgroundColor = Color.new(0, 1, 0, 1),
})
-- Layout children
container:layoutChildren()
-- Flex child should start at x = 0 (no reserved space)
lu.assertEquals(flexChild.x, 0, "Absolute children without positioning offsets should not reserve space")
end
print("Running Sibling Space Reservation Tests...")
lu.LuaUnit.run()

View File

@@ -1,225 +0,0 @@
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
TestFontFamilyInheritance = {}
function TestFontFamilyInheritance:setUp()
FlexLove.Gui.destroy()
FlexLove.Gui.init({ baseScale = { width = 1920, height = 1080 } })
end
function TestFontFamilyInheritance:tearDown()
FlexLove.Gui.destroy()
end
function TestFontFamilyInheritance:testBasicInheritanceFromParent()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
fontFamily = "Arial",
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child",
})
lu.assertEquals(child.fontFamily, "Arial", "Child should inherit fontFamily from parent")
end
function TestFontFamilyInheritance:testInheritanceThroughMultipleLevels()
local grandparent = FlexLove.Element.new({
width = 300,
height = 300,
fontFamily = "Times",
})
local parent = FlexLove.Element.new({
parent = grandparent,
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Grandchild",
})
lu.assertEquals(parent.fontFamily, "Times", "Parent should inherit fontFamily from grandparent")
lu.assertEquals(child.fontFamily, "Times", "Child should inherit fontFamily through parent")
end
function TestFontFamilyInheritance:testExplicitOverrideBreaksInheritance()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
fontFamily = "Arial",
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child",
fontFamily = "Helvetica",
})
lu.assertEquals(child.fontFamily, "Helvetica", "Child's explicit fontFamily should override parent's")
end
function TestFontFamilyInheritance:testInheritanceWithNoParentFontFamily()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child",
})
lu.assertNil(child.fontFamily, "Child should have nil fontFamily when parent doesn't have one")
end
function TestFontFamilyInheritance:testInheritanceInFlexContainer()
local flexParent = FlexLove.Element.new({
width = 300,
height = 300,
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL,
fontFamily = "Courier",
})
local child1 = FlexLove.Element.new({
parent = flexParent,
width = 100,
height = 100,
text = "Child 1",
})
local child2 = FlexLove.Element.new({
parent = flexParent,
width = 100,
height = 100,
text = "Child 2",
})
lu.assertEquals(child1.fontFamily, "Courier", "Child 1 should inherit fontFamily in flex container")
lu.assertEquals(child2.fontFamily, "Courier", "Child 2 should inherit fontFamily in flex container")
end
function TestFontFamilyInheritance:testInheritanceInGridContainer()
local gridParent = FlexLove.Element.new({
width = 300,
height = 300,
positioning = FlexLove.enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
fontFamily = "Verdana",
})
local child1 = FlexLove.Element.new({
parent = gridParent,
text = "Cell 1",
})
local child2 = FlexLove.Element.new({
parent = gridParent,
text = "Cell 2",
})
lu.assertEquals(child1.fontFamily, "Verdana", "Child 1 should inherit fontFamily in grid container")
lu.assertEquals(child2.fontFamily, "Verdana", "Child 2 should inherit fontFamily in grid container")
end
function TestFontFamilyInheritance:testMixedInheritanceAndOverride()
local grandparent = FlexLove.Element.new({
width = 400,
height = 400,
fontFamily = "Georgia",
})
local parent = FlexLove.Element.new({
parent = grandparent,
width = 300,
height = 300,
})
local child1 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child 1",
})
local child2 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child 2",
fontFamily = "Impact",
})
lu.assertEquals(parent.fontFamily, "Georgia", "Parent should inherit from grandparent")
lu.assertEquals(child1.fontFamily, "Georgia", "Child 1 should inherit through parent")
lu.assertEquals(child2.fontFamily, "Impact", "Child 2 should use explicit fontFamily")
end
function TestFontFamilyInheritance:testInheritanceWithAbsolutePositioning()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
fontFamily = "Comic Sans",
})
local child = FlexLove.Element.new({
parent = parent,
positioning = FlexLove.enums.Positioning.ABSOLUTE,
x = 50,
y = 50,
width = 100,
height = 100,
text = "Absolute Child",
})
lu.assertEquals(child.fontFamily, "Comic Sans", "Absolutely positioned child should still inherit fontFamily")
end
function TestFontFamilyInheritance:testInheritanceDoesNotAffectSiblings()
local parent = FlexLove.Element.new({
width = 300,
height = 300,
fontFamily = "Tahoma",
})
local child1 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child 1",
fontFamily = "Trebuchet",
})
local child2 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
text = "Child 2",
})
lu.assertEquals(child1.fontFamily, "Trebuchet", "Child 1 should have its own fontFamily")
lu.assertEquals(child2.fontFamily, "Tahoma", "Child 2 should inherit parent's fontFamily")
lu.assertNotEquals(child2.fontFamily, child1.fontFamily, "Siblings should have independent fontFamily values")
end
print("Running Font Family Inheritance Tests...")
lu.LuaUnit.run()

View File

@@ -1,337 +0,0 @@
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
TestNegativeMargin = {}
function TestNegativeMargin:setUp()
FlexLove.Gui.destroy()
-- Don't call init to use 1:1 scaling (like other tests)
end
function TestNegativeMargin:tearDown()
FlexLove.Gui.destroy()
end
function TestNegativeMargin:testBasicNegativeMarginTop()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.VERTICAL,
})
local child1 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
})
local child2 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
margin = { top = -20 },
})
parent:layoutChildren()
lu.assertNotNil(child2.margin.top)
lu.assertEquals(child2.margin.top, -20, "Child2 should have -20 top margin")
end
function TestNegativeMargin:testNegativeMarginLeft()
local parent = FlexLove.Element.new({
width = 300,
height = 100,
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL,
})
local child1 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
})
local child2 = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
margin = { left = -30 },
})
parent:layoutChildren()
lu.assertEquals(child2.margin.left, -30, "Child2 should have -30 left margin")
end
function TestNegativeMargin:testNegativeMarginRight()
local parent = FlexLove.Element.new({
width = 300,
height = 100,
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
margin = { right = -15 },
})
parent:layoutChildren()
lu.assertEquals(child.margin.right, -15, "Child should have -15 right margin")
end
function TestNegativeMargin:testNegativeMarginBottom()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.VERTICAL,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 50,
margin = { bottom = -10 },
})
parent:layoutChildren()
lu.assertEquals(child.margin.bottom, -10, "Child should have -10 bottom margin")
end
function TestNegativeMargin:testMultipleNegativeMargins()
local parent = FlexLove.Element.new({
width = 300,
height = 300,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { top = -20, right = -15, bottom = -10, left = -5 },
})
lu.assertEquals(child.margin.top, -20)
lu.assertEquals(child.margin.right, -15)
lu.assertEquals(child.margin.bottom, -10)
lu.assertEquals(child.margin.left, -5)
end
function TestNegativeMargin:testNegativeMarginWithPercentage()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { left = "-10%" },
})
lu.assertEquals(child.margin.left, -20, "Negative 10% of 200 width should be -20")
end
function TestNegativeMargin:testNegativeMarginWithVwUnit()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { left = "-2vw" },
})
lu.assertNotNil(child.margin.left)
lu.assertTrue(child.margin.left < 0, "Negative vw margin should be negative")
end
function TestNegativeMargin:testNegativeMarginWithVhUnit()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { top = "-2vh" },
})
lu.assertNotNil(child.margin.top)
lu.assertTrue(child.margin.top < 0, "Negative vh margin should be negative")
end
function TestNegativeMargin:testNegativeMarginInGridLayout()
local gridParent = FlexLove.Element.new({
width = 300,
height = 300,
positioning = FlexLove.enums.Positioning.GRID,
gridRows = 2,
gridColumns = 2,
})
local child = FlexLove.Element.new({
parent = gridParent,
width = 100,
height = 100,
margin = { top = -10, left = -10 },
})
lu.assertEquals(child.margin.top, -10)
lu.assertEquals(child.margin.left, -10)
end
function TestNegativeMargin:testNegativeMarginVerticalShorthand()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { vertical = -15 },
})
lu.assertEquals(child.margin.top, -15, "Vertical shorthand should set top to -15")
lu.assertEquals(child.margin.bottom, -15, "Vertical shorthand should set bottom to -15")
end
function TestNegativeMargin:testNegativeMarginHorizontalShorthand()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { horizontal = -20 },
})
lu.assertEquals(child.margin.left, -20, "Horizontal shorthand should set left to -20")
lu.assertEquals(child.margin.right, -20, "Horizontal shorthand should set right to -20")
end
function TestNegativeMargin:testMixedPositiveAndNegativeMargins()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { top = 20, right = -10, bottom = 15, left = -5 },
})
lu.assertEquals(child.margin.top, 20)
lu.assertEquals(child.margin.right, -10)
lu.assertEquals(child.margin.bottom, 15)
lu.assertEquals(child.margin.left, -5)
end
function TestNegativeMargin:testNegativeMarginWithAbsolutePositioning()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
positioning = FlexLove.enums.Positioning.ABSOLUTE,
x = 50,
y = 50,
width = 100,
height = 100,
margin = { top = -10, left = -10 },
})
lu.assertEquals(child.margin.top, -10)
lu.assertEquals(child.margin.left, -10)
end
function TestNegativeMargin:testNegativeMarginDoesNotAffectPadding()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
padding = { top = 10, left = 10 },
margin = { top = -15, left = -15 },
})
lu.assertEquals(child.padding.top, 10, "Padding should not be affected by negative margin")
lu.assertEquals(child.padding.left, 10, "Padding should not be affected by negative margin")
lu.assertEquals(child.margin.top, -15)
lu.assertEquals(child.margin.left, -15)
end
function TestNegativeMargin:testExtremeNegativeMarginValues()
local parent = FlexLove.Element.new({
width = 200,
height = 200,
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { top = -1000, left = -1000 },
})
lu.assertEquals(child.margin.top, -1000, "Extreme negative margin should be allowed")
lu.assertEquals(child.margin.left, -1000, "Extreme negative margin should be allowed")
end
function TestNegativeMargin:testNegativeMarginInNestedElements()
local grandparent = FlexLove.Element.new({
width = 300,
height = 300,
})
local parent = FlexLove.Element.new({
parent = grandparent,
width = 200,
height = 200,
margin = { top = -20, left = -20 },
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
margin = { top = -10, left = -10 },
})
lu.assertEquals(parent.margin.top, -20)
lu.assertEquals(parent.margin.left, -20)
lu.assertEquals(child.margin.top, -10)
lu.assertEquals(child.margin.left, -10)
end
print("Running Negative Margin Tests...")
lu.LuaUnit.run()

View File

@@ -1,243 +0,0 @@
-- Test padding resize behavior with percentage units
package.path = package.path .. ";?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
_G.love = loveStub
local FlexLove = require("FlexLove")
TestPaddingResize = {}
function TestPaddingResize:setUp()
-- Reset GUI state before each test
FlexLove.Gui.destroy()
-- Set up a consistent viewport size
love.window.setMode(1920, 1080)
-- Initialize with base scaling
FlexLove.Gui.init({
baseScale = { width = 1920, height = 1080 },
})
end
function TestPaddingResize:tearDown()
FlexLove.Gui.destroy()
end
-- Test that percentage padding recalculates on resize
function TestPaddingResize:testPercentagePaddingHorizontalResize()
-- Create parent with percentage padding
local parent = FlexLove.Element.new({
x = 0,
y = 0,
width = "100%",
height = "100%",
padding = { horizontal = "10%", vertical = "5%" },
})
-- Initial padding should be 10% of 1920 = 192px (horizontal), 5% of 1080 = 54px (vertical)
luaunit.assertAlmostEquals(parent.padding.left, 192, 1, "Initial left padding should be 10% of 1920")
luaunit.assertAlmostEquals(parent.padding.right, 192, 1, "Initial right padding should be 10% of 1920")
luaunit.assertAlmostEquals(parent.padding.top, 54, 1, "Initial top padding should be 5% of 1080")
luaunit.assertAlmostEquals(parent.padding.bottom, 54, 1, "Initial bottom padding should be 5% of 1080")
-- Resize to larger viewport
parent:resize(2560, 1440)
-- Padding should recalculate: 10% of 2560 = 256px (horizontal), 5% of 1440 = 72px (vertical)
luaunit.assertAlmostEquals(parent.padding.left, 256, 1, "After resize, left padding should be 10% of 2560")
luaunit.assertAlmostEquals(parent.padding.right, 256, 1, "After resize, right padding should be 10% of 2560")
luaunit.assertAlmostEquals(parent.padding.top, 72, 1, "After resize, top padding should be 5% of 1440")
luaunit.assertAlmostEquals(parent.padding.bottom, 72, 1, "After resize, bottom padding should be 5% of 1440")
-- Resize to smaller viewport
parent:resize(1280, 720)
-- Padding should recalculate: 10% of 1280 = 128px (horizontal), 5% of 720 = 36px (vertical)
luaunit.assertAlmostEquals(parent.padding.left, 128, 1, "After second resize, left padding should be 10% of 1280")
luaunit.assertAlmostEquals(parent.padding.right, 128, 1, "After second resize, right padding should be 10% of 1280")
luaunit.assertAlmostEquals(parent.padding.top, 36, 1, "After second resize, top padding should be 5% of 720")
luaunit.assertAlmostEquals(parent.padding.bottom, 36, 1, "After second resize, bottom padding should be 5% of 720")
end
-- Test that pixel padding with fixed dimensions doesn't shrink on resize
function TestPaddingResize:testPixelPaddingFixedDimensions()
-- Create element with pixel padding and fixed dimensions
local element = FlexLove.Element.new({
x = 0,
y = 0,
width = 160,
height = 40,
padding = { horizontal = 12, vertical = 8 },
})
-- Store initial dimensions
local initialWidth = element.width
local initialHeight = element.height
local initialPaddingLeft = element.padding.left
local initialPaddingTop = element.padding.top
-- Resize multiple times
for i = 1, 5 do
element:resize(1920 + i * 100, 1080 + i * 50)
end
-- Dimensions should scale with base scaling but not shrink progressively
luaunit.assertTrue(
element.width >= initialWidth * 0.9,
string.format("Width should not shrink significantly. Initial: %f, Current: %f", initialWidth, element.width)
)
luaunit.assertTrue(
element.height >= initialHeight * 0.9,
string.format("Height should not shrink significantly. Initial: %f, Current: %f", initialHeight, element.height)
)
end
-- Test nested elements with percentage padding
function TestPaddingResize:testNestedPercentagePadding()
-- Create parent with percentage padding
local parent = FlexLove.Element.new({
x = 0,
y = 0,
width = "100%",
height = "100%",
padding = { horizontal = "10%", vertical = "10%" },
positioning = FlexLove.enums.Positioning.FLEX,
flexDirection = FlexLove.enums.FlexDirection.VERTICAL,
})
-- Create child with percentage padding (relative to parent's content area)
local child = FlexLove.Element.new({
parent = parent,
width = "80%",
height = "50%",
padding = { horizontal = "5%", vertical = "5%" },
})
-- Store initial child padding
local initialChildPaddingLeft = child.padding.left
local initialChildPaddingTop = child.padding.top
-- Resize
parent:resize(2560, 1440)
-- Child padding should recalculate based on parent's new content area
-- Parent content width after padding: 2560 - 2*(10% of 2560) = 2560 - 512 = 2048
-- Child width: 80% of 2048 = 1638.4
-- Child horizontal padding: 5% of 1638.4 = 81.92
luaunit.assertTrue(
child.padding.left > initialChildPaddingLeft,
string.format(
"Child left padding should increase after resize. Initial: %f, Current: %f",
initialChildPaddingLeft,
child.padding.left
)
)
luaunit.assertTrue(
child.padding.top > initialChildPaddingTop,
string.format(
"Child top padding should increase after resize. Initial: %f, Current: %f",
initialChildPaddingTop,
child.padding.top
)
)
end
-- Test that percentage padding doesn't cause progressive shrinkage
function TestPaddingResize:testNoProgressiveShrinkage()
-- Create element with percentage padding
local element = FlexLove.Element.new({
x = 0,
y = 0,
width = "100%",
height = "100%",
padding = { horizontal = "10%", vertical = "10%" },
})
-- Store initial content dimensions
local initialContentWidth = element.width
local initialContentHeight = element.height
-- Resize back to original size multiple times
for i = 1, 10 do
element:resize(2560, 1440) -- Larger
element:resize(1920, 1080) -- Back to original
end
-- Content dimensions should return to original (no progressive shrinkage)
luaunit.assertAlmostEquals(
element.width,
initialContentWidth,
5,
string.format(
"Content width should return to original after multiple resizes. Initial: %f, Current: %f",
initialContentWidth,
element.width
)
)
luaunit.assertAlmostEquals(
element.height,
initialContentHeight,
5,
string.format(
"Content height should return to original after multiple resizes. Initial: %f, Current: %f",
initialContentHeight,
element.height
)
)
end
-- Test viewport-relative padding (vw/vh)
function TestPaddingResize:testViewportRelativePadding()
-- Create element with viewport-relative padding
local element = FlexLove.Element.new({
x = 0,
y = 0,
width = "50%",
height = "50%",
padding = { horizontal = "2vw", vertical = "3vh" },
})
-- Initial padding: 2vw of 1920 = 38.4px, 3vh of 1080 = 32.4px
luaunit.assertAlmostEquals(element.padding.left, 38.4, 1, "Initial left padding should be 2vw of 1920")
luaunit.assertAlmostEquals(element.padding.top, 32.4, 1, "Initial top padding should be 3vh of 1080")
-- Resize
element:resize(2560, 1440)
-- Padding should recalculate: 2vw of 2560 = 51.2px, 3vh of 1440 = 43.2px
luaunit.assertAlmostEquals(element.padding.left, 51.2, 1, "After resize, left padding should be 2vw of 2560")
luaunit.assertAlmostEquals(element.padding.top, 43.2, 1, "After resize, top padding should be 3vh of 1440")
end
-- Test individual side padding with different units
function TestPaddingResize:testMixedPaddingUnits()
-- Create element with mixed padding units
local element = FlexLove.Element.new({
x = 0,
y = 0,
width = "100%",
height = "100%",
padding = { top = "5%", right = "2vw", bottom = 20, left = "3vh" },
})
-- Store initial padding
local initialTop = element.padding.top
local initialRight = element.padding.right
local initialBottom = element.padding.bottom
local initialLeft = element.padding.left
-- Resize
element:resize(2560, 1440)
-- Check that each side recalculates according to its unit
luaunit.assertTrue(initialTop < element.padding.top, "Top padding (%) should increase")
luaunit.assertTrue(initialRight < element.padding.right, "Right padding (vw) should increase")
luaunit.assertTrue(
math.abs(initialBottom - element.padding.bottom) < 1,
"Bottom padding (px) should remain roughly the same with base scaling"
)
luaunit.assertTrue(initialLeft < element.padding.left, "Left padding (vh) should increase")
end
luaunit.LuaUnit.run()

View File

@@ -1,202 +0,0 @@
package.path = package.path .. ";?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
_G.love = loveStub
local FlexLove = require("FlexLove")
TestImageScalerNearest = {}
function TestImageScalerNearest:setUp()
-- Create a simple test image (2x2 with distinct colors)
self.testImage2x2 = love.image.newImageData(2, 2)
-- Top-left: red
self.testImage2x2:setPixel(0, 0, 1, 0, 0, 1)
-- Top-right: green
self.testImage2x2:setPixel(1, 0, 0, 1, 0, 1)
-- Bottom-left: blue
self.testImage2x2:setPixel(0, 1, 0, 0, 1, 1)
-- Bottom-right: white
self.testImage2x2:setPixel(1, 1, 1, 1, 1, 1)
end
function TestImageScalerNearest:test2xScaling()
-- Scale 2x2 to 4x4 (2x factor)
local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- Top-left quadrant should be red (0,0 -> 1,1)
local r, g, b, a = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
r, g, b, a = scaled:getPixel(1, 1)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
-- Top-right quadrant should be green (2,0 -> 3,1)
r, g, b, a = scaled:getPixel(2, 0)
luaunit.assertAlmostEquals(r, 0, 0.01)
luaunit.assertAlmostEquals(g, 1, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
r, g, b, a = scaled:getPixel(3, 1)
luaunit.assertAlmostEquals(r, 0, 0.01)
luaunit.assertAlmostEquals(g, 1, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
-- Bottom-left quadrant should be blue (0,2 -> 1,3)
r, g, b, a = scaled:getPixel(0, 2)
luaunit.assertAlmostEquals(r, 0, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 1, 0.01)
-- Bottom-right quadrant should be white (2,2 -> 3,3)
r, g, b, a = scaled:getPixel(3, 3)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 1, 0.01)
luaunit.assertAlmostEquals(b, 1, 0.01)
end
function TestImageScalerNearest:test3xScaling()
-- Scale 2x2 to 6x6 (3x factor)
local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 6, 6)
luaunit.assertEquals(scaled:getWidth(), 6)
luaunit.assertEquals(scaled:getHeight(), 6)
-- Verify nearest-neighbor: each source pixel should map to 3x3 block
-- Top-left (red) should cover 0-2, 0-2
local r, g, b = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01)
r, g, b = scaled:getPixel(2, 2)
luaunit.assertAlmostEquals(r, 1, 0.01)
-- Top-right (green) should cover 3-5, 0-2
r, g, b = scaled:getPixel(3, 0)
luaunit.assertAlmostEquals(g, 1, 0.01)
r, g, b = scaled:getPixel(5, 2)
luaunit.assertAlmostEquals(g, 1, 0.01)
end
function TestImageScalerNearest:testNonUniformScaling()
-- Scale 2x2 to 6x4 (3x horizontal, 2x vertical)
local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 6, 4)
luaunit.assertEquals(scaled:getWidth(), 6)
luaunit.assertEquals(scaled:getHeight(), 4)
-- Top-left red should cover 0-2 horizontally, 0-1 vertically
local r, g, b = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01)
r, g, b = scaled:getPixel(2, 1)
luaunit.assertAlmostEquals(r, 1, 0.01)
-- Top-right green should cover 3-5 horizontally, 0-1 vertically
r, g, b = scaled:getPixel(3, 0)
luaunit.assertAlmostEquals(g, 1, 0.01)
end
function TestImageScalerNearest:testSameSizeScaling()
-- Scale 2x2 to 2x2 (should be identical)
local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 2, 2)
luaunit.assertEquals(scaled:getWidth(), 2)
luaunit.assertEquals(scaled:getHeight(), 2)
-- Verify all pixels match original
for y = 0, 1 do
for x = 0, 1 do
local r1, g1, b1, a1 = self.testImage2x2:getPixel(x, y)
local r2, g2, b2, a2 = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r1, r2, 0.01)
luaunit.assertAlmostEquals(g1, g2, 0.01)
luaunit.assertAlmostEquals(b1, b2, 0.01)
luaunit.assertAlmostEquals(a1, a2, 0.01)
end
end
end
function TestImageScalerNearest:test1x1Scaling()
-- Create 1x1 image
local img1x1 = love.image.newImageData(1, 1)
img1x1:setPixel(0, 0, 0.5, 0.5, 0.5, 1)
-- Scale to 4x4
local scaled = FlexLove.ImageScaler.scaleNearest(img1x1, 0, 0, 1, 1, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- All pixels should be the same color
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 0.5, 0.01)
luaunit.assertAlmostEquals(g, 0.5, 0.01)
luaunit.assertAlmostEquals(b, 0.5, 0.01)
end
end
end
function TestImageScalerNearest:testSubregionScaling()
-- Create 4x4 image with different quadrants
local img4x4 = love.image.newImageData(4, 4)
-- Fill with pattern: top-left red, rest black
for y = 0, 3 do
for x = 0, 3 do
if x < 2 and y < 2 then
img4x4:setPixel(x, y, 1, 0, 0, 1) -- red
else
img4x4:setPixel(x, y, 0, 0, 0, 1) -- black
end
end
end
-- Scale only the top-left 2x2 red quadrant to 4x4
local scaled = FlexLove.ImageScaler.scaleNearest(img4x4, 0, 0, 2, 2, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- All pixels should be red (from source quadrant)
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
end
end
end
function TestImageScalerNearest:testAlphaChannel()
-- Create image with varying alpha
local img = love.image.newImageData(2, 2)
img:setPixel(0, 0, 1, 0, 0, 1.0) -- Opaque red
img:setPixel(1, 0, 0, 1, 0, 0.5) -- Semi-transparent green
img:setPixel(0, 1, 0, 0, 1, 0.25) -- More transparent blue
img:setPixel(1, 1, 1, 1, 1, 0.0) -- Fully transparent white
local scaled = FlexLove.ImageScaler.scaleNearest(img, 0, 0, 2, 2, 4, 4)
-- Check alpha values are preserved
local r, g, b, a = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(a, 1.0, 0.01)
r, g, b, a = scaled:getPixel(2, 0)
luaunit.assertAlmostEquals(a, 0.5, 0.01)
r, g, b, a = scaled:getPixel(0, 2)
luaunit.assertAlmostEquals(a, 0.25, 0.01)
r, g, b, a = scaled:getPixel(3, 3)
luaunit.assertAlmostEquals(a, 0.0, 0.01)
end
luaunit.LuaUnit.run()

View File

@@ -1,291 +0,0 @@
package.path = package.path .. ";?.lua"
local luaunit = require("testing.luaunit")
local loveStub = require("testing.loveStub")
_G.love = loveStub
local FlexLove = require("FlexLove")
TestImageScalerBilinear = {}
function TestImageScalerBilinear:setUp()
-- Create a simple test image (2x2 with distinct colors)
self.testImage2x2 = love.image.newImageData(2, 2)
-- Top-left: red
self.testImage2x2:setPixel(0, 0, 1, 0, 0, 1)
-- Top-right: green
self.testImage2x2:setPixel(1, 0, 0, 1, 0, 1)
-- Bottom-left: blue
self.testImage2x2:setPixel(0, 1, 0, 0, 1, 1)
-- Bottom-right: white
self.testImage2x2:setPixel(1, 1, 1, 1, 1, 1)
end
function TestImageScalerBilinear:test2xScaling()
-- Scale 2x2 to 4x4 (2x factor)
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- Corner pixels should match original (no interpolation at exact positions)
local r, g, b, a = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01) -- Red
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
-- Center pixel at (1,1) should be blend of all 4 corners
-- At (0.5, 0.5) in source space -> blend of all 4 colors
r, g, b, a = scaled:getPixel(1, 1)
-- Should be approximately (0.5, 0.5, 0.5) - average of red, green, blue, white
luaunit.assertTrue(r > 0.3 and r < 0.7, "Center pixel should be blended")
luaunit.assertTrue(g > 0.3 and g < 0.7, "Center pixel should be blended")
luaunit.assertTrue(b > 0.3 and b < 0.7, "Center pixel should be blended")
end
function TestImageScalerBilinear:testGradientSmoothing()
-- Create a simple gradient: black to white horizontally
local gradient = love.image.newImageData(2, 1)
gradient:setPixel(0, 0, 0, 0, 0, 1) -- Black
gradient:setPixel(1, 0, 1, 1, 1, 1) -- White
-- Scale to 4 pixels wide
local scaled = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 4, 1)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 1)
-- Check smooth gradient progression
local r0 = scaled:getPixel(0, 0)
local r1 = scaled:getPixel(1, 0)
local r2 = scaled:getPixel(2, 0)
local r3 = scaled:getPixel(3, 0)
-- Should be monotonically increasing (or equal at end due to clamping)
luaunit.assertTrue(r0 < r1, "Gradient should increase")
luaunit.assertTrue(r1 < r2, "Gradient should increase")
luaunit.assertTrue(r2 <= r3, "Gradient should increase or stay same")
-- First should be close to black, last close to white
luaunit.assertAlmostEquals(r0, 0, 0.15)
luaunit.assertAlmostEquals(r3, 1, 0.15)
end
function TestImageScalerBilinear:testSameSizeScaling()
-- Scale 2x2 to 2x2 (should be identical)
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 2, 2)
luaunit.assertEquals(scaled:getWidth(), 2)
luaunit.assertEquals(scaled:getHeight(), 2)
-- Verify all pixels match original
for y = 0, 1 do
for x = 0, 1 do
local r1, g1, b1, a1 = self.testImage2x2:getPixel(x, y)
local r2, g2, b2, a2 = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r1, r2, 0.01)
luaunit.assertAlmostEquals(g1, g2, 0.01)
luaunit.assertAlmostEquals(b1, b2, 0.01)
luaunit.assertAlmostEquals(a1, a2, 0.01)
end
end
end
function TestImageScalerBilinear:test1x1Scaling()
-- Create 1x1 image
local img1x1 = love.image.newImageData(1, 1)
img1x1:setPixel(0, 0, 0.5, 0.5, 0.5, 1)
-- Scale to 4x4
local scaled = FlexLove.ImageScaler.scaleBilinear(img1x1, 0, 0, 1, 1, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- All pixels should be the same color (no neighbors to interpolate with)
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 0.5, 0.01)
luaunit.assertAlmostEquals(g, 0.5, 0.01)
luaunit.assertAlmostEquals(b, 0.5, 0.01)
end
end
end
function TestImageScalerBilinear:testPureColorMaintenance()
-- Create pure white image
local whiteImg = love.image.newImageData(2, 2)
for y = 0, 1 do
for x = 0, 1 do
whiteImg:setPixel(x, y, 1, 1, 1, 1)
end
end
local scaled = FlexLove.ImageScaler.scaleBilinear(whiteImg, 0, 0, 2, 2, 4, 4)
-- All pixels should remain pure white
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 1, 0.01)
luaunit.assertAlmostEquals(b, 1, 0.01)
end
end
-- Test pure black
local blackImg = love.image.newImageData(2, 2)
for y = 0, 1 do
for x = 0, 1 do
blackImg:setPixel(x, y, 0, 0, 0, 1)
end
end
scaled = FlexLove.ImageScaler.scaleBilinear(blackImg, 0, 0, 2, 2, 4, 4)
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 0, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
end
end
end
function TestImageScalerBilinear:testAlphaInterpolation()
-- Create image with varying alpha
local img = love.image.newImageData(2, 2)
img:setPixel(0, 0, 1, 0, 0, 1.0) -- Opaque red
img:setPixel(1, 0, 1, 0, 0, 0.0) -- Transparent red
img:setPixel(0, 1, 1, 0, 0, 1.0) -- Opaque red
img:setPixel(1, 1, 1, 0, 0, 0.0) -- Transparent red
local scaled = FlexLove.ImageScaler.scaleBilinear(img, 0, 0, 2, 2, 4, 2)
-- Check that alpha is interpolated smoothly
local r, g, b, a0 = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(a0, 1.0, 0.01)
local r, g, b, a1 = scaled:getPixel(1, 0)
-- Should be between 1.0 and 0.0
luaunit.assertTrue(a1 > 0.3 and a1 < 0.7, "Alpha should be interpolated")
local r, g, b, a3 = scaled:getPixel(3, 0)
luaunit.assertAlmostEquals(a3, 0.0, 0.15)
end
function TestImageScalerBilinear:testSubregionScaling()
-- Create 4x4 image with different quadrants
local img4x4 = love.image.newImageData(4, 4)
-- Fill with pattern: top-left red, rest black
for y = 0, 3 do
for x = 0, 3 do
if x < 2 and y < 2 then
img4x4:setPixel(x, y, 1, 0, 0, 1) -- red
else
img4x4:setPixel(x, y, 0, 0, 0, 1) -- black
end
end
end
-- Scale only the top-left 2x2 red quadrant to 4x4
local scaled = FlexLove.ImageScaler.scaleBilinear(img4x4, 0, 0, 2, 2, 4, 4)
luaunit.assertEquals(scaled:getWidth(), 4)
luaunit.assertEquals(scaled:getHeight(), 4)
-- All pixels should be red (from source quadrant)
for y = 0, 3 do
for x = 0, 3 do
local r, g, b = scaled:getPixel(x, y)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
luaunit.assertAlmostEquals(b, 0, 0.01)
end
end
end
function TestImageScalerBilinear:testEdgePixelHandling()
-- Create 3x3 checkerboard
local checkerboard = love.image.newImageData(3, 3)
for y = 0, 2 do
for x = 0, 2 do
if (x + y) % 2 == 0 then
checkerboard:setPixel(x, y, 1, 1, 1, 1) -- white
else
checkerboard:setPixel(x, y, 0, 0, 0, 1) -- black
end
end
end
-- Scale to 9x9
local scaled = FlexLove.ImageScaler.scaleBilinear(checkerboard, 0, 0, 3, 3, 9, 9)
luaunit.assertEquals(scaled:getWidth(), 9)
luaunit.assertEquals(scaled:getHeight(), 9)
-- Verify corners are correct (no out-of-bounds access)
local r, g, b = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01) -- Top-left should be white
r, g, b = scaled:getPixel(8, 8)
luaunit.assertAlmostEquals(r, 1, 0.01) -- Bottom-right should be white
end
function TestImageScalerBilinear:testNonUniformScaling()
-- Scale 2x2 to 6x4 (3x horizontal, 2x vertical)
local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 6, 4)
luaunit.assertEquals(scaled:getWidth(), 6)
luaunit.assertEquals(scaled:getHeight(), 4)
-- Top-left corner should be red
local r, g, b = scaled:getPixel(0, 0)
luaunit.assertAlmostEquals(r, 1, 0.01)
luaunit.assertAlmostEquals(g, 0, 0.01)
-- Should have smooth interpolation in between
r, g, b = scaled:getPixel(2, 1)
-- Middle area should have blended colors
luaunit.assertTrue(r > 0.1, "Should have some red component")
luaunit.assertTrue(g > 0.1, "Should have some green component")
luaunit.assertTrue(b > 0.1, "Should have some blue component")
end
function TestImageScalerBilinear:testComparison_SmootherThanNearest()
-- Create gradient
local gradient = love.image.newImageData(2, 1)
gradient:setPixel(0, 0, 0, 0, 0, 1)
gradient:setPixel(1, 0, 1, 1, 1, 1)
local bilinear = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 8, 1)
local nearest = FlexLove.ImageScaler.scaleNearest(gradient, 0, 0, 2, 1, 8, 1)
-- Count unique values (nearest should have fewer due to blocky nature)
local bilinearValues = {}
local nearestValues = {}
for x = 0, 7 do
local rb = bilinear:getPixel(x, 0)
local rn = nearest:getPixel(x, 0)
bilinearValues[string.format("%.2f", rb)] = true
nearestValues[string.format("%.2f", rn)] = true
end
local bilinearCount = 0
for _ in pairs(bilinearValues) do
bilinearCount = bilinearCount + 1
end
local nearestCount = 0
for _ in pairs(nearestValues) do
nearestCount = nearestCount + 1
end
-- Bilinear should have more unique values (smoother gradient)
luaunit.assertTrue(bilinearCount >= nearestCount, "Bilinear should produce smoother gradient with more unique values")
end
luaunit.LuaUnit.run()

View File

@@ -1,238 +0,0 @@
-- Test suite for blur effects (contentBlur and backdropBlur)
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
TestBlurEffects = {}
function TestBlurEffects:setUp()
-- Initialize FlexLove with default config
FlexLove.Gui.init({ baseScale = { width = 1920, height = 1080 } })
end
function TestBlurEffects:tearDown()
FlexLove.Gui.destroy()
end
-- Test 1: Element with contentBlur property
function TestBlurEffects:test_content_blur_property()
local element = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 5 },
})
lu.assertNotNil(element.contentBlur, "Element should have contentBlur property")
lu.assertEquals(element.contentBlur.intensity, 50, "Content blur intensity should be 50")
lu.assertEquals(element.contentBlur.quality, 5, "Content blur quality should be 5")
end
-- Test 2: Element with backdropBlur property
function TestBlurEffects:test_backdrop_blur_property()
local element = FlexLove.Element.new({
width = 200,
height = 200,
backdropBlur = { intensity = 75, quality = 7 },
})
lu.assertNotNil(element.backdropBlur, "Element should have backdropBlur property")
lu.assertEquals(element.backdropBlur.intensity, 75, "Backdrop blur intensity should be 75")
lu.assertEquals(element.backdropBlur.quality, 7, "Backdrop blur quality should be 7")
end
-- Test 3: Element with both blur types
function TestBlurEffects:test_both_blur_types()
local element = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 30, quality = 3 },
backdropBlur = { intensity = 60, quality = 6 },
})
lu.assertNotNil(element.contentBlur, "Element should have contentBlur property")
lu.assertNotNil(element.backdropBlur, "Element should have backdropBlur property")
lu.assertEquals(element.contentBlur.intensity, 30)
lu.assertEquals(element.backdropBlur.intensity, 60)
end
-- Test 4: Blur instance creation (skip if no graphics context)
function TestBlurEffects:test_blur_instance_creation()
if not love or not love.graphics then
lu.success() -- Skip test if no LÖVE graphics context
return
end
local element = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 5 },
})
local blurInstance = element:getBlurInstance()
lu.assertNotNil(blurInstance, "Blur instance should be created")
lu.assertEquals(blurInstance.quality, 5, "Blur instance should have correct quality")
end
-- Test 5: Blur instance caching (skip if no graphics context)
function TestBlurEffects:test_blur_instance_caching()
if not love or not love.graphics then
lu.success() -- Skip test if no LÖVE graphics context
return
end
local element = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 5 },
})
local instance1 = element:getBlurInstance()
local instance2 = element:getBlurInstance()
lu.assertEquals(instance1, instance2, "Blur instance should be cached and reused")
end
-- Test 6: Blur instance recreation on quality change (skip if no graphics context)
function TestBlurEffects:test_blur_instance_quality_change()
if not love or not love.graphics then
lu.success() -- Skip test if no LÖVE graphics context
return
end
local element = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 5 },
})
local instance1 = element:getBlurInstance()
-- Change quality
element.contentBlur.quality = 8
local instance2 = element:getBlurInstance()
lu.assertNotEquals(instance1, instance2, "Blur instance should be recreated when quality changes")
lu.assertEquals(instance2.quality, 8, "New blur instance should have updated quality")
end
-- Test 7: Element without blur can still create instance with default quality (skip if no graphics context)
function TestBlurEffects:test_no_blur_default_instance()
if not love or not love.graphics then
lu.success() -- Skip test if no LÖVE graphics context
return
end
local element = FlexLove.Element.new({
width = 200,
height = 200,
})
-- Element without blur should still be able to get a blur instance (with default quality)
local instance = element:getBlurInstance()
lu.assertNotNil(instance, "Element should be able to create blur instance even without blur config")
lu.assertEquals(instance.quality, 5, "Default quality should be 5")
end
-- Test 8: Blur intensity boundaries
function TestBlurEffects:test_blur_intensity_boundaries()
-- Test minimum intensity (0)
local element1 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 0, quality = 5 },
})
lu.assertEquals(element1.contentBlur.intensity, 0, "Minimum intensity should be 0")
-- Test maximum intensity (100)
local element2 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 100, quality = 5 },
})
lu.assertEquals(element2.contentBlur.intensity, 100, "Maximum intensity should be 100")
end
-- Test 9: Blur quality boundaries
function TestBlurEffects:test_blur_quality_boundaries()
-- Test minimum quality (1)
local element1 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 1 },
})
lu.assertEquals(element1.contentBlur.quality, 1, "Minimum quality should be 1")
-- Test maximum quality (10)
local element2 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 10 },
})
lu.assertEquals(element2.contentBlur.quality, 10, "Maximum quality should be 10")
end
-- Test 10: Nested elements with blur
function TestBlurEffects:test_nested_elements_with_blur()
local parent = FlexLove.Element.new({
width = 400,
height = 400,
contentBlur = { intensity = 40, quality = 5 },
})
local child = FlexLove.Element.new({
parent = parent,
width = 100,
height = 100,
backdropBlur = { intensity = 60, quality = 6 },
})
lu.assertNotNil(parent.contentBlur, "Parent should have content blur")
lu.assertNotNil(child.backdropBlur, "Child should have backdrop blur")
lu.assertEquals(#parent.children, 1, "Parent should have one child")
end
-- Test 11: Draw method accepts backdrop canvas parameter
function TestBlurEffects:test_draw_accepts_backdrop_canvas()
local element = FlexLove.Element.new({
width = 200,
height = 200,
backdropBlur = { intensity = 50, quality = 5 },
})
-- This should not error (we can't actually test rendering without a graphics context)
-- But we can verify the method signature accepts the parameter
local success = pcall(function()
-- Create a mock canvas (will fail in test environment, but that's ok)
-- element:draw(nil)
end)
-- Test passes if we get here without syntax errors
lu.assertTrue(true, "Draw method should accept backdrop canvas parameter")
end
-- Test 12: Quality affects blur instance taps (skip if no graphics context)
function TestBlurEffects:test_quality_affects_taps()
if not love or not love.graphics then
lu.success() -- Skip test if no LÖVE graphics context
return
end
local element1 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 1 },
})
local element2 = FlexLove.Element.new({
width = 200,
height = 200,
contentBlur = { intensity = 50, quality = 10 },
})
local instance1 = element1:getBlurInstance()
local instance2 = element2:getBlurInstance()
-- Higher quality should have more taps
lu.assertTrue(instance2.taps > instance1.taps, "Higher quality should result in more blur taps")
end
lu.LuaUnit.run()

View File

@@ -1,730 +0,0 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local Element = FlexLove.Element
TestKeyboardInput = {}
-- Helper function to ensure clean keyboard state
local function clearModifierKeys()
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
end
function TestKeyboardInput:setUp()
-- Clear all keyboard modifier states at start of each test
if love.keyboard.isDown("lgui", "rgui", "lalt", "ralt", "lctrl", "rctrl", "lshift", "rshift") then
local mods = {}
if love.keyboard.isDown("lshift") then table.insert(mods, "lshift") end
if love.keyboard.isDown("rshift") then table.insert(mods, "rshift") end
if love.keyboard.isDown("lctrl") then table.insert(mods, "lctrl") end
if love.keyboard.isDown("rctrl") then table.insert(mods, "rctrl") end
if love.keyboard.isDown("lalt") then table.insert(mods, "lalt") end
if love.keyboard.isDown("ralt") then table.insert(mods, "ralt") end
if love.keyboard.isDown("lgui") then table.insert(mods, "lgui") end
if love.keyboard.isDown("rgui") then table.insert(mods, "rgui") end
print("WARNING: Modifiers down at setUp: " .. table.concat(mods, ", "))
end
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
Gui.init({ baseScale = { width = 1920, height = 1080 } })
end
function TestKeyboardInput:tearDown()
-- Clear all keyboard modifier states
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
Gui.destroy()
end
-- ====================
-- Focus Management Tests
-- ====================
function TestKeyboardInput:testFocusEditable()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
lu.assertFalse(input:isFocused())
input:focus()
lu.assertTrue(input:isFocused())
lu.assertEquals(Gui._focusedElement, input)
end
function TestKeyboardInput:testBlurEditable()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
lu.assertTrue(input:isFocused())
input:blur()
lu.assertFalse(input:isFocused())
lu.assertNil(Gui._focusedElement)
end
function TestKeyboardInput:testFocusSwitching()
local input1 = Element.new({
width = 200,
height = 40,
editable = true,
text = "Input 1",
})
local input2 = Element.new({
width = 200,
height = 40,
editable = true,
text = "Input 2",
})
input1:focus()
lu.assertTrue(input1:isFocused())
lu.assertFalse(input2:isFocused())
input2:focus()
lu.assertFalse(input1:isFocused())
lu.assertTrue(input2:isFocused())
lu.assertEquals(Gui._focusedElement, input2)
end
function TestKeyboardInput:testSelectOnFocus()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
selectOnFocus = true,
})
lu.assertFalse(input:hasSelection())
input:focus()
lu.assertTrue(input:hasSelection())
local startPos, endPos = input:getSelection()
lu.assertEquals(startPos, 0)
lu.assertEquals(endPos, 11) -- Length of "Hello World"
end
-- ====================
-- Text Input Tests
-- ====================
function TestKeyboardInput:testTextInput()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
})
input:focus()
input:textinput("H")
input:textinput("i")
lu.assertEquals(input:getText(), "Hi")
lu.assertEquals(input._cursorPosition, 2)
end
function TestKeyboardInput:testTextInputAtPosition()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(2) -- After "He"
input:textinput("X")
lu.assertEquals(input:getText(), "HeXllo")
lu.assertEquals(input._cursorPosition, 3)
end
function TestKeyboardInput:testTextInputWithSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(0, 5) -- Select "Hello"
input:textinput("Hi")
lu.assertEquals(input:getText(), "Hi World")
lu.assertEquals(input._cursorPosition, 2)
lu.assertFalse(input:hasSelection())
end
function TestKeyboardInput:testMaxLengthConstraint()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
maxLength = 5,
})
input:focus()
input:textinput("Hello")
lu.assertEquals(input:getText(), "Hello")
input:textinput("X") -- Should not be added
lu.assertEquals(input:getText(), "Hello")
end
-- ====================
-- Backspace/Delete Tests
-- ====================
function TestKeyboardInput:testBackspace()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5) -- At end
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hell")
lu.assertEquals(input._cursorPosition, 4)
end
function TestKeyboardInput:testBackspaceAtStart()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0) -- At start
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hello") -- No change
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testDelete()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0) -- At start
input:keypressed("delete", "delete", false)
lu.assertEquals(input:getText(), "ello")
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testDeleteAtEnd()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5) -- At end
input:keypressed("delete", "delete", false)
lu.assertEquals(input:getText(), "Hello") -- No change
lu.assertEquals(input._cursorPosition, 5)
end
function TestKeyboardInput:testBackspaceWithSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(0, 5) -- Select "Hello"
input:keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), " World")
lu.assertEquals(input._cursorPosition, 0)
lu.assertFalse(input:hasSelection())
end
-- ====================
-- Cursor Movement Tests
-- ====================
function TestKeyboardInput:testArrowLeft()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
input:keypressed("left", "left", false)
lu.assertEquals(input._cursorPosition, 4)
end
function TestKeyboardInput:testArrowRight()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0)
input:keypressed("right", "right", false)
lu.assertEquals(input._cursorPosition, 1)
end
function TestKeyboardInput:testHomeKey()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
input:keypressed("home", "home", false)
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testEndKey()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(0)
input:keypressed("end", "end", false)
lu.assertEquals(input._cursorPosition, 5)
end
-- ====================
-- Modifier Key Tests
-- ====================
function TestKeyboardInput:testSuperLeftMovesToStart()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setCursorPosition(8) -- Middle of text
-- Simulate Super (Cmd/Win) key being held
love.keyboard.setDown("lgui", true)
input:keypressed("left", "left", false)
love.keyboard.setDown("lgui", false)
lu.assertEquals(input._cursorPosition, 0)
end
function TestKeyboardInput:testSuperRightMovesToEnd()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setCursorPosition(3) -- Middle of text
-- Simulate Super (Cmd/Win) key being held
love.keyboard.setDown("lgui", true)
input:keypressed("right", "right", false)
love.keyboard.setDown("lgui", false)
lu.assertEquals(input._cursorPosition, 11) -- End of "Hello World"
end
function TestKeyboardInput:testAltLeftMovesByWord()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World Test",
})
input:focus()
input:setCursorPosition(16) -- End of text
-- Simulate Alt key being held and move left by word
love.keyboard.setDown("lalt", true)
input:keypressed("left", "left", false)
love.keyboard.setDown("lalt", false)
lu.assertEquals(input._cursorPosition, 12) -- Start of "Test"
end
function TestKeyboardInput:testAltRightMovesByWord()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World Test",
})
input:focus()
input:setCursorPosition(0) -- Start of text
-- Simulate Alt key being held and move right by word
love.keyboard.setDown("lalt", true)
input:keypressed("right", "right", false)
love.keyboard.setDown("lalt", false)
lu.assertEquals(input._cursorPosition, 6) -- After "Hello "
end
function TestKeyboardInput:testAltLeftMultipleWords()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "The quick brown fox",
})
input:focus()
input:setCursorPosition(19) -- End of text
-- Move left by word three times
love.keyboard.setDown("lalt", true)
input:keypressed("left", "left", false) -- to "fox"
input:keypressed("left", "left", false) -- to "brown"
input:keypressed("left", "left", false) -- to "quick"
love.keyboard.setDown("lalt", false)
lu.assertEquals(input._cursorPosition, 4) -- Start of "quick"
end
function TestKeyboardInput:testAltRightMultipleWords()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "The quick brown fox",
})
input:focus()
input:setCursorPosition(0) -- Start of text
-- Move right by word three times
love.keyboard.setDown("lalt", true)
input:keypressed("right", "right", false) -- after "The "
input:keypressed("right", "right", false) -- after "quick "
input:keypressed("right", "right", false) -- after "brown "
love.keyboard.setDown("lalt", false)
lu.assertEquals(input._cursorPosition, 16) -- After "brown "
end
function TestKeyboardInput:testSuperLeftWithSelection()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(3, 8)
lu.assertTrue(input:hasSelection())
-- Super+Left should move to start and clear selection
love.keyboard.setDown("lgui", true)
input:keypressed("left", "left", false)
love.keyboard.setDown("lgui", false)
lu.assertEquals(input._cursorPosition, 0)
lu.assertFalse(input:hasSelection())
end
function TestKeyboardInput:testSuperRightWithSelection()
clearModifierKeys()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello World",
})
input:focus()
input:setSelection(3, 8)
lu.assertTrue(input:hasSelection())
-- Super+Right should move to end and clear selection
love.keyboard.setDown("lgui", true)
input:keypressed("right", "right", false)
love.keyboard.setDown("lgui", false)
lu.assertEquals(input._cursorPosition, 11)
lu.assertFalse(input:hasSelection())
end
function TestKeyboardInput:testEscapeClearsSelection()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:selectAll()
lu.assertTrue(input:hasSelection())
input:keypressed("escape", "escape", false)
lu.assertFalse(input:hasSelection())
lu.assertTrue(input:isFocused()) -- Still focused
end
function TestKeyboardInput:testEscapeBlurs()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
lu.assertTrue(input:isFocused())
input:keypressed("escape", "escape", false)
lu.assertFalse(input:isFocused())
end
-- ====================
-- Callback Tests
-- ====================
function TestKeyboardInput:testOnTextChangeCallback()
local changeCount = 0
local oldTextValue = nil
local newTextValue = nil
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onTextChange = function(element, newText, oldText)
changeCount = changeCount + 1
newTextValue = newText
oldTextValue = oldText
end,
})
input:focus()
input:textinput("X")
lu.assertEquals(changeCount, 1)
lu.assertEquals(oldTextValue, "Hello")
lu.assertEquals(newTextValue, "HelloX")
end
function TestKeyboardInput:testOnTextInputCallback()
local inputCount = 0
local lastChar = nil
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
onTextInput = function(element, text)
inputCount = inputCount + 1
lastChar = text
end,
})
input:focus()
input:textinput("A")
input:textinput("B")
lu.assertEquals(inputCount, 2)
lu.assertEquals(lastChar, "B")
end
function TestKeyboardInput:testOnEnterCallback()
local enterCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
multiline = false,
text = "Hello",
onEnter = function(element)
enterCalled = true
end,
})
input:focus()
input:keypressed("return", "return", false)
lu.assertTrue(enterCalled)
end
function TestKeyboardInput:testOnFocusCallback()
local focusCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onFocus = function(element)
focusCalled = true
end,
})
input:focus()
lu.assertTrue(focusCalled)
end
function TestKeyboardInput:testOnBlurCallback()
local blurCalled = false
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
onBlur = function(element)
blurCalled = true
end,
})
input:focus()
input:blur()
lu.assertTrue(blurCalled)
end
-- ====================
-- GUI-level Input Forwarding Tests
-- ====================
function TestKeyboardInput:testGuiTextinputForwarding()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "",
})
input:focus()
Gui.textinput("A")
lu.assertEquals(input:getText(), "A")
end
function TestKeyboardInput:testGuiKeypressedForwarding()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
input:focus()
input:setCursorPosition(5)
Gui.keypressed("backspace", "backspace", false)
lu.assertEquals(input:getText(), "Hell")
end
function TestKeyboardInput:testGuiInputWithoutFocus()
local input = Element.new({
width = 200,
height = 40,
editable = true,
text = "Hello",
})
-- No focus
Gui.textinput("X")
lu.assertEquals(input:getText(), "Hello") -- No change
end
lu.LuaUnit.run()

View File

@@ -1,232 +0,0 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local ImageCache = FlexLove.ImageCache
TestImageCache = {}
function TestImageCache:setUp()
-- Clear cache before each test
ImageCache.clear()
-- Create a test image programmatically
self.testImageData = love.image.newImageData(64, 64)
-- Fill with a simple pattern
for y = 0, 63 do
for x = 0, 63 do
local r = x / 63
local g = y / 63
local b = 0.5
self.testImageData:setPixel(x, y, r, g, b, 1)
end
end
-- Save to a temporary file (register in mock filesystem)
self.testImagePath = "testing/temp_test_image.png"
self.testImageData:encode("png", self.testImagePath)
-- Register file in mock filesystem so love.graphics.newImage can find it
love.filesystem.addMockFile(self.testImagePath, "mock_png_data")
end
function TestImageCache:tearDown()
-- Clear cache after each test
ImageCache.clear()
-- Clean up temporary test file
if love.filesystem.getInfo(self.testImagePath) then
love.filesystem.remove(self.testImagePath)
end
end
-- ====================
-- Basic Loading Tests
-- ====================
function TestImageCache:testLoadValidImage()
local image, err = ImageCache.load(self.testImagePath)
lu.assertNotNil(image)
lu.assertNil(err)
-- In the test stub, Image is a table with metatable, not userdata
lu.assertTrue(type(image) == "table" or type(image) == "userdata")
lu.assertNotNil(image.getDimensions) -- Should have Image methods
end
function TestImageCache:testLoadInvalidPath()
local image, err = ImageCache.load("nonexistent/path/to/image.png")
lu.assertNil(image)
lu.assertNotNil(err)
lu.assertStrContains(err, "Failed to load image")
end
function TestImageCache:testLoadEmptyPath()
local image, err = ImageCache.load("")
lu.assertNil(image)
lu.assertNotNil(err)
lu.assertStrContains(err, "Invalid image path")
end
function TestImageCache:testLoadNilPath()
local image, err = ImageCache.load(nil)
lu.assertNil(image)
lu.assertNotNil(err)
lu.assertStrContains(err, "Invalid image path")
end
-- ====================
-- Caching Tests
-- ====================
function TestImageCache:testCachingSameImageReturnsSameReference()
local image1, err1 = ImageCache.load(self.testImagePath)
local image2, err2 = ImageCache.load(self.testImagePath)
lu.assertNotNil(image1)
lu.assertNotNil(image2)
lu.assertEquals(image1, image2) -- Same reference
end
function TestImageCache:testCachingDifferentImages()
-- Create a second test image
local testImageData2 = love.image.newImageData(32, 32)
for y = 0, 31 do
for x = 0, 31 do
testImageData2:setPixel(x, y, 1, 0, 0, 1)
end
end
local testImagePath2 = "testing/temp_test_image2.png"
testImageData2:encode("png", testImagePath2)
local image1 = ImageCache.load(self.testImagePath)
local image2, err2 = ImageCache.load(testImagePath2)
lu.assertNotNil(image1)
-- Note: The stub may not support loading dynamically created files
if image2 then
lu.assertNotEquals(image1, image2) -- Different images
end
-- Cleanup
love.filesystem.remove(testImagePath2)
end
function TestImageCache:testGetCachedImage()
-- Load image first
local loadedImage = ImageCache.load(self.testImagePath)
-- Get from cache
local cachedImage = ImageCache.get(self.testImagePath)
lu.assertNotNil(cachedImage)
lu.assertEquals(loadedImage, cachedImage)
end
function TestImageCache:testGetNonCachedImage()
local image = ImageCache.get("nonexistent.png")
lu.assertNil(image)
end
-- ====================
-- ImageData Loading Tests
-- ====================
function TestImageCache:testLoadWithImageData()
local image, err = ImageCache.load(self.testImagePath, true)
lu.assertNotNil(image)
lu.assertNil(err)
local imageData = ImageCache.getImageData(self.testImagePath)
-- Note: The stub's newImageData doesn't support loading from path
-- so imageData may be nil in test environment
if imageData then
lu.assertTrue(type(imageData) == "table" or type(imageData) == "userdata")
end
end
function TestImageCache:testLoadWithoutImageData()
local image, err = ImageCache.load(self.testImagePath, false)
lu.assertNotNil(image)
lu.assertNil(err)
local imageData = ImageCache.getImageData(self.testImagePath)
lu.assertNil(imageData) -- Should not be loaded
end
-- ====================
-- Cache Management Tests
-- ====================
function TestImageCache:testRemoveImage()
ImageCache.load(self.testImagePath)
local removed = ImageCache.remove(self.testImagePath)
lu.assertTrue(removed)
-- Verify it's no longer in cache
local cachedImage = ImageCache.get(self.testImagePath)
lu.assertNil(cachedImage)
end
function TestImageCache:testRemoveNonExistentImage()
local removed = ImageCache.remove("nonexistent.png")
lu.assertFalse(removed)
end
function TestImageCache:testClearCache()
-- Load multiple images
ImageCache.load(self.testImagePath)
local stats1 = ImageCache.getStats()
lu.assertEquals(stats1.count, 1)
ImageCache.clear()
local stats2 = ImageCache.getStats()
lu.assertEquals(stats2.count, 0)
end
-- ====================
-- Statistics Tests
-- ====================
function TestImageCache:testCacheStats()
local stats1 = ImageCache.getStats()
lu.assertEquals(stats1.count, 0)
lu.assertEquals(stats1.memoryEstimate, 0)
ImageCache.load(self.testImagePath)
local stats2 = ImageCache.getStats()
lu.assertEquals(stats2.count, 1)
lu.assertTrue(stats2.memoryEstimate > 0)
-- Memory estimate should be > 0 (stub creates 100x100 images = 40000 bytes)
lu.assertTrue(stats2.memoryEstimate >= 16384)
end
-- ====================
-- Path Normalization Tests
-- ====================
function TestImageCache:testPathNormalization()
-- Load with different path formats
local image1 = ImageCache.load(self.testImagePath)
local image2 = ImageCache.load(" " .. self.testImagePath .. " ") -- With whitespace
local image3 = ImageCache.load(self.testImagePath:gsub("/", "\\")) -- With backslashes
lu.assertEquals(image1, image2)
lu.assertEquals(image1, image3)
-- Should only have one cache entry
local stats = ImageCache.getStats()
lu.assertEquals(stats.count, 1)
end
lu.LuaUnit.run()

View File

@@ -1,244 +0,0 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local ImageRenderer = FlexLove.ImageRenderer
TestObjectFitModes = {}
function TestObjectFitModes:setUp()
-- Test dimensions
self.imageWidth = 400
self.imageHeight = 300
self.boundsWidth = 200
self.boundsHeight = 200
end
-- ====================
-- Fill Mode Tests
-- ====================
function TestObjectFitModes:testFillModeStretchesToExactBounds()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill")
lu.assertEquals(params.dw, self.boundsWidth)
lu.assertEquals(params.dh, self.boundsHeight)
lu.assertEquals(params.dx, 0)
lu.assertEquals(params.dy, 0)
end
function TestObjectFitModes:testFillModeUsesFullSourceImage()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill")
lu.assertEquals(params.sx, 0)
lu.assertEquals(params.sy, 0)
lu.assertEquals(params.sw, self.imageWidth)
lu.assertEquals(params.sh, self.imageHeight)
end
-- ====================
-- Contain Mode Tests
-- ====================
function TestObjectFitModes:testContainModePreservesAspectRatio()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain")
-- Image is 4:3, bounds are 1:1
-- Should scale to fit width (200), height becomes 150
local expectedScale = self.boundsWidth / self.imageWidth
local expectedHeight = self.imageHeight * expectedScale
lu.assertAlmostEquals(params.dw, self.boundsWidth, 0.01)
lu.assertAlmostEquals(params.dh, expectedHeight, 0.01)
end
function TestObjectFitModes:testContainModeFitsWithinBounds()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain")
lu.assertTrue(params.dw <= self.boundsWidth)
lu.assertTrue(params.dh <= self.boundsHeight)
end
function TestObjectFitModes:testContainModeCentersImage()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain")
-- Image should be centered in letterbox
-- With default "center center" position
lu.assertTrue(params.dx >= 0)
lu.assertTrue(params.dy >= 0)
end
-- ====================
-- Cover Mode Tests
-- ====================
function TestObjectFitModes:testCoverModePreservesAspectRatio()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover")
-- Check that aspect ratio is preserved in source crop
local sourceAspect = params.sw / params.sh
local boundsAspect = self.boundsWidth / self.boundsHeight
lu.assertAlmostEquals(sourceAspect, boundsAspect, 0.01)
end
function TestObjectFitModes:testCoverModeCoversEntireBounds()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover")
lu.assertEquals(params.dw, self.boundsWidth)
lu.assertEquals(params.dh, self.boundsHeight)
end
function TestObjectFitModes:testCoverModeCropsImage()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover")
-- Source should be cropped (not full image)
lu.assertTrue(params.sw < self.imageWidth or params.sh < self.imageHeight)
end
-- ====================
-- None Mode Tests
-- ====================
function TestObjectFitModes:testNoneModeUsesNaturalSize()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "none")
lu.assertEquals(params.dw, self.imageWidth)
lu.assertEquals(params.dh, self.imageHeight)
lu.assertEquals(params.scaleX, 1)
lu.assertEquals(params.scaleY, 1)
end
function TestObjectFitModes:testNoneModeUsesFullSourceImage()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "none")
lu.assertEquals(params.sx, 0)
lu.assertEquals(params.sy, 0)
lu.assertEquals(params.sw, self.imageWidth)
lu.assertEquals(params.sh, self.imageHeight)
end
-- ====================
-- Scale-Down Mode Tests
-- ====================
function TestObjectFitModes:testScaleDownUsesNoneWhenImageFits()
-- Image smaller than bounds
local smallWidth = 100
local smallHeight = 75
local params = ImageRenderer.calculateFit(smallWidth, smallHeight, self.boundsWidth, self.boundsHeight, "scale-down")
-- Should use natural size (none mode)
lu.assertEquals(params.dw, smallWidth)
lu.assertEquals(params.dh, smallHeight)
end
function TestObjectFitModes:testScaleDownUsesContainWhenImageTooBig()
local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "scale-down")
-- Should use contain mode
lu.assertTrue(params.dw <= self.boundsWidth)
lu.assertTrue(params.dh <= self.boundsHeight)
-- Should preserve aspect ratio
local scale = params.dw / self.imageWidth
lu.assertAlmostEquals(params.dh, self.imageHeight * scale, 0.01)
end
-- ====================
-- Edge Cases
-- ====================
function TestObjectFitModes:testLandscapeImageInPortraitBounds()
local params = ImageRenderer.calculateFit(
400,
200, -- Landscape image (2:1)
200,
400, -- Portrait bounds (1:2)
"contain"
)
-- Should fit width
lu.assertEquals(params.dw, 200)
lu.assertTrue(params.dh < 400)
end
function TestObjectFitModes:testPortraitImageInLandscapeBounds()
local params = ImageRenderer.calculateFit(
200,
400, -- Portrait image (1:2)
400,
200, -- Landscape bounds (2:1)
"contain"
)
-- Should fit height
lu.assertEquals(params.dh, 200)
lu.assertTrue(params.dw < 400)
end
function TestObjectFitModes:testSquareImageInNonSquareBounds()
local params = ImageRenderer.calculateFit(
300,
300, -- Square image
200,
400, -- Non-square bounds
"contain"
)
-- Should fit to smaller dimension (width)
lu.assertEquals(params.dw, 200)
lu.assertEquals(params.dh, 200)
end
function TestObjectFitModes:testImageSmallerThanBounds()
local params = ImageRenderer.calculateFit(100, 100, 200, 200, "contain")
-- Should scale up to fit
lu.assertEquals(params.dw, 200)
lu.assertEquals(params.dh, 200)
end
function TestObjectFitModes:testImageLargerThanBounds()
local params = ImageRenderer.calculateFit(800, 600, 200, 200, "contain")
-- Should scale down to fit
lu.assertTrue(params.dw <= 200)
lu.assertTrue(params.dh <= 200)
end
-- ====================
-- Invalid Input Tests
-- ====================
function TestObjectFitModes:testInvalidFitModeThrowsError()
lu.assertErrorMsgContains("Invalid fit mode", ImageRenderer.calculateFit, 100, 100, 200, 200, "invalid-mode")
end
function TestObjectFitModes:testZeroDimensionsThrowsError()
lu.assertErrorMsgContains("Dimensions must be positive", ImageRenderer.calculateFit, 0, 100, 200, 200, "fill")
end
function TestObjectFitModes:testNegativeDimensionsThrowsError()
lu.assertErrorMsgContains("Dimensions must be positive", ImageRenderer.calculateFit, 100, -100, 200, 200, "fill")
end
-- ====================
-- Default Mode Test
-- ====================
function TestObjectFitModes:testDefaultModeIsFill()
local params1 = ImageRenderer.calculateFit(
self.imageWidth,
self.imageHeight,
self.boundsWidth,
self.boundsHeight,
nil -- No mode specified
)
local params2 = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill")
lu.assertEquals(params1.dw, params2.dw)
lu.assertEquals(params1.dh, params2.dh)
end
lu.LuaUnit.run()

View File

@@ -1,184 +0,0 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local ImageRenderer = FlexLove.ImageRenderer
TestObjectPosition = {}
-- ====================
-- Position Parsing Tests
-- ====================
function TestObjectPosition:testCenterCenterDefault()
local x, y = ImageRenderer._parsePosition("center center")
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.5)
end
function TestObjectPosition:testTopLeft()
local x, y = ImageRenderer._parsePosition("top left")
lu.assertEquals(x, 0)
lu.assertEquals(y, 0)
end
function TestObjectPosition:testBottomRight()
local x, y = ImageRenderer._parsePosition("bottom right")
lu.assertEquals(x, 1)
lu.assertEquals(y, 1)
end
function TestObjectPosition:testPercentage50()
local x, y = ImageRenderer._parsePosition("50% 50%")
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.5)
end
function TestObjectPosition:testPercentage0()
local x, y = ImageRenderer._parsePosition("0% 0%")
lu.assertEquals(x, 0)
lu.assertEquals(y, 0)
end
function TestObjectPosition:testPercentage100()
local x, y = ImageRenderer._parsePosition("100% 100%")
lu.assertEquals(x, 1)
lu.assertEquals(y, 1)
end
function TestObjectPosition:testMixedKeywordPercentage()
local x, y = ImageRenderer._parsePosition("center 25%")
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.25)
end
function TestObjectPosition:testSingleValueLeft()
local x, y = ImageRenderer._parsePosition("left")
lu.assertEquals(x, 0)
lu.assertEquals(y, 0.5) -- Should center on Y axis
end
function TestObjectPosition:testSingleValueTop()
local x, y = ImageRenderer._parsePosition("top")
lu.assertEquals(x, 0.5) -- Should center on X axis
lu.assertEquals(y, 0)
end
function TestObjectPosition:testInvalidPositionDefaultsToCenter()
local x, y = ImageRenderer._parsePosition("invalid position")
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.5)
end
function TestObjectPosition:testNilPositionDefaultsToCenter()
local x, y = ImageRenderer._parsePosition(nil)
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.5)
end
function TestObjectPosition:testEmptyStringDefaultsToCenter()
local x, y = ImageRenderer._parsePosition("")
lu.assertEquals(x, 0.5)
lu.assertEquals(y, 0.5)
end
-- ====================
-- Position with Contain Mode Tests
-- ====================
function TestObjectPosition:testContainWithTopLeft()
local params = ImageRenderer.calculateFit(
400,
300, -- Image (landscape)
200,
200, -- Bounds (square)
"contain",
"top left"
)
-- Image should be in top-left of letterbox
lu.assertEquals(params.dx, 0)
lu.assertEquals(params.dy, 0)
end
function TestObjectPosition:testContainWithBottomRight()
local params = ImageRenderer.calculateFit(
400,
300, -- Image (landscape)
200,
200, -- Bounds (square)
"contain",
"bottom right"
)
-- Image should be in bottom-right of letterbox
lu.assertTrue(params.dx + params.dw <= 200)
lu.assertTrue(params.dy + params.dh <= 200)
-- Should be at the bottom right
lu.assertAlmostEquals(params.dx + params.dw, 200, 0.01)
lu.assertAlmostEquals(params.dy + params.dh, 200, 0.01)
end
function TestObjectPosition:testContainWithCenter()
local params = ImageRenderer.calculateFit(400, 300, 200, 200, "contain", "center center")
-- Image (400x300) will be scaled to fit width (200x150)
-- Should be centered horizontally (dx=0) and vertically (dy=25)
lu.assertEquals(params.dx, 0)
lu.assertTrue(params.dy > 0)
end
-- ====================
-- Position with Cover Mode Tests
-- ====================
function TestObjectPosition:testCoverWithTopLeft()
local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "top left")
-- Crop should start from top-left
lu.assertEquals(params.sx, 0)
lu.assertEquals(params.sy, 0)
end
function TestObjectPosition:testCoverWithBottomRight()
local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "bottom right")
-- Crop should be from bottom-right
lu.assertTrue(params.sx > 0)
lu.assertTrue(params.sy >= 0)
end
function TestObjectPosition:testCoverWithCenter()
local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "center center")
-- Crop should be centered
lu.assertTrue(params.sx > 0)
end
-- ====================
-- Position with None Mode Tests
-- ====================
function TestObjectPosition:testNoneWithTopLeft()
local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "top left")
-- Image should be at top-left
lu.assertEquals(params.dx, 0)
lu.assertEquals(params.dy, 0)
end
function TestObjectPosition:testNoneWithBottomRight()
local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "bottom right")
-- Image should be at bottom-right
lu.assertEquals(params.dx, 100) -- 200 - 100
lu.assertEquals(params.dy, 100) -- 200 - 100
end
function TestObjectPosition:testNoneWithCenter()
local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "center center")
-- Image should be centered
lu.assertEquals(params.dx, 50) -- (200 - 100) / 2
lu.assertEquals(params.dy, 50) -- (200 - 100) / 2
end
lu.LuaUnit.run()

View File

@@ -1,391 +0,0 @@
local lu = require("testing.luaunit")
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
local Element = FlexLove.Element
local ImageCache = FlexLove.ImageCache
TestElementImageIntegration = {}
function TestElementImageIntegration:setUp()
Gui.init({ baseScale = { width = 1920, height = 1080 } })
-- Create a test image programmatically
self.testImageData = love.image.newImageData(400, 300)
-- Fill with a gradient pattern
for y = 0, 299 do
for x = 0, 399 do
local r = x / 399
local g = y / 299
local b = 0.5
self.testImageData:setPixel(x, y, r, g, b, 1)
end
end
-- Save to a temporary file (mock filesystem)
self.testImagePath = "testing/temp_element_test_image.png"
self.testImageData:encode("png", self.testImagePath)
love.filesystem.addMockFile(self.testImagePath, "mock_image_data")
-- Create test image object
self.testImage = love.graphics.newImage(self.testImageData)
end
function TestElementImageIntegration:tearDown()
Gui.destroy()
ImageCache.clear()
-- Clean up temporary test file
if love.filesystem.getInfo(self.testImagePath) then
love.filesystem.remove(self.testImagePath)
end
end
-- ====================
-- Element Creation Tests
-- ====================
function TestElementImageIntegration:testElementWithImagePath()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element.imagePath, self.testImagePath)
end
function TestElementImageIntegration:testElementWithImageObject()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
image = self.testImage,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element._loadedImage, self.testImage)
end
function TestElementImageIntegration:testElementWithInvalidImagePath()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = "nonexistent/image.png",
})
-- Should not crash, just not have a loaded image
lu.assertNil(element._loadedImage)
end
function TestElementImageIntegration:testElementWithoutImage()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
})
lu.assertNil(element._loadedImage)
lu.assertNil(element.imagePath)
lu.assertNil(element.image)
end
-- ====================
-- Property Tests
-- ====================
function TestElementImageIntegration:testObjectFitProperty()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
objectFit = "contain",
})
lu.assertEquals(element.objectFit, "contain")
end
function TestElementImageIntegration:testObjectFitDefaultValue()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
lu.assertEquals(element.objectFit, "fill")
end
function TestElementImageIntegration:testObjectPositionProperty()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
objectPosition = "top left",
})
lu.assertEquals(element.objectPosition, "top left")
end
function TestElementImageIntegration:testObjectPositionDefaultValue()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
lu.assertEquals(element.objectPosition, "center center")
end
function TestElementImageIntegration:testImageOpacityProperty()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
imageOpacity = 0.5,
})
lu.assertEquals(element.imageOpacity, 0.5)
end
function TestElementImageIntegration:testImageOpacityDefaultValue()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
lu.assertEquals(element.imageOpacity, 1)
end
-- ====================
-- Image Caching Tests
-- ====================
function TestElementImageIntegration:testMultipleElementsShareCachedImage()
local element1 = Element.new({
x = 0,
y = 0,
width = 100,
height = 100,
imagePath = self.testImagePath,
})
local element2 = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
-- Both should have the same cached image reference
lu.assertEquals(element1._loadedImage, element2._loadedImage)
-- Cache should only have one entry
local stats = ImageCache.getStats()
lu.assertEquals(stats.count, 1)
end
-- ====================
-- Rendering Tests (Basic Validation)
-- ====================
function TestElementImageIntegration:testDrawDoesNotCrashWithImage()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
-- Should not crash when drawing
lu.assertNotNil(function()
element:draw()
end)
end
function TestElementImageIntegration:testDrawDoesNotCrashWithoutImage()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
})
-- Should not crash when drawing without image
lu.assertNotNil(function()
element:draw()
end)
end
function TestElementImageIntegration:testDrawWithZeroOpacity()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
opacity = 0,
})
-- Should not crash (early exit in draw)
lu.assertNotNil(function()
element:draw()
end)
end
-- ====================
-- Combined Properties Tests
-- ====================
function TestElementImageIntegration:testImageWithPadding()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
padding = { top = 10, right = 10, bottom = 10, left = 10 },
imagePath = self.testImagePath,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element.padding.top, 10)
lu.assertEquals(element.padding.left, 10)
-- Image should render in content area (180x180 = 200 - 10 - 10)
lu.assertEquals(element.width, 180)
lu.assertEquals(element.height, 180)
end
function TestElementImageIntegration:testImageWithCornerRadius()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
cornerRadius = 20,
imagePath = self.testImagePath,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element.cornerRadius.topLeft, 20)
lu.assertEquals(element.cornerRadius.topRight, 20)
lu.assertEquals(element.cornerRadius.bottomLeft, 20)
lu.assertEquals(element.cornerRadius.bottomRight, 20)
end
function TestElementImageIntegration:testImageWithBackgroundColor()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
backgroundColor = FlexLove.Color.new(1, 0, 0, 1),
imagePath = self.testImagePath,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element.backgroundColor.r, 1)
lu.assertEquals(element.backgroundColor.g, 0)
lu.assertEquals(element.backgroundColor.b, 0)
end
function TestElementImageIntegration:testImageWithAllObjectFitModes()
local modes = { "fill", "contain", "cover", "scale-down", "none" }
for _, mode in ipairs(modes) do
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
objectFit = mode,
})
lu.assertEquals(element.objectFit, mode)
lu.assertNotNil(element._loadedImage)
end
end
function TestElementImageIntegration:testImageWithCombinedOpacity()
local element = Element.new({
x = 100,
y = 100,
width = 200,
height = 200,
imagePath = self.testImagePath,
opacity = 0.5,
imageOpacity = 0.8,
})
lu.assertEquals(element.opacity, 0.5)
lu.assertEquals(element.imageOpacity, 0.8)
-- Combined opacity should be 0.5 * 0.8 = 0.4 (tested in rendering)
end
-- ====================
-- Layout Integration Tests
-- ====================
function TestElementImageIntegration:testImageWithFlexLayout()
local container = Element.new({
x = 0,
y = 0,
width = 600,
height = 200,
flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL,
})
local imageElement = Element.new({
width = 200,
height = 200,
imagePath = self.testImagePath,
parent = container,
})
table.insert(container.children, imageElement)
lu.assertNotNil(imageElement._loadedImage)
lu.assertEquals(imageElement.width, 200)
lu.assertEquals(imageElement.height, 200)
end
function TestElementImageIntegration:testImageWithAbsolutePositioning()
local element = Element.new({
positioning = FlexLove.enums.Positioning.ABSOLUTE,
top = 50,
left = 50,
width = 200,
height = 200,
imagePath = self.testImagePath,
})
lu.assertNotNil(element._loadedImage)
lu.assertEquals(element.positioning, FlexLove.enums.Positioning.ABSOLUTE)
end
-- Run tests if executed directly
if arg and arg[0]:match("28_element_image_integration_tests.lua$") then
os.exit(lu.LuaUnit.run())
end
return TestElementImageIntegration

View File

@@ -1,282 +0,0 @@
-- Drag Event Tests
-- Tests for the new drag event functionality
package.path = package.path .. ";?.lua"
local lu = require("testing.luaunit")
require("testing.loveStub")
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
TestDragEvent = {}
function TestDragEvent:setUp()
-- Initialize GUI before each test
Gui.init({ baseScale = { width = 1920, height = 1080 } })
love.window.setMode(1920, 1080)
Gui.resize(1920, 1080) -- Recalculate scale factors after setMode
end
function TestDragEvent:tearDown()
-- Clean up after each test
Gui.destroy()
end
-- Test 1: Drag event is fired when mouse moves while pressed
function TestDragEvent:test_drag_event_fired_on_mouse_movement()
local dragEventReceived = false
local dragEvent = nil
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
dragEventReceived = true
dragEvent = event
end
end,
})
-- Simulate mouse press
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
-- Move mouse while pressed (drag)
love.mouse.setPosition(160, 155)
element:update(0.016)
lu.assertTrue(dragEventReceived, "Drag event should be fired when mouse moves while pressed")
lu.assertNotNil(dragEvent, "Drag event object should exist")
end
-- Test 2: Drag event contains dx and dy fields
function TestDragEvent:test_drag_event_contains_delta_values()
local dragEvent = nil
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
dragEvent = event
end
end,
})
-- Simulate mouse press at (150, 150)
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
-- Move mouse to (160, 155) - delta should be (10, 5)
love.mouse.setPosition(160, 155)
element:update(0.016)
lu.assertNotNil(dragEvent, "Drag event should be received")
lu.assertNotNil(dragEvent.dx, "Drag event should have dx field")
lu.assertNotNil(dragEvent.dy, "Drag event should have dy field")
lu.assertEquals(dragEvent.dx, 10, "dx should be 10 (160 - 150)")
lu.assertEquals(dragEvent.dy, 5, "dy should be 5 (155 - 150)")
end
-- Test 3: Drag event updates dx/dy as mouse continues to move
function TestDragEvent:test_drag_event_updates_delta_continuously()
local dragEvents = {}
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
table.insert(dragEvents, { dx = event.dx, dy = event.dy })
end
end,
})
-- Press at (150, 150)
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
-- Move to (160, 155)
love.mouse.setPosition(160, 155)
element:update(0.016)
-- Move to (170, 160)
love.mouse.setPosition(170, 160)
element:update(0.016)
lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events")
lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10")
lu.assertEquals(dragEvents[1].dy, 5, "First drag dy should be 5")
lu.assertEquals(dragEvents[2].dx, 20, "Second drag dx should be 20 (170 - 150)")
lu.assertEquals(dragEvents[2].dy, 10, "Second drag dy should be 10 (160 - 150)")
end
-- Test 4: No drag event when mouse doesn't move
function TestDragEvent:test_no_drag_event_when_mouse_stationary()
local dragEventCount = 0
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
dragEventCount = dragEventCount + 1
end
end,
})
-- Press at (150, 150)
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
-- Update again without moving mouse
element:update(0.016)
element:update(0.016)
lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse doesn't move")
end
-- Test 5: Drag tracking is cleaned up on release
function TestDragEvent:test_drag_tracking_cleaned_up_on_release()
local dragEvents = {}
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
table.insert(dragEvents, { dx = event.dx, dy = event.dy })
end
end,
})
-- First drag sequence
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
love.mouse.setPosition(160, 155)
element:update(0.016)
-- Release
love.mouse.setDown(1, false)
element:update(0.016)
-- Second drag sequence - should start fresh
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
love.mouse.setPosition(155, 152)
element:update(0.016)
lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events total")
lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10")
lu.assertEquals(dragEvents[2].dx, 5, "Second drag dx should be 5 (new drag start)")
end
-- Test 6: Drag works with different mouse buttons
function TestDragEvent:test_drag_works_with_different_buttons()
local dragEvents = {}
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
table.insert(dragEvents, { button = event.button, dx = event.dx })
end
end,
})
-- Right button drag
-- Make sure no other buttons are down
love.mouse.setDown(1, false)
love.mouse.setDown(3, false)
love.mouse.setPosition(150, 150)
love.mouse.setDown(2, true)
element:update(0.016)
love.mouse.setPosition(160, 150)
element:update(0.016)
lu.assertEquals(#dragEvents, 1, "Should receive drag event for right button")
lu.assertEquals(dragEvents[1].button, 2, "Drag event should be for button 2")
lu.assertEquals(dragEvents[1].dx, 10, "Drag dx should be 10")
end
-- Test 7: Drag event contains correct mouse position
function TestDragEvent:test_drag_event_contains_mouse_position()
local dragEvent = nil
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
dragEvent = event
end
end,
})
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
love.mouse.setPosition(175, 165)
element:update(0.016)
lu.assertNotNil(dragEvent, "Drag event should be received")
lu.assertEquals(dragEvent.x, 175, "Drag event x should match current mouse x")
lu.assertEquals(dragEvent.y, 165, "Drag event y should match current mouse y")
end
-- Test 8: No drag event when mouse leaves element
function TestDragEvent:test_no_drag_when_mouse_leaves_element()
local dragEventCount = 0
local element = Gui.new({
x = 100,
y = 100,
width = 200,
height = 100,
onEvent = function(el, event)
if event.type == "drag" then
dragEventCount = dragEventCount + 1
end
end,
})
-- Press inside element
love.mouse.setPosition(150, 150)
love.mouse.setDown(1, true)
element:update(0.016)
-- Move outside element
love.mouse.setPosition(50, 50)
element:update(0.016)
lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse leaves element")
end
lu.LuaUnit.run()

View File

@@ -1,552 +0,0 @@
package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua"
local luaunit = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui, enums, Color = FlexLove.Gui, FlexLove.enums, FlexLove.Color
local Positioning = enums.Positioning
-- Create test cases for scrollbar features
TestScrollbarFeatures = {}
function TestScrollbarFeatures:setUp()
-- Clean up before each test
Gui.destroy()
end
function TestScrollbarFeatures:tearDown()
-- Clean up after each test
Gui.destroy()
end
-- ========================================
-- Test 1: hideScrollbars with boolean value
-- ========================================
function TestScrollbarFeatures:testHideScrollbarsBooleanTrue()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
hideScrollbars = true,
})
-- Verify hideScrollbars is properly initialized
luaunit.assertNotNil(container.hideScrollbars)
luaunit.assertEquals(type(container.hideScrollbars), "table")
luaunit.assertEquals(container.hideScrollbars.vertical, true)
luaunit.assertEquals(container.hideScrollbars.horizontal, true)
end
function TestScrollbarFeatures:testHideScrollbarsBooleanFalse()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
hideScrollbars = false,
})
-- Verify hideScrollbars defaults to showing scrollbars
luaunit.assertNotNil(container.hideScrollbars)
luaunit.assertEquals(container.hideScrollbars.vertical, false)
luaunit.assertEquals(container.hideScrollbars.horizontal, false)
end
-- ========================================
-- Test 2: hideScrollbars with table configuration
-- ========================================
function TestScrollbarFeatures:testHideScrollbarsTableVerticalOnly()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
hideScrollbars = { vertical = true, horizontal = false },
})
-- Verify only vertical scrollbar is hidden
luaunit.assertEquals(container.hideScrollbars.vertical, true)
luaunit.assertEquals(container.hideScrollbars.horizontal, false)
end
function TestScrollbarFeatures:testHideScrollbarsTableHorizontalOnly()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
hideScrollbars = { vertical = false, horizontal = true },
})
-- Verify only horizontal scrollbar is hidden
luaunit.assertEquals(container.hideScrollbars.vertical, false)
luaunit.assertEquals(container.hideScrollbars.horizontal, true)
end
function TestScrollbarFeatures:testHideScrollbarsTableBothHidden()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
hideScrollbars = { vertical = false, horizontal = false },
})
-- Verify both scrollbars are shown
luaunit.assertEquals(container.hideScrollbars.vertical, false)
luaunit.assertEquals(container.hideScrollbars.horizontal, false)
end
-- ========================================
-- Test 3: Default hideScrollbars behavior
-- ========================================
function TestScrollbarFeatures:testHideScrollbarsDefault()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
})
-- Verify default is to show scrollbars (backward compatibility)
luaunit.assertNotNil(container.hideScrollbars)
luaunit.assertEquals(container.hideScrollbars.vertical, false)
luaunit.assertEquals(container.hideScrollbars.horizontal, false)
end
-- ========================================
-- Test 4: Independent hover states initialization
-- ========================================
function TestScrollbarFeatures:testIndependentHoverStatesInitialization()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
})
-- Verify independent hover states are initialized
luaunit.assertNotNil(container._scrollbarHoveredVertical)
luaunit.assertNotNil(container._scrollbarHoveredHorizontal)
luaunit.assertEquals(container._scrollbarHoveredVertical, false)
luaunit.assertEquals(container._scrollbarHoveredHorizontal, false)
end
-- ========================================
-- Test 5: Scrollbar dimensions calculation
-- ========================================
function TestScrollbarFeatures:testScrollbarDimensionsCalculation()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Calculate scrollbar dimensions
local dims = container:_calculateScrollbarDimensions()
-- Verify dimensions structure
luaunit.assertNotNil(dims.vertical)
luaunit.assertNotNil(dims.horizontal)
luaunit.assertNotNil(dims.vertical.visible)
luaunit.assertNotNil(dims.horizontal.visible)
end
-- ========================================
-- Test 6: Scroll position management
-- ========================================
function TestScrollbarFeatures:testScrollPositionSetAndGet()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow to set max scroll
container:_detectOverflow()
-- Set scroll position
container:setScrollPosition(50, 100)
-- Get scroll position
local scrollX, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollX, 50)
luaunit.assertEquals(scrollY, 100)
end
function TestScrollbarFeatures:testScrollPositionClamping()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow to set max scroll
container:_detectOverflow()
-- Try to set scroll position beyond max
container:setScrollPosition(1000, 1000)
-- Get scroll position - should be clamped to max
local scrollX, scrollY = container:getScrollPosition()
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollX, maxScrollX)
luaunit.assertEquals(scrollY, maxScrollY)
end
-- ========================================
-- Test 7: Scroll by delta
-- ========================================
function TestScrollbarFeatures:testScrollByDelta()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Initial scroll position
container:setScrollPosition(50, 50)
-- Scroll by delta
container:scrollBy(10, 20)
-- Verify new position
local scrollX, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollX, 60)
luaunit.assertEquals(scrollY, 70)
end
-- ========================================
-- Test 8: Scroll to top/bottom/left/right
-- ========================================
function TestScrollbarFeatures:testScrollToTop()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Set initial scroll position
container:setScrollPosition(50, 50)
-- Scroll to top
container:scrollToTop()
-- Verify position
local scrollX, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollY, 0)
end
function TestScrollbarFeatures:testScrollToBottom()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Scroll to bottom
container:scrollToBottom()
-- Verify position
local scrollX, scrollY = container:getScrollPosition()
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollY, maxScrollY)
end
function TestScrollbarFeatures:testScrollToLeft()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Set initial scroll position
container:setScrollPosition(50, 50)
-- Scroll to left
container:scrollToLeft()
-- Verify position
local scrollX, scrollY = container:getScrollPosition()
luaunit.assertEquals(scrollX, 0)
end
function TestScrollbarFeatures:testScrollToRight()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Scroll to right
container:scrollToRight()
-- Verify position
local scrollX, scrollY = container:getScrollPosition()
local maxScrollX, maxScrollY = container:getMaxScroll()
luaunit.assertEquals(scrollX, maxScrollX)
end
-- ========================================
-- Test 9: Get scroll percentage
-- ========================================
function TestScrollbarFeatures:testGetScrollPercentage()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Scroll to middle
local maxScrollX, maxScrollY = container:getMaxScroll()
container:setScrollPosition(maxScrollX / 2, maxScrollY / 2)
-- Get scroll percentage
local percentX, percentY = container:getScrollPercentage()
luaunit.assertAlmostEquals(percentX, 0.5, 0.01)
luaunit.assertAlmostEquals(percentY, 0.5, 0.01)
end
-- ========================================
-- Test 10: Has overflow detection
-- ========================================
function TestScrollbarFeatures:testHasOverflow()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows vertically
local child = Gui.new({
parent = container,
width = 150,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Check overflow
local hasOverflowX, hasOverflowY = container:hasOverflow()
luaunit.assertEquals(hasOverflowX, false)
luaunit.assertEquals(hasOverflowY, true)
end
-- ========================================
-- Test 11: Get content size
-- ========================================
function TestScrollbarFeatures:testGetContentSize()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child with specific size
local child = Gui.new({
parent = container,
width = 300,
height = 400,
})
-- Detect overflow
container:_detectOverflow()
-- Get content size
local contentWidth, contentHeight = container:getContentSize()
luaunit.assertEquals(contentWidth, 300)
luaunit.assertEquals(contentHeight, 400)
end
-- ========================================
-- Test 12: Scrollbar configuration options
-- ========================================
function TestScrollbarFeatures:testScrollbarConfigurationOptions()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
scrollbarWidth = 20,
scrollbarRadius = 10,
scrollbarPadding = 5,
scrollSpeed = 30,
scrollbarColor = Color.new(1, 0, 0, 1),
scrollbarTrackColor = Color.new(0, 1, 0, 1),
})
-- Verify custom configuration
luaunit.assertEquals(container.scrollbarWidth, 20)
luaunit.assertEquals(container.scrollbarRadius, 10)
luaunit.assertEquals(container.scrollbarPadding, 5)
luaunit.assertEquals(container.scrollSpeed, 30)
luaunit.assertEquals(container.scrollbarColor.r, 1)
luaunit.assertEquals(container.scrollbarTrackColor.g, 1)
end
-- ========================================
-- Test 13: Wheel scroll handling
-- ========================================
function TestScrollbarFeatures:testWheelScrollHandling()
local container = Gui.new({
x = 0,
y = 0,
width = 200,
height = 200,
overflow = "scroll",
positioning = Positioning.FLEX,
})
-- Add child that overflows
local child = Gui.new({
parent = container,
width = 300,
height = 300,
})
-- Detect overflow
container:_detectOverflow()
-- Set initial position away from top so we can scroll up
container:setScrollPosition(nil, 50)
local initialScrollX, initialScrollY = container:getScrollPosition()
-- Handle wheel scroll (vertical) - positive y means scroll up
local handled = container:_handleWheelScroll(0, 1)
-- Verify scroll was handled and position changed (scrolled up means lower scroll value)
luaunit.assertEquals(handled, true)
local scrollX, scrollY = container:getScrollPosition()
luaunit.assertTrue(scrollY < initialScrollY, "Expected scroll position to decrease when scrolling up")
end
-- Run the tests
luaunit.LuaUnit.run()

View File

@@ -1,273 +0,0 @@
-- Test: Immediate Mode Basic Functionality
package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua"
local luaunit = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
TestImmediateModeBasic = {}
function TestImmediateModeBasic:setUp()
-- Reset GUI state
if Gui.destroy then
Gui.destroy()
end
-- Initialize with immediate mode enabled
Gui.init({
baseScale = { width = 1920, height = 1080 },
immediateMode = true,
})
end
function TestImmediateModeBasic:tearDown()
-- Clear all states
if Gui.clearAllStates then
Gui.clearAllStates()
end
-- Reset immediate mode state
if Gui._immediateModeState then
Gui._immediateModeState.reset()
end
if Gui.destroy then
Gui.destroy()
end
-- Reset immediate mode flag
Gui._immediateMode = false
Gui._frameNumber = 0
end
function TestImmediateModeBasic:test_immediate_mode_enabled()
luaunit.assertTrue(Gui._immediateMode, "Immediate mode should be enabled")
luaunit.assertNotNil(Gui._immediateModeState, "Immediate mode state should be initialized")
end
function TestImmediateModeBasic:test_frame_lifecycle()
-- Begin frame
Gui.beginFrame()
luaunit.assertEquals(Gui._frameNumber, 1, "Frame number should increment to 1")
luaunit.assertEquals(#Gui.topElements, 0, "Top elements should be empty at frame start")
-- Create an element
local button = Gui.new({
id = "test_button",
width = 100,
height = 50,
text = "Click me",
})
luaunit.assertNotNil(button, "Button should be created")
luaunit.assertEquals(button.id, "test_button", "Button should have correct ID")
-- End frame
Gui.endFrame()
-- State should persist
luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state entry")
end
function TestImmediateModeBasic:test_auto_id_generation()
Gui.beginFrame()
-- Create element without explicit ID
local element1 = Gui.new({
width = 100,
height = 50,
})
luaunit.assertNotNil(element1.id, "Element should have auto-generated ID")
luaunit.assertNotEquals(element1.id, "", "Auto-generated ID should not be empty")
Gui.endFrame()
end
function TestImmediateModeBasic:test_state_persistence()
-- Frame 1: Create button and simulate click
Gui.beginFrame()
local button = Gui.new({
id = "persistent_button",
width = 100,
height = 50,
text = "Click me",
})
-- Simulate some state
button._clickCount = 5
button._lastClickTime = 123.45
Gui.endFrame()
-- Frame 2: Recreate button - state should persist
Gui.beginFrame()
local button2 = Gui.new({
id = "persistent_button",
width = 100,
height = 50,
text = "Click me",
})
luaunit.assertEquals(button2._clickCount, 5, "Click count should persist")
luaunit.assertEquals(button2._lastClickTime, 123.45, "Last click time should persist")
Gui.endFrame()
end
function TestImmediateModeBasic:test_helper_functions()
Gui.beginFrame()
-- Test button helper
local button = Gui.button({
id = "helper_button",
width = 100,
height = 50,
text = "Button",
})
luaunit.assertNotNil(button, "Button helper should create element")
luaunit.assertEquals(button.themeComponent, "button", "Button should have theme component")
-- Test panel helper
local panel = Gui.panel({
id = "helper_panel",
width = 200,
height = 200,
})
luaunit.assertNotNil(panel, "Panel helper should create element")
-- Test text helper
local text = Gui.text({
id = "helper_text",
text = "Hello",
})
luaunit.assertNotNil(text, "Text helper should create element")
-- Test input helper
local input = Gui.input({
id = "helper_input",
width = 150,
height = 30,
})
luaunit.assertNotNil(input, "Input helper should create element")
luaunit.assertTrue(input.editable, "Input should be editable")
Gui.endFrame()
end
function TestImmediateModeBasic:test_state_cleanup()
Gui.init({
immediateMode = true,
stateRetentionFrames = 2, -- Very short retention for testing
})
-- Frame 1: Create temporary element
Gui.beginFrame()
Gui.new({
id = "temp_element",
width = 100,
height = 50,
})
Gui.endFrame()
luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state after frame 1")
-- Frame 2: Don't create the element
Gui.beginFrame()
Gui.endFrame()
luaunit.assertEquals(Gui.getStateCount(), 1, "Should still have 1 state after frame 2")
-- Frame 3: Still don't create it
Gui.beginFrame()
Gui.endFrame()
luaunit.assertEquals(Gui.getStateCount(), 1, "Should still have 1 state after frame 3")
-- Frame 4: Should be cleaned up now (retention = 2 frames)
Gui.beginFrame()
Gui.endFrame()
luaunit.assertEquals(Gui.getStateCount(), 0, "State should be cleaned up after retention period")
end
function TestImmediateModeBasic:test_manual_state_management()
Gui.beginFrame()
Gui.new({
id = "element1",
width = 100,
height = 50,
})
Gui.new({
id = "element2",
width = 100,
height = 50,
})
Gui.endFrame()
luaunit.assertEquals(Gui.getStateCount(), 2, "Should have 2 states")
-- Clear specific state
Gui.clearState("element1")
luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state after clearing element1")
-- Clear all states
Gui.clearAllStates()
luaunit.assertEquals(Gui.getStateCount(), 0, "Should have 0 states after clearing all")
end
function TestImmediateModeBasic:test_retained_mode_still_works()
-- Reinitialize without immediate mode
Gui.destroy()
Gui.init({
baseScale = { width = 1920, height = 1080 },
immediateMode = false, -- Explicitly disable
})
luaunit.assertFalse(Gui._immediateMode, "Immediate mode should be disabled")
-- Create element in retained mode
local element = Gui.new({
width = 100,
height = 50,
text = "Retained",
})
luaunit.assertNotNil(element, "Element should be created in retained mode")
luaunit.assertEquals(#Gui.topElements, 1, "Should have 1 top element")
-- Element should persist without beginFrame/endFrame
luaunit.assertEquals(#Gui.topElements, 1, "Element should still exist")
end
function TestImmediateModeBasic:test_state_stats()
Gui.beginFrame()
Gui.new({
id = "stats_test",
width = 100,
height = 50,
})
Gui.endFrame()
local stats = Gui.getStateStats()
luaunit.assertNotNil(stats, "Stats should be returned")
luaunit.assertEquals(stats.stateCount, 1, "Stats should show 1 state")
luaunit.assertNotNil(stats.frameNumber, "Stats should include frame number")
end
luaunit.LuaUnit.run()

View File

@@ -1,322 +0,0 @@
-- ====================
-- StateManager Module Tests
-- ====================
local luaunit = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local StateManager = require("modules.StateManager")
TestStateManager = {}
function TestStateManager:setUp()
-- Reset StateManager before each test
StateManager.clearAllStates()
end
function TestStateManager:tearDown()
-- Clean up after each test
StateManager.clearAllStates()
end
-- ====================
-- Basic State Operations
-- ====================
function TestStateManager:test_getState_createsNewState()
local state = StateManager.getState("test-element")
luaunit.assertNotNil(state)
luaunit.assertEquals(state.hover, false)
luaunit.assertEquals(state.pressed, false)
luaunit.assertEquals(state.focused, false)
luaunit.assertEquals(state.disabled, false)
luaunit.assertEquals(state.active, false)
end
function TestStateManager:test_getState_returnsExistingState()
local state1 = StateManager.getState("test-element")
state1.hover = true
local state2 = StateManager.getState("test-element")
luaunit.assertEquals(state2.hover, true)
luaunit.assertTrue(state1 == state2) -- Same reference
end
function TestStateManager:test_updateState_modifiesState()
StateManager.updateState("test-element", {
hover = true,
pressed = false,
})
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.hover, true)
luaunit.assertEquals(state.pressed, false)
end
function TestStateManager:test_updateState_mergesPartialState()
StateManager.updateState("test-element", { hover = true })
StateManager.updateState("test-element", { pressed = true })
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.hover, true)
luaunit.assertEquals(state.pressed, true)
end
function TestStateManager:test_clearState_removesState()
StateManager.updateState("test-element", { hover = true })
StateManager.clearState("test-element")
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.hover, false) -- New state created with defaults
end
-- ====================
-- Scrollbar State Tests
-- ====================
function TestStateManager:test_scrollbarStates_initialization()
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.scrollbarHoveredVertical, false)
luaunit.assertEquals(state.scrollbarHoveredHorizontal, false)
luaunit.assertEquals(state.scrollbarDragging, false)
luaunit.assertNil(state.hoveredScrollbar)
luaunit.assertEquals(state.scrollbarDragOffset, 0)
end
function TestStateManager:test_scrollbarStates_updates()
StateManager.updateState("test-element", {
scrollbarHoveredVertical = true,
scrollbarDragging = true,
hoveredScrollbar = "vertical",
scrollbarDragOffset = 25,
})
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.scrollbarHoveredVertical, true)
luaunit.assertEquals(state.scrollbarDragging, true)
luaunit.assertEquals(state.hoveredScrollbar, "vertical")
luaunit.assertEquals(state.scrollbarDragOffset, 25)
end
-- ====================
-- Frame Management Tests
-- ====================
function TestStateManager:test_frameNumber_increments()
local frame1 = StateManager.getFrameNumber()
StateManager.incrementFrame()
local frame2 = StateManager.getFrameNumber()
luaunit.assertEquals(frame2, frame1 + 1)
end
function TestStateManager:test_updateState_updatesFrameNumber()
StateManager.incrementFrame()
StateManager.incrementFrame()
local currentFrame = StateManager.getFrameNumber()
StateManager.updateState("test-element", { hover = true })
-- State should exist and be accessible
local state = StateManager.getState("test-element")
luaunit.assertNotNil(state)
end
-- ====================
-- Cleanup Tests
-- ====================
function TestStateManager:test_cleanup_removesStaleStates()
-- Configure short retention
StateManager.configure({ stateRetentionFrames = 5 })
-- Create state
StateManager.updateState("test-element", { hover = true })
-- Advance frames beyond retention
for i = 1, 10 do
StateManager.incrementFrame()
end
-- Cleanup should remove the state
local cleanedCount = StateManager.cleanup()
luaunit.assertEquals(cleanedCount, 1)
-- Reset config
StateManager.configure({ stateRetentionFrames = 60 })
end
function TestStateManager:test_cleanup_keepsActiveStates()
StateManager.configure({ stateRetentionFrames = 5 })
StateManager.updateState("test-element", { hover = true })
-- Update state within retention period
for i = 1, 3 do
StateManager.incrementFrame()
StateManager.updateState("test-element", { hover = true })
end
local cleanedCount = StateManager.cleanup()
luaunit.assertEquals(cleanedCount, 0) -- Should not clean active state
StateManager.configure({ stateRetentionFrames = 60 })
end
function TestStateManager:test_forceCleanupIfNeeded_activatesWhenOverLimit()
StateManager.configure({ maxStateEntries = 5 })
-- Create more states than limit
for i = 1, 10 do
StateManager.updateState("element-" .. i, { hover = true })
end
-- Advance frames
for i = 1, 15 do
StateManager.incrementFrame()
end
local cleanedCount = StateManager.forceCleanupIfNeeded()
luaunit.assertTrue(cleanedCount > 0)
StateManager.configure({ maxStateEntries = 1000 })
end
-- ====================
-- State Count Tests
-- ====================
function TestStateManager:test_getStateCount_returnsCorrectCount()
luaunit.assertEquals(StateManager.getStateCount(), 0)
StateManager.getState("element-1")
StateManager.getState("element-2")
StateManager.getState("element-3")
luaunit.assertEquals(StateManager.getStateCount(), 3)
end
-- ====================
-- Active State Tests
-- ====================
function TestStateManager:test_getActiveState_returnsOnlyActiveProperties()
StateManager.updateState("test-element", {
hover = true,
pressed = false,
focused = true,
})
local activeState = StateManager.getActiveState("test-element")
luaunit.assertEquals(activeState.hover, true)
luaunit.assertEquals(activeState.pressed, false)
luaunit.assertEquals(activeState.focused, true)
luaunit.assertNil(activeState.lastUpdateFrame) -- Should not include frame tracking
end
-- ====================
-- Helper Function Tests
-- ====================
function TestStateManager:test_isHovered_returnsTrueWhenHovered()
StateManager.updateState("test-element", { hover = true })
luaunit.assertTrue(StateManager.isHovered("test-element"))
end
function TestStateManager:test_isHovered_returnsFalseWhenNotHovered()
StateManager.updateState("test-element", { hover = false })
luaunit.assertFalse(StateManager.isHovered("test-element"))
end
function TestStateManager:test_isPressed_returnsTrueWhenPressed()
StateManager.updateState("test-element", { pressed = true })
luaunit.assertTrue(StateManager.isPressed("test-element"))
end
function TestStateManager:test_isFocused_returnsTrueWhenFocused()
StateManager.updateState("test-element", { focused = true })
luaunit.assertTrue(StateManager.isFocused("test-element"))
end
function TestStateManager:test_isDisabled_returnsTrueWhenDisabled()
StateManager.updateState("test-element", { disabled = true })
luaunit.assertTrue(StateManager.isDisabled("test-element"))
end
function TestStateManager:test_isActive_returnsTrueWhenActive()
StateManager.updateState("test-element", { active = true })
luaunit.assertTrue(StateManager.isActive("test-element"))
end
-- ====================
-- ID Generation Tests
-- ====================
function TestStateManager:test_generateID_createsUniqueID()
local id1 = StateManager.generateID({ test = "value1" })
local id2 = StateManager.generateID({ test = "value2" })
luaunit.assertNotNil(id1)
luaunit.assertNotNil(id2)
luaunit.assertTrue(type(id1) == "string")
luaunit.assertTrue(type(id2) == "string")
end
function TestStateManager:test_generateID_withoutProps()
local id = StateManager.generateID(nil)
luaunit.assertNotNil(id)
luaunit.assertTrue(type(id) == "string")
end
-- ====================
-- Scroll Position Tests
-- ====================
function TestStateManager:test_scrollPosition_initialization()
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.scrollX, 0)
luaunit.assertEquals(state.scrollY, 0)
end
function TestStateManager:test_scrollPosition_updates()
StateManager.updateState("test-element", {
scrollX = 100,
scrollY = 200,
})
local state = StateManager.getState("test-element")
luaunit.assertEquals(state.scrollX, 100)
luaunit.assertEquals(state.scrollY, 200)
end
-- ====================
-- Configuration Tests
-- ====================
function TestStateManager:test_configure_updatesSettings()
StateManager.configure({
stateRetentionFrames = 30,
maxStateEntries = 500,
})
-- Test that configuration was applied by creating many states
-- and checking cleanup behavior (indirect test)
for i = 1, 10 do
StateManager.updateState("element-" .. i, { hover = true })
end
luaunit.assertEquals(StateManager.getStateCount(), 10)
-- Reset to defaults
StateManager.configure({
stateRetentionFrames = 60,
maxStateEntries = 1000,
})
end
luaunit.LuaUnit.run()

File diff suppressed because it is too large Load Diff

View File

@@ -1,438 +0,0 @@
-- ====================
-- Password Mode Tests
-- ====================
-- Test suite for password mode functionality in FlexLove input fields
local lu = require("testing.luaunit")
local loveStub = require("testing.loveStub")
local utf8 = require("utf8")
-- Setup LÖVE environment
_G.love = loveStub
-- Load FlexLove after setting up love stub
local FlexLove = require("FlexLove")
local StateManager = require("modules.StateManager")
-- Test fixtures
local testElement
TestPasswordMode = {}
function TestPasswordMode:setUp()
-- Clear all keyboard modifier states at start of each test
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
-- Reset FlexLove state
FlexLove.Gui.topElements = {}
FlexLove.Gui._focusedElement = nil
-- Create a test password input element
testElement = FlexLove.Element.new({
x = 100,
y = 100,
width = 200,
height = 40,
editable = true,
passwordMode = true,
text = "",
})
end
function TestPasswordMode:tearDown()
-- Clear all keyboard modifier states
love.keyboard.setDown("lshift", false)
love.keyboard.setDown("rshift", false)
love.keyboard.setDown("lctrl", false)
love.keyboard.setDown("rctrl", false)
love.keyboard.setDown("lalt", false)
love.keyboard.setDown("ralt", false)
love.keyboard.setDown("lgui", false)
love.keyboard.setDown("rgui", false)
-- Clear StateManager to prevent test contamination
StateManager.reset()
testElement = nil
FlexLove.Gui.topElements = {}
FlexLove.Gui._focusedElement = nil
end
-- ====================
-- Property Tests
-- ====================
function TestPasswordMode:testPasswordModePropertyExists()
-- Test that passwordMode property exists and can be set
lu.assertNotNil(testElement.passwordMode)
lu.assertTrue(testElement.passwordMode)
end
function TestPasswordMode:testPasswordModeDefaultIsFalse()
-- Test that passwordMode defaults to false
local normalElement = FlexLove.Element.new({
x = 100,
y = 100,
width = 200,
height = 40,
editable = true,
text = "Normal text",
})
lu.assertFalse(normalElement.passwordMode or false)
end
function TestPasswordMode:testPasswordModeIsSingleLineOnly()
-- Password mode should only work with single-line inputs
-- The constraint is enforced in Element.lua line 292-293
local multilinePassword = FlexLove.Element.new({
x = 100,
y = 100,
width = 200,
height = 100,
editable = true,
multiline = true,
passwordMode = true,
text = "Password",
})
-- Based on the constraint, multiline should be set to false
lu.assertFalse(multilinePassword.multiline)
end
-- ====================
-- Text Buffer Tests
-- ====================
function TestPasswordMode:testActualTextContentRemains()
-- Insert text into password field
testElement:focus()
testElement:insertText("S")
testElement:insertText("e")
testElement:insertText("c")
testElement:insertText("r")
testElement:insertText("e")
testElement:insertText("t")
-- Verify actual text buffer contains the real text
lu.assertEquals(testElement._textBuffer, "Secret")
lu.assertEquals(testElement:getText(), "Secret")
end
function TestPasswordMode:testPasswordTextIsNotModified()
-- Set initial text
testElement:setText("MyPassword123")
-- The actual buffer should contain the real password
lu.assertEquals(testElement._textBuffer, "MyPassword123")
lu.assertEquals(testElement:getText(), "MyPassword123")
end
-- ====================
-- Cursor Position Tests
-- ====================
function TestPasswordMode:testCursorPositionWithPasswordMode()
testElement:setText("test")
testElement:focus()
-- Set cursor to end
testElement:setCursorPosition(4)
lu.assertEquals(testElement._cursorPosition, 4)
-- Move cursor to middle
testElement:setCursorPosition(2)
lu.assertEquals(testElement._cursorPosition, 2)
-- Move cursor to start
testElement:setCursorPosition(0)
lu.assertEquals(testElement._cursorPosition, 0)
end
function TestPasswordMode:testCursorMovementInPasswordField()
testElement:setText("password")
testElement:focus()
testElement:setCursorPosition(0)
-- Move right
testElement:moveCursorBy(1)
lu.assertEquals(testElement._cursorPosition, 1)
-- Move right again
testElement:moveCursorBy(1)
lu.assertEquals(testElement._cursorPosition, 2)
-- Move left
testElement:moveCursorBy(-1)
lu.assertEquals(testElement._cursorPosition, 1)
end
-- ====================
-- Text Editing Tests
-- ====================
function TestPasswordMode:testInsertTextInPasswordMode()
testElement:focus()
testElement:setCursorPosition(0)
testElement:insertText("a")
lu.assertEquals(testElement._textBuffer, "a")
testElement:insertText("b")
lu.assertEquals(testElement._textBuffer, "ab")
testElement:insertText("c")
lu.assertEquals(testElement._textBuffer, "abc")
end
function TestPasswordMode:testBackspaceInPasswordMode()
testElement:setText("password")
testElement:focus()
testElement:setCursorPosition(8) -- End of text
-- Delete last character
testElement:keypressed("backspace", nil, false)
lu.assertEquals(testElement._textBuffer, "passwor")
-- Delete another character
testElement:keypressed("backspace", nil, false)
lu.assertEquals(testElement._textBuffer, "passwo")
end
function TestPasswordMode:testDeleteInPasswordMode()
testElement:setText("password")
testElement:focus()
testElement:setCursorPosition(0) -- Start of text
-- Delete first character
testElement:keypressed("delete", nil, false)
lu.assertEquals(testElement._textBuffer, "assword")
-- Delete another character
testElement:keypressed("delete", nil, false)
lu.assertEquals(testElement._textBuffer, "ssword")
end
function TestPasswordMode:testInsertTextAtPosition()
testElement:setText("pass")
testElement:focus()
testElement:setCursorPosition(2) -- Between 'pa' and 'ss'
testElement:insertText("x")
lu.assertEquals(testElement._textBuffer, "paxss")
lu.assertEquals(testElement._cursorPosition, 3)
end
-- ====================
-- Selection Tests
-- ====================
function TestPasswordMode:testTextSelectionInPasswordMode()
testElement:setText("password")
testElement:focus()
-- Select from position 2 to 5
testElement:setSelection(2, 5)
local selStart, selEnd = testElement:getSelection()
lu.assertEquals(selStart, 2)
lu.assertEquals(selEnd, 5)
lu.assertTrue(testElement:hasSelection())
end
function TestPasswordMode:testDeleteSelectionInPasswordMode()
testElement:setText("password")
testElement:focus()
-- Select "sswo" (positions 2-6)
testElement:setSelection(2, 6)
-- Delete selection
testElement:deleteSelection()
lu.assertEquals(testElement._textBuffer, "pard")
lu.assertFalse(testElement:hasSelection())
end
function TestPasswordMode:testReplaceSelectionInPasswordMode()
testElement:setText("password")
testElement:focus()
-- Select "sswo" (positions 2-6)
testElement:setSelection(2, 6)
-- Type new text (should replace selection)
testElement:textinput("X")
lu.assertEquals(testElement._textBuffer, "paXrd")
end
function TestPasswordMode:testSelectAllInPasswordMode()
testElement:setText("secret")
testElement:focus()
testElement:selectAll()
local selStart, selEnd = testElement:getSelection()
lu.assertEquals(selStart, 0)
lu.assertEquals(selEnd, 6)
lu.assertTrue(testElement:hasSelection())
end
-- ====================
-- Integration Tests
-- ====================
function TestPasswordMode:testPasswordModeWithMaxLength()
testElement.maxLength = 5
testElement:focus()
testElement:insertText("1")
testElement:insertText("2")
testElement:insertText("3")
testElement:insertText("4")
testElement:insertText("5")
testElement:insertText("6") -- Should be rejected
lu.assertEquals(testElement._textBuffer, "12345")
lu.assertEquals(utf8.len(testElement._textBuffer), 5)
end
function TestPasswordMode:testPasswordModeWithPlaceholder()
local passwordWithPlaceholder = FlexLove.Element.new({
x = 100,
y = 100,
width = 200,
height = 40,
editable = true,
passwordMode = true,
placeholder = "Enter password",
text = "",
})
-- When empty and not focused, placeholder should be available
lu.assertEquals(passwordWithPlaceholder.placeholder, "Enter password")
lu.assertEquals(passwordWithPlaceholder._textBuffer, "")
-- When text is added, actual text should be stored
passwordWithPlaceholder:focus()
passwordWithPlaceholder:insertText("secret")
lu.assertEquals(passwordWithPlaceholder._textBuffer, "secret")
end
function TestPasswordMode:testPasswordModeClearText()
testElement:setText("password123")
lu.assertEquals(testElement._textBuffer, "password123")
-- Clear text
testElement:setText("")
lu.assertEquals(testElement._textBuffer, "")
lu.assertEquals(testElement:getText(), "")
end
function TestPasswordMode:testPasswordModeToggle()
-- Start with password mode off
local toggleElement = FlexLove.Element.new({
x = 100,
y = 100,
width = 200,
height = 40,
editable = true,
passwordMode = false,
text = "visible",
})
lu.assertEquals(toggleElement._textBuffer, "visible")
lu.assertFalse(toggleElement.passwordMode)
-- Enable password mode
toggleElement.passwordMode = true
lu.assertTrue(toggleElement.passwordMode)
-- Text buffer should remain unchanged
lu.assertEquals(toggleElement._textBuffer, "visible")
-- Disable password mode again
toggleElement.passwordMode = false
lu.assertFalse(toggleElement.passwordMode)
lu.assertEquals(toggleElement._textBuffer, "visible")
end
-- ====================
-- UTF-8 Support Tests
-- ====================
function TestPasswordMode:testPasswordModeWithUTF8Characters()
testElement:focus()
-- Insert UTF-8 characters
testElement:insertText("h")
testElement:insertText("é")
testElement:insertText("l")
testElement:insertText("l")
testElement:insertText("ö")
-- Text buffer should contain actual UTF-8 text
lu.assertEquals(testElement._textBuffer, "héllö")
lu.assertEquals(utf8.len(testElement._textBuffer), 5)
end
function TestPasswordMode:testPasswordModeCursorWithUTF8()
testElement:setText("café")
testElement:focus()
-- Move cursor through UTF-8 text
testElement:setCursorPosition(0)
lu.assertEquals(testElement._cursorPosition, 0)
testElement:moveCursorBy(1)
lu.assertEquals(testElement._cursorPosition, 1)
testElement:moveCursorBy(1)
lu.assertEquals(testElement._cursorPosition, 2)
testElement:setCursorPosition(4)
lu.assertEquals(testElement._cursorPosition, 4)
end
-- ====================
-- Edge Cases
-- ====================
function TestPasswordMode:testPasswordModeWithEmptyString()
testElement:setText("")
lu.assertEquals(testElement._textBuffer, "")
lu.assertEquals(testElement:getText(), "")
end
function TestPasswordMode:testPasswordModeWithSingleCharacter()
testElement:setText("x")
lu.assertEquals(testElement._textBuffer, "x")
lu.assertEquals(utf8.len(testElement._textBuffer), 1)
end
function TestPasswordMode:testPasswordModeWithLongPassword()
local longPassword = string.rep("a", 100)
testElement:setText(longPassword)
lu.assertEquals(testElement._textBuffer, longPassword)
lu.assertEquals(utf8.len(testElement._textBuffer), 100)
end
function TestPasswordMode:testPasswordModeSetTextUpdatesBuffer()
testElement:setText("initial")
lu.assertEquals(testElement._textBuffer, "initial")
testElement:setText("updated")
lu.assertEquals(testElement._textBuffer, "updated")
testElement:setText("")
lu.assertEquals(testElement._textBuffer, "")
end
lu.LuaUnit.run()

View File

@@ -1,423 +0,0 @@
-- Test: Stable ID Generation in Immediate Mode
package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua"
local luaunit = require("testing.luaunit")
require("testing.loveStub") -- Required to mock LOVE functions
local FlexLove = require("FlexLove")
local Gui = FlexLove.Gui
TestStableIDGeneration = {}
function TestStableIDGeneration:setUp()
-- Reset GUI state
if Gui.destroy then
Gui.destroy()
end
-- Initialize with immediate mode enabled
Gui.init({
baseScale = { width = 1920, height = 1080 },
immediateMode = true,
})
end
function TestStableIDGeneration:tearDown()
-- Clear all states
if Gui.clearAllStates then
Gui.clearAllStates()
end
-- Reset immediate mode state
if Gui._immediateModeState then
Gui._immediateModeState.reset()
end
if Gui.destroy then
Gui.destroy()
end
-- Reset immediate mode flag
Gui._immediateMode = false
Gui._frameNumber = 0
end
function TestStableIDGeneration:test_child_ids_stable_across_frames()
-- Frame 1: Create parent with children
Gui.beginFrame()
local parent = Gui.new({
id = "test_parent",
width = 400,
height = 300,
})
local child1 = Gui.new({
parent = parent,
width = 100,
height = 50,
text = "Child 1",
})
local child2 = Gui.new({
parent = parent,
width = 100,
height = 50,
text = "Child 2",
})
local child1Id = child1.id
local child2Id = child2.id
Gui.endFrame()
-- Frame 2: Recreate same structure
Gui.beginFrame()
local parent2 = Gui.new({
id = "test_parent",
width = 400,
height = 300,
})
local child1_2 = Gui.new({
parent = parent2,
width = 100,
height = 50,
text = "Child 1",
})
local child2_2 = Gui.new({
parent = parent2,
width = 100,
height = 50,
text = "Child 2",
})
Gui.endFrame()
-- IDs should be stable
luaunit.assertEquals(child1_2.id, child1Id, "Child 1 ID should be stable across frames")
luaunit.assertEquals(child2_2.id, child2Id, "Child 2 ID should be stable across frames")
end
function TestStableIDGeneration:test_conditional_rendering_does_not_affect_siblings()
-- Frame 1: Create parent with 3 children
Gui.beginFrame()
local parent1 = Gui.new({
id = "test_parent2",
width = 400,
height = 300,
})
local child1 = Gui.new({
parent = parent1,
width = 100,
height = 50,
text = "Child 1",
})
local child2 = Gui.new({
parent = parent1,
width = 100,
height = 50,
text = "Child 2",
})
local child3 = Gui.new({
parent = parent1,
width = 100,
height = 50,
text = "Child 3",
})
local child1Id = child1.id
local child3Id = child3.id
Gui.endFrame()
-- Frame 2: Skip child 2 (conditional rendering)
Gui.beginFrame()
local parent2 = Gui.new({
id = "test_parent2",
width = 400,
height = 300,
})
local child1_2 = Gui.new({
parent = parent2,
width = 100,
height = 50,
text = "Child 1",
})
-- Child 2 not rendered this frame
local child3_2 = Gui.new({
parent = parent2,
width = 100,
height = 50,
text = "Child 3",
})
Gui.endFrame()
-- Child 1 should keep its ID
luaunit.assertEquals(child1_2.id, child1Id, "Child 1 ID should remain stable")
-- Child 3 will have a different ID because it's now at sibling index 1 instead of 2
-- This is EXPECTED behavior - the position in the tree changed
luaunit.assertNotEquals(child3_2.id, child3Id, "Child 3 ID changes because its sibling position changed")
end
function TestStableIDGeneration:test_input_field_maintains_state_across_frames()
-- Frame 1: Create input field and simulate text entry
Gui.beginFrame()
local container = Gui.new({
id = "test_container",
width = 400,
height = 300,
})
local input1 = Gui.new({
parent = container,
width = 200,
height = 40,
editable = true,
text = "",
})
-- Simulate text input
input1._textBuffer = "Hello World"
input1._focused = true
local inputId = input1.id
Gui.endFrame()
-- Frame 2: Recreate same structure
Gui.beginFrame()
local container2 = Gui.new({
id = "test_container",
width = 400,
height = 300,
})
local input2 = Gui.new({
parent = container2,
width = 200,
height = 40,
editable = true,
text = "",
})
Gui.endFrame()
-- Input should have same ID and restored state
luaunit.assertEquals(input2.id, inputId, "Input field ID should be stable")
luaunit.assertEquals(input2._textBuffer, "Hello World", "Input text should be restored")
luaunit.assertTrue(input2._focused, "Input focus state should be restored")
end
function TestStableIDGeneration:test_nested_children_stable_ids()
-- Frame 1: Create nested hierarchy
Gui.beginFrame()
local root = Gui.new({
id = "test_root",
width = 400,
height = 300,
})
local level1 = Gui.new({
parent = root,
width = 300,
height = 200,
})
local level2 = Gui.new({
parent = level1,
width = 200,
height = 100,
})
local deepChild = Gui.new({
parent = level2,
width = 100,
height = 50,
text = "Deep Child",
})
local deepChildId = deepChild.id
Gui.endFrame()
-- Frame 2: Recreate same nested structure
Gui.beginFrame()
local root2 = Gui.new({
id = "test_root",
width = 400,
height = 300,
})
local level1_2 = Gui.new({
parent = root2,
width = 300,
height = 200,
})
local level2_2 = Gui.new({
parent = level1_2,
width = 200,
height = 100,
})
local deepChild2 = Gui.new({
parent = level2_2,
width = 100,
height = 50,
text = "Deep Child",
})
Gui.endFrame()
-- Deep child ID should be stable
luaunit.assertEquals(deepChild2.id, deepChildId, "Deeply nested child ID should be stable")
end
function TestStableIDGeneration:test_siblings_with_different_props_have_different_ids()
-- Frame 1: Create siblings with different properties
Gui.beginFrame()
local parent = Gui.new({
width = 400,
height = 300,
})
local child1 = Gui.new({
parent = parent,
width = 100,
height = 50,
text = "Button 1",
})
local child2 = Gui.new({
parent = parent,
width = 100,
height = 50,
text = "Button 2",
})
Gui.endFrame()
-- Siblings should have different IDs due to different sibling indices and props
luaunit.assertNotEquals(child1.id, child2.id, "Siblings should have different IDs")
end
-- Helper function to create elements from consistent location (simulates real usage)
local function createTopLevelElements()
local elements = {}
for i = 1, 3 do
elements[i] = Gui.new({ width = 100, height = 50, text = "Element " .. i })
end
return elements
end
function TestStableIDGeneration:test_top_level_elements_use_call_site_counter()
-- Frame 1: Create multiple top-level elements at same location (in loop)
Gui.beginFrame()
local elements = createTopLevelElements()
local ids = {}
for i = 1, 3 do
ids[i] = elements[i].id
end
Gui.endFrame()
-- Frame 2: Recreate same elements from SAME line (via helper)
Gui.beginFrame()
local elements2 = createTopLevelElements()
Gui.endFrame()
-- IDs should be stable for top-level elements when called from same location
for i = 1, 3 do
luaunit.assertEquals(elements2[i].id, ids[i], "Top-level element " .. i .. " ID should be stable")
end
end
function TestStableIDGeneration:test_mixed_conditional_and_stable_elements()
-- Simulate a real-world scenario: navigation with conditional screens
-- Frame 1: Screen A with input field
Gui.beginFrame()
local backdrop1 = Gui.new({
id = "backdrop",
width = "100%",
height = "100%",
})
local window1 = Gui.new({
parent = backdrop1,
width = "80%",
height = "80%",
})
-- Screen A content
local inputA = Gui.new({
parent = window1,
width = 200,
height = 40,
editable = true,
text = "Screen A Input",
})
inputA._textBuffer = "User typed this"
inputA._focused = true
local inputAId = inputA.id
Gui.endFrame()
-- Frame 2: Same screen structure (user is still on Screen A)
Gui.beginFrame()
local backdrop2 = Gui.new({
id = "backdrop",
width = "100%",
height = "100%",
})
local window2 = Gui.new({
parent = backdrop2,
width = "80%",
height = "80%",
})
-- Screen A content (same position in tree)
local inputA2 = Gui.new({
parent = window2,
width = 200,
height = 40,
editable = true,
text = "Screen A Input",
})
Gui.endFrame()
-- Input field should maintain ID and state
luaunit.assertEquals(inputA2.id, inputAId, "Input field ID should be stable within same screen")
luaunit.assertEquals(inputA2._textBuffer, "User typed this", "Input text should be preserved")
luaunit.assertTrue(inputA2._focused, "Input focus should be preserved")
end
luaunit.LuaUnit.run()

View File

@@ -6,43 +6,7 @@ _G.RUNNING_ALL_TESTS = true
local luaunit = require("testing.luaunit")
-- Run all tests in the __tests__ directory
local testFiles = {
"testing/__tests__/01_absolute_positioning_basic_tests.lua",
"testing/__tests__/02_absolute_positioning_child_layout_tests.lua",
"testing/__tests__/03_flex_direction_horizontal_tests.lua",
"testing/__tests__/04_flex_direction_vertical_tests.lua",
"testing/__tests__/05_justify_content_tests.lua",
"testing/__tests__/06_align_items_tests.lua",
"testing/__tests__/07_flex_wrap_tests.lua",
"testing/__tests__/08_comprehensive_flex_tests.lua",
"testing/__tests__/09_layout_validation_tests.lua",
"testing/__tests__/10_performance_tests.lua",
"testing/__tests__/11_auxiliary_functions_tests.lua",
"testing/__tests__/12_units_system_tests.lua",
"testing/__tests__/13_relative_positioning_tests.lua",
"testing/__tests__/14_text_scaling_basic_tests.lua",
"testing/__tests__/15_grid_layout_tests.lua",
"testing/__tests__/16_event_system_tests.lua",
"testing/__tests__/17_sibling_space_reservation_tests.lua",
"testing/__tests__/18_font_family_inheritance_tests.lua",
"testing/__tests__/19_negative_margin_tests.lua",
"testing/__tests__/20_padding_resize_tests.lua",
"testing/__tests__/21_image_scaler_nearest_tests.lua",
"testing/__tests__/22_image_scaler_bilinear_tests.lua",
"testing/__tests__/23_blur_effects_tests.lua",
"testing/__tests__/24_keyboard_input_tests.lua",
"testing/__tests__/25_image_cache_tests.lua",
"testing/__tests__/26_object_fit_modes_tests.lua",
"testing/__tests__/27_object_position_tests.lua",
"testing/__tests__/28_element_image_integration_tests.lua",
"testing/__tests__/29_drag_event_tests.lua",
"testing/__tests__/30_scrollbar_features_tests.lua",
"testing/__tests__/31_immediate_mode_basic_tests.lua",
"testing/__tests__/32_state_manager_tests.lua",
"testing/__tests__/33_input_field_tests.lua",
"testing/__tests__/34_password_mode_tests.lua",
"testing/__tests__/35_stable_id_generation_tests.lua",
}
local testFiles = {}
local success = true
print("========================================")