424 lines
8.8 KiB
Lua
424 lines
8.8 KiB
Lua
-- 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()
|