starting mode escape hatch

This commit is contained in:
Michael Freno
2025-12-11 11:37:20 -05:00
parent eaf7383b8f
commit 0bceade7d5
8 changed files with 1420 additions and 16 deletions

View File

@@ -0,0 +1,343 @@
-- Test suite for Element mode override functionality
package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Load love stub before anything else
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
-- Load FlexLove which properly initializes all dependencies
local FlexLove = require("FlexLove")
local StateManager = require("modules.StateManager")
TestElementModeOverride = {}
function TestElementModeOverride:setUp()
-- Initialize FlexLove in immediate mode by default
FlexLove.init({ immediateMode = true })
FlexLove.beginFrame()
end
function TestElementModeOverride:tearDown()
if FlexLove.getMode() == "immediate" then
FlexLove.endFrame()
end
-- Reset to default state
FlexLove.init({ immediateMode = false })
end
-- Test 01: Mode resolution - explicit immediate
function TestElementModeOverride:test_modeResolution_explicitImmediate()
local element = FlexLove.new({
mode = "immediate",
text = "Test",
})
luaunit.assertEquals(element._elementMode, "immediate")
end
-- Test 02: Mode resolution - explicit retained
function TestElementModeOverride:test_modeResolution_explicitRetained()
local element = FlexLove.new({
mode = "retained",
text = "Test",
})
luaunit.assertEquals(element._elementMode, "retained")
end
-- Test 03: Mode resolution - nil uses global (immediate)
function TestElementModeOverride:test_modeResolution_nilUsesGlobalImmediate()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
local element = FlexLove.new({
text = "Test",
})
luaunit.assertEquals(element._elementMode, "immediate")
end
-- Test 04: Mode resolution - nil uses global (retained)
function TestElementModeOverride:test_modeResolution_nilUsesGlobalRetained()
FlexLove.setMode("retained")
local element = FlexLove.new({
text = "Test",
})
luaunit.assertEquals(element._elementMode, "retained")
end
-- Test 05: ID auto-generation only for immediate mode
function TestElementModeOverride:test_idGeneration_onlyForImmediate()
-- Immediate element without ID should get auto-generated ID
local immediateEl = FlexLove.new({
mode = "immediate",
text = "Immediate",
})
luaunit.assertNotNil(immediateEl.id)
luaunit.assertNotEquals(immediateEl.id, "")
-- Retained element without ID should have empty ID
local retainedEl = FlexLove.new({
mode = "retained",
text = "Retained",
})
luaunit.assertEquals(retainedEl.id, "")
end
-- Test 06: Immediate override in retained context
function TestElementModeOverride:test_immediateOverrideInRetainedContext()
FlexLove.setMode("retained")
local element = FlexLove.new({
mode = "immediate",
id = "test-immediate",
text = "Immediate in retained context",
})
luaunit.assertEquals(element._elementMode, "immediate")
luaunit.assertEquals(element.id, "test-immediate")
end
-- Test 07: Retained override in immediate context
function TestElementModeOverride:test_retainedOverrideInImmediateContext()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
local element = FlexLove.new({
mode = "retained",
text = "Retained in immediate context",
})
luaunit.assertEquals(element._elementMode, "retained")
luaunit.assertEquals(element.id, "") -- Should not auto-generate ID
end
-- Test 08: Mixed-mode parent-child (immediate parent, retained child)
function TestElementModeOverride:test_mixedMode_immediateParent_retainedChild()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
local parent = FlexLove.new({
mode = "immediate",
id = "parent",
text = "Parent",
})
local child = FlexLove.new({
mode = "retained",
parent = parent,
text = "Child",
})
luaunit.assertEquals(parent._elementMode, "immediate")
luaunit.assertEquals(child._elementMode, "retained")
-- Child should not inherit parent mode
luaunit.assertNotEquals(child._elementMode, parent._elementMode)
end
-- Test 09: Mixed-mode parent-child (retained parent, immediate child)
function TestElementModeOverride:test_mixedMode_retainedParent_immediateChild()
FlexLove.setMode("retained")
local parent = FlexLove.new({
mode = "retained",
text = "Parent",
})
local child = FlexLove.new({
mode = "immediate",
id = "child",
parent = parent,
text = "Child",
})
luaunit.assertEquals(parent._elementMode, "retained")
luaunit.assertEquals(child._elementMode, "immediate")
luaunit.assertEquals(child.id, "child")
end
-- Test 10: Frame registration only for immediate elements
function TestElementModeOverride:test_frameRegistration_onlyImmediate()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
local immediate1 = FlexLove.new({
mode = "immediate",
id = "imm1",
text = "Immediate 1",
})
local retained1 = FlexLove.new({
mode = "retained",
text = "Retained 1",
})
local immediate2 = FlexLove.new({
mode = "immediate",
id = "imm2",
text = "Immediate 2",
})
-- Count immediate elements in _currentFrameElements
local immediateCount = 0
for _, element in ipairs(FlexLove._currentFrameElements) do
if element._elementMode == "immediate" then
immediateCount = immediateCount + 1
end
end
luaunit.assertEquals(immediateCount, 2)
end
-- Test 11: Layout calculation for retained parent with immediate children
function TestElementModeOverride:test_layoutRetainedParentWithImmediateChildren()
FlexLove.setMode("retained")
-- Create retained parent with flex layout
local parent = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
positioning = "flex",
flexDirection = "horizontal",
gap = 10,
})
-- Switch to immediate mode and add children
FlexLove.setMode("immediate")
FlexLove.beginFrame()
local child1 = FlexLove.new({
mode = "immediate",
id = "child1",
parent = parent,
width = 100,
height = 50,
})
local child2 = FlexLove.new({
mode = "immediate",
id = "child2",
parent = parent,
width = 100,
height = 50,
})
FlexLove.endFrame()
-- Verify children are positioned correctly by flex layout
luaunit.assertEquals(child1.x, 0)
luaunit.assertEquals(child1.y, 0)
luaunit.assertEquals(child2.x, 110) -- 100 + 10 gap
luaunit.assertEquals(child2.y, 0)
end
-- Test 12: Deeply nested mixed modes (retained -> immediate -> retained)
function TestElementModeOverride:test_deeplyNestedMixedModes()
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Level 1: Retained root
local root = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
positioning = "flex",
flexDirection = "vertical",
gap = 5,
})
-- Level 2: Immediate child of retained parent
local middle = FlexLove.new({
mode = "immediate",
id = "middle",
parent = root,
width = 400,
height = 300,
positioning = "flex",
flexDirection = "horizontal",
gap = 10,
})
-- Level 3: Retained grandchildren
local leaf1 = FlexLove.new({
mode = "retained",
parent = middle,
width = 100,
height = 50,
})
local leaf2 = FlexLove.new({
mode = "retained",
parent = middle,
width = 100,
height = 50,
})
FlexLove.endFrame()
-- Verify all levels are positioned correctly
luaunit.assertEquals(root.x, 0)
luaunit.assertEquals(root.y, 0)
luaunit.assertEquals(middle.x, 0)
luaunit.assertEquals(middle.y, 0)
luaunit.assertEquals(leaf1.x, 0)
luaunit.assertEquals(leaf1.y, 0)
luaunit.assertEquals(leaf2.x, 110) -- 100 + 10 gap
luaunit.assertEquals(leaf2.y, 0)
end
-- Test 13: Immediate children of retained parents receive updates
function TestElementModeOverride:test_immediateChildrenOfRetainedParentsGetUpdated()
FlexLove.setMode("retained")
local updateCount = 0
-- Create retained parent
local parent = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
})
-- Switch to immediate mode for child
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Create immediate child that tracks updates
local child = FlexLove.new({
mode = "immediate",
id = "updateTest",
parent = parent,
width = 100,
height = 50,
})
-- Manually call update on the child to simulate what endFrame should do
-- In the real implementation, endFrame calls update on retained parents,
-- which cascades to immediate children
FlexLove.endFrame()
-- The child should be in the state manager
local state = StateManager.getState("updateTest")
luaunit.assertNotNil(state)
end
os.exit(luaunit.LuaUnit.run())

