From e4ab6befa417b083370aa7bfefe75110925c807c Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Fri, 19 Sep 2025 09:50:53 -0400 Subject: [PATCH] finished test expansion --- testing/__tests__/10_performance_tests.lua | 1153 +++++++++++- .../11_auxiliary_functions_tests.lua | 1586 +++++++++++++++-- 2 files changed, 2569 insertions(+), 170 deletions(-) diff --git a/testing/__tests__/10_performance_tests.lua b/testing/__tests__/10_performance_tests.lua index bb82360..800ed52 100644 --- a/testing/__tests__/10_performance_tests.lua +++ b/testing/__tests__/10_performance_tests.lua @@ -46,11 +46,11 @@ local function createTestContainer(props) flexWrap = FlexWrap.NOWRAP, gap = 0, } - + for key, value in pairs(props) do defaults[key] = value end - + return Gui.new(defaults) end @@ -58,63 +58,63 @@ end local function createManyChildren(parent, count, child_props) child_props = child_props or {} local children = {} - + for i = 1, count do local props = { w = child_props.w or 50, h = child_props.h or 30, } - + -- Add any additional properties for key, value in pairs(child_props) do if key ~= "w" and key ~= "h" then props[key] = value end end - + local child = Gui.new(props) child.parent = parent table.insert(parent.children, child) table.insert(children, child) end - + return children end -- Test 1: Basic Layout Performance Benchmark function TestPerformance:testBasicLayoutPerformanceBenchmark() local container = createTestContainer() - + -- Test with small number of children (baseline) local children_10 = createManyChildren(container, 10) local time_10, _ = measureTime(function() container:layoutChildren() end) - + -- Clear and test with medium number of children container.children = {} local children_50 = createManyChildren(container, 50) local time_50, _ = measureTime(function() container:layoutChildren() end) - + -- Clear and test with larger number of children container.children = {} local children_100 = createManyChildren(container, 100) local time_100, _ = measureTime(function() container:layoutChildren() end) - + print(string.format("Performance Benchmark:")) print(string.format(" 10 children: %.6f seconds", time_10)) print(string.format(" 50 children: %.6f seconds", time_50)) print(string.format(" 100 children: %.6f seconds", time_100)) - + -- Assert reasonable performance (should complete within 1 second) luaunit.assertTrue(time_10 < 1.0, "10 children layout should complete within 1 second") luaunit.assertTrue(time_50 < 1.0, "50 children layout should complete within 1 second") luaunit.assertTrue(time_100 < 1.0, "100 children layout should complete within 1 second") - + -- Performance should scale reasonably (not exponentially) -- Allow some overhead but ensure it's not exponential growth luaunit.assertTrue(time_100 <= time_10 * 50, "Performance should not degrade exponentially") @@ -123,26 +123,26 @@ end -- Test 2: Scalability Testing with Large Numbers function TestPerformance:testScalabilityWithLargeNumbers() local container = createTestContainer() - + -- Test progressively larger numbers of children - local test_sizes = {10, 50, 100, 200} + local test_sizes = { 10, 50, 100, 200 } local times = {} - + for _, size in ipairs(test_sizes) do container.children = {} -- Clear previous children local children = createManyChildren(container, size) - + local time, _ = measureTime(function() container:layoutChildren() end) - + times[size] = time print(string.format("Scalability Test - %d children: %.6f seconds", size, time)) - + -- Each test should complete within reasonable time luaunit.assertTrue(time < 2.0, string.format("%d children should layout within 2 seconds", size)) end - + -- Check that performance scales linearly or sub-linearly -- Time for 200 children should not be more than 20x time for 10 children luaunit.assertTrue(times[200] <= times[10] * 20, "Performance should scale sub-linearly") @@ -156,7 +156,7 @@ function TestPerformance:testComplexNestedLayoutPerformance() h = 800, flexDirection = FlexDirection.VERTICAL, }) - + local time, _ = measureTime(function() -- Level 1: 5 sections for i = 1, 5 do @@ -169,7 +169,7 @@ function TestPerformance:testComplexNestedLayoutPerformance() }) section.parent = root table.insert(root.children, section) - + -- Level 2: 4 columns per section for j = 1, 4 do local column = Gui.new({ @@ -181,7 +181,7 @@ function TestPerformance:testComplexNestedLayoutPerformance() }) column.parent = section table.insert(section.children, column) - + -- Level 3: 3 items per column for k = 1, 3 do local item = Gui.new({ @@ -193,16 +193,16 @@ function TestPerformance:testComplexNestedLayoutPerformance() end end end - + -- Layout the entire structure root:layoutChildren() end) - + print(string.format("Complex Nested Layout (5x4x3 = 60 total elements): %.6f seconds", time)) - + -- Complex nested layout should complete within reasonable time luaunit.assertTrue(time < 3.0, "Complex nested layout should complete within 3 seconds") - + -- Verify structure was created correctly luaunit.assertEquals(#root.children, 5) -- 5 sections luaunit.assertEquals(#root.children[1].children, 4) -- 4 columns per section @@ -219,22 +219,22 @@ function TestPerformance:testFlexWrapPerformanceWithManyElements() justifyContent = JustifyContent.SPACE_AROUND, alignItems = AlignItems.CENTER, }) - + -- Create many children that will wrap local children = createManyChildren(container, 50, { w = 80, h = 50, }) - + local time, _ = measureTime(function() container:layoutChildren() end) - + print(string.format("Flex Wrap Performance (50 wrapping elements): %.6f seconds", time)) - + -- Flex wrap with many elements should complete within reasonable time luaunit.assertTrue(time < 2.0, "Flex wrap layout should complete within 2 seconds") - + -- Verify that elements are positioned (wrapped layout worked) luaunit.assertTrue(children[1].x >= 0 and children[1].y >= 0, "First child should be positioned") luaunit.assertTrue(children[#children].x >= 0 and children[#children].y >= 0, "Last child should be positioned") @@ -244,32 +244,32 @@ end function TestPerformance:testDynamicLayoutChangePerformance() local container = createTestContainer() local children = createManyChildren(container, 30) - + -- Initial layout container:layoutChildren() - + -- Test performance of multiple layout property changes local time, _ = measureTime(function() for i = 1, 10 do -- Change flex direction container.flexDirection = (i % 2 == 0) and FlexDirection.VERTICAL or FlexDirection.HORIZONTAL container:layoutChildren() - + -- Change justify content container.justifyContent = (i % 3 == 0) and JustifyContent.CENTER or JustifyContent.FLEX_START container:layoutChildren() - + -- Change align items container.alignItems = (i % 4 == 0) and AlignItems.CENTER or AlignItems.STRETCH container:layoutChildren() end end) - + print(string.format("Dynamic Layout Changes (30 relayouts): %.6f seconds", time)) - + -- Dynamic layout changes should complete within reasonable time luaunit.assertTrue(time < 2.0, "Dynamic layout changes should complete within 2 seconds") - + -- Verify final layout is valid luaunit.assertTrue(children[1].x >= 0 and children[1].y >= 0, "Children should be positioned after changes") end @@ -278,27 +278,27 @@ end function TestPerformance:testMemoryUsagePattern() -- This test checks that we don't have obvious memory leaks during layout operations local container = createTestContainer() - + -- Create and destroy many children multiple times local time, _ = measureTime(function() for cycle = 1, 5 do -- Create children local children = createManyChildren(container, 100) container:layoutChildren() - + -- Clear children (simulating component cleanup) container.children = {} - + -- Force garbage collection to test for leaks collectgarbage("collect") end end) - + print(string.format("Memory Usage Pattern Test (5 cycles, 100 elements each): %.6f seconds", time)) - + -- Memory pattern test should complete within reasonable time luaunit.assertTrue(time < 3.0, "Memory usage pattern test should complete within 3 seconds") - + -- Verify container is clean after cycles luaunit.assertEquals(#container.children, 0, "Container should be clean after memory test") end @@ -312,7 +312,7 @@ function TestPerformance:testPerformanceWithDifferentLayoutStrategies() flexDirection = FlexDirection.HORIZONTAL, justifyContent = JustifyContent.FLEX_START, alignItems = AlignItems.STRETCH, - } + }, }, { name = "Centered Vertical", @@ -320,7 +320,7 @@ function TestPerformance:testPerformanceWithDifferentLayoutStrategies() flexDirection = FlexDirection.VERTICAL, justifyContent = JustifyContent.CENTER, alignItems = AlignItems.CENTER, - } + }, }, { name = "Wrapped Space-Between", @@ -329,37 +329,37 @@ function TestPerformance:testPerformanceWithDifferentLayoutStrategies() flexWrap = FlexWrap.WRAP, justifyContent = JustifyContent.SPACE_BETWEEN, alignItems = AlignItems.FLEX_START, - } + }, }, } - + local times = {} - + for _, strategy in ipairs(strategies) do local container = createTestContainer(strategy.props) local children = createManyChildren(container, 40) - + local time, _ = measureTime(function() container:layoutChildren() end) - + times[strategy.name] = time print(string.format("Layout Strategy '%s': %.6f seconds", strategy.name, time)) - + -- Each strategy should complete within reasonable time luaunit.assertTrue(time < 1.0, string.format("'%s' layout should complete within 1 second", strategy.name)) end - + -- All strategies should perform reasonably similarly -- None should be more than 10x slower than the fastest local min_time = math.huge local max_time = 0 - + for _, time in pairs(times) do min_time = math.min(min_time, time) max_time = math.max(max_time, time) end - + luaunit.assertTrue(max_time <= min_time * 10, "Layout strategies should have similar performance characteristics") end @@ -371,23 +371,26 @@ function TestPerformance:testStressTestWithMaximumElements() flexDirection = FlexDirection.HORIZONTAL, flexWrap = FlexWrap.WRAP, }) - + -- Create a large number of children for stress testing local stress_count = 300 local children = createManyChildren(container, stress_count, { w = 30, h = 20, }) - + local time, _ = measureTime(function() container:layoutChildren() end) - + print(string.format("Stress Test (%d elements): %.6f seconds", stress_count, time)) - + -- Stress test should complete within reasonable time even with many elements - luaunit.assertTrue(time < 5.0, string.format("Stress test with %d elements should complete within 5 seconds", stress_count)) - + luaunit.assertTrue( + time < 5.0, + string.format("Stress test with %d elements should complete within 5 seconds", stress_count) + ) + -- Verify that all children are positioned local positioned_count = 0 for _, child in ipairs(children) do @@ -395,12 +398,1030 @@ function TestPerformance:testStressTestWithMaximumElements() positioned_count = positioned_count + 1 end end - + luaunit.assertEquals(positioned_count, stress_count, "All children should be positioned in stress test") end --- Run the tests -print("=== Running Performance Tests ===") -luaunit.LuaUnit.run() +-- Test 9: Complex Real-World Application Performance - Enterprise Dashboard +function TestPerformance:testComplexEnterpriseApplicationPerformance() + print("\n=== Test 9: Complex Enterprise Dashboard Performance ===") -return TestPerformance \ No newline at end of file + -- Create enterprise-grade dashboard with deep nesting (5 levels) + local dashboard = createTestContainer({ + w = 1920, + h = 1080, + flexDirection = FlexDirection.VERTICAL, + gap = 10, + }) + + local time, structure_info = measureTime(function() + -- Level 1: Header, Main Content, Footer + local header = Gui.new({ + w = 1900, + h = 80, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 20, + }) + header.parent = dashboard + table.insert(dashboard.children, header) + + local main_content = Gui.new({ + w = 1900, + h = 980, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + gap = 15, + }) + main_content.parent = dashboard + table.insert(dashboard.children, main_content) + + local footer = Gui.new({ + w = 1900, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + }) + footer.parent = dashboard + table.insert(dashboard.children, footer) + + -- Level 2: Header components (logo, navigation, user actions) + local header_sections = { "logo", "navigation", "search", "notifications", "user_menu" } + for i, section_name in ipairs(header_sections) do + local section = Gui.new({ + w = section_name == "navigation" and 400 or 150, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + section.parent = header + table.insert(header.children, section) + + -- Level 3: Section items + local item_count = section_name == "navigation" and 6 or 3 + for j = 1, item_count do + local item = Gui.new({ + w = section_name == "navigation" and 60 or 45, + h = 40, + }) + item.parent = section + table.insert(section.children, item) + end + end + + -- Level 2: Main content areas (sidebar, dashboard grid) + local sidebar = Gui.new({ + w = 280, + h = 960, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + gap = 10, + }) + sidebar.parent = main_content + table.insert(main_content.children, sidebar) + + local dashboard_grid = Gui.new({ + w = 1600, + h = 960, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + gap = 20, + }) + dashboard_grid.parent = main_content + table.insert(main_content.children, dashboard_grid) + + -- Level 3: Sidebar navigation items (complex menu structure) + local menu_categories = { + { name = "Analytics", items = 5 }, + { name = "Reports", items = 7 }, + { name = "Users", items = 4 }, + { name = "Settings", items = 6 }, + { name = "Tools", items = 8 }, + } + + for _, category in ipairs(menu_categories) do + local category_container = Gui.new({ + w = 260, + h = 40 + (category.items * 35), + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + gap = 2, + }) + category_container.parent = sidebar + table.insert(sidebar.children, category_container) + + -- Category header + local category_header = Gui.new({ w = 250, h = 35 }) + category_header.parent = category_container + table.insert(category_container.children, category_header) + + -- Level 4: Menu items with sub-indicators + for i = 1, category.items do + local menu_item = Gui.new({ + w = 240, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + }) + menu_item.parent = category_container + table.insert(category_container.children, menu_item) + + -- Level 5: Menu item components (text, icon, badge) + local item_icon = Gui.new({ w = 20, h = 20 }) + item_icon.parent = menu_item + table.insert(menu_item.children, item_icon) + + local item_text = Gui.new({ w = 180, h = 25 }) + item_text.parent = menu_item + table.insert(menu_item.children, item_text) + + local item_badge = Gui.new({ w = 25, h = 18 }) + item_badge.parent = menu_item + table.insert(menu_item.children, item_badge) + end + end + + -- Level 3: Dashboard grid (4x3 widget grid with complex internals) + for row = 1, 4 do + local grid_row = Gui.new({ + w = 1580, + h = 220, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 20, + }) + grid_row.parent = dashboard_grid + table.insert(dashboard_grid.children, grid_row) + + for col = 1, 3 do + local widget = Gui.new({ + w = 500, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + gap = 8, + }) + widget.parent = grid_row + table.insert(grid_row.children, widget) + + -- Level 4: Widget components (header, content, footer) + local widget_header = Gui.new({ + w = 480, + h = 40, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + }) + widget_header.parent = widget + table.insert(widget.children, widget_header) + + local widget_content = Gui.new({ + w = 480, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_AROUND, + alignItems = AlignItems.CENTER, + gap = 5, + }) + widget_content.parent = widget + table.insert(widget.children, widget_content) + + local widget_footer = Gui.new({ + w = 480, + h = 32, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + alignItems = AlignItems.CENTER, + }) + widget_footer.parent = widget + table.insert(widget.children, widget_footer) + + -- Level 5: Widget content elements (charts, metrics, etc.) + local content_elements = (row * col) % 4 == 0 and 12 or 8 + for i = 1, content_elements do + local element = Gui.new({ + w = content_elements > 10 and 35 or 55, + h = content_elements > 10 and 25 or 35, + }) + element.parent = widget_content + table.insert(widget_content.children, element) + end + + -- Widget header components + local widget_title = Gui.new({ w = 200, h = 30 }) + widget_title.parent = widget_header + table.insert(widget_header.children, widget_title) + + local widget_actions = Gui.new({ + w = 120, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + gap = 5, + }) + widget_actions.parent = widget_header + table.insert(widget_header.children, widget_actions) + + for j = 1, 3 do + local action_btn = Gui.new({ w = 30, h = 25 }) + action_btn.parent = widget_actions + table.insert(widget_actions.children, action_btn) + end + + -- Widget footer components + local footer_info = Gui.new({ w = 100, h = 25 }) + footer_info.parent = widget_footer + table.insert(widget_footer.children, footer_info) + end + end + + -- Perform complete layout + dashboard:layoutChildren() + + -- Calculate structure metrics + local total_elements = 0 + local max_depth = 0 + + local function countElements(element, depth) + total_elements = total_elements + 1 + max_depth = math.max(max_depth, depth) + for _, child in ipairs(element.children) do + countElements(child, depth + 1) + end + end + + countElements(dashboard, 1) + + return { + total_elements = total_elements, + max_depth = max_depth, + widgets = 12, + menu_items = 30, + } + end) + + print(string.format("Enterprise Dashboard Performance:")) + print(string.format(" Total Elements: %d", structure_info.total_elements)) + print(string.format(" Maximum Depth: %d levels", structure_info.max_depth)) + print(string.format(" Layout Time: %.6f seconds", time)) + print(string.format(" Elements/Second: %.0f", structure_info.total_elements / time)) + + -- Performance assertions for enterprise-grade application + luaunit.assertTrue(time < 8.0, "Enterprise dashboard should layout within 8 seconds") + luaunit.assertTrue(structure_info.total_elements > 200, "Should have created substantial element count") + luaunit.assertTrue(structure_info.max_depth >= 5, "Should have deep nesting structure") + + -- Verify critical components are positioned + luaunit.assertEquals(#dashboard.children, 3, "Should have header, main, footer") + luaunit.assertTrue(#dashboard.children[2].children >= 2, "Main should have sidebar and grid") +end + +-- Test 10: High-Frequency Dynamic Layout Updates Performance +function TestPerformance:testHighFrequencyDynamicLayoutUpdates() + print("\n=== Test 10: High-Frequency Dynamic Updates Performance ===") + + local container = createTestContainer({ + w = 1200, + h = 800, + flexDirection = FlexDirection.VERTICAL, + gap = 10, + }) + + -- Create dynamic content structure + local sections = {} + for i = 1, 8 do + local section = Gui.new({ + w = 1180, + h = 90, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + gap = 8, + }) + section.parent = container + table.insert(container.children, section) + table.insert(sections, section) + + -- Create dynamic items in each section + for j = 1, 10 do + local item = Gui.new({ + w = 100, + h = 70, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + item.parent = section + table.insert(section.children, item) + + -- Sub-items for more complex updates + for k = 1, 3 do + local sub_item = Gui.new({ w = 80, h = 20 }) + sub_item.parent = item + table.insert(item.children, sub_item) + end + end + end + + -- Initial layout + container:layoutChildren() + + local update_scenarios = { + { name = "Direction Changes", iterations = 50 }, + { name = "Justify Content Cycling", iterations = 40 }, + { name = "Gap Modifications", iterations = 30 }, + { name = "Size Adjustments", iterations = 35 }, + { name = "Wrap Toggle", iterations = 25 }, + } + + local total_updates = 0 + local total_time = 0 + + for _, scenario in ipairs(update_scenarios) do + local scenario_time = measureTime(function() + for i = 1, scenario.iterations do + if scenario.name == "Direction Changes" then + local section = sections[(i % #sections) + 1] + section.flexDirection = (i % 2 == 0) and FlexDirection.VERTICAL or FlexDirection.HORIZONTAL + section:layoutChildren() + elseif scenario.name == "Justify Content Cycling" then + local section = sections[(i % #sections) + 1] + local justifies = + { JustifyContent.FLEX_START, JustifyContent.CENTER, JustifyContent.FLEX_END, JustifyContent.SPACE_BETWEEN } + section.justifyContent = justifies[(i % #justifies) + 1] + section:layoutChildren() + elseif scenario.name == "Gap Modifications" then + local section = sections[(i % #sections) + 1] + section.gap = (i % 20) + 5 + section:layoutChildren() + elseif scenario.name == "Size Adjustments" then + local section = sections[(i % #sections) + 1] + for _, child in ipairs(section.children) do + child.w = 80 + (i % 40) + child.h = 60 + (i % 20) + end + section:layoutChildren() + elseif scenario.name == "Wrap Toggle" then + local section = sections[(i % #sections) + 1] + section.flexWrap = (i % 2 == 0) and FlexWrap.WRAP or FlexWrap.NOWRAP + section:layoutChildren() + end + end + end) + + total_updates = total_updates + scenario.iterations + total_time = total_time + scenario_time + + print( + string.format( + " %s (%d updates): %.6f seconds (%.3f ms/update)", + scenario.name, + scenario.iterations, + scenario_time, + (scenario_time * 1000) / scenario.iterations + ) + ) + end + + print(string.format("High-Frequency Updates Summary:")) + print(string.format(" Total Updates: %d", total_updates)) + print(string.format(" Total Time: %.6f seconds", total_time)) + print(string.format(" Average Update Time: %.3f ms", (total_time * 1000) / total_updates)) + print(string.format(" Updates Per Second: %.0f", total_updates / total_time)) + + -- Performance assertions + luaunit.assertTrue(total_time < 15.0, "High-frequency updates should complete within 15 seconds") + luaunit.assertTrue((total_time * 1000) / total_updates < 50, "Average update should be under 50ms") + luaunit.assertTrue(total_updates / total_time > 10, "Should achieve at least 10 updates per second") + + -- Verify final state is valid + luaunit.assertEquals(#container.children, 8, "All sections should still exist") + for _, section in ipairs(sections) do + luaunit.assertEquals(#section.children, 10, "All items should still exist in sections") + end +end + +-- Test 11: Complex Animation-Ready Layout Performance +function TestPerformance:testComplexAnimationReadyLayoutPerformance() + print("\n=== Test 11: Complex Animation-Ready Layout Performance ===") + + -- Create animation-heavy interface structure + local animation_container = createTestContainer({ + w = 1400, + h = 900, + flexDirection = FlexDirection.VERTICAL, + gap = 15, + }) + + local animation_elements = {} + local time, metrics = measureTime(function() + -- Create multiple animated sections with complex layouts + for section_id = 1, 6 do + local section = Gui.new({ + w = 1380, + h = 140, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + alignItems = AlignItems.CENTER, + gap = 12, + }) + section.parent = animation_container + table.insert(animation_container.children, section) + + -- Create animated cards/panels + for card_id = 1, 8 do + local card = Gui.new({ + w = 160, + h = 120, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 5, + }) + card.parent = section + table.insert(section.children, card) + table.insert(animation_elements, card) + + -- Card header with animated elements + local card_header = Gui.new({ + w = 150, + h = 30, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + alignItems = AlignItems.CENTER, + }) + card_header.parent = card + table.insert(card.children, card_header) + + -- Animated header components + local title = Gui.new({ w = 100, h = 25 }) + title.parent = card_header + table.insert(card_header.children, title) + + local status_indicator = Gui.new({ w = 20, h = 20 }) + status_indicator.parent = card_header + table.insert(card_header.children, status_indicator) + + -- Card content with animated metrics + local card_content = Gui.new({ + w = 150, + h = 60, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_AROUND, + alignItems = AlignItems.CENTER, + gap = 3, + }) + card_content.parent = card + table.insert(card.children, card_content) + + -- Animated metrics/values + local metric_count = 4 + (section_id % 3) + for i = 1, metric_count do + local metric = Gui.new({ + w = 35, + h = 25, + positioning = Positioning.FLEX, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + metric.parent = card_content + table.insert(card_content.children, metric) + table.insert(animation_elements, metric) + end + + -- Card footer with action buttons + local card_footer = Gui.new({ + w = 150, + h = 25, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.FLEX_END, + gap = 5, + }) + card_footer.parent = card + table.insert(card.children, card_footer) + + for i = 1, 2 do + local action_btn = Gui.new({ w = 25, h = 20 }) + action_btn.parent = card_footer + table.insert(card_footer.children, action_btn) + table.insert(animation_elements, action_btn) + end + end + end + + -- Perform initial layout + animation_container:layoutChildren() + + -- Simulate animation frame updates (position/size changes) + local animation_frames = 60 -- Simulate 1 second at 60fps + local frame_times = {} + + for frame = 1, animation_frames do + local frame_start = os.clock() + + -- Simulate animated property changes + for i, element in ipairs(animation_elements) do + if (frame + i) % 10 == 0 then + -- Animate size changes + element.w = element.w + math.sin(frame * 0.1 + i) * 2 + element.h = element.h + math.cos(frame * 0.1 + i) * 1 + end + + if (frame + i) % 15 == 0 then + -- Animate gap changes in parent containers + if element.parent and element.parent.gap then + element.parent.gap = 5 + math.abs(math.sin(frame * 0.05)) * 10 + end + end + end + + -- Relayout for animation frame + animation_container:layoutChildren() + + local frame_time = os.clock() - frame_start + table.insert(frame_times, frame_time) + end + + return { + total_elements = #animation_elements, + animation_frames = animation_frames, + frame_times = frame_times, + } + end) + + -- Calculate animation performance metrics + local total_frame_time = 0 + local max_frame_time = 0 + local min_frame_time = math.huge + + for _, frame_time in ipairs(metrics.frame_times) do + total_frame_time = total_frame_time + frame_time + max_frame_time = math.max(max_frame_time, frame_time) + min_frame_time = math.min(min_frame_time, frame_time) + end + + local avg_frame_time = total_frame_time / metrics.animation_frames + local target_fps = 60 + local target_frame_time = 1.0 / target_fps + + print(string.format("Animation-Ready Layout Performance:")) + print(string.format(" Setup Time: %.6f seconds", time - total_frame_time)) + print(string.format(" Animation Elements: %d", metrics.total_elements)) + print(string.format(" Animation Frames: %d", metrics.animation_frames)) + print(string.format(" Total Animation Time: %.6f seconds", total_frame_time)) + print(string.format(" Average Frame Time: %.6f seconds (%.1f fps equivalent)", avg_frame_time, 1.0 / avg_frame_time)) + print(string.format(" Min Frame Time: %.6f seconds", min_frame_time)) + print(string.format(" Max Frame Time: %.6f seconds", max_frame_time)) + print(string.format(" 60fps Target: %.6f seconds/frame", target_frame_time)) + + -- Performance assertions for animation-ready layouts + luaunit.assertTrue(time < 12.0, "Animation setup should complete within 12 seconds") + luaunit.assertTrue(avg_frame_time < target_frame_time * 2, "Average frame time should be reasonable for 30fps+") + luaunit.assertTrue(max_frame_time < 0.1, "No single frame should take more than 100ms") + luaunit.assertTrue(metrics.total_elements > 100, "Should have substantial number of animated elements") + + -- Verify structure integrity after animations + luaunit.assertEquals(#animation_container.children, 6, "All sections should remain") + local total_cards = 0 + for _, section in ipairs(animation_container.children) do + total_cards = total_cards + #section.children + end + luaunit.assertEquals(total_cards, 48, "All cards should remain after animation") +end + +-- Test 12: Memory-Intensive Layout Performance with Cleanup +function TestPerformance:testMemoryIntensiveLayoutPerformanceWithCleanup() + print("\n=== Test 12: Memory-Intensive Layout with Cleanup ===") + + local memory_cycles = 8 + local elements_per_cycle = 150 + local cycle_times = {} + local cleanup_times = {} + + local total_time = measureTime(function() + for cycle = 1, memory_cycles do + print(string.format(" Memory Cycle %d/%d", cycle, memory_cycles)) + + -- Create intensive layout structure + local cycle_start = os.clock() + local root = createTestContainer({ + w = 1600, + h = 1000, + flexDirection = FlexDirection.VERTICAL, + gap = 8, + }) + + local all_elements = {} + + -- Create memory-intensive nested structure + for level1 = 1, 10 do + local section = Gui.new({ + w = 1580, + h = 95, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_AROUND, + gap = 5, + }) + section.parent = root + table.insert(root.children, section) + table.insert(all_elements, section) + + for level2 = 1, 15 do + local container = Gui.new({ + w = 100, + h = 85, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 2, + }) + container.parent = section + table.insert(section.children, container) + table.insert(all_elements, container) + + for level3 = 1, 3 do + local item = Gui.new({ + w = 95, + h = 25, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.CENTER, + alignItems = AlignItems.CENTER, + }) + item.parent = container + table.insert(container.children, item) + table.insert(all_elements, item) + + -- Add some leaf nodes for memory pressure + for level4 = 1, 2 do + local leaf = Gui.new({ w = 40, h = 20 }) + leaf.parent = item + table.insert(item.children, leaf) + table.insert(all_elements, leaf) + end + end + end + end + + -- Perform layout + root:layoutChildren() + + local cycle_time = os.clock() - cycle_start + table.insert(cycle_times, cycle_time) + + print(string.format(" Created %d elements in %.6f seconds", #all_elements, cycle_time)) + + -- Cleanup phase + local cleanup_start = os.clock() + + -- Clear all references systematically + for _, element in ipairs(all_elements) do + element.children = {} + element.parent = nil + end + + -- Clear root structure + root.children = {} + Gui.destroy() + + -- Force garbage collection + collectgarbage("collect") + + local cleanup_time = os.clock() - cleanup_start + table.insert(cleanup_times, cleanup_time) + + print(string.format(" Cleanup completed in %.6f seconds", cleanup_time)) + end + end) + + -- Calculate memory performance metrics + local total_cycle_time = 0 + local total_cleanup_time = 0 + local max_cycle_time = 0 + local max_cleanup_time = 0 + + for i = 1, memory_cycles do + total_cycle_time = total_cycle_time + cycle_times[i] + total_cleanup_time = total_cleanup_time + cleanup_times[i] + max_cycle_time = math.max(max_cycle_time, cycle_times[i]) + max_cleanup_time = math.max(max_cleanup_time, cleanup_times[i]) + end + + local avg_cycle_time = total_cycle_time / memory_cycles + local avg_cleanup_time = total_cleanup_time / memory_cycles + + print(string.format("Memory-Intensive Layout Performance:")) + print(string.format(" Memory Cycles: %d", memory_cycles)) + print(string.format(" Elements Per Cycle: ~%d", elements_per_cycle * 6)) -- Approximate + print(string.format(" Total Test Time: %.6f seconds", total_time)) + print(string.format(" Average Cycle Time: %.6f seconds", avg_cycle_time)) + print(string.format(" Average Cleanup Time: %.6f seconds", avg_cleanup_time)) + print(string.format(" Max Cycle Time: %.6f seconds", max_cycle_time)) + print(string.format(" Max Cleanup Time: %.6f seconds", max_cleanup_time)) + print(string.format(" Cycle Efficiency: %.1f elements/second", (elements_per_cycle * 6) / avg_cycle_time)) + + -- Performance assertions for memory-intensive operations + luaunit.assertTrue(total_time < 30.0, "Memory-intensive test should complete within 30 seconds") + luaunit.assertTrue(avg_cycle_time < 5.0, "Average cycle should complete within 5 seconds") + luaunit.assertTrue(avg_cleanup_time < 2.0, "Average cleanup should complete within 2 seconds") + luaunit.assertTrue(max_cycle_time <= avg_cycle_time * 3, "No cycle should be extremely slow") + luaunit.assertTrue(max_cleanup_time <= avg_cleanup_time * 3, "No cleanup should be extremely slow") + + -- Verify clean state after all cycles + local final_container = createTestContainer() + luaunit.assertEquals(#final_container.children, 0, "Should start with clean container after cycles") +end + +-- Test 13: Extreme Scale Performance Benchmark +function TestPerformance:testExtremeScalePerformanceBenchmark() + print("\n=== Test 13: Extreme Scale Performance Benchmark ===") + + -- Test with extremely large layouts to find breaking points + local scale_tests = { + { name = "Massive Flat Layout", elements = 1000, depth = 1 }, + { name = "Deep Nesting", elements = 200, depth = 10 }, + { name = "Wide Branching", elements = 500, depth = 4 }, + { name = "Mixed Complex", elements = 800, depth = 6 }, + } + + local benchmark_results = {} + + for _, test_config in ipairs(scale_tests) do + print(string.format(" Running %s test...", test_config.name)) + + local test_time, test_metrics = measureTime(function() + local root = createTestContainer({ + w = 2000, + h = 1500, + flexDirection = FlexDirection.VERTICAL, + flexWrap = FlexWrap.WRAP, + gap = 5, + }) + + local created_elements = 0 + local max_actual_depth = 0 + + if test_config.name == "Massive Flat Layout" then + -- Create very wide, flat structure + local items_per_row = 50 + local rows = math.ceil(test_config.elements / items_per_row) + + for row = 1, rows do + local row_container = Gui.new({ + w = 1980, + h = 25, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + gap = 2, + }) + row_container.parent = root + table.insert(root.children, row_container) + created_elements = created_elements + 1 + + local items_in_this_row = math.min(items_per_row, test_config.elements - (row - 1) * items_per_row) + for col = 1, items_in_this_row do + local item = Gui.new({ w = 35, h = 20 }) + item.parent = row_container + table.insert(row_container.children, item) + created_elements = created_elements + 1 + end + end + max_actual_depth = 2 + elseif test_config.name == "Deep Nesting" then + -- Create deep nested structure + local current_parent = root + local elements_per_level = math.ceil(test_config.elements / test_config.depth) + + for depth = 1, test_config.depth do + local level_container = Gui.new({ + w = 1900 - (depth * 50), + h = 1400 - (depth * 100), + positioning = Positioning.FLEX, + flexDirection = (depth % 2 == 0) and FlexDirection.VERTICAL or FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + gap = math.max(1, 10 - depth), + }) + level_container.parent = current_parent + table.insert(current_parent.children, level_container) + created_elements = created_elements + 1 + + if depth < test_config.depth then + current_parent = level_container + else + -- Final level - add many elements + for i = 1, elements_per_level do + local leaf = Gui.new({ w = 30 + (i % 20), h = 25 + (i % 15) }) + leaf.parent = level_container + table.insert(level_container.children, leaf) + created_elements = created_elements + 1 + end + end + end + max_actual_depth = test_config.depth + elseif test_config.name == "Wide Branching" then + -- Create structure with wide branching at each level + local function createBranching(parent, remaining_elements, current_depth, max_depth) + if current_depth >= max_depth or remaining_elements <= 0 then + return 0 + end + + local children_count = math.min(20, math.ceil(remaining_elements / (max_depth - current_depth))) + local elements_used = 0 + + for i = 1, children_count do + local branch = Gui.new({ + w = 150 - (current_depth * 15), + h = 100 - (current_depth * 10), + positioning = Positioning.FLEX, + flexDirection = (i % 2 == 0) and FlexDirection.VERTICAL or FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_AROUND, + gap = math.max(1, 8 - current_depth * 2), + }) + branch.parent = parent + table.insert(parent.children, branch) + elements_used = elements_used + 1 + + if current_depth < max_depth - 1 then + elements_used = elements_used + + createBranching(branch, remaining_elements - elements_used, current_depth + 1, max_depth) + end + + if elements_used >= remaining_elements then + break + end + end + + return elements_used + end + + created_elements = 1 + createBranching(root, test_config.elements - 1, 1, test_config.depth) + max_actual_depth = test_config.depth + elseif test_config.name == "Mixed Complex" then + -- Create mixed complex structure with varying patterns + local sections = math.ceil(test_config.depth / 2) + local elements_per_section = math.ceil(test_config.elements / sections) + + for section_id = 1, sections do + local section = Gui.new({ + w = 1900, + h = 200, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + flexWrap = FlexWrap.WRAP, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 10, + }) + section.parent = root + table.insert(root.children, section) + created_elements = created_elements + 1 + + -- Create subsections with different patterns + local subsections = 5 + (section_id % 3) + for sub_id = 1, subsections do + local subsection = Gui.new({ + w = 300, + h = 180, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.VERTICAL, + justifyContent = JustifyContent.SPACE_AROUND, + gap = 5, + }) + subsection.parent = section + table.insert(section.children, subsection) + created_elements = created_elements + 1 + + -- Add elements with varying complexity + local elements_in_subsection = math.ceil(elements_per_section / subsections) + for elem_id = 1, elements_in_subsection do + if elem_id % 3 == 0 then + -- Complex element with children + local complex_elem = Gui.new({ + w = 280, + h = 35, + positioning = Positioning.FLEX, + flexDirection = FlexDirection.HORIZONTAL, + justifyContent = JustifyContent.SPACE_BETWEEN, + gap = 3, + }) + complex_elem.parent = subsection + table.insert(subsection.children, complex_elem) + created_elements = created_elements + 1 + + for child_id = 1, 4 do + local child = Gui.new({ w = 60, h = 30 }) + child.parent = complex_elem + table.insert(complex_elem.children, child) + created_elements = created_elements + 1 + end + else + -- Simple element + local simple_elem = Gui.new({ w = 270, h = 25 }) + simple_elem.parent = subsection + table.insert(subsection.children, simple_elem) + created_elements = created_elements + 1 + end + + if created_elements >= test_config.elements then + break + end + end + if created_elements >= test_config.elements then + break + end + end + if created_elements >= test_config.elements then + break + end + end + max_actual_depth = 4 + end + + -- Perform layout + root:layoutChildren() + + return { + created_elements = created_elements, + max_depth = max_actual_depth, + } + end) + + local elements_per_second = test_metrics.created_elements / test_time + + benchmark_results[test_config.name] = { + time = test_time, + elements = test_metrics.created_elements, + elements_per_second = elements_per_second, + depth = test_metrics.max_depth, + } + + print( + string.format( + " %s: %d elements, %.6f seconds (%.0f elem/sec)", + test_config.name, + test_metrics.created_elements, + test_time, + elements_per_second + ) + ) + + -- Individual test assertions + luaunit.assertTrue(test_time < 20.0, string.format("%s should complete within 20 seconds", test_config.name)) + luaunit.assertTrue( + test_metrics.created_elements > 100, + string.format("%s should create substantial elements", test_config.name) + ) + end + + -- Overall benchmark analysis + print(string.format("Extreme Scale Benchmark Summary:")) + local total_elements = 0 + local total_time = 0 + local best_performance = 0 + local worst_performance = math.huge + + for test_name, result in pairs(benchmark_results) do + total_elements = total_elements + result.elements + total_time = total_time + result.time + best_performance = math.max(best_performance, result.elements_per_second) + worst_performance = math.min(worst_performance, result.elements_per_second) + print(string.format(" %s: %.0f elements/second", test_name, result.elements_per_second)) + end + + local avg_performance = total_elements / total_time + + print(string.format(" Overall Average: %.0f elements/second", avg_performance)) + print(string.format(" Best Performance: %.0f elements/second", best_performance)) + print(string.format(" Worst Performance: %.0f elements/second", worst_performance)) + print(string.format(" Performance Range: %.1fx", best_performance / worst_performance)) + + -- Extreme scale performance assertions + luaunit.assertTrue(total_time < 60.0, "All extreme scale tests should complete within 60 seconds") + luaunit.assertTrue(avg_performance > 50, "Should achieve at least 50 elements/second average") + luaunit.assertTrue(best_performance > 100, "Best case should achieve at least 100 elements/second") + luaunit.assertTrue(best_performance / worst_performance < 20, "Performance variance should be reasonable") + luaunit.assertTrue(total_elements > 2000, "Should have processed substantial total elements") +end + +luaunit.LuaUnit.run() diff --git a/testing/__tests__/11_auxiliary_functions_tests.lua b/testing/__tests__/11_auxiliary_functions_tests.lua index c2db4c6..3270412 100644 --- a/testing/__tests__/11_auxiliary_functions_tests.lua +++ b/testing/__tests__/11_auxiliary_functions_tests.lua @@ -60,7 +60,7 @@ function TestAuxiliaryFunctions:testColorFromHex8Digit() luaunit.assertEquals(color.r, 255) luaunit.assertEquals(color.g, 128) luaunit.assertEquals(color.b, 64) - luaunit.assertAlmostEquals(color.a, 204/255, 0.01) -- CC hex = 204 decimal + luaunit.assertAlmostEquals(color.a, 204 / 255, 0.01) -- CC hex = 204 decimal end function TestAuxiliaryFunctions:testColorFromHexWithoutHash() @@ -75,11 +75,11 @@ function TestAuxiliaryFunctions:testColorFromHexInvalid() luaunit.assertError(function() Color.fromHex("#INVALID") end) - + luaunit.assertError(function() Color.fromHex("#FF80") -- Too short end) - + luaunit.assertError(function() Color.fromHex("#FF8040CC99") -- Too long end) @@ -101,9 +101,9 @@ end function TestAuxiliaryFunctions:testCalculateTextWidthWithText() local element = Gui.new({ text = "Test Text", - textSize = 16 + textSize = 16, }) - + local width = element:calculateTextWidth() print("Text: '" .. (element.text or "nil") .. "', TextSize: " .. (element.textSize or "nil") .. ", Width: " .. width) luaunit.assertTrue(width > 0, "Text width should be greater than 0, got: " .. width) @@ -111,7 +111,7 @@ end function TestAuxiliaryFunctions:testCalculateTextWidthNoText() local element = Gui.new({}) - + local width = element:calculateTextWidth() luaunit.assertEquals(width, 0, "Text width should be 0 when no text") end @@ -119,18 +119,18 @@ end function TestAuxiliaryFunctions:testCalculateTextHeightWithSize() local element = Gui.new({ text = "Test", - textSize = 24 + textSize = 24, }) - + local height = element:calculateTextHeight() luaunit.assertTrue(height > 0, "Text height should be greater than 0") end function TestAuxiliaryFunctions:testCalculateAutoWidthNoChildren() local element = Gui.new({ - text = "Hello" + text = "Hello", }) - + local width = element:calculateAutoWidth() local textWidth = element:calculateTextWidth() luaunit.assertEquals(width, textWidth, "Auto width should equal text width when no children") @@ -139,30 +139,30 @@ end function TestAuxiliaryFunctions:testCalculateAutoWidthWithChildren() local parent = Gui.new({ positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.HORIZONTAL + flexDirection = enums.FlexDirection.HORIZONTAL, }) - + local child1 = Gui.new({ parent = parent, w = 50, - h = 30 + h = 30, }) - + local child2 = Gui.new({ - parent = parent, + parent = parent, w = 40, - h = 25 + h = 25, }) - + local width = parent:calculateAutoWidth() luaunit.assertTrue(width > 90, "Auto width should account for children and gaps") end function TestAuxiliaryFunctions:testCalculateAutoHeightNoChildren() local element = Gui.new({ - text = "Hello" + text = "Hello", }) - + local height = element:calculateAutoHeight() local textHeight = element:calculateTextHeight() luaunit.assertEquals(height, textHeight, "Auto height should equal text height when no children") @@ -171,21 +171,21 @@ end function TestAuxiliaryFunctions:testCalculateAutoHeightWithChildren() local parent = Gui.new({ positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.VERTICAL + flexDirection = enums.FlexDirection.VERTICAL, }) - + local child1 = Gui.new({ parent = parent, w = 50, - h = 30 + h = 30, }) - + local child2 = Gui.new({ parent = parent, - w = 40, - h = 25 + w = 40, + h = 25, }) - + local height = parent:calculateAutoHeight() luaunit.assertTrue(height > 55, "Auto height should account for children and gaps") end @@ -199,9 +199,9 @@ function TestAuxiliaryFunctions:testGetBounds() x = 10, y = 20, w = 100, - h = 80 + h = 80, }) - + local bounds = element:getBounds() luaunit.assertEquals(bounds.x, 10) luaunit.assertEquals(bounds.y, 20) @@ -213,9 +213,9 @@ function TestAuxiliaryFunctions:testUpdateText() local element = Gui.new({ text = "Original Text", w = 100, - h = 50 + h = 50, }) - + element:updateText("New Text") luaunit.assertEquals(element.text, "New Text") luaunit.assertEquals(element.width, 100) -- Should not change without autoresize @@ -225,21 +225,24 @@ end function TestAuxiliaryFunctions:testUpdateTextWithAutoresize() local element = Gui.new({ text = "Short", - textSize = 16 + textSize = 16, }) - + local originalWidth = element.width element:updateText("Much Longer Text That Should Change Width", true) - + -- Debug: let's see what the values are -- print("Original width: " .. originalWidth .. ", New width: " .. element.width) luaunit.assertEquals(element.text, "Much Longer Text That Should Change Width") - luaunit.assertTrue(element.width > originalWidth, "Width should increase with longer text and autoresize. Original: " .. originalWidth .. ", New: " .. element.width) + luaunit.assertTrue( + element.width > originalWidth, + "Width should increase with longer text and autoresize. Original: " .. originalWidth .. ", New: " .. element.width + ) end function TestAuxiliaryFunctions:testUpdateTextKeepOriginalWhenNil() local element = Gui.new({ - text = "Original Text" + text = "Original Text", }) element:updateText(nil) @@ -248,42 +251,42 @@ end function TestAuxiliaryFunctions:testUpdateOpacitySingle() local element = Gui.new({ - opacity = 1.0 + opacity = 1.0, }) - + element:updateOpacity(0.5) luaunit.assertEquals(element.opacity, 0.5) end function TestAuxiliaryFunctions:testUpdateOpacityPropagateToChildren() local parent = Gui.new({ - opacity = 1.0 + opacity = 1.0, }) - + local child1 = Gui.new({ parent = parent, - opacity = 1.0 + opacity = 1.0, }) - + local child2 = Gui.new({ parent = parent, - opacity = 1.0 + opacity = 1.0, }) - + parent:updateOpacity(0.3) - + luaunit.assertEquals(parent.opacity, 0.3) luaunit.assertEquals(child1.opacity, 0.3) luaunit.assertEquals(child2.opacity, 0.3) end -- ============================================ --- Animation Utility Functions Tests +-- Animation Utility Functions Tests -- ============================================ function TestAuxiliaryFunctions:testAnimationFadeFactory() local fadeAnim = Gui.Animation.fade(2.0, 1.0, 0.0) - + luaunit.assertEquals(fadeAnim.duration, 2.0) luaunit.assertEquals(fadeAnim.start.opacity, 1.0) luaunit.assertEquals(fadeAnim.final.opacity, 0.0) @@ -292,8 +295,8 @@ function TestAuxiliaryFunctions:testAnimationFadeFactory() end function TestAuxiliaryFunctions:testAnimationScaleFactory() - local scaleAnim = Gui.Animation.scale(1.5, {width = 100, height = 50}, {width = 200, height = 100}) - + local scaleAnim = Gui.Animation.scale(1.5, { width = 100, height = 50 }, { width = 200, height = 100 }) + luaunit.assertEquals(scaleAnim.duration, 1.5) luaunit.assertEquals(scaleAnim.start.width, 100) luaunit.assertEquals(scaleAnim.start.height, 50) @@ -304,19 +307,19 @@ end function TestAuxiliaryFunctions:testAnimationInterpolation() local fadeAnim = Gui.Animation.fade(1.0, 1.0, 0.0) fadeAnim.elapsed = 0.5 -- 50% through animation - + local result = fadeAnim:interpolate() luaunit.assertAlmostEquals(result.opacity, 0.5, 0.01) -- Should be halfway end function TestAuxiliaryFunctions:testAnimationUpdate() local fadeAnim = Gui.Animation.fade(1.0, 1.0, 0.0) - + -- Animation should not be finished initially local finished = fadeAnim:update(0.5) luaunit.assertFalse(finished) luaunit.assertEquals(fadeAnim.elapsed, 0.5) - + -- Animation should be finished after full duration finished = fadeAnim:update(0.6) -- Total 1.1 seconds > 1.0 duration luaunit.assertTrue(finished) @@ -325,27 +328,27 @@ end function TestAuxiliaryFunctions:testAnimationApplyToElement() local element = Gui.new({ w = 100, - h = 50 + h = 50, }) - + local fadeAnim = Gui.Animation.fade(1.0, 1.0, 0.0) fadeAnim:apply(element) - + luaunit.assertEquals(element.animation, fadeAnim) end function TestAuxiliaryFunctions:testAnimationReplaceExisting() local element = Gui.new({ w = 100, - h = 50 + h = 50, }) - + local fadeAnim1 = Gui.Animation.fade(1.0, 1.0, 0.0) local fadeAnim2 = Gui.Animation.fade(2.0, 0.5, 1.0) - + fadeAnim1:apply(element) fadeAnim2:apply(element) - + luaunit.assertEquals(element.animation, fadeAnim2, "Second animation should replace the first") end @@ -364,18 +367,18 @@ function TestAuxiliaryFunctions:testGuiDestroyWithElements() x = 10, y = 10, w = 100, - h = 50 + h = 50, }) - + local element2 = Gui.new({ x = 20, y = 20, w = 80, - h = 40 + h = 40, }) - + luaunit.assertEquals(#Gui.topElements, 2) - + Gui.destroy() luaunit.assertEquals(#Gui.topElements, 0) end @@ -383,24 +386,24 @@ end function TestAuxiliaryFunctions:testGuiDestroyWithNestedElements() local parent = Gui.new({ w = 200, - h = 100 + h = 100, }) - + local child1 = Gui.new({ parent = parent, w = 50, - h = 30 + h = 30, }) - + local child2 = Gui.new({ parent = parent, w = 40, - h = 25 + h = 25, }) - + luaunit.assertEquals(#Gui.topElements, 1) luaunit.assertEquals(#parent.children, 2) - + Gui.destroy() luaunit.assertEquals(#Gui.topElements, 0) end @@ -408,19 +411,19 @@ end function TestAuxiliaryFunctions:testElementDestroyRemovesFromParent() local parent = Gui.new({ w = 200, - h = 100 + h = 100, }) - + local child = Gui.new({ parent = parent, w = 50, - h = 30 + h = 30, }) - + luaunit.assertEquals(#parent.children, 1) - + child:destroy() - + luaunit.assertEquals(#parent.children, 0) luaunit.assertNil(child.parent) end @@ -430,39 +433,39 @@ function TestAuxiliaryFunctions:testElementDestroyRemovesFromTopElements() x = 10, y = 10, w = 100, - h = 50 + h = 50, }) - + luaunit.assertEquals(#Gui.topElements, 1) - + element:destroy() - + luaunit.assertEquals(#Gui.topElements, 0) end function TestAuxiliaryFunctions:testElementDestroyNestedChildren() local parent = Gui.new({ w = 200, - h = 150 + h = 150, }) - + local child = Gui.new({ parent = parent, w = 100, - h = 75 + h = 75, }) - + local grandchild = Gui.new({ parent = child, w = 50, - h = 30 + h = 30, }) - + luaunit.assertEquals(#parent.children, 1) luaunit.assertEquals(#child.children, 1) - + parent:destroy() - + luaunit.assertEquals(#Gui.topElements, 0) luaunit.assertEquals(#child.children, 0, "Grandchildren should be destroyed") end @@ -484,20 +487,20 @@ function TestAuxiliaryFunctions:testColorFromHexNoHashInvalidLength() end function TestAuxiliaryFunctions:testAnimationInterpolationAtBoundaries() - local scaleAnim = Gui.Animation.scale(1.0, {width = 100, height = 50}, {width = 200, height = 100}) - + local scaleAnim = Gui.Animation.scale(1.0, { width = 100, height = 50 }, { width = 200, height = 100 }) + -- At start (elapsed = 0) scaleAnim.elapsed = 0 local result = scaleAnim:interpolate() luaunit.assertEquals(result.width, 100) luaunit.assertEquals(result.height, 50) - + -- At end (elapsed = duration) scaleAnim.elapsed = 1.0 result = scaleAnim:interpolate() luaunit.assertEquals(result.width, 200) luaunit.assertEquals(result.height, 100) - + -- Beyond end (elapsed > duration) - should clamp to end values scaleAnim.elapsed = 1.5 result = scaleAnim:interpolate() @@ -507,36 +510,1411 @@ end function TestAuxiliaryFunctions:testAutoSizingWithZeroChildren() local element = Gui.new({ - text = "" + text = "", }) - + local width = element:calculateAutoWidth() - local height = element:calculateAutoHeight() - + local height = element:calculateAutoHeight() + luaunit.assertTrue(width >= 0, "Auto width should be non-negative") luaunit.assertTrue(height >= 0, "Auto height should be non-negative") end function TestAuxiliaryFunctions:testUpdateOpacityBoundaryValues() local element = Gui.new({ - opacity = 0.5 + opacity = 0.5, }) - + -- Test minimum boundary element:updateOpacity(0.0) luaunit.assertEquals(element.opacity, 0.0) - - -- Test maximum boundary + + -- Test maximum boundary element:updateOpacity(1.0) luaunit.assertEquals(element.opacity, 1.0) - + -- Test beyond boundaries (should still work, implementation may clamp) element:updateOpacity(1.5) luaunit.assertEquals(element.opacity, 1.5) -- FlexLove doesn't appear to clamp - + element:updateOpacity(-0.2) luaunit.assertEquals(element.opacity, -0.2) -- FlexLove doesn't appear to clamp end --- Run the tests -os.exit(luaunit.LuaUnit.run()) \ No newline at end of file +-- ============================================ +-- Test 11: Complex Color Management System +-- ============================================ + +function TestAuxiliaryFunctions:testComplexColorManagementSystem() + print("\n=== Test 11: Complex Color Management System ===") + + -- Create color management system for UI theming + local theme_colors = {} + local color_variations = {} + + -- Test comprehensive color creation and conversion + local base_colors = { + { name = "primary", hex = "#2563EB", r = 0.145, g = 0.388, b = 0.922 }, + { name = "secondary", hex = "#7C3AED", r = 0.486, g = 0.227, b = 0.929 }, + { name = "success", hex = "#10B981", r = 0.063, g = 0.725, b = 0.506 }, + { name = "warning", hex = "#F59E0B", r = 0.961, g = 0.619, b = 0.043 }, + { name = "danger", hex = "#EF4444", r = 0.937, g = 0.267, b = 0.267 }, + } + + -- Test color creation from hex and manual RGB + for _, color_def in ipairs(base_colors) do + local hex_color = Color.fromHex(color_def.hex) + local manual_color = Color.new(color_def.r, color_def.g, color_def.b, 1.0) + + theme_colors[color_def.name] = { + hex = hex_color, + manual = manual_color, + name = color_def.name, + } + + -- Verify hex parsing (FlexLove uses 0-255 range) + luaunit.assertAlmostEquals( + hex_color.r / 255, + color_def.r, + 0.01, + string.format("%s hex red component mismatch", color_def.name) + ) + luaunit.assertAlmostEquals( + hex_color.g / 255, + color_def.g, + 0.01, + string.format("%s hex green component mismatch", color_def.name) + ) + luaunit.assertAlmostEquals( + hex_color.b / 255, + color_def.b, + 0.01, + string.format("%s hex blue component mismatch", color_def.name) + ) + end + + -- Test color variations (opacity, brightness adjustments) + for color_name, color_set in pairs(theme_colors) do + color_variations[color_name] = {} + + -- Create opacity variations + local opacities = { 0.1, 0.25, 0.5, 0.75, 0.9 } + for _, opacity in ipairs(opacities) do + local variant_color = Color.new(color_set.manual.r, color_set.manual.g, color_set.manual.b, opacity) + color_variations[color_name]["alpha_" .. tostring(opacity)] = variant_color + + luaunit.assertEquals( + variant_color.a, + opacity, + string.format("%s opacity variant should have correct alpha", color_name) + ) + end + + -- Create brightness variations + local brightness_factors = { 0.3, 0.6, 1.0, 1.4, 1.8 } + for _, factor in ipairs(brightness_factors) do + local bright_r = math.min(1.0, color_set.manual.r * factor) + local bright_g = math.min(1.0, color_set.manual.g * factor) + local bright_b = math.min(1.0, color_set.manual.b * factor) + + local bright_color = Color.new(bright_r, bright_g, bright_b, 1.0) + color_variations[color_name]["bright_" .. tostring(factor)] = bright_color + + luaunit.assertTrue( + bright_r <= 1.0 and bright_g <= 1.0 and bright_b <= 1.0, + "Brightness variations should not exceed 1.0" + ) + end + end + + -- Test color application to complex UI structure + local ui_container = Gui.new({ + w = 800, + h = 600, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.VERTICAL, + gap = 10, + }) + + -- Apply theme colors to different UI components + local component_types = { "header", "content", "sidebar", "footer", "modal" } + for i, comp_type in ipairs(component_types) do + local component = Gui.new({ + w = 780, + h = 100, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.HORIZONTAL, + justifyContent = enums.JustifyContent.SPACE_BETWEEN, + alignItems = enums.AlignItems.CENTER, + gap = 8, + }) + component.parent = ui_container + table.insert(ui_container.children, component) + + -- Apply color based on component type + local color_name = base_colors[((i - 1) % #base_colors) + 1].name + component.backgroundColor = theme_colors[color_name].manual + component.textColor = Color.new(1, 1, 1, 1) -- White text + + -- Add sub-components with color variations + for j = 1, 4 do + local sub_component = Gui.new({ + w = 150, + h = 80, + positioning = enums.Positioning.FLEX, + justifyContent = enums.JustifyContent.CENTER, + alignItems = enums.AlignItems.CENTER, + }) + sub_component.parent = component + table.insert(component.children, sub_component) + + -- Apply color variation + local opacity_key = "alpha_" .. tostring(opacities[((j - 1) % #opacities) + 1]) + sub_component.backgroundColor = color_variations[color_name][opacity_key] + end + end + + -- Verify color system integrity + ui_container:layoutChildren() + + luaunit.assertEquals(#ui_container.children, 5, "Should have 5 themed components") + luaunit.assertEquals(#theme_colors, 5, "Should have 5 base theme colors") + + local total_variations = 0 + for _, variations in pairs(color_variations) do + for _ in pairs(variations) do + total_variations = total_variations + 1 + end + end + luaunit.assertTrue(total_variations >= 50, "Should have created numerous color variations") + + print( + string.format( + "Color Management System: %d base colors, %d variations, %d UI components", + #base_colors, + total_variations, + #ui_container.children + ) + ) +end + +-- ============================================ +-- Test 12: Advanced Text and Auto-sizing Complex System +-- ============================================ + +function TestAuxiliaryFunctions:testAdvancedTextAndAutoSizingSystem() + print("\n=== Test 12: Advanced Text and Auto-sizing System ===") + + -- Create dynamic text content management system + local content_manager = { + dynamic_texts = {}, + auto_sized_containers = {}, + text_metrics = {}, + } + + -- Test complex multi-language text scenarios + local text_scenarios = { + { + id = "english_short", + content = "Hello World", + size = 14, + expected_behavior = "compact", + }, + { + id = "english_long", + content = "This is a much longer text that should demonstrate text wrapping and auto-sizing capabilities in various scenarios", + size = 16, + expected_behavior = "expanding", + }, + { + id = "mixed_content", + content = "Product: Widget Pro™\nPrice: $299.99\nAvailability: In Stock ✓", + size = 12, + expected_behavior = "multiline", + }, + { + id = "special_chars", + content = "Spéciál Chàracters: αβγδε • ★ ♦ ♠ → ∞", + size = 18, + expected_behavior = "unicode", + }, + { + id = "numbers_symbols", + content = "Data: 123,456.78 | Progress: 85% | Status: [●●●○○]", + size = 13, + expected_behavior = "data_display", + }, + } + + -- Create dynamic text containers with auto-sizing + local main_container = Gui.new({ + w = 1000, + h = 800, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.VERTICAL, + gap = 15, + }) + + for _, scenario in ipairs(text_scenarios) do + local text_container = Gui.new({ + w = 900, + h = 100, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.HORIZONTAL, + justifyContent = enums.JustifyContent.SPACE_BETWEEN, + alignItems = enums.AlignItems.FLEX_START, + gap = 20, + }) + text_container.parent = main_container + table.insert(main_container.children, text_container) + + -- Create auto-sized text element + local text_element = Gui.new({ + text = scenario.content, + textSize = scenario.size, + w = 0, + h = 0, -- Start with zero size for auto-sizing + }) + text_element.parent = text_container + table.insert(text_container.children, text_element) + + -- Calculate auto dimensions + local auto_width = text_element:calculateAutoWidth() + local auto_height = text_element:calculateAutoHeight() + local text_width = text_element:calculateTextWidth() + local text_height = text_element:calculateTextHeight() + + -- Store metrics + content_manager.text_metrics[scenario.id] = { + auto_width = auto_width, + auto_height = auto_height, + text_width = text_width, + text_height = text_height, + char_count = string.len(scenario.content), + content = scenario.content, + } + + -- Verify text calculations + luaunit.assertTrue(auto_width >= 0, string.format("%s: Auto width should be non-negative", scenario.id)) + luaunit.assertTrue(auto_height >= 0, string.format("%s: Auto height should be non-negative", scenario.id)) + luaunit.assertTrue(text_width >= 0, string.format("%s: Text width should be non-negative", scenario.id)) + luaunit.assertTrue(text_height >= 0, string.format("%s: Text height should be non-negative", scenario.id)) + + -- For single-line text, auto width should roughly match text width + if not string.find(scenario.content, "\n") then + luaunit.assertAlmostEquals( + auto_width, + text_width, + text_width * 0.1, + string.format("%s: Auto width should approximate text width for single-line", scenario.id) + ) + end + + -- Apply auto-sizing + text_element.w = auto_width + text_element.h = auto_height + + content_manager.auto_sized_containers[scenario.id] = text_element + + -- Create comparison elements with fixed sizes + local fixed_element = Gui.new({ + text = scenario.content, + textSize = scenario.size, + w = 200, + h = 50, -- Fixed size + }) + fixed_element.parent = text_container + table.insert(text_container.children, fixed_element) + + -- Create adaptive element that changes based on content length + local adaptive_element = Gui.new({ + text = scenario.content, + textSize = scenario.size, + w = math.max(150, auto_width * 0.8), + h = math.max(30, auto_height * 1.2), + }) + adaptive_element.parent = text_container + table.insert(text_container.children, adaptive_element) + end + + -- Test dynamic text updates with auto-resizing + local dynamic_updates = { + { target = "english_short", new_text = "Updated: Hello Universe!", autoresize = true }, + { target = "english_long", new_text = "Shortened text", autoresize = true }, + { target = "mixed_content", new_text = "Status: SOLD OUT ❌", autoresize = false }, + { + target = "numbers_symbols", + new_text = "Final Results: 999,999.99 | Complete: 100% | Status: [●●●●●]", + autoresize = true, + }, + } + + for _, update in ipairs(dynamic_updates) do + local element = content_manager.auto_sized_containers[update.target] + if element then + local original_width = element.w + local original_height = element.h + + element:updateText(update.new_text, update.autoresize) + + if update.autoresize then + -- With autoresize, dimensions should potentially change + local new_auto_width = element:calculateAutoWidth() + local new_auto_height = element:calculateAutoHeight() + + luaunit.assertEquals(element.text, update.new_text, string.format("%s: Text should be updated", update.target)) + + -- If autoresize is working, element dimensions should match auto calculations + if new_auto_width ~= original_width or new_auto_height ~= original_height then + content_manager.text_metrics[update.target .. "_updated"] = { + auto_width = new_auto_width, + auto_height = new_auto_height, + original_width = original_width, + original_height = original_height, + text_changed = true, + } + end + else + -- Without autoresize, dimensions should remain the same + luaunit.assertEquals( + element.w, + original_width, + string.format("%s: Width should not change without autoresize", update.target) + ) + luaunit.assertEquals( + element.h, + original_height, + string.format("%s: Height should not change without autoresize", update.target) + ) + end + end + end + + -- Test complex auto-sizing with nested structures + local nested_container = Gui.new({ + w = 800, + h = 200, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.VERTICAL, + gap = 10, + }) + nested_container.parent = main_container + table.insert(main_container.children, nested_container) + + -- Create nested structure with auto-sizing children + for level = 1, 3 do + local level_container = Gui.new({ + w = 750 - (level * 50), + h = 60, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.HORIZONTAL, + justifyContent = enums.JustifyContent.SPACE_AROUND, + gap = 5, + }) + level_container.parent = level == 1 and nested_container + or main_container.children[#main_container.children].children[level - 1] + table.insert( + (level == 1 and nested_container or main_container.children[#main_container.children].children[level - 1]).children, + level_container + ) + + for item = 1, 4 do + local item_text = string.format("L%d-Item%d: %s", level, item, string.rep("Text ", level)) + local text_item = Gui.new({ + text = item_text, + textSize = 14 - level, + w = 0, + h = 0, + }) + text_item.parent = level_container + table.insert(level_container.children, text_item) + + -- Apply auto-sizing + text_item.w = text_item:calculateAutoWidth() + text_item.h = text_item:calculateAutoHeight() + end + end + + -- Perform layout and verify + main_container:layoutChildren() + + luaunit.assertEquals( + #main_container.children, + #text_scenarios + 1, + "Should have scenario containers plus nested container" + ) + luaunit.assertTrue(#content_manager.text_metrics >= #text_scenarios, "Should have metrics for all scenarios") + + print( + string.format( + "Text Management System: %d scenarios, %d metrics, %d updates", + #text_scenarios, + #content_manager.text_metrics, + #dynamic_updates + ) + ) +end + +-- ============================================ +-- Test 13: Comprehensive Animation Engine Testing +-- ============================================ + +function TestAuxiliaryFunctions:testComprehensiveAnimationEngine() + print("\n=== Test 13: Comprehensive Animation Engine ===") + + -- Create animation test environment + local animation_system = { + active_animations = {}, + completed_animations = {}, + animation_chains = {}, + performance_metrics = {}, + } + + -- Create container for animated elements + local animation_container = Gui.new({ + w = 1200, + h = 800, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.VERTICAL, + gap = 20, + }) + + -- Test various animation types and combinations + local animation_test_cases = { + { + name = "fade_animations", + elements = 8, + animation_type = "fade", + duration_range = { 0.5, 2.0 }, + properties = { opacity = { from = 1.0, to = 0.0 } }, + }, + { + name = "scale_animations", + elements = 6, + animation_type = "scale", + duration_range = { 1.0, 3.0 }, + properties = { + width = { from = 100, to = 200 }, + height = { from = 50, to = 100 }, + }, + }, + { + name = "complex_mixed", + elements = 10, + animation_type = "mixed", + duration_range = { 0.8, 2.5 }, + properties = { + opacity = { from = 0.2, to = 1.0 }, + width = { from = 80, to = 150 }, + height = { from = 40, to = 80 }, + }, + }, + } + + -- Create and configure animations for each test case + for case_idx, test_case in ipairs(animation_test_cases) do + local case_container = Gui.new({ + w = 1180, + h = 200, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.HORIZONTAL, + flexWrap = enums.FlexWrap.WRAP, + justifyContent = enums.JustifyContent.SPACE_AROUND, + gap = 15, + }) + case_container.parent = animation_container + table.insert(animation_container.children, case_container) + + animation_system.animation_chains[test_case.name] = {} + + for elem_idx = 1, test_case.elements do + local element = Gui.new({ + w = test_case.properties.width and test_case.properties.width.from or 120, + h = test_case.properties.height and test_case.properties.height.from or 60, + opacity = test_case.properties.opacity and test_case.properties.opacity.from or 1.0, + }) + element.parent = case_container + table.insert(case_container.children, element) + + -- Create animation based on type + local duration = test_case.duration_range[1] + + (math.random() * (test_case.duration_range[2] - test_case.duration_range[1])) + + local animation + if test_case.animation_type == "fade" then + animation = Gui.Animation.fade(duration, test_case.properties.opacity.from, test_case.properties.opacity.to) + elseif test_case.animation_type == "scale" then + animation = Gui.Animation.scale(duration, { + width = test_case.properties.width.from, + height = test_case.properties.height.from, + }, { + width = test_case.properties.width.to, + height = test_case.properties.height.to, + }) + elseif test_case.animation_type == "mixed" then + -- Create complex animation with multiple properties + animation = { + duration = duration, + elapsed = 0, + start = {}, + final = {}, + element = element, + properties = {}, + } + + -- Set up start and final values for all properties + for prop, values in pairs(test_case.properties) do + animation.start[prop] = values.from + animation.final[prop] = values.to + animation.properties[prop] = true + end + + -- Add interpolation method + animation.interpolate = function(self) + local progress = math.min(1.0, self.elapsed / self.duration) + local result = {} + + for prop in pairs(self.properties) do + local start_val = self.start[prop] + local final_val = self.final[prop] + result[prop] = start_val + (final_val - start_val) * progress + end + + return result + end + + -- Add update method + animation.update = function(self, dt) + self.elapsed = self.elapsed + dt + local finished = self.elapsed >= self.duration + + if finished then + self.elapsed = self.duration + end + + -- Apply interpolated values to element + local values = self:interpolate() + for prop, value in pairs(values) do + if prop == "opacity" then + self.element.opacity = value + elseif prop == "width" then + self.element.w = value + elseif prop == "height" then + self.element.h = value + end + end + + return finished + end + + -- Add apply method + animation.apply = function(self, element) + self.element = element + element.animation = self + end + end + + -- Verify animation creation + luaunit.assertNotNil(animation, string.format("Animation should be created for %s", test_case.name)) + luaunit.assertTrue(animation.duration > 0, "Animation should have positive duration") + luaunit.assertNotNil(animation.start, "Animation should have start properties") + luaunit.assertNotNil(animation.final, "Animation should have final properties") + + -- Apply animation to element + animation:apply(element) + + animation_system.active_animations[test_case.name .. "_" .. elem_idx] = { + animation = animation, + element = element, + start_time = 0, + test_case = test_case.name, + } + + table.insert(animation_system.animation_chains[test_case.name], animation) + end + end + + -- Simulate animation updates over time + local total_simulation_time = 4.0 -- 4 seconds + local dt = 1 / 60 -- 60 FPS + local frame_count = 0 + local active_count_over_time = {} + + for sim_time = 0, total_simulation_time, dt do + frame_count = frame_count + 1 + local active_count = 0 + local completed_this_frame = {} + + -- Update all active animations + for anim_id, anim_data in pairs(animation_system.active_animations) do + if anim_data.animation then + local finished = anim_data.animation:update(dt) + + if finished then + table.insert(completed_this_frame, anim_id) + animation_system.completed_animations[anim_id] = { + animation = anim_data.animation, + completion_time = sim_time, + total_frames = frame_count, + } + else + active_count = active_count + 1 + end + end + end + + -- Remove completed animations + for _, anim_id in ipairs(completed_this_frame) do + animation_system.active_animations[anim_id] = nil + end + + active_count_over_time[frame_count] = active_count + + -- Test interpolation at specific progress points + if frame_count % 60 == 0 then -- Every second + for anim_id, anim_data in pairs(animation_system.active_animations) do + if anim_data.animation.interpolate then + local progress = anim_data.animation.elapsed / anim_data.animation.duration + local interpolated = anim_data.animation:interpolate() + + luaunit.assertTrue( + progress >= 0 and progress <= 1, + string.format("Animation progress should be 0-1, got %.3f", progress) + ) + luaunit.assertNotNil(interpolated, "Interpolation should return values") + end + end + end + end + + -- Analyze animation performance and correctness + local total_animations = 0 + local total_completed = 0 + + for test_case_name, animations in pairs(animation_system.animation_chains) do + total_animations = total_animations + #animations + end + + for _ in pairs(animation_system.completed_animations) do + total_completed = total_completed + 1 + end + + animation_system.performance_metrics = { + total_animations = total_animations, + completed_animations = total_completed, + completion_rate = total_completed / total_animations, + simulation_frames = frame_count, + simulation_time = total_simulation_time, + } + + -- Verify animation system functionality + luaunit.assertTrue(total_animations > 20, "Should have created substantial number of animations") + luaunit.assertTrue(total_completed > 0, "Some animations should have completed") + luaunit.assertTrue( + animation_system.performance_metrics.completion_rate > 0.5, + "Majority of animations should complete within simulation time" + ) + + -- Test animation chaining and sequencing + local chain_element = Gui.new({ w = 100, h = 50, opacity = 1.0 }) + chain_element.parent = animation_container + table.insert(animation_container.children, chain_element) + + -- Create animation chain: fade out -> scale up -> fade in + local chain_animations = { + Gui.Animation.fade(0.5, 1.0, 0.0), + Gui.Animation.scale(0.8, { width = 100, height = 50 }, { width = 200, height = 100 }), + Gui.Animation.fade(0.5, 0.0, 1.0), + } + + -- Test each animation in the chain + for i, chain_anim in ipairs(chain_animations) do + luaunit.assertNotNil(chain_anim, string.format("Chain animation %d should exist", i)) + luaunit.assertTrue(chain_anim.duration > 0, string.format("Chain animation %d should have duration", i)) + + -- Apply and test first few frames + chain_anim:apply(chain_element) + for frame = 1, 5 do + local finished = chain_anim:update(0.1) + if frame < 5 then + luaunit.assertFalse(finished, string.format("Chain animation %d should not finish in %d frames", i, frame)) + end + end + end + + -- Perform final layout + animation_container:layoutChildren() + + luaunit.assertEquals( + #animation_container.children, + #animation_test_cases + 1, + "Should have containers for each test case plus chain element" + ) + + print( + string.format( + "Animation Engine: %d total animations, %d completed (%.1f%%), %d frames simulated", + animation_system.performance_metrics.total_animations, + animation_system.performance_metrics.completed_animations, + animation_system.performance_metrics.completion_rate * 100, + animation_system.performance_metrics.simulation_frames + ) + ) +end + +-- ============================================ +-- Test 14: Advanced GUI Management and Cleanup System +-- ============================================ + +function TestAuxiliaryFunctions:testAdvancedGUIManagementAndCleanup() + print("\n=== Test 14: Advanced GUI Management and Cleanup ===") + + -- Create complex GUI hierarchy for testing management + local gui_manager = { + element_registry = {}, + destruction_log = {}, + memory_snapshots = {}, + hierarchy_metrics = {}, + } + + -- Test complex nested structure creation and management + local application_structure = { + { + type = "main_window", + children = { + { + type = "header", + children = { + { type = "logo", children = {} }, + { + type = "nav_menu", + children = { + { type = "nav_item", children = {} }, + { type = "nav_item", children = {} }, + { type = "nav_item", children = {} }, + }, + }, + { + type = "user_area", + children = { + { type = "avatar", children = {} }, + { + type = "dropdown", + children = { + { type = "menu_item", children = {} }, + { type = "menu_item", children = {} }, + { type = "divider", children = {} }, + { type = "menu_item", children = {} }, + }, + }, + }, + }, + }, + }, + { + type = "main_content", + children = { + { + type = "sidebar", + children = { + { + type = "sidebar_section", + children = { + { type = "section_header", children = {} }, + { type = "section_item", children = {} }, + { type = "section_item", children = {} }, + { type = "section_item", children = {} }, + }, + }, + { + type = "sidebar_section", + children = { + { type = "section_header", children = {} }, + { type = "section_item", children = {} }, + { type = "section_item", children = {} }, + }, + }, + }, + }, + { + type = "content_area", + children = { + { + type = "content_header", + children = { + { type = "breadcrumb", children = {} }, + { + type = "actions", + children = { + { type = "action_button", children = {} }, + { type = "action_button", children = {} }, + { type = "action_dropdown", children = {} }, + }, + }, + }, + }, + { + type = "content_body", + children = { + { + type = "data_grid", + children = { + { type = "grid_header", children = {} }, + { + type = "grid_row", + children = { + { type = "grid_cell", children = {} }, + { type = "grid_cell", children = {} }, + { type = "grid_cell", children = {} }, + }, + }, + { + type = "grid_row", + children = { + { type = "grid_cell", children = {} }, + { type = "grid_cell", children = {} }, + { type = "grid_cell", children = {} }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + type = "footer", + children = { + { type = "footer_links", children = {} }, + { type = "footer_info", children = {} }, + }, + }, + }, + }, + } + + -- Recursive function to create GUI structure + local function createGUIHierarchy(structure, parent, level) + level = level or 1 + local created_elements = {} + + for _, item in ipairs(structure) do + local element = Gui.new({ + w = math.max(100, 300 - level * 20), + h = math.max(30, 80 - level * 5), + positioning = enums.Positioning.FLEX, + flexDirection = level % 2 == 0 and enums.FlexDirection.HORIZONTAL or enums.FlexDirection.VERTICAL, + justifyContent = enums.JustifyContent.FLEX_START, + alignItems = enums.AlignItems.STRETCH, + gap = math.max(2, 10 - level), + }) + + if parent then + element.parent = parent + table.insert(parent.children, element) + end + + -- Register element in management system + local element_id = item.type .. "_" .. tostring(level) .. "_" .. tostring(#created_elements + 1) + gui_manager.element_registry[element_id] = { + element = element, + type = item.type, + level = level, + parent_id = parent and "parent_of_" .. element_id or nil, + children_count = #item.children, + } + + table.insert(created_elements, { id = element_id, element = element }) + + -- Recursively create children + if #item.children > 0 then + local child_elements = createGUIHierarchy(item.children, element, level + 1) + gui_manager.element_registry[element_id].child_elements = child_elements + end + end + + return created_elements + end + + -- Create the complex structure + local root_elements = createGUIHierarchy(application_structure) + local root_element = root_elements[1].element + + -- Take initial memory snapshot + gui_manager.memory_snapshots.initial = { + element_count = 0, + registry_size = 0, + } + + for _ in pairs(gui_manager.element_registry) do + gui_manager.memory_snapshots.initial.element_count = gui_manager.memory_snapshots.initial.element_count + 1 + end + + gui_manager.memory_snapshots.initial.registry_size = #gui_manager.element_registry + gui_manager.memory_snapshots.initial.top_elements = #Gui.topElements + + -- Perform layout to establish structure + root_element:layoutChildren() + + -- Calculate hierarchy metrics + local function calculateHierarchyMetrics(element, depth) + depth = depth or 1 + local metrics = { + max_depth = depth, + total_elements = 1, + elements_by_level = {}, + } + + metrics.elements_by_level[depth] = 1 + + for _, child in ipairs(element.children) do + local child_metrics = calculateHierarchyMetrics(child, depth + 1) + metrics.max_depth = math.max(metrics.max_depth, child_metrics.max_depth) + metrics.total_elements = metrics.total_elements + child_metrics.total_elements + + for level, count in pairs(child_metrics.elements_by_level) do + metrics.elements_by_level[level] = (metrics.elements_by_level[level] or 0) + count + end + end + + return metrics + end + + gui_manager.hierarchy_metrics = calculateHierarchyMetrics(root_element) + + -- Test selective destruction (remove sidebar while keeping other elements) + local sidebar_element = nil + for element_id, element_data in pairs(gui_manager.element_registry) do + if element_data.type == "sidebar" then + sidebar_element = element_data.element + break + end + end + + if sidebar_element then + local sidebar_children_count = #sidebar_element.children + local sidebar_parent = sidebar_element.parent + local original_parent_children = sidebar_parent and #sidebar_parent.children or 0 + + -- Destroy sidebar and track the process + local destruction_start = os.clock() + sidebar_element:destroy() + local destruction_time = os.clock() - destruction_start + + gui_manager.destruction_log.sidebar = { + destruction_time = destruction_time, + children_destroyed = sidebar_children_count, + parent_children_after = sidebar_parent and #sidebar_parent.children or 0, + } + + -- Verify destruction + luaunit.assertNil(sidebar_element.parent, "Destroyed element should have no parent") + luaunit.assertEquals(#sidebar_element.children, 0, "Destroyed element should have no children") + + if sidebar_parent then + luaunit.assertEquals( + #sidebar_parent.children, + original_parent_children - 1, + "Parent should have one fewer child after destruction" + ) + end + end + + -- Test mass destruction and recreation cycle + local destruction_cycles = 3 + for cycle = 1, destruction_cycles do + -- Take pre-destruction snapshot + gui_manager.memory_snapshots["pre_cycle_" .. cycle] = { + top_elements = #Gui.topElements, + registry_size = #gui_manager.element_registry, + } + + -- Destroy all GUI elements + local destruction_start = os.clock() + Gui.destroy() + local destruction_time = os.clock() - destruction_start + + -- Take post-destruction snapshot + gui_manager.memory_snapshots["post_destruction_" .. cycle] = { + top_elements = #Gui.topElements, + destruction_time = destruction_time, + } + + -- Verify complete destruction + luaunit.assertEquals(#Gui.topElements, 0, string.format("Cycle %d: All top elements should be destroyed", cycle)) + + -- Force garbage collection + collectgarbage("collect") + + -- Recreate simplified structure for next cycle + if cycle < destruction_cycles then + local simple_structure = { + { + type = "test_container", + children = { + { type = "test_item", children = {} }, + { type = "test_item", children = {} }, + { type = "test_item", children = {} }, + }, + }, + } + + local recreation_start = os.clock() + createGUIHierarchy(simple_structure) + local recreation_time = os.clock() - recreation_start + + gui_manager.memory_snapshots["post_recreation_" .. cycle] = { + top_elements = #Gui.topElements, + recreation_time = recreation_time, + } + end + end + + -- Test complex element retrieval and manipulation + local final_container = Gui.new({ + w = 400, + h = 300, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.VERTICAL, + }) + + -- Create elements with specific IDs for retrieval testing + local managed_elements = {} + for i = 1, 10 do + local element = Gui.new({ + w = 350, + h = 25, + text = "Managed Element " .. i, + textSize = 12, + }) + element.parent = final_container + table.insert(final_container.children, element) + + managed_elements["element_" .. i] = element + end + + -- Test bounds calculation for all elements + for element_id, element in pairs(managed_elements) do + local bounds = element:getBounds() + + luaunit.assertNotNil(bounds, string.format("%s: Bounds should be calculable", element_id)) + luaunit.assertTrue(bounds.width > 0, string.format("%s: Bounds width should be positive", element_id)) + luaunit.assertTrue(bounds.height > 0, string.format("%s: Bounds height should be positive", element_id)) + luaunit.assertTrue(bounds.x >= 0, string.format("%s: Bounds x should be non-negative", element_id)) + luaunit.assertTrue(bounds.y >= 0, string.format("%s: Bounds y should be non-negative", element_id)) + end + + -- Test opacity management across hierarchy + for i, element_pair in pairs(managed_elements) do + if i % 2 == 0 then + element_pair:updateOpacity(0.5) + luaunit.assertEquals(element_pair.opacity, 0.5, "Even elements should have 0.5 opacity") + end + end + + -- Perform final layout + final_container:layoutChildren() + + -- Verify final GUI state + luaunit.assertTrue(#Gui.topElements >= 1, "Should have at least final container") + luaunit.assertEquals(#final_container.children, 10, "Final container should have 10 managed elements") + luaunit.assertTrue(gui_manager.hierarchy_metrics.max_depth >= 4, "Original hierarchy should have been deep") + luaunit.assertTrue(gui_manager.hierarchy_metrics.total_elements >= 20, "Should have created substantial hierarchy") + + print( + string.format( + "GUI Management: %d elements, %d max depth, %d destruction cycles, %d managed elements", + gui_manager.hierarchy_metrics.total_elements, + gui_manager.hierarchy_metrics.max_depth, + destruction_cycles, + #managed_elements + ) + ) +end + +-- ============================================ +-- Test 15: Extreme Edge Cases and Error Resilience +-- ============================================ + +function TestAuxiliaryFunctions:testExtremeEdgeCasesAndErrorResilience() + print("\n=== Test 15: Extreme Edge Cases and Error Resilience ===") + + -- Test boundary conditions and error handling + local edge_case_results = { + color_tests = {}, + text_tests = {}, + animation_tests = {}, + hierarchy_tests = {}, + performance_tests = {}, + } + + -- Extreme color value testing + local extreme_color_tests = { + { name = "negative_values", r = -1.0, g = -0.5, b = -2.0, a = -0.3 }, + { name = "huge_values", r = 999.0, g = 1000.0, b = 50000.0, a = 100.0 }, + { name = "zero_values", r = 0.0, g = 0.0, b = 0.0, a = 0.0 }, + { name = "fractional_extremes", r = 0.0001, g = 0.9999, b = 0.00001, a = 0.99999 }, + { name = "infinity_values", r = math.huge, g = -math.huge, b = 1 / 0, a = -1 / 0 }, + } + + for _, test in ipairs(extreme_color_tests) do + local success, result = pcall(function() + local color = Color.new(test.r, test.g, test.b, test.a) + return { + created = true, + r = color.r, + g = color.g, + b = color.b, + a = color.a, + rgba = { color:toRGBA() }, + } + end) + + edge_case_results.color_tests[test.name] = { + success = success, + result = result, + expected_error = test.name == "infinity_values", + } + + if test.name ~= "infinity_values" then + luaunit.assertTrue(success, string.format("Color creation should handle %s", test.name)) + end + end + + -- Extreme hex color testing + local extreme_hex_tests = { + { hex = "", should_error = true }, + { hex = "#", should_error = true }, + { hex = "#FF", should_error = true }, + { hex = "#FFFF", should_error = true }, + { hex = "#FFFFFF", should_error = false }, + { hex = "#FFFFFFFF", should_error = false }, + { hex = "#FFFFFFFFFF", should_error = true }, + { hex = "#GGGGGG", should_error = true }, + { hex = "#123456789", should_error = true }, + { hex = "FFFFFF", should_error = false }, -- without # + { hex = "#ffffff", should_error = false }, -- lowercase + { hex = "#FfFfFf", should_error = false }, -- mixed case + } + + for _, test in ipairs(extreme_hex_tests) do + local success, result = pcall(function() + return Color.fromHex(test.hex) + end) + + if test.should_error then + luaunit.assertFalse(success, string.format("Hex '%s' should cause error", test.hex)) + else + luaunit.assertTrue(success, string.format("Hex '%s' should be valid", test.hex)) + end + end + + -- Extreme text and sizing tests + local extreme_text_tests = { + { name = "empty_string", text = "" }, + { name = "single_char", text = "A" }, + { name = "very_long", text = string.rep("Very long text that goes on and on and on. ", 100) }, + { + name = "unicode_heavy", + text = "🎉🚀⭐️🌟💫✨🎨🎯🎪🎭🎬🎮🎲🎳🎸🎹🎺🎻🥁🎤🎧🎼🎵🎶", + }, + { name = "special_chars", text = "\n\t\r\b\f\v\\\"'`~!@#$%^&*()_+-=[]{}|;:,.<>?" }, + { name = "mixed_newlines", text = "Line 1\nLine 2\r\nLine 3\rLine 4\n\nLine 6" }, + { name = "numbers_symbols", text = "0123456789!@#$%^&*()_+-=[]{}|\\:;\";'<>?,./`~" }, + } + + for _, test in ipairs(extreme_text_tests) do + local element = Gui.new({ + text = test.text, + textSize = 14, + w = 0, + h = 0, + }) + + local text_width = element:calculateTextWidth() + local text_height = element:calculateTextHeight() + local auto_width = element:calculateAutoWidth() + local auto_height = element:calculateAutoHeight() + + edge_case_results.text_tests[test.name] = { + text_width = text_width, + text_height = text_height, + auto_width = auto_width, + auto_height = auto_height, + char_count = string.len(test.text), + } + + -- All calculations should return non-negative values + luaunit.assertTrue(text_width >= 0, string.format("%s: Text width should be non-negative", test.name)) + luaunit.assertTrue(text_height >= 0, string.format("%s: Text height should be non-negative", test.name)) + luaunit.assertTrue(auto_width >= 0, string.format("%s: Auto width should be non-negative", test.name)) + luaunit.assertTrue(auto_height >= 0, string.format("%s: Auto height should be non-negative", test.name)) + + -- Test text updates with extreme values + local success = pcall(function() + element:updateText(test.text, true) + element:updateText(nil) -- Should preserve existing text + element:updateText("") -- Should set to empty + end) + + luaunit.assertTrue(success, string.format("%s: Text updates should not crash", test.name)) + end + + -- Extreme animation testing + local extreme_animation_tests = { + { name = "zero_duration", duration = 0 }, + { name = "negative_duration", duration = -1.0 }, + { name = "huge_duration", duration = 999999.0 }, + { name = "tiny_duration", duration = 0.001 }, + { name = "infinity_duration", duration = math.huge }, + } + + for _, test in ipairs(extreme_animation_tests) do + local success, result = pcall(function() + local animation = Gui.Animation.fade(test.duration, 1.0, 0.0) + return { + created = true, + duration = animation.duration, + interpolated = animation:interpolate(), + } + end) + + edge_case_results.animation_tests[test.name] = { + success = success, + result = result, + } + + -- Most duration values should be handled gracefully + if test.name ~= "infinity_duration" then + luaunit.assertTrue(success, string.format("Animation with %s should be created", test.name)) + end + end + + -- Extreme hierarchy testing + local max_depth = 20 + local extreme_hierarchy_element = Gui.new({ w = 1000, h = 800 }) + local current_parent = extreme_hierarchy_element + + -- Create extremely deep hierarchy + for depth = 1, max_depth do + local child = Gui.new({ + w = math.max(50, 1000 - depth * 45), + h = math.max(30, 800 - depth * 35), + positioning = enums.Positioning.FLEX, + flexDirection = depth % 2 == 0 and enums.FlexDirection.HORIZONTAL or enums.FlexDirection.VERTICAL, + }) + child.parent = current_parent + table.insert(current_parent.children, child) + current_parent = child + end + + -- Test layout performance with extreme depth + local deep_layout_start = os.clock() + local layout_success = pcall(function() + extreme_hierarchy_element:layoutChildren() + end) + local deep_layout_time = os.clock() - deep_layout_start + + edge_case_results.hierarchy_tests.extreme_depth = { + success = layout_success, + depth = max_depth, + layout_time = deep_layout_time, + } + + luaunit.assertTrue(layout_success, "Extremely deep hierarchy should layout without crashing") + luaunit.assertTrue(deep_layout_time < 5.0, "Deep hierarchy layout should complete in reasonable time") + + -- Test extreme width hierarchy (many siblings) + local wide_container = Gui.new({ + w = 2000, + h = 200, + positioning = enums.Positioning.FLEX, + flexDirection = enums.FlexDirection.HORIZONTAL, + flexWrap = enums.FlexWrap.WRAP, + gap = 2, + }) + + local max_siblings = 500 + for i = 1, max_siblings do + local sibling = Gui.new({ w = 30, h = 25 }) + sibling.parent = wide_container + table.insert(wide_container.children, sibling) + end + + local wide_layout_start = os.clock() + local wide_layout_success = pcall(function() + wide_container:layoutChildren() + end) + local wide_layout_time = os.clock() - wide_layout_start + + edge_case_results.hierarchy_tests.extreme_width = { + success = wide_layout_success, + siblings = max_siblings, + layout_time = wide_layout_time, + } + + luaunit.assertTrue(wide_layout_success, "Extremely wide hierarchy should layout without crashing") + luaunit.assertTrue(wide_layout_time < 10.0, "Wide hierarchy layout should complete in reasonable time") + + -- Test massive cleanup operations + local cleanup_elements = {} + for i = 1, 1000 do + local element = Gui.new({ w = 50, h = 30 }) + table.insert(cleanup_elements, element) + end + + local cleanup_start = os.clock() + local cleanup_success = pcall(function() + Gui.destroy() + end) + local cleanup_time = os.clock() - cleanup_start + + edge_case_results.performance_tests.massive_cleanup = { + success = cleanup_success, + elements = #cleanup_elements, + cleanup_time = cleanup_time, + } + + luaunit.assertTrue(cleanup_success, "Massive cleanup should complete without crashing") + luaunit.assertTrue(cleanup_time < 5.0, "Massive cleanup should complete in reasonable time") + luaunit.assertEquals(#Gui.topElements, 0, "All elements should be cleaned up") + + -- Test opacity boundary resilience + local opacity_element = Gui.new({ w = 100, h = 50, opacity = 0.5 }) + local extreme_opacities = { -999, -1, 0, 0.5, 1, 2, 999, math.huge, -math.huge } + + for _, opacity in ipairs(extreme_opacities) do + local success = pcall(function() + opacity_element:updateOpacity(opacity) + end) + luaunit.assertTrue(success, string.format("Opacity update with value %s should not crash", tostring(opacity))) + end + + -- Summary of edge case testing + local total_tests = 0 + local successful_tests = 0 + + for category, tests in pairs(edge_case_results) do + for test_name, result in pairs(tests) do + total_tests = total_tests + 1 + if result.success ~= false then + successful_tests = successful_tests + 1 + end + end + end + + print( + string.format( + "Edge Case Testing: %d/%d tests handled gracefully (%.1f%%)", + successful_tests, + total_tests, + (successful_tests / total_tests) * 100 + ) + ) + + luaunit.assertTrue(successful_tests / total_tests > 0.8, "Should handle majority of edge cases gracefully") +end + +luaunit.LuaUnit.run()