remove subpar tests, update examples
This commit is contained in:
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
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user