View File

@@ -0,0 +1,192 @@
-- Test event handling for immediate children of retained parents
package.path = package.path .. ";./?.lua;./modules/?.lua"
-- Load love stub before anything else
require("testing.loveStub")
local luaunit = require("testing.luaunit")
local ErrorHandler = require("modules.ErrorHandler")
-- Initialize ErrorHandler
ErrorHandler.init({})
-- Setup package loader to map FlexLove.modules.X to modules/X
local originalSearchers = package.searchers or package.loaders
table.insert(originalSearchers, 2, function(modname)
if modname:match("^FlexLove%.modules%.") then
local moduleName = modname:gsub("^FlexLove%.modules%.", "")
return function() return require("modules." .. moduleName) end
end
end)
local FlexLove = require("FlexLove")
local Color = require("modules.Color")
local Element = require("modules.Element")
TestMixedModeEvents = {}
function TestMixedModeEvents:setUp()
FlexLove.init({ immediateMode = false })
end
function TestMixedModeEvents:tearDown()
if FlexLove.getMode() == "immediate" then
FlexLove.endFrame()
end
FlexLove.init({ immediateMode = false })
end
-- Test that immediate children of retained parents can handle events
function TestMixedModeEvents:test_immediateChildOfRetainedParentHandlesEvents()
local eventFired = false
local eventType = nil
-- Create retained parent
local parent = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
})
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Create immediate child with event handler
local child = FlexLove.new({
mode = "immediate",
id = "eventTestChild",
parent = parent,
x = 0,
y = 0,
width = 100,
height = 50,
onEvent = function(element, event)
eventFired = true
eventType = event.type
end,
})
FlexLove.endFrame()
-- Verify child is positioned correctly
luaunit.assertEquals(child.x, 0)
luaunit.assertEquals(child.y, 0)
-- Manually call the event handler (simulating event processing)
-- In the real app, this would be triggered by mousepressed/released
if child.onEvent then
child.onEvent(child, { type = "release", x = 50, y = 25, button = 1 })
end
-- Verify event was handled
luaunit.assertTrue(eventFired)
luaunit.assertEquals(eventType, "release")
end
-- Test that hover state is tracked for immediate children
function TestMixedModeEvents:test_immediateChildOfRetainedParentTracksHover()
FlexLove.setMode("retained")
-- Create retained parent
local parent = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
})
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Create immediate child
local child = FlexLove.new({
mode = "immediate",
id = "hoverTestChild",
parent = parent,
x = 100,
y = 100,
width = 100,
height = 50,
})
FlexLove.endFrame()
-- Child should have event handler module
luaunit.assertNotNil(child._eventHandler)
-- Verify child can track hover state (stored in StateManager for immediate mode)
-- The actual hover detection happens in Element's event processing
luaunit.assertEquals(child._elementMode, "immediate")
end
-- Test multiple immediate children with different event handlers
function TestMixedModeEvents:test_multipleImmediateChildrenHandleEventsIndependently()
local button1Clicked = false
local button2Clicked = false
FlexLove.setMode("retained")
-- Create retained parent
local parent = FlexLove.new({
mode = "retained",
width = 800,
height = 600,
positioning = "flex",
flexDirection = "horizontal",
gap = 10,
})
FlexLove.setMode("immediate")
FlexLove.beginFrame()
-- Create two immediate button children
local button1 = FlexLove.new({
mode = "immediate",
id = "button1",
parent = parent,
width = 100,
height = 50,
onEvent = function(element, event)
if event.type == "release" then
button1Clicked = true
end
end,
})
local button2 = FlexLove.new({
mode = "immediate",
id = "button2",
parent = parent,
width = 100,
height = 50,
onEvent = function(element, event)
if event.type == "release" then
button2Clicked = true
end
end,
})
FlexLove.endFrame()
-- Verify buttons are positioned correctly
luaunit.assertEquals(button1.x, 0)
luaunit.assertEquals(button2.x, 110) -- 100 + 10 gap
-- Simulate clicking button1
if button1.onEvent then
button1.onEvent(button1, { type = "release", x = 50, y = 25, button = 1 })
end
luaunit.assertTrue(button1Clicked)
luaunit.assertFalse(button2Clicked)
-- Simulate clicking button2
if button2.onEvent then
button2.onEvent(button2, { type = "release", x = 150, y = 25, button = 1 })
end
luaunit.assertTrue(button1Clicked)
luaunit.assertTrue(button2Clicked)
end
os.exit(luaunit.LuaUnit.run())