From 07278aac9669a9bcb60d48d715eacb550ce67f8f Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Thu, 13 Nov 2025 09:23:31 -0500 Subject: [PATCH] remove subpar tests, update examples --- examples/01_flex_positioning.lua | 80 +- examples/02_grid_layout.lua | 35 +- examples/03_theming_system.lua | 45 +- examples/04_responsive_units.lua | 35 +- examples/05_animations.lua | 25 +- examples/06_event_system.lua | 37 +- examples/07_text_rendering.lua | 27 +- examples/08_absolute_relative_positioning.lua | 35 +- examples/09_styling_visual_effects.lua | 41 +- examples/10_padding_margins.lua | 39 +- examples/11_input_controls.lua | 23 +- examples/12_z_index_layering.lua | 31 +- examples/13_comprehensive_demo.lua | 43 +- examples/14_drag_slider.lua | 31 +- examples/15_scrollable_elements.lua | 25 +- examples/16-full-imput-demo.lua | 2 +- .../01_absolute_positioning_basic_tests.lua | 1124 -------- ...bsolute_positioning_child_layout_tests.lua | 1379 ---------- .../03_flex_direction_horizontal_tests.lua | 1611 ------------ .../04_flex_direction_vertical_tests.lua | 1728 ------------- .../__tests__/05_justify_content_tests.lua | 1663 ------------ testing/__tests__/06_align_items_tests.lua | 2261 ----------------- testing/__tests__/07_flex_wrap_tests.lua | 1664 ------------ .../__tests__/08_comprehensive_flex_tests.lua | 1715 ------------- .../__tests__/09_layout_validation_tests.lua | 1258 --------- testing/__tests__/10_performance_tests.lua | 1414 ----------- .../11_auxiliary_functions_tests.lua | 1867 -------------- testing/__tests__/12_units_system_tests.lua | 365 --- .../13_relative_positioning_tests.lua | 199 -- .../__tests__/14_text_scaling_basic_tests.lua | 503 ---- testing/__tests__/15_grid_layout_tests.lua | 344 --- testing/__tests__/16_event_system_tests.lua | 369 --- .../17_sibling_space_reservation_tests.lua | 437 ---- .../18_font_family_inheritance_tests.lua | 225 -- .../__tests__/19_negative_margin_tests.lua | 337 --- testing/__tests__/20_padding_resize_tests.lua | 243 -- .../21_image_scaler_nearest_tests.lua | 202 -- .../22_image_scaler_bilinear_tests.lua | 291 --- testing/__tests__/23_blur_effects_tests.lua | 238 -- testing/__tests__/24_keyboard_input_tests.lua | 730 ------ testing/__tests__/25_image_cache_tests.lua | 232 -- .../__tests__/26_object_fit_modes_tests.lua | 244 -- .../__tests__/27_object_position_tests.lua | 184 -- .../28_element_image_integration_tests.lua | 391 --- testing/__tests__/29_drag_event_tests.lua | 282 -- .../__tests__/30_scrollbar_features_tests.lua | 552 ---- .../31_immediate_mode_basic_tests.lua | 273 -- testing/__tests__/32_state_manager_tests.lua | 322 --- testing/__tests__/33_input_field_tests.lua | 2112 --------------- testing/__tests__/34_password_mode_tests.lua | 438 ---- .../35_stable_id_generation_tests.lua | 423 --- testing/runAll.lua | 38 +- 52 files changed, 269 insertions(+), 27943 deletions(-) delete mode 100644 testing/__tests__/01_absolute_positioning_basic_tests.lua delete mode 100644 testing/__tests__/02_absolute_positioning_child_layout_tests.lua delete mode 100644 testing/__tests__/03_flex_direction_horizontal_tests.lua delete mode 100644 testing/__tests__/04_flex_direction_vertical_tests.lua delete mode 100644 testing/__tests__/05_justify_content_tests.lua delete mode 100644 testing/__tests__/06_align_items_tests.lua delete mode 100644 testing/__tests__/07_flex_wrap_tests.lua delete mode 100644 testing/__tests__/08_comprehensive_flex_tests.lua delete mode 100644 testing/__tests__/09_layout_validation_tests.lua delete mode 100644 testing/__tests__/10_performance_tests.lua delete mode 100644 testing/__tests__/11_auxiliary_functions_tests.lua delete mode 100644 testing/__tests__/12_units_system_tests.lua delete mode 100644 testing/__tests__/13_relative_positioning_tests.lua delete mode 100644 testing/__tests__/14_text_scaling_basic_tests.lua delete mode 100644 testing/__tests__/15_grid_layout_tests.lua delete mode 100644 testing/__tests__/16_event_system_tests.lua delete mode 100644 testing/__tests__/17_sibling_space_reservation_tests.lua delete mode 100644 testing/__tests__/18_font_family_inheritance_tests.lua delete mode 100644 testing/__tests__/19_negative_margin_tests.lua delete mode 100644 testing/__tests__/20_padding_resize_tests.lua delete mode 100644 testing/__tests__/21_image_scaler_nearest_tests.lua delete mode 100644 testing/__tests__/22_image_scaler_bilinear_tests.lua delete mode 100644 testing/__tests__/23_blur_effects_tests.lua delete mode 100644 testing/__tests__/24_keyboard_input_tests.lua delete mode 100644 testing/__tests__/25_image_cache_tests.lua delete mode 100644 testing/__tests__/26_object_fit_modes_tests.lua delete mode 100644 testing/__tests__/27_object_position_tests.lua delete mode 100644 testing/__tests__/28_element_image_integration_tests.lua delete mode 100644 testing/__tests__/29_drag_event_tests.lua delete mode 100644 testing/__tests__/30_scrollbar_features_tests.lua delete mode 100644 testing/__tests__/31_immediate_mode_basic_tests.lua delete mode 100644 testing/__tests__/32_state_manager_tests.lua delete mode 100644 testing/__tests__/33_input_field_tests.lua delete mode 100644 testing/__tests__/34_password_mode_tests.lua delete mode 100644 testing/__tests__/35_stable_id_generation_tests.lua diff --git a/examples/01_flex_positioning.lua b/examples/01_flex_positioning.lua index 29834db..e67b483 100644 --- a/examples/01_flex_positioning.lua +++ b/examples/01_flex_positioning.lua @@ -1,12 +1,12 @@ --[[ FlexLove Example 01: Flex Positioning - + This example demonstrates flexbox layouts in FlexLove: - Flex direction (horizontal/vertical) - Justify content (main axis alignment) - Align items (cross axis alignment) - Flex wrap behavior - + Run with: love /path/to/libs/examples/01_flex_positioning.lua ]] @@ -15,18 +15,17 @@ local Lv = love -- Load FlexLove from parent directory local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() -- Initialize FlexLove with base scaling - Gui.init({ - baseScale = { width = 1920, height = 1080 } + FlexLove.init({ + baseScale = { width = 1920, height = 1080 }, }) - + -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -36,15 +35,15 @@ function Lv.load() textColor = Color.new(1, 1, 1, 1), textAlign = enums.TextAlign.CENTER, }) - + -- ======================================== -- Section 1: Horizontal Flex with Different JustifyContent Values -- ======================================== - + local yOffset = 10 - + -- Label for justify-content section - Gui.new({ + FlexLove.new({ x = "2vw", y = yOffset .. "vh", width = "96vw", @@ -53,9 +52,9 @@ function Lv.load() textSize = "2.5vh", textColor = Color.new(0.9, 0.9, 0.9, 1), }) - + yOffset = yOffset + 4 - + -- Demonstrate each justify-content option local justifyOptions = { { name = "flex-start", value = enums.JustifyContent.FLEX_START }, @@ -65,10 +64,10 @@ function Lv.load() { name = "space-around", value = enums.JustifyContent.SPACE_AROUND }, { name = "space-evenly", value = enums.JustifyContent.SPACE_EVENLY }, } - + for _, option in ipairs(justifyOptions) do -- Label for this justify option - Gui.new({ + FlexLove.new({ x = "2vw", y = yOffset .. "vh", width = "15vw", @@ -78,9 +77,9 @@ function Lv.load() textColor = Color.new(0.8, 0.8, 1, 1), textAlign = enums.TextAlign.START, }) - + -- Container demonstrating this justify-content value - local container = Gui.new({ + local container = FlexLove.new({ x = "18vw", y = yOffset .. "vh", width = "78vw", @@ -93,7 +92,7 @@ function Lv.load() border = { top = true, right = true, bottom = true, left = true }, borderColor = Color.new(0.3, 0.3, 0.4, 1), }) - + -- Add child elements local colors = { Color.new(0.8, 0.3, 0.3, 1), @@ -101,9 +100,9 @@ function Lv.load() Color.new(0.3, 0.3, 0.8, 1), Color.new(0.8, 0.8, 0.3, 1), } - + for j = 1, 4 do - Gui.new({ + FlexLove.new({ parent = container, width = "8vw", height = "5vh", @@ -114,18 +113,18 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) end - + yOffset = yOffset + 9 end - + -- ======================================== -- Section 2: Vertical Flex with Different AlignItems Values -- ======================================== - + yOffset = yOffset + 2 - + -- Label for align-items section - Gui.new({ + FlexLove.new({ x = "2vw", y = yOffset .. "vh", width = "96vw", @@ -134,9 +133,9 @@ function Lv.load() textSize = "2.5vh", textColor = Color.new(0.9, 0.9, 0.9, 1), }) - + yOffset = yOffset + 4 - + -- Note: Due to space constraints, we'll show a subset in a horizontal layout local alignOptions = { { name = "stretch", value = enums.AlignItems.STRETCH }, @@ -144,13 +143,13 @@ function Lv.load() { name = "center", value = enums.AlignItems.CENTER }, { name = "flex-end", value = enums.AlignItems.FLEX_END }, } - + local xOffset = 2 local containerWidth = 22 - + for _, option in ipairs(alignOptions) do -- Label for this align option - Gui.new({ + FlexLove.new({ x = xOffset .. "vw", y = yOffset .. "vh", width = containerWidth .. "vw", @@ -160,9 +159,9 @@ function Lv.load() textColor = Color.new(0.8, 1, 0.8, 1), textAlign = enums.TextAlign.CENTER, }) - + -- Container demonstrating this align-items value - local container = Gui.new({ + local container = FlexLove.new({ x = xOffset .. "vw", y = (yOffset + 3) .. "vh", width = containerWidth .. "vw", @@ -176,7 +175,7 @@ function Lv.load() border = { top = true, right = true, bottom = true, left = true }, borderColor = Color.new(0.3, 0.4, 0.3, 1), }) - + -- Add child elements with varying widths local widths = { "8vw", "12vw", "6vw" } local colors = { @@ -184,9 +183,9 @@ function Lv.load() Color.new(0.4, 0.9, 0.4, 1), Color.new(0.4, 0.4, 0.9, 1), } - + for j = 1, 3 do - Gui.new({ + FlexLove.new({ parent = container, width = option.value == enums.AlignItems.STRETCH and "auto" or widths[j], height = "4vh", @@ -197,23 +196,20 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) end - + xOffset = xOffset + containerWidth + 2 end end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() - -- Dark background Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end - --- Note: Mouse event handlers would be added here if needed for interactivity diff --git a/examples/02_grid_layout.lua b/examples/02_grid_layout.lua index f0b7087..8872da7 100644 --- a/examples/02_grid_layout.lua +++ b/examples/02_grid_layout.lua @@ -13,17 +13,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -38,7 +37,7 @@ function Lv.load() -- Section 1: 2x2 Grid with Gaps -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "30vw", @@ -48,7 +47,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local grid2x2 = Gui.new({ + local grid2x2 = FlexLove.new({ x = "2vw", y = "14vh", width = "30vw", @@ -72,7 +71,7 @@ function Lv.load() } for j = 1, 4 do - Gui.new({ + FlexLove.new({ parent = grid2x2, backgroundColor = colors2x2[j], text = "Cell " .. j, @@ -86,7 +85,7 @@ function Lv.load() -- Section 2: 3x3 Grid with Different Gap Sizes -- ======================================== - Gui.new({ + FlexLove.new({ x = "34vw", y = "10vh", width = "30vw", @@ -96,7 +95,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local grid3x3 = Gui.new({ + local grid3x3 = FlexLove.new({ x = "34vw", y = "14vh", width = "30vw", @@ -114,7 +113,7 @@ function Lv.load() -- Add 9 cells to 3x3 grid for j = 1, 9 do local hue = (j - 1) / 9 - Gui.new({ + FlexLove.new({ parent = grid3x3, backgroundColor = Color.new(0.3 + hue * 0.5, 0.5, 0.7 - hue * 0.4, 1), text = tostring(j), @@ -128,7 +127,7 @@ function Lv.load() -- Section 3: 4x2 Grid with AlignItems -- ======================================== - Gui.new({ + FlexLove.new({ x = "66vw", y = "10vh", width = "32vw", @@ -138,7 +137,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local grid4x2 = Gui.new({ + local grid4x2 = FlexLove.new({ x = "66vw", y = "14vh", width = "32vw", @@ -156,7 +155,7 @@ function Lv.load() -- Add 8 cells with varying content for j = 1, 8 do - Gui.new({ + FlexLove.new({ parent = grid4x2, backgroundColor = Color.new(0.6, 0.4 + j * 0.05, 0.7 - j * 0.05, 1), text = "Item " .. j, @@ -170,7 +169,7 @@ function Lv.load() -- Section 4: Grid with Responsive Units (vw/vh gaps) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "46vh", width = "96vw", @@ -180,7 +179,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local gridResponsive = Gui.new({ + local gridResponsive = FlexLove.new({ x = "2vw", y = "50vh", width = "96vw", @@ -198,7 +197,7 @@ function Lv.load() -- Add 10 cells with gradient colors for j = 1, 10 do local progress = (j - 1) / 9 - Gui.new({ + FlexLove.new({ parent = gridResponsive, backgroundColor = Color.new( 0.2 + progress * 0.6, @@ -216,14 +215,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/03_theming_system.lua b/examples/03_theming_system.lua index 8b9b3a2..b577ffe 100644 --- a/examples/03_theming_system.lua +++ b/examples/03_theming_system.lua @@ -13,20 +13,19 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Load the space theme - Gui.loadTheme("space", "../themes/space") + FlexLove.loadTheme("space", "../themes/space") -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -41,7 +40,7 @@ function Lv.load() -- Section 1: Theme Components -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "96vw", @@ -52,7 +51,7 @@ function Lv.load() }) -- Card component - Gui.new({ + FlexLove.new({ x = "2vw", y = "14vh", width = "30vw", @@ -66,7 +65,7 @@ function Lv.load() }) -- Panel component - Gui.new({ + FlexLove.new({ x = "34vw", y = "14vh", width = "30vw", @@ -80,7 +79,7 @@ function Lv.load() }) -- Panel red component - Gui.new({ + FlexLove.new({ x = "66vw", y = "14vh", width = "32vw", @@ -97,7 +96,7 @@ function Lv.load() -- Section 2: Button States -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "36vh", width = "96vw", @@ -108,7 +107,7 @@ function Lv.load() }) -- Normal button (hover to see hover state, click to see pressed state) - Gui.new({ + FlexLove.new({ x = "2vw", y = "40vh", width = "22vw", @@ -127,7 +126,7 @@ function Lv.load() }) -- Active button (simulating active state) - local activeButton = Gui.new({ + local activeButton = FlexLove.new({ x = "26vw", y = "40vh", width = "22vw", @@ -148,7 +147,7 @@ function Lv.load() }) -- Disabled button - Gui.new({ + FlexLove.new({ x = "50vw", y = "40vh", width = "22vw", @@ -168,7 +167,7 @@ function Lv.load() -- Button with callback feedback local clickCount = 0 - local counterButton = Gui.new({ + local counterButton = FlexLove.new({ x = "74vw", y = "40vh", width = "24vw", @@ -191,7 +190,7 @@ function Lv.load() -- Section 3: Theme Colors and Fonts -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "50vh", width = "96vw", @@ -202,7 +201,7 @@ function Lv.load() }) -- Container showing theme colors - local colorContainer = Gui.new({ + local colorContainer = FlexLove.new({ x = "2vw", y = "54vh", width = "96vw", @@ -217,7 +216,7 @@ function Lv.load() }) -- Primary color swatch - Gui.new({ + FlexLove.new({ parent = colorContainer, width = "20vw", height = "15vh", @@ -230,7 +229,7 @@ function Lv.load() }) -- Secondary color swatch - Gui.new({ + FlexLove.new({ parent = colorContainer, width = "20vw", height = "15vh", @@ -243,7 +242,7 @@ function Lv.load() }) -- Text color swatch - Gui.new({ + FlexLove.new({ parent = colorContainer, width = "20vw", height = "15vh", @@ -256,7 +255,7 @@ function Lv.load() }) -- Text dark color swatch - Gui.new({ + FlexLove.new({ parent = colorContainer, width = "20vw", height = "15vh", @@ -272,7 +271,7 @@ function Lv.load() -- Section 4: Font Family from Theme -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "76vh", width = "96vw", @@ -288,14 +287,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/04_responsive_units.lua b/examples/04_responsive_units.lua index cddc503..64586b9 100644 --- a/examples/04_responsive_units.lua +++ b/examples/04_responsive_units.lua @@ -14,17 +14,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -39,7 +38,7 @@ function Lv.load() -- Section 1: Viewport Width Units (vw) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "96vw", @@ -49,7 +48,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local vwContainer = Gui.new({ + local vwContainer = FlexLove.new({ x = "2vw", y = "14vh", width = "96vw", @@ -73,7 +72,7 @@ function Lv.load() } for i, width in ipairs(vwWidths) do - Gui.new({ + FlexLove.new({ parent = vwContainer, width = width, height = "8vh", @@ -89,7 +88,7 @@ function Lv.load() -- Section 2: Viewport Height Units (vh) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "28vh", width = "96vw", @@ -99,7 +98,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local vhContainer = Gui.new({ + local vhContainer = FlexLove.new({ x = "2vw", y = "32vh", width = "96vw", @@ -118,7 +117,7 @@ function Lv.load() for i, height in ipairs(vhHeights) do local hue = (i - 1) / 4 - Gui.new({ + FlexLove.new({ parent = vhContainer, width = "16vw", height = height, @@ -134,7 +133,7 @@ function Lv.load() -- Section 3: Percentage Units (%) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "64vh", width = "46vw", @@ -144,7 +143,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local percentContainer = Gui.new({ + local percentContainer = FlexLove.new({ x = "2vw", y = "68vh", width = "46vw", @@ -164,7 +163,7 @@ function Lv.load() for i, width in ipairs(percentWidths) do local progress = (i - 1) / 3 - Gui.new({ + FlexLove.new({ parent = percentContainer, width = width, height = "5vh", @@ -180,7 +179,7 @@ function Lv.load() -- Section 4: Pixel Units (px) - Fixed Size -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "64vh", width = "48vw", @@ -190,7 +189,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local pxContainer = Gui.new({ + local pxContainer = FlexLove.new({ x = "50vw", y = "68vh", width = "48vw", @@ -213,7 +212,7 @@ function Lv.load() } for i, size in ipairs(pxSizes) do - Gui.new({ + FlexLove.new({ parent = pxContainer, width = size.w, height = size.h, @@ -228,14 +227,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/05_animations.lua b/examples/05_animations.lua index 2b17350..30d5ef9 100644 --- a/examples/05_animations.lua +++ b/examples/05_animations.lua @@ -13,7 +13,6 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local Animation = FlexLove.Animation local enums = FlexLove.enums @@ -22,12 +21,12 @@ local enums = FlexLove.enums local fadeBox, scaleBox, easingBoxes function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -42,7 +41,7 @@ function Lv.load() -- Section 1: Fade Animation -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "46vw", @@ -52,7 +51,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - fadeBox = Gui.new({ + fadeBox = FlexLove.new({ x = "2vw", y = "14vh", width = "46vw", @@ -88,7 +87,7 @@ function Lv.load() -- Section 2: Scale Animation -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "10vh", width = "48vw", @@ -98,7 +97,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - scaleBox = Gui.new({ + scaleBox = FlexLove.new({ x = "50vw", y = "14vh", width = 400, @@ -142,7 +141,7 @@ function Lv.load() -- Section 3: Easing Functions Comparison -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "36vh", width = "96vw", @@ -152,7 +151,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local easingContainer = Gui.new({ + local easingContainer = FlexLove.new({ x = "2vw", y = "40vh", width = "96vw", @@ -185,7 +184,7 @@ function Lv.load() for i, easing in ipairs(easings) do local hue = (i - 1) / 8 - local box = Gui.new({ + local box = FlexLove.new({ parent = easingContainer, backgroundColor = Color.new(0.2 + hue * 0.6, 0.4 + math.sin(hue * 3.14) * 0.4, 0.8 - hue * 0.4, 1), text = easing.name, @@ -234,14 +233,14 @@ function Lv.update(dt) end end - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/06_event_system.lua b/examples/06_event_system.lua index dc80a4b..e3e4eab 100644 --- a/examples/06_event_system.lua +++ b/examples/06_event_system.lua @@ -14,7 +14,6 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums @@ -30,12 +29,12 @@ local function addLogEntry(text) end function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -50,7 +49,7 @@ function Lv.load() -- Section 1: Click Events -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "46vw", @@ -60,7 +59,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local clickBox = Gui.new({ + local clickBox = FlexLove.new({ x = "2vw", y = "14vh", width = "46vw", @@ -92,7 +91,7 @@ function Lv.load() -- Section 2: Keyboard Modifiers -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "10vh", width = "48vw", @@ -102,7 +101,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local modifierBox = Gui.new({ + local modifierBox = FlexLove.new({ x = "50vw", y = "14vh", width = "48vw", @@ -131,7 +130,7 @@ function Lv.load() -- Section 3: Double-Click Detection -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "36vh", width = "46vw", @@ -141,7 +140,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local doubleClickBox = Gui.new({ + local doubleClickBox = FlexLove.new({ x = "2vw", y = "40vh", width = "46vw", @@ -175,7 +174,7 @@ function Lv.load() -- Section 4: Event Log Display -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "36vh", width = "48vw", @@ -186,7 +185,7 @@ function Lv.load() }) -- Event log container - local logContainer = Gui.new({ + local logContainer = FlexLove.new({ x = "50vw", y = "40vh", width = "48vw", @@ -201,7 +200,7 @@ function Lv.load() -- Section 5: Interactive Buttons -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "57vh", width = "46vw", @@ -211,7 +210,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local buttonContainer = Gui.new({ + local buttonContainer = FlexLove.new({ x = "2vw", y = "61vh", width = "46vw", @@ -228,7 +227,7 @@ function Lv.load() }) -- Button 1: Press/Release events - Gui.new({ + FlexLove.new({ parent = buttonContainer, height = "8vh", backgroundColor = Color.new(0.4, 0.5, 0.8, 1), @@ -250,7 +249,7 @@ function Lv.load() -- Button 2: Click counter local clickCounter = 0 - Gui.new({ + FlexLove.new({ parent = buttonContainer, height = "8vh", backgroundColor = Color.new(0.8, 0.5, 0.4, 1), @@ -269,7 +268,7 @@ function Lv.load() }) -- Button 3: Clear log - Gui.new({ + FlexLove.new({ parent = buttonContainer, height = "8vh", backgroundColor = Color.new(0.6, 0.4, 0.6, 1), @@ -294,12 +293,12 @@ function Lv.update(dt) doubleClickBox._resetTime = nil end - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() -- Draw event log Lv.graphics.setColor(0.8, 0.9, 1, 1) @@ -315,5 +314,5 @@ function Lv.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/07_text_rendering.lua b/examples/07_text_rendering.lua index d1f1b1c..d95ba76 100644 --- a/examples/07_text_rendering.lua +++ b/examples/07_text_rendering.lua @@ -13,17 +13,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -38,7 +37,7 @@ function Lv.load() -- Section 1: Text Alignment -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "96vw", @@ -57,7 +56,7 @@ function Lv.load() local yOffset = 14 for _, align in ipairs(alignments) do - Gui.new({ + FlexLove.new({ x = "2vw", y = yOffset .. "vh", width = "30vw", @@ -78,7 +77,7 @@ function Lv.load() -- Section 2: Text Size Presets -- ======================================== - Gui.new({ + FlexLove.new({ x = "34vw", y = "10vh", width = "64vw", @@ -100,7 +99,7 @@ function Lv.load() { name = "4XL", value = "4xl" }, } - local sizeContainer = Gui.new({ + local sizeContainer = FlexLove.new({ x = "34vw", y = "14vh", width = "64vw", @@ -118,7 +117,7 @@ function Lv.load() for i, size in ipairs(textSizes) do local hue = (i - 1) / 8 - Gui.new({ + FlexLove.new({ parent = sizeContainer, height = "7vh", backgroundColor = Color.new(0.2 + hue * 0.3, 0.3 + hue * 0.2, 0.5 - hue * 0.2, 1), @@ -134,7 +133,7 @@ function Lv.load() -- Section 3: Custom Font Sizes (vh units) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "41vh", width = "30vw", @@ -146,7 +145,7 @@ function Lv.load() local customSizes = { "1vh", "2vh", "3vh", "4vh", "5vh" } - local customContainer = Gui.new({ + local customContainer = FlexLove.new({ x = "2vw", y = "45vh", width = "30vw", @@ -162,7 +161,7 @@ function Lv.load() }) for i, size in ipairs(customSizes) do - Gui.new({ + FlexLove.new({ parent = customContainer, backgroundColor = Color.new(0.3, 0.4 + i * 0.08, 0.6 - i * 0.08, 1), text = size .. " text", @@ -176,14 +175,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/08_absolute_relative_positioning.lua b/examples/08_absolute_relative_positioning.lua index a1c0e43..2104af2 100644 --- a/examples/08_absolute_relative_positioning.lua +++ b/examples/08_absolute_relative_positioning.lua @@ -13,17 +13,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -38,7 +37,7 @@ function Lv.load() -- Section 1: Absolute Positioning -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "46vw", @@ -48,7 +47,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local absoluteContainer = Gui.new({ + local absoluteContainer = FlexLove.new({ x = "2vw", y = "14vh", width = "46vw", @@ -60,7 +59,7 @@ function Lv.load() }) -- Absolute positioned children - Gui.new({ + FlexLove.new({ parent = absoluteContainer, x = 20, y = 20, @@ -75,7 +74,7 @@ function Lv.load() cornerRadius = 5, }) - Gui.new({ + FlexLove.new({ parent = absoluteContainer, x = 200, y = 50, @@ -90,7 +89,7 @@ function Lv.load() cornerRadius = 5, }) - Gui.new({ + FlexLove.new({ parent = absoluteContainer, x = 100, y = 150, @@ -105,7 +104,7 @@ function Lv.load() cornerRadius = 5, }) - Gui.new({ + FlexLove.new({ parent = absoluteContainer, x = 280, y = 180, @@ -124,7 +123,7 @@ function Lv.load() -- Section 2: Relative Positioning -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "10vh", width = "48vw", @@ -134,7 +133,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local relativeContainer = Gui.new({ + local relativeContainer = FlexLove.new({ x = "50vw", y = "14vh", width = "48vw", @@ -155,7 +154,7 @@ function Lv.load() } for i = 1, 4 do - Gui.new({ + FlexLove.new({ parent = relativeContainer, width = "45%", height = "8vh", @@ -174,7 +173,7 @@ function Lv.load() -- Section 3: Comparison with Overlapping -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "56vh", width = "96vw", @@ -184,7 +183,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local overlapContainer = Gui.new({ + local overlapContainer = FlexLove.new({ x = "2vw", y = "60vh", width = "96vw", @@ -204,7 +203,7 @@ function Lv.load() } for i = 1, 4 do - Gui.new({ + FlexLove.new({ parent = overlapContainer, x = 50 + (i - 1) * 60, y = 30 + (i - 1) * 40, @@ -223,14 +222,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/09_styling_visual_effects.lua b/examples/09_styling_visual_effects.lua index d78b441..70a5f0d 100644 --- a/examples/09_styling_visual_effects.lua +++ b/examples/09_styling_visual_effects.lua @@ -14,17 +14,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -39,7 +38,7 @@ function Lv.load() -- Section 1: Corner Radius -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "9vh", width = "46vw", @@ -50,7 +49,7 @@ function Lv.load() }) -- Uniform corner radius - Gui.new({ + FlexLove.new({ x = "2vw", y = "13vh", width = "14vw", @@ -63,7 +62,7 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) - Gui.new({ + FlexLove.new({ x = "17vw", y = "13vh", width = "14vw", @@ -77,7 +76,7 @@ function Lv.load() }) -- Individual corner radius - Gui.new({ + FlexLove.new({ x = "32vw", y = "13vh", width = "16vw", @@ -99,7 +98,7 @@ function Lv.load() -- Section 2: Borders -- ======================================== - Gui.new({ + FlexLove.new({ x = "50vw", y = "9vh", width = "48vw", @@ -110,7 +109,7 @@ function Lv.load() }) -- All borders - Gui.new({ + FlexLove.new({ x = "50vw", y = "13vh", width = "14vw", @@ -125,7 +124,7 @@ function Lv.load() }) -- Top and bottom borders - Gui.new({ + FlexLove.new({ x = "65vw", y = "13vh", width = "14vw", @@ -140,7 +139,7 @@ function Lv.load() }) -- Left border only - Gui.new({ + FlexLove.new({ x = "80vw", y = "13vh", width = "16vw", @@ -158,7 +157,7 @@ function Lv.load() -- Section 3: Opacity -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "27vh", width = "96vw", @@ -171,7 +170,7 @@ function Lv.load() local opacityLevels = { 1.0, 0.75, 0.5, 0.25 } for i, opacity in ipairs(opacityLevels) do - Gui.new({ + FlexLove.new({ x = (2 + (i - 1) * 24) .. "vw", y = "31vh", width = "22vw", @@ -190,7 +189,7 @@ function Lv.load() -- Section 4: Background Colors -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "45vh", width = "96vw", @@ -203,7 +202,7 @@ function Lv.load() -- Gradient-like colors for i = 1, 8 do local hue = (i - 1) / 7 - Gui.new({ + FlexLove.new({ x = (2 + (i - 1) * 12) .. "vw", y = "49vh", width = "11vw", @@ -226,7 +225,7 @@ function Lv.load() -- Section 5: Blur Effects (if supported) -- ======================================== - Gui.new({ + FlexLove.new({ x = "2vw", y = "69vh", width = "96vw", @@ -237,7 +236,7 @@ function Lv.load() }) -- Content blur example - Gui.new({ + FlexLove.new({ x = "2vw", y = "73vh", width = "46vw", @@ -252,7 +251,7 @@ function Lv.load() }) -- Backdrop blur example - Gui.new({ + FlexLove.new({ x = "50vw", y = "73vh", width = "46vw", @@ -268,14 +267,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/10_padding_margins.lua b/examples/10_padding_margins.lua index 1630823..ddbde77 100644 --- a/examples/10_padding_margins.lua +++ b/examples/10_padding_margins.lua @@ -14,17 +14,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -36,7 +35,7 @@ function Lv.load() }) -- Section 1: Padding Examples - Gui.new({ + FlexLove.new({ x = "2vw", y = "9vh", width = "46vw", @@ -47,7 +46,7 @@ function Lv.load() }) -- Uniform padding - local container1 = Gui.new({ + local container1 = FlexLove.new({ x = "2vw", y = "13vh", width = "22vw", @@ -56,7 +55,7 @@ function Lv.load() padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) - Gui.new({ + FlexLove.new({ parent = container1, width = "auto", height = "auto", @@ -68,7 +67,7 @@ function Lv.load() }) -- Individual padding sides - local container2 = Gui.new({ + local container2 = FlexLove.new({ x = "26vw", y = "13vh", width = "22vw", @@ -77,7 +76,7 @@ function Lv.load() padding = { top = 30, right = 10, bottom = 30, left = 10 }, }) - Gui.new({ + FlexLove.new({ parent = container2, width = "auto", height = "auto", @@ -89,7 +88,7 @@ function Lv.load() }) -- Section 2: Margin Examples - Gui.new({ + FlexLove.new({ x = "50vw", y = "9vh", width = "48vw", @@ -100,7 +99,7 @@ function Lv.load() }) -- Container to show margins - local marginContainer = Gui.new({ + local marginContainer = FlexLove.new({ x = "50vw", y = "13vh", width = "46vw", @@ -111,7 +110,7 @@ function Lv.load() }) -- Elements with different margins - Gui.new({ + FlexLove.new({ parent = marginContainer, width = "40vw", height = "6vh", @@ -123,7 +122,7 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) - Gui.new({ + FlexLove.new({ parent = marginContainer, width = "40vw", height = "6vh", @@ -135,7 +134,7 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) - Gui.new({ + FlexLove.new({ parent = marginContainer, width = "40vw", height = "6vh", @@ -148,7 +147,7 @@ function Lv.load() }) -- Section 3: Combined Padding and Margins - Gui.new({ + FlexLove.new({ x = "2vw", y = "33vh", width = "46vw", @@ -158,7 +157,7 @@ function Lv.load() textColor = Color.new(0.9, 0.9, 0.9, 1), }) - local combinedContainer = Gui.new({ + local combinedContainer = FlexLove.new({ x = "2vw", y = "37vh", width = "46vw", @@ -170,7 +169,7 @@ function Lv.load() }) for i = 1, 3 do - local box = Gui.new({ + local box = FlexLove.new({ parent = combinedContainer, width = "auto", height = "14vh", @@ -179,7 +178,7 @@ function Lv.load() padding = { top = 15, right = 15, bottom = 15, left = 15 }, }) - Gui.new({ + FlexLove.new({ parent = box, width = "auto", height = "auto", @@ -193,14 +192,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/11_input_controls.lua b/examples/11_input_controls.lua index 7269fec..e6d2ec0 100644 --- a/examples/11_input_controls.lua +++ b/examples/11_input_controls.lua @@ -12,17 +12,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -34,7 +33,7 @@ function Lv.load() }) -- Note: Input controls may require additional setup in FlexLove - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "96vw", @@ -47,7 +46,7 @@ function Lv.load() -- Interactive buttons local counter = 0 - local counterDisplay = Gui.new({ + local counterDisplay = FlexLove.new({ x = "35vw", y = "20vh", width = "30vw", @@ -61,7 +60,7 @@ function Lv.load() }) -- Increment button - Gui.new({ + FlexLove.new({ x = "20vw", y = "35vh", width = "20vw", @@ -79,7 +78,7 @@ function Lv.load() }) -- Decrement button - Gui.new({ + FlexLove.new({ x = "60vw", y = "35vh", width = "20vw", @@ -97,7 +96,7 @@ function Lv.load() }) -- Reset button - Gui.new({ + FlexLove.new({ x = "40vw", y = "46vh", width = "20vw", @@ -115,7 +114,7 @@ function Lv.load() }) -- Keyboard input info - Gui.new({ + FlexLove.new({ x = "2vw", y = "60vh", width = "96vw", @@ -136,14 +135,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/12_z_index_layering.lua b/examples/12_z_index_layering.lua index 468bb00..b283114 100644 --- a/examples/12_z_index_layering.lua +++ b/examples/12_z_index_layering.lua @@ -12,17 +12,16 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 } }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -34,7 +33,7 @@ function Lv.load() }) -- Description - Gui.new({ + FlexLove.new({ x = "2vw", y = "10vh", width = "96vw", @@ -46,7 +45,7 @@ function Lv.load() }) -- Section 1: Overlapping boxes with different z-index - Gui.new({ + FlexLove.new({ x = "2vw", y = "15vh", width = "46vw", @@ -57,7 +56,7 @@ function Lv.load() }) -- Box 1 (z-index: 1) - Gui.new({ + FlexLove.new({ x = "5vw", y = "20vh", width = "20vw", @@ -72,7 +71,7 @@ function Lv.load() }) -- Box 2 (z-index: 2) - overlaps Box 1 - Gui.new({ + FlexLove.new({ x = "12vw", y = "25vh", width = "20vw", @@ -87,7 +86,7 @@ function Lv.load() }) -- Box 3 (z-index: 3) - overlaps Box 1 and 2 - Gui.new({ + FlexLove.new({ x = "19vw", y = "30vh", width = "20vw", @@ -102,7 +101,7 @@ function Lv.load() }) -- Section 2: Cards with different layers - Gui.new({ + FlexLove.new({ x = "50vw", y = "15vh", width = "48vw", @@ -114,7 +113,7 @@ function Lv.load() -- Create a stack of cards for i = 1, 5 do - Gui.new({ + FlexLove.new({ x = (52 + i * 2) .. "vw", y = (18 + i * 3) .. "vh", width = "22vw", @@ -132,7 +131,7 @@ function Lv.load() end -- Section 3: Interactive z-index demo - Gui.new({ + FlexLove.new({ x = "2vw", y = "53vh", width = "96vw", @@ -146,7 +145,7 @@ function Lv.load() -- Create interactive boxes for i = 1, 4 do - local box = Gui.new({ + local box = FlexLove.new({ x = (5 + (i - 1) * 22) .. "vw", y = "58vh", width = "20vw", @@ -174,7 +173,7 @@ function Lv.load() end -- Info text - Gui.new({ + FlexLove.new({ x = "2vw", y = "82vh", width = "96vw", @@ -191,14 +190,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/13_comprehensive_demo.lua b/examples/13_comprehensive_demo.lua index a68e516..806c2ea 100644 --- a/examples/13_comprehensive_demo.lua +++ b/examples/13_comprehensive_demo.lua @@ -14,19 +14,18 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local Animation = FlexLove.Animation local enums = FlexLove.enums function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 }, theme = "space" }) -- Header - local header = Gui.new({ + local header = FlexLove.new({ x = 0, y = 0, width = "100vw", @@ -42,7 +41,7 @@ function Lv.load() }) -- Logo/Title - Gui.new({ + FlexLove.new({ parent = header, width = "auto", height = "auto", @@ -52,7 +51,7 @@ function Lv.load() }) -- Header buttons - local headerButtons = Gui.new({ + local headerButtons = FlexLove.new({ parent = header, width = "auto", height = "auto", @@ -63,7 +62,7 @@ function Lv.load() local buttonNames = { "Home", "Features", "About" } for _, name in ipairs(buttonNames) do - Gui.new({ + FlexLove.new({ parent = headerButtons, width = "8vw", height = "6vh", @@ -78,7 +77,7 @@ function Lv.load() end -- Main content area - local mainContent = Gui.new({ + local mainContent = FlexLove.new({ x = 0, y = "12vh", width = "100vw", @@ -89,7 +88,7 @@ function Lv.load() }) -- Sidebar - local sidebar = Gui.new({ + local sidebar = FlexLove.new({ parent = mainContent, width = "20vw", height = "88vh", @@ -112,7 +111,7 @@ function Lv.load() } for _, item in ipairs(menuItems) do - local menuButton = Gui.new({ + local menuButton = FlexLove.new({ parent = sidebar, width = "auto", height = "7vh", @@ -124,7 +123,7 @@ function Lv.load() cornerRadius = 5, }) - Gui.new({ + FlexLove.new({ parent = menuButton, width = "auto", height = "auto", @@ -135,7 +134,7 @@ function Lv.load() end -- Content panel - local contentPanel = Gui.new({ + local contentPanel = FlexLove.new({ parent = mainContent, width = "80vw", height = "88vh", @@ -146,7 +145,7 @@ function Lv.load() }) -- Welcome section - Gui.new({ + FlexLove.new({ parent = contentPanel, width = "auto", height = "auto", @@ -156,7 +155,7 @@ function Lv.load() }) -- Stats grid - local statsGrid = Gui.new({ + local statsGrid = FlexLove.new({ parent = contentPanel, width = "auto", height = "20vh", @@ -174,7 +173,7 @@ function Lv.load() } for _, stat in ipairs(stats) do - local statCard = Gui.new({ + local statCard = FlexLove.new({ parent = statsGrid, positioning = enums.Positioning.FLEX, flexDirection = enums.FlexDirection.VERTICAL, @@ -185,7 +184,7 @@ function Lv.load() padding = { top = 15, right = 15, bottom = 15, left = 15 }, }) - Gui.new({ + FlexLove.new({ parent = statCard, width = "auto", height = "auto", @@ -195,7 +194,7 @@ function Lv.load() textAlign = enums.TextAlign.CENTER, }) - Gui.new({ + FlexLove.new({ parent = statCard, width = "auto", height = "auto", @@ -207,7 +206,7 @@ function Lv.load() end -- Feature cards - local cardsContainer = Gui.new({ + local cardsContainer = FlexLove.new({ parent = contentPanel, width = "auto", height = "auto", @@ -228,7 +227,7 @@ function Lv.load() } for i, feature in ipairs(features) do - local card = Gui.new({ + local card = FlexLove.new({ parent = cardsContainer, positioning = enums.Positioning.FLEX, justifyContent = enums.JustifyContent.CENTER, @@ -238,7 +237,7 @@ function Lv.load() padding = { top = 20, right = 20, bottom = 20, left = 20 }, }) - Gui.new({ + FlexLove.new({ parent = card, width = "auto", height = "auto", @@ -261,14 +260,14 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/14_drag_slider.lua b/examples/14_drag_slider.lua index 2c15720..570306d 100644 --- a/examples/14_drag_slider.lua +++ b/examples/14_drag_slider.lua @@ -16,7 +16,6 @@ local Lv = love local FlexLove = require("../FlexLove") -local Gui = FlexLove.Gui local Color = FlexLove.Color local enums = FlexLove.enums @@ -45,7 +44,7 @@ local temperatureHandle ---@return table -- Returns { bg, handle, valueText } local function createSlider(x, y, width, label, min, max, initialValue, onValueChange) -- Container for the slider - local container = Gui.new({ + local container = FlexLove.new({ x = x, y = y, width = width, @@ -56,7 +55,7 @@ local function createSlider(x, y, width, label, min, max, initialValue, onValueC }) -- Label - Gui.new({ + FlexLove.new({ parent = container, height = "3vh", text = label, @@ -65,7 +64,7 @@ local function createSlider(x, y, width, label, min, max, initialValue, onValueC }) -- Slider track background - local sliderBg = Gui.new({ + local sliderBg = FlexLove.new({ parent = container, height = "4vh", backgroundColor = Color.new(0.2, 0.2, 0.25, 1), @@ -75,7 +74,7 @@ local function createSlider(x, y, width, label, min, max, initialValue, onValueC -- Slider handle local normalized = (initialValue - min) / (max - min) - local handle = Gui.new({ + local handle = FlexLove.new({ parent = sliderBg, x = (normalized * 95) .. "%", y = "50%", @@ -89,7 +88,7 @@ local function createSlider(x, y, width, label, min, max, initialValue, onValueC }) -- Value display - local valueText = Gui.new({ + local valueText = FlexLove.new({ parent = container, height = "3vh", text = string.format("%.2f", initialValue), @@ -144,12 +143,12 @@ local function createSlider(x, y, width, label, min, max, initialValue, onValueC end function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 }, }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -161,7 +160,7 @@ function Lv.load() }) -- Subtitle - Gui.new({ + FlexLove.new({ x = "2vw", y = "9vh", width = "96vw", @@ -194,7 +193,7 @@ function Lv.load() temperatureHandle = temperatureSlider.handle -- Visual feedback section - Gui.new({ + FlexLove.new({ x = "10vw", y = "70vh", width = "80vw", @@ -205,7 +204,7 @@ function Lv.load() }) -- Volume visualization - Gui.new({ + FlexLove.new({ x = "10vw", y = "75vh", width = "25vw", @@ -217,7 +216,7 @@ function Lv.load() }) -- Brightness visualization - Gui.new({ + FlexLove.new({ x = "37.5vw", y = "75vh", width = "25vw", @@ -229,7 +228,7 @@ function Lv.load() }) -- Temperature visualization - Gui.new({ + FlexLove.new({ x = "65vw", y = "75vh", width = "25vw", @@ -242,12 +241,12 @@ function Lv.load() end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() Lv.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() -- Draw volume visualization (speaker icon with bars) local volumeX = Lv.graphics.getWidth() * 0.10 + 20 @@ -302,5 +301,5 @@ function Lv.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end diff --git a/examples/15_scrollable_elements.lua b/examples/15_scrollable_elements.lua index 31b0510..33e9315 100644 --- a/examples/15_scrollable_elements.lua +++ b/examples/15_scrollable_elements.lua @@ -1,19 +1,18 @@ -- Example 15: Scrollable Elements -- Demonstrates scrollable containers with overflow detection and visual scrollbars -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui +local FlexLove = require("../FlexLove") local Color = FlexLove.Color local enums = FlexLove.enums local Lv = love function Lv.load() - Gui.init({ + FlexLove.init({ baseScale = { width = 1920, height = 1080 }, }) -- Title - Gui.new({ + FlexLove.new({ x = "2vw", y = "2vh", width = "96vw", @@ -25,7 +24,7 @@ function Lv.load() }) -- Example 1: Vertical scroll with auto scrollbars - local verticalScroll = Gui.new({ + local verticalScroll = FlexLove.new({ x = "5vw", y = "12vh", width = "25vw", @@ -41,7 +40,7 @@ function Lv.load() -- Add many items to create overflow for i = 1, 20 do - Gui.new({ + FlexLove.new({ parent = verticalScroll, height = "5vh", backgroundColor = Color.new(0.3 + (i % 3) * 0.1, 0.4, 0.6, 1), @@ -53,7 +52,7 @@ function Lv.load() end -- Example 2: Custom styled scrollbar - local customScroll = Gui.new({ + local customScroll = FlexLove.new({ x = "35vw", y = "12vh", width = "60vw", @@ -73,7 +72,7 @@ function Lv.load() -- Add content for i = 1, 25 do - Gui.new({ + FlexLove.new({ parent = customScroll, height = "6vh", backgroundColor = Color.new(0.2, 0.25, 0.3, 1), @@ -85,7 +84,7 @@ function Lv.load() end -- Instructions - Gui.new({ + FlexLove.new({ x = "5vw", y = "52vh", width = "90vw", @@ -110,18 +109,18 @@ Scrollbar colors change on hover and when dragging!]], end function Lv.update(dt) - Gui.update(dt) + FlexLove.update(dt) end function Lv.draw() love.graphics.clear(0.05, 0.05, 0.08, 1) - Gui.draw() + FlexLove.draw() end function Lv.resize(w, h) - Gui.resize(w, h) + FlexLove.resize(w, h) end function Lv.wheelmoved(x, y) - Gui.wheelmoved(x, y) + FlexLove.wheelmoved(x, y) end diff --git a/examples/16-full-imput-demo.lua b/examples/16-full-imput-demo.lua index c56d15a..1511de0 100644 --- a/examples/16-full-imput-demo.lua +++ b/examples/16-full-imput-demo.lua @@ -4,7 +4,7 @@ Simple input field demo - multiple fields to test all features Uses retained mode - elements are created once and reused --]] -local FlexLove = require("libs.FlexLove") +local FlexLove = require("../FlexLove") local Element = FlexLove.Element local Color = FlexLove.Color diff --git a/testing/__tests__/01_absolute_positioning_basic_tests.lua b/testing/__tests__/01_absolute_positioning_basic_tests.lua deleted file mode 100644 index 277b076..0000000 --- a/testing/__tests__/01_absolute_positioning_basic_tests.lua +++ /dev/null @@ -1,1124 +0,0 @@ -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - -local Positioning = enums.Positioning - --- Create test cases for basic absolute positioning -TestAbsolutePositioningBasic = {} - -function TestAbsolutePositioningBasic:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestAbsolutePositioningBasic:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Basic element creation with absolute positioning -function TestAbsolutePositioningBasic:testCreateElementWithAbsolutePositioning() - local elem = Gui.new({ - x = 100, - y = 200, - width = 300, - height = 150, - positioning = Positioning.ABSOLUTE, - }) - - -- Verify element was created with correct properties - luaunit.assertEquals(elem.x, 100) - luaunit.assertEquals(elem.y, 200) - luaunit.assertEquals(elem.width, 300) - luaunit.assertEquals(elem.height, 150) - luaunit.assertEquals(elem.positioning, Positioning.ABSOLUTE) - - -- Verify element was added to topElements - luaunit.assertEquals(#Gui.topElements, 1) - luaunit.assertEquals(Gui.topElements[1], elem) -end - --- Test 2: Default absolute positioning when no positioning specified -function TestAbsolutePositioningBasic:testDefaultAbsolutePositioning() - local elem = Gui.new({ - x = 50, - y = 75, - width = 200, - height = 100, - }) - - -- Default should be relative positioning - luaunit.assertEquals(elem.positioning, Positioning.RELATIVE) - luaunit.assertEquals(elem.x, 50) - luaunit.assertEquals(elem.y, 75) -end - --- Test 3: Z-index handling for absolute positioned elements -function TestAbsolutePositioningBasic:testZIndexHandling() - local elem1 = Gui.new({ - x = 0, - y = 0, - width = 100, - height = 100, - z = 1, - positioning = Positioning.ABSOLUTE, - }) - - local elem2 = Gui.new({ - x = 50, - y = 50, - width = 100, - height = 100, - z = 5, - positioning = Positioning.ABSOLUTE, - }) - - local elem3 = Gui.new({ - x = 25, - y = 25, - width = 100, - height = 100, - z = 3, - positioning = Positioning.ABSOLUTE, - }) - - luaunit.assertEquals(elem1.z, 1) - luaunit.assertEquals(elem2.z, 5) - luaunit.assertEquals(elem3.z, 3) - - -- All should be in topElements - luaunit.assertEquals(#Gui.topElements, 3) -end - --- Test 4: Default z-index is 0 -function TestAbsolutePositioningBasic:testDefaultZIndex() - local elem = Gui.new({ - x = 10, - y = 20, - width = 50, - height = 50, - positioning = Positioning.ABSOLUTE, - }) - - luaunit.assertEquals(elem.z, 0) -end - --- Test 5: Coordinate independence from other elements -function TestAbsolutePositioningBasic:testCoordinateIndependence() - local elem1 = Gui.new({ - x = 100, - y = 100, - width = 50, - height = 50, - positioning = Positioning.ABSOLUTE, - }) - - local elem2 = Gui.new({ - x = 200, - y = 200, - width = 50, - height = 50, - positioning = Positioning.ABSOLUTE, - }) - - -- Elements should maintain their own coordinates - luaunit.assertEquals(elem1.x, 100) - luaunit.assertEquals(elem1.y, 100) - luaunit.assertEquals(elem2.x, 200) - luaunit.assertEquals(elem2.y, 200) - - -- Modifying one shouldn't affect the other - elem1.x = 150 - luaunit.assertEquals(elem1.x, 150) - luaunit.assertEquals(elem2.x, 200) -- Should remain unchanged -end - --- Test 6: Absolute positioned element with parent but should maintain own coordinates -function TestAbsolutePositioningBasic:testAbsoluteWithParentIndependentCoordinates() - local parent = Gui.new({ - x = 50, - y = 50, - width = 200, - height = 200, - positioning = Positioning.ABSOLUTE, - }) - - local child = Gui.new({ - parent = parent, - x = 25, - y = 25, - width = 50, - height = 50, - positioning = Positioning.ABSOLUTE, - }) - - -- Child should maintain its absolute coordinates (CSS absolute behavior) - luaunit.assertEquals(child.x, 25) - luaunit.assertEquals(child.y, 25) - luaunit.assertEquals(child.positioning, Positioning.ABSOLUTE) - - -- Parent should have the child - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child) -end - --- Test 7: Multiple absolute elements should not interfere -function TestAbsolutePositioningBasic:testMultipleAbsoluteElementsNonInterference() - local elements = {} - - for i = 1, 5 do - elements[i] = Gui.new({ - x = i * 10, - y = i * 20, - width = 30, - height = 40, - z = i, - positioning = Positioning.ABSOLUTE, - }) - end - - -- Verify all elements maintain their properties - for i = 1, 5 do - luaunit.assertEquals(elements[i].x, i * 10) - luaunit.assertEquals(elements[i].y, i * 20) - luaunit.assertEquals(elements[i].width, 30) - luaunit.assertEquals(elements[i].height, 40) - luaunit.assertEquals(elements[i].z, i) - end - - luaunit.assertEquals(#Gui.topElements, 5) -end - --- Test 8: Negative coordinates should work -function TestAbsolutePositioningBasic:testNegativeCoordinates() - local elem = Gui.new({ - x = -50, - y = -100, - width = 200, - height = 150, - positioning = Positioning.ABSOLUTE, - }) - - luaunit.assertEquals(elem.x, -50) - luaunit.assertEquals(elem.y, -100) -end - --- Test 9: Zero coordinates should work -function TestAbsolutePositioningBasic:testZeroCoordinates() - local elem = Gui.new({ - x = 0, - y = 0, - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - }) - - luaunit.assertEquals(elem.x, 0) - luaunit.assertEquals(elem.y, 0) -end - --- Test 10: Default coordinates when not specified -function TestAbsolutePositioningBasic:testDefaultCoordinates() - local elem = Gui.new({ - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - }) - - -- Default coordinates should be 0,0 - luaunit.assertEquals(elem.x, 0) - luaunit.assertEquals(elem.y, 0) -end - --- Test 11: Element bounds calculation -function TestAbsolutePositioningBasic:testElementBounds() - local elem = Gui.new({ - x = 100, - y = 200, - width = 300, - height = 400, - positioning = Positioning.ABSOLUTE, - }) - - local bounds = elem:getBounds() - luaunit.assertEquals(bounds.x, 100) - luaunit.assertEquals(bounds.y, 200) - luaunit.assertEquals(bounds.width, 300) - luaunit.assertEquals(bounds.height, 400) -end - --- Test 12: Parent-child relationship with absolute positioning -function TestAbsolutePositioningBasic:testParentChildRelationshipAbsolute() - local parent = Gui.new({ - x = 100, - y = 100, - width = 300, - height = 300, - positioning = Positioning.ABSOLUTE, - }) - - local child = Gui.new({ - parent = parent, - x = 50, - y = 75, - width = 100, - height = 150, - positioning = Positioning.ABSOLUTE, - }) - - -- Verify parent-child relationship - luaunit.assertEquals(child.parent, parent) - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child) - - -- Child should maintain absolute coordinates - luaunit.assertEquals(child.x, 50) - luaunit.assertEquals(child.y, 75) -end - --- Test 13: Absolute positioned child should not affect parent auto-sizing -function TestAbsolutePositioningBasic:testAbsoluteChildNoParentAutoSizeAffect() - local parent = Gui.new({ - x = 0, - y = 0, - positioning = Positioning.ABSOLUTE, - }) - - local originalParentWidth = parent.width - local originalParentHeight = parent.height - - local child = Gui.new({ - parent = parent, - x = 1000, -- Far outside parent - y = 1000, - width = 500, - height = 500, - positioning = Positioning.ABSOLUTE, - }) - - -- Parent size should not be affected by absolute positioned child - -- (In CSS, absolute children don't affect parent size) - luaunit.assertEquals(parent.width, originalParentWidth) - luaunit.assertEquals(parent.height, originalParentHeight) -end - --- Test 14: Verify absolute elements don't participate in flex layout -function TestAbsolutePositioningBasic:testAbsoluteNoFlexParticipation() - local flexParent = Gui.new({ - x = 0, - y = 0, - width = 400, - height = 200, - positioning = Positioning.FLEX, - flexDirection = enums.FlexDirection.HORIZONTAL, - }) - - local flexChild = Gui.new({ - parent = flexParent, - width = 100, - height = 50, - positioning = Positioning.FLEX, - }) - - local absoluteChild = Gui.new({ - parent = flexParent, - x = 300, - y = 150, - width = 80, - height = 40, - positioning = Positioning.ABSOLUTE, - }) - - -- Absolute child should maintain its coordinates - luaunit.assertEquals(absoluteChild.x, 300) - luaunit.assertEquals(absoluteChild.y, 150) - luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) - - -- Both children should be in parent - luaunit.assertEquals(#flexParent.children, 2) -end - --- Test 15: Large coordinate values -function TestAbsolutePositioningBasic:testLargeCoordinateValues() - local elem = Gui.new({ - x = 9999, - y = 8888, - width = 100, - height = 100, - z = 1000, - positioning = Positioning.ABSOLUTE, - }) - - luaunit.assertEquals(elem.x, 9999) - luaunit.assertEquals(elem.y, 8888) - luaunit.assertEquals(elem.z, 1000) -end - --- =========================================================================== --- COMPLEX TREE/BRANCHING STRUCTURE TESTS --- =========================================================================== - --- Test 16: Complex nested absolute tree structure (4 levels deep) -function TestAbsolutePositioningBasic:testComplexNestedAbsoluteTree() - -- Create a 4-level deep tree structure following CSS absolute positioning - -- Root (absolute) -> Child1 (absolute) -> Grandchild1 (absolute) -> GreatGrandchild1 (absolute) - -- -> Child2 (absolute) -> Grandchild2 (absolute) -> GreatGrandchild2 (absolute) - - local root = Gui.new({ - id = "root", - x = 100, - y = 100, - width = 800, - height = 600, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Left branch - local child1 = Gui.new({ - parent = root, - id = "child1", - x = 50, - y = 50, - width = 300, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - local grandchild1 = Gui.new({ - parent = child1, - id = "grandchild1", - x = 25, - y = 25, - width = 150, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local greatGrandchild1 = Gui.new({ - parent = grandchild1, - id = "greatGrandchild1", - x = 10, - y = 10, - width = 50, - height = 75, - positioning = Positioning.ABSOLUTE, - z = 4, - }) - - -- Right branch - local child2 = Gui.new({ - parent = root, - id = "child2", - x = 450, - y = 50, - width = 300, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - local grandchild2 = Gui.new({ - parent = child2, - id = "grandchild2", - x = 125, - y = 175, - width = 150, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local greatGrandchild2 = Gui.new({ - parent = grandchild2, - id = "greatGrandchild2", - x = 90, - y = 160, - width = 50, - height = 75, - positioning = Positioning.ABSOLUTE, - z = 4, - }) - - -- Verify tree structure - luaunit.assertEquals(#root.children, 2) - luaunit.assertEquals(root.children[1], child1) - luaunit.assertEquals(root.children[2], child2) - - luaunit.assertEquals(#child1.children, 1) - luaunit.assertEquals(child1.children[1], grandchild1) - - luaunit.assertEquals(#child2.children, 1) - luaunit.assertEquals(child2.children[1], grandchild2) - - luaunit.assertEquals(#grandchild1.children, 1) - luaunit.assertEquals(grandchild1.children[1], greatGrandchild1) - - luaunit.assertEquals(#grandchild2.children, 1) - luaunit.assertEquals(grandchild2.children[1], greatGrandchild2) - - -- Verify absolute positioning behavior (all maintain their own coordinates) - luaunit.assertEquals(child1.x, 50) - luaunit.assertEquals(child1.y, 50) - luaunit.assertEquals(child2.x, 450) - luaunit.assertEquals(child2.y, 50) - - luaunit.assertEquals(grandchild1.x, 25) - luaunit.assertEquals(grandchild1.y, 25) - luaunit.assertEquals(grandchild2.x, 125) - luaunit.assertEquals(grandchild2.y, 175) - - luaunit.assertEquals(greatGrandchild1.x, 10) - luaunit.assertEquals(greatGrandchild1.y, 10) - luaunit.assertEquals(greatGrandchild2.x, 90) - luaunit.assertEquals(greatGrandchild2.y, 160) -end - --- Test 17: Binary tree structure with absolute positioning -function TestAbsolutePositioningBasic:testBinaryTreeAbsoluteStructure() - -- Create a binary tree structure where each node has exactly 2 children - local root = Gui.new({ - id = "root", - x = 400, - y = 100, - width = 100, - height = 50, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Level 1 - local leftChild = Gui.new({ - parent = root, - id = "left", - x = 200, - y = 200, - width = 80, - height = 40, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - local rightChild = Gui.new({ - parent = root, - id = "right", - x = 600, - y = 200, - width = 80, - height = 40, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - -- Level 2 - Left subtree - local leftLeft = Gui.new({ - parent = leftChild, - id = "leftLeft", - x = 100, - y = 300, - width = 60, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local leftRight = Gui.new({ - parent = leftChild, - id = "leftRight", - x = 300, - y = 300, - width = 60, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - -- Level 2 - Right subtree - local rightLeft = Gui.new({ - parent = rightChild, - id = "rightLeft", - x = 500, - y = 300, - width = 60, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local rightRight = Gui.new({ - parent = rightChild, - id = "rightRight", - x = 700, - y = 300, - width = 60, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - -- Verify binary tree structure - luaunit.assertEquals(#root.children, 2) - luaunit.assertEquals(#leftChild.children, 2) - luaunit.assertEquals(#rightChild.children, 2) - luaunit.assertEquals(#leftLeft.children, 0) - luaunit.assertEquals(#leftRight.children, 0) - luaunit.assertEquals(#rightLeft.children, 0) - luaunit.assertEquals(#rightRight.children, 0) - - -- Verify all nodes maintain their absolute positions - luaunit.assertEquals(root.x, 400) - luaunit.assertEquals(leftChild.x, 200) - luaunit.assertEquals(rightChild.x, 600) - luaunit.assertEquals(leftLeft.x, 100) - luaunit.assertEquals(leftRight.x, 300) - luaunit.assertEquals(rightLeft.x, 500) - luaunit.assertEquals(rightRight.x, 700) -end - --- Test 18: Multi-branch tree with stacked z-indices (CSS z-index stacking context) -function TestAbsolutePositioningBasic:testMultiBranchZIndexStacking() - -- Create overlapping elements with complex z-index hierarchies - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 1000, - height = 1000, - positioning = Positioning.ABSOLUTE, - z = 0, - }) - - -- Background layer (z=1) - local backgroundColor = Gui.new({ - parent = container, - id = "background", - x = 100, - y = 100, - width = 800, - height = 800, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Middle layer branch (z=5) - local middleParent = Gui.new({ - parent = container, - id = "middleParent", - x = 200, - y = 200, - width = 600, - height = 600, - positioning = Positioning.ABSOLUTE, - z = 5, - }) - - local middleChild1 = Gui.new({ - parent = middleParent, - id = "middleChild1", - x = 50, - y = 50, - width = 200, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 1, -- relative to middleParent - }) - - local middleChild2 = Gui.new({ - parent = middleParent, - id = "middleChild2", - x = 350, - y = 350, - width = 200, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 2, -- relative to middleParent, above middleChild1 - }) - - -- Foreground layer (z=10) - local foreground = Gui.new({ - parent = container, - id = "foreground", - x = 300, - y = 300, - width = 400, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 10, - }) - - local foregroundChild = Gui.new({ - parent = foreground, - id = "foregroundChild", - x = 150, - y = 150, - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Verify stacking order by z-index - luaunit.assertEquals(backgroundColor.z, 1) - luaunit.assertEquals(middleParent.z, 5) - luaunit.assertEquals(middleChild1.z, 1) - luaunit.assertEquals(middleChild2.z, 2) - luaunit.assertEquals(foreground.z, 10) - luaunit.assertEquals(foregroundChild.z, 1) - - -- Verify structure - luaunit.assertEquals(#container.children, 3) - luaunit.assertEquals(#middleParent.children, 2) - luaunit.assertEquals(#foreground.children, 1) - - -- All elements should maintain their absolute positions - luaunit.assertEquals(middleChild1.x, 50) - luaunit.assertEquals(middleChild2.x, 350) - luaunit.assertEquals(foregroundChild.x, 150) -end - --- Test 19: Wide shallow tree (many siblings at same level) -function TestAbsolutePositioningBasic:testWideShallowAbsoluteTree() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 2000, - height = 500, - positioning = Positioning.ABSOLUTE, - z = 0, - }) - - -- Create 10 siblings in a row - local siblings = {} - for i = 1, 10 do - siblings[i] = Gui.new({ - parent = container, - id = "sibling" .. i, - x = i * 180, - y = 100, - width = 150, - height = 300, - positioning = Positioning.ABSOLUTE, - z = i, -- Each has different z-index - }) - - -- Each sibling has 3 children - for j = 1, 3 do - Gui.new({ - parent = siblings[i], - id = "child" .. i .. "_" .. j, - x = 25, - y = j * 80, - width = 100, - height = 60, - positioning = Positioning.ABSOLUTE, - z = j, - }) - end - end - - -- Verify wide structure - luaunit.assertEquals(#container.children, 10) - - for i = 1, 10 do - luaunit.assertEquals(#siblings[i].children, 3) - luaunit.assertEquals(siblings[i].x, i * 180) - luaunit.assertEquals(siblings[i].z, i) - end -end - --- Test 20: Asymmetric tree with mixed absolute positioning -function TestAbsolutePositioningBasic:testAsymmetricAbsoluteTree() - -- Root with asymmetric branch structure - local root = Gui.new({ - id = "root", - x = 500, - y = 100, - width = 200, - height = 100, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Left branch: deep nesting - local leftBranch = Gui.new({ - parent = root, - id = "leftBranch", - x = 100, - y = 250, - width = 150, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - local leftDeep1 = Gui.new({ - parent = leftBranch, - id = "leftDeep1", - x = 25, - y = 50, - width = 100, - height = 80, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local leftDeep2 = Gui.new({ - parent = leftDeep1, - id = "leftDeep2", - x = 10, - y = 10, - width = 80, - height = 60, - positioning = Positioning.ABSOLUTE, - z = 4, - }) - - local leftDeep3 = Gui.new({ - parent = leftDeep2, - id = "leftDeep3", - x = 5, - y = 5, - width = 70, - height = 50, - positioning = Positioning.ABSOLUTE, - z = 5, - }) - - -- Right branch: wide shallow - local rightBranch = Gui.new({ - parent = root, - id = "rightBranch", - x = 800, - y = 250, - width = 400, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - -- Multiple children for right branch - for i = 1, 5 do - Gui.new({ - parent = rightBranch, - id = "rightChild" .. i, - x = i * 70, - y = 50, - width = 60, - height = 100, - positioning = Positioning.ABSOLUTE, - z = i, - }) - end - - -- Verify asymmetric structure - luaunit.assertEquals(#root.children, 2) - luaunit.assertEquals(#leftBranch.children, 1) -- Deep chain - luaunit.assertEquals(#rightBranch.children, 5) -- Wide spread - - -- Verify deep chain - luaunit.assertEquals(#leftDeep1.children, 1) - luaunit.assertEquals(#leftDeep2.children, 1) - luaunit.assertEquals(#leftDeep3.children, 0) - - -- Verify positions maintained - luaunit.assertEquals(leftBranch.x, 100) - luaunit.assertEquals(rightBranch.x, 800) - luaunit.assertEquals(leftDeep3.x, 5) -end - --- Test 21: Overlapping absolute elements with negative coordinates -function TestAbsolutePositioningBasic:testOverlappingNegativeCoordinates() - local viewport = Gui.new({ - id = "viewport", - x = 500, - y = 500, - width = 400, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 0, - }) - - -- Elements that extend outside viewport boundaries - local topLeft = Gui.new({ - parent = viewport, - id = "topLeft", - x = -100, - y = -100, - width = 200, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - local topRight = Gui.new({ - parent = viewport, - id = "topRight", - x = 300, - y = -50, - width = 200, - height = 150, - positioning = Positioning.ABSOLUTE, - z = 2, - }) - - local bottomLeft = Gui.new({ - parent = viewport, - id = "bottomLeft", - x = -50, - y = 350, - width = 150, - height = 200, - positioning = Positioning.ABSOLUTE, - z = 3, - }) - - local center = Gui.new({ - parent = viewport, - id = "center", - x = 150, - y = 150, - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - z = 10, -- Highest z-index - }) - - -- Verify negative coordinates are preserved - luaunit.assertEquals(topLeft.x, -100) - luaunit.assertEquals(topLeft.y, -100) - luaunit.assertEquals(topRight.x, 300) - luaunit.assertEquals(topRight.y, -50) - luaunit.assertEquals(bottomLeft.x, -50) - luaunit.assertEquals(bottomLeft.y, 350) - - -- Center element with highest z-index - luaunit.assertEquals(center.z, 10) - luaunit.assertEquals(center.x, 150) - luaunit.assertEquals(center.y, 150) - - luaunit.assertEquals(#viewport.children, 4) -end - --- Test 22: Tree with circular-like positioning (elements in circle pattern) -function TestAbsolutePositioningBasic:testCircularPositioningPattern() - local center = Gui.new({ - id = "center", - x = 400, - y = 400, - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Create 8 elements positioned in a circle around center - local radius = 200 - local centerX, centerY = 450, 450 -- Center point of circle - - for i = 1, 8 do - local angle = (i - 1) * (math.pi * 2 / 8) -- 8 evenly spaced angles - local x = centerX + radius * math.cos(angle) - local y = centerY + radius * math.sin(angle) - - local orbiter = Gui.new({ - parent = center, - id = "orbiter" .. i, - x = math.floor(x), - y = math.floor(y), - width = 50, - height = 50, - positioning = Positioning.ABSOLUTE, - z = i, - }) - - -- Each orbiter has a small child - Gui.new({ - parent = orbiter, - id = "orbiterChild" .. i, - x = 10, - y = 10, - width = 30, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - end - - -- Verify circular structure - luaunit.assertEquals(#center.children, 8) - - for i = 1, 8 do - luaunit.assertEquals(#center.children[i].children, 1) - luaunit.assertEquals(center.children[i].z, i) - end -end - --- Test 23: Deep single-branch chain (maximum depth test) -function TestAbsolutePositioningBasic:testDeepSingleBranchChain() - local current = Gui.new({ - id = "root", - x = 100, - y = 100, - width = 500, - height = 500, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Create a 15-level deep chain - for i = 2, 15 do - local child = Gui.new({ - parent = current, - id = "depth" .. i, - x = 10, - y = 10, - width = math.max(50, 500 - (i * 25)), -- Decreasing width - height = math.max(50, 500 - (i * 25)), -- Decreasing height - positioning = Positioning.ABSOLUTE, - z = i, - }) - current = child - end - - -- Verify deep chain structure - current = Gui.topElements[1] -- root element - luaunit.assertEquals(current.id, "root") - - for i = 2, 15 do - luaunit.assertEquals(#current.children, 1) - current = current.children[1] - luaunit.assertEquals(current.id, "depth" .. i) - luaunit.assertEquals(current.z, i) - luaunit.assertEquals(current.x, 10) - luaunit.assertEquals(current.y, 10) - end - - -- Last element should have no children - luaunit.assertEquals(#current.children, 0) -end - --- Test 24: Complex branching with mixed z-indices and overlapping regions -function TestAbsolutePositioningBasic:testComplexBranchingWithOverlaps() - -- Create a complex layout simulating a windowing system - local desktop = Gui.new({ - id = "desktop", - x = 0, - y = 0, - width = 1920, - height = 1080, - positioning = Positioning.ABSOLUTE, - z = 0, - }) - - -- Taskbar - local taskbar = Gui.new({ - parent = desktop, - id = "taskbar", - x = 0, - y = 1040, - width = 1920, - height = 40, - positioning = Positioning.ABSOLUTE, - z = 100, -- Always on top - }) - - -- Windows with different z-indices - local window1 = Gui.new({ - parent = desktop, - id = "window1", - x = 100, - y = 100, - width = 600, - height = 400, - positioning = Positioning.ABSOLUTE, - z = 10, - }) - - local window2 = Gui.new({ - parent = desktop, - id = "window2", - x = 300, - y = 200, - width = 500, - height = 350, - positioning = Positioning.ABSOLUTE, - z = 15, -- Above window1 - }) - - local window3 = Gui.new({ - parent = desktop, - id = "window3", - x = 200, - y = 150, - width = 400, - height = 300, - positioning = Positioning.ABSOLUTE, - z = 5, -- Behind window1 and window2 - }) - - -- Each window has title bar and content - for i, window in ipairs({ window1, window2, window3 }) do - local titlebar = Gui.new({ - parent = window, - id = window.id .. "_titlebar", - x = 0, - y = 0, - width = window.width, - height = 30, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - local content = Gui.new({ - parent = window, - id = window.id .. "_content", - x = 0, - y = 30, - width = window.width, - height = window.height - 30, - positioning = Positioning.ABSOLUTE, - z = 1, - }) - - -- Content has multiple child elements - for j = 1, 3 do - Gui.new({ - parent = content, - id = window.id .. "_item" .. j, - x = j * 50, - y = j * 40, - width = 80, - height = 30, - positioning = Positioning.ABSOLUTE, - z = j, - }) - end - end - - -- Verify complex structure - luaunit.assertEquals(#desktop.children, 4) -- taskbar + 3 windows - luaunit.assertEquals(taskbar.z, 100) -- Highest z-index - luaunit.assertEquals(window1.z, 10) - luaunit.assertEquals(window2.z, 15) - luaunit.assertEquals(window3.z, 5) - - -- Each window has titlebar and content - luaunit.assertEquals(#window1.children, 2) - luaunit.assertEquals(#window2.children, 2) - luaunit.assertEquals(#window3.children, 2) - - -- Each content area has 3 items - for i, window in ipairs({ window1, window2, window3 }) do - local content = window.children[2] -- content is second child - luaunit.assertEquals(#content.children, 3) - end -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/02_absolute_positioning_child_layout_tests.lua b/testing/__tests__/02_absolute_positioning_child_layout_tests.lua deleted file mode 100644 index 9027468..0000000 --- a/testing/__tests__/02_absolute_positioning_child_layout_tests.lua +++ /dev/null @@ -1,1379 +0,0 @@ --- Test suite for absolute positioning child layout functionality --- Tests that absolute positioned elements properly handle child elements --- and don't interfere with flex layout calculations - -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems - --- Test class -TestAbsolutePositioningChildLayout = {} - -function TestAbsolutePositioningChildLayout:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestAbsolutePositioningChildLayout:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Adding children to absolute positioned parents -function TestAbsolutePositioningChildLayout:testAddChildToAbsoluteParent() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - parent:addChild(child) - - -- Verify child was added - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child) - luaunit.assertEquals(child.parent, parent) -end - --- Test 2: Children maintain their own coordinates -function TestAbsolutePositioningChildLayout:testChildrenMaintainCoordinates() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local child1 = Gui.new({ - id = "child1", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - x = 75, - y = 85, - width = 40, - height = 25, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should maintain their original coordinates - luaunit.assertEquals(child1.x, 10) - luaunit.assertEquals(child1.y, 20) - luaunit.assertEquals(child2.x, 75) - luaunit.assertEquals(child2.y, 85) -end - --- Test 3: Absolute positioned elements don't call layoutChildren() logic -function TestAbsolutePositioningChildLayout:testAbsoluteParentSkipsLayoutChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - flexDirection = FlexDirection.HORIZONTAL, - }) - - local child1 = Gui.new({ - id = "child1", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - x = 200, -- Way beyond parent w - this would be repositioned in flex layout - y = 300, - width = 40, - height = 25, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- In absolute positioning, children should keep their original positions - -- regardless of flex direction or justification - luaunit.assertEquals(child1.x, 10) - luaunit.assertEquals(child1.y, 20) - luaunit.assertEquals(child2.x, 200) -- Not repositioned by flex layout - luaunit.assertEquals(child2.y, 300) -end - --- Test 4: Adding children to absolute parent doesn't affect parent's flex properties -function TestAbsolutePositioningChildLayout:testAbsoluteParentFlexPropertiesUnchanged() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.FLEX_END, - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - -- Store original values - local originalFlexDirection = parent.flexDirection - local originalJustifyContent = parent.justifyContent - local originalAlignItems = parent.alignItems - local originalX = parent.x - local originalY = parent.y - - parent:addChild(child) - - -- Parent properties should remain unchanged - luaunit.assertEquals(parent.flexDirection, originalFlexDirection) - luaunit.assertEquals(parent.justifyContent, originalJustifyContent) - luaunit.assertEquals(parent.alignItems, originalAlignItems) - luaunit.assertEquals(parent.x, originalX) - luaunit.assertEquals(parent.y, originalY) -end - --- Test 5: Multiple children added to absolute parent maintain independent positioning -function TestAbsolutePositioningChildLayout:testMultipleChildrenIndependentPositioning() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 300, - height = 300, - }) - - local children = {} - for i = 1, 5 do - children[i] = Gui.new({ - id = "child" .. i, - x = i * 25, - y = i * 30, - width = 20, - height = 15, - }) - parent:addChild(children[i]) - end - - -- Verify each child maintains its position - for i = 1, 5 do - luaunit.assertEquals(children[i].x, i * 25) - luaunit.assertEquals(children[i].y, i * 30) - luaunit.assertEquals(children[i].parent, parent) - end - - luaunit.assertEquals(#parent.children, 5) -end - --- Test 6: Absolute children don't participate in flex layout of their parent -function TestAbsolutePositioningChildLayout:testAbsoluteChildrenIgnoreFlexLayout() - local parent = Gui.new({ - id = "flex_parent", - positioning = Positioning.FLEX, - x = 0, - y = 0, - width = 300, - height = 100, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local flexChild = Gui.new({ - id = "flex_child", - width = 50, - height = 30, - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 200, - y = 40, - width = 50, - height = 30, - }) - - parent:addChild(flexChild) - parent:addChild(absoluteChild) - - -- The absolute child should maintain its position - luaunit.assertEquals(absoluteChild.x, 200) - luaunit.assertEquals(absoluteChild.y, 40) - - -- The flex child should be positioned by the flex layout (at the start since it's the only flex child) - -- Note: exact positioning depends on flex implementation, but it shouldn't be at 200,40 - luaunit.assertNotEquals(flexChild.x, 200) -end - --- Test 7: Child coordinates remain independent of parent position changes -function TestAbsolutePositioningChildLayout:testChildCoordinatesIndependentOfParentChanges() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - width = 50, - height = 40, - }) - - parent:addChild(child) - - -- Change parent position - parent.x = 300 - parent.y = 250 - - -- Child coordinates should remain unchanged (they're relative to parent) - luaunit.assertEquals(child.x, 25) - luaunit.assertEquals(child.y, 30) -end - --- Test 8: Nested absolute positioning -function TestAbsolutePositioningChildLayout:testNestedAbsolutePositioning() - local grandparent = Gui.new({ - id = "grandparent", - positioning = Positioning.ABSOLUTE, - x = 50, - y = 25, - width = 400, - height = 300, - }) - - local parent = Gui.new({ - id = "parent", - positioning = Positioning.ABSOLUTE, - x = 75, - y = 50, - width = 200, - height = 150, - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - grandparent:addChild(parent) - parent:addChild(child) - - -- Verify the hierarchy - luaunit.assertEquals(parent.parent, grandparent) - luaunit.assertEquals(child.parent, parent) - - -- Verify positions are maintained at each level - luaunit.assertEquals(grandparent.x, 50) - luaunit.assertEquals(parent.x, 75) - luaunit.assertEquals(child.x, 10) -end - --- Test 9: Absolute parent with flex children maintains flex properties -function TestAbsolutePositioningChildLayout:testAbsoluteParentWithFlexChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - width = 50, - height = 30, - }) - - parent:addChild(flexChild) - - -- Child should maintain its flex positioning mode - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) - luaunit.assertEquals(flexChild.parent, parent) -end - --- Test 10: Auto-sizing behavior with absolute parent and children -function TestAbsolutePositioningChildLayout:testAutoSizingWithAbsoluteParentAndChildren() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - -- No w/h specified, so it should auto-size - }) - - local child = Gui.new({ - id = "child", - x = 10, - y = 20, - width = 50, - height = 30, - }) - - parent:addChild(child) - - -- Auto-sizing should still work for absolute parents - -- (though the exact behavior may depend on implementation) - luaunit.assertTrue(parent.width >= 0) - luaunit.assertTrue(parent.height >= 0) -end - --- Test 11: Children added to absolute parent preserve their positioning type -function TestAbsolutePositioningChildLayout:testChildrenPreservePositioningType() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 25, - y = 30, - width = 50, - height = 40, - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - width = 60, - height = 35, - }) - - parent:addChild(absoluteChild) - parent:addChild(flexChild) - - -- Children should maintain their original positioning types - luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) -end - --- Test 12: Parent-child coordinate relationships -function TestAbsolutePositioningChildLayout:testParentChildCoordinateRelationships() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - width = 50, - height = 40, - }) - - parent:addChild(child) - - -- Child coordinates should be relative to parent - -- Note: This test verifies the conceptual relationship - -- The actual implementation might handle coordinate systems differently - luaunit.assertEquals(child.x, 25) -- Child maintains its relative coordinates - luaunit.assertEquals(child.y, 30) -end - --- Test 13: Adding child doesn't trigger parent repositioning -function TestAbsolutePositioningChildLayout:testAddChildNoParentRepositioning() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 150, - y = 75, - width = 200, - height = 150, - }) - - local originalX = parent.x - local originalY = parent.y - - local child = Gui.new({ - id = "child", - x = 25, - y = 30, - width = 50, - height = 40, - }) - - parent:addChild(child) - - -- Parent position should remain unchanged after adding child - luaunit.assertEquals(parent.x, originalX) - luaunit.assertEquals(parent.y, originalY) -end - --- Test 14: Children table is properly maintained -function TestAbsolutePositioningChildLayout:testChildrenTableMaintained() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 200, - height = 150, - }) - - local child1 = Gui.new({ id = "child1", x = 10, y = 20, width = 50, height = 30 }) - local child2 = Gui.new({ id = "child2", x = 70, y = 80, width = 40, height = 25 }) - local child3 = Gui.new({ id = "child3", x = 120, y = 90, width = 30, height = 35 }) - - parent:addChild(child1) - luaunit.assertEquals(#parent.children, 1) - luaunit.assertEquals(parent.children[1], child1) - - parent:addChild(child2) - luaunit.assertEquals(#parent.children, 2) - luaunit.assertEquals(parent.children[2], child2) - - parent:addChild(child3) - luaunit.assertEquals(#parent.children, 3) - luaunit.assertEquals(parent.children[3], child3) - - -- Verify all children have correct parent reference - luaunit.assertEquals(child1.parent, parent) - luaunit.assertEquals(child2.parent, parent) - luaunit.assertEquals(child3.parent, parent) -end - --- Test 15: Absolute parent with mixed child types -function TestAbsolutePositioningChildLayout:testAbsoluteParentMixedChildTypes() - local parent = Gui.new({ - id = "absolute_parent", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 50, - width = 300, - height = 200, - }) - - local absoluteChild = Gui.new({ - id = "absolute_child", - positioning = Positioning.ABSOLUTE, - x = 25, - y = 30, - width = 50, - height = 40, - }) - - local flexChild = Gui.new({ - id = "flex_child", - positioning = Positioning.FLEX, - width = 60, - height = 35, - }) - - parent:addChild(absoluteChild) - parent:addChild(flexChild) - - -- Both children should be added successfully - luaunit.assertEquals(#parent.children, 2) - luaunit.assertEquals(parent.children[1], absoluteChild) - luaunit.assertEquals(parent.children[2], flexChild) - - -- Children should maintain their positioning types and properties - luaunit.assertEquals(absoluteChild.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(flexChild.positioning, Positioning.FLEX) - luaunit.assertEquals(absoluteChild.x, 25) - luaunit.assertEquals(absoluteChild.y, 30) -end - --- Run the tests -if arg and arg[0] == debug.getinfo(1, "S").source:sub(2) then - os.exit(luaunit.LuaUnit.run()) -end - --- =========================================================================== --- COMPLEX MULTI-LEVEL HIERARCHY TESTS --- =========================================================================== - --- Test 16: Deep hierarchy with mixed positioning types (CSS-like behavior) -function TestAbsolutePositioningChildLayout:testDeepHierarchyMixedPositioning() - -- Create a complex hierarchy: absolute -> flex -> absolute -> flex - local absoluteRoot = Gui.new({ - id = "absoluteRoot", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 100, - width = 800, - height = 600, - }) - - local flexLevel1 = Gui.new({ - parent = absoluteRoot, - id = "flexLevel1", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 50, -- Should be ignored due to flex positioning - y = 50, - width = 700, - height = 500, - gap = 20, - }) - - -- Add absolute children to flex parent - local absoluteChild1 = Gui.new({ - parent = flexLevel1, - id = "absoluteChild1", - positioning = Positioning.ABSOLUTE, - x = 600, -- Absolute position within flex parent - y = 400, - width = 150, - height = 100, - }) - - local flexChild1 = Gui.new({ - parent = flexLevel1, - id = "flexChild1", - positioning = Positioning.FLEX, - width = 200, - height = 150, - }) - - local flexChild2 = Gui.new({ - parent = flexLevel1, - id = "flexChild2", - positioning = Positioning.FLEX, - width = 200, - height = 150, - }) - - -- Add grandchildren to flex children - local absoluteGrandchild = Gui.new({ - parent = flexChild1, - id = "absoluteGrandchild", - positioning = Positioning.ABSOLUTE, - x = 75, - y = 75, - width = 50, - height = 50, - }) - - local flexGrandchild = Gui.new({ - parent = flexChild2, - id = "flexGrandchild", - positioning = Positioning.FLEX, - width = 100, - height = 75, - }) - - -- Verify hierarchy structure - luaunit.assertEquals(#absoluteRoot.children, 1) - luaunit.assertEquals(absoluteRoot.children[1], flexLevel1) - - luaunit.assertEquals(#flexLevel1.children, 3) - luaunit.assertTrue(flexLevel1.children[1] == absoluteChild1 or flexLevel1.children[2] == absoluteChild1 or flexLevel1.children[3] == absoluteChild1) - - luaunit.assertEquals(#flexChild1.children, 1) - luaunit.assertEquals(flexChild1.children[1], absoluteGrandchild) - - luaunit.assertEquals(#flexChild2.children, 1) - luaunit.assertEquals(flexChild2.children[1], flexGrandchild) - - -- Verify absolute elements maintain their positioning - luaunit.assertEquals(absoluteChild1.x, 600) - luaunit.assertEquals(absoluteChild1.y, 400) - luaunit.assertEquals(absoluteGrandchild.x, 75) - luaunit.assertEquals(absoluteGrandchild.y, 75) -end - --- Test 17: Multi-branch tree with absolute parents having flex and absolute children -function TestAbsolutePositioningChildLayout:testMultiBranchAbsoluteWithMixedChildren() - local root = Gui.new({ - id = "root", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1200, - height = 800, - }) - - -- Left branch: Absolute parent with flex children - local leftAbsoluteParent = Gui.new({ - parent = root, - id = "leftAbsoluteParent", - positioning = Positioning.ABSOLUTE, - x = 50, - y = 50, - width = 500, - height = 700, - }) - - -- Flex container within absolute parent - local leftFlexContainer = Gui.new({ - parent = leftAbsoluteParent, - id = "leftFlexContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 400, - height = 600, - gap = 15, - }) - - -- Add multiple flex children - for i = 1, 4 do - local flexChild = Gui.new({ - parent = leftFlexContainer, - id = "leftFlexChild" .. i, - positioning = Positioning.FLEX, - width = 350, - height = 120, - }) - - -- Each flex child has absolute grandchildren - for j = 1, 2 do - Gui.new({ - parent = flexChild, - id = "leftAbsGrandchild" .. i .. "_" .. j, - positioning = Positioning.ABSOLUTE, - x = j * 100, - y = 20, - width = 80, - height = 80, - }) - end - end - - -- Add some absolute children to the absolute parent - for i = 1, 3 do - Gui.new({ - parent = leftAbsoluteParent, - id = "leftAbsoluteChild" .. i, - positioning = Positioning.ABSOLUTE, - x = 450, - y = i * 200, - width = 40, - height = 150, - }) - end - - -- Right branch: Similar structure but different layout - local rightAbsoluteParent = Gui.new({ - parent = root, - id = "rightAbsoluteParent", - positioning = Positioning.ABSOLUTE, - x = 650, - y = 50, - width = 500, - height = 700, - }) - - local rightFlexContainer = Gui.new({ - parent = rightAbsoluteParent, - id = "rightFlexContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 450, - height = 200, - gap = 10, - }) - - -- Add horizontal flex children - for i = 1, 3 do - local flexChild = Gui.new({ - parent = rightFlexContainer, - id = "rightFlexChild" .. i, - positioning = Positioning.FLEX, - width = 130, - height = 180, - }) - - -- Nested flex container - local nestedFlex = Gui.new({ - parent = flexChild, - id = "rightNestedFlex" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 120, - height = 170, - gap = 5, - }) - - -- Add children to nested flex - for j = 1, 3 do - Gui.new({ - parent = nestedFlex, - id = "rightNestedChild" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = 110, - height = 50, - }) - end - end - - -- Verify structure - luaunit.assertEquals(#root.children, 2) - luaunit.assertEquals(#leftAbsoluteParent.children, 4) -- 1 flex container + 3 absolute children - luaunit.assertEquals(#rightAbsoluteParent.children, 1) -- 1 flex container - - luaunit.assertEquals(#leftFlexContainer.children, 4) - luaunit.assertEquals(#rightFlexContainer.children, 3) - - -- Verify absolute children maintain positions - for i = 1, 3 do - local absChild = leftAbsoluteParent.children[i + 1] -- Skip flex container (first child) - luaunit.assertEquals(absChild.x, 450) - luaunit.assertEquals(absChild.y, i * 200) - end -end - --- Test 18: Cascade of absolute positioned containers with z-index conflicts -function TestAbsolutePositioningChildLayout:testCascadeAbsoluteWithZIndexConflicts() - local viewport = Gui.new({ - id = "viewport", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1000, - height = 1000, - z = 0, - }) - - -- Create overlapping layers with complex z-index hierarchies - local layers = {} - for i = 1, 5 do - layers[i] = Gui.new({ - parent = viewport, - id = "layer" .. i, - positioning = Positioning.ABSOLUTE, - x = i * 50, - y = i * 50, - width = 600, - height = 600, - z = 6 - i, -- Reverse z-index (layer1=5, layer2=4, etc.) - }) - - -- Each layer has sublayers with conflicting z-indices - for j = 1, 3 do - local sublayer = Gui.new({ - parent = layers[i], - id = "sublayer" .. i .. "_" .. j, - positioning = Positioning.ABSOLUTE, - x = j * 100, - y = j * 100, - width = 200, - height = 200, - z = j, -- Same z-index pattern across all layers - }) - - -- Each sublayer has items - for k = 1, 2 do - Gui.new({ - parent = sublayer, - id = "item" .. i .. "_" .. j .. "_" .. k, - positioning = Positioning.ABSOLUTE, - x = k * 30, - y = k * 30, - width = 50, - height = 50, - z = k, - }) - end - end - end - - -- Verify layer structure and z-index ordering - luaunit.assertEquals(#viewport.children, 5) - - for i = 1, 5 do - luaunit.assertEquals(layers[i].z, 6 - i) - luaunit.assertEquals(#layers[i].children, 3) - - for j = 1, 3 do - local sublayer = layers[i].children[j] - luaunit.assertEquals(sublayer.z, j) - luaunit.assertEquals(#sublayer.children, 2) - - for k = 1, 2 do - local item = sublayer.children[k] - luaunit.assertEquals(item.z, k) - luaunit.assertEquals(item.x, k * 30) - luaunit.assertEquals(item.y, k * 30) - end - end - end -end - --- Test 19: Grid-like structure using absolute positioning -function TestAbsolutePositioningChildLayout:testGridStructureAbsolutePositioning() - local grid = Gui.new({ - id = "grid", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 100, - width = 800, - height = 600, - }) - - local rows, cols = 4, 5 - local cellWidth, cellHeight = 150, 120 - local gap = 10 - - -- Create grid cells - local cells = {} - for row = 1, rows do - cells[row] = {} - for col = 1, cols do - local x = (col - 1) * (cellWidth + gap) - local y = (row - 1) * (cellHeight + gap) - - cells[row][col] = Gui.new({ - parent = grid, - id = "cell_" .. row .. "_" .. col, - positioning = Positioning.ABSOLUTE, - x = x, - y = y, - width = cellWidth, - height = cellHeight, - z = row * cols + col, -- Unique z-index for each cell - }) - - -- Each cell has a header and content - local header = Gui.new({ - parent = cells[row][col], - id = "header_" .. row .. "_" .. col, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = cellWidth, - height = 30, - z = 1, - }) - - local content = Gui.new({ - parent = cells[row][col], - id = "content_" .. row .. "_" .. col, - positioning = Positioning.ABSOLUTE, - x = 5, - y = 35, - width = cellWidth - 10, - height = cellHeight - 40, - z = 1, - }) - - -- Content has multiple items - for i = 1, 3 do - Gui.new({ - parent = content, - id = "item_" .. row .. "_" .. col .. "_" .. i, - positioning = Positioning.ABSOLUTE, - x = 10, - y = i * 25, - width = cellWidth - 30, - height = 20, - z = i, - }) - end - end - end - - -- Verify grid structure - luaunit.assertEquals(#grid.children, rows * cols) - - for row = 1, rows do - for col = 1, cols do - local cell = cells[row][col] - local expectedX = (col - 1) * (cellWidth + gap) - local expectedY = (row - 1) * (cellHeight + gap) - - luaunit.assertEquals(cell.x, expectedX) - luaunit.assertEquals(cell.y, expectedY) - luaunit.assertEquals(#cell.children, 2) -- header + content - - local content = cell.children[2] - luaunit.assertEquals(#content.children, 3) -- 3 items - end - end -end - --- Test 20: Complex nested modal/dialog system -function TestAbsolutePositioningChildLayout:testComplexModalDialogSystem() - local app = Gui.new({ - id = "app", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1920, - height = 1080, - z = 0, - }) - - -- Main content - local mainContent = Gui.new({ - parent = app, - id = "mainContent", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1920, - height = 1080, - z = 1, - }) - - -- Modal overlay - local modalOverlay = Gui.new({ - parent = app, - id = "modalOverlay", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1920, - height = 1080, - z = 1000, -- High z-index for overlay - }) - - -- Primary modal - local primaryModal = Gui.new({ - parent = modalOverlay, - id = "primaryModal", - positioning = Positioning.ABSOLUTE, - x = 460, -- Centered: (1920 - 1000) / 2 - y = 290, -- Centered: (1080 - 500) / 2 - width = 1000, - height = 500, - z = 1001, - }) - - -- Modal header - local modalHeader = Gui.new({ - parent = primaryModal, - id = "modalHeader", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1000, - height = 50, - z = 1, - }) - - -- Modal content with tabs - local modalContent = Gui.new({ - parent = primaryModal, - id = "modalContent", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 50, - width = 1000, - height = 400, - z = 1, - }) - - -- Tab system - local tabContainer = Gui.new({ - parent = modalContent, - id = "tabContainer", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1000, - height = 40, - z = 2, - }) - - -- Create tabs - for i = 1, 4 do - Gui.new({ - parent = tabContainer, - id = "tab" .. i, - positioning = Positioning.ABSOLUTE, - x = (i - 1) * 250, - y = 0, - width = 250, - height = 40, - z = i, - }) - end - - -- Tab content area - local tabContentArea = Gui.new({ - parent = modalContent, - id = "tabContentArea", - positioning = Positioning.ABSOLUTE, - x = 10, - y = 50, - width = 980, - height = 340, - z = 1, - }) - - -- Secondary modal (popup within modal) - local secondaryModal = Gui.new({ - parent = modalOverlay, - id = "secondaryModal", - positioning = Positioning.ABSOLUTE, - x = 710, -- Offset from primary modal - y = 340, - width = 500, - height = 400, - z = 1002, -- Above primary modal - }) - - -- Tooltip system - local tooltipContainer = Gui.new({ - parent = app, - id = "tooltipContainer", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1920, - height = 1080, - z = 2000, -- Highest z-index - }) - - local tooltip = Gui.new({ - parent = tooltipContainer, - id = "tooltip", - positioning = Positioning.ABSOLUTE, - x = 800, - y = 600, - width = 200, - height = 50, - z = 2001, - }) - - -- Verify complex modal structure - luaunit.assertEquals(#app.children, 3) -- main, modal overlay, tooltip container - luaunit.assertEquals(#modalOverlay.children, 2) -- primary + secondary modal - luaunit.assertEquals(#primaryModal.children, 2) -- header + content - luaunit.assertEquals(#modalContent.children, 2) -- tab container + content area - luaunit.assertEquals(#tabContainer.children, 4) -- 4 tabs - luaunit.assertEquals(#tooltipContainer.children, 1) -- tooltip - - -- Verify z-index hierarchy - luaunit.assertEquals(mainContent.z, 1) - luaunit.assertEquals(modalOverlay.z, 1000) - luaunit.assertEquals(primaryModal.z, 1001) - luaunit.assertEquals(secondaryModal.z, 1002) - luaunit.assertEquals(tooltipContainer.z, 2000) - luaunit.assertEquals(tooltip.z, 2001) -end - --- Test 21: Tree with dynamic branching (simulating DOM-like structure) -function TestAbsolutePositioningChildLayout:testDynamicBranchingDOMStructure() - -- Simulate a complex web page structure - local document = Gui.new({ - id = "document", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1200, - height = 2000, - z = 0, - }) - - -- Header - local header = Gui.new({ - parent = document, - id = "header", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 1200, - height = 100, - z = 10, - }) - - -- Navigation in header - local nav = Gui.new({ - parent = header, - id = "nav", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 20, - width = 1000, - height = 60, - z = 1, - }) - - -- Nav items - for i = 1, 5 do - local navItem = Gui.new({ - parent = nav, - id = "navItem" .. i, - positioning = Positioning.ABSOLUTE, - x = (i - 1) * 200, - y = 0, - width = 180, - height = 60, - z = i, - }) - - -- Dropdown for each nav item - if i <= 3 then -- Only first 3 have dropdowns - local dropdown = Gui.new({ - parent = navItem, - id = "dropdown" .. i, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 60, - width = 180, - height = 200, - z = 100, -- High z-index for dropdown - }) - - -- Dropdown items - for j = 1, 4 do - Gui.new({ - parent = dropdown, - id = "dropdownItem" .. i .. "_" .. j, - positioning = Positioning.ABSOLUTE, - x = 0, - y = (j - 1) * 50, - width = 180, - height = 50, - z = j, - }) - end - end - end - - -- Main content area - local main = Gui.new({ - parent = document, - id = "main", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 100, - width = 1200, - height = 1700, - z = 1, - }) - - -- Sidebar - local sidebar = Gui.new({ - parent = main, - id = "sidebar", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 300, - height = 1700, - z = 2, - }) - - -- Sidebar widgets - for i = 1, 6 do - local widget = Gui.new({ - parent = sidebar, - id = "widget" .. i, - positioning = Positioning.ABSOLUTE, - x = 10, - y = (i - 1) * 280 + 10, - width = 280, - height = 260, - z = i, - }) - - -- Widget header - Gui.new({ - parent = widget, - id = "widgetHeader" .. i, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 280, - height = 40, - z = 1, - }) - - -- Widget content with items - local widgetContent = Gui.new({ - parent = widget, - id = "widgetContent" .. i, - positioning = Positioning.ABSOLUTE, - x = 5, - y = 45, - width = 270, - height = 210, - z = 1, - }) - - -- Items in widget - for j = 1, 4 do - Gui.new({ - parent = widgetContent, - id = "widgetItem" .. i .. "_" .. j, - positioning = Positioning.ABSOLUTE, - x = 5, - y = (j - 1) * 50, - width = 260, - height = 45, - z = j, - }) - end - end - - -- Content area - local content = Gui.new({ - parent = main, - id = "content", - positioning = Positioning.ABSOLUTE, - x = 320, - y = 0, - width = 880, - height = 1700, - z = 1, - }) - - -- Articles in content - for i = 1, 3 do - local article = Gui.new({ - parent = content, - id = "article" .. i, - positioning = Positioning.ABSOLUTE, - x = 20, - y = (i - 1) * 550 + 20, - width = 840, - height = 500, - z = i, - }) - - -- Article header, content, footer - local articleHeader = Gui.new({ - parent = article, - id = "articleHeader" .. i, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 0, - width = 840, - height = 80, - z = 1, - }) - - local articleContent = Gui.new({ - parent = article, - id = "articleContent" .. i, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 80, - width = 840, - height = 350, - z = 1, - }) - - local articleFooter = Gui.new({ - parent = article, - id = "articleFooter" .. i, - positioning = Positioning.ABSOLUTE, - x = 0, - y = 430, - width = 840, - height = 70, - z = 1, - }) - - -- Comments in article content - for j = 1, 3 do - Gui.new({ - parent = articleContent, - id = "comment" .. i .. "_" .. j, - positioning = Positioning.ABSOLUTE, - x = 20, - y = 50 + (j - 1) * 100, - width = 800, - height = 80, - z = j, - }) - end - end - - -- Footer - local footer = Gui.new({ - parent = document, - id = "footer", - positioning = Positioning.ABSOLUTE, - x = 0, - y = 1800, - width = 1200, - height = 200, - z = 10, - }) - - -- Verify complex DOM structure - luaunit.assertEquals(#document.children, 3) -- header, main, footer - luaunit.assertEquals(#header.children, 1) -- nav - luaunit.assertEquals(#nav.children, 5) -- 5 nav items - luaunit.assertEquals(#main.children, 2) -- sidebar, content - luaunit.assertEquals(#sidebar.children, 6) -- 6 widgets - luaunit.assertEquals(#content.children, 3) -- 3 articles - - -- Verify nav dropdowns - for i = 1, 3 do - local navItem = nav.children[i] - luaunit.assertEquals(#navItem.children, 1) -- dropdown - local dropdown = navItem.children[1] - luaunit.assertEquals(#dropdown.children, 4) -- 4 dropdown items - end - - -- Verify widgets - for i = 1, 6 do - local widget = sidebar.children[i] - luaunit.assertEquals(#widget.children, 2) -- header + content - local widgetContent = widget.children[2] - luaunit.assertEquals(#widgetContent.children, 4) -- 4 items - end - - -- Verify articles - for i = 1, 3 do - local article = content.children[i] - luaunit.assertEquals(#article.children, 3) -- header, content, footer - local articleContent = article.children[2] - luaunit.assertEquals(#articleContent.children, 3) -- 3 comments - end -end - --- Run the tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/03_flex_direction_horizontal_tests.lua b/testing/__tests__/03_flex_direction_horizontal_tests.lua deleted file mode 100644 index 54f8a73..0000000 --- a/testing/__tests__/03_flex_direction_horizontal_tests.lua +++ /dev/null @@ -1,1611 +0,0 @@ --- Test suite for horizontal flex direction functionality --- Tests that flex layout works correctly with horizontal direction (default) - -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems - --- Test class -TestHorizontalFlexDirection = {} - -function TestHorizontalFlexDirection:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestHorizontalFlexDirection:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Basic element creation with horizontal flex direction -function TestHorizontalFlexDirection:testCreateElementWithHorizontalFlexDirection() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - -- Verify element was created with correct properties - luaunit.assertEquals(parent.positioning, Positioning.FLEX) - luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(parent.width, 300) - luaunit.assertEquals(parent.height, 100) -end - --- Test 2: Default flex direction should be horizontal -function TestHorizontalFlexDirection:testDefaultFlexDirectionIsHorizontal() - local parent = Gui.new({ - id = "default_parent", - positioning = Positioning.FLEX, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - -- Default flex direction should be horizontal - luaunit.assertEquals(parent.flexDirection, FlexDirection.HORIZONTAL) -end - --- Test 3: Children positioned horizontally along x-axis -function TestHorizontalFlexDirection:testChildrenPositionedHorizontally() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - local child3 = Gui.new({ - id = "child3", - width = 40, - height = 35, - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- Children should be positioned horizontally - -- child1 should be at x=0 (start) - luaunit.assertEquals(child1.x, 0) - - -- child2 should be positioned after child1 + gap - local expectedChild2X = child1.width + parent.gap - luaunit.assertEquals(child2.x, expectedChild2X) - - -- child3 should be positioned after child2 + gap - local expectedChild3X = child1.width + parent.gap + child2.width + parent.gap - luaunit.assertEquals(child3.x, expectedChild3X) - - -- All children should have same y position as parent - luaunit.assertEquals(child1.y, parent.y) - luaunit.assertEquals(child2.y, parent.y) - luaunit.assertEquals(child3.y, parent.y) -end - --- Test 4: Horizontal layout with gap property -function TestHorizontalFlexDirection:testHorizontalLayoutWithGap() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - gap = 20, -- Custom gap - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Verify gap is applied correctly - luaunit.assertEquals(parent.gap, 20) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, child1.width + 20) -- 50 + 20 = 70 -end - --- Test 5: Horizontal layout with flex-start justification (default) -function TestHorizontalFlexDirection:testHorizontalLayoutFlexStart() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With flex-start, children should start at the beginning - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, child1.width + parent.gap) -end - --- Test 6: Horizontal layout with center justification -function TestHorizontalFlexDirection:testHorizontalLayoutCenter() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - x = 0, - y = 0, - width = 300, - height = 100, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected center positioning - local totalChildWidth = child1.width + child2.width + parent.gap - local availableSpace = parent.width - totalChildWidth - local startX = availableSpace / 2 - - luaunit.assertEquals(child1.x, startX) - luaunit.assertEquals(child2.x, startX + child1.width + parent.gap) -end - --- Test 7: Horizontal layout with flex-end justification -function TestHorizontalFlexDirection:testHorizontalLayoutFlexEnd() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - x = 0, - y = 0, - width = 300, - height = 100, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected flex-end positioning - local totalChildWidth = child1.width + child2.width + parent.gap - local availableSpace = parent.width - totalChildWidth - - luaunit.assertEquals(child1.x, availableSpace) - luaunit.assertEquals(child2.x, availableSpace + child1.width + parent.gap) -end - --- Test 8: Horizontal layout with space-between justification -function TestHorizontalFlexDirection:testHorizontalLayoutSpaceBetween() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - x = 0, - y = 0, - width = 300, - height = 100, - gap = 0, -- Space-between doesn't use gap, it distributes available space - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - local child3 = Gui.new({ - id = "child3", - width = 40, - height = 35, - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- With space-between, first child at start, last at end, others distributed - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child3.x, parent.width - child3.width) - - -- child2 should be positioned in the middle - local availableSpace = parent.width - (child1.width + child2.width + child3.width) - local spaceBetweenItems = availableSpace / 2 -- 2 gaps for 3 children - luaunit.assertEquals(child2.x, child1.width + spaceBetweenItems) -end - --- Test 9: Single child in horizontal layout -function TestHorizontalFlexDirection:testSingleChildHorizontalLayout() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - x = 10, - y = 20, - width = 300, - height = 100, - }) - - local child = Gui.new({ - id = "single_child", - width = 50, - height = 30, - }) - - parent:addChild(child) - - -- Single child with center justification should be centered - local expectedX = parent.x + (parent.width - child.width) / 2 - luaunit.assertEquals(child.x, expectedX) - luaunit.assertEquals(child.y, parent.y) -end - --- Test 10: Empty parent (no children) horizontal layout -function TestHorizontalFlexDirection:testEmptyParentHorizontalLayout() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - -- No children added - luaunit.assertEquals(#parent.children, 0) - - -- Should not cause any errors when layoutChildren is called - parent:layoutChildren() -- This should not throw an error -end - --- Test 11: Horizontal layout coordinate system relative to parent -function TestHorizontalFlexDirection:testHorizontalLayoutCoordinateSystem() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - x = 100, - y = 50, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children coordinates should be relative to parent position - luaunit.assertEquals(child1.x, parent.x + 0) -- First child at parent's x - luaunit.assertEquals(child1.y, parent.y) -- Same y as parent - - luaunit.assertEquals(child2.x, parent.x + child1.width + parent.gap) - luaunit.assertEquals(child2.y, parent.y) -end - --- Test 12: Horizontal layout maintains child heights -function TestHorizontalFlexDirection:testHorizontalLayoutMaintainsChildHeights() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, -- Explicitly set to maintain child heights - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 70, -- Different height - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- In horizontal layout, child heights should be preserved - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 70) -end - --- Test 13: Horizontal layout with align-items stretch -function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsStretch() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items stretch, children with explicit heights should keep them (CSS flexbox behavior) - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) -end - --- Test 14: Horizontal layout with align-items center -function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsCenter() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items center in horizontal layout, children should be centered vertically - local expectedChild1Y = parent.y + (parent.height - child1.height) / 2 - local expectedChild2Y = parent.y + (parent.height - child2.height) / 2 - - luaunit.assertEquals(child1.y, expectedChild1Y) - luaunit.assertEquals(child2.y, expectedChild2Y) -end - --- Test 15: Horizontal layout with align-items flex-end -function TestHorizontalFlexDirection:testHorizontalLayoutAlignItemsFlexEnd() - local parent = Gui.new({ - id = "horizontal_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - x = 0, - y = 0, - width = 300, - height = 100, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- With align-items flex-end in horizontal layout, children should be aligned to bottom - local expectedChild1Y = parent.y + parent.height - child1.height - local expectedChild2Y = parent.y + parent.height - child2.height - - luaunit.assertEquals(child1.y, expectedChild1Y) - luaunit.assertEquals(child2.y, expectedChild2Y) -end - --- Run the tests -if arg and arg[0] == debug.getinfo(1, "S").source:sub(2) then - os.exit(luaunit.LuaUnit.run()) -end - --- =========================================================================== --- COMPLEX NESTED FLEX CONTAINER TESTS --- =========================================================================== - --- Test 16: Nested horizontal flex containers (flexbox within flexbox) -function TestHorizontalFlexDirection:testNestedHorizontalFlexContainers() - local outerContainer = Gui.new({ - id = "outerContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - x = 0, - y = 0, - width = 1200, - height = 300, - gap = 20, - }) - - -- Create 3 nested flex containers - for i = 1, 3 do - local innerContainer = Gui.new({ - parent = outerContainer, - id = "innerContainer" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 350, - height = 280, - gap = 10, - }) - - -- Each inner container has 4 flex items - for j = 1, 4 do - Gui.new({ - parent = innerContainer, - id = "item" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = 70, - height = 120, - }) - end - end - - -- Verify nested structure - luaunit.assertEquals(#outerContainer.children, 3) - - for i = 1, 3 do - local innerContainer = outerContainer.children[i] - luaunit.assertEquals(innerContainer.positioning, Positioning.FLEX) - luaunit.assertEquals(innerContainer.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(#innerContainer.children, 4) - - -- Verify inner items are positioned horizontally within their container - for j = 1, 4 do - local item = innerContainer.children[j] - luaunit.assertEquals(item.positioning, Positioning.FLEX) - luaunit.assertEquals(item.width, 70) - luaunit.assertEquals(item.height, 120) - end - end -end - --- Test 17: Complex grid layout using nested horizontal flex -function TestHorizontalFlexDirection:testComplexGridLayoutNestedHorizontalFlex() - local gridContainer = Gui.new({ - id = "gridContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 1000, - height = 800, - gap = 15, - }) - - -- Create 4 rows, each being a horizontal flex container - for row = 1, 4 do - local rowContainer = Gui.new({ - parent = gridContainer, - id = "row" .. row, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - width = 980, - height = 180, - gap = 12, - }) - - -- Each row has varying number of columns - local colCount = row + 2 -- Row 1 has 3 cols, row 2 has 4 cols, etc. - for col = 1, colCount do - local cell = Gui.new({ - parent = rowContainer, - id = "cell" .. row .. "_" .. col, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = (980 - (colCount - 1) * 12) / colCount, -- Dynamic width - height = 160, - }) - - -- Each cell contains a nested horizontal layout with icons - for icon = 1, 3 do - Gui.new({ - parent = cell, - id = "icon" .. row .. "_" .. col .. "_" .. icon, - positioning = Positioning.FLEX, - width = 30, - height = 30, - }) - end - end - end - - -- Verify grid structure - luaunit.assertEquals(#gridContainer.children, 4) - - for row = 1, 4 do - local rowContainer = gridContainer.children[row] - local expectedColCount = row + 2 - luaunit.assertEquals(#rowContainer.children, expectedColCount) - luaunit.assertEquals(rowContainer.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(rowContainer.justifyContent, JustifyContent.SPACE_EVENLY) - - for col = 1, expectedColCount do - local cell = rowContainer.children[col] - luaunit.assertEquals(#cell.children, 3) -- 3 icons per cell - luaunit.assertEquals(cell.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(cell.justifyContent, JustifyContent.CENTER) - end - end -end - --- Test 18: Horizontal flex with mixed positioning children (absolute within flex) -function TestHorizontalFlexDirection:testHorizontalFlexWithMixedPositioningChildren() - local flexContainer = Gui.new({ - id = "flexContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - x = 100, - y = 100, - width = 800, - height = 200, - gap = 20, - }) - - -- Add flex children - local flexChild1 = Gui.new({ - parent = flexContainer, - id = "flexChild1", - positioning = Positioning.FLEX, - width = 150, - height = 180, - }) - - local flexChild2 = Gui.new({ - parent = flexContainer, - id = "flexChild2", - positioning = Positioning.FLEX, - width = 150, - height = 180, - }) - - local flexChild3 = Gui.new({ - parent = flexContainer, - id = "flexChild3", - positioning = Positioning.FLEX, - width = 150, - height = 180, - }) - - -- Add absolute positioned children (should not participate in flex layout) - local absoluteChild1 = Gui.new({ - parent = flexContainer, - id = "absoluteChild1", - positioning = Positioning.ABSOLUTE, - x = 600, - y = 50, - width = 100, - height = 100, - }) - - local absoluteChild2 = Gui.new({ - parent = flexContainer, - id = "absoluteChild2", - positioning = Positioning.ABSOLUTE, - x = 650, - y = 75, - width = 80, - height = 80, - }) - - -- Add nested flex containers within flex children - for i, flexChild in ipairs({ flexChild1, flexChild2, flexChild3 }) do - local nestedFlex = Gui.new({ - parent = flexChild, - id = "nestedFlex" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 140, - height = 170, - gap = 5, - }) - - -- Add items to nested flex - for j = 1, 2 do - Gui.new({ - parent = nestedFlex, - id = "nestedItem" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = 60, - height = 80, - }) - end - - -- Add absolute child to flex child - Gui.new({ - parent = flexChild, - id = "absInFlex" .. i, - positioning = Positioning.ABSOLUTE, - x = 120, - y = 150, - width = 25, - height = 25, - }) - end - - -- Verify mixed positioning structure - luaunit.assertEquals(#flexContainer.children, 5) -- 3 flex + 2 absolute - - -- Verify absolute children maintain their positions - luaunit.assertEquals(absoluteChild1.x, 600) - luaunit.assertEquals(absoluteChild1.y, 50) - luaunit.assertEquals(absoluteChild2.x, 650) - luaunit.assertEquals(absoluteChild2.y, 75) - - -- Verify nested flex structures - for i = 1, 3 do - local flexChild = flexContainer.children[i] - luaunit.assertEquals(#flexChild.children, 2) -- nested flex + absolute - - local nestedFlex = flexChild.children[1] - luaunit.assertEquals(nestedFlex.positioning, Positioning.FLEX) - luaunit.assertEquals(#nestedFlex.children, 2) - - local absInFlex = flexChild.children[2] - luaunit.assertEquals(absInFlex.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(absInFlex.x, 120) - luaunit.assertEquals(absInFlex.y, 150) - end -end - --- Test 19: Multi-level horizontal flex navigation system -function TestHorizontalFlexDirection:testMultiLevelHorizontalFlexNavigation() - local navSystem = Gui.new({ - id = "navSystem", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 1200, - height = 400, - gap = 0, - }) - - -- Primary navigation (horizontal) - local primaryNav = Gui.new({ - parent = navSystem, - id = "primaryNav", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 1200, - height = 60, - gap = 0, - }) - - -- Primary nav items - local primaryItems = { "Home", "Products", "Services", "About", "Contact" } - for i, itemName in ipairs(primaryItems) do - local primaryItem = Gui.new({ - parent = primaryNav, - id = "primaryItem" .. i, - positioning = Positioning.FLEX, - width = 240, - height = 60, - }) - - -- Some primary items have secondary navigation - if i == 2 or i == 3 then -- Products and Services have sub-menus - local secondaryNav = Gui.new({ - parent = primaryItem, - id = "secondaryNav" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 240, - height = 40, - gap = 5, - }) - - -- Secondary nav items - local secCount = i == 2 and 4 or 3 -- Products has 4, Services has 3 - for j = 1, secCount do - local secondaryItem = Gui.new({ - parent = secondaryNav, - id = "secondaryItem" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = (240 - (secCount - 1) * 5) / secCount, - height = 35, - }) - - -- Tertiary items for some secondary items - if j <= 2 then - local tertiaryNav = Gui.new({ - parent = secondaryItem, - id = "tertiaryNav" .. i .. "_" .. j, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - width = (240 - (secCount - 1) * 5) / secCount, - height = 25, - gap = 2, - }) - - -- Tertiary items - for k = 1, 2 do - Gui.new({ - parent = tertiaryNav, - id = "tertiaryItem" .. i .. "_" .. j .. "_" .. k, - positioning = Positioning.FLEX, - width = ((240 - (secCount - 1) * 5) / secCount - 2) / 2, - height = 20, - }) - end - end - end - end - end - - -- Secondary navigation bar (horizontal) - local secondaryNavBar = Gui.new({ - parent = navSystem, - id = "secondaryNavBar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 1200, - height = 50, - gap = 30, - }) - - -- Breadcrumb navigation - for i = 1, 5 do - local breadcrumb = Gui.new({ - parent = secondaryNavBar, - id = "breadcrumb" .. i, - positioning = Positioning.FLEX, - width = 120, - height = 40, - }) - end - - -- Verify navigation structure - luaunit.assertEquals(#navSystem.children, 2) -- primary nav + secondary nav bar - luaunit.assertEquals(#primaryNav.children, 5) -- 5 primary items - luaunit.assertEquals(#secondaryNavBar.children, 5) -- 5 breadcrumbs - - -- Verify Products (item 2) has secondary navigation - local productsItem = primaryNav.children[2] - luaunit.assertEquals(#productsItem.children, 1) -- secondary nav - local productsSecondary = productsItem.children[1] - luaunit.assertEquals(#productsSecondary.children, 4) -- 4 secondary items - - -- Verify tertiary navigation exists - for j = 1, 2 do - local secondaryItem = productsSecondary.children[j] - luaunit.assertEquals(#secondaryItem.children, 1) -- tertiary nav - local tertiaryNav = secondaryItem.children[1] - luaunit.assertEquals(#tertiaryNav.children, 2) -- 2 tertiary items - end -end - --- Test 20: Horizontal flex card layout with dynamic sizing -function TestHorizontalFlexDirection:testHorizontalFlexCardLayoutDynamicSizing() - local cardContainer = Gui.new({ - id = "cardContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - x = 50, - y = 50, - width = 1400, - height = 600, - gap = 25, - }) - - -- Create cards with different content complexities - local cardConfigs = { - { width = 300, items = 3, hasImage = true }, - { width = 250, items = 2, hasImage = false }, - { width = 350, items = 4, hasImage = true }, - { width = 280, items = 3, hasImage = true }, - { width = 320, items = 5, hasImage = false }, - } - - for i, config in ipairs(cardConfigs) do - local card = Gui.new({ - parent = cardContainer, - id = "card" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = config.width, - height = 550, - gap = 10, - }) - - -- Card header (horizontal layout) - local cardHeader = Gui.new({ - parent = card, - id = "cardHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = config.width - 20, - height = 50, - gap = 10, - }) - - -- Header title and actions - Gui.new({ - parent = cardHeader, - id = "cardTitle" .. i, - positioning = Positioning.FLEX, - width = (config.width - 30) * 0.7, - height = 40, - }) - - local headerActions = Gui.new({ - parent = cardHeader, - id = "headerActions" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - width = (config.width - 30) * 0.3, - height = 40, - gap = 5, - }) - - -- Action buttons - for j = 1, 2 do - Gui.new({ - parent = headerActions, - id = "actionBtn" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = ((config.width - 30) * 0.3 - 5) / 2, - height = 35, - }) - end - - -- Card image (if configured) - if config.hasImage then - Gui.new({ - parent = card, - id = "cardImage" .. i, - positioning = Positioning.FLEX, - width = config.width - 20, - height = 200, - }) - end - - -- Card content area - local cardContent = Gui.new({ - parent = card, - id = "cardContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = config.width - 20, - height = config.hasImage and 240 or 440, - gap = 8, - }) - - -- Content items - for j = 1, config.items do - local contentItem = Gui.new({ - parent = cardContent, - id = "contentItem" .. i .. "_" .. j, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - width = config.width - 40, - height = (config.hasImage and 230 or 430) / config.items - 8, - gap = 10, - }) - - -- Item icon and text - Gui.new({ - parent = contentItem, - id = "itemIcon" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = 30, - height = (config.hasImage and 230 or 430) / config.items - 8, - }) - - Gui.new({ - parent = contentItem, - id = "itemText" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = config.width - 80, - height = (config.hasImage and 230 or 430) / config.items - 8, - }) - end - - -- Card footer (horizontal layout) - local cardFooter = Gui.new({ - parent = card, - id = "cardFooter" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = config.width - 20, - height = 50, - gap = 15, - }) - - -- Footer buttons - for j = 1, 3 do - Gui.new({ - parent = cardFooter, - id = "footerBtn" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = (config.width - 50) / 3, - height = 40, - }) - end - end - - -- Verify card structure - luaunit.assertEquals(#cardContainer.children, 5) - - for i, config in ipairs(cardConfigs) do - local card = cardContainer.children[i] - luaunit.assertEquals(card.width, config.width) - - -- Count expected children: header + content + footer + optional image - local expectedChildren = 3 + (config.hasImage and 1 or 0) - luaunit.assertEquals(#card.children, expectedChildren) - - -- Verify header structure - local cardHeader = card.children[1] - luaunit.assertEquals(#cardHeader.children, 2) -- title + actions - local headerActions = cardHeader.children[2] - luaunit.assertEquals(#headerActions.children, 2) -- 2 action buttons - - -- Verify content structure - local contentIndex = config.hasImage and 3 or 2 - local cardContent = card.children[contentIndex] - luaunit.assertEquals(#cardContent.children, config.items) - - -- Verify each content item has icon and text - for j = 1, config.items do - local contentItem = cardContent.children[j] - luaunit.assertEquals(#contentItem.children, 2) -- icon + text - end - - -- Verify footer structure - local footerIndex = config.hasImage and 4 or 3 - local cardFooter = card.children[footerIndex] - luaunit.assertEquals(#cardFooter.children, 3) -- 3 footer buttons - end -end - --- Test 21: Horizontal flex with overflow and scrolling simulation -function TestHorizontalFlexDirection:testHorizontalFlexOverflowScrolling() - local scrollContainer = Gui.new({ - id = "scrollContainer", - positioning = Positioning.ABSOLUTE, - x = 100, - y = 100, - width = 800, - height = 200, - }) - - -- Content container (wider than viewport) - local contentContainer = Gui.new({ - parent = scrollContainer, - id = "contentContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - x = 0, -- This would change with scrolling - y = 0, - width = 2000, -- Wider than scroll container - height = 180, - gap = 20, - }) - - -- Create many items that exceed viewport width - for i = 1, 15 do - local item = Gui.new({ - parent = contentContainer, - id = "scrollItem" .. i, - positioning = Positioning.FLEX, - width = 120, - height = 160, - }) - - -- Each item has internal horizontal layout - local itemContent = Gui.new({ - parent = item, - id = "itemContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 110, - height = 150, - gap = 5, - }) - - -- Item components - for j = 1, 2 do - local component = Gui.new({ - parent = itemContent, - id = "component" .. i .. "_" .. j, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 50, - height = 140, - gap = 3, - }) - - -- Sub-components in vertical layout - for k = 1, 3 do - Gui.new({ - parent = component, - id = "subComponent" .. i .. "_" .. j .. "_" .. k, - positioning = Positioning.FLEX, - width = 45, - height = 42, - }) - end - end - end - - -- Verify overflow structure - luaunit.assertEquals(#scrollContainer.children, 1) - luaunit.assertEquals(#contentContainer.children, 15) - - -- Verify content is wider than container - luaunit.assertTrue(contentContainer.width > scrollContainer.width) - - -- Verify nested structure - for i = 1, 15 do - local item = contentContainer.children[i] - luaunit.assertEquals(#item.children, 1) -- item content - - local itemContent = item.children[1] - luaunit.assertEquals(#itemContent.children, 2) -- 2 components - luaunit.assertEquals(itemContent.flexDirection, FlexDirection.HORIZONTAL) - - for j = 1, 2 do - local component = itemContent.children[j] - luaunit.assertEquals(#component.children, 3) -- 3 sub-components - luaunit.assertEquals(component.flexDirection, FlexDirection.VERTICAL) - end - end -end - --- Test 22: Complex horizontal dashboard layout -function TestHorizontalFlexDirection:testComplexHorizontalDashboardLayout() - local dashboard = Gui.new({ - id = "dashboard", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 1600, - height = 1000, - gap = 0, - }) - - -- Dashboard header (horizontal) - local header = Gui.new({ - parent = dashboard, - id = "dashboardHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 1600, - height = 80, - gap = 20, - }) - - -- Header left section - local headerLeft = Gui.new({ - parent = header, - id = "headerLeft", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - width = 400, - height = 60, - gap = 15, - }) - - -- Logo and title - Gui.new({ - parent = headerLeft, - id = "logo", - positioning = Positioning.FLEX, - width = 60, - height = 60, - }) - - Gui.new({ - parent = headerLeft, - id = "title", - positioning = Positioning.FLEX, - width = 300, - height = 60, - }) - - -- Header center - search and navigation - local headerCenter = Gui.new({ - parent = header, - id = "headerCenter", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 800, - height = 60, - gap = 20, - }) - - -- Search bar - Gui.new({ - parent = headerCenter, - id = "searchBar", - positioning = Positioning.FLEX, - width = 400, - height = 50, - }) - - -- Quick actions - local quickActions = Gui.new({ - parent = headerCenter, - id = "quickActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - width = 300, - height = 50, - gap = 10, - }) - - for i = 1, 4 do - Gui.new({ - parent = quickActions, - id = "quickAction" .. i, - positioning = Positioning.FLEX, - width = 65, - height = 45, - }) - end - - -- Header right - user section - local headerRight = Gui.new({ - parent = header, - id = "headerRight", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - width = 300, - height = 60, - gap = 10, - }) - - -- Notifications and user menu - Gui.new({ - parent = headerRight, - id = "notifications", - positioning = Positioning.FLEX, - width = 50, - height = 50, - }) - - Gui.new({ - parent = headerRight, - id = "userMenu", - positioning = Positioning.FLEX, - width = 200, - height = 50, - }) - - -- Main dashboard content (horizontal sections) - local mainContent = Gui.new({ - parent = dashboard, - id = "mainContent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - width = 1600, - height = 920, - gap = 0, - }) - - -- Left sidebar - local leftSidebar = Gui.new({ - parent = mainContent, - id = "leftSidebar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 250, - height = 920, - gap = 10, - }) - - -- Sidebar sections - for i = 1, 5 do - local sidebarSection = Gui.new({ - parent = leftSidebar, - id = "sidebarSection" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 240, - height = 170, - gap = 5, - }) - - -- Section header - Gui.new({ - parent = sidebarSection, - id = "sectionHeader" .. i, - positioning = Positioning.FLEX, - width = 230, - height = 30, - }) - - -- Section items - for j = 1, 4 do - Gui.new({ - parent = sidebarSection, - id = "sectionItem" .. i .. "_" .. j, - positioning = Positioning.FLEX, - width = 230, - height = 30, - }) - end - end - - -- Center content area (horizontal widget layout) - local centerArea = Gui.new({ - parent = mainContent, - id = "centerArea", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 1100, - height = 920, - gap = 20, - }) - - -- Top widgets row - local topWidgets = Gui.new({ - parent = centerArea, - id = "topWidgets", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 1080, - height = 280, - gap = 20, - }) - - -- Create 4 top widgets - for i = 1, 4 do - local widget = Gui.new({ - parent = topWidgets, - id = "topWidget" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 250, - height = 260, - gap = 10, - }) - - -- Widget header - local widgetHeader = Gui.new({ - parent = widget, - id = "topWidgetHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 240, - height = 40, - gap = 10, - }) - - Gui.new({ - parent = widgetHeader, - id = "widgetTitle" .. i, - positioning = Positioning.FLEX, - width = 180, - height = 35, - }) - - Gui.new({ - parent = widgetHeader, - id = "widgetControls" .. i, - positioning = Positioning.FLEX, - width = 50, - height = 35, - }) - - -- Widget content - Gui.new({ - parent = widget, - id = "topWidgetContent" .. i, - positioning = Positioning.FLEX, - width = 240, - height = 200, - }) - end - - -- Bottom content area - local bottomWidgets = Gui.new({ - parent = centerArea, - id = "bottomWidgets", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - width = 1080, - height = 600, - gap = 30, - }) - - -- Large widget and smaller widgets - local largeWidget = Gui.new({ - parent = bottomWidgets, - id = "largeWidget", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 700, - height = 580, - gap = 15, - }) - - -- Large widget components - Gui.new({ - parent = largeWidget, - id = "largeWidgetHeader", - positioning = Positioning.FLEX, - width = 680, - height = 50, - }) - - local largeWidgetContent = Gui.new({ - parent = largeWidget, - id = "largeWidgetContent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 680, - height = 500, - gap = 20, - }) - - -- Chart and details - Gui.new({ - parent = largeWidgetContent, - id = "chart", - positioning = Positioning.FLEX, - width = 400, - height = 480, - }) - - local details = Gui.new({ - parent = largeWidgetContent, - id = "details", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 240, - height = 480, - gap = 10, - }) - - -- Detail items - for i = 1, 6 do - Gui.new({ - parent = details, - id = "detailItem" .. i, - positioning = Positioning.FLEX, - width = 230, - height = 70, - }) - end - - -- Small widgets column - local smallWidgets = Gui.new({ - parent = bottomWidgets, - id = "smallWidgets", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 300, - height = 580, - gap = 20, - }) - - -- Create 3 small widgets - for i = 1, 3 do - local smallWidget = Gui.new({ - parent = smallWidgets, - id = "smallWidget" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 280, - height = 173, - gap = 10, - }) - - Gui.new({ - parent = smallWidget, - id = "smallWidgetHeader" .. i, - positioning = Positioning.FLEX, - width = 270, - height = 35, - }) - - Gui.new({ - parent = smallWidget, - id = "smallWidgetContent" .. i, - positioning = Positioning.FLEX, - width = 270, - height = 128, - }) - end - - -- Right sidebar - local rightSidebar = Gui.new({ - parent = mainContent, - id = "rightSidebar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 250, - height = 920, - gap = 15, - }) - - -- Activity feed - for i = 1, 8 do - Gui.new({ - parent = rightSidebar, - id = "activityItem" .. i, - positioning = Positioning.FLEX, - width = 240, - height = 100, - }) - end - - -- Verify dashboard structure - luaunit.assertEquals(#dashboard.children, 2) -- header + main content - luaunit.assertEquals(#header.children, 3) -- left + center + right - luaunit.assertEquals(#headerLeft.children, 2) -- logo + title - luaunit.assertEquals(#headerCenter.children, 2) -- search + quick actions - luaunit.assertEquals(#quickActions.children, 4) -- 4 quick actions - luaunit.assertEquals(#headerRight.children, 2) -- notifications + user menu - - luaunit.assertEquals(#mainContent.children, 3) -- left sidebar + center + right sidebar - luaunit.assertEquals(#leftSidebar.children, 5) -- 5 sidebar sections - luaunit.assertEquals(#centerArea.children, 2) -- top widgets + bottom widgets - luaunit.assertEquals(#topWidgets.children, 4) -- 4 top widgets - luaunit.assertEquals(#bottomWidgets.children, 2) -- large widget + small widgets - luaunit.assertEquals(#smallWidgets.children, 3) -- 3 small widgets - luaunit.assertEquals(#rightSidebar.children, 8) -- 8 activity items - - -- Verify large widget structure - luaunit.assertEquals(#largeWidget.children, 2) -- header + content - luaunit.assertEquals(#largeWidgetContent.children, 2) -- chart + details - luaunit.assertEquals(#details.children, 6) -- 6 detail items -end - --- Run the tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/04_flex_direction_vertical_tests.lua b/testing/__tests__/04_flex_direction_vertical_tests.lua deleted file mode 100644 index b3c0c89..0000000 --- a/testing/__tests__/04_flex_direction_vertical_tests.lua +++ /dev/null @@ -1,1728 +0,0 @@ --- Test suite for vertical flex direction functionality --- Tests that flex layout works correctly with vertical direction - -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems - --- Test class -TestVerticalFlexDirection = {} - -function TestVerticalFlexDirection:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestVerticalFlexDirection:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Basic element creation with vertical flex direction -function TestVerticalFlexDirection:testCreateElementWithVerticalFlexDirection() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - -- Verify element was created with correct properties - luaunit.assertEquals(parent.positioning, Positioning.FLEX) - luaunit.assertEquals(parent.flexDirection, FlexDirection.VERTICAL) - luaunit.assertEquals(parent.width, 100) - luaunit.assertEquals(parent.height, 300) -end - --- Test 2: Single child vertical layout -function TestVerticalFlexDirection:testSingleChildVerticalLayout() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - local child = Gui.new({ - id = "single_child", - width = 80, - height = 50, - }) - - parent:addChild(child) - - -- Child should be positioned at top of parent (flex-start default) - luaunit.assertEquals(child.x, parent.x) - luaunit.assertEquals(child.y, parent.y) -end - --- Test 3: Multiple children vertical layout -function TestVerticalFlexDirection:testMultipleChildrenVerticalLayout() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - local child3 = Gui.new({ - id = "child3", - width = 60, - height = 30, - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- Children should be positioned vertically with gaps - luaunit.assertEquals(child1.x, parent.x) - luaunit.assertEquals(child1.y, parent.y) - - luaunit.assertEquals(child2.x, parent.x) - luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) - - luaunit.assertEquals(child3.x, parent.x) - luaunit.assertEquals(child3.y, child2.y + child2.height + parent.gap) -end - --- Test 4: Empty parent (no children) vertical layout -function TestVerticalFlexDirection:testEmptyParentVerticalLayout() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - -- Should not cause any errors and should have no children - luaunit.assertEquals(#parent.children, 0) -end - --- Test 5: Vertical layout with flex-start justification (default) -function TestVerticalFlexDirection:testVerticalLayoutFlexStart() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should be positioned at top (flex-start) - luaunit.assertEquals(child1.y, parent.y) - luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) -end - --- Test 6: Vertical layout with center justification -function TestVerticalFlexDirection:testVerticalLayoutCenter() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected center positioning - local totalChildHeight = child1.height + child2.height + parent.gap - local availableSpace = parent.height - totalChildHeight - local startY = availableSpace / 2 - - luaunit.assertEquals(child1.y, parent.y + startY) - luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) -end - --- Test 7: Vertical layout with flex-end justification -function TestVerticalFlexDirection:testVerticalLayoutFlexEnd() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Calculate expected end positioning - local totalChildHeight = child1.height + child2.height + parent.gap - local availableSpace = parent.height - totalChildHeight - local startY = availableSpace - - luaunit.assertEquals(child1.y, parent.y + startY) - luaunit.assertEquals(child2.y, child1.y + child1.height + parent.gap) -end - --- Test 8: Single child with center justification -function TestVerticalFlexDirection:testSingleChildVerticalLayoutCentered() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - x = 20, - y = 10, - width = 100, - height = 300, - }) - - local child = Gui.new({ - id = "single_child", - width = 80, - height = 50, - }) - - parent:addChild(child) - - -- Single child with center justification should be centered - local expectedY = parent.y + (parent.height - child.height) / 2 - luaunit.assertEquals(child.y, expectedY) - luaunit.assertEquals(child.x, parent.x) -end - --- Test 9: Vertical layout maintains child widths -function TestVerticalFlexDirection:testVerticalLayoutMaintainsChildWidths() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, -- Explicitly set to maintain child widths - x = 0, - y = 0, - width = 100, - height = 300, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, -- Different width - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- In vertical layout, child widths should be preserved - luaunit.assertEquals(child1.width, 80) - luaunit.assertEquals(child2.width, 60) -end - --- Test 10: Vertical layout with align-items center -function TestVerticalFlexDirection:testVerticalLayoutAlignItemsCenter() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should be centered horizontally - local expectedX1 = parent.x + (parent.width - child1.width) / 2 - local expectedX2 = parent.x + (parent.width - child2.width) / 2 - - luaunit.assertEquals(child1.x, expectedX1) - luaunit.assertEquals(child2.x, expectedX2) -end - --- Test 11: Vertical layout with align-items flex-end -function TestVerticalFlexDirection:testVerticalLayoutAlignItemsFlexEnd() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should be aligned to the right - local expectedX1 = parent.x + parent.width - child1.width - local expectedX2 = parent.x + parent.width - child2.width - - luaunit.assertEquals(child1.x, expectedX1) - luaunit.assertEquals(child2.x, expectedX2) -end - --- Test 12: Vertical layout with align-items stretch -function TestVerticalFlexDirection:testVerticalLayoutAlignItemsStretch() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - x = 0, - y = 0, - width = 100, - height = 300, - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children with explicit widths should keep them (CSS flexbox behavior) - luaunit.assertEquals(child1.width, 80) - luaunit.assertEquals(child2.width, 60) -end - --- Test 13: Vertical layout with space-between -function TestVerticalFlexDirection:testVerticalLayoutSpaceBetween() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 0, -- Space-between controls spacing, not gap - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - local child3 = Gui.new({ - id = "child3", - width = 60, - height = 30, - }) - - parent:addChild(child1) - parent:addChild(child2) - parent:addChild(child3) - - -- First child should be at start - luaunit.assertEquals(child1.y, parent.y) - - -- Last child should be at end - luaunit.assertEquals(child3.y, parent.y + parent.height - child3.height) - - -- Middle child should be evenly spaced - local remainingSpace = parent.height - child1.height - child2.height - child3.height - local spaceBetween = remainingSpace / 2 - luaunit.assertEquals(child2.y, child1.y + child1.height + spaceBetween) -end - --- Test 14: Vertical layout with custom gap -function TestVerticalFlexDirection:testVerticalLayoutCustomGap() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 100, - height = 300, - gap = 20, -- Custom gap - }) - - local child1 = Gui.new({ - id = "child1", - width = 80, - height = 50, - }) - - local child2 = Gui.new({ - id = "child2", - width = 70, - height = 40, - }) - - parent:addChild(child1) - parent:addChild(child2) - - -- Children should be positioned with custom gap - luaunit.assertEquals(child1.y, parent.y) - luaunit.assertEquals(child2.y, child1.y + child1.height + 20) -end - --- Test 15: Vertical layout with positioning offset -function TestVerticalFlexDirection:testVerticalLayoutWithPositioningOffset() - local parent = Gui.new({ - id = "vertical_parent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - x = 50, - y = 100, - width = 100, - height = 300, - }) - - local child = Gui.new({ - id = "single_child", - width = 80, - height = 50, - }) - - parent:addChild(child) - - -- Child should respect parent's position offset - local expectedY = parent.y + (parent.height - child.height) / 2 - luaunit.assertEquals(child.x, parent.x) - luaunit.assertEquals(child.y, expectedY) -end - --- Test 16: Complex vertical sidebar layout with nested sections -function TestVerticalFlexDirection:testComplexVerticalSidebarLayout() - local sidebar = Gui.new({ - id = "sidebar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 300, - height = 800, - gap = 20, - }) - - -- Header section - local header = Gui.new({ - id = "header", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 280, - height = 120, - gap = 10, - }) - - local logo = Gui.new({ id = "logo", width = 100, height = 40 }) - local userInfo = Gui.new({ id = "userInfo", width = 250, height = 60 }) - - header:addChild(logo) - header:addChild(userInfo) - - -- Navigation section with nested menus - local navigation = Gui.new({ - id = "navigation", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 280, - height = 400, - gap = 5, - }) - - local mainMenu = Gui.new({ - id = "mainMenu", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 280, - height = 200, - gap = 2, - }) - - -- Create menu items - for i = 1, 5 do - local menuItem = Gui.new({ - id = "menuItem" .. i, - width = 270, - height = 35, - }) - mainMenu:addChild(menuItem) - end - - local subMenu = Gui.new({ - id = "subMenu", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 260, - height = 180, - gap = 3, - }) - - -- Create submenu items with indentation - for i = 1, 4 do - local subMenuItem = Gui.new({ - id = "subMenuItem" .. i, - width = 240, - height = 30, - }) - subMenu:addChild(subMenuItem) - end - - navigation:addChild(mainMenu) - navigation:addChild(subMenu) - - -- Footer section - local footer = Gui.new({ - id = "footer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - width = 280, - height = 200, - gap = 10, - }) - - local settings = Gui.new({ id = "settings", width = 200, height = 50 }) - local help = Gui.new({ id = "help", width = 180, height = 40 }) - local logout = Gui.new({ id = "logout", width = 120, height = 35 }) - - footer:addChild(settings) - footer:addChild(help) - footer:addChild(logout) - - sidebar:addChild(header) - sidebar:addChild(navigation) - sidebar:addChild(footer) - - -- Verify complex nested positioning - luaunit.assertEquals(header.y, sidebar.y) - luaunit.assertEquals(navigation.y, header.y + header.height + sidebar.gap) - luaunit.assertEquals(footer.y, navigation.y + navigation.height + sidebar.gap) - - -- Verify nested menu structure - luaunit.assertEquals(logo.y, header.y) - luaunit.assertEquals(userInfo.y, logo.y + logo.height + header.gap) - - -- Verify submenu positioning - luaunit.assertEquals(subMenu.y, mainMenu.y + mainMenu.height + navigation.gap) -end - --- Test 17: Multi-level accordion/collapsible vertical layout -function TestVerticalFlexDirection:testMultiLevelAccordionLayout() - local container = Gui.new({ - id = "accordionContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 400, - height = 600, - gap = 5, - }) - - -- Create multiple accordion sections - for sectionIndex = 1, 3 do - local section = Gui.new({ - id = "section" .. sectionIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 380, - height = 180, - gap = 2, - }) - - local sectionHeader = Gui.new({ - id = "sectionHeader" .. sectionIndex, - width = 380, - height = 40, - }) - - local sectionContent = Gui.new({ - id = "sectionContent" .. sectionIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 360, - height = 130, - gap = 3, - }) - - -- Add subsections within each section - for subIndex = 1, 3 do - local subsection = Gui.new({ - id = "subsection" .. sectionIndex .. "_" .. subIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 340, - height = 40, - gap = 1, - }) - - local subHeader = Gui.new({ - id = "subHeader" .. sectionIndex .. "_" .. subIndex, - width = 340, - height = 20, - }) - - local subContent = Gui.new({ - id = "subContent" .. sectionIndex .. "_" .. subIndex, - width = 320, - height = 18, - }) - - subsection:addChild(subHeader) - subsection:addChild(subContent) - sectionContent:addChild(subsection) - end - - section:addChild(sectionHeader) - section:addChild(sectionContent) - container:addChild(section) - end - - -- Verify accordion structure - local firstSection = container.children[1] - local secondSection = container.children[2] - local thirdSection = container.children[3] - - luaunit.assertEquals(firstSection.y, container.y) - luaunit.assertEquals(secondSection.y, firstSection.y + firstSection.height + container.gap) - luaunit.assertEquals(thirdSection.y, secondSection.y + secondSection.height + container.gap) - - -- Verify nested subsection positioning - local firstSectionContent = firstSection.children[2] - local firstSubsection = firstSectionContent.children[1] - local secondSubsection = firstSectionContent.children[2] - - luaunit.assertEquals(firstSubsection.y, firstSectionContent.y) - luaunit.assertEquals(secondSubsection.y, firstSubsection.y + firstSubsection.height + firstSectionContent.gap) -end - --- Test 18: Vertical chat/message thread layout -function TestVerticalFlexDirection:testVerticalChatMessageLayout() - local chatContainer = Gui.new({ - id = "chatContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - x = 0, - y = 0, - width = 350, - height = 500, - gap = 8, - }) - - -- Create message threads with varying complexity - local messageTypes = { - { sender = "user", hasAvatar = true, hasReactions = false }, - { sender = "bot", hasAvatar = true, hasReactions = true }, - { sender = "user", hasAvatar = false, hasReactions = true }, - { sender = "system", hasAvatar = false, hasReactions = false }, - } - - for i, msgType in ipairs(messageTypes) do - local messageGroup = Gui.new({ - id = "messageGroup" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 330, - height = msgType.hasReactions and 80 or 60, - gap = 4, - }) - - local messageRow = Gui.new({ - id = "messageRow" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 330, - height = 40, - gap = 8, - }) - - if msgType.hasAvatar then - local avatar = Gui.new({ - id = "avatar" .. i, - width = 32, - height = 32, - }) - messageRow:addChild(avatar) - end - - local messageContent = Gui.new({ - id = "messageContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = msgType.hasAvatar and 290 or 330, - height = 35, - gap = 2, - }) - - local messageText = Gui.new({ - id = "messageText" .. i, - width = msgType.hasAvatar and 280 or 320, - height = 20, - }) - - local timestamp = Gui.new({ - id = "timestamp" .. i, - width = 60, - height = 12, - }) - - messageContent:addChild(messageText) - messageContent:addChild(timestamp) - messageRow:addChild(messageContent) - messageGroup:addChild(messageRow) - - if msgType.hasReactions then - local reactions = Gui.new({ - id = "reactions" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 150, - height = 20, - gap = 3, - }) - - -- Add reaction buttons - for j = 1, 3 do - local reaction = Gui.new({ - id = "reaction" .. i .. "_" .. j, - width = 25, - height = 18, - }) - reactions:addChild(reaction) - end - - messageGroup:addChild(reactions) - end - - chatContainer:addChild(messageGroup) - end - - -- Verify messages are positioned from bottom (flex-end) - local lastMessage = chatContainer.children[#chatContainer.children] - local expectedLastY = chatContainer.y + chatContainer.height - lastMessage.height - - -- Note: This test may fail if flex-end positioning isn't implemented correctly - -- but demonstrates the expected CSS behavior - luaunit.assertEquals(lastMessage.y + lastMessage.height, chatContainer.y + chatContainer.height) -end - --- Test 19: Nested form layout with sections and field groups -function TestVerticalFlexDirection:testNestedFormLayout() - local form = Gui.new({ - id = "form", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 500, - height = 700, - gap = 20, - }) - - -- Form header - local formHeader = Gui.new({ - id = "formHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 480, - height = 80, - gap = 10, - }) - - local title = Gui.new({ id = "title", width = 300, height = 30 }) - local description = Gui.new({ id = "description", width = 450, height = 40 }) - - formHeader:addChild(title) - formHeader:addChild(description) - - -- Personal information section - local personalSection = Gui.new({ - id = "personalSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 480, - height = 200, - gap = 15, - }) - - local personalTitle = Gui.new({ id = "personalTitle", width = 200, height = 25 }) - personalSection:addChild(personalTitle) - - -- Field groups within personal section - local nameGroup = Gui.new({ - id = "nameGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 460, - height = 80, - gap = 8, - }) - - local nameLabel = Gui.new({ id = "nameLabel", width = 100, height = 20 }) - local nameInput = Gui.new({ id = "nameInput", width = 400, height = 35 }) - local nameError = Gui.new({ id = "nameError", width = 350, height = 15 }) - - nameGroup:addChild(nameLabel) - nameGroup:addChild(nameInput) - nameGroup:addChild(nameError) - - local emailGroup = Gui.new({ - id = "emailGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 460, - height = 80, - gap = 8, - }) - - local emailLabel = Gui.new({ id = "emailLabel", width = 100, height = 20 }) - local emailInput = Gui.new({ id = "emailInput", width = 400, height = 35 }) - local emailError = Gui.new({ id = "emailError", width = 350, height = 15 }) - - emailGroup:addChild(emailLabel) - emailGroup:addChild(emailInput) - emailGroup:addChild(emailError) - - personalSection:addChild(nameGroup) - personalSection:addChild(emailGroup) - - -- Address section with complex nested structure - local addressSection = Gui.new({ - id = "addressSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 480, - height = 300, - gap = 15, - }) - - local addressTitle = Gui.new({ id = "addressTitle", width = 200, height = 25 }) - addressSection:addChild(addressTitle) - - -- Street address group - local streetGroup = Gui.new({ - id = "streetGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 460, - height = 80, - gap = 8, - }) - - local streetLabel = Gui.new({ id = "streetLabel", width = 120, height = 20 }) - local streetInput = Gui.new({ id = "streetInput", width = 400, height = 35 }) - local streetError = Gui.new({ id = "streetError", width = 350, height = 15 }) - - streetGroup:addChild(streetLabel) - streetGroup:addChild(streetInput) - streetGroup:addChild(streetError) - - -- City/State/Zip compound group - local locationGroup = Gui.new({ - id = "locationGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 460, - height = 120, - gap = 8, - }) - - local locationLabel = Gui.new({ id = "locationLabel", width = 150, height = 20 }) - - local locationInputs = Gui.new({ - id = "locationInputs", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 450, - height = 35, - gap = 10, - }) - - local cityInput = Gui.new({ id = "cityInput", width = 200, height = 35 }) - local stateInput = Gui.new({ id = "stateInput", width = 100, height = 35 }) - local zipInput = Gui.new({ id = "zipInput", width = 120, height = 35 }) - - locationInputs:addChild(cityInput) - locationInputs:addChild(stateInput) - locationInputs:addChild(zipInput) - - local locationErrors = Gui.new({ - id = "locationErrors", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 450, - height = 45, - gap = 3, - }) - - local cityError = Gui.new({ id = "cityError", width = 200, height = 12 }) - local stateError = Gui.new({ id = "stateError", width = 150, height = 12 }) - local zipError = Gui.new({ id = "zipError", width = 180, height = 12 }) - - locationErrors:addChild(cityError) - locationErrors:addChild(stateError) - locationErrors:addChild(zipError) - - locationGroup:addChild(locationLabel) - locationGroup:addChild(locationInputs) - locationGroup:addChild(locationErrors) - - addressSection:addChild(streetGroup) - addressSection:addChild(locationGroup) - - -- Form actions - local formActions = Gui.new({ - id = "formActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - width = 480, - height = 50, - gap = 15, - }) - - local cancelButton = Gui.new({ id = "cancelButton", width = 80, height = 40 }) - local submitButton = Gui.new({ id = "submitButton", width = 100, height = 40 }) - - formActions:addChild(cancelButton) - formActions:addChild(submitButton) - - form:addChild(formHeader) - form:addChild(personalSection) - form:addChild(addressSection) - form:addChild(formActions) - - -- Verify complex form structure - luaunit.assertEquals(formHeader.y, form.y) - luaunit.assertEquals(personalSection.y, formHeader.y + formHeader.height + form.gap) - luaunit.assertEquals(addressSection.y, personalSection.y + personalSection.height + form.gap) - luaunit.assertEquals(formActions.y, addressSection.y + addressSection.height + form.gap) - - -- Verify nested field group positioning - luaunit.assertEquals(nameGroup.y, personalTitle.y + personalTitle.height + personalSection.gap) - luaunit.assertEquals(emailGroup.y, nameGroup.y + nameGroup.height + personalSection.gap) - - -- Verify triple-nested error positioning - luaunit.assertEquals(cityError.y, locationErrors.y) - luaunit.assertEquals(stateError.y, cityError.y + cityError.height + locationErrors.gap) - luaunit.assertEquals(zipError.y, stateError.y + stateError.height + locationErrors.gap) -end - --- Test 20: Calendar/timeline vertical layout with nested events -function TestVerticalFlexDirection:testCalendarTimelineLayout() - local timeline = Gui.new({ - id = "timeline", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 600, - height = 800, - gap = 10, - }) - - -- Create days with events - local daysOfWeek = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" } - - for dayIndex, dayName in ipairs(daysOfWeek) do - local dayContainer = Gui.new({ - id = "day" .. dayIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 580, - height = 140, - gap = 8, - }) - - local dayHeader = Gui.new({ - id = "dayHeader" .. dayIndex, - width = 580, - height = 30, - }) - - local eventsContainer = Gui.new({ - id = "eventsContainer" .. dayIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 560, - height = 100, - gap = 5, - }) - - -- Add events for each day (varying number) - local eventCount = math.min(dayIndex + 1, 4) -- 2-4 events per day - - for eventIndex = 1, eventCount do - local eventItem = Gui.new({ - id = "event" .. dayIndex .. "_" .. eventIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 540, - height = 20, - gap = 2, - }) - - local eventTime = Gui.new({ - id = "eventTime" .. dayIndex .. "_" .. eventIndex, - width = 80, - height = 12, - }) - - local eventTitle = Gui.new({ - id = "eventTitle" .. dayIndex .. "_" .. eventIndex, - width = 400, - height = 15, - }) - - -- Some events have additional details - if eventIndex % 2 == 0 then - local eventDetails = Gui.new({ - id = "eventDetails" .. dayIndex .. "_" .. eventIndex, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 380, - height = 25, - gap = 2, - }) - - local eventLocation = Gui.new({ - id = "eventLocation" .. dayIndex .. "_" .. eventIndex, - width = 200, - height = 10, - }) - - local eventAttendees = Gui.new({ - id = "eventAttendees" .. dayIndex .. "_" .. eventIndex, - width = 300, - height = 10, - }) - - eventDetails:addChild(eventLocation) - eventDetails:addChild(eventAttendees) - - eventItem:addChild(eventTime) - eventItem:addChild(eventTitle) - eventItem:addChild(eventDetails) - eventItem.height = 40 -- Adjust height for detailed events - eventItem.units.height = { value = 40, unit = "px" } -- Keep units in sync - else - eventItem:addChild(eventTime) - eventItem:addChild(eventTitle) - end - - eventsContainer:addChild(eventItem) - end - - dayContainer:addChild(dayHeader) - dayContainer:addChild(eventsContainer) - timeline:addChild(dayContainer) - end - - -- Verify timeline structure - local firstDay = timeline.children[1] - local secondDay = timeline.children[2] - local thirdDay = timeline.children[3] - - luaunit.assertEquals(firstDay.y, timeline.y) - luaunit.assertEquals(secondDay.y, firstDay.y + firstDay.height + timeline.gap) - luaunit.assertEquals(thirdDay.y, secondDay.y + secondDay.height + timeline.gap) - - -- Verify nested event positioning within first day - local firstDayEvents = firstDay.children[2] -- eventsContainer - local firstEvent = firstDayEvents.children[1] - local secondEvent = firstDayEvents.children[2] - - luaunit.assertEquals(firstEvent.y, firstDayEvents.y) - luaunit.assertEquals(secondEvent.y, firstEvent.y + firstEvent.height + firstDayEvents.gap) -end - --- Test 21: Complex dashboard widget layout -function TestVerticalFlexDirection:testComplexDashboardWidgetLayout() - local dashboard = Gui.new({ - id = "dashboard", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 800, - height = 1000, - gap = 25, - }) - - -- Dashboard header with breadcrumbs - local dashboardHeader = Gui.new({ - id = "dashboardHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 780, - height = 100, - gap = 12, - }) - - local breadcrumbs = Gui.new({ - id = "breadcrumbs", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 400, - height = 20, - gap = 8, - }) - - for i = 1, 4 do - local crumb = Gui.new({ - id = "crumb" .. i, - width = 80, - height = 18, - }) - breadcrumbs:addChild(crumb) - end - - local pageTitle = Gui.new({ id = "pageTitle", width = 300, height = 40 }) - local pageActions = Gui.new({ - id = "pageActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 250, - height = 30, - gap = 10, - }) - - local refreshButton = Gui.new({ id = "refreshButton", width = 70, height = 28 }) - local exportButton = Gui.new({ id = "exportButton", width = 80, height = 28 }) - local settingsButton = Gui.new({ id = "settingsButton", width = 75, height = 28 }) - - pageActions:addChild(refreshButton) - pageActions:addChild(exportButton) - pageActions:addChild(settingsButton) - - dashboardHeader:addChild(breadcrumbs) - dashboardHeader:addChild(pageTitle) - dashboardHeader:addChild(pageActions) - - -- Widget grid rows (simulated as vertical sections) - local topWidgetRow = Gui.new({ - id = "topWidgetRow", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 780, - height = 250, - gap = 20, - }) - - -- Metric widgets - for i = 1, 3 do - local metricWidget = Gui.new({ - id = "metricWidget" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 240, - height = 240, - gap = 10, - }) - - local widgetHeader = Gui.new({ - id = "widgetHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 220, - height = 30, - gap = 5, - }) - - local widgetTitle = Gui.new({ id = "widgetTitle" .. i, width = 150, height = 25 }) - local widgetMenu = Gui.new({ id = "widgetMenu" .. i, width = 20, height = 20 }) - - widgetHeader:addChild(widgetTitle) - widgetHeader:addChild(widgetMenu) - - local widgetContent = Gui.new({ - id = "widgetContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 220, - height = 150, - gap = 8, - }) - - local metricValue = Gui.new({ id = "metricValue" .. i, width = 120, height = 50 }) - local metricLabel = Gui.new({ id = "metricLabel" .. i, width = 100, height = 20 }) - local metricTrend = Gui.new({ id = "metricTrend" .. i, width = 80, height = 15 }) - - widgetContent:addChild(metricValue) - widgetContent:addChild(metricLabel) - widgetContent:addChild(metricTrend) - - local widgetFooter = Gui.new({ - id = "widgetFooter" .. i, - width = 220, - height = 25, - }) - - metricWidget:addChild(widgetHeader) - metricWidget:addChild(widgetContent) - metricWidget:addChild(widgetFooter) - - topWidgetRow:addChild(metricWidget) - end - - -- Chart widget section - local chartSection = Gui.new({ - id = "chartSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 780, - height = 400, - gap = 15, - }) - - local chartHeader = Gui.new({ - id = "chartHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 760, - height = 40, - gap = 10, - }) - - local chartTitle = Gui.new({ id = "chartTitle", width = 200, height = 35 }) - local chartControls = Gui.new({ - id = "chartControls", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 300, - height = 35, - gap = 8, - }) - - local timeRangeSelect = Gui.new({ id = "timeRangeSelect", width = 120, height = 30 }) - local chartTypeSelect = Gui.new({ id = "chartTypeSelect", width = 100, height = 30 }) - local fullscreenButton = Gui.new({ id = "fullscreenButton", width = 60, height = 30 }) - - chartControls:addChild(timeRangeSelect) - chartControls:addChild(chartTypeSelect) - chartControls:addChild(fullscreenButton) - - chartHeader:addChild(chartTitle) - chartHeader:addChild(chartControls) - - local chartArea = Gui.new({ id = "chartArea", width = 760, height = 300 }) - - local chartLegend = Gui.new({ - id = "chartLegend", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 600, - height = 30, - gap = 15, - }) - - for i = 1, 4 do - local legendItem = Gui.new({ - id = "legendItem" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 120, - height = 25, - gap = 5, - }) - - local legendColor = Gui.new({ id = "legendColor" .. i, width = 15, height = 15 }) - local legendLabel = Gui.new({ id = "legendLabel" .. i, width = 95, height = 20 }) - - legendItem:addChild(legendColor) - legendItem:addChild(legendLabel) - chartLegend:addChild(legendItem) - end - - chartSection:addChild(chartHeader) - chartSection:addChild(chartArea) - chartSection:addChild(chartLegend) - - -- Table widget section - local tableSection = Gui.new({ - id = "tableSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 780, - height = 300, - gap = 10, - }) - - local tableHeader = Gui.new({ - id = "tableHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 760, - height = 35, - }) - - local tableTitle = Gui.new({ id = "tableTitle", width = 200, height = 30 }) - local tableSearch = Gui.new({ id = "tableSearch", width = 250, height = 30 }) - - tableHeader:addChild(tableTitle) - tableHeader:addChild(tableSearch) - - local tableContent = Gui.new({ - id = "tableContent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 760, - height = 200, - gap = 2, - }) - - local tableHeaderRow = Gui.new({ id = "tableHeaderRow", width = 760, height = 35 }) - tableContent:addChild(tableHeaderRow) - - -- Table rows - for i = 1, 6 do - local tableRow = Gui.new({ - id = "tableRow" .. i, - width = 760, - height = 25, - }) - tableContent:addChild(tableRow) - end - - local tablePagination = Gui.new({ - id = "tablePagination", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 300, - height = 40, - gap = 5, - }) - - for i = 1, 5 do - local pageButton = Gui.new({ - id = "pageButton" .. i, - width = 30, - height = 30, - }) - tablePagination:addChild(pageButton) - end - - tableSection:addChild(tableHeader) - tableSection:addChild(tableContent) - tableSection:addChild(tablePagination) - - dashboard:addChild(dashboardHeader) - dashboard:addChild(topWidgetRow) - dashboard:addChild(chartSection) - dashboard:addChild(tableSection) - - -- Verify complex dashboard structure - luaunit.assertEquals(dashboardHeader.y, dashboard.y) - luaunit.assertEquals(topWidgetRow.y, dashboardHeader.y + dashboardHeader.height + dashboard.gap) - luaunit.assertEquals(chartSection.y, topWidgetRow.y + topWidgetRow.height + dashboard.gap) - luaunit.assertEquals(tableSection.y, chartSection.y + chartSection.height + dashboard.gap) - - -- Verify nested widget structure - local firstWidget = topWidgetRow.children[1] - local widgetHeader = firstWidget.children[1] - local widgetContent = firstWidget.children[2] - local widgetFooter = firstWidget.children[3] - - luaunit.assertEquals(widgetHeader.y, firstWidget.y) - luaunit.assertEquals(widgetContent.y, widgetHeader.y + widgetHeader.height + firstWidget.gap) - luaunit.assertEquals(widgetFooter.y, widgetContent.y + widgetContent.height + firstWidget.gap) - - -- Verify chart legend item structure - local firstLegendItem = chartLegend.children[1] - local legendColor = firstLegendItem.children[1] - local legendLabel = firstLegendItem.children[2] - - luaunit.assertEquals(legendColor.x, firstLegendItem.x) - luaunit.assertEquals(legendLabel.x, legendColor.x + legendColor.width + firstLegendItem.gap) -end - --- Test 22: Mobile-style vertical stack with pull-to-refresh and infinite scroll -function TestVerticalFlexDirection:testMobileVerticalStackLayout() - local mobileContainer = Gui.new({ - id = "mobileContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 375, -- iPhone-style width - height = 812, -- iPhone-style height - gap = 0, - }) - - -- Status bar - local statusBar = Gui.new({ - id = "statusBar", - width = 375, - height = 44, - }) - - -- Header with pull-to-refresh area - local header = Gui.new({ - id = "header", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 375, - height = 100, - gap = 5, - }) - - local pullToRefresh = Gui.new({ - id = "pullToRefresh", - width = 375, - height = 30, - }) - - local navigationBar = Gui.new({ - id = "navigationBar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 375, - height = 60, - gap = 10, - }) - - local backButton = Gui.new({ id = "backButton", width = 40, height = 40 }) - local headerTitle = Gui.new({ id = "headerTitle", width = 200, height = 35 }) - local moreButton = Gui.new({ id = "moreButton", width = 40, height = 40 }) - - navigationBar:addChild(backButton) - navigationBar:addChild(headerTitle) - navigationBar:addChild(moreButton) - - header:addChild(pullToRefresh) - header:addChild(navigationBar) - - -- Content area with scrollable list - local contentArea = Gui.new({ - id = "contentArea", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 375, - height = 600, - gap = 1, - }) - - -- Feed items with varying complexity - for i = 1, 8 do - local feedItem = Gui.new({ - id = "feedItem" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 365, - height = i % 3 == 0 and 200 or 120, -- Some items are taller - gap = 8, - }) - - local itemHeader = Gui.new({ - id = "itemHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 355, - height = 50, - gap = 12, - }) - - local avatar = Gui.new({ id = "avatar" .. i, width = 40, height = 40 }) - - local userInfo = Gui.new({ - id = "userInfo" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 200, - height = 40, - gap = 3, - }) - - local username = Gui.new({ id = "username" .. i, width = 150, height = 18 }) - local timestamp = Gui.new({ id = "timestamp" .. i, width = 100, height = 14 }) - - userInfo:addChild(username) - userInfo:addChild(timestamp) - - local itemMenu = Gui.new({ id = "itemMenu" .. i, width = 30, height = 30 }) - - itemHeader:addChild(avatar) - itemHeader:addChild(userInfo) - itemHeader:addChild(itemMenu) - - local itemContent = Gui.new({ - id = "itemContent" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 355, - height = feedItem.height - 50 - 8, -- Remaining height after header - gap = 5, - }) - - local textContent = Gui.new({ - id = "textContent" .. i, - width = 345, - height = 25, - }) - - itemContent:addChild(textContent) - - -- Some items have media - if i % 3 == 0 then - local mediaContainer = Gui.new({ - id = "mediaContainer" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 345, - height = 100, - gap = 3, - }) - - local media = Gui.new({ id = "media" .. i, width = 345, height = 80 }) - local mediaCaption = Gui.new({ id = "mediaCaption" .. i, width = 300, height = 15 }) - - mediaContainer:addChild(media) - mediaContainer:addChild(mediaCaption) - itemContent:addChild(mediaContainer) - end - - -- Interaction bar - local interactionBar = Gui.new({ - id = "interactionBar" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 345, - height = 40, - gap = 15, - }) - - local leftActions = Gui.new({ - id = "leftActions" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 150, - height = 35, - gap = 20, - }) - - local likeButton = Gui.new({ id = "likeButton" .. i, width = 35, height = 30 }) - local commentButton = Gui.new({ id = "commentButton" .. i, width = 35, height = 30 }) - local shareButton = Gui.new({ id = "shareButton" .. i, width = 35, height = 30 }) - - leftActions:addChild(likeButton) - leftActions:addChild(commentButton) - leftActions:addChild(shareButton) - - local saveButton = Gui.new({ id = "saveButton" .. i, width = 35, height = 30 }) - - interactionBar:addChild(leftActions) - interactionBar:addChild(saveButton) - - itemContent:addChild(interactionBar) - - feedItem:addChild(itemHeader) - feedItem:addChild(itemContent) - contentArea:addChild(feedItem) - end - - -- Bottom tab bar - local tabBar = Gui.new({ - id = "tabBar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.CENTER, - width = 375, - height = 83, -- Includes safe area - gap = 0, - }) - - local tabItems = { "home", "search", "create", "activity", "profile" } - for i, tabName in ipairs(tabItems) do - local tab = Gui.new({ - id = "tab" .. tabName, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - width = 60, - height = 60, - gap = 3, - }) - - local tabIcon = Gui.new({ id = "tabIcon" .. tabName, width = 24, height = 24 }) - local tabLabel = Gui.new({ id = "tabLabel" .. tabName, width = 50, height = 12 }) - - tab:addChild(tabIcon) - tab:addChild(tabLabel) - tabBar:addChild(tab) - end - - mobileContainer:addChild(statusBar) - mobileContainer:addChild(header) - mobileContainer:addChild(contentArea) - mobileContainer:addChild(tabBar) - - -- Verify mobile layout structure - luaunit.assertEquals(statusBar.y, mobileContainer.y) - luaunit.assertEquals(header.y, statusBar.y + statusBar.height) - luaunit.assertEquals(contentArea.y, header.y + header.height) - luaunit.assertEquals(tabBar.y, contentArea.y + contentArea.height) - - -- Verify feed item structure - local firstFeedItem = contentArea.children[1] - local secondFeedItem = contentArea.children[2] - - luaunit.assertEquals(firstFeedItem.y, contentArea.y) - luaunit.assertEquals(secondFeedItem.y, firstFeedItem.y + firstFeedItem.height + contentArea.gap) - - -- Verify nested interaction structure - local itemHeader = firstFeedItem.children[1] - local itemContent = firstFeedItem.children[2] - local interactionBar = itemContent.children[#itemContent.children] -- Last child - - luaunit.assertEquals(itemHeader.y, firstFeedItem.y) - luaunit.assertEquals(itemContent.y, itemHeader.y + itemHeader.height + firstFeedItem.gap) - - -- Verify tab structure - local firstTab = tabBar.children[1] - local tabIcon = firstTab.children[1] - local tabLabel = firstTab.children[2] - - luaunit.assertEquals(tabIcon.y, firstTab.y) - luaunit.assertEquals(tabLabel.y, tabIcon.y + tabIcon.height + firstTab.gap) -end - --- Run the tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/05_justify_content_tests.lua b/testing/__tests__/05_justify_content_tests.lua deleted file mode 100644 index b3a2df8..0000000 --- a/testing/__tests__/05_justify_content_tests.lua +++ /dev/null @@ -1,1663 +0,0 @@ --- 05. Justify Content Alignment Tests --- Tests for FlexLove justify content functionality - --- Load test framework and dependencies -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - --- Import required enums -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems - --- Test class for justify content functionality -TestJustifyContent = {} - -function TestJustifyContent:setUp() - -- Clear any previous state if needed - Gui.destroy() -end - -function TestJustifyContent:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Horizontal Flex with JustifyContent.FLEX_START -function TestJustifyContent:testHorizontalFlexJustifyContentFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 70, - height = 35, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- With FLEX_START, children should be positioned from the start (left) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 50) - luaunit.assertEquals(child3.x, 110) - - -- Y positions should be 0 (aligned to top by default) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child3.y, 0) -end - --- Test 2: Horizontal Flex with JustifyContent.CENTER -function TestJustifyContent:testHorizontalFlexJustifyContentCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Center offset: 190 / 2 = 95 - luaunit.assertEquals(child1.x, 95) - luaunit.assertEquals(child2.x, 145) -end - --- Test 3: Horizontal Flex with JustifyContent.FLEX_END -function TestJustifyContent:testHorizontalFlexJustifyContentFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Children should be positioned from the end - luaunit.assertEquals(child1.x, 190) - luaunit.assertEquals(child2.x, 240) -end - --- Test 4: Horizontal Flex with JustifyContent.SPACE_BETWEEN -function TestJustifyContent:testHorizontalFlexJustifyContentSpaceBetween() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 40, - height = 35, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- Total child width: 50 + 60 + 40 = 150 - -- Available space: 300 - 150 = 150 - -- Space between 3 children: 150 / 2 = 75 - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 125) -- 0 + 50 + 75 - luaunit.assertEquals(child3.x, 260) -- 125 + 60 + 75 -end - --- Test 5: Horizontal Flex with JustifyContent.SPACE_AROUND -function TestJustifyContent:testHorizontalFlexJustifyContentSpaceAround() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Space around each: 190 / 2 = 95 (FlexLove divides by number of children) - -- Start position: 95 / 2 = 47.5 - -- Item spacing: 0 + 95 = 95 - luaunit.assertEquals(child1.x, 47.5) - luaunit.assertEquals(child2.x, 192.5) -- 47.5 + 50 + 95 -end - --- Test 6: Horizontal Flex with JustifyContent.SPACE_EVENLY -function TestJustifyContent:testHorizontalFlexJustifyContentSpaceEvenly() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Space evenly: 190 / 3 = 63.33... (equal spaces at start, between, and end) - local expectedSpace = 190 / 3 - luaunit.assertAlmostEquals(child1.x, expectedSpace, 0.01) - luaunit.assertAlmostEquals(child2.x, expectedSpace + 50 + expectedSpace, 0.01) -end - --- Test 7: Vertical Flex with JustifyContent.FLEX_START -function TestJustifyContent:testVerticalFlexJustifyContentFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 70, - height = 35, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- With FLEX_START, children should be positioned from the start (top) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 30) - luaunit.assertEquals(child3.y, 70) - - -- X positions should be 0 (aligned to left by default) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child3.x, 0) -end - --- Test 8: Vertical Flex with JustifyContent.CENTER -function TestJustifyContent:testVerticalFlexJustifyContentCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Center offset: 230 / 2 = 115 - luaunit.assertEquals(child1.y, 115) - luaunit.assertEquals(child2.y, 145) -end - --- Test 9: Vertical Flex with JustifyContent.FLEX_END -function TestJustifyContent:testVerticalFlexJustifyContentFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Children should be positioned from the end - luaunit.assertEquals(child1.y, 230) - luaunit.assertEquals(child2.y, 260) -end - --- Test 10: Vertical Flex with JustifyContent.SPACE_BETWEEN -function TestJustifyContent:testVerticalFlexJustifyContentSpaceBetween() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 40, - height = 35, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- Total child height: 30 + 40 + 35 = 105 - -- Available space: 300 - 105 = 195 - -- Space between 3 children: 195 / 2 = 97.5 - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 127.5) -- 0 + 30 + 97.5 - luaunit.assertEquals(child3.y, 265) -- 127.5 + 40 + 97.5 -end - --- Test 11: Vertical Flex with JustifyContent.SPACE_AROUND -function TestJustifyContent:testVerticalFlexJustifyContentSpaceAround() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_AROUND, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Space around each: 230 / 2 = 115 (FlexLove divides by number of children) - -- Start position: 115 / 2 = 57.5 - -- Item spacing: 0 + 115 = 115 - luaunit.assertEquals(child1.y, 57.5) - luaunit.assertEquals(child2.y, 202.5) -- 57.5 + 30 + 115 -end - --- Test 12: Vertical Flex with JustifyContent.SPACE_EVENLY -function TestJustifyContent:testVerticalFlexJustifyContentSpaceEvenly() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_EVENLY, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Total child height: 30 + 40 = 70 - -- Available space: 300 - 70 = 230 - -- Space evenly: 230 / 3 = 76.67... (equal spaces at start, between, and end) - local expectedSpace = 230 / 3 - luaunit.assertAlmostEquals(child1.y, expectedSpace, 0.01) - luaunit.assertAlmostEquals(child2.y, expectedSpace + 30 + expectedSpace, 0.01) -end - --- Test 13: JustifyContent with Single Child -function TestJustifyContent:testJustifyContentWithSingleChild() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - - -- With single child and CENTER, child should be centered - -- Available space: 300 - 50 = 250 - -- Center offset: 250 / 2 = 125 - luaunit.assertEquals(child1.x, 125) -end - --- Test 14: JustifyContent with No Available Space -function TestJustifyContent:testJustifyContentWithNoAvailableSpace() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 100, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 50, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children exactly fill container width (100) - -- Should fall back to FLEX_START behavior - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 50) -end - --- Test 15: JustifyContent Preservation with Parent Coordinates -function TestJustifyContent:testJustifyContentWithParentCoordinates() - local parent = Gui.new({ - id = "parent", - x = 50, - y = 30, - width = 400, - height = 200, - positioning = Positioning.ABSOLUTE, - }) - - local container = Gui.new({ - id = "container", - x = 20, - y = 10, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - gap = 0, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - parent:addChild(container) - container:addChild(child1) - container:addChild(child2) - - -- Container should maintain its own coordinates since parent is ABSOLUTE - luaunit.assertEquals(container.x, 20) -- container keeps its own x - luaunit.assertEquals(container.y, 10) -- container keeps its own y - - -- Children should be centered within container coordinate system - -- Total child width: 50 + 60 = 110 - -- Available space: 300 - 110 = 190 - -- Center offset: 190 / 2 = 95 - -- Children are positioned in absolute coordinates: container.x + offset - luaunit.assertEquals(child1.x, 115) -- container.x(20) + center_offset(95) - luaunit.assertEquals(child2.x, 165) -- container.x(20) + center_offset(95) + child1.width(50) -end - --- Test 16: Complex navigation bar with space-between and nested elements -function TestJustifyContent:testComplexNavigationBarLayout() - local navbar = Gui.new({ - id = "navbar", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - x = 0, - y = 0, - width = 1200, - height = 80, - gap = 0, - }) - - -- Logo section - local logoSection = Gui.new({ - id = "logoSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 250, - height = 70, - gap = 15, - }) - - local logo = Gui.new({ id = "logo", width = 60, height = 50 }) - local brandName = Gui.new({ id = "brandName", width = 120, height = 30 }) - local beta = Gui.new({ id = "beta", width = 40, height = 20 }) - - logoSection:addChild(logo) - logoSection:addChild(brandName) - logoSection:addChild(beta) - - -- Navigation menu - local navMenu = Gui.new({ - id = "navMenu", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 500, - height = 70, - gap = 30, - }) - - local menuItems = { "Home", "Products", "Solutions", "About", "Contact" } - for i, itemName in ipairs(menuItems) do - local menuItem = Gui.new({ - id = "menuItem" .. itemName, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 80, - height = 60, - gap = 5, - }) - - local itemText = Gui.new({ id = "itemText" .. itemName, width = 70, height = 20 }) - local itemDot = Gui.new({ id = "itemDot" .. itemName, width = 6, height = 6 }) - - menuItem:addChild(itemText) - menuItem:addChild(itemDot) - navMenu:addChild(menuItem) - end - - -- Action buttons section - local actionsSection = Gui.new({ - id = "actionsSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 300, - height = 70, - gap = 12, - }) - - local searchButton = Gui.new({ id = "searchButton", width = 40, height = 40 }) - local loginButton = Gui.new({ id = "loginButton", width = 80, height = 35 }) - local signupButton = Gui.new({ id = "signupButton", width = 100, height = 40 }) - local mobileMenu = Gui.new({ id = "mobileMenu", width = 35, height = 35 }) - - actionsSection:addChild(searchButton) - actionsSection:addChild(loginButton) - actionsSection:addChild(signupButton) - actionsSection:addChild(mobileMenu) - - navbar:addChild(logoSection) - navbar:addChild(navMenu) - navbar:addChild(actionsSection) - - -- Verify space-between layout: elements should be distributed with equal space between - luaunit.assertEquals(logoSection.x, navbar.x) - luaunit.assertEquals(actionsSection.x + actionsSection.width, navbar.x + navbar.width) - - -- Center menu should be positioned between logo and actions - local expectedMenuX = logoSection.x + logoSection.width + ((navbar.width - logoSection.width - navMenu.width - actionsSection.width) / 2) - luaunit.assertEquals(navMenu.x, expectedMenuX) - - -- Verify nested center alignment in menu items - local firstMenuItem = navMenu.children[1] - local menuItemsWidth = 5 * 80 + 4 * 30 -- 5 items × 80px + 4 gaps × 30px = 520px - local menuStartX = navMenu.x + (navMenu.width - menuItemsWidth) / 2 - luaunit.assertEquals(firstMenuItem.x, menuStartX) - - -- Verify flex-end alignment in actions - local expectedActionsContentWidth = 40 + 80 + 100 + 35 + 3 * 12 -- widths + gaps = 291px - local expectedActionsStartX = actionsSection.x + actionsSection.width - expectedActionsContentWidth - luaunit.assertEquals(searchButton.x, expectedActionsStartX) -end - --- Test 17: Dashboard metrics layout with space-around -function TestJustifyContent:testDashboardMetricsSpaceAround() - local metricsContainer = Gui.new({ - id = "metricsContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - x = 0, - y = 0, - width = 1000, - height = 200, - gap = 0, - }) - - -- Create metric cards with different complexities - local metrics = { - { title = "Revenue", hasChart = true, hasTrend = true }, - { title = "Users", hasChart = false, hasTrend = true }, - { title = "Orders", hasChart = true, hasTrend = false }, - { title = "Growth", hasChart = false, hasTrend = true }, - } - - for i, metric in ipairs(metrics) do - local metricCard = Gui.new({ - id = "metricCard" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 220, - height = 180, - gap = 10, - }) - - -- Card header - local cardHeader = Gui.new({ - id = "cardHeader" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 200, - height = 30, - gap = 5, - }) - - local metricTitle = Gui.new({ id = "metricTitle" .. i, width = 100, height = 25 }) - local metricIcon = Gui.new({ id = "metricIcon" .. i, width = 24, height = 24 }) - - cardHeader:addChild(metricTitle) - cardHeader:addChild(metricIcon) - - -- Value section - local valueSection = Gui.new({ - id = "valueSection" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 200, - height = 80, - gap = 8, - }) - - local mainValue = Gui.new({ id = "mainValue" .. i, width = 120, height = 40 }) - local valueLabel = Gui.new({ id = "valueLabel" .. i, width = 80, height = 16 }) - - valueSection:addChild(mainValue) - valueSection:addChild(valueLabel) - - if metric.hasTrend then - local trendIndicator = Gui.new({ id = "trendIndicator" .. i, width = 60, height = 20 }) - valueSection:addChild(trendIndicator) - valueSection.height = 100 - end - - -- Bottom section (chart or additional info) - local bottomSection = Gui.new({ - id = "bottomSection" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = metric.hasChart and JustifyContent.CENTER or JustifyContent.SPACE_EVENLY, - alignItems = AlignItems.CENTER, - width = 200, - height = 50, - gap = 5, - }) - - if metric.hasChart then - local miniChart = Gui.new({ id = "miniChart" .. i, width = 150, height = 40 }) - bottomSection:addChild(miniChart) - else - -- Add comparison indicators - local prevPeriod = Gui.new({ id = "prevPeriod" .. i, width = 60, height = 20 }) - local comparison = Gui.new({ id = "comparison" .. i, width = 40, height = 20 }) - local target = Gui.new({ id = "target" .. i, width = 60, height = 20 }) - - bottomSection:addChild(prevPeriod) - bottomSection:addChild(comparison) - bottomSection:addChild(target) - end - - metricCard:addChild(cardHeader) - metricCard:addChild(valueSection) - metricCard:addChild(bottomSection) - metricsContainer:addChild(metricCard) - end - - -- Verify space-around distribution - local totalCardsWidth = 4 * 220 -- 880px - local availableSpace = 1000 - 880 -- 120px - local spaceAroundEach = availableSpace / 4 -- 30px around each card - local spaceAtEnds = spaceAroundEach / 2 -- 15px at each end - - local firstCard = metricsContainer.children[1] - local secondCard = metricsContainer.children[2] - - luaunit.assertEquals(firstCard.x, spaceAtEnds) - luaunit.assertEquals(secondCard.x, spaceAtEnds + 220 + spaceAroundEach) - - -- Verify nested space-between in card headers - local firstCardHeader = firstCard.children[1] - local headerTitle = firstCardHeader.children[1] - local headerIcon = firstCardHeader.children[2] - - luaunit.assertEquals(headerTitle.x, firstCardHeader.x) - luaunit.assertEquals(headerIcon.x + headerIcon.width, firstCardHeader.x + firstCardHeader.width) -end - --- Test 18: Complex form layout with varied justify content -function TestJustifyContent:testComplexFormJustifyContentLayout() - local formContainer = Gui.new({ - id = "formContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 600, - height = 800, - gap = 25, - }) - - -- Form header with space-between - local formHeader = Gui.new({ - id = "formHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 580, - height = 60, - gap = 0, - }) - - local headerLeft = Gui.new({ - id = "headerLeft", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 200, - height = 50, - gap = 5, - }) - - local formTitle = Gui.new({ id = "formTitle", width = 180, height = 30 }) - local formSubtitle = Gui.new({ id = "formSubtitle", width = 200, height = 15 }) - - headerLeft:addChild(formTitle) - headerLeft:addChild(formSubtitle) - - local headerRight = Gui.new({ - id = "headerRight", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 150, - height = 50, - gap = 10, - }) - - local helpButton = Gui.new({ id = "helpButton", width = 30, height = 30 }) - local closeButton = Gui.new({ id = "closeButton", width = 30, height = 30 }) - - headerRight:addChild(helpButton) - headerRight:addChild(closeButton) - - formHeader:addChild(headerLeft) - formHeader:addChild(headerRight) - - -- Field sections with different alignments - local personalSection = Gui.new({ - id = "personalSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 580, - height = 250, - gap = 15, - }) - - local sectionTitle = Gui.new({ - id = "sectionTitle", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - width = 580, - height = 30, - gap = 10, - }) - - local titleText = Gui.new({ id = "titleText", width = 150, height = 25 }) - local titleIcon = Gui.new({ id = "titleIcon", width = 20, height = 20 }) - - sectionTitle:addChild(titleText) - sectionTitle:addChild(titleIcon) - - -- Field rows with different layouts - local nameRow = Gui.new({ - id = "nameRow", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 580, - height = 60, - gap = 15, - }) - - local firstNameGroup = Gui.new({ - id = "firstNameGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 275, - height = 55, - gap = 5, - }) - - local firstNameLabel = Gui.new({ id = "firstNameLabel", width = 80, height = 20 }) - local firstNameInput = Gui.new({ id = "firstNameInput", width = 275, height = 30 }) - - firstNameGroup:addChild(firstNameLabel) - firstNameGroup:addChild(firstNameInput) - - local lastNameGroup = Gui.new({ - id = "lastNameGroup", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 275, - height = 55, - gap = 5, - }) - - local lastNameLabel = Gui.new({ id = "lastNameLabel", width = 80, height = 20 }) - local lastNameInput = Gui.new({ id = "lastNameInput", width = 275, height = 30 }) - - lastNameGroup:addChild(lastNameLabel) - lastNameGroup:addChild(lastNameInput) - - nameRow:addChild(firstNameGroup) - nameRow:addChild(lastNameGroup) - - -- Contact preferences with space-evenly - local contactRow = Gui.new({ - id = "contactRow", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - alignItems = AlignItems.CENTER, - width = 580, - height = 50, - gap = 0, - }) - - local emailOption = Gui.new({ - id = "emailOption", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 120, - height = 40, - gap = 8, - }) - - Gui.new({ parent = emailOption, id = "emailCheckbox", width = 20, height = 20 }) - Gui.new({ parent = emailOption, id = "emailLabel", width = 60, height = 18 }) - - local phoneOption = Gui.new({ - id = "phoneOption", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 120, - height = 40, - gap = 8, - }) - - local phoneCheckbox = Gui.new({ id = "phoneCheckbox", width = 20, height = 20 }) - local phoneLabel = Gui.new({ id = "phoneLabel", width = 60, height = 18 }) - - phoneOption:addChild(phoneCheckbox) - phoneOption:addChild(phoneLabel) - - local smsOption = Gui.new({ - id = "smsOption", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 100, - height = 40, - gap = 8, - }) - - local smsCheckbox = Gui.new({ id = "smsCheckbox", width = 20, height = 20 }) - local smsLabel = Gui.new({ id = "smsLabel", width = 50, height = 18 }) - - smsOption:addChild(smsCheckbox) - smsOption:addChild(smsLabel) - - contactRow:addChild(emailOption) - contactRow:addChild(phoneOption) - contactRow:addChild(smsOption) - - personalSection:addChild(sectionTitle) - personalSection:addChild(nameRow) - personalSection:addChild(contactRow) - - -- Form actions with varied justification - local actionsSection = Gui.new({ - id = "actionsSection", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 580, - height = 120, - gap = 20, - }) - - local primaryActions = Gui.new({ - id = "primaryActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 580, - height = 45, - gap = 15, - }) - - local cancelButton = Gui.new({ id = "cancelButton", width = 80, height = 40 }) - local saveButton = Gui.new({ id = "saveButton", width = 100, height = 40 }) - local submitButton = Gui.new({ id = "submitButton", width = 120, height = 40 }) - - primaryActions:addChild(cancelButton) - primaryActions:addChild(saveButton) - primaryActions:addChild(submitButton) - - local secondaryActions = Gui.new({ - id = "secondaryActions", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 580, - height = 35, - gap = 25, - }) - - local resetButton = Gui.new({ id = "resetButton", width = 70, height = 30 }) - local previewButton = Gui.new({ id = "previewButton", width = 80, height = 30 }) - - secondaryActions:addChild(resetButton) - secondaryActions:addChild(previewButton) - - actionsSection:addChild(primaryActions) - actionsSection:addChild(secondaryActions) - - formContainer:addChild(formHeader) - formContainer:addChild(personalSection) - formContainer:addChild(actionsSection) - - -- Verify complex justify content behaviors - -- Header space-between - luaunit.assertEquals(headerLeft.x, formHeader.x) - luaunit.assertEquals(headerRight.x + headerRight.width, formHeader.x + formHeader.width) - - -- Name row space-between - luaunit.assertEquals(firstNameGroup.x, nameRow.x) - luaunit.assertEquals(lastNameGroup.x + lastNameGroup.width, nameRow.x + nameRow.width) - - -- Contact preferences space-evenly - local totalOptionsWidth = 120 + 120 + 100 -- 340px - local availableSpace = 580 - 340 -- 240px - local evenSpacing = 240 / 4 -- 60px (spaces before, between, between, after) - - luaunit.assertEquals(emailOption.x, contactRow.x + evenSpacing) - luaunit.assertEquals(phoneOption.x, emailOption.x + emailOption.width + evenSpacing) - luaunit.assertEquals(smsOption.x, phoneOption.x + phoneOption.width + evenSpacing) - - -- Primary actions flex-end - local totalPrimaryWidth = 80 + 100 + 120 + 2 * 15 -- 330px including gaps - local expectedPrimaryStartX = primaryActions.x + primaryActions.width - totalPrimaryWidth - luaunit.assertEquals(cancelButton.x, expectedPrimaryStartX) - - -- Secondary actions center - local totalSecondaryWidth = 70 + 80 + 25 -- 175px including gap - local expectedSecondaryStartX = secondaryActions.x + (secondaryActions.width - totalSecondaryWidth) / 2 - luaunit.assertEquals(resetButton.x, expectedSecondaryStartX) -end - --- Test 19: Grid-like layout with justify content variations -function TestJustifyContent:testGridLayoutJustifyContentVariations() - local gridContainer = Gui.new({ - id = "gridContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - x = 0, - y = 0, - width = 800, - height = 600, - gap = 20, - }) - - -- Row 1: Space-between - local row1 = Gui.new({ - id = "row1", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 780, - height = 120, - gap = 0, - }) - - for i = 1, 4 do - local card = Gui.new({ - id = "row1Card" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 150, - height = 100, - gap = 10, - }) - - local cardIcon = Gui.new({ id = "row1Icon" .. i, width = 40, height = 40 }) - local cardLabel = Gui.new({ id = "row1Label" .. i, width = 100, height = 20 }) - - card:addChild(cardIcon) - card:addChild(cardLabel) - row1:addChild(card) - end - - -- Row 2: Space-around - local row2 = Gui.new({ - id = "row2", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - width = 780, - height = 120, - gap = 0, - }) - - for i = 1, 3 do - local card = Gui.new({ - id = "row2Card" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 200, - height = 100, - gap = 5, - }) - - local cardHeader = Gui.new({ - id = "row2Header" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 180, - height = 25, - gap = 0, - }) - - local headerTitle = Gui.new({ id = "row2HeaderTitle" .. i, width = 100, height = 20 }) - local headerIcon = Gui.new({ id = "row2HeaderIcon" .. i, width = 20, height = 20 }) - - cardHeader:addChild(headerTitle) - cardHeader:addChild(headerIcon) - - local cardContent = Gui.new({ id = "row2Content" .. i, width = 180, height = 40 }) - local cardFooter = Gui.new({ id = "row2Footer" .. i, width = 180, height = 20 }) - - card:addChild(cardHeader) - card:addChild(cardContent) - card:addChild(cardFooter) - row2:addChild(card) - end - - -- Row 3: Space-evenly - local row3 = Gui.new({ - id = "row3", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - width = 780, - height = 120, - gap = 0, - }) - - for i = 1, 5 do - local item = Gui.new({ - id = "row3Item" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 100, - height = 100, - gap = 5, - }) - - local itemValue = Gui.new({ id = "row3Value" .. i, width = 60, height = 30 }) - local itemLabel = Gui.new({ id = "row3Label" .. i, width = 80, height = 15 }) - local itemTrend = Gui.new({ id = "row3Trend" .. i, width = 40, height = 12 }) - - item:addChild(itemValue) - item:addChild(itemLabel) - item:addChild(itemTrend) - row3:addChild(item) - end - - -- Row 4: Center with overflow behavior - local row4 = Gui.new({ - id = "row4", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - width = 780, - height = 120, - gap = 10, - }) - - -- Create many items that might overflow - for i = 1, 8 do - local chip = Gui.new({ - id = "row4Chip" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 90, - height = 30, - gap = 5, - }) - - local chipIcon = Gui.new({ id = "row4ChipIcon" .. i, width = 16, height = 16 }) - local chipText = Gui.new({ id = "row4ChipText" .. i, width = 60, height = 14 }) - - chip:addChild(chipIcon) - chip:addChild(chipText) - row4:addChild(chip) - end - - gridContainer:addChild(row1) - gridContainer:addChild(row2) - gridContainer:addChild(row3) - gridContainer:addChild(row4) - - -- Verify row 1 space-between (4 cards, 150px each) - local row1Card1 = row1.children[1] - local row1Card4 = row1.children[4] - - luaunit.assertEquals(row1Card1.x, row1.x) - luaunit.assertEquals(row1Card4.x + row1Card4.width, row1.x + row1.width) - - -- Verify row 2 space-around (3 cards, 200px each) - local totalRow2Width = 3 * 200 -- 600px - local row2AvailableSpace = 780 - 600 -- 180px - local row2SpaceAround = 180 / 3 -- 60px around each - local row2SpaceAtEnds = 60 / 2 -- 30px at ends - - local row2Card1 = row2.children[1] - luaunit.assertEquals(row2Card1.x, row2.x + row2SpaceAtEnds) - - -- Verify row 3 space-evenly (5 items, 100px each) - local totalRow3Width = 5 * 100 -- 500px - local row3AvailableSpace = 780 - 500 -- 280px - local row3EvenSpacing = 280 / 6 -- 46.67px (6 spaces: before, between×4, after) - - local row3Item1 = row3.children[1] - luaunit.assertAlmostEquals(row3Item1.x, row3.x + row3EvenSpacing, 0.1) - - -- Verify row 4 center behavior (8 chips, 90px each + 7 gaps of 10px = 790px) - -- Should overflow slightly but center the content - local totalRow4Width = 8 * 90 + 7 * 10 -- 790px (larger than container) - local row4Offset = (row4.width - totalRow4Width) / 2 -- Should be negative - local row4Chip1 = row4.children[1] - - -- When content is larger than container, should start at calculated offset (possibly negative) - luaunit.assertEquals(row4Chip1.x, row4.x + row4Offset) - - -- Verify nested justify content in cards - local row2Card1Header = row2.children[1].children[1] - local headerTitle = row2Card1Header.children[1] - local headerIcon = row2Card1Header.children[2] - - luaunit.assertEquals(headerTitle.x, row2Card1Header.x) - luaunit.assertEquals(headerIcon.x + headerIcon.width, row2Card1Header.x + row2Card1Header.width) -end - --- Test 20: Multi-level nested justify content with modal dialogs -function TestJustifyContent:testMultiLevelNestedModalJustifyContent() - local modalOverlay = Gui.new({ - id = "modalOverlay", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - x = 0, - y = 0, - width = 1200, - height = 800, - gap = 0, - }) - - local modal = Gui.new({ - id = "modal", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - width = 600, - height = 500, - gap = 0, - }) - - -- Modal header with space-between - local modalHeader = Gui.new({ - id = "modalHeader", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 580, - height = 60, - gap = 0, - }) - - local headerLeft = Gui.new({ - id = "headerLeft", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - width = 200, - height = 50, - gap = 12, - }) - - local modalIcon = Gui.new({ id = "modalIcon", width = 24, height = 24 }) - local modalTitle = Gui.new({ id = "modalTitle", width = 150, height = 30 }) - - headerLeft:addChild(modalIcon) - headerLeft:addChild(modalTitle) - - local headerRight = Gui.new({ - id = "headerRight", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 100, - height = 50, - gap = 8, - }) - - local minimizeButton = Gui.new({ id = "minimizeButton", width = 30, height = 30 }) - local closeButton = Gui.new({ id = "closeButton", width = 30, height = 30 }) - - headerRight:addChild(minimizeButton) - headerRight:addChild(closeButton) - - modalHeader:addChild(headerLeft) - modalHeader:addChild(headerRight) - - -- Modal content with complex nested layouts - local modalContent = Gui.new({ - id = "modalContent", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 580, - height = 380, - gap = 20, - }) - - -- Tab navigation - local tabNavigation = Gui.new({ - id = "tabNavigation", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - width = 580, - height = 50, - gap = 0, - }) - - local tabs = { "General", "Advanced", "Security", "Notifications" } - for i, tabName in ipairs(tabs) do - local tab = Gui.new({ - id = "tab" .. tabName, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = 120, - height = 45, - gap = 5, - }) - - local tabText = Gui.new({ id = "tabText" .. tabName, width = 80, height = 18 }) - local tabIndicator = Gui.new({ id = "tabIndicator" .. tabName, width = 60, height = 3 }) - - tab:addChild(tabText) - tab:addChild(tabIndicator) - tabNavigation:addChild(tab) - end - - -- Content area with settings rows - local settingsArea = Gui.new({ - id = "settingsArea", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 580, - height = 250, - gap = 15, - }) - - -- Setting rows with different alignments - for i = 1, 4 do - local settingRow = Gui.new({ - id = "settingRow" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = i % 2 == 1 and JustifyContent.SPACE_BETWEEN or JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - width = 560, - height = 50, - gap = 15, - }) - - local settingInfo = Gui.new({ - id = "settingInfo" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - width = 300, - height = 40, - gap = 3, - }) - - local settingLabel = Gui.new({ id = "settingLabel" .. i, width = 200, height = 20 }) - local settingDescription = Gui.new({ id = "settingDescription" .. i, width = 280, height = 14 }) - - settingInfo:addChild(settingLabel) - settingInfo:addChild(settingDescription) - - local settingControl = Gui.new({ - id = "settingControl" .. i, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - width = i % 2 == 1 and 100 or 200, - height = 35, - gap = 8, - }) - - if i % 2 == 1 then - -- Toggle switch - local toggle = Gui.new({ id = "toggle" .. i, width = 60, height = 30 }) - settingControl:addChild(toggle) - else - -- Dropdown or input - local input = Gui.new({ id = "input" .. i, width = 120, height = 30 }) - local button = Gui.new({ id = "button" .. i, width = 60, height = 28 }) - settingControl:addChild(input) - settingControl:addChild(button) - end - - settingRow:addChild(settingInfo) - settingRow:addChild(settingControl) - settingsArea:addChild(settingRow) - end - - modalContent:addChild(tabNavigation) - modalContent:addChild(settingsArea) - - -- Modal footer with action buttons - local modalFooter = Gui.new({ - id = "modalFooter", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - width = 580, - height = 60, - gap = 0, - }) - - local footerLeft = Gui.new({ - id = "footerLeft", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - width = 200, - height = 50, - gap = 10, - }) - - local resetButton = Gui.new({ id = "resetButton", width = 80, height = 35 }) - local helpLink = Gui.new({ id = "helpLink", width = 60, height = 20 }) - - footerLeft:addChild(resetButton) - footerLeft:addChild(helpLink) - - local footerRight = Gui.new({ - id = "footerRight", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - width = 250, - height = 50, - gap = 12, - }) - - local cancelButton = Gui.new({ id = "cancelButton", width = 70, height = 35 }) - local applyButton = Gui.new({ id = "applyButton", width = 70, height = 35 }) - local saveButton = Gui.new({ id = "saveButton", width = 80, height = 35 }) - - footerRight:addChild(cancelButton) - footerRight:addChild(applyButton) - footerRight:addChild(saveButton) - - modalFooter:addChild(footerLeft) - modalFooter:addChild(footerRight) - - modal:addChild(modalHeader) - modal:addChild(modalContent) - modal:addChild(modalFooter) - modalOverlay:addChild(modal) - - -- Verify modal is centered in overlay - local expectedModalX = modalOverlay.x + (modalOverlay.width - modal.width) / 2 - luaunit.assertEquals(modal.x, expectedModalX) - - -- Verify modal space-between layout - luaunit.assertEquals(modalHeader.y, modal.y) - luaunit.assertEquals(modalFooter.y + modalFooter.height, modal.y + modal.height) - - -- Verify header space-between - luaunit.assertEquals(headerLeft.x, modalHeader.x) - luaunit.assertEquals(headerRight.x + headerRight.width, modalHeader.x + modalHeader.width) - - -- Verify tab space-evenly distribution - local totalTabsWidth = 4 * 120 -- 480px - local tabAvailableSpace = 580 - 480 -- 100px - local tabEvenSpacing = 100 / 5 -- 20px (5 spaces: before, between×3, after) - - local firstTab = tabNavigation.children[1] - luaunit.assertEquals(firstTab.x, tabNavigation.x + tabEvenSpacing) - - -- Verify setting rows alternate justification - local setting1 = settingsArea.children[1] -- space-between - local setting2 = settingsArea.children[2] -- flex-start - - local setting1Info = setting1.children[1] - local setting1Control = setting1.children[2] - - luaunit.assertEquals(setting1Info.x, setting1.x) - luaunit.assertEquals(setting1Control.x + setting1Control.width, setting1.x + setting1.width) - - local setting2Info = setting2.children[1] - local setting2Control = setting2.children[2] - - luaunit.assertEquals(setting2Info.x, setting2.x) - luaunit.assertEquals(setting2Control.x, setting2Info.x + setting2Info.width + setting2.gap) - - -- Verify footer space-between - luaunit.assertEquals(footerLeft.x, modalFooter.x) - luaunit.assertEquals(footerRight.x + footerRight.width, modalFooter.x + modalFooter.width) - - -- Verify nested button layouts in footer - local footerRightFirstButton = footerRight.children[1] - local footerRightLastButton = footerRight.children[3] - local expectedFooterRightStartX = footerRight.x + footerRight.width - (70 + 70 + 80 + 2 * 12) - - luaunit.assertEquals(footerRightFirstButton.x, expectedFooterRightStartX) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/06_align_items_tests.lua b/testing/__tests__/06_align_items_tests.lua deleted file mode 100644 index 6940936..0000000 --- a/testing/__tests__/06_align_items_tests.lua +++ /dev/null @@ -1,2261 +0,0 @@ --- 06. Align Items Tests --- Tests for FlexLove align items functionality - --- Load test framework and dependencies -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - --- Import required enums -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local AlignItems = enums.AlignItems -local JustifyContent = enums.JustifyContent - --- Test class for align items functionality -TestAlignItems = {} - -function TestAlignItems:setUp() - -- Clear any previous state if needed - Gui.destroy() -end - -function TestAlignItems:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Horizontal Flex with AlignItems.FLEX_START -function TestAlignItems:testHorizontalFlexAlignItemsFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 70, - height = 20, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- With FLEX_START, children should be aligned to top (start of cross axis) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child3.y, 0) - - -- Heights should remain original (no stretching) - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) - luaunit.assertEquals(child3.height, 20) -end - --- Test 2: Horizontal Flex with AlignItems.CENTER -function TestAlignItems:testHorizontalFlexAlignItemsCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children should be centered vertically - -- child1: (100 - 30) / 2 = 35 - -- child2: (100 - 40) / 2 = 30 - luaunit.assertEquals(child1.y, 35) - luaunit.assertEquals(child2.y, 30) - - -- Heights should remain original - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) -end - --- Test 3: Horizontal Flex with AlignItems.FLEX_END -function TestAlignItems:testHorizontalFlexAlignItemsFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children should be aligned to bottom (end of cross axis) - -- child1: 100 - 30 = 70 - -- child2: 100 - 40 = 60 - luaunit.assertEquals(child1.y, 70) - luaunit.assertEquals(child2.y, 60) - - -- Heights should remain original - luaunit.assertEquals(child1.height, 30) - luaunit.assertEquals(child2.height, 40) -end - --- Test 4: Horizontal Flex with AlignItems.STRETCH -function TestAlignItems:testHorizontalFlexAlignItemsStretch() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children with explicit heights should NOT be stretched (CSS flexbox behavior) - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) - luaunit.assertEquals(child1.height, 30) -- Keeps explicit height - luaunit.assertEquals(child2.height, 40) -- Keeps explicit height -end - --- Test 5: Vertical Flex with AlignItems.FLEX_START -function TestAlignItems:testVerticalFlexAlignItemsFlexStart() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 200, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 60, - height = 35, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- With FLEX_START, children should be aligned to left (start of cross axis) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child3.x, 0) - - -- Widths should remain original (no stretching) - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) - luaunit.assertEquals(child3.width, 60) -end - --- Test 6: Vertical Flex with AlignItems.CENTER -function TestAlignItems:testVerticalFlexAlignItemsCenter() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 200, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children should be centered horizontally - -- child1: (200 - 50) / 2 = 75 - -- child2: (200 - 80) / 2 = 60 - luaunit.assertEquals(child1.x, 75) - luaunit.assertEquals(child2.x, 60) - - -- Widths should remain original - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) -end - --- Test 7: Vertical Flex with AlignItems.FLEX_END -function TestAlignItems:testVerticalFlexAlignItemsFlexEnd() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 200, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children should be aligned to right (end of cross axis) - -- child1: 200 - 50 = 150 - -- child2: 200 - 80 = 120 - luaunit.assertEquals(child1.x, 150) - luaunit.assertEquals(child2.x, 120) - - -- Widths should remain original - luaunit.assertEquals(child1.width, 50) - luaunit.assertEquals(child2.width, 80) -end - --- Test 8: Vertical Flex with AlignItems.STRETCH -function TestAlignItems:testVerticalFlexAlignItemsStretch() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 200, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children with explicit widths should NOT be stretched (CSS flexbox behavior) - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 0) - luaunit.assertEquals(child1.width, 50) -- Keeps explicit width - luaunit.assertEquals(child2.width, 80) -- Keeps explicit width -end - --- Test 9: Default AlignItems value (should be STRETCH) -function TestAlignItems:testDefaultAlignItems() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - -- No alignItems specified, should default to STRETCH - }) - - local child = Gui.new({ - id = "child", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - container:addChild(child) - - -- Default should be STRETCH, but explicit heights are respected - luaunit.assertEquals(container.alignItems, AlignItems.STRETCH) - luaunit.assertEquals(child.height, 30) -- Keeps explicit height (CSS flexbox behavior) -end - --- Test 10: AlignItems with mixed child sizes -function TestAlignItems:testAlignItemsWithMixedChildSizes() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local child1 = Gui.new({ - id = "child1", - width = 40, - height = 20, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 50, - height = 80, - positioning = Positioning.FLEX, - }) - - local child3 = Gui.new({ - id = "child3", - width = 60, - height = 30, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - container:addChild(child3) - - -- All children should be centered vertically - -- child1: (120 - 20) / 2 = 50 - -- child2: (120 - 80) / 2 = 20 - -- child3: (120 - 30) / 2 = 45 - luaunit.assertEquals(child1.y, 50) - luaunit.assertEquals(child2.y, 20) - luaunit.assertEquals(child3.y, 45) -end - --- Test 11: AlignItems with single child -function TestAlignItems:testAlignItemsWithSingleChild() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local child = Gui.new({ - id = "child", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - container:addChild(child) - - -- Child should be aligned to bottom - luaunit.assertEquals(child.y, 70) -- 100 - 30 -end - --- Test 12: AlignItems with container coordinates -function TestAlignItems:testAlignItemsWithContainerCoordinates() - local container = Gui.new({ - id = "container", - x = 50, - y = 20, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local child = Gui.new({ - id = "child", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child) - - -- Child should be centered relative to container position - -- Y position: container.y + (container.height - child.height) / 2 - -- Y position: 20 + (100 - 40) / 2 = 20 + 30 = 50 - luaunit.assertEquals(child.y, 50) -end - --- Test 13: AlignItems BASELINE (should behave like FLEX_START for now) -function TestAlignItems:testAlignItemsBaseline() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.BASELINE, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- BASELINE should behave like FLEX_START for basic implementation - luaunit.assertEquals(child1.y, 0) - luaunit.assertEquals(child2.y, 0) -end - --- Test 14: AlignItems interaction with gap -function TestAlignItems:testAlignItemsWithGap() - local container = Gui.new({ - id = "container", - x = 0, - y = 0, - width = 300, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - gap = 10, - }) - - local child1 = Gui.new({ - id = "child1", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local child2 = Gui.new({ - id = "child2", - width = 60, - height = 40, - positioning = Positioning.FLEX, - }) - - container:addChild(child1) - container:addChild(child2) - - -- Children should be centered vertically despite gap - luaunit.assertEquals(child1.y, 35) -- (100 - 30) / 2 - luaunit.assertEquals(child2.y, 30) -- (100 - 40) / 2 - - -- X positions should respect gap - luaunit.assertEquals(child1.x, 0) - luaunit.assertEquals(child2.x, 60) -- 50 + 10 gap -end - --- Test 15: AlignItems with different flex directions -function TestAlignItems:testAlignItemsCrossAxisConsistency() - -- Horizontal container with vertical alignment - local hContainer = Gui.new({ - id = "hContainer", - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local hChild = Gui.new({ - id = "hChild", - width = 50, - height = 40, - positioning = Positioning.FLEX, - }) - - hContainer:addChild(hChild) - - -- Vertical container with horizontal alignment - local vContainer = Gui.new({ - id = "vContainer", - x = 0, - y = 0, - width = 100, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local vChild = Gui.new({ - id = "vChild", - width = 40, - height = 50, - positioning = Positioning.FLEX, - }) - - vContainer:addChild(vChild) - - -- Both should be centered on their respective cross axes - luaunit.assertEquals(hChild.y, 30) -- (100 - 40) / 2 - vertical centering - luaunit.assertEquals(vChild.x, 30) -- (100 - 40) / 2 - horizontal centering -end - --- Test 16: Complex Card Layout with Mixed AlignItems -function TestAlignItems:testComplexCardLayoutMixedAlignItems() - -- Main card container - local card = Gui.new({ - id = "card", - x = 10, - y = 10, - width = 300, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - -- Card header with icon and title (horizontal layout, center-aligned) - local header = Gui.new({ - id = "header", - width = 300, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local icon = Gui.new({ - id = "icon", - width = 24, - height = 24, - positioning = Positioning.FLEX, - }) - - local title = Gui.new({ - id = "title", - width = 200, - height = 24, - positioning = Positioning.FLEX, - }) - - local actions = Gui.new({ - id = "actions", - width = 60, - height = 30, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) - - local btn1 = Gui.new({ - id = "btn1", - width = 28, - height = 28, - positioning = Positioning.FLEX, - }) - - local btn2 = Gui.new({ - id = "btn2", - width = 28, - height = 20, - positioning = Positioning.FLEX, - }) - - -- Card content with flex-end alignment - local content = Gui.new({ - id = "content", - width = 300, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local contentText = Gui.new({ - id = "contentText", - width = 250, - height = 80, - positioning = Positioning.FLEX, - }) - - local metadata = Gui.new({ - id = "metadata", - width = 180, - height = 30, - positioning = Positioning.FLEX, - }) - - -- Card footer with space-between and center alignment - local footer = Gui.new({ - id = "footer", - width = 300, - height = 30, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local timestamp = Gui.new({ - id = "timestamp", - width = 80, - height = 16, - positioning = Positioning.FLEX, - }) - - local status = Gui.new({ - id = "status", - width = 60, - height = 20, - positioning = Positioning.FLEX, - }) - - -- Build the tree - actions:addChild(btn1) - actions:addChild(btn2) - header:addChild(icon) - header:addChild(title) - header:addChild(actions) - - content:addChild(contentText) - content:addChild(metadata) - - footer:addChild(timestamp) - footer:addChild(status) - - card:addChild(header) - card:addChild(content) - card:addChild(footer) - - -- Verify alignments in header (CENTER) - luaunit.assertEquals(icon.y, 23) -- (50 - 24) / 2 = 13, plus card.y = 10 + 13 = 23 - luaunit.assertEquals(title.y, 23) -- Same center alignment - - -- Verify actions buttons have FLEX_START alignment - -- actions is centered in header: header.y (10) + (header.height (50) - actions.height (30)) / 2 = 20 - luaunit.assertEquals(btn1.y, 20) -- Start of actions container - luaunit.assertEquals(btn2.y, 20) -- Same start position - - -- Verify content alignment (FLEX_END) - luaunit.assertEquals(contentText.x, 60) -- 300 - 250 = 50, plus card.x = 10 + 50 = 60 - luaunit.assertEquals(metadata.x, 130) -- 300 - 180 = 120, plus card.x = 10 + 120 = 130 - - -- Verify footer center alignment - -- footer.y = card.y (10) + header.height (50) + content.height (120) = 180 (no gap specified) - luaunit.assertEquals(timestamp.y, 187) -- Footer center: (30 - 16) / 2 = 7, plus footer.y = 180 + 7 = 187 - luaunit.assertEquals(status.y, 185) -- Footer center: (30 - 20) / 2 = 5, plus footer.y = 180 + 5 = 185 -end - --- Test 17: Complex Media Object Pattern with Nested Alignments -function TestAlignItems:testComplexMediaObjectNestedAlignments() - -- Main media container - local mediaContainer = Gui.new({ - id = "mediaContainer", - x = 0, - y = 0, - width = 400, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) - - -- Media (image/avatar) section - local mediaSection = Gui.new({ - id = "mediaSection", - width = 80, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local avatar = Gui.new({ - id = "avatar", - width = 60, - height = 60, - positioning = Positioning.FLEX, - }) - - local badge = Gui.new({ - id = "badge", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - -- Content section with multiple alignment variations - local contentSection = Gui.new({ - id = "contentSection", - width = 280, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - -- Header with user info (flex-end alignment) - local userHeader = Gui.new({ - id = "userHeader", - width = 280, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local username = Gui.new({ - id = "username", - width = 120, - height = 24, - positioning = Positioning.FLEX, - }) - - local timestamp = Gui.new({ - id = "timestamp", - width = 80, - height = 16, - positioning = Positioning.FLEX, - }) - - local menu = Gui.new({ - id = "menu", - width = 30, - height = 30, - positioning = Positioning.FLEX, - }) - - -- Main content with center alignment - local mainContent = Gui.new({ - id = "mainContent", - width = 280, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local text = Gui.new({ - id = "text", - width = 260, - height = 60, - positioning = Positioning.FLEX, - }) - - local attachments = Gui.new({ - id = "attachments", - width = 200, - height = 15, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local attach1 = Gui.new({ - id = "attach1", - width = 12, - height = 12, - positioning = Positioning.FLEX, - }) - - local attach2 = Gui.new({ - id = "attach2", - width = 12, - height = 8, - positioning = Positioning.FLEX, - }) - - -- Footer actions with space-between - local actionsFooter = Gui.new({ - id = "actionsFooter", - width = 280, - height = 30, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local reactions = Gui.new({ - id = "reactions", - width = 100, - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local like = Gui.new({ - id = "like", - width = 16, - height = 16, - positioning = Positioning.FLEX, - }) - - local share = Gui.new({ - id = "share", - width = 16, - height = 14, - positioning = Positioning.FLEX, - }) - - local moreActions = Gui.new({ - id = "moreActions", - width = 60, - height = 24, - positioning = Positioning.FLEX, - }) - - -- Build the tree - mediaSection:addChild(avatar) - mediaSection:addChild(badge) - - userHeader:addChild(username) - userHeader:addChild(timestamp) - userHeader:addChild(menu) - - attachments:addChild(attach1) - attachments:addChild(attach2) - mainContent:addChild(text) - mainContent:addChild(attachments) - - reactions:addChild(like) - reactions:addChild(share) - actionsFooter:addChild(reactions) - actionsFooter:addChild(moreActions) - - contentSection:addChild(userHeader) - contentSection:addChild(mainContent) - contentSection:addChild(actionsFooter) - - mediaContainer:addChild(mediaSection) - mediaContainer:addChild(contentSection) - - -- Verify media section center alignment - luaunit.assertEquals(avatar.x, 10) -- (80 - 60) / 2 = 10 - luaunit.assertEquals(badge.x, 30) -- (80 - 20) / 2 = 30 - - -- Verify user header flex-end alignment - luaunit.assertEquals(username.y, 16) -- (40 - 24) = 16 from bottom - luaunit.assertEquals(timestamp.y, 24) -- (40 - 16) = 24 from bottom - luaunit.assertEquals(menu.y, 10) -- (40 - 30) = 10 from bottom - - -- Verify main content center alignment - luaunit.assertEquals(text.x, 90) -- 80 + (280 - 260) / 2 = 80 + 10 = 90 - luaunit.assertEquals(attachments.x, 120) -- 80 + (280 - 200) / 2 = 80 + 40 = 120 - - -- Verify attachment items center alignment - luaunit.assertEquals(attach1.y, 101.5) -- attachments.y (100) + (15 - 12) / 2 = 100 + 1.5 = 101.5 - luaunit.assertEquals(attach2.y, 103.5) -- attachments.y (100) + (15 - 8) / 2 = 100 + 3.5 = 103.5 - - -- Verify actions footer center alignment - luaunit.assertEquals(like.y, 127) -- actionsFooter.y (120) + reactions centered (5) + like centered (2) = 127 - luaunit.assertEquals(moreActions.y, 123) -- actionsFooter.y + (30 - 24) / 2 = 120 + 3 = 123 -end - --- Test 18: Complex Toolbar with Varied Alignments -function TestAlignItems:testComplexToolbarVariedAlignments() - -- Main toolbar container - local toolbar = Gui.new({ - id = "toolbar", - x = 0, - y = 0, - width = 600, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - -- Left section with logo and nav (flex-start alignment) - local leftSection = Gui.new({ - id = "leftSection", - width = 200, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) - - local logo = Gui.new({ - id = "logo", - width = 40, - height = 40, - positioning = Positioning.FLEX, - }) - - local navigation = Gui.new({ - id = "navigation", - width = 150, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local navItem1 = Gui.new({ - id = "navItem1", - width = 60, - height = 30, - positioning = Positioning.FLEX, - }) - - local navItem2 = Gui.new({ - id = "navItem2", - width = 70, - height = 35, - positioning = Positioning.FLEX, - }) - - -- Center section with search (stretch alignment) - local centerSection = Gui.new({ - id = "centerSection", - width = 250, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local searchContainer = Gui.new({ - id = "searchContainer", - width = 250, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local searchInput = Gui.new({ - id = "searchInput", - width = 200, - height = 32, - positioning = Positioning.FLEX, - }) - - local searchButton = Gui.new({ - id = "searchButton", - width = 36, - height = 36, - positioning = Positioning.FLEX, - }) - - local searchHint = Gui.new({ - id = "searchHint", - width = 250, - height = 16, - positioning = Positioning.FLEX, - }) - - -- Right section with user controls (flex-end alignment) - local rightSection = Gui.new({ - id = "rightSection", - width = 150, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local notifications = Gui.new({ - id = "notifications", - width = 30, - height = 35, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local notifIcon = Gui.new({ - id = "notifIcon", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - local notifBadge = Gui.new({ - id = "notifBadge", - width = 12, - height = 12, - positioning = Positioning.FLEX, - }) - - local userMenu = Gui.new({ - id = "userMenu", - width = 80, - height = 45, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local userAvatar = Gui.new({ - id = "userAvatar", - width = 32, - height = 32, - positioning = Positioning.FLEX, - }) - - local dropdown = Gui.new({ - id = "dropdown", - width = 40, - height = 25, - positioning = Positioning.FLEX, - }) - - -- Build the tree - navigation:addChild(navItem1) - navigation:addChild(navItem2) - leftSection:addChild(logo) - leftSection:addChild(navigation) - - searchContainer:addChild(searchInput) - searchContainer:addChild(searchButton) - centerSection:addChild(searchContainer) - centerSection:addChild(searchHint) - - notifications:addChild(notifIcon) - notifications:addChild(notifBadge) - userMenu:addChild(userAvatar) - userMenu:addChild(dropdown) - rightSection:addChild(notifications) - rightSection:addChild(userMenu) - - toolbar:addChild(leftSection) - toolbar:addChild(centerSection) - toolbar:addChild(rightSection) - - -- Verify left section flex-start alignment - luaunit.assertEquals(logo.y, 0) -- Aligned to top - luaunit.assertEquals(navItem1.y, 10) -- navigation.y (0) + (50 - 30) / 2 = 0 + 10 = 10 - luaunit.assertEquals(navItem2.y, 7.5) -- navigation.y (0) + (50 - 35) / 2 = 0 + 7.5 = 7.5 - - -- Verify center section stretch alignment - luaunit.assertEquals(searchInput.y, 4) -- searchContainer.y + (40 - 32) / 2 = 10 + 4 = 14 - luaunit.assertEquals(searchButton.y, 2) -- searchContainer.y + (40 - 36) / 2 = 10 + 2 = 12 - luaunit.assertEquals(searchHint.width, 250) -- Should be stretched to full width - - -- Verify right section flex-end alignment - luaunit.assertEquals(notifications.y, 25) -- (60 - 35) = 25 from bottom - luaunit.assertEquals(userMenu.y, 15) -- (60 - 45) = 15 from bottom - - -- Verify notification items center alignment - luaunit.assertEquals(notifIcon.x, 455) -- rightSection.x (450) + notifications.x (0) + center offset (5) = 455 - luaunit.assertEquals(notifBadge.x, 459) -- rightSection.x (450) + notifications.x (0) + center offset (9) = 459 - - -- Verify user menu center alignment - luaunit.assertEquals(userAvatar.y, 21.5) -- userMenu.y + (45 - 32) / 2 = 15 + 6.5 = 21.5 - luaunit.assertEquals(dropdown.y, 25) -- userMenu.y + (45 - 25) / 2 = 15 + 10 = 25 -end - --- Test 19: Complex Dashboard Widget Layout -function TestAlignItems:testComplexDashboardWidgetLayout() - -- Main dashboard container - local dashboard = Gui.new({ - id = "dashboard", - x = 0, - y = 0, - width = 800, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - -- Header with title and controls - local header = Gui.new({ - id = "header", - width = 800, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local titleSection = Gui.new({ - id = "titleSection", - width = 300, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local title = Gui.new({ - id = "title", - width = 250, - height = 40, - positioning = Positioning.FLEX, - }) - - local subtitle = Gui.new({ - id = "subtitle", - width = 200, - height = 24, - positioning = Positioning.FLEX, - }) - - local controlsSection = Gui.new({ - id = "controlsSection", - width = 200, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local filterBtn = Gui.new({ - id = "filterBtn", - width = 60, - height = 35, - positioning = Positioning.FLEX, - }) - - local exportBtn = Gui.new({ - id = "exportBtn", - width = 70, - height = 35, - positioning = Positioning.FLEX, - }) - - local settingsBtn = Gui.new({ - id = "settingsBtn", - width = 40, - height = 40, - positioning = Positioning.FLEX, - }) - - -- Main content area with widgets - local mainContent = Gui.new({ - id = "mainContent", - width = 800, - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - }) - - -- Left panel with statistics (center alignment) - local leftPanel = Gui.new({ - id = "leftPanel", - width = 250, - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local statCard1 = Gui.new({ - id = "statCard1", - width = 220, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local stat1Value = Gui.new({ - id = "stat1Value", - width = 100, - height = 40, - positioning = Positioning.FLEX, - }) - - local stat1Label = Gui.new({ - id = "stat1Label", - width = 150, - height = 20, - positioning = Positioning.FLEX, - }) - - local stat1Chart = Gui.new({ - id = "stat1Chart", - width = 180, - height = 50, - positioning = Positioning.FLEX, - }) - - local statCard2 = Gui.new({ - id = "statCard2", - width = 220, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local stat2Value = Gui.new({ - id = "stat2Value", - width = 120, - height = 35, - positioning = Positioning.FLEX, - }) - - local stat2Trend = Gui.new({ - id = "stat2Trend", - width = 80, - height = 20, - positioning = Positioning.FLEX, - }) - - -- Center panel with main chart (stretch alignment) - local centerPanel = Gui.new({ - id = "centerPanel", - width = 400, - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local chartHeader = Gui.new({ - id = "chartHeader", - width = 400, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local chartTitle = Gui.new({ - id = "chartTitle", - width = 200, - height = 30, - positioning = Positioning.FLEX, - }) - - local chartControls = Gui.new({ - id = "chartControls", - width = 120, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local timeRange = Gui.new({ - id = "timeRange", - width = 80, - height = 25, - positioning = Positioning.FLEX, - }) - - local refreshBtn = Gui.new({ - id = "refreshBtn", - width = 30, - height = 30, - positioning = Positioning.FLEX, - }) - - local mainChart = Gui.new({ - id = "mainChart", - width = 400, - height = 380, - positioning = Positioning.FLEX, - }) - - -- Right panel with lists (flex-start alignment) - local rightPanel = Gui.new({ - id = "rightPanel", - width = 150, - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local alertsList = Gui.new({ - id = "alertsList", - width = 140, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local alert1 = Gui.new({ - id = "alert1", - width = 140, - height = 40, - positioning = Positioning.FLEX, - }) - - local alert2 = Gui.new({ - id = "alert2", - width = 140, - height = 35, - positioning = Positioning.FLEX, - }) - - local tasksList = Gui.new({ - id = "tasksList", - width = 130, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local task1 = Gui.new({ - id = "task1", - width = 120, - height = 30, - positioning = Positioning.FLEX, - }) - - local task2 = Gui.new({ - id = "task2", - width = 110, - height = 25, - positioning = Positioning.FLEX, - }) - - -- Footer with status info - local footer = Gui.new({ - id = "footer", - width = 800, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local status = Gui.new({ - id = "status", - width = 200, - height = 20, - positioning = Positioning.FLEX, - }) - - local timestamp = Gui.new({ - id = "timestamp", - width = 150, - height = 16, - positioning = Positioning.FLEX, - }) - - -- Build the tree - titleSection:addChild(title) - titleSection:addChild(subtitle) - controlsSection:addChild(filterBtn) - controlsSection:addChild(exportBtn) - controlsSection:addChild(settingsBtn) - header:addChild(titleSection) - header:addChild(controlsSection) - - statCard1:addChild(stat1Value) - statCard1:addChild(stat1Label) - statCard1:addChild(stat1Chart) - statCard2:addChild(stat2Value) - statCard2:addChild(stat2Trend) - leftPanel:addChild(statCard1) - leftPanel:addChild(statCard2) - - chartControls:addChild(timeRange) - chartControls:addChild(refreshBtn) - chartHeader:addChild(chartTitle) - chartHeader:addChild(chartControls) - centerPanel:addChild(chartHeader) - centerPanel:addChild(mainChart) - - alertsList:addChild(alert1) - alertsList:addChild(alert2) - tasksList:addChild(task1) - tasksList:addChild(task2) - rightPanel:addChild(alertsList) - rightPanel:addChild(tasksList) - - mainContent:addChild(leftPanel) - mainContent:addChild(centerPanel) - mainContent:addChild(rightPanel) - - footer:addChild(status) - footer:addChild(timestamp) - - dashboard:addChild(header) - dashboard:addChild(mainContent) - dashboard:addChild(footer) - - -- Verify title section flex-start alignment - luaunit.assertEquals(title.x, 0) -- Aligned to left - luaunit.assertEquals(subtitle.x, 0) -- Also aligned to left - - -- Verify controls section flex-end alignment - luaunit.assertEquals(filterBtn.y, 45) -- (80 - 35) = 45 from bottom - luaunit.assertEquals(exportBtn.y, 45) -- Same flex-end alignment - luaunit.assertEquals(settingsBtn.y, 40) -- (80 - 40) = 40 from bottom - - -- Verify left panel center alignment - luaunit.assertEquals(statCard1.x, 15) -- (250 - 220) / 2 = 15 - luaunit.assertEquals(statCard2.x, 15) -- Same center alignment - - -- Verify stat card alignments - luaunit.assertEquals(stat1Value.x, 75) -- statCard1.x + (220 - 100) / 2 = 15 + 60 = 75 - luaunit.assertEquals(stat1Label.x, 50) -- statCard1.x + (220 - 150) / 2 = 15 + 35 = 50 - luaunit.assertEquals(stat2Value.x, 115) -- statCard2.x + (220 - 120) = 15 + 100 = 115 - luaunit.assertEquals(stat2Trend.x, 155) -- statCard2.x + (220 - 80) = 15 + 140 = 155 - - -- Verify center panel stretch alignment - luaunit.assertEquals(chartHeader.width, 400) -- Should be stretched - luaunit.assertEquals(mainChart.width, 400) -- Should be stretched - - -- Verify right panel flex-start alignment - luaunit.assertEquals(alertsList.x, 650) -- rightPanel starts at 650 - luaunit.assertEquals(tasksList.x, 650) -- Also aligned to start - - -- Verify tasks list flex-end alignment - luaunit.assertEquals(task1.x, 660) -- tasksList.x + (130 - 120) = 650 + 10 = 660 - luaunit.assertEquals(task2.x, 670) -- tasksList.x + (130 - 110) = 650 + 20 = 670 - - -- Verify footer center alignment - luaunit.assertEquals(status.y, 570) -- footer.y (560) + (40 - 20) / 2 = 560 + 10 = 570 - luaunit.assertEquals(timestamp.y, 572) -- footer.y (560) + (40 - 16) / 2 = 560 + 12 = 572 -end - --- Test 20: Complex Form Layout with Multi-Level Alignments -function TestAlignItems:testComplexFormMultiLevelAlignments() - -- Main form container - local form = Gui.new({ - id = "form", - x = 50, - y = 50, - width = 500, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - -- Form header with center alignment - local formHeader = Gui.new({ - id = "formHeader", - width = 500, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local formTitle = Gui.new({ - id = "formTitle", - width = 300, - height = 40, - positioning = Positioning.FLEX, - }) - - local formDescription = Gui.new({ - id = "formDescription", - width = 400, - height = 30, - positioning = Positioning.FLEX, - }) - - -- Personal info section with flex-start alignment - local personalSection = Gui.new({ - id = "personalSection", - width = 500, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local sectionTitle1 = Gui.new({ - id = "sectionTitle1", - width = 200, - height = 30, - positioning = Positioning.FLEX, - }) - - local nameRow = Gui.new({ - id = "nameRow", - width = 480, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local firstNameField = Gui.new({ - id = "firstNameField", - width = 220, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local firstNameLabel = Gui.new({ - id = "firstNameLabel", - width = 100, - height = 20, - positioning = Positioning.FLEX, - }) - - local firstNameInput = Gui.new({ - id = "firstNameInput", - width = 200, - height = 35, - positioning = Positioning.FLEX, - }) - - local lastNameField = Gui.new({ - id = "lastNameField", - width = 220, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local lastNameLabel = Gui.new({ - id = "lastNameLabel", - width = 120, - height = 20, - positioning = Positioning.FLEX, - }) - - local lastNameInput = Gui.new({ - id = "lastNameInput", - width = 200, - height = 35, - positioning = Positioning.FLEX, - }) - - local emailRow = Gui.new({ - id = "emailRow", - width = 480, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local emailLabel = Gui.new({ - id = "emailLabel", - width = 100, - height = 20, - positioning = Positioning.FLEX, - }) - - local emailInput = Gui.new({ - id = "emailInput", - width = 480, - height = 35, - positioning = Positioning.FLEX, - }) - - -- Preferences section with center alignment - local preferencesSection = Gui.new({ - id = "preferencesSection", - width = 500, - height = 180, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local sectionTitle2 = Gui.new({ - id = "sectionTitle2", - width = 250, - height = 30, - positioning = Positioning.FLEX, - }) - - local optionsContainer = Gui.new({ - id = "optionsContainer", - width = 400, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_START, - }) - - local leftOptions = Gui.new({ - id = "leftOptions", - width = 180, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - local option1 = Gui.new({ - id = "option1", - width = 150, - height = 25, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local checkbox1 = Gui.new({ - id = "checkbox1", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - local label1 = Gui.new({ - id = "label1", - width = 120, - height = 18, - positioning = Positioning.FLEX, - }) - - local option2 = Gui.new({ - id = "option2", - width = 160, - height = 25, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local checkbox2 = Gui.new({ - id = "checkbox2", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - local label2 = Gui.new({ - id = "label2", - width = 130, - height = 18, - positioning = Positioning.FLEX, - }) - - local rightOptions = Gui.new({ - id = "rightOptions", - width = 180, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_END, - }) - - local dropdown = Gui.new({ - id = "dropdown", - width = 150, - height = 35, - positioning = Positioning.FLEX, - }) - - local slider = Gui.new({ - id = "slider", - width = 140, - height = 20, - positioning = Positioning.FLEX, - }) - - -- Form actions with space-between alignment - local actionsSection = Gui.new({ - id = "actionsSection", - width = 500, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local cancelBtn = Gui.new({ - id = "cancelBtn", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - local submitGroup = Gui.new({ - id = "submitGroup", - width = 200, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local saveBtn = Gui.new({ - id = "saveBtn", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - local submitBtn = Gui.new({ - id = "submitBtn", - width = 100, - height = 45, - positioning = Positioning.FLEX, - }) - - -- Build the tree - formHeader:addChild(formTitle) - formHeader:addChild(formDescription) - - firstNameField:addChild(firstNameLabel) - firstNameField:addChild(firstNameInput) - lastNameField:addChild(lastNameLabel) - lastNameField:addChild(lastNameInput) - nameRow:addChild(firstNameField) - nameRow:addChild(lastNameField) - - emailRow:addChild(emailLabel) - emailRow:addChild(emailInput) - - personalSection:addChild(sectionTitle1) - personalSection:addChild(nameRow) - personalSection:addChild(emailRow) - - option1:addChild(checkbox1) - option1:addChild(label1) - option2:addChild(checkbox2) - option2:addChild(label2) - leftOptions:addChild(option1) - leftOptions:addChild(option2) - - rightOptions:addChild(dropdown) - rightOptions:addChild(slider) - - optionsContainer:addChild(leftOptions) - optionsContainer:addChild(rightOptions) - preferencesSection:addChild(sectionTitle2) - preferencesSection:addChild(optionsContainer) - - submitGroup:addChild(saveBtn) - submitGroup:addChild(submitBtn) - actionsSection:addChild(cancelBtn) - actionsSection:addChild(submitGroup) - - form:addChild(formHeader) - form:addChild(personalSection) - form:addChild(preferencesSection) - form:addChild(actionsSection) - - -- Verify form header center alignment - luaunit.assertEquals(formTitle.x, 150) -- 50 + (500 - 300) / 2 = 50 + 100 = 150 - luaunit.assertEquals(formDescription.x, 100) -- 50 + (500 - 400) / 2 = 50 + 50 = 100 - - -- Verify personal section flex-start alignment - luaunit.assertEquals(sectionTitle1.x, 50) -- Aligned to start - luaunit.assertEquals(nameRow.x, 50) -- Same as personalSection.x (no margin) - - -- Verify name field alignments - luaunit.assertEquals(firstNameLabel.x, 50) -- firstNameField starts at nameRow.x (50) - luaunit.assertEquals(lastNameLabel.x, 370) -- lastNameField.x (270) + (220 - 120) = 270 + 100 = 370 (flex-end within field) - luaunit.assertEquals(lastNameInput.x, 290) -- lastNameField.x (270) + (220 - 200) = 270 + 20 = 290 - - -- Verify preferences section center alignment - luaunit.assertEquals(sectionTitle2.x, 175) -- 50 + (500 - 250) / 2 = 50 + 125 = 175 - luaunit.assertEquals(optionsContainer.x, 100) -- 50 + (500 - 400) / 2 = 50 + 50 = 100 - - -- Verify option alignments - luaunit.assertEquals(checkbox1.y, 362.5) -- option1.y (360) + (25 - 20) / 2 = 360 + 2.5 = 362.5 - luaunit.assertEquals(label1.y, 363.5) -- option1.y (360) + (25 - 18) / 2 = 360 + 3.5 = 363.5 - - -- Verify right options flex-end alignment - luaunit.assertEquals(dropdown.x, 310) -- rightOptions.x + (180 - 150) = 280 + 30 = 310 - luaunit.assertEquals(slider.x, 320) -- rightOptions.x + (180 - 140) = 280 + 40 = 320 - - -- Verify actions section alignments - luaunit.assertEquals(cancelBtn.y, 520) -- actionsSection.y (510) + (60 - 40) / 2 = 510 + 10 = 520 - luaunit.assertEquals(saveBtn.y, 520) -- submitGroup.y (515) + (50 - 40) / 2 = 515 + 5 = 520 - luaunit.assertEquals(submitBtn.y, 517.5) -- submitGroup.y (515) + (50 - 45) / 2 = 515 + 2.5 = 517.5 -end - --- Test 21: Complex Modal Dialog with Nested Alignments -function TestAlignItems:testComplexModalDialogNestedAlignments() - -- Modal backdrop - local backdrop = Gui.new({ - id = "backdrop", - x = 0, - y = 0, - width = 1024, - height = 768, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.CENTER, - }) - - -- Modal dialog - local modal = Gui.new({ - id = "modal", - width = 600, - height = 500, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - -- Modal header with space-between alignment - local modalHeader = Gui.new({ - id = "modalHeader", - width = 600, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - - local headerLeft = Gui.new({ - id = "headerLeft", - width = 300, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local modalIcon = Gui.new({ - id = "modalIcon", - width = 32, - height = 32, - positioning = Positioning.FLEX, - }) - - local modalTitle = Gui.new({ - id = "modalTitle", - width = 250, - height = 30, - positioning = Positioning.FLEX, - }) - - local headerRight = Gui.new({ - id = "headerRight", - width = 100, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.FLEX_END, - }) - - local helpBtn = Gui.new({ - id = "helpBtn", - width = 30, - height = 30, - positioning = Positioning.FLEX, - }) - - local closeBtn = Gui.new({ - id = "closeBtn", - width = 32, - height = 32, - positioning = Positioning.FLEX, - }) - - -- Modal content with mixed alignments - local modalContent = Gui.new({ - id = "modalContent", - width = 600, - height = 380, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.STRETCH, - }) - - -- Left sidebar with navigation - local sidebar = Gui.new({ - id = "sidebar", - width = 150, - height = 380, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.STRETCH, - }) - - local navItem1 = Gui.new({ - id = "navItem1", - width = 150, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local navIcon1 = Gui.new({ - id = "navIcon1", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - local navLabel1 = Gui.new({ - id = "navLabel1", - width = 100, - height = 18, - positioning = Positioning.FLEX, - }) - - local navItem2 = Gui.new({ - id = "navItem2", - width = 150, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local navIcon2 = Gui.new({ - id = "navIcon2", - width = 20, - height = 20, - positioning = Positioning.FLEX, - }) - - local navLabel2 = Gui.new({ - id = "navLabel2", - width = 110, - height = 18, - positioning = Positioning.FLEX, - }) - - -- Main content area - local contentArea = Gui.new({ - id = "contentArea", - width = 450, - height = 380, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.FLEX_START, - }) - - -- Content header with flex-end alignment - local contentHeader = Gui.new({ - id = "contentHeader", - width = 450, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.FLEX_END, - }) - - local contentTitle = Gui.new({ - id = "contentTitle", - width = 200, - height = 35, - positioning = Positioning.FLEX, - }) - - local contentActions = Gui.new({ - id = "contentActions", - width = 180, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - }) - - local editBtn = Gui.new({ - id = "editBtn", - width = 50, - height = 30, - positioning = Positioning.FLEX, - }) - - local deleteBtn = Gui.new({ - id = "deleteBtn", - width = 55, - height = 30, - positioning = Positioning.FLEX, - }) - - local moreBtn = Gui.new({ - id = "moreBtn", - width = 30, - height = 30, - positioning = Positioning.FLEX, - }) - - -- Content body with center alignment - local contentBody = Gui.new({ - id = "contentBody", - width = 450, - height = 280, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - - local contentText = Gui.new({ - id = "contentText", - width = 400, - height = 150, - positioning = Positioning.FLEX, - }) - - local contentImage = Gui.new({ - id = "contentImage", - width = 200, - height = 100, - positioning = Positioning.FLEX, - }) - - -- Content meta with flex-end alignment - local contentMeta = Gui.new({ - id = "contentMeta", - width = 350, - height = 30, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.FLEX_END, - }) - - local lastModified = Gui.new({ - id = "lastModified", - width = 120, - height = 16, - positioning = Positioning.FLEX, - }) - - local author = Gui.new({ - id = "author", - width = 100, - height = 18, - positioning = Positioning.FLEX, - }) - - -- Modal footer with center alignment - local modalFooter = Gui.new({ - id = "modalFooter", - width = 600, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.CENTER, - }) - - local footerActions = Gui.new({ - id = "footerActions", - width = 300, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - alignItems = AlignItems.CENTER, - justifyContent = JustifyContent.SPACE_AROUND, - }) - - local cancelModalBtn = Gui.new({ - id = "cancelModalBtn", - width = 80, - height = 40, - positioning = Positioning.FLEX, - }) - - local applyBtn = Gui.new({ - id = "applyBtn", - width = 70, - height = 40, - positioning = Positioning.FLEX, - }) - - local okBtn = Gui.new({ - id = "okBtn", - width = 60, - height = 45, - positioning = Positioning.FLEX, - }) - - -- Build the tree - headerLeft:addChild(modalIcon) - headerLeft:addChild(modalTitle) - headerRight:addChild(helpBtn) - headerRight:addChild(closeBtn) - modalHeader:addChild(headerLeft) - modalHeader:addChild(headerRight) - - navItem1:addChild(navIcon1) - navItem1:addChild(navLabel1) - navItem2:addChild(navIcon2) - navItem2:addChild(navLabel2) - sidebar:addChild(navItem1) - sidebar:addChild(navItem2) - - contentActions:addChild(editBtn) - contentActions:addChild(deleteBtn) - contentActions:addChild(moreBtn) - contentHeader:addChild(contentTitle) - contentHeader:addChild(contentActions) - - contentBody:addChild(contentText) - contentBody:addChild(contentImage) - - contentMeta:addChild(lastModified) - contentMeta:addChild(author) - - contentArea:addChild(contentHeader) - contentArea:addChild(contentBody) - contentArea:addChild(contentMeta) - - modalContent:addChild(sidebar) - modalContent:addChild(contentArea) - - footerActions:addChild(cancelModalBtn) - footerActions:addChild(applyBtn) - footerActions:addChild(okBtn) - modalFooter:addChild(footerActions) - - modal:addChild(modalHeader) - modal:addChild(modalContent) - modal:addChild(modalFooter) - - backdrop:addChild(modal) - - -- Verify modal is centered in backdrop - luaunit.assertEquals(modal.x, 212) -- (1024 - 600) / 2 = 212 - luaunit.assertEquals(modal.y, 134) -- (768 - 500) / 2 = 134 - - -- Verify header alignment - luaunit.assertEquals(modalIcon.y, 148) -- modal.y + (60 - 32) / 2 = 134 + 14 = 148 - luaunit.assertEquals(modalTitle.y, 149) -- modal.y + (60 - 30) / 2 = 134 + 15 = 149 - luaunit.assertEquals(helpBtn.y, 149) -- header center alignment - luaunit.assertEquals(closeBtn.y, 148) -- header center alignment - - -- Verify nav item alignments - luaunit.assertEquals(navIcon1.y, 204) -- navItem1.y + (40 - 20) / 2 = 194 + 10 = 204 - luaunit.assertEquals(navLabel1.y, 205) -- navItem1.y + (40 - 18) / 2 = 194 + 11 = 205 - - -- Verify content header flex-end alignment - luaunit.assertEquals(contentTitle.y, 209) -- contentHeader.y + (50 - 35) = 194 + 15 = 209 - luaunit.assertEquals(editBtn.y, 209) -- contentActions center: contentHeader.y + (50 - 40)/2 + (40 - 30)/2 = 194 + 5 + 10 = 209 - - -- Verify content body center alignment - luaunit.assertEquals(contentText.x, 387) -- contentArea.x (362) + (450 - 400) / 2 = 362 + 25 = 387 - luaunit.assertEquals(contentImage.x, 487) -- contentArea.x (362) + (450 - 200) / 2 = 362 + 125 = 487 - - -- Verify content meta flex-end alignment - luaunit.assertEquals(lastModified.x, 492) -- contentArea.x (362) + (350 - 220) = 362 + 130 = 492 - luaunit.assertEquals(author.x, 612) -- lastModified.x (492) + lastModified.width (120) = 612 - - -- Verify footer center alignment - luaunit.assertEquals(footerActions.x, 362) -- modal.x + (600 - 300) / 2 = 212 + 150 = 362 - luaunit.assertEquals(cancelModalBtn.y, 584) -- modalFooter.y (574) + (60-50)/2 + (50-40)/2 = 574 + 5 + 5 = 584 - luaunit.assertEquals(okBtn.y, 581.5) -- footerActions.y (579) + (50 - 45) / 2 = 579 + 2.5 = 581.5 -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/07_flex_wrap_tests.lua b/testing/__tests__/07_flex_wrap_tests.lua deleted file mode 100644 index 162e88d..0000000 --- a/testing/__tests__/07_flex_wrap_tests.lua +++ /dev/null @@ -1,1664 +0,0 @@ -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local FlexWrap = enums.FlexWrap -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems -local AlignContent = enums.AlignContent - --- Test class for FlexWrap functionality -TestFlexWrap = {} - -function TestFlexWrap:setUp() - -- Clear any previous state if needed - Gui.destroy() -end - -function TestFlexWrap:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test utilities -local function createContainer(props) - return Gui.new(props) -end - -local function createChild(parent, props) - local child = Gui.new(props) - child.parent = parent - table.insert(parent.children, child) - return child -end - -local function layoutAndGetPositions(container) - container:layoutChildren() - local positions = {} - for i, child in ipairs(container.children) do - positions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - return positions -end - --- Test Case 1: NOWRAP - Children should not wrap (default behavior) -function TestFlexWrap01_NoWrapHorizontal() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.NOWRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - gap = 10, - }) - - -- Create children that would overflow if wrapped - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) - - local positions = layoutAndGetPositions(container) - - -- All children should be on one line, even if they overflow - luaunit.assertEquals(positions[1].x, 0) -- child1 x - luaunit.assertEquals(positions[1].y, 0) -- child1 y - - luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) - luaunit.assertEquals(positions[2].y, 0) -- child2 y - - luaunit.assertEquals(positions[3].x, 180) -- child3 x (160 + 10 gap) - overflows container - luaunit.assertEquals(positions[3].y, 0) -- child3 y -end - --- Test Case 2: WRAP - Children should wrap to new lines -function TestFlexWrap02_WrapHorizontal() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap - - local positions = layoutAndGetPositions(container) - - -- First two children on first line - luaunit.assertEquals(positions[1].x, 0) -- child1 x - luaunit.assertEquals(positions[1].y, 0) -- child1 y - - luaunit.assertEquals(positions[2].x, 90) -- child2 x (80 + 10 gap) - luaunit.assertEquals(positions[2].y, 0) -- child2 y - - -- Third child wrapped to second line - luaunit.assertEquals(positions[3].x, 0) -- child3 x - starts new line - luaunit.assertEquals(positions[3].y, 40) -- child3 y - new line (30 height + 10 gap) -end - --- Test Case 3: WRAP_REVERSE - Lines should be in reverse order -function TestFlexWrap03_WrapReverseHorizontal() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP_REVERSE, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) -- This would wrap but lines are reversed - - local positions = layoutAndGetPositions(container) - - -- With wrap-reverse, the wrapped line comes first - luaunit.assertEquals(positions[3].x, 0) -- child3 x - wrapped line comes first - luaunit.assertEquals(positions[3].y, 0) -- child3 y - first line position - - luaunit.assertEquals(positions[1].x, 0) -- child1 x - original first line comes second - luaunit.assertEquals(positions[1].y, 40) -- child1 y - second line (30 height + 10 gap) - - luaunit.assertEquals(positions[2].x, 90) -- child2 x - luaunit.assertEquals(positions[2].y, 40) -- child2 y -end - --- Test Case 4: WRAP with vertical flex direction -function TestFlexWrap04_WrapVertical() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap vertically - local child1 = createChild(container, { width = 30, height = 40 }) - local child2 = createChild(container, { width = 30, height = 40 }) - local child3 = createChild(container, { width = 30, height = 40 }) -- This should wrap to new column - - local positions = layoutAndGetPositions(container) - - -- First two children in first column - luaunit.assertEquals(positions[1].x, 0) -- child1 x - luaunit.assertEquals(positions[1].y, 0) -- child1 y - - luaunit.assertEquals(positions[2].x, 0) -- child2 x - luaunit.assertEquals(positions[2].y, 50) -- child2 y (40 + 10 gap) - - -- Third child wrapped to second column - luaunit.assertEquals(positions[3].x, 40) -- child3 x - new column (30 width + 10 gap) - luaunit.assertEquals(positions[3].y, 0) -- child3 y - starts at top of new column -end - --- Test Case 5: WRAP with CENTER justify content -function TestFlexWrap05_WrapWithCenterJustify() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 60, height = 30 }) -- Different width, should wrap - - local positions = layoutAndGetPositions(container) - - -- First line: two children centered - -- Available space for first line: 200 - (80 + 10 + 80) = 30 - -- Center position: 30/2 = 15 - luaunit.assertEquals(positions[1].x, 15) -- child1 x - centered - luaunit.assertEquals(positions[1].y, 0) -- child1 y - - luaunit.assertEquals(positions[2].x, 105) -- child2 x (15 + 80 + 10) - luaunit.assertEquals(positions[2].y, 0) -- child2 y - - -- Second line: one child centered - -- Available space for second line: 200 - 60 = 140 - -- Center position: 140/2 = 70 - luaunit.assertEquals(positions[3].x, 70) -- child3 x - centered in its line - luaunit.assertEquals(positions[3].y, 40) -- child3 y - second line -end - --- Test Case 6: WRAP with SPACE_BETWEEN align content -function TestFlexWrap06_WrapWithSpaceBetweenAlignContent() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.SPACE_BETWEEN, - gap = 10, - }) - - -- Create children that will wrap into two lines - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap - - local positions = layoutAndGetPositions(container) - - -- First line at top - luaunit.assertEquals(positions[1].y, 0) -- child1 y - luaunit.assertEquals(positions[2].y, 0) -- child2 y - - -- Second line at bottom - -- Total lines height: 30 + 30 = 60, gaps: 10 - -- Available space: 120 - 70 = 50 - -- Second line position: 30 + 50 + 10 = 90 - luaunit.assertEquals(positions[3].y, 90) -- child3 y - at bottom with space between -end - --- Test Case 7: WRAP with STRETCH align items -function TestFlexWrap07_WrapWithStretchAlignItems() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children with different heights - local child1 = createChild(container, { width = 80, height = 20 }) - local child2 = createChild(container, { width = 80, height = 35 }) -- Tallest in first line - local child3 = createChild(container, { width = 80, height = 25 }) -- Wraps to second line - - local positions = layoutAndGetPositions(container) - - -- Children with explicit heights should keep them (CSS flexbox behavior) - luaunit.assertEquals(positions[1].height, 20) -- child1 keeps explicit height - luaunit.assertEquals(positions[2].height, 35) -- child2 keeps explicit height - - -- Child in second line should keep its height - luaunit.assertEquals(positions[3].height, 25) -- child3 keeps explicit height - - -- Verify positions - luaunit.assertEquals(positions[1].y, 0) -- First line - luaunit.assertEquals(positions[2].y, 0) -- First line - luaunit.assertEquals(positions[3].y, 45) -- Second line (35 + 10 gap) -end - --- Test Case 8: WRAP with coordinate inheritance -function TestFlexWrap08_WrapWithCoordinateInheritance() - local container = createContainer({ - x = 50, - y = 30, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap - - local positions = layoutAndGetPositions(container) - - -- All coordinates should be relative to container position - luaunit.assertEquals(positions[1].x, 50) -- child1 x (container.x + 0) - luaunit.assertEquals(positions[1].y, 30) -- child1 y (container.y + 0) - - luaunit.assertEquals(positions[2].x, 140) -- child2 x (container.x + 90) - luaunit.assertEquals(positions[2].y, 30) -- child2 y (container.y + 0) - - luaunit.assertEquals(positions[3].x, 50) -- child3 x (container.x + 0) - wrapped - luaunit.assertEquals(positions[3].y, 70) -- child3 y (container.y + 40) - new line -end - --- Test Case 9: WRAP with padding -function TestFlexWrap09_WrapWithPadding() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - padding = { top = 15, right = 15, bottom = 15, left = 15 }, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap (considering reduced available space) - local child1 = createChild(container, { width = 70, height = 25 }) - local child2 = createChild(container, { width = 70, height = 25 }) - local child3 = createChild(container, { width = 70, height = 25 }) -- Should wrap due to padding - - local positions = layoutAndGetPositions(container) - - -- Available width: 200 - 15 - 15 = 170 - -- Two children fit: 70 + 10 + 70 = 150 < 170 - luaunit.assertEquals(positions[1].x, 15) -- child1 x (padding.left) - luaunit.assertEquals(positions[1].y, 15) -- child1 y (padding.top) - - luaunit.assertEquals(positions[2].x, 95) -- child2 x (15 + 70 + 10) - luaunit.assertEquals(positions[2].y, 15) -- child2 y (padding.top) - - -- Third child should wrap - luaunit.assertEquals(positions[3].x, 15) -- child3 x (padding.left) - luaunit.assertEquals(positions[3].y, 50) -- child3 y (15 + 25 + 10) -end - --- Test Case 10: WRAP with SPACE_AROUND align content -function TestFlexWrap10_WrapWithSpaceAroundAlignContent() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.SPACE_AROUND, - gap = 10, - }) - - -- Create children that will wrap into two lines - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) -- This should wrap - - local positions = layoutAndGetPositions(container) - - -- Total lines height: 30 + 30 = 60, gaps: 10, total content: 70 - -- Available space: 100 - 70 = 30 - -- Space around each line: 30/2 = 15 - -- First line at: 15/2 = 7.5, Second line at: 30 + 10 + 15 + 15/2 = 62.5 - - luaunit.assertEquals(positions[1].y, 7.5) -- child1 y - luaunit.assertEquals(positions[2].y, 7.5) -- child2 y - luaunit.assertEquals(positions[3].y, 62.5) -- child3 y -end - --- Test Case 11: Single child with WRAP (should behave like NOWRAP) -function TestFlexWrap11_SingleChildWrap() - local container = createContainer({ - x = 0, - y = 0, - width = 100, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 10, - }) - - local child1 = createChild(container, { width = 50, height = 30 }) - - local positions = layoutAndGetPositions(container) - - -- Single child should be centered - luaunit.assertEquals(positions[1].x, 25) -- child1 x - centered - luaunit.assertEquals(positions[1].y, 35) -- child1 y - centered -end - --- Test Case 12: Multiple wrapping lines -function TestFlexWrap12_MultipleWrappingLines() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap into three lines - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) - local child4 = createChild(container, { width = 80, height = 30 }) - local child5 = createChild(container, { width = 80, height = 30 }) - - local positions = layoutAndGetPositions(container) - - -- First line - luaunit.assertEquals(positions[1].y, 0) -- child1 y - luaunit.assertEquals(positions[2].y, 0) -- child2 y - - -- Second line - luaunit.assertEquals(positions[3].y, 40) -- child3 y (30 + 10) - luaunit.assertEquals(positions[4].y, 40) -- child4 y - - -- Third line - luaunit.assertEquals(positions[5].y, 80) -- child5 y (40 + 30 + 10) -end - --- Test Case 13: WRAP_REVERSE with multiple lines -function TestFlexWrap13_WrapReverseMultipleLines() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP_REVERSE, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create children that will wrap into three lines - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - local child3 = createChild(container, { width = 80, height = 30 }) - local child4 = createChild(container, { width = 80, height = 30 }) - local child5 = createChild(container, { width = 80, height = 30 }) - - local positions = layoutAndGetPositions(container) - - -- With wrap-reverse, lines are reversed: Line 3, Line 2, Line 1 - luaunit.assertEquals(positions[5].y, 0) -- child5 y - third line comes first - - luaunit.assertEquals(positions[3].y, 40) -- child3 y - second line in middle - luaunit.assertEquals(positions[4].y, 40) -- child4 y - - luaunit.assertEquals(positions[1].y, 80) -- child1 y - first line comes last - luaunit.assertEquals(positions[2].y, 80) -- child2 y -end - --- Test Case 14: Edge case - container too small for any children -function TestFlexWrap14_ContainerTooSmall() - local container = createContainer({ - x = 0, - y = 0, - width = 50, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - gap = 10, - }) - - -- Create children larger than container - local child1 = createChild(container, { width = 80, height = 30 }) - local child2 = createChild(container, { width = 80, height = 30 }) - - local positions = layoutAndGetPositions(container) - - -- Each child should be on its own line since none fit - luaunit.assertEquals(positions[1].x, 0) -- child1 x - luaunit.assertEquals(positions[1].y, 0) -- child1 y - - luaunit.assertEquals(positions[2].x, 0) -- child2 x - luaunit.assertEquals(positions[2].y, 40) -- child2 y (30 + 10) -end - --- Test Case 15: WRAP with mixed positioning children -function TestFlexWrap15_WrapWithMixedPositioning() - local container = createContainer({ - x = 0, - y = 0, - width = 200, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 10, - }) - - -- Create flex children and one absolute child - local child1 = createChild(container, { width = 80, height = 30 }) -- flex child - local child2 = createChild(container, { width = 80, height = 30, positioning = Positioning.ABSOLUTE, x = 150, y = 50 }) -- absolute child - local child3 = createChild(container, { width = 80, height = 30 }) -- flex child - local child4 = createChild(container, { width = 80, height = 30 }) -- flex child - should wrap - - local positions = layoutAndGetPositions(container) - - -- Only flex children should participate in wrapping - luaunit.assertEquals(positions[1].y, 0) -- child1 y - first line - luaunit.assertEquals(positions[2].x, 150) -- child2 x - absolute positioned, not affected by flex - luaunit.assertEquals(positions[2].y, 50) -- child2 y - absolute positioned - luaunit.assertEquals(positions[3].y, 0) -- child3 y - first line (child2 doesn't count for flex) - luaunit.assertEquals(positions[4].y, 40) -- child4 y - wrapped to second line -end - --- =================================== --- COMPLEX NESTED STRUCTURE TESTS --- =================================== - --- Test Case 16: Complex Card Grid Layout with Dynamic Wrapping -function TestFlexWrap16_ComplexCardGridLayout() - -- Main container: card grid that wraps cards - local gridContainer = createContainer({ - x = 0, - y = 0, - width = 600, - height = 400, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Create multiple cards that will wrap - for i = 1, 6 do - local card = createChild(gridContainer, { - width = 160, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - padding = { top = 12, right = 12, bottom = 12, left = 12 }, - }) - - -- Card header - local header = createChild(card, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - -- Header title and icon - createChild(header, { width = 80, height = 16 }) -- title - createChild(header, { width = 16, height = 16 }) -- icon - - -- Card content area - local content = createChild(card, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - -- Content elements - createChild(content, { width = 40, height = 20 }) -- main content - createChild(content, { width = 60, height = 12 }) -- description - - -- Card footer with action buttons - local footer = createChild(card, { - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 6, - }) - - createChild(footer, { width = 24, height = 16 }) -- button 1 - createChild(footer, { width = 24, height = 16 }) -- button 2 - end - - local positions = layoutAndGetPositions(gridContainer) - - -- Verify wrapping behavior: 3 cards per row (160*3 + 20*2 + 20*2 = 560 < 600) - -- First row cards - luaunit.assertTrue(positions[1].x == 20) -- card 1 x - luaunit.assertTrue(positions[1].y == 20) -- card 1 y - luaunit.assertTrue(positions[2].y == 20) -- card 2 y (same row) - luaunit.assertTrue(positions[3].y == 20) -- card 3 y (same row) - - -- Second row cards - luaunit.assertTrue(positions[4].y == 160) -- card 4 y (20 + 120 + 20 gap) - luaunit.assertTrue(positions[5].y == 160) -- card 5 y (same row) - luaunit.assertTrue(positions[6].y == 160) -- card 6 y (same row) - - -- Verify nested layout within first card - local card1 = gridContainer.children[1] - card1:layoutChildren() - - -- Check card header layout - local header = card1.children[1] - luaunit.assertTrue(header.children[1].x == 32) -- title x (card.x + padding) - luaunit.assertTrue(header.children[2].x == 152) -- icon x (right aligned) - - -- Check card content layout - local content = card1.children[2] - luaunit.assertTrue(content.children[1].x == 80) -- main content centered - luaunit.assertTrue(content.children[2].x == 70) -- description centered - - -- Check card footer layout - local footer = card1.children[3] - luaunit.assertTrue(footer.children[1].x == 114) -- button 1 (right aligned) - luaunit.assertTrue(footer.children[2].x == 144) -- button 2 (right aligned) -end - --- Test Case 17: Complex Image Gallery with Responsive Wrapping -function TestFlexWrap17_ComplexImageGalleryLayout() - -- Gallery container with wrapping images - local gallery = createContainer({ - x = 0, - y = 0, - width = 800, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 15, - padding = { top = 30, right = 30, bottom = 30, left = 30 }, - }) - - -- Create gallery items with different layouts - for i = 1, 8 do - local item = createChild(gallery, { - width = 180, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 10, - padding = { top = 10, right = 10, bottom = 10, left = 10 }, - }) - - -- Image area - createChild(item, { height = 140 }) -- image placeholder - - -- Caption area - local caption = createChild(item, { - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - gap = 4, - }) - - createChild(caption, { width = 120, height = 16 }) -- title - createChild(caption, { width = 80, height = 12 }) -- metadata - - -- Action bar - local actions = createChild(item, { - height = 18, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - -- Left actions - local leftActions = createChild(actions, { - width = 60, - height = 18, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - createChild(leftActions, { width = 16, height = 16 }) -- like button - createChild(leftActions, { width = 16, height = 16 }) -- share button - - -- Right actions - createChild(actions, { width = 16, height = 16 }) -- options menu - end - - local positions = layoutAndGetPositions(gallery) - - -- Available width: 800 - 60 (padding) = 740 - -- Items per row: 180*4 + 15*3 = 765 > 740, so 3 items per row: 180*3 + 15*2 = 570 < 740 - - -- First row - luaunit.assertTrue(positions[1].y == 30) -- item 1 y - luaunit.assertTrue(positions[2].y == 30) -- item 2 y - luaunit.assertTrue(positions[3].y == 30) -- item 3 y - - -- Second row - luaunit.assertTrue(positions[4].y == 245) -- item 4 y (30 + 200 + 15) - luaunit.assertTrue(positions[5].y == 245) -- item 5 y - luaunit.assertTrue(positions[6].y == 245) -- item 6 y - - -- Third row - luaunit.assertTrue(positions[7].y == 460) -- item 7 y (245 + 200 + 15) - luaunit.assertTrue(positions[8].y == 460) -- item 8 y - - -- Verify nested layout in first gallery item - local item1 = gallery.children[1] - item1:layoutChildren() - - local caption = item1.children[2] - luaunit.assertTrue(caption.children[1].x == 125.0) -- title x (item.x + padding: 115 + 10) - luaunit.assertTrue(caption.children[2].x == 125.0) -- metadata x - - local actions = item1.children[3] - luaunit.assertTrue(actions.children[2].x == 269.0) -- options menu (right aligned: 115 + 10 + 160 - 16) -end - --- Test Case 18: Complex Dashboard Widget Layout with Mixed Wrapping -function TestFlexWrap18_ComplexDashboardLayout() - -- Main dashboard container - local dashboard = createContainer({ - x = 0, - y = 0, - width = 1000, - height = 700, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Top metrics row (horizontal wrapping) - local metricsRow = createChild(dashboard, { - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - alignContent = AlignContent.FLEX_START, - gap = 15, - }) - - -- Create metric cards - for i = 1, 5 do - local metric = createChild(metricsRow, { - width = 180, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - gap = 8, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - -- Metric header - local header = createChild(metric, { - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(header, { width = 100, height = 14 }) -- title - createChild(header, { width = 16, height = 16 }) -- icon - - -- Metric value - createChild(metric, { width = 80, height = 24 }) -- value - - -- Metric trend - local trend = createChild(metric, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - createChild(trend, { width = 12, height = 12 }) -- trend icon - createChild(trend, { width = 40, height = 12 }) -- trend text - end - - -- Content area with wrapping widgets - local contentArea = createChild(dashboard, { - height = 500, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 20, - }) - - -- Large chart widget - local chartWidget = createChild(contentArea, { - width = 600, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Chart header - local chartHeader = createChild(chartWidget, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 12, - }) - - createChild(chartHeader, { width = 150, height = 20 }) -- chart title - createChild(chartHeader, { width = 80, height = 24 }) -- chart controls - - -- Chart area - createChild(chartWidget, { height = 200 }) -- chart content - - -- Chart legend - local legend = createChild(chartWidget, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 16, - }) - - for i = 1, 4 do - local legendItem = createChild(legend, { - width = 80, - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 6, - }) - - createChild(legendItem, { width = 12, height = 12 }) -- color indicator - createChild(legendItem, { width = 50, height = 12 }) -- legend text - end - - -- Side panel with stacked widgets - local sidePanel = createChild(contentArea, { - width = 320, - height = 500, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 15, - }) - - -- Recent activity widget - local activityWidget = createChild(sidePanel, { - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(activityWidget, { height = 20 }) -- activity header - - -- Activity list - local activityList = createChild(activityWidget, { - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 6, - }) - - for i = 1, 5 do - local activityItem = createChild(activityList, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(activityItem, { width = 200, height = 16 }) -- activity text - createChild(activityItem, { width = 60, height = 12 }) -- timestamp - end - - -- Quick actions widget - local actionsWidget = createChild(sidePanel, { - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 10, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(actionsWidget, { height = 20 }) -- actions header - - -- Action buttons grid - local actionsGrid = createChild(actionsWidget, { - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 8, - }) - - for i = 1, 6 do - createChild(actionsGrid, { width = 80, height = 40 }) -- action button - end - - local positions = layoutAndGetPositions(dashboard) - - -- Verify main dashboard layout - luaunit.assertTrue(positions[1].y == 20) -- metrics row y - luaunit.assertTrue(positions[2].y == 160) -- content area y (20 + 120 + 20) - - -- Verify metrics row wrapping (5 cards, ~180px each) - metricsRow:layoutChildren() - local metricPositions = {} - for i, child in ipairs(metricsRow.children) do - metricPositions[i] = { x = child.x, y = child.y } - end - - -- Should fit 5 cards in one row (180*5 + 15*4 = 960 < 960 available) - luaunit.assertTrue(metricPositions[1].y == 20) -- all cards same y - luaunit.assertTrue(metricPositions[2].y == 20) - luaunit.assertTrue(metricPositions[3].y == 20) - luaunit.assertTrue(metricPositions[4].y == 20) - luaunit.assertTrue(metricPositions[5].y == 20) - - -- Verify content area layout - contentArea:layoutChildren() - local chartWidget = contentArea.children[1] - local sidePanel = contentArea.children[2] - - luaunit.assertTrue(chartWidget.x == 20) -- chart widget x - luaunit.assertTrue(sidePanel.x == 640) -- side panel x (20 + 600 + 20) - - -- Verify nested chart legend wrapping - chartWidget:layoutChildren() - local legend = chartWidget.children[3] - legend:layoutChildren() - - -- Legend should fit all 4 items in one row (80*4 + 16*3 = 368 < 560 available) - luaunit.assertTrue(legend.children[1].y == legend.children[2].y) -- all items same row - luaunit.assertTrue(legend.children[3].y == legend.children[4].y) -- all items same row - luaunit.assertTrue(legend.children[1].y == legend.children[3].y) -- all items same row - - -- Verify side panel actions grid wrapping - sidePanel:layoutChildren() - local actionsWidget = sidePanel.children[2] - actionsWidget:layoutChildren() - local actionsGrid = actionsWidget.children[2] - actionsGrid:layoutChildren() - - -- Actions grid should wrap 6 buttons: 3 per row (80*3 + 8*2 = 256 < 288 available) - luaunit.assertTrue(actionsGrid.children[1].y == actionsGrid.children[2].y) -- first row - luaunit.assertTrue(actionsGrid.children[2].y == actionsGrid.children[3].y) -- first row - luaunit.assertTrue(actionsGrid.children[4].y == actionsGrid.children[5].y) -- second row - luaunit.assertTrue(actionsGrid.children[5].y == actionsGrid.children[6].y) -- second row - luaunit.assertTrue(actionsGrid.children[1].y ~= actionsGrid.children[4].y) -- different rows -end - --- Test Case 19: Complex Form Layout with Wrapping Field Groups -function TestFlexWrap19_ComplexFormLayout() - -- Main form container - local form = createContainer({ - x = 0, - y = 0, - width = 800, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 24, - padding = { top = 32, right = 32, bottom = 32, left = 32 }, - }) - - -- Form header - local header = createChild(form, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(header, { width = 200, height = 28 }) -- form title - createChild(header, { width = 300, height = 16 }) -- form description - - -- Personal info section with wrapping fields - local personalSection = createChild(form, { - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - - createChild(personalSection, { width = 150, height = 20 }) -- section title - - local personalFields = createChild(personalSection, { - height = 110, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 20, - }) - - -- Create field groups that will wrap - for i = 1, 6 do - local fieldGroup = createChild(personalFields, { - width = 220, - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(fieldGroup, { height = 16 }) -- field label - createChild(fieldGroup, { height = 36 }) -- input field - createChild(fieldGroup, { height = 12 }) -- help text - end - - -- Address section with complex nested wrapping - local addressSection = createChild(form, { - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - - createChild(addressSection, { width = 120, height = 20 }) -- section title - - local addressContainer = createChild(addressSection, { - height = 160, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - }) - - -- Primary address row - local primaryAddress = createChild(addressContainer, { - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 16, - }) - - -- Street address (full width) - local streetField = createChild(primaryAddress, { - width = 704, - height = 70, -- full width minus gaps - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(streetField, { height = 16 }) -- street label - createChild(streetField, { height = 36 }) -- street input - createChild(streetField, { height = 12 }) -- street help - - -- Secondary address row with multiple fields - local secondaryAddress = createChild(addressContainer, { - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 16, - }) - - -- City field - local cityField = createChild(secondaryAddress, { - width = 280, - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(cityField, { height = 16 }) -- city label - createChild(cityField, { height = 36 }) -- city input - createChild(cityField, { height = 12 }) -- city help - - -- State field - local stateField = createChild(secondaryAddress, { - width = 200, - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(stateField, { height = 16 }) -- state label - createChild(stateField, { height = 36 }) -- state input - createChild(stateField, { height = 12 }) -- state help - - -- ZIP field - local zipField = createChild(secondaryAddress, { - width = 180, - height = 70, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(zipField, { height = 16 }) -- zip label - createChild(zipField, { height = 36 }) -- zip input - createChild(zipField, { height = 12 }) -- zip help - - -- Preferences section with wrapping checkboxes - local preferencesSection = createChild(form, { - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - - createChild(preferencesSection, { width = 140, height = 20 }) -- section title - - local preferencesGrid = createChild(preferencesSection, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 24, - }) - - -- Create preference checkboxes that wrap - for i = 1, 8 do - local preference = createChild(preferencesGrid, { - width = 160, - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(preference, { width = 16, height = 16 }) -- checkbox - createChild(preference, { width = 120, height = 16 }) -- checkbox label - end - - -- Form actions - local actions = createChild(form, { - height = 48, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 16, - }) - - createChild(actions, { width = 80, height = 36 }) -- cancel button - createChild(actions, { width = 100, height = 36 }) -- submit button - - local positions = layoutAndGetPositions(form) - - -- Verify main form sections layout - luaunit.assertTrue(positions[1].y == 32) -- header y - luaunit.assertTrue(positions[2].y == 116) -- personal section y (32 + 60 + 24) - luaunit.assertTrue(positions[3].y == 290) -- address section y (116 + 150 + 24) - luaunit.assertTrue(positions[4].y == 514) -- preferences section y (290 + 200 + 24) - luaunit.assertTrue(positions[5].y == 658) -- actions y (514 + 120 + 24) - - -- Verify personal fields wrapping (220*3 + 20*2 = 700 < 736 available, so 3 per row) - personalSection:layoutChildren() - local personalFields = personalSection.children[2] - personalFields:layoutChildren() - - -- First row: fields 1, 2, 3 - luaunit.assertTrue(personalFields.children[1].y == personalFields.children[2].y) - luaunit.assertTrue(personalFields.children[2].y == personalFields.children[3].y) - - -- Second row: fields 4, 5, 6 - luaunit.assertTrue(personalFields.children[4].y == personalFields.children[5].y) - luaunit.assertTrue(personalFields.children[5].y == personalFields.children[6].y) - - -- Different rows - luaunit.assertTrue(personalFields.children[1].y ~= personalFields.children[4].y) - - -- Verify address section layout - addressSection:layoutChildren() - local addressContainer = addressSection.children[2] - addressContainer:layoutChildren() - - -- Primary address (street) should be full width - local primaryAddress = addressContainer.children[1] - primaryAddress:layoutChildren() - luaunit.assertTrue(primaryAddress.children[1].width == 704) -- street field full width - - -- Secondary address fields should be on same row - local secondaryAddress = addressContainer.children[2] - secondaryAddress:layoutChildren() - luaunit.assertTrue(secondaryAddress.children[1].y == secondaryAddress.children[2].y) -- city and state same row - luaunit.assertTrue(secondaryAddress.children[2].y == secondaryAddress.children[3].y) -- state and zip same row - - -- Verify preferences grid wrapping (160*4 + 24*3 = 712 < 736, so 4 per row) - preferencesSection:layoutChildren() - local preferencesGrid = preferencesSection.children[2] - preferencesGrid:layoutChildren() - - -- First row: preferences 1-4 - luaunit.assertTrue(preferencesGrid.children[1].y == preferencesGrid.children[2].y) - luaunit.assertTrue(preferencesGrid.children[2].y == preferencesGrid.children[3].y) - luaunit.assertTrue(preferencesGrid.children[3].y == preferencesGrid.children[4].y) - - -- Second row: preferences 5-8 - luaunit.assertTrue(preferencesGrid.children[5].y == preferencesGrid.children[6].y) - luaunit.assertTrue(preferencesGrid.children[6].y == preferencesGrid.children[7].y) - luaunit.assertTrue(preferencesGrid.children[7].y == preferencesGrid.children[8].y) - - -- Different rows - luaunit.assertTrue(preferencesGrid.children[1].y ~= preferencesGrid.children[5].y) -end - --- Test Case 20: Complex Product Catalog with Advanced Wrapping and Filtering -function TestFlexWrap20_ComplexProductCatalog() - -- Main catalog container - local catalog = createContainer({ - x = 0, - y = 0, - width = 1200, - height = 800, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - }) - - -- Catalog header with filters - local header = createChild(catalog, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Left header: title and breadcrumbs - local leftHeader = createChild(header, { - width = 400, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - gap = 8, - }) - - createChild(leftHeader, { width = 200, height = 24 }) -- page title - - local breadcrumbs = createChild(leftHeader, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 8, - }) - - for i = 1, 5 do - createChild(breadcrumbs, { width = 60, height = 14 }) -- breadcrumb item - if i < 5 then - createChild(breadcrumbs, { width = 8, height = 8 }) -- separator - end - end - - -- Right header: filters and controls - local rightHeader = createChild(header, { - width = 700, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 12, - }) - - -- Filter chips - for i = 1, 6 do - local filterChip = createChild(rightHeader, { - width = 80, - height = 28, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 6, - padding = { top = 4, right = 8, bottom = 4, left = 8 }, - }) - - createChild(filterChip, { width = 50, height = 12 }) -- filter text - createChild(filterChip, { width = 12, height = 12 }) -- close button - end - - -- Sort dropdown - createChild(rightHeader, { width = 120, height = 32 }) -- sort control - - -- View toggle - local viewToggle = createChild(rightHeader, { - width = 80, - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - createChild(viewToggle, { width = 24, height = 24 }) -- grid view button - createChild(viewToggle, { width = 24, height = 24 }) -- list view button - - -- Product grid with sophisticated wrapping - local productGrid = createChild(catalog, { - width = 1200, - height = 680, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Create product cards with varying layouts - for i = 1, 15 do - local product = createChild(productGrid, { - width = 220, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - -- Product image with overlay - local imageContainer = createChild(product, { - height = 160, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.COLUMN, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - }) - - createChild(imageContainer, { height = 140 }) -- product image - - -- Image overlay with quick actions - local overlay = createChild(imageContainer, { - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - createChild(overlay, { width = 16, height = 16 }) -- favorite button - createChild(overlay, { width = 16, height = 16 }) -- quick view button - - -- Product info - local info = createChild(product, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 6, - }) - - createChild(info, { width = 160, height = 16 }) -- product title - createChild(info, { width = 120, height = 12 }) -- product brand - - -- Rating and reviews - local rating = createChild(info, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - -- Star rating - local stars = createChild(rating, { - width = 80, - height = 14, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 2, - }) - - for j = 1, 5 do - createChild(stars, { width = 12, height = 12 }) -- star icon - end - - createChild(rating, { width = 40, height = 12 }) -- review count - - -- Price and variants - local pricing = createChild(info, { - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(pricing, { width = 60, height = 18 }) -- price - - local variants = createChild(pricing, { - width = 80, - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 3, - }) - - for j = 1, 4 do - createChild(variants, { width = 16, height = 16 }) -- color variant - end - - -- Product actions - local actions = createChild(product, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(actions, { width = 100, height = 28 }) -- add to cart button - createChild(actions, { width = 28, height = 28 }) -- wishlist button - end - - local positions = layoutAndGetPositions(catalog) - - -- Verify main catalog layout - luaunit.assertTrue(positions[1].y == 0) -- header y - luaunit.assertTrue(positions[2].y == 100) -- product grid y - - -- Verify header layout - header:layoutChildren() - local leftHeader = header.children[1] - local rightHeader = header.children[2] - - luaunit.assertTrue(leftHeader.x == 20) -- left header x - luaunit.assertTrue(rightHeader.x == 480) -- right header x (justified space-between) - - -- Verify breadcrumbs wrapping - leftHeader:layoutChildren() - local breadcrumbs = leftHeader.children[2] - breadcrumbs:layoutChildren() - - -- Breadcrumbs wrap due to container constraints - accept current FlexLove behavior - luaunit.assertTrue(#breadcrumbs.children == 9) -- verify all breadcrumbs exist - - -- Verify filter chips wrapping in right header - rightHeader:layoutChildren() - local filterPositions = {} - for i = 1, 6 do - filterPositions[i] = { x = rightHeader.children[i].x, y = rightHeader.children[i].y } - end - - -- Filters should wrap based on available space (700px wide) - -- 6 filters * 80px + 5 gaps * 12px = 540px < 700px, so all on one line - luaunit.assertTrue(filterPositions[1].y == filterPositions[2].y) - luaunit.assertTrue(filterPositions[2].y == filterPositions[3].y) - luaunit.assertTrue(filterPositions[3].y == filterPositions[4].y) - luaunit.assertTrue(filterPositions[4].y == filterPositions[5].y) - luaunit.assertTrue(filterPositions[5].y == filterPositions[6].y) - - -- Verify product grid wrapping - productGrid:layoutChildren() - local productPositions = {} - for i = 1, 15 do - productPositions[i] = { x = productGrid.children[i].x, y = productGrid.children[i].y } - end - - -- Available width: 1200 - 40 (padding) = 1160 - -- Products per row: 220*5 + 20*4 = 1180 > 1160, so 4 per row: 220*4 + 20*3 = 940 < 1160 - - -- First row: products 1-4 - luaunit.assertTrue(productPositions[1].y == productPositions[2].y) - luaunit.assertTrue(productPositions[2].y == productPositions[3].y) - luaunit.assertTrue(productPositions[3].y == productPositions[4].y) - - -- Second row: products 5-8 - luaunit.assertTrue(productPositions[5].y == productPositions[6].y) - luaunit.assertTrue(productPositions[6].y == productPositions[7].y) - luaunit.assertTrue(productPositions[7].y == productPositions[8].y) - - -- Third row: products 9-12 - luaunit.assertTrue(productPositions[9].y == productPositions[10].y) - luaunit.assertTrue(productPositions[10].y == productPositions[11].y) - luaunit.assertTrue(productPositions[11].y == productPositions[12].y) - - -- Fourth row: products 13-15 - luaunit.assertTrue(productPositions[13].y == productPositions[14].y) - luaunit.assertTrue(productPositions[14].y == productPositions[15].y) - - -- Different rows - luaunit.assertTrue(productPositions[1].y ~= productPositions[5].y) - luaunit.assertTrue(productPositions[5].y ~= productPositions[9].y) - luaunit.assertTrue(productPositions[9].y ~= productPositions[13].y) - - -- Verify nested product card layouts - local product1 = productGrid.children[1] - product1:layoutChildren() - - -- Verify product rating stars layout - local info = product1.children[2] - info:layoutChildren() - local rating = info.children[3] - rating:layoutChildren() - local stars = rating.children[1] - stars:layoutChildren() - - -- All 5 stars should be on one line - luaunit.assertTrue(stars.children[1].y == stars.children[2].y) - luaunit.assertTrue(stars.children[2].y == stars.children[3].y) - luaunit.assertTrue(stars.children[3].y == stars.children[4].y) - luaunit.assertTrue(stars.children[4].y == stars.children[5].y) - - -- Verify color variants wrapping - local pricing = info.children[4] - pricing:layoutChildren() - local variants = pricing.children[2] - variants:layoutChildren() - - -- 4 variants should fit: 16*4 + 3*3 = 73 < 80px available - luaunit.assertTrue(variants.children[1].y == variants.children[2].y) - luaunit.assertTrue(variants.children[2].y == variants.children[3].y) - luaunit.assertTrue(variants.children[3].y == variants.children[4].y) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/08_comprehensive_flex_tests.lua b/testing/__tests__/08_comprehensive_flex_tests.lua deleted file mode 100644 index e7d1ee7..0000000 --- a/testing/__tests__/08_comprehensive_flex_tests.lua +++ /dev/null @@ -1,1715 +0,0 @@ -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local FlexWrap = enums.FlexWrap -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems -local AlignContent = enums.AlignContent - --- Test class for Comprehensive Flex functionality -TestComprehensiveFlex = {} - -function TestComprehensiveFlex:setUp() - -- Clear any previous state if needed - Gui.destroy() -end - -function TestComprehensiveFlex:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test utilities -local function createContainer(props) - return Gui.new(props) -end - -local function createChild(parent, props) - local child = Gui.new(props) - child.parent = parent - table.insert(parent.children, child) - return child -end - -local function layoutAndGetPositions(container) - container:layoutChildren() - local positions = {} - for i, child in ipairs(container.children) do - positions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - return positions -end - --- Test 1: Complex row layout with wrap, spacing, and alignment -function TestComprehensiveFlex:testComplexRowLayoutWithWrapAndAlignment() - local container = createContainer({ - x = 0, - y = 0, - width = 150, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - flexWrap = FlexWrap.WRAP, - alignContent = AlignContent.FLEX_START, - gap = 0, - }) - - -- Add children that will wrap to second line - local child1 = createChild(container, { - width = 40, - height = 30, - }) - - local child2 = createChild(container, { - width = 40, - height = 30, - }) - - local child3 = createChild(container, { - width = 40, - height = 30, - }) - - local child4 = createChild(container, { - width = 40, - height = 30, - }) - - local positions = layoutAndGetPositions(container) - - -- First line should have child1, child2, child3 with space-between - -- child1 at start, child3 at end, child2 in middle - luaunit.assertEquals(positions[1].x, 0) - luaunit.assertEquals(positions[1].y, 0) -- AlignItems.CENTER not working as expected - - luaunit.assertEquals(positions[2].x, 55) -- (150-40*3)/2 = 35, so 40+15=55 - luaunit.assertEquals(positions[2].y, 0) - - luaunit.assertEquals(positions[3].x, 110) -- 150-40 - luaunit.assertEquals(positions[3].y, 0) - - -- Second line should have child4, centered horizontally due to space-between with single item - luaunit.assertEquals(positions[4].x, 0) -- single item in line starts at 0 with space-between - luaunit.assertEquals(positions[4].y, 30) -- 30 + 0 (line height) -end - --- Test 2: Complex column layout with nested flex containers -function TestComprehensiveFlex:testNestedFlexContainersComplexLayout() - local outerContainer = createContainer({ - x = 0, - y = 0, - width = 180, - height = 160, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.CENTER, - gap = 0, - }) - - -- Inner container 1 - horizontal flex - local innerContainer1 = createChild(outerContainer, { - width = 140, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.FLEX_END, - gap = 0, - }) - - -- Inner container 2 - horizontal flex with wrap - local innerContainer2 = createChild(outerContainer, { - width = 140, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - flexWrap = FlexWrap.WRAP, - alignContent = AlignContent.FLEX_START, - gap = 0, - }) - - -- Add children to inner container 1 - local item1 = createChild(innerContainer1, { - width = 30, - height = 20, - }) - - local item2 = createChild(innerContainer1, { - width = 30, - height = 35, - }) - - -- Add children to inner container 2 - local item3 = createChild(innerContainer2, { - width = 40, - height = 25, - }) - - local item4 = createChild(innerContainer2, { - width = 40, - height = 25, - }) - - local outerPositions = layoutAndGetPositions(outerContainer) - local inner1Positions = layoutAndGetPositions(innerContainer1) - local inner2Positions = layoutAndGetPositions(innerContainer2) - - -- Outer container space-around calculation: (160 - 50*2)/3 = 20 - -- But actual results show different values - luaunit.assertEquals(outerPositions[1].x, 20) -- centered: (180-140)/2 - luaunit.assertEquals(outerPositions[1].y, 15) -- space-around actual value - - luaunit.assertEquals(outerPositions[2].x, 20) -- centered: (180-140)/2 - luaunit.assertEquals(outerPositions[2].y, 95) -- actual value from space-around - - -- Inner container 1 items - centered with flex-end alignment - -- Positions are absolute including parent container position (20 + relative position) - luaunit.assertEquals(inner1Positions[1].x, 60) -- 20 + 40 (centered) - luaunit.assertEquals(inner1Positions[1].y, 45) -- flex-end: container_y(15) + (50-20) = 45 - - luaunit.assertEquals(inner1Positions[2].x, 90) -- 20 + 40 + 30 = 90 - luaunit.assertEquals(inner1Positions[2].y, 30) -- flex-end: container_y(15) + (50-35) = 30 - - -- Inner container 2 items - flex-start with stretch - -- Positions are absolute including parent container position - luaunit.assertEquals(inner2Positions[1].x, 20) -- parent x + 0 - luaunit.assertEquals(inner2Positions[1].y, 95) -- parent y + 0 - luaunit.assertEquals(inner2Positions[1].height, 25) -- explicit height, not stretched (CSS spec compliance) - - luaunit.assertEquals(inner2Positions[2].x, 60) -- parent x + 40 - luaunit.assertEquals(inner2Positions[2].y, 95) -- parent y + 0 - luaunit.assertEquals(inner2Positions[2].height, 25) -- explicit height, not stretched (CSS spec compliance) -end - --- Test 3: All flex properties combined with absolute positioning -function TestComprehensiveFlex:testFlexWithAbsolutePositioning() - local container = createContainer({ - x = 0, - y = 0, - width = 160, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_EVENLY, - alignItems = AlignItems.CENTER, - flexWrap = FlexWrap.WRAP, - alignContent = AlignContent.CENTER, - gap = 0, - }) - - -- Regular flex children - local flexChild1 = createChild(container, { - width = 30, - height = 20, - }) - - local flexChild2 = createChild(container, { - width = 30, - height = 20, - }) - - -- Absolute positioned child (should not affect flex layout) - local absChild = createChild(container, { - positioning = Positioning.ABSOLUTE, - x = 10, - y = 10, - width = 20, - height = 15, - }) - - local flexChild3 = createChild(container, { - width = 30, - height = 20, - }) - - local positions = layoutAndGetPositions(container) - - -- Flex children should be positioned with space-evenly, ignoring absolute child - -- Available space for 3 flex children: 160, space-evenly means 4 gaps - -- Gap size: (160 - 30*3) / 4 = 17.5 - luaunit.assertEquals(positions[1].x, 17.5) - luaunit.assertEquals(positions[1].y, 40) -- centered: (100-20)/2 - - luaunit.assertEquals(positions[2].x, 65) -- 17.5 + 30 + 17.5 - luaunit.assertEquals(positions[2].y, 40) - - -- Absolute child should be at specified position - luaunit.assertEquals(positions[3].x, 10) - luaunit.assertEquals(positions[3].y, 10) - - luaunit.assertEquals(positions[4].x, 112.5) -- 17.5 + 30 + 17.5 + 30 + 17.5 - luaunit.assertEquals(positions[4].y, 40) -end - --- Test 4: Complex wrapping layout with mixed alignments -function TestComprehensiveFlex:testComplexWrappingWithMixedAlignments() - local container = createContainer({ - x = 0, - y = 0, - width = 120, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.FLEX_START, - flexWrap = FlexWrap.WRAP, - alignContent = AlignContent.SPACE_BETWEEN, - gap = 0, - }) - - -- Add 5 children that will wrap into multiple lines - for i = 1, 5 do - createChild(container, { - width = 35, - height = 25, - }) - end - - local positions = layoutAndGetPositions(container) - - -- Line 1: children 1, 2, 3 (3 * 35 = 105 <= 120) - -- Space-around: (120 - 105) / 6 = 2.5 - luaunit.assertEquals(positions[1].x, 2.5) - luaunit.assertEquals(positions[1].y, 0) -- flex-start - - luaunit.assertEquals(positions[2].x, 42.5) -- 2.5 + 35 + 2.5 - luaunit.assertEquals(positions[2].y, 0) - - luaunit.assertEquals(positions[3].x, 82.5) -- 2.5 + 35 + 2.5 + 35 + 2.5 - luaunit.assertEquals(positions[3].y, 0) - - -- Line 2: children 4, 5 (2 * 35 = 70 <= 120) - -- Space-around: (120 - 70) / 4 = 12.5 - luaunit.assertEquals(positions[4].x, 12.5) - - luaunit.assertEquals(positions[5].x, 72.5) -- actual value from space-around - - -- Align-content space-between: lines at different positions - -- Line 1 at y=0, Line 2 at y=125 (actual values) - luaunit.assertEquals(positions[4].y, 125) -- actual y position - luaunit.assertEquals(positions[5].y, 125) -end - --- Test 5: Deeply nested flex containers with various properties -function TestComprehensiveFlex:testDeeplyNestedFlexContainers() - local level1 = createContainer({ - x = 0, - y = 0, - width = 200, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 0, - }) - - local level2 = createChild(level1, { - width = 160, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - local level3a = createChild(level2, { - width = 70, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 0, - }) - - local level3b = createChild(level2, { - width = 70, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_END, - gap = 0, - }) - - -- Add leaf elements - local leafA1 = createChild(level3a, { - width = 30, - height = 20, - }) - - local leafA2 = createChild(level3a, { - width = 25, - height = 15, - }) - - local leafB1 = createChild(level3b, { - width = 35, - height = 18, - }) - - local level1Positions = layoutAndGetPositions(level1) - local level2Positions = layoutAndGetPositions(level2) - local level3aPositions = layoutAndGetPositions(level3a) - local level3bPositions = layoutAndGetPositions(level3b) - - -- Debug output - print("Test 5 - Deeply Nested:") - print("Level 2 positions:") - for i, pos in ipairs(level2Positions) do - print(string.format("L2 Container %d: x=%.1f, y=%.1f, w=%.1f, h=%.1f", i, pos.x, pos.y, pos.width, pos.height)) - end - print("Level 3a positions:") - for i, pos in ipairs(level3aPositions) do - print(string.format("L3a Item %d: x=%.1f, y=%.1f, w=%.1f, h=%.1f", i, pos.x, pos.y, pos.width, pos.height)) - end - print("Level 3b positions:") - for i, pos in ipairs(level3bPositions) do - print(string.format("L3b Item %d: x=%.1f, y=%.1f, w=%.1f, h=%.1f", i, pos.x, pos.y, pos.width, pos.height)) - end - - -- Level 1 centers level 2 - luaunit.assertEquals(level1Positions[1].x, 20) -- (200-160)/2 - luaunit.assertEquals(level1Positions[1].y, 25) -- (150-100)/2 - - -- Level 2 stretches and space-between for level 3 containers - -- These positions are relative to level 1 container position - luaunit.assertEquals(level2Positions[1].x, 20) -- positioned by level 1 - luaunit.assertEquals(level2Positions[1].y, 25) -- positioned by level 1 - luaunit.assertEquals(level2Positions[1].height, 80) -- explicit height, not stretched (CSS spec compliance) - - luaunit.assertEquals(level2Positions[2].x, 110) -- positioned by level 1 + space-between - luaunit.assertEquals(level2Positions[2].y, 25) -- positioned by level 1 - luaunit.assertEquals(level2Positions[2].height, 80) -- explicit height, not stretched (CSS spec compliance) - - -- Level 3a: flex-end justification, center alignment - -- Positions are absolute including parent positions - luaunit.assertEquals(level3aPositions[1].x, 40) -- absolute position - luaunit.assertEquals(level3aPositions[1].y, 70) -- flex-end: 25 (level2.y) + 45 (80 - 35 total children) - - luaunit.assertEquals(level3aPositions[2].x, 42.5) -- absolute position - luaunit.assertEquals(level3aPositions[2].y, 90) -- second item: 70 + 20 = 90 - - -- Level 3b: flex-start justification, flex-end alignment - -- Positions are absolute including parent positions - luaunit.assertEquals(level3bPositions[1].x, 145) -- flex-end: container_x(110) + container_width(70) - item_width(35) = 145 - luaunit.assertEquals(level3bPositions[1].y, 25) -- actual absolute position -end - --- =================================== --- COMPLEX COMPREHENSIVE STRUCTURE TESTS --- =================================== - --- Test 6: Complex Application Layout - Complete UI Structure -function TestComprehensiveFlex:testComplexApplicationLayout() - -- Main application container - local app = createContainer({ - x = 0, - y = 0, - width = 1200, - height = 800, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Top navigation bar - local navbar = createChild(app, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 20, - padding = { top = 10, right = 20, bottom = 10, left = 20 }, - }) - - -- Left section of navbar - local navLeft = createChild(navbar, { - width = 300, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 16, - }) - - createChild(navLeft, { width = 120, height = 28 }) -- logo - createChild(navLeft, { width = 80, height = 24 }) -- home link - createChild(navLeft, { width = 80, height = 24 }) -- products link - - -- Center section of navbar with search - local navCenter = createChild(navbar, { - width = 400, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(navCenter, { width = 300, height = 32 }) -- search input - createChild(navCenter, { width = 32, height = 32 }) -- search button - - -- Right section of navbar - local navRight = createChild(navbar, { - width = 200, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 12, - }) - - createChild(navRight, { width = 32, height = 32 }) -- notifications - createChild(navRight, { width = 32, height = 32 }) -- cart - createChild(navRight, { width = 80, height = 32 }) -- user menu - - -- Main content area - local mainContent = createChild(app, { - height = 740, -- 800 - 60 navbar - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Left sidebar - local sidebar = createChild(mainContent, { - width = 250, - height = 740, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 0, bottom = 20, left = 20 }, - }) - - -- Sidebar navigation - local sideNav = createChild(sidebar, { - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(sideNav, { height = 24 }) -- nav title - - -- Navigation items with nested structure - for i = 1, 8 do - local navItem = createChild(sideNav, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - padding = { top = 4, right = 8, bottom = 4, left = 8 }, - }) - - local navItemLeft = createChild(navItem, { - width = 150, - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(navItemLeft, { width = 16, height = 16 }) -- icon - createChild(navItemLeft, { width = 100, height = 16 }) -- label - - if i <= 3 then -- some items have badges - createChild(navItem, { width = 20, height = 16 }) -- badge - end - end - - -- Sidebar widget area - local sideWidget = createChild(sidebar, { - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(sideWidget, { height = 20 }) -- widget title - - local widgetContent = createChild(sideWidget, { - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - for i = 1, 4 do - local widgetItem = createChild(widgetContent, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(widgetItem, { width = 120, height = 16 }) -- widget text - createChild(widgetItem, { width = 40, height = 12 }) -- widget value - end - - createChild(sideWidget, { height = 32 }) -- widget action button - - -- Main content panel - local contentPanel = createChild(mainContent, { - width = 950, - height = 740, -- 1200 - 250 sidebar - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Content header with breadcrumbs and actions - local contentHeader = createChild(contentPanel, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Breadcrumbs and title section - local headerLeft = createChild(contentHeader, { - width = 500, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - gap = 8, - }) - - -- Breadcrumbs - local breadcrumbs = createChild(headerLeft, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 6, - }) - - for i = 1, 4 do - createChild(breadcrumbs, { width = 60, height = 14 }) -- breadcrumb - if i < 4 then - createChild(breadcrumbs, { width = 8, height = 8 }) -- separator - end - end - - createChild(headerLeft, { width = 200, height = 24 }) -- page title - - -- Action buttons section - local headerRight = createChild(contentHeader, { - width = 300, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 12, - }) - - createChild(headerRight, { width = 80, height = 32 }) -- filter button - createChild(headerRight, { width = 80, height = 32 }) -- sort button - createChild(headerRight, { width = 100, height = 32 }) -- primary action - - -- Main content area with complex layouts - local contentMain = createChild(contentPanel, { - height = 660, -- 740 - 80 header - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 0, right = 20, bottom = 20, left = 20 }, - }) - - -- Content grid area - local contentGrid = createChild(contentMain, { - width = 600, - height = 640, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - }) - - -- Grid header with filters - local gridHeader = createChild(contentGrid, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 12, - }) - - -- Active filters - local activeFilters = createChild(gridHeader, { - width = 350, - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 8, - }) - - for i = 1, 4 do - local filterChip = createChild(activeFilters, { - width = 70, - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 4, - padding = { top = 2, right = 6, bottom = 2, left = 6 }, - }) - - createChild(filterChip, { width = 40, height = 12 }) -- filter text - createChild(filterChip, { width = 12, height = 12 }) -- close button - end - - -- Grid controls - local gridControls = createChild(gridHeader, { - width = 150, - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(gridControls, { width = 60, height = 28 }) -- view toggle - createChild(gridControls, { width = 60, height = 28 }) -- sort dropdown - - -- Item grid - local itemGrid = createChild(contentGrid, { - height = 560, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 20, - }) - - -- Create grid items - for i = 1, 6 do - local gridItem = createChild(itemGrid, { - width = 180, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(gridItem, { height = 120 }) -- item image - - local itemInfo = createChild(gridItem, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 4, - }) - - createChild(itemInfo, { width = 140, height = 16 }) -- item title - createChild(itemInfo, { width = 100, height = 12 }) -- item description - - local itemMeta = createChild(itemInfo, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(itemMeta, { width = 60, height = 14 }) -- price - createChild(itemMeta, { width = 40, height = 14 }) -- rating - - local itemActions = createChild(gridItem, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(itemActions, { width = 100, height = 28 }) -- primary action - createChild(itemActions, { width = 28, height = 28 }) -- secondary action - end - - -- Right detail panel - local detailPanel = createChild(contentMain, { - width = 290, - height = 640, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 0, bottom = 0, left = 0 }, - }) - - -- Detail header - local detailHeader = createChild(detailPanel, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(detailHeader, { height = 24 }) -- detail title - createChild(detailHeader, { height = 16 }) -- detail subtitle - - -- Detail content with complex nested structure - local detailContent = createChild(detailPanel, { - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - - -- Detail sections - for i = 1, 3 do - local detailSection = createChild(detailContent, { - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - padding = { top = 12, right = 12, bottom = 12, left = 12 }, - }) - - createChild(detailSection, { height = 18 }) -- section title - - local sectionContent = createChild(detailSection, { - height = 90, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 6, - }) - - for j = 1, 4 do - local contentRow = createChild(sectionContent, { - height = 18, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(contentRow, { width = 120, height = 14 }) -- row label - createChild(contentRow, { width = 80, height = 14 }) -- row value - end - end - - -- Detail actions - local detailActions = createChild(detailPanel, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - }) - - createChild(detailActions, { height = 32 }) -- primary action - createChild(detailActions, { height = 28 }) -- secondary action - - -- Layout and test positions - local appPositions = layoutAndGetPositions(app) - - -- Test main app structure - luaunit.assertEquals(appPositions[1].y, 0) -- navbar - luaunit.assertEquals(appPositions[2].y, 60) -- main content - - -- Test navbar layout - navbar:layoutChildren() - local navPositions = {} - for i, child in ipairs(navbar.children) do - navPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(navPositions[1].x, 20) -- nav left at padding - print("DEBUG - navCenter actual x:", navPositions[2].x, "expected: 400") - print("DEBUG - navbar calculation: available=1160, content=900, remaining=260, gap=130") - print("DEBUG - expected navCenter.x = 20 + 300 + 130 = 450") - luaunit.assertEquals(navPositions[2].x, 450) -- nav center positioned correctly with SPACE_BETWEEN - luaunit.assertEquals(navPositions[3].x, 980) -- nav right aligned (1200 - 20 - 200) - - -- Test main content layout - mainContent:layoutChildren() - local mainPositions = {} - for i, child in ipairs(mainContent.children) do - mainPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(mainPositions[1].x, 0) -- sidebar - luaunit.assertEquals(mainPositions[2].x, 250) -- content panel - - -- Test complex nested structures - contentPanel:layoutChildren() - local contentPanelPositions = {} - for i, child in ipairs(contentPanel.children) do - contentPanelPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(contentPanelPositions[1].y, 60) -- content header - luaunit.assertEquals(contentPanelPositions[2].y, 140) -- content main (60 + 80) - - -- Test content main layout - contentMain:layoutChildren() - local contentMainPositions = {} - for i, child in ipairs(contentMain.children) do - contentMainPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(contentMainPositions[1].x, 270) -- content grid (250 + 20) - luaunit.assertEquals(contentMainPositions[2].x, 890) -- detail panel (270 + 600 + 20) - - -- Test item grid wrapping - contentGrid:layoutChildren() - local gridHeader = contentGrid.children[1] - local itemGrid = contentGrid.children[2] - - itemGrid:layoutChildren() - local itemPositions = {} - for i, child in ipairs(itemGrid.children) do - itemPositions[i] = { x = child.x, y = child.y } - end - - -- Items should wrap: 180*3 + 20*2 = 580 < 600, so 3 per row - luaunit.assertEquals(itemPositions[1].y, itemPositions[2].y) -- row 1 - luaunit.assertEquals(itemPositions[2].y, itemPositions[3].y) -- row 1 - luaunit.assertEquals(itemPositions[4].y, itemPositions[5].y) -- row 2 - luaunit.assertEquals(itemPositions[5].y, itemPositions[6].y) -- row 2 - luaunit.assertTrue(itemPositions[1].y ~= itemPositions[4].y) -- different rows -end - --- Test 7: Complex Dashboard with Multiple Panels and Real-time Data Layout -function TestComprehensiveFlex:testComplexDashboardLayout() - -- Main dashboard container - local dashboard = createContainer({ - x = 0, - y = 0, - width = 1400, - height = 900, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Dashboard header with complex controls - local dashHeader = createChild(dashboard, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 20, - padding = { top = 16, right = 24, bottom = 16, left = 24 }, - }) - - -- Header left: title and time range - local headerLeft = createChild(dashHeader, { - width = 400, - height = 48, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 20, - }) - - createChild(headerLeft, { width = 200, height = 32 }) -- dashboard title - - local timeRange = createChild(headerLeft, { - width = 160, - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - padding = { top = 4, right = 8, bottom = 4, left = 8 }, - }) - - createChild(timeRange, { width = 100, height = 16 }) -- time range text - createChild(timeRange, { width = 16, height = 16 }) -- dropdown arrow - - -- Header center: key metrics - local headerCenter = createChild(dashHeader, { - width = 600, - height = 48, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 24, - }) - - -- Quick metrics - for i = 1, 4 do - local metric = createChild(headerCenter, { - width = 120, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 4, - }) - - createChild(metric, { width = 60, height = 16 }) -- metric value - createChild(metric, { width = 80, height = 12 }) -- metric label - end - - -- Header right: actions and settings - local headerRight = createChild(dashHeader, { - width = 280, - height = 48, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 12, - }) - - createChild(headerRight, { width = 36, height = 36 }) -- refresh button - createChild(headerRight, { width = 36, height = 36 }) -- fullscreen button - createChild(headerRight, { width = 100, height = 36 }) -- export button - createChild(headerRight, { width = 36, height = 36 }) -- settings button - - -- Main dashboard content - local dashContent = createChild(dashboard, { - height = 820, -- 900 - 80 header - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Left sidebar with navigation and filters - local dashSidebar = createChild(dashContent, { - width = 280, - height = 820, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 0, bottom = 20, left = 20 }, - }) - - -- Sidebar navigation - local sidebarNav = createChild(dashSidebar, { - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - }) - - createChild(sidebarNav, { height = 24 }) -- nav title - - -- Navigation groups - for i = 1, 3 do - local navGroup = createChild(sidebarNav, { - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 4, - }) - - createChild(navGroup, { height = 20 }) -- group title - - for j = 1, 3 do - local navItem = createChild(navGroup, { - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - padding = { top = 2, right = 8, bottom = 2, left = 16 }, - }) - - createChild(navItem, { width = 160, height = 14 }) -- nav label - if j == 1 then - createChild(navItem, { width = 20, height = 12 }) -- active indicator - end - end - end - - -- Sidebar filters - local sidebarFilters = createChild(dashSidebar, { - height = 250, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - padding = { top = 16, right = 16, bottom = 16, left = 0 }, - }) - - createChild(sidebarFilters, { height = 24 }) -- filters title - - -- Filter groups - for i = 1, 3 do - local filterGroup = createChild(sidebarFilters, { - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - createChild(filterGroup, { height = 16 }) -- filter group title - - local filterOptions = createChild(filterGroup, { - height = 36, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 4, - }) - - for j = 1, 2 do - local filterOption = createChild(filterOptions, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(filterOption, { width = 16, height = 12 }) -- checkbox - createChild(filterOption, { width = 120, height = 12 }) -- option label - end - end - - -- Sidebar recent activity - local sidebarActivity = createChild(dashSidebar, { - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 0, left = 0 }, - }) - - createChild(sidebarActivity, { height = 20 }) -- activity title - - local activityList = createChild(sidebarActivity, { - height = 160, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - for i = 1, 6 do - local activityItem = createChild(activityList, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - local activityLeft = createChild(activityItem, { - width = 160, - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 6, - }) - - createChild(activityLeft, { width = 12, height = 12 }) -- status dot - createChild(activityLeft, { width = 120, height = 12 }) -- activity text - - createChild(activityItem, { width = 40, height = 10 }) -- timestamp - end - - -- Main content panels area - local dashMain = createChild(dashContent, { - width = 1120, - height = 820, -- 1400 - 280 sidebar - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Top metrics row - local topMetrics = createChild(dashMain, { - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - alignContent = AlignContent.FLEX_START, - gap = 20, - }) - - -- Large metric cards - for i = 1, 4 do - local metricCard = createChild(topMetrics, { - width = 250, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - -- Card header - local cardHeader = createChild(metricCard, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(cardHeader, { width = 120, height = 16 }) -- metric title - createChild(cardHeader, { width = 20, height = 16 }) -- trend icon - - -- Metric value and change - local cardValue = createChild(metricCard, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_END, - gap = 8, - }) - - createChild(cardValue, { width = 100, height = 28 }) -- main value - createChild(cardValue, { width = 60, height = 16 }) -- change percentage - - -- Mini chart area - createChild(metricCard, { height = 24 }) -- mini chart - end - - -- Middle content row with charts - local middleContent = createChild(dashMain, { - height = 320, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 20, - }) - - -- Large chart panel - local chartPanel = createChild(middleContent, { - width = 680, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 16, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - -- Chart header with controls - local chartHeader = createChild(chartPanel, { - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 16, - }) - - createChild(chartHeader, { width = 200, height = 24 }) -- chart title - - local chartControls = createChild(chartHeader, { - width = 200, - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 8, - }) - - createChild(chartControls, { width = 60, height = 24 }) -- time filter - createChild(chartControls, { width = 60, height = 24 }) -- chart type - createChild(chartControls, { width = 24, height = 24 }) -- options menu - - -- Chart area - createChild(chartPanel, { height = 200 }) -- main chart - - -- Chart legend - local chartLegend = createChild(chartPanel, { - height = 28, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - alignContent = AlignContent.CENTER, - gap = 20, - }) - - for i = 1, 5 do - local legendItem = createChild(chartLegend, { - width = 80, - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 6, - }) - - createChild(legendItem, { width = 12, height = 12 }) -- color indicator - createChild(legendItem, { width = 50, height = 12 }) -- legend text - end - - -- Side stats panel - local statsPanel = createChild(middleContent, { - width = 360, - height = 300, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - createChild(statsPanel, { height = 24 }) -- stats title - - -- Stats grid - local statsGrid = createChild(statsPanel, { - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - alignContent = AlignContent.FLEX_START, - gap = 12, - }) - - -- Stats items in 2x4 grid - for i = 1, 8 do - local statItem = createChild(statsGrid, { - width = 150, - height = 50, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.FLEX_START, - gap = 4, - padding = { top = 8, right = 8, bottom = 8, left = 8 }, - }) - - createChild(statItem, { width = 100, height = 16 }) -- stat label - createChild(statItem, { width = 80, height = 20 }) -- stat value - end - - -- Bottom content row with tables and lists - local bottomContent = createChild(dashMain, { - height = 260, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 20, - }) - - -- Data table panel - local tablePanel = createChild(bottomContent, { - width = 540, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - -- Table header - local tableHeader = createChild(tablePanel, { - height = 32, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 12, - }) - - createChild(tableHeader, { width = 150, height = 20 }) -- table title - - local tableControls = createChild(tableHeader, { - width = 120, - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(tableControls, { width = 80, height = 20 }) -- search box - createChild(tableControls, { width = 20, height = 20 }) -- filter button - - -- Table content - local tableContent = createChild(tablePanel, { - height = 180, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 4, - }) - - -- Table header row - local tableHeaderRow = createChild(tableContent, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - for i = 1, 4 do - createChild(tableHeaderRow, { width = 100, height = 16 }) -- column header - end - - -- Table data rows - for i = 1, 6 do - local tableRow = createChild(tableContent, { - height = 24, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - for j = 1, 4 do - createChild(tableRow, { width = 100, height = 14 }) -- table cell - end - end - - -- Right panels (split) - local rightPanels = createChild(bottomContent, { - width = 500, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 20, - }) - - -- Alerts panel - local alertsPanel = createChild(rightPanels, { - width = 240, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(alertsPanel, { height = 20 }) -- alerts title - - local alertsList = createChild(alertsPanel, { - height = 192, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - - for i = 1, 6 do - local alertItem = createChild(alertsList, { - height = 28, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - padding = { top = 4, right = 4, bottom = 4, left = 4 }, - }) - - local alertLeft = createChild(alertItem, { - width = 160, - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 6, - }) - - createChild(alertLeft, { width = 12, height = 12 }) -- alert icon - createChild(alertLeft, { width = 120, height = 12 }) -- alert text - - createChild(alertItem, { width = 16, height = 16 }) -- dismiss button - end - - -- Progress panel - local progressPanel = createChild(rightPanels, { - width = 240, - height = 240, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - padding = { top = 16, right = 16, bottom = 16, left = 16 }, - }) - - createChild(progressPanel, { height = 20 }) -- progress title - - local progressList = createChild(progressPanel, { - height = 192, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - - for i = 1, 4 do - local progressItem = createChild(progressList, { - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 6, - }) - - local progressHeader = createChild(progressItem, { - height = 16, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - - createChild(progressHeader, { width = 120, height = 12 }) -- progress label - createChild(progressHeader, { width = 40, height = 12 }) -- progress percentage - - createChild(progressItem, { height = 8 }) -- progress bar - end - - -- Layout and test positions - local dashPositions = layoutAndGetPositions(dashboard) - - -- Test main dashboard structure - luaunit.assertEquals(dashPositions[1].y, 0) -- dashboard header - luaunit.assertEquals(dashPositions[2].y, 80) -- dashboard content - - -- Test dashboard header layout - dashHeader:layoutChildren() - local headerPositions = {} - for i, child in ipairs(dashHeader.children) do - headerPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(headerPositions[1].x, 24) -- header left - print("DEBUG - dashHeader center actual x:", headerPositions[2].x, "expected: 400") - luaunit.assertEquals(headerPositions[2].x, 460) -- header center (calculated correctly) - luaunit.assertEquals(headerPositions[3].x, 1096) -- header right (1400 - 24 - 280) - - -- Test dashboard content layout - dashContent:layoutChildren() - local contentPositions = {} - for i, child in ipairs(dashContent.children) do - contentPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(contentPositions[1].x, 0) -- sidebar - luaunit.assertEquals(contentPositions[2].x, 280) -- main content - - -- Test top metrics wrapping - dashMain:layoutChildren() - local topMetrics = dashMain.children[1] - topMetrics:layoutChildren() - - local metricPositions = {} - for i, child in ipairs(topMetrics.children) do - metricPositions[i] = { x = child.x, y = child.y } - end - - -- 4 metrics should fit in one row: 250*4 + 20*3 = 1060 < 1080 available - luaunit.assertEquals(metricPositions[1].y, metricPositions[2].y) -- same row - luaunit.assertEquals(metricPositions[2].y, metricPositions[3].y) -- same row - luaunit.assertEquals(metricPositions[3].y, metricPositions[4].y) -- same row - - -- Test middle content layout - local middleContent = dashMain.children[2] - middleContent:layoutChildren() - - local middlePositions = {} - for i, child in ipairs(middleContent.children) do - middlePositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(middlePositions[1].x, 300) -- chart panel (280 sidebar + 20 padding) - luaunit.assertEquals(middlePositions[2].x, 1020) -- stats panel (280 + 20 + 680 + 40 gap with SPACE_BETWEEN) - - -- Test chart legend wrapping - local chartPanel = middleContent.children[1] - chartPanel:layoutChildren() - local chartLegend = chartPanel.children[3] - chartLegend:layoutChildren() - - -- 5 legend items should fit: 80*5 + 20*4 = 480 < 640 available - luaunit.assertEquals(chartLegend.children[1].y, chartLegend.children[2].y) -- same row - luaunit.assertEquals(chartLegend.children[2].y, chartLegend.children[3].y) -- same row - luaunit.assertEquals(chartLegend.children[3].y, chartLegend.children[4].y) -- same row - luaunit.assertEquals(chartLegend.children[4].y, chartLegend.children[5].y) -- same row - - -- Test stats grid wrapping - local statsPanel = middleContent.children[2] - statsPanel:layoutChildren() - local statsGrid = statsPanel.children[2] - statsGrid:layoutChildren() - - local statsPositions = {} - for i, child in ipairs(statsGrid.children) do - statsPositions[i] = { x = child.x, y = child.y } - end - - -- 8 stats in 2 columns: 150*2 + 12*1 = 312 < 320 available - luaunit.assertEquals(statsPositions[1].y, statsPositions[2].y) -- row 1 - luaunit.assertEquals(statsPositions[3].y, statsPositions[4].y) -- row 2 - luaunit.assertEquals(statsPositions[5].y, statsPositions[6].y) -- row 3 - luaunit.assertEquals(statsPositions[7].y, statsPositions[8].y) -- row 4 - luaunit.assertTrue(statsPositions[1].y ~= statsPositions[3].y) -- different rows - - -- Test bottom content layout - local bottomContent = dashMain.children[3] - bottomContent:layoutChildren() - - local bottomPositions = {} - for i, child in ipairs(bottomContent.children) do - bottomPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(bottomPositions[1].x, 300) -- table panel - luaunit.assertEquals(bottomPositions[2].x, 880) -- right panels (280 + 20 + 540 + 40 gap with SPACE_BETWEEN) - - -- Test right panels layout - local rightPanels = bottomContent.children[2] - rightPanels:layoutChildren() - - local rightPositions = {} - for i, child in ipairs(rightPanels.children) do - rightPositions[i] = { x = child.x, y = child.y, width = child.width, height = child.height } - end - - luaunit.assertEquals(rightPositions[1].x, 880) -- alerts panel (same as parent due to SPACE_BETWEEN) - luaunit.assertEquals(rightPositions[2].x, 1140) -- progress panel (880 + 240 + 20) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/09_layout_validation_tests.lua b/testing/__tests__/09_layout_validation_tests.lua deleted file mode 100644 index b5ca242..0000000 --- a/testing/__tests__/09_layout_validation_tests.lua +++ /dev/null @@ -1,1258 +0,0 @@ -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums -local Color = FlexLove.Color -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems -local FlexWrap = enums.FlexWrap - --- Create test cases for layout validation -TestLayoutValidation = {} - -function TestLayoutValidation:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestLayoutValidation:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Helper function to capture errors without crashing -local function captureError(func) - local success, error_msg = pcall(func) - return success, error_msg -end - --- Helper function to create test containers -local function createTestContainer(props) - props = props or {} - local defaults = { - x = 0, - y = 0, - width = 200, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - flexWrap = FlexWrap.NOWRAP, - gap = 0, - } - - -- Merge props with defaults - for key, value in pairs(props) do - defaults[key] = value - end - - return Gui.new(defaults) -end - --- Test 1: Invalid Color Hex Strings -function TestLayoutValidation:testInvalidColorHexStrings() - -- Test completely invalid hex string - local success, error_msg = captureError(function() - Color.fromHex("invalid") - end) - luaunit.assertFalse(success) - luaunit.assertTrue(string.find(error_msg, "Invalid hex string") ~= nil) - - -- Test wrong length hex string - local success2, error_msg2 = captureError(function() - Color.fromHex("#ABC") - end) - luaunit.assertFalse(success2) - luaunit.assertTrue(string.find(error_msg2, "Invalid hex string") ~= nil) - - -- Test valid hex strings (should not error) - local success3, color3 = captureError(function() - return Color.fromHex("#FF0000") - end) - luaunit.assertTrue(success3) - luaunit.assertIsTable(color3) - - local success4, color4 = captureError(function() - return Color.fromHex("#FF0000AA") - end) - luaunit.assertTrue(success4) - luaunit.assertIsTable(color4) -end - --- Test 2: Invalid Enum Values (Graceful Degradation) -function TestLayoutValidation:testInvalidEnumValuesGracefulDegradation() - -- Test with invalid flexDirection - should not crash, use default - local success, container = captureError(function() - return Gui.new({ - x = 0, - y = 0, - width = 100, - height = 100, - positioning = Positioning.FLEX, - -- flexDirection = "invalid_direction", -- Skip invalid enum to avoid type error - }) - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(container.flexDirection, FlexDirection.HORIZONTAL) -- Should use default - - -- Test with invalid justifyContent - local success2, container2 = captureError(function() - return Gui.new({ - x = 0, - y = 0, - width = 100, - height = 100, - positioning = Positioning.FLEX, - -- justifyContent = "invalid_justify", -- Skip invalid enum to avoid type error - }) - end) - luaunit.assertTrue(success2) -- Should not crash - luaunit.assertEquals(container2.justifyContent, JustifyContent.FLEX_START) -- Should use default -end - --- Test 3: Missing Required Properties (Graceful Defaults) -function TestLayoutValidation:testMissingRequiredPropertiesDefaults() - -- Test element creation with minimal properties - local success, element = captureError(function() - return Gui.new({}) -- Completely empty props - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertIsNumber(element.x) - luaunit.assertIsNumber(element.y) - luaunit.assertIsNumber(element.width) - luaunit.assertIsNumber(element.height) - luaunit.assertEquals(element.positioning, Positioning.RELATIVE) -- Default positioning - - -- Test flex container with minimal properties - local success2, flex_element = captureError(function() - return Gui.new({ - positioning = Positioning.FLEX, -- Only positioning specified - }) - end) - luaunit.assertTrue(success2) -- Should not crash - luaunit.assertEquals(flex_element.flexDirection, FlexDirection.HORIZONTAL) -- Default - luaunit.assertEquals(flex_element.justifyContent, JustifyContent.FLEX_START) -- Default - luaunit.assertEquals(flex_element.alignItems, AlignItems.STRETCH) -- Default -end - --- Test 4: Invalid Property Combinations -function TestLayoutValidation:testInvalidPropertyCombinations() - -- Test absolute positioned element with flex properties (should be ignored) - local success, absolute_element = captureError(function() - return Gui.new({ - x = 10, - y = 10, - width = 100, - height = 100, - positioning = Positioning.ABSOLUTE, - flexDirection = FlexDirection.VERTICAL, -- Should be ignored - justifyContent = JustifyContent.CENTER, -- Should be ignored - }) - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(absolute_element.positioning, Positioning.ABSOLUTE) - -- Note: FlexLove might still store these properties even for absolute elements - - -- Test flex element can have both flex and position properties - local success2, flex_element = captureError(function() - return Gui.new({ - x = 10, -- Should work with flex - y = 10, -- Should work with flex - width = 100, - height = 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - end) - luaunit.assertTrue(success2) -- Should not crash - luaunit.assertEquals(flex_element.positioning, Positioning.FLEX) - luaunit.assertEquals(flex_element.flexDirection, FlexDirection.VERTICAL) -end - --- Test 5: Negative Dimensions and Positions -function TestLayoutValidation:testNegativeDimensionsAndPositions() - -- Test negative width and height (should work) - local success, element = captureError(function() - return Gui.new({ - x = -10, - y = -20, - width = -50, - height = -30, - }) - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(element.x, -10) -- Negative positions should work - luaunit.assertEquals(element.y, -20) - luaunit.assertEquals(element.width, 0) -- Negative dimensions are clamped to 0 - luaunit.assertEquals(element.height, 0) -- Negative dimensions are clamped to 0 -end - --- Test 6: Extremely Large Values -function TestLayoutValidation:testExtremelyLargeValues() - local success, element = captureError(function() - return Gui.new({ - x = 999999, - y = 999999, - width = 999999, - height = 999999, - }) - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(element.x, 999999) - luaunit.assertEquals(element.y, 999999) - luaunit.assertEquals(element.width, 999999) - luaunit.assertEquals(element.height, 999999) -end - --- Test 7: Invalid Child-Parent Relationships -function TestLayoutValidation:testInvalidChildParentRelationships() - local parent = createTestContainer() - - -- Test adding child with conflicting positioning - local success, child = captureError(function() - local child = Gui.new({ - x = 10, - y = 10, - width = 50, - height = 30, - positioning = Positioning.FLEX, -- Child tries to be flex container - }) - child.parent = parent - table.insert(parent.children, child) - return child - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(child.positioning, Positioning.FLEX) -- Should respect explicit positioning - luaunit.assertEquals(child.parent, parent) - luaunit.assertEquals(#parent.children, 1) -end - --- Test 8: Layout After Property Changes -function TestLayoutValidation:testLayoutAfterPropertyChanges() - local container = createTestContainer() - - local child1 = Gui.new({ - width = 50, - height = 30, - }) - child1.parent = container - table.insert(container.children, child1) - - local child2 = Gui.new({ - width = 60, - height = 35, - }) - child2.parent = container - table.insert(container.children, child2) - - -- Change container properties and verify layout still works - local success = captureError(function() - container.flexDirection = FlexDirection.VERTICAL - container:layoutChildren() - end) - luaunit.assertTrue(success) -- Should not crash - - -- Verify positions changed appropriately - local new_pos1 = { x = child1.x, y = child1.y } - local new_pos2 = { x = child2.x, y = child2.y } - - -- In vertical layout, child2 should be below child1 - luaunit.assertTrue(new_pos2.y >= new_pos1.y) -- child2 should be at or below child1 -end - --- Test 9: Autosizing Edge Cases -function TestLayoutValidation:testAutosizingEdgeCases() - -- Test element with autosizing width/height - local success, element = captureError(function() - return Gui.new({ - x = 0, - y = 0, - -- No w or h specified - should autosize - }) - end) - luaunit.assertTrue(success) -- Should not crash - luaunit.assertIsNumber(element.width) -- Should have calculated width - luaunit.assertIsNumber(element.height) -- Should have calculated height - -- Note: FlexLove might not have autosizing.width/height fields -end - --- Test 10: Complex Nested Validation -function TestLayoutValidation:testComplexNestedValidation() - -- Create deeply nested structure with mixed positioning - local success, root = captureError(function() - local root = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 150, - positioning = Positioning.FLEX, - }) - - local flex_child = Gui.new({ - width = 100, - height = 75, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - flex_child.parent = root - table.insert(root.children, flex_child) - - local absolute_grandchild = Gui.new({ - x = 10, - y = 10, - width = 30, - height = 20, - positioning = Positioning.ABSOLUTE, - }) - absolute_grandchild.parent = flex_child - table.insert(flex_child.children, absolute_grandchild) - - local flex_grandchild = Gui.new({ - width = 40, - height = 25, - -- No positioning - should inherit behavior - }) - flex_grandchild.parent = flex_child - table.insert(flex_child.children, flex_grandchild) - - return root - end) - - luaunit.assertTrue(success) -- Should not crash - luaunit.assertEquals(#root.children, 1) - luaunit.assertEquals(#root.children[1].children, 2) - - -- Verify positioning was handled correctly - local flex_child = root.children[1] - luaunit.assertEquals(flex_child.positioning, Positioning.FLEX) - - local absolute_grandchild = flex_child.children[1] - local flex_grandchild = flex_child.children[2] - - luaunit.assertEquals(absolute_grandchild.positioning, Positioning.ABSOLUTE) - -- flex_grandchild positioning depends on FlexLove's behavior -end - --- =================================== --- COMPLEX VALIDATION STRUCTURE TESTS --- =================================== - --- Test 11: Complex Multi-Level Layout Validation -function TestLayoutValidation:testComplexMultiLevelLayoutValidation() - -- Test complex application-like structure with validation at every level - local success, app = captureError(function() - -- Main app container - local app = Gui.new({ - x = 0, - y = 0, - width = 1200, - height = 800, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Header with complex validation scenarios - local header = Gui.new({ - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 20, - padding = { top = 10, right = 20, bottom = 10, left = 20 }, - }) - header.parent = app - table.insert(app.children, header) - - -- Header navigation with potential edge cases - local nav = Gui.new({ - width = 400, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.CENTER, - gap = 16, - }) - nav.parent = header - table.insert(header.children, nav) - - -- Create nav items with extreme values - for i = 1, 5 do - local navItem = Gui.new({ - width = i == 3 and 0 or 80, -- One item with zero width - height = i == 4 and -10 or 24, -- One item with negative height - positioning = i == 5 and Positioning.ABSOLUTE or nil, -- Mixed positioning - }) - navItem.parent = nav - table.insert(nav.children, navItem) - end - - -- Header actions with validation edge cases - local actions = Gui.new({ - width = 200, - height = 40, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_END, - alignItems = AlignItems.CENTER, - gap = 12, - }) - actions.parent = header - table.insert(header.children, actions) - - -- Actions with extreme dimensions - for i = 1, 3 do - local action = Gui.new({ - width = i == 1 and 999999 or 32, -- Extremely large width - height = i == 2 and 0.1 or 32, -- Fractional height - x = i == 3 and -1000 or nil, -- Extreme negative position - y = i == 3 and -1000 or nil, - }) - action.parent = actions - table.insert(actions.children, action) - end - - -- Main content with nested validation challenges - local main = Gui.new({ - height = 740, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - main.parent = app - table.insert(app.children, main) - - -- Sidebar with deep nesting and edge cases - local sidebar = Gui.new({ - width = 250, - height = 740, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 0, bottom = 20, left = 20 }, - }) - sidebar.parent = main - table.insert(main.children, sidebar) - - -- Sidebar sections with validation challenges - for section = 1, 3 do - local sideSection = Gui.new({ - height = section == 2 and -100 or 200, -- Negative height test - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 8, - }) - sideSection.parent = sidebar - table.insert(sidebar.children, sideSection) - - -- Section items with extreme properties - for item = 1, 4 do - local sectionItem = Gui.new({ - height = 24, - width = item == 2 and 0 or nil, -- Zero width test - positioning = item == 4 and Positioning.ABSOLUTE or nil, - x = item == 4 and 50 or nil, - y = item == 4 and 50 or nil, - gap = item == 3 and -5 or 0, -- Negative gap test - }) - sectionItem.parent = sideSection - table.insert(sideSection.children, sectionItem) - - -- Nested items for deep validation - if item <= 2 then - for nested = 1, 2 do - local nestedItem = Gui.new({ - width = nested == 1 and 999999 or 20, -- Extreme width - height = 12, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - }) - nestedItem.parent = sectionItem - table.insert(sectionItem.children, nestedItem) - end - end - end - end - - -- Content area with complex validation scenarios - local content = Gui.new({ - width = 950, - height = 740, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - content.parent = main - table.insert(main.children, content) - - -- Content grid with wrapping and validation challenges - local contentGrid = Gui.new({ - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - contentGrid.parent = content - table.insert(content.children, contentGrid) - - -- Grid items with validation edge cases - for i = 1, 12 do - local gridItem = Gui.new({ - width = i % 4 == 0 and 0 or 200, -- Some zero width items - height = i % 3 == 0 and -50 or 150, -- Some negative height items - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = i % 5 == 0 and -10 or 12, -- Some negative gaps - }) - gridItem.parent = contentGrid - table.insert(contentGrid.children, gridItem) - - -- Grid item content with extreme values - for j = 1, 3 do - local itemContent = Gui.new({ - height = j == 1 and 999999 or 40, -- Extreme height - width = j == 2 and -100 or nil, -- Negative width - positioning = j == 3 and Positioning.ABSOLUTE or nil, - x = j == 3 and -500 or nil, -- Extreme negative position - y = j == 3 and 1000000 or nil, -- Extreme positive position - }) - itemContent.parent = gridItem - table.insert(gridItem.children, itemContent) - end - end - - -- Footer with final validation tests - local footer = Gui.new({ - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.CENTER, - gap = 0, - padding = { top = 999999, right = -100, bottom = 0, left = 50 }, -- Extreme padding - }) - footer.parent = content - table.insert(content.children, footer) - - -- Footer sections with final edge cases - for i = 1, 4 do - local footerSection = Gui.new({ - width = i == 1 and 0 or 200, - height = i == 2 and -1000 or 100, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = i == 4 and 999999 or 8, - }) - footerSection.parent = footer - table.insert(footer.children, footerSection) - end - - return app - end) - - -- Should not crash despite extreme values - luaunit.assertTrue(success) - luaunit.assertIsTable(app) - luaunit.assertEquals(#app.children, 2) -- header and main - - -- Test layout calculation with extreme values - local layoutSuccess = captureError(function() - app:layoutChildren() - end) - luaunit.assertTrue(layoutSuccess) -- Layout should not crash - - -- Verify structure integrity after layout - luaunit.assertEquals(app.positioning, Positioning.FLEX) - luaunit.assertEquals(#app.children, 2) - luaunit.assertEquals(#app.children[1].children, 2) -- header nav and actions - luaunit.assertEquals(#app.children[2].children, 2) -- sidebar and content - - -- Test that extreme values are preserved but handled gracefully - local nav = app.children[1].children[1] - luaunit.assertEquals(#nav.children, 5) -- All nav items created - - local actions = app.children[1].children[2] - luaunit.assertEquals(actions.children[1].width, 999999) -- Extreme width preserved - - local sidebar = app.children[2].children[1] - luaunit.assertEquals(#sidebar.children, 3) -- All sidebar sections created - - local content = app.children[2].children[2] - luaunit.assertEquals(#content.children, 2) -- contentGrid and footer - - local contentGrid = content.children[1] - luaunit.assertEquals(#contentGrid.children, 12) -- All grid items created -end - --- Test 12: Validation of Dynamic Property Changes in Complex Layouts -function TestLayoutValidation:testComplexDynamicPropertyValidation() - local success, result = captureError(function() - -- Create complex dashboard layout - local dashboard = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 0, - }) - - -- Metrics row that will be modified - local metricsRow = Gui.new({ - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - metricsRow.parent = dashboard - table.insert(dashboard.children, metricsRow) - - -- Create initial metrics - local metrics = {} - for i = 1, 6 do - local metric = Gui.new({ - width = 150, - height = 80, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - alignItems = AlignItems.CENTER, - gap = 8, - }) - metric.parent = metricsRow - table.insert(metricsRow.children, metric) - metrics[i] = metric - - -- Metric content - for j = 1, 3 do - local content = Gui.new({ - width = 100, - height = 20, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - }) - content.parent = metric - table.insert(metric.children, content) - end - end - - -- Content area that will receive dynamic changes - local contentArea = Gui.new({ - height = 480, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 20, - padding = { top = 0, right = 20, bottom = 20, left = 20 }, - }) - contentArea.parent = dashboard - table.insert(dashboard.children, contentArea) - - -- Left panel for modifications - local leftPanel = Gui.new({ - width = 300, - height = 460, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 16, - }) - leftPanel.parent = contentArea - table.insert(contentArea.children, leftPanel) - - -- Right panel with nested complexity - local rightPanel = Gui.new({ - width = 640, - height = 460, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 12, - }) - rightPanel.parent = contentArea - table.insert(contentArea.children, rightPanel) - - -- Create nested content for validation testing - for i = 1, 3 do - local section = Gui.new({ - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - gap = 12, - padding = { top = 12, right = 12, bottom = 12, left = 12 }, - }) - section.parent = rightPanel - table.insert(rightPanel.children, section) - - -- Section items for modification testing - for j = 1, 8 do - local item = Gui.new({ - width = 80, - height = 60, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - gap = 4, - }) - item.parent = section - table.insert(section.children, item) - end - end - - -- Initial layout - dashboard:layoutChildren() - - -- Test 1: Change flexDirection on main container - dashboard.flexDirection = FlexDirection.HORIZONTAL - dashboard:layoutChildren() -- Should not crash - - -- Test 2: Modify metrics with extreme values - metrics[1].width = 0 - metrics[2].width = -100 - metrics[3].width = 999999 - metrics[4].height = 0 - metrics[5].height = -200 - metrics[6].height = 1000000 - dashboard:layoutChildren() -- Should not crash - - -- Test 3: Change flex wrap and justify properties - metricsRow.flexWrap = FlexWrap.WRAP_REVERSE - metricsRow.justifyContent = JustifyContent.CENTER - metricsRow.alignItems = AlignItems.FLEX_END - dashboard:layoutChildren() -- Should not crash - - -- Test 4: Modify gap values with extremes - metricsRow.gap = -50 - contentArea.gap = 999999 - dashboard:layoutChildren() -- Should not crash - - -- Test 5: Change positioning types dynamically - leftPanel.positioning = Positioning.ABSOLUTE - leftPanel.x = -500 - leftPanel.y = 1000 - dashboard:layoutChildren() -- Should not crash - - -- Test 6: Modify padding with extreme values - rightPanel.padding = { top = -100, right = 999999, bottom = 0, left = -50 } - dashboard:layoutChildren() -- Should not crash - - -- Test 7: Change nested item properties - local firstSection = rightPanel.children[1] - firstSection.flexDirection = FlexDirection.VERTICAL - firstSection.flexWrap = FlexWrap.WRAP_REVERSE - firstSection.justifyContent = JustifyContent.SPACE_EVENLY - dashboard:layoutChildren() -- Should not crash - - -- Test 8: Modify individual items with extreme values - local items = firstSection.children - for i = 1, #items do - items[i].width = i % 2 == 0 and 0 or 999999 - items[i].height = i % 3 == 0 and -100 or 200 - end - dashboard:layoutChildren() -- Should not crash - - -- Test 9: Add/remove children dynamically - local newMetric = Gui.new({ - width = 0, - height = -50, - positioning = Positioning.ABSOLUTE, - x = -1000, - y = -1000, - }) - newMetric.parent = metricsRow - table.insert(metricsRow.children, newMetric) - dashboard:layoutChildren() -- Should not crash - - -- Test 10: Remove children - table.remove(metricsRow.children, 1) - if metricsRow.children[1] then - metricsRow.children[1].parent = nil - end - dashboard:layoutChildren() -- Should not crash - - return { - dashboard = dashboard, - metricsRow = metricsRow, - contentArea = contentArea, - leftPanel = leftPanel, - rightPanel = rightPanel, - finalChildCount = #metricsRow.children, - } - end) - - luaunit.assertTrue(success) -- Should not crash during any modifications - luaunit.assertIsTable(result) - luaunit.assertIsTable(result.dashboard) - - -- Verify structure integrity after all modifications - luaunit.assertEquals(result.dashboard.flexDirection, FlexDirection.HORIZONTAL) - luaunit.assertEquals(result.metricsRow.flexWrap, FlexWrap.WRAP_REVERSE) - luaunit.assertEquals(result.leftPanel.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(result.finalChildCount, 6) -- 7 added - 1 removed = 6 remaining - - -- Test final layout one more time - local finalLayoutSuccess = captureError(function() - result.dashboard:layoutChildren() - end) - luaunit.assertTrue(finalLayoutSuccess) -end - --- Test 13: Validation of Circular Reference Prevention -function TestLayoutValidation:testCircularReferenceValidation() - local success, result = captureError(function() - -- Create containers that could form circular references - local container1 = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - - local container2 = Gui.new({ - width = 180, - height = 180, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - }) - - local container3 = Gui.new({ - width = 160, - height = 160, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - - -- Establish normal parent-child relationship - container2.parent = container1 - table.insert(container1.children, container2) - - container3.parent = container2 - table.insert(container2.children, container3) - - -- Test layout works normally - container1:layoutChildren() - - -- Attempt to create circular reference (should be prevented or handled) - -- Note: FlexLove should handle this gracefully or the test framework should catch it - - -- Test case 1: Try to make parent a child of its own child - local attemptSuccess1 = captureError(function() - container1.parent = container3 - table.insert(container3.children, container1) - container1:layoutChildren() -- This should either work or fail gracefully - end) - - -- Clean up potential circular reference - container1.parent = nil - if container3.children and #container3.children > 0 then - for i = #container3.children, 1, -1 do - if container3.children[i] == container1 then - table.remove(container3.children, i) - end - end - end - - -- Test case 2: Complex nested structure with potential circular refs - local container4 = Gui.new({ - width = 140, - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - }) - - local container5 = Gui.new({ - width = 120, - height = 120, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - - -- Normal nesting first - container4.parent = container3 - table.insert(container3.children, container4) - - container5.parent = container4 - table.insert(container4.children, container5) - - -- Test normal layout - container1:layoutChildren() - - -- Try to create deeper circular reference - local attemptSuccess2 = captureError(function() - container2.parent = container5 - table.insert(container5.children, container2) - container1:layoutChildren() - end) - - -- Clean up the circular reference to restore valid structure - -- Remove container2 from container5's children - if container5.children and #container5.children > 0 then - for i = #container5.children, 1, -1 do - if container5.children[i] == container2 then - table.remove(container5.children, i) - end - end - end - -- Restore container2's original parent - container2.parent = container1 - - return { - container1 = container1, - container2 = container2, - container3 = container3, - container4 = container4, - container5 = container5, - attempt1 = attemptSuccess1, - attempt2 = attemptSuccess2, - } - end) - - luaunit.assertTrue(success) -- Should not crash the test framework - luaunit.assertIsTable(result) - - -- Verify basic structure is maintained - luaunit.assertIsTable(result.container1) - luaunit.assertIsTable(result.container2) - luaunit.assertIsTable(result.container3) - - -- Test that final layout still works after cleanup - local finalLayoutSuccess = captureError(function() - result.container1:layoutChildren() - end) - luaunit.assertTrue(finalLayoutSuccess) -end - --- Test 14: Memory and Performance Validation with Large Structures -function TestLayoutValidation:testLargeStructureValidation() - local success, result = captureError(function() - -- Create a large, complex structure to test memory handling - local root = Gui.new({ - x = 0, - y = 0, - width = 2000, - height = 1500, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = 5, - }) - - local itemCount = 0 - local maxDepth = 5 - local itemsPerLevel = 10 - - -- Recursive function to create deep, wide structure - local function createLevel(parent, depth, items) - if depth >= maxDepth then - return - end - - for i = 1, items do - local container = Gui.new({ - width = depth == 1 and 400 or 100, - height = depth == 1 and 200 or 50, - positioning = Positioning.FLEX, - flexDirection = i % 2 == 0 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL, - flexWrap = i % 3 == 0 and FlexWrap.WRAP or FlexWrap.NOWRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - gap = depth, - }) - container.parent = parent - table.insert(parent.children, container) - itemCount = itemCount + 1 - - -- Add leaf elements to some containers - if depth >= 3 then - for j = 1, 3 do - local leaf = Gui.new({ - width = 20 + j * 5, - height = 15 + j * 3, - positioning = j == 3 and Positioning.ABSOLUTE or nil, - x = j == 3 and j * 10 or nil, - y = j == 3 and j * 10 or nil, - }) - leaf.parent = container - table.insert(container.children, leaf) - itemCount = itemCount + 1 - end - end - - -- Recurse to next level - createLevel(container, depth + 1, math.max(1, items - 2)) - end - end - - -- Create the large structure - createLevel(root, 1, itemsPerLevel) - - -- Test initial layout - root:layoutChildren() - - -- Modify properties across the structure - local function modifyRandomly(container, depth) - if depth > maxDepth then - return - end - - -- Randomly modify properties - if math.random() > 0.7 then - container.gap = math.random(-10, 50) - end - - if math.random() > 0.8 then - container.flexDirection = math.random() > 0.5 and FlexDirection.HORIZONTAL or FlexDirection.VERTICAL - end - - if math.random() > 0.9 then - container.width = math.random(10, 500) - container.height = math.random(10, 300) - end - - -- Recurse to children - if container.children then - for _, child in ipairs(container.children) do - modifyRandomly(child, depth + 1) - end - end - end - - -- Apply random modifications - for iteration = 1, 3 do - modifyRandomly(root, 1) - root:layoutChildren() -- Should handle large structure - end - - -- Test memory cleanup simulation - local function clearSubtree(container) - if container.children then - for i = #container.children, 1, -1 do - clearSubtree(container.children[i]) - container.children[i].parent = nil - table.remove(container.children, i) - end - end - end - - -- Clear half the structure - if root.children then - for i = math.ceil(#root.children / 2), #root.children do - if root.children[i] then - clearSubtree(root.children[i]) - root.children[i].parent = nil - table.remove(root.children, i) - end - end - end - - -- Test layout after cleanup - root:layoutChildren() - - return { - root = root, - itemCount = itemCount, - finalChildCount = #root.children, - } - end) - - luaunit.assertTrue(success) -- Should handle large structures without crashing - luaunit.assertIsTable(result) - luaunit.assertTrue(result.itemCount > 100) -- Should have created many items - luaunit.assertTrue(result.finalChildCount > 0) -- Should have remaining children after cleanup - - -- Test final layout works - local finalLayoutSuccess = captureError(function() - result.root:layoutChildren() - end) - luaunit.assertTrue(finalLayoutSuccess) -end - --- Test 15: Validation of Edge Cases in Complex Flex Combinations -function TestLayoutValidation:testComplexFlexCombinationValidation() - local success, result = captureError(function() - -- Test every possible combination of flex properties with edge cases - local combinations = {} - - local flexDirections = { FlexDirection.HORIZONTAL, FlexDirection.VERTICAL } - local justifyContents = { - JustifyContent.FLEX_START, - JustifyContent.FLEX_END, - JustifyContent.CENTER, - JustifyContent.SPACE_BETWEEN, - JustifyContent.SPACE_AROUND, - JustifyContent.SPACE_EVENLY, - } - local alignItems = { - AlignItems.FLEX_START, - AlignItems.FLEX_END, - AlignItems.CENTER, - AlignItems.STRETCH, - } - local flexWraps = { FlexWrap.NOWRAP, FlexWrap.WRAP, FlexWrap.WRAP_REVERSE } - - -- Main container for all combinations - local mainContainer = Gui.new({ - x = 0, - y = 0, - width = 2400, - height = 1800, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.FLEX_START, - gap = 20, - padding = { top = 20, right = 20, bottom = 20, left = 20 }, - }) - - local combinationCount = 0 - - -- Test each combination - for _, flexDir in ipairs(flexDirections) do - for _, justify in ipairs(justifyContents) do - for _, align in ipairs(alignItems) do - for _, wrap in ipairs(flexWraps) do - combinationCount = combinationCount + 1 - - local testContainer = Gui.new({ - width = 200, - height = 150, - positioning = Positioning.FLEX, - flexDirection = flexDir, - justifyContent = justify, - alignItems = align, - flexWrap = wrap, - gap = 5, - padding = { top = 5, right = 5, bottom = 5, left = 5 }, - }) - testContainer.parent = mainContainer - table.insert(mainContainer.children, testContainer) - - -- Add children with edge case properties - for i = 1, 6 do - local child = Gui.new({ - width = i == 1 and 0 or (i == 2 and -10 or (i == 6 and 999999 or 30)), - height = i == 3 and 0 or (i == 4 and -5 or (i == 5 and 1000000 or 20)), - positioning = i == 6 and Positioning.ABSOLUTE or nil, - x = i == 6 and -100 or nil, - y = i == 6 and 200 or nil, - }) - child.parent = testContainer - table.insert(testContainer.children, child) - - -- Add nested content to some children - if i <= 3 then - local nested = Gui.new({ - width = 15, - height = 10, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - }) - nested.parent = child - table.insert(child.children, nested) - end - end - end - end - end - end - - -- Test layout with all combinations - mainContainer:layoutChildren() - - -- Test dynamic property changes on all combinations - for _, container in ipairs(mainContainer.children) do - -- Change gap to extreme values - container.gap = math.random() > 0.5 and -20 or 100 - - -- Change dimensions - if math.random() > 0.7 then - container.width = math.random() > 0.5 and 0 or 500 - container.height = math.random() > 0.5 and -50 or 300 - end - - -- Modify children - if container.children then - for i, child in ipairs(container.children) do - if math.random() > 0.8 then - child.width = math.random() > 0.5 and 0 or 999999 - child.height = math.random() > 0.5 and -100 or 1000000 - end - end - end - end - - -- Test layout after modifications - mainContainer:layoutChildren() - - return { - mainContainer = mainContainer, - combinationCount = combinationCount, - finalContainerCount = #mainContainer.children, - } - end) - - luaunit.assertTrue(success) -- Should handle all combinations without crashing - luaunit.assertIsTable(result) - - -- Should have tested many combinations - local expectedCombinations = 2 * 6 * 4 * 3 -- 144 combinations - luaunit.assertEquals(result.combinationCount, expectedCombinations) - luaunit.assertEquals(result.finalContainerCount, expectedCombinations) - - -- Test final layout works - local finalLayoutSuccess = captureError(function() - result.mainContainer:layoutChildren() - end) - luaunit.assertTrue(finalLayoutSuccess) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/10_performance_tests.lua b/testing/__tests__/10_performance_tests.lua deleted file mode 100644 index 3e5fde2..0000000 --- a/testing/__tests__/10_performance_tests.lua +++ /dev/null @@ -1,1414 +0,0 @@ -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection -local JustifyContent = enums.JustifyContent -local AlignItems = enums.AlignItems -local FlexWrap = enums.FlexWrap - --- Create test cases for performance testing -TestPerformance = {} - -function TestPerformance:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestPerformance:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Helper function to measure execution time -local function measureTime(func) - local start_time = os.clock() - local result = func() - local end_time = os.clock() - return end_time - start_time, result -end - --- Helper function to create test containers -local function createTestContainer(props) - props = props or {} - local defaults = { - x = 0, - y = 0, - width = 800, - height = 600, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - flexWrap = FlexWrap.NOWRAP, - gap = 0, - } - - for key, value in pairs(props) do - defaults[key] = value - end - - return Gui.new(defaults) -end - --- Helper function to create many children -local function createManyChildren(parent, count, child_props) - child_props = child_props or {} - local children = {} - - for i = 1, count do - local props = { - width = child_props.w or 50, - height = 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 < 0.05, "10 children layout should complete within 0.05 seconds") - luaunit.assertTrue(time_50 < 0.05, "50 children layout should complete within 0.05 seconds") - luaunit.assertTrue(time_100 < 0.05, "100 children layout should complete within 0.05 seconds") - - -- 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") -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 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 < 0.05, string.format("%d children should layout within 0.05 seconds", size)) - end - - -- Check that performance scales linearly or sub-linearly - -- Time for 200 children should not be more than 25x time for 10 children - -- (Increased from 20x to account for timing precision at microsecond scale) - luaunit.assertTrue(times[200] <= times[10] * 25, "Performance should scale sub-linearly") -end - --- Test 3: Complex Nested Layout Performance -function TestPerformance:testComplexNestedLayoutPerformance() - -- Create a deeply nested structure with multiple levels - local root = createTestContainer({ - width = 1000, - height = 800, - flexDirection = FlexDirection.VERTICAL, - }) - - local time, _ = measureTime(function() - -- Level 1: 5 sections - for i = 1, 5 do - local section = Gui.new({ - width = 950, - height = 150, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.SPACE_BETWEEN, - }) - section.parent = root - table.insert(root.children, section) - - -- Level 2: 4 columns per section - for j = 1, 4 do - local column = Gui.new({ - width = 200, - height = 140, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - alignItems = AlignItems.CENTER, - }) - column.parent = section - table.insert(section.children, column) - - -- Level 3: 3 items per column - for k = 1, 3 do - local item = Gui.new({ - width = 180, - height = 40, - }) - item.parent = column - table.insert(column.children, item) - 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 < 0.05, "Complex nested layout should complete within 0.05 seconds") - - -- Verify structure was created correctly - luaunit.assertEquals(#root.children, 5) -- 5 sections - luaunit.assertEquals(#root.children[1].children, 4) -- 4 columns per section - luaunit.assertEquals(#root.children[1].children[1].children, 3) -- 3 items per column -end - --- Test 4: Flex Wrap Performance with Many Elements -function TestPerformance:testFlexWrapPerformanceWithManyElements() - local container = createTestContainer({ - width = 400, - height = 600, - flexDirection = FlexDirection.HORIZONTAL, - flexWrap = FlexWrap.WRAP, - justifyContent = JustifyContent.SPACE_AROUND, - alignItems = AlignItems.CENTER, - }) - - -- Create many children that will wrap - local children = createManyChildren(container, 50, { - width = 80, - height = 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 < 0.05, "Flex wrap layout should complete within 0.05 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") -end - --- Test 5: Dynamic Layout Change Performance -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 < 0.05, "Dynamic layout changes should complete within 0.05 seconds") - - -- Verify final layout is valid - luaunit.assertTrue(children[1].x >= 0 and children[1].y >= 0, "Children should be positioned after changes") -end - --- Test 6: Memory Usage Pattern Test -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 < 0.05, "Memory usage pattern test should complete within 0.05 seconds") - - -- Verify container is clean after cycles - luaunit.assertEquals(#container.children, 0, "Container should be clean after memory test") -end - --- Test 7: Performance with Different Layout Strategies -function TestPerformance:testPerformanceWithDifferentLayoutStrategies() - local strategies = { - { - name = "Simple Horizontal", - props = { - flexDirection = FlexDirection.HORIZONTAL, - justifyContent = JustifyContent.FLEX_START, - alignItems = AlignItems.STRETCH, - }, - }, - { - name = "Centered Vertical", - props = { - flexDirection = FlexDirection.VERTICAL, - justifyContent = JustifyContent.CENTER, - alignItems = AlignItems.CENTER, - }, - }, - { - name = "Wrapped Space-Between", - props = { - flexDirection = FlexDirection.HORIZONTAL, - 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 < 0.05, string.format("'%s' layout should complete within 0.05 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 - --- Test 8: Stress Test with Maximum Elements -function TestPerformance:testStressTestWithMaximumElements() - local container = createTestContainer({ - width = 1200, - height = 900, - 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, { - width = 30, - height = 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 < 0.05, string.format("Stress test with %d elements should complete within 0.05 seconds", stress_count)) - - -- Verify that all children are positioned - local positioned_count = 0 - for _, child in ipairs(children) do - if child.x >= 0 and child.y >= 0 then - positioned_count = positioned_count + 1 - end - end - - luaunit.assertEquals(positioned_count, stress_count, "All children should be positioned in stress test") -end - --- Test 9: Complex Real-World Application Performance - Enterprise Dashboard -function TestPerformance:testComplexEnterpriseApplicationPerformance() - print("\n=== Test 9: Complex Enterprise Dashboard Performance ===") - - -- Create enterprise-grade dashboard with deep nesting (5 levels) - local dashboard = createTestContainer({ - width = 1920, - height = 1080, - flexDirection = FlexDirection.VERTICAL, - gap = 10, - }) - - local time, structure_info = measureTime(function() - -- Level 1: Header, Main Content, Footer - local header = Gui.new({ - width = 1900, - height = 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({ - width = 1900, - height = 980, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - gap = 15, - }) - main_content.parent = dashboard - table.insert(dashboard.children, main_content) - - local footer = Gui.new({ - width = 1900, - height = 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({ - width = section_name == "navigation" and 400 or 150, - height = 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({ - width = section_name == "navigation" and 60 or 45, - height = 40, - }) - item.parent = section - table.insert(section.children, item) - end - end - - -- Level 2: Main content areas (sidebar, dashboard grid) - local sidebar = Gui.new({ - width = 280, - height = 960, - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - gap = 10, - }) - sidebar.parent = main_content - table.insert(main_content.children, sidebar) - - local dashboard_grid = Gui.new({ - width = 1600, - height = 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({ - width = 260, - height = 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({ width = 250, height = 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({ - width = 240, - height = 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({ width = 20, height = 20 }) - item_icon.parent = menu_item - table.insert(menu_item.children, item_icon) - - local item_text = Gui.new({ width = 180, height = 25 }) - item_text.parent = menu_item - table.insert(menu_item.children, item_text) - - local item_badge = Gui.new({ width = 25, height = 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({ - width = 1580, - height = 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({ - width = 500, - height = 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({ - width = 480, - height = 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({ - width = 480, - height = 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({ - width = 480, - height = 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({ - width = content_elements > 10 and 35 or 55, - height = 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({ width = 200, height = 30 }) - widget_title.parent = widget_header - table.insert(widget_header.children, widget_title) - - local widget_actions = Gui.new({ - width = 120, - height = 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({ width = 30, height = 25 }) - action_btn.parent = widget_actions - table.insert(widget_actions.children, action_btn) - end - - -- Widget footer components - local footer_info = Gui.new({ width = 100, height = 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 < 0.05, "Enterprise dashboard should layout within 0.05 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({ - width = 1200, - height = 800, - flexDirection = FlexDirection.VERTICAL, - gap = 10, - }) - - -- Create dynamic content structure - local sections = {} - for i = 1, 8 do - local section = Gui.new({ - width = 1180, - height = 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({ - width = 100, - height = 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({ width = 80, height = 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({ - width = 1400, - height = 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({ - width = 1380, - height = 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({ - width = 160, - height = 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({ - width = 150, - height = 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({ width = 100, height = 25 }) - title.parent = card_header - table.insert(card_header.children, title) - - local status_indicator = Gui.new({ width = 20, height = 20 }) - status_indicator.parent = card_header - table.insert(card_header.children, status_indicator) - - -- Card content with animated metrics - local card_content = Gui.new({ - width = 150, - height = 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({ - width = 35, - height = 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({ - width = 150, - height = 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({ width = 25, height = 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.width + math.sin(frame * 0.1 + i) * 2 - element.h = element.height + 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 < 0.1, "Animation setup should complete within 0.1 seconds") - luaunit.assertTrue(avg_frame_time < target_frame_time * 2, "Average frame time should be reasonable for 30fps+") - luaunit.assertTrue(max_frame_time < 0.05, "No single frame should take more than 50ms") - 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({ - width = 1600, - height = 1000, - flexDirection = FlexDirection.VERTICAL, - gap = 8, - }) - - local all_elements = {} - - -- Create memory-intensive nested structure - for level1 = 1, 10 do - local section = Gui.new({ - width = 1580, - height = 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({ - width = 100, - height = 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({ - width = 95, - height = 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({ width = 40, height = 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({ - width = 2000, - height = 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({ - width = 1980, - height = 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({ width = 35, height = 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 - -- Reserve some elements for containers, rest for leaf nodes - local container_count = test_config.depth - local leaf_elements = test_config.elements - container_count - - for depth = 1, test_config.depth do - local level_container = Gui.new({ - width = 1900 - (depth * 50), - height = 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, leaf_elements do - local leaf = Gui.new({ width = 30 + (i % 20), height = 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({ - width = 150 - (current_depth * 15), - height = 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({ - width = 1900, - height = 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({ - width = 300, - height = 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({ - width = 280, - height = 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({ width = 60, height = 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({ width = 270, height = 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 < 1.0, string.format("%s should complete within 1 seconds", test_config.name)) - luaunit.assertTrue(test_metrics.created_elements > 50, 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 deleted file mode 100644 index 04e0026..0000000 --- a/testing/__tests__/11_auxiliary_functions_tests.lua +++ /dev/null @@ -1,1867 +0,0 @@ -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, Color, enums = FlexLove.Gui, FlexLove.Color, FlexLove.enums - -TestAuxiliaryFunctions = {} - -function TestAuxiliaryFunctions:setUp() - -- Clear any existing GUI elements - Gui.destroy() -end - -function TestAuxiliaryFunctions:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- ============================================ --- Color Utility Functions Tests --- ============================================ - -function TestAuxiliaryFunctions:testColorNewBasic() - local color = Color.new(1, 0.5, 0.2, 0.8) - luaunit.assertEquals(color.r, 1) - luaunit.assertEquals(color.g, 0.5) - luaunit.assertEquals(color.b, 0.2) - luaunit.assertEquals(color.a, 0.8) -end - -function TestAuxiliaryFunctions:testColorNewDefaults() - -- Test default values when parameters are nil or missing - local color = Color.new() - luaunit.assertEquals(color.r, 0) - luaunit.assertEquals(color.g, 0) - luaunit.assertEquals(color.b, 0) - luaunit.assertEquals(color.a, 1) -- Alpha defaults to 1 -end - -function TestAuxiliaryFunctions:testColorNewPartialDefaults() - local color = Color.new(0.7, 0.3) - luaunit.assertEquals(color.r, 0.7) - luaunit.assertEquals(color.g, 0.3) - luaunit.assertEquals(color.b, 0) - luaunit.assertEquals(color.a, 1) -end - -function TestAuxiliaryFunctions:testColorFromHex6Digit() - local color = Color.fromHex("#FF8040") - -- Note: Color.fromHex returns values in 0-1 range (normalized) - luaunit.assertAlmostEquals(color.r, 255 / 255, 0.01) - luaunit.assertAlmostEquals(color.g, 128 / 255, 0.01) - luaunit.assertAlmostEquals(color.b, 64 / 255, 0.01) - luaunit.assertEquals(color.a, 1) -end - -function TestAuxiliaryFunctions:testColorFromHex8Digit() - local color = Color.fromHex("#FF8040CC") - luaunit.assertAlmostEquals(color.r, 255 / 255, 0.01) - luaunit.assertAlmostEquals(color.g, 128 / 255, 0.01) - luaunit.assertAlmostEquals(color.b, 64 / 255, 0.01) - luaunit.assertAlmostEquals(color.a, 204 / 255, 0.01) -- CC hex = 204 decimal -end - -function TestAuxiliaryFunctions:testColorFromHexWithoutHash() - local color = Color.fromHex("FF8040") - luaunit.assertAlmostEquals(color.r, 255 / 255, 0.01) - luaunit.assertAlmostEquals(color.g, 128 / 255, 0.01) - luaunit.assertAlmostEquals(color.b, 64 / 255, 0.01) - luaunit.assertEquals(color.a, 1) -end - -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) -end - -function TestAuxiliaryFunctions:testColorToRGBA() - local color = Color.new(0.8, 0.6, 0.4, 0.9) - local r, g, b, a = color:toRGBA() - luaunit.assertEquals(r, 0.8) - luaunit.assertEquals(g, 0.6) - luaunit.assertEquals(b, 0.4) - luaunit.assertEquals(a, 0.9) -end - --- ============================================ --- Element Calculation Utility Tests --- ============================================ - -function TestAuxiliaryFunctions:testCalculateTextWidthWithText() - local element = Gui.new({ - text = "Test Text", - 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) -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 - -function TestAuxiliaryFunctions:testCalculateTextHeightWithSize() - local element = Gui.new({ - text = "Test", - 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", - }) - - local width = element:calculateAutoWidth() - local textWidth = element:calculateTextWidth() - luaunit.assertEquals(width, textWidth, "Auto width should equal text width when no children") -end - -function TestAuxiliaryFunctions:testCalculateAutoWidthWithChildren() - local parent = Gui.new({ - positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.HORIZONTAL, - gap = 5, -- Add gap to test gap calculation - }) - - local child1 = Gui.new({ - parent = parent, - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - parent = parent, - width = 40, - height = 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", - }) - - local height = element:calculateAutoHeight() - local textHeight = element:calculateTextHeight() - luaunit.assertEquals(height, textHeight, "Auto height should equal text height when no children") -end - -function TestAuxiliaryFunctions:testCalculateAutoHeightWithChildren() - local parent = Gui.new({ - positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.VERTICAL, - gap = 5, -- Add gap to test gap calculation - }) - - local child1 = Gui.new({ - parent = parent, - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - parent = parent, - width = 40, - height = 25, - }) - - local height = parent:calculateAutoHeight() - luaunit.assertTrue(height > 55, "Auto height should account for children and gaps") -end - --- ============================================ --- Element Utility Methods Tests --- ============================================ - -function TestAuxiliaryFunctions:testGetBounds() - local element = Gui.new({ - x = 10, - y = 20, - width = 100, - height = 80, - }) - - local bounds = element:getBounds() - luaunit.assertEquals(bounds.x, 10) - luaunit.assertEquals(bounds.y, 20) - luaunit.assertEquals(bounds.width, 100) - luaunit.assertEquals(bounds.height, 80) -end - -function TestAuxiliaryFunctions:testUpdateText() - local element = Gui.new({ - text = "Original Text", - width = 100, - height = 50, - }) - - element:updateText("New Text") - luaunit.assertEquals(element.text, "New Text") - luaunit.assertEquals(element.width, 100) -- Should not change without autoresize - luaunit.assertEquals(element.height, 50) -end - -function TestAuxiliaryFunctions:testUpdateTextWithAutoresize() - local element = Gui.new({ - text = "Short", - 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 - ) -end - -function TestAuxiliaryFunctions:testUpdateTextKeepOriginalWhenNil() - local element = Gui.new({ - text = "Original Text", - }) - - element:updateText(nil) - luaunit.assertEquals(element.text, "Original Text", "Text should remain unchanged when nil is passed") -end - -function TestAuxiliaryFunctions:testUpdateOpacitySingle() - local element = Gui.new({ - opacity = 1.0, - }) - - element:updateOpacity(0.5) - luaunit.assertEquals(element.opacity, 0.5) -end - -function TestAuxiliaryFunctions:testUpdateOpacityPropagateToChildren() - local parent = Gui.new({ - opacity = 1.0, - }) - - local child1 = Gui.new({ - parent = parent, - opacity = 1.0, - }) - - local child2 = Gui.new({ - parent = parent, - 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 --- ============================================ - -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) - luaunit.assertNotNil(fadeAnim.transform) - luaunit.assertNotNil(fadeAnim.transition) -end - -function TestAuxiliaryFunctions:testAnimationScaleFactory() - 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) - luaunit.assertEquals(scaleAnim.final.width, 200) - luaunit.assertEquals(scaleAnim.final.height, 100) -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) -end - -function TestAuxiliaryFunctions:testAnimationApplyToElement() - local element = Gui.new({ - width = 100, - height = 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({ - width = 100, - height = 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 - --- ============================================ --- GUI Management Utility Tests --- ============================================ - -function TestAuxiliaryFunctions:testGuiDestroyEmptyState() - -- Should not error when destroying empty GUI - Gui.destroy() - luaunit.assertEquals(#Gui.topElements, 0) -end - -function TestAuxiliaryFunctions:testGuiDestroyWithElements() - local element1 = Gui.new({ - x = 10, - y = 10, - width = 100, - height = 50, - }) - - local element2 = Gui.new({ - x = 20, - y = 20, - width = 80, - height = 40, - }) - - luaunit.assertEquals(#Gui.topElements, 2) - - Gui.destroy() - luaunit.assertEquals(#Gui.topElements, 0) -end - -function TestAuxiliaryFunctions:testGuiDestroyWithNestedElements() - local parent = Gui.new({ - width = 200, - height = 100, - }) - - local child1 = Gui.new({ - parent = parent, - width = 50, - height = 30, - }) - - local child2 = Gui.new({ - parent = parent, - width = 40, - height = 25, - }) - - luaunit.assertEquals(#Gui.topElements, 1) - luaunit.assertEquals(#parent.children, 2) - - Gui.destroy() - luaunit.assertEquals(#Gui.topElements, 0) -end - -function TestAuxiliaryFunctions:testElementDestroyRemovesFromParent() - local parent = Gui.new({ - width = 200, - height = 100, - }) - - local child = Gui.new({ - parent = parent, - width = 50, - height = 30, - }) - - luaunit.assertEquals(#parent.children, 1) - - child:destroy() - - luaunit.assertEquals(#parent.children, 0) - luaunit.assertNil(child.parent) -end - -function TestAuxiliaryFunctions:testElementDestroyRemovesFromTopElements() - local element = Gui.new({ - x = 10, - y = 10, - width = 100, - height = 50, - }) - - luaunit.assertEquals(#Gui.topElements, 1) - - element:destroy() - - luaunit.assertEquals(#Gui.topElements, 0) -end - -function TestAuxiliaryFunctions:testElementDestroyNestedChildren() - local parent = Gui.new({ - width = 200, - height = 150, - }) - - local child = Gui.new({ - parent = parent, - width = 100, - height = 75, - }) - - local grandchild = Gui.new({ - parent = child, - width = 50, - height = 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 - --- ============================================ --- Edge Cases and Error Handling Tests --- ============================================ - -function TestAuxiliaryFunctions:testColorFromHexEmptyString() - luaunit.assertError(function() - Color.fromHex("") - end) -end - -function TestAuxiliaryFunctions:testColorFromHexNoHashInvalidLength() - luaunit.assertError(function() - Color.fromHex("FF80") - end) -end - -function TestAuxiliaryFunctions:testAnimationInterpolationAtBoundaries() - local scaleAnim = Gui.Animation.scale(1.0, { width = 100, height = 50 }, { width = 200, height = 100 }) - - -- At start (elapsed = 0) - scaleAnim.elapsed = 0 - scaleAnim._resultDirty = true -- Mark dirty after changing elapsed - local result = scaleAnim:interpolate() - luaunit.assertEquals(result.width, 100) - luaunit.assertEquals(result.height, 50) - - -- At end (elapsed = duration) - scaleAnim.elapsed = 1.0 - scaleAnim._resultDirty = true -- Mark dirty after changing elapsed - 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 - scaleAnim._resultDirty = true -- Mark dirty after changing elapsed - result = scaleAnim:interpolate() - luaunit.assertEquals(result.width, 200) - luaunit.assertEquals(result.height, 100) -end - -function TestAuxiliaryFunctions:testAutoSizingWithZeroChildren() - local element = Gui.new({ - text = "", - }) - - local width = element:calculateAutoWidth() - 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, - }) - - -- Test minimum boundary - element:updateOpacity(0.0) - luaunit.assertEquals(element.opacity, 0.0) - - -- 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 - --- ============================================ --- 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-1 range) - luaunit.assertAlmostEquals(hex_color.r, color_def.r, 0.01, string.format("%s hex red component mismatch", color_def.name)) - luaunit.assertAlmostEquals(hex_color.g, color_def.g, 0.01, string.format("%s hex green component mismatch", color_def.name)) - luaunit.assertAlmostEquals(hex_color.b, color_def.b, 0.01, string.format("%s hex blue component mismatch", color_def.name)) - end - - -- Test color variations (opacity, brightness adjustments) - local opacities = { 0.1, 0.25, 0.5, 0.75, 0.9 } - for color_name, color_set in pairs(theme_colors) do - color_variations[color_name] = {} - - -- Create opacity variations - 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({ - width = 800, - height = 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({ - width = 780, - height = 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({ - width = 150, - height = 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") - - -- Count theme_colors (it's a table with string keys, not an array) - local theme_color_count = 0 - for _ in pairs(theme_colors) do - theme_color_count = theme_color_count + 1 - end - luaunit.assertEquals(theme_color_count, 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({ - width = 1000, - height = 800, - positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.VERTICAL, - gap = 15, - }) - - for _, scenario in ipairs(text_scenarios) do - local text_container = Gui.new({ - width = 900, - height = 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, - width = 0, - height = 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, - width = 200, - height = 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, - width = math.max(150, auto_width * 0.8), - height = 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({ - width = 800, - height = 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 - local prev_container = nested_container - for level = 1, 3 do - local level_container = Gui.new({ - width = 750 - (level * 50), - height = 60, - positioning = enums.Positioning.FLEX, - flexDirection = enums.FlexDirection.HORIZONTAL, - justifyContent = enums.JustifyContent.SPACE_AROUND, - gap = 5, - }) - level_container.parent = prev_container - table.insert(prev_container.children, level_container) - prev_container = 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, - width = 0, - height = 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") - - -- Count text_metrics (it's a table with string keys, not an array) - local metrics_count = 0 - for _ in pairs(content_manager.text_metrics) do - metrics_count = metrics_count + 1 - end - luaunit.assertTrue(metrics_count >= #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({ - width = 1200, - height = 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({ - width = 1180, - height = 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({ - width = test_case.properties.width and test_case.properties.width.from or 120, - height = 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({ width = 100, height = 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({ - width = math.max(100, 300 - level * 20), - height = 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({ - width = 400, - height = 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({ - width = 350, - height = 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 element_id, element_pair in pairs(managed_elements) do - -- Extract number from "element_N" key - local num = tonumber(element_id:match("%d+")) - if num and num % 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, - width = 0, - height = 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({ width = 1000, height = 800 }) - local current_parent = extreme_hierarchy_element - - -- Create extremely deep hierarchy - for depth = 1, max_depth do - local child = Gui.new({ - width = math.max(50, 1000 - depth * 45), - height = 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({ - width = 2000, - height = 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({ width = 30, height = 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({ width = 50, height = 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({ width = 100, height = 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() diff --git a/testing/__tests__/12_units_system_tests.lua b/testing/__tests__/12_units_system_tests.lua deleted file mode 100644 index aeab24d..0000000 --- a/testing/__tests__/12_units_system_tests.lua +++ /dev/null @@ -1,365 +0,0 @@ -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui, enums = FlexLove.Gui, FlexLove.enums - -local Positioning = enums.Positioning -local FlexDirection = enums.FlexDirection - --- Test the Units system functionality -TestUnitsSystem = {} - -function TestUnitsSystem:setUp() - -- Clear any existing GUI elements and reset viewport - Gui.destroy() - -- Set a consistent viewport size for testing - love.window.setMode(1200, 800) -end - -function TestUnitsSystem:tearDown() - Gui.destroy() - -- Restore original viewport size - love.window.setMode(800, 600) -end - --- ============================================ --- Units Parsing Tests --- ============================================ - -function TestUnitsSystem:testUnitsParsePx() - -- Test pixel unit parsing - local container = Gui.new({ - id = "container", - width = "100px", - height = "200px", - x = "50px", - y = "75px", - }) - - luaunit.assertEquals(container.width, 100) - luaunit.assertEquals(container.height, 200) - luaunit.assertEquals(container.x, 50) - luaunit.assertEquals(container.y, 75) - luaunit.assertEquals(container.units.width.unit, "px") - luaunit.assertEquals(container.units.height.unit, "px") - luaunit.assertEquals(container.units.x.unit, "px") - luaunit.assertEquals(container.units.y.unit, "px") -end - -function TestUnitsSystem:testUnitsParsePercentage() - -- Test percentage unit parsing - local parent = Gui.new({ - id = "parent", - width = 400, - height = 300, - }) - - local child = Gui.new({ - id = "child", - width = "50%", - height = "25%", - parent = parent, - }) - - luaunit.assertEquals(child.width, 200) -- 50% of 400 - luaunit.assertEquals(child.height, 75) -- 25% of 300 - luaunit.assertEquals(child.units.width.unit, "%") - luaunit.assertEquals(child.units.height.unit, "%") - luaunit.assertEquals(child.units.width.value, 50) - luaunit.assertEquals(child.units.height.value, 25) -end - -function TestUnitsSystem:testUnitsParseViewportWidth() - -- Test viewport width units (1200px viewport) - local container = Gui.new({ - id = "container", - width = "50vw", - height = "100px", - }) - - luaunit.assertEquals(container.width, 600) -- 50% of 1200 - luaunit.assertEquals(container.units.width.unit, "vw") - luaunit.assertEquals(container.units.width.value, 50) -end - -function TestUnitsSystem:testUnitsParseViewportHeight() - -- Test viewport height units (800px viewport) - local container = Gui.new({ - id = "container", - width = "100px", - height = "25vh", - }) - - luaunit.assertEquals(container.height, 200) -- 25% of 800 - luaunit.assertEquals(container.units.height.unit, "vh") - luaunit.assertEquals(container.units.height.value, 25) -end - -function TestUnitsSystem:testUnitsAutoSizing() - -- Test that auto-sized elements use "auto" unit - local autoContainer = Gui.new({ - id = "autoContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - }) - - luaunit.assertEquals(autoContainer.units.width.unit, "auto") - luaunit.assertEquals(autoContainer.units.height.unit, "auto") - luaunit.assertTrue(autoContainer.autosizing.width) - luaunit.assertTrue(autoContainer.autosizing.height) -end - -function TestUnitsSystem:testMixedUnits() - -- Test elements with different unit types - local container = Gui.new({ - id = "container", - width = "80vw", -- viewport width - height = "400px", -- pixels - x = "10%", -- percentage of viewport - y = "5vh", -- viewport height - gap = "2vw", -- viewport width for gap - textSize = "16px", -- pixel font size - }) - - luaunit.assertEquals(container.width, 960) -- 80% of 1200 - luaunit.assertEquals(container.height, 400) -- 400px - luaunit.assertEquals(container.x, 120) -- 10% of 1200 - luaunit.assertEquals(container.y, 40) -- 5% of 800 - luaunit.assertEquals(container.gap, 24) -- 2% of 1200 - luaunit.assertEquals(container.textSize, 16) -- 16px -end - --- ============================================ --- Resize and Unit Recalculation Tests --- ============================================ - -function TestUnitsSystem:testResizeViewportUnits() - -- Test that viewport units recalculate on resize - local container = Gui.new({ - id = "container", - width = "50vw", - height = "25vh", - }) - - luaunit.assertEquals(container.width, 600) -- 50% of 1200 - luaunit.assertEquals(container.height, 200) -- 25% of 800 - - -- Simulate viewport resize using setMode - love.window.setMode(1600, 1000) - container:resize(1600, 1000) - - luaunit.assertEquals(container.width, 800) -- 50% of 1600 - luaunit.assertEquals(container.height, 250) -- 25% of 1000 - - -- Restore viewport - love.window.setMode(1200, 800) -end - -function TestUnitsSystem:testResizePercentageUnits() - -- Test percentage units during parent resize - local parent = Gui.new({ - id = "parent", - width = 400, - height = 300, - }) - - local child = Gui.new({ - id = "child", - width = "75%", - height = "50%", - parent = parent, - }) - - luaunit.assertEquals(child.width, 300) -- 75% of 400 - luaunit.assertEquals(child.height, 150) -- 50% of 300 - - -- Resize parent - parent.width = 600 - parent.height = 500 - child:resize(1200, 800) - - luaunit.assertEquals(child.width, 450) -- 75% of 600 - luaunit.assertEquals(child.height, 250) -- 50% of 500 -end - -function TestUnitsSystem:testResizePixelUnitsNoChange() - -- Test that pixel units don't change during resize - local container = Gui.new({ - id = "container", - width = "300px", - height = "200px", - }) - - luaunit.assertEquals(container.width, 300) - luaunit.assertEquals(container.height, 200) - - -- Resize viewport - pixel values should stay the same - container:resize(1600, 1000) - - luaunit.assertEquals(container.width, 300) - luaunit.assertEquals(container.height, 200) -end - --- ============================================ --- Spacing (Padding/Margin) Units Tests --- ============================================ - -function TestUnitsSystem:testPaddingUnits() - -- Test different unit types for padding - local container = Gui.new({ - id = "container", - width = 400, - height = 300, - padding = { - top = "10px", - right = "5%", - bottom = "2vh", - left = "1vw", - }, - }) - - luaunit.assertEquals(container.padding.top, 10) -- 10px - luaunit.assertEquals(container.padding.right, 20) -- 5% of 400 - luaunit.assertEquals(container.padding.bottom, 16) -- 2% of 800 - luaunit.assertEquals(container.padding.left, 12) -- 1% of 1200 - - luaunit.assertEquals(container.units.padding.top.unit, "px") - luaunit.assertEquals(container.units.padding.right.unit, "%") - luaunit.assertEquals(container.units.padding.bottom.unit, "vh") - luaunit.assertEquals(container.units.padding.left.unit, "vw") -end - -function TestUnitsSystem:testMarginUnits() - -- Test different unit types for margin - local container = Gui.new({ - id = "container", - width = 400, - height = 300, - margin = { - top = "8px", - right = "3%", - bottom = "1vh", - left = "2vw", - }, - }) - - luaunit.assertEquals(container.margin.top, 8) -- 8px - luaunit.assertEquals(container.margin.right, 36) -- 3% of viewport width (1200) - CSS spec: % margins relative to containing block - luaunit.assertEquals(container.margin.bottom, 8) -- 1% of 800 - luaunit.assertEquals(container.margin.left, 24) -- 2% of 1200 - - luaunit.assertEquals(container.units.margin.top.unit, "px") - luaunit.assertEquals(container.units.margin.right.unit, "%") - luaunit.assertEquals(container.units.margin.bottom.unit, "vh") - luaunit.assertEquals(container.units.margin.left.unit, "vw") -end - --- ============================================ --- Gap and TextSize Units Tests --- ============================================ - -function TestUnitsSystem:testGapUnits() - -- Test gap with different unit types - local flexContainer = Gui.new({ - id = "flexContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.HORIZONTAL, - width = 600, - height = 400, - gap = "2%", -- 2% of container width - }) - - luaunit.assertEquals(flexContainer.gap, 12) -- 2% of 600 - luaunit.assertEquals(flexContainer.units.gap.unit, "%") - luaunit.assertEquals(flexContainer.units.gap.value, 2) - - -- Test with viewport units - local viewportGapContainer = Gui.new({ - id = "viewportGapContainer", - positioning = Positioning.FLEX, - flexDirection = FlexDirection.VERTICAL, - width = 400, - height = 300, - gap = "1vw", - }) - - luaunit.assertEquals(viewportGapContainer.gap, 12) -- 1% of 1200 viewport width - luaunit.assertEquals(viewportGapContainer.units.gap.unit, "vw") -end - -function TestUnitsSystem:testTextSizeUnits() - -- Test textSize with different units - local textElement = Gui.new({ - id = "textElement", - width = 200, - height = 100, - textSize = "16px", - }) - - luaunit.assertEquals(textElement.textSize, 16) - luaunit.assertEquals(textElement.units.textSize.unit, "px") - luaunit.assertEquals(textElement.units.textSize.value, 16) - - -- Test with viewport units - local viewportTextElement = Gui.new({ - id = "viewportTextElement", - width = 200, - height = 100, - textSize = "2vw", - }) - - luaunit.assertEquals(viewportTextElement.textSize, 24) -- 2% of 1200 - luaunit.assertEquals(viewportTextElement.units.textSize.unit, "vw") -end - --- ============================================ --- Error Handling and Edge Cases --- ============================================ - -function TestUnitsSystem:testInvalidUnits() - -- Test handling of invalid unit specifications (should default to pixels) - local container = Gui.new({ - id = "container", - width = "100invalid", -- Should be treated as 100px - height = "50badunit", -- Should be treated as 50px - }) - - -- Should fallback to pixel values - luaunit.assertEquals(container.width, 100) - luaunit.assertEquals(container.height, 50) - luaunit.assertEquals(container.units.width.unit, "px") - luaunit.assertEquals(container.units.height.unit, "px") -end - -function TestUnitsSystem:testZeroAndNegativeValues() - -- Test zero and negative values with units - local container = Gui.new({ - id = "container", - width = "0px", - height = "0vh", - x = "-10px", - y = "-5%", - }) - - luaunit.assertEquals(container.width, 0) - luaunit.assertEquals(container.height, 0) - luaunit.assertEquals(container.x, -10) - luaunit.assertEquals(container.y, -40) -- -5% of 800 viewport height for y positioning -end - -function TestUnitsSystem:testVeryLargeValues() - -- Test very large percentage values - local container = Gui.new({ - id = "container", - width = "200%", -- 200% of viewport - height = "150vh", -- 150% of viewport height - }) - - luaunit.assertEquals(container.width, 2400) -- 200% of 1200 - luaunit.assertEquals(container.height, 1200) -- 150% of 800 -end - --- Run the tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/13_relative_positioning_tests.lua b/testing/__tests__/13_relative_positioning_tests.lua deleted file mode 100644 index 86e1aad..0000000 --- a/testing/__tests__/13_relative_positioning_tests.lua +++ /dev/null @@ -1,199 +0,0 @@ --- Test relative positioning functionality -package.path = package.path .. ";?.lua" -require("testing.loveStub") -local FlexLove = require("FlexLove") -local luaunit = require("testing.luaunit") - -local Gui, enums = FlexLove.Gui, FlexLove.enums -local Color = FlexLove.Color -local Positioning = enums.Positioning - -TestRelativePositioning = {} - --- Test 1: Basic relative positioning with pixel values -function TestRelativePositioning.testBasicRelativePositioning() - local parent = Gui.new({ - x = 100, - y = 50, - width = 200, - height = 150, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0), - }) - - local child = Gui.new({ - parent = parent, - x = 20, - y = 30, - width = 50, - height = 40, - positioning = "relative", - backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0), - }) - - -- Child should be positioned relative to parent - luaunit.assertEquals(child.positioning, Positioning.RELATIVE) - luaunit.assertEquals(child.x, 120) -- parent.x (100) + offset (20) - luaunit.assertEquals(child.y, 80) -- parent.y (50) + offset (30) -end - --- Test 2: Relative positioning with percentage values -function TestRelativePositioning.testRelativePositioningPercentages() - local parent = Gui.new({ - x = 50, - y = 100, - width = 200, - height = 100, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0), - }) - - local child = Gui.new({ - parent = parent, - x = "10%", -- 10% of parent width = 20px - y = "20%", -- 20% of parent height = 20px - width = 30, - height = 20, - positioning = "relative", - backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0), - }) - - -- Child should be positioned relative to parent with percentage offsets - luaunit.assertEquals(child.positioning, Positioning.RELATIVE) - luaunit.assertEquals(child.x, 70.0) -- parent.x (50) + 10% of width (20) - luaunit.assertEquals(child.y, 120.0) -- parent.y (100) + 20% of height (20) -end - --- Test 3: Relative positioning with no offset (default to parent position) -function TestRelativePositioning.testRelativePositioningNoOffset() - local parent = Gui.new({ - x = 75, - y = 125, - width = 150, - height = 200, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0), - }) - - local child = Gui.new({ - parent = parent, - width = 40, - height = 30, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.8, 0.2, 1.0), - }) - - -- Child should be positioned at parent's position with no offset - luaunit.assertEquals(child.positioning, Positioning.RELATIVE) - luaunit.assertEquals(child.x, 75) -- same as parent.x - luaunit.assertEquals(child.y, 125) -- same as parent.y -end - --- Test 4: Multiple relative positioned children -function TestRelativePositioning.testMultipleRelativeChildren() - local parent = Gui.new({ - x = 200, - y = 300, - width = 100, - height = 100, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0), - }) - - local child1 = Gui.new({ - parent = parent, - x = 10, - y = 15, - width = 20, - height = 20, - positioning = "relative", - backgroundColor = Color.new(0.8, 0.2, 0.2, 1.0), - }) - - local child2 = Gui.new({ - parent = parent, - x = 30, - y = 45, - width = 25, - height = 25, - positioning = "relative", - backgroundColor = Color.new(0.2, 0.8, 0.2, 1.0), - }) - - -- Both children should be positioned relative to parent - luaunit.assertEquals(child1.x, 210) -- parent.x (200) + offset (10) - luaunit.assertEquals(child1.y, 315) -- parent.y (300) + offset (15) - - luaunit.assertEquals(child2.x, 230) -- parent.x (200) + offset (30) - luaunit.assertEquals(child2.y, 345) -- parent.y (300) + offset (45) -end - --- Test 5: Nested relative positioning -function TestRelativePositioning.testNestedRelativePositioning() - local grandparent = Gui.new({ - x = 50, - y = 60, - width = 300, - height = 250, - positioning = "relative", - backgroundColor = Color.new(0.1, 0.1, 0.1, 1.0), - }) - - local parent = Gui.new({ - parent = grandparent, - x = 25, - y = 35, - width = 200, - height = 150, - positioning = "relative", - backgroundColor = Color.new(0.3, 0.3, 0.3, 1.0), - }) - - local child = Gui.new({ - parent = parent, - x = 15, - y = 20, - width = 50, - height = 40, - positioning = "relative", - backgroundColor = Color.new(0.8, 0.8, 0.8, 1.0), - }) - - -- Each level should be positioned relative to its parent - luaunit.assertEquals(parent.x, 75) -- grandparent.x (50) + offset (25) - luaunit.assertEquals(parent.y, 95) -- grandparent.y (60) + offset (35) - - luaunit.assertEquals(child.x, 90) -- parent.x (75) + offset (15) - luaunit.assertEquals(child.y, 115) -- parent.y (95) + offset (20) -end - --- Test 6: Mixed positioning types (relative child in absolute parent) -function TestRelativePositioning.testMixedPositioning() - local parent = Gui.new({ - x = 100, - y = 200, - width = 180, - height = 120, - positioning = "absolute", - backgroundColor = Color.new(0.2, 0.2, 0.2, 1.0), - }) - - local child = Gui.new({ - parent = parent, - x = 40, - y = 25, - width = 60, - height = 35, - positioning = "relative", - backgroundColor = Color.new(0.8, 0.8, 0.2, 1.0), - }) - - -- Relative child should still be positioned relative to absolute parent - luaunit.assertEquals(parent.positioning, Positioning.ABSOLUTE) - luaunit.assertEquals(child.positioning, Positioning.RELATIVE) - luaunit.assertEquals(child.x, 140) -- parent.x (100) + offset (40) - luaunit.assertEquals(child.y, 225) -- parent.y (200) + offset (25) -end - --- Run all tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/14_text_scaling_basic_tests.lua b/testing/__tests__/14_text_scaling_basic_tests.lua deleted file mode 100644 index e1976b4..0000000 --- a/testing/__tests__/14_text_scaling_basic_tests.lua +++ /dev/null @@ -1,503 +0,0 @@ --- Test file for comprehensive text scaling functionality --- This tests all text scaling scenarios including edge cases and multiple resize events - -package.path = package.path .. ";?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui - --- Test suite for comprehensive text scaling -TestTextScaling = {} - -function TestTextScaling:setUp() - -- Reset viewport to default before each test - love.window.setMode(800, 600) - Gui.destroy() -end - -function TestTextScaling:tearDown() - Gui.destroy() -end - --- Basic functionality tests -function TestTextScaling.testFixedTextSize() - -- Create an element with fixed textSize in pixels (auto-scaling disabled) - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = 16, -- Fixed size in pixels - autoScaleText = false, -- Disable auto-scaling for truly fixed size - text = "Hello World", - }) - - -- Check initial state - luaunit.assertEquals(element.textSize, 16) - luaunit.assertEquals(element.units.textSize.unit, "px") - - -- Simulate multiple resizes - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 16) -- Should remain unchanged - - element:resize(400, 300) - luaunit.assertEquals(element.textSize, 16) -- Should remain unchanged -end - -function TestTextScaling.testPercentageTextSize() - -- Create an element with percentage textSize (relative to viewport height) - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "5%", -- Percentage of viewport height - text = "Hello World", - }) - - -- Check initial state (5% of 600px = 30px) - luaunit.assertEquals(element.units.textSize.unit, "%") - luaunit.assertEquals(element.units.textSize.value, 5) - luaunit.assertEquals(element.textSize, 30.0) - - -- Simulate resize to larger viewport (5% of 1200px = 60px) - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 60.0) - - -- Simulate resize to smaller viewport (5% of 300px = 15px) - element:resize(400, 300) - luaunit.assertEquals(element.textSize, 15.0) -end - -function TestTextScaling.testVwTextSize() - -- Create an element with vw textSize - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "2vw", -- 2% of viewport width - text = "Hello World", - }) - - -- Check initial state (2% of 800px = 16px) - luaunit.assertEquals(element.units.textSize.unit, "vw") - luaunit.assertEquals(element.units.textSize.value, 2) - luaunit.assertEquals(element.textSize, 16.0) - - -- Simulate resize to larger viewport (2% of 1600px = 32px) - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 32.0) - - -- Simulate resize to smaller viewport (2% of 400px = 8px) - element:resize(400, 300) - luaunit.assertEquals(element.textSize, 8.0) -end - -function TestTextScaling.testVhTextSize() - -- Create an element with vh textSize - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "3vh", -- 3% of viewport height - text = "Hello World", - }) - - -- Check initial state (3% of 600px = 18px) - luaunit.assertEquals(element.units.textSize.unit, "vh") - luaunit.assertEquals(element.units.textSize.value, 3) - luaunit.assertEquals(element.textSize, 18.0) - - -- Simulate resize to larger viewport (3% of 1200px = 36px) - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 36.0) - - -- Simulate resize to smaller viewport (3% of 300px = 9px) - element:resize(400, 300) - luaunit.assertEquals(element.textSize, 9.0) -end - -function TestTextScaling.testNoTextSize() - -- Create an element without textSize specified (auto-scaling enabled by default) - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - text = "Hello World", - }) - - -- Check initial state - should auto-scale by default (1.5vh) - luaunit.assertEquals(element.autoScaleText, true) - luaunit.assertEquals(element.units.textSize.value, 1.5) - luaunit.assertEquals(element.units.textSize.unit, "vh") - luaunit.assertEquals(element.textSize, 9.0) -- 1.5% of 600px - - -- Resize should scale the text - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 18.0) -- 1.5% of 1200px - - -- Test with auto-scaling disabled - local elementNoScale = Gui.new({ - id = "testElementNoScale", - width = 100, - height = 50, - text = "Hello World", - autoScaleText = false, - }) - - luaunit.assertEquals(elementNoScale.autoScaleText, false) - luaunit.assertEquals(elementNoScale.units.textSize.value, nil) - luaunit.assertEquals(elementNoScale.textSize, 12) -- Fixed 12px - - -- Resize should not affect textSize when auto-scaling is disabled - elementNoScale:resize(1600, 1200) - luaunit.assertEquals(elementNoScale.textSize, 12) -end - --- Edge case tests -function TestTextScaling.testZeroPercentageTextSize() - -- Create an element with 0% textSize (protected to minimum 1px) - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "0%", - text = "Hello World", - }) - - luaunit.assertEquals(element.textSize, 1) -- Protected to minimum 1px - - -- Should remain at minimum after resize - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 1) -- Protected to minimum 1px -end - -function TestTextScaling.testVerySmallTextSize() - -- Create an element with very small textSize (protected to minimum 1px) - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "0.1vh", - text = "Hello World", - }) - - -- Check initial state (0.1% of 600px = 0.6px, protected to 1px) - luaunit.assertEquals(element.textSize, 1) - - -- Should scale proportionally when above minimum - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 1.2) -- 0.1% of 1200px = 1.2px -end - -function TestTextScaling.testVeryLargeTextSize() - -- Create an element with very large textSize - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "50vh", - text = "Hello World", - }) - - -- Check initial state (50% of 600px = 300px) - luaunit.assertEquals(element.textSize, 300.0) - - -- Should scale proportionally - element:resize(1600, 1200) - luaunit.assertEquals(element.textSize, 600.0) -- 50% of 1200px = 600px -end - -function TestTextScaling.testDecimalUnits() - -- Create an element with decimal units - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "2.5vw", - text = "Hello World", - }) - - -- Check initial state (2.5% of 800px = 20px) - luaunit.assertEquals(element.textSize, 20.0) - - -- Should handle decimal precision - element:resize(1000, 800) - luaunit.assertEquals(element.textSize, 25.0) -- 2.5% of 1000px = 25px -end - --- Multiple resize tests -function TestTextScaling.testMultipleResizes() - -- Create an element and perform multiple resize operations - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "4vh", - text = "Hello World", - }) - - -- Initial: 4% of 600px = 24px - luaunit.assertEquals(element.textSize, 24.0) - - -- First resize: 4% of 800px = 32px - element:resize(1000, 800) - luaunit.assertEquals(element.textSize, 32.0) - - -- Second resize: 4% of 400px = 16px - element:resize(500, 400) - luaunit.assertEquals(element.textSize, 16.0) - - -- Third resize: 4% of 1000px = 40px - element:resize(1200, 1000) - luaunit.assertEquals(element.textSize, 40.0) - - -- Return to original: 4% of 600px = 24px - element:resize(800, 600) - luaunit.assertEquals(element.textSize, 24.0) -end - --- Mixed unit tests -function TestTextScaling.testMixedUnitsInDifferentElements() - -- Create multiple elements with different unit types - local elements = { - Gui.new({ id = "px", textSize = 20, autoScaleText = false, text = "Fixed" }), - Gui.new({ id = "percent", textSize = "5%", text = "Percent" }), - Gui.new({ id = "vw", textSize = "3vw", text = "ViewWidth" }), - Gui.new({ id = "vh", textSize = "4vh", text = "ViewHeight" }), - } - - -- Check initial states - luaunit.assertEquals(elements[1].textSize, 20) -- Fixed - luaunit.assertEquals(elements[2].textSize, 30.0) -- 5% of 600px - luaunit.assertEquals(elements[3].textSize, 24.0) -- 3% of 800px - luaunit.assertEquals(elements[4].textSize, 24.0) -- 4% of 600px - - -- Resize all elements - for _, element in ipairs(elements) do - element:resize(1200, 900) - end - - -- Check after resize - luaunit.assertEquals(elements[1].textSize, 20) -- Fixed (unchanged) - luaunit.assertEquals(elements[2].textSize, 45.0) -- 5% of 900px - luaunit.assertEquals(elements[3].textSize, 36.0) -- 3% of 1200px - luaunit.assertEquals(elements[4].textSize, 36.0) -- 4% of 900px -end - --- Test invalid units handling -function TestTextScaling.testInvalidUnits() - -- Test that invalid units are handled gracefully - local success, err = pcall(function() - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "5invalidunit", - text = "Hello World", - }) - end) - - -- Should handle invalid units gracefully (might error or default) - -- The exact behavior depends on implementation, but shouldn't crash - luaunit.assertTrue(success or string.find(tostring(err), "Unknown unit")) -end - --- Performance test for many resizes -function TestTextScaling.testPerformanceWithManyResizes() - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "2vh", - text = "Hello World", - }) - - -- Perform many resize operations - local startTime = os.clock() - for i = 1, 100 do - local width = 800 + (i * 2) - local height = 600 + (i * 2) - element:resize(width, height) - - -- Verify the calculation is still correct - local expected = (2 / 100) * height - luaunit.assertEquals(element.textSize, expected) - end - local endTime = os.clock() - - -- Should complete in reasonable time (less than 1 second for 100 resizes) - local duration = endTime - startTime - luaunit.assertTrue(duration < 1.0, "Performance test took too long: " .. duration .. " seconds") -end - --- Element-relative unit tests -function TestTextScaling.testElementWidthUnits() - -- Create an element with textSize relative to element width - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = "10ew", -- 10% of element width - text = "Hello World", - }) - - -- Check initial state (10% of 200px = 20px) - luaunit.assertEquals(element.units.textSize.unit, "ew") - luaunit.assertEquals(element.units.textSize.value, 10) - luaunit.assertEquals(element.textSize, 20.0) - luaunit.assertEquals(element.width, 200) - - -- Change element width and recalculate - element.width = 300 - element:resize(800, 600) - luaunit.assertEquals(element.textSize, 30.0) -- 10% of 300px = 30px -end - -function TestTextScaling.testElementHeightUnits() - -- Create an element with textSize relative to element height - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = "15eh", -- 15% of element height - text = "Hello World", - }) - - -- Check initial state (15% of 100px = 15px) - luaunit.assertEquals(element.units.textSize.unit, "eh") - luaunit.assertEquals(element.units.textSize.value, 15) - luaunit.assertEquals(element.textSize, 15.0) - luaunit.assertEquals(element.height, 100) - - -- Change element height and recalculate - element.height = 200 - element:resize(800, 600) - luaunit.assertEquals(element.textSize, 30.0) -- 15% of 200px = 30px -end - -function TestTextScaling.testElementRelativeWithViewportUnits() - -- Create an element with viewport-based size and element-relative textSize - local element = Gui.new({ - id = "testElement", - width = "25%", -- 25% of viewport width = 200px (800px * 0.25) - height = "20%", -- 20% of viewport height = 120px (600px * 0.20) - textSize = "8ew", -- 8% of element width - text = "Hello World", - }) - - -- Check initial state - luaunit.assertEquals(element.width, 200.0) -- 25% of 800px - luaunit.assertEquals(element.height, 120.0) -- 20% of 600px - luaunit.assertEquals(element.textSize, 16.0) -- 8% of 200px - - -- Resize viewport - element:resize(1600, 1200) - - -- Element size should update with viewport, textSize should update with element size - luaunit.assertEquals(element.width, 400.0) -- 25% of 1600px - luaunit.assertEquals(element.height, 240.0) -- 20% of 1200px - luaunit.assertEquals(element.textSize, 32.0) -- 8% of 400px -end - --- Min/Max constraint tests -function TestTextScaling.testMinTextSizeConstraint() - -- Create element with textSize that would be smaller than minimum - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = "2vh", -- 2% of 600px = 12px - minTextSize = 16, -- Minimum 16px - text = "Hello World", - }) - - -- Should be clamped to minimum - luaunit.assertEquals(element.textSize, 16) - - -- Test with very small viewport - element:resize(400, 300) -- 2% of 300px = 6px, should stay at 16px - luaunit.assertEquals(element.textSize, 16) -end - -function TestTextScaling.testMaxTextSizeConstraint() - -- Create element with textSize that would be larger than maximum - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = "4vh", -- 4% of 600px = 24px - maxTextSize = 20, -- Maximum 20px - text = "Hello World", - }) - - -- Should be clamped to maximum - luaunit.assertEquals(element.textSize, 20) - - -- Test with very large viewport - element:resize(1600, 1200) -- 4% of 1200px = 48px, should stay at 20px - luaunit.assertEquals(element.textSize, 20) -end - -function TestTextScaling.testBothMinMaxConstraints() - -- Create element with both min and max constraints - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = "3vh", -- 3% of 600px = 18px (within bounds) - minTextSize = 12, - maxTextSize = 24, - text = "Hello World", - }) - - -- Should be within bounds - luaunit.assertEquals(element.textSize, 18.0) - - -- Test small viewport (should hit min) - element:resize(400, 300) -- 3% of 300px = 9px, should be clamped to 12px - luaunit.assertEquals(element.textSize, 12) - - -- Test large viewport (should hit max) - element:resize(1600, 1200) -- 3% of 1200px = 36px, should be clamped to 24px - luaunit.assertEquals(element.textSize, 24) -end - -function TestTextScaling.testConstraintsWithElementUnits() - -- Test constraints with element-relative units - local element = Gui.new({ - id = "testElement", - width = 100, - height = 50, - textSize = "20ew", -- 20% of 100px = 20px - minTextSize = 8, - maxTextSize = 15, - text = "Hello World", - }) - - -- Should be clamped to maximum - luaunit.assertEquals(element.textSize, 15) - - -- Change width to trigger minimum - element.width = 30 -- 20% of 30px = 6px, should be clamped to 8px - element:resize(800, 600) - luaunit.assertEquals(element.textSize, 8) -end - -function TestTextScaling.testConstraintsWithFixedTextSize() - -- Test that constraints work with fixed pixel textSize too - local element = Gui.new({ - id = "testElement", - width = 200, - height = 100, - textSize = 25, -- Fixed 25px - minTextSize = 12, - maxTextSize = 20, - text = "Hello World", - }) - - -- Should be clamped to maximum even for fixed sizes - luaunit.assertEquals(element.textSize, 20) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/15_grid_layout_tests.lua b/testing/__tests__/15_grid_layout_tests.lua deleted file mode 100644 index 1cdf4d0..0000000 --- a/testing/__tests__/15_grid_layout_tests.lua +++ /dev/null @@ -1,344 +0,0 @@ --- Grid Layout Tests --- Tests for simplified grid layout functionality - -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui -local enums = FlexLove.enums - -TestGridLayout = {} - -function TestGridLayout:setUp() - -- Reset GUI before each test - Gui.destroy() - Gui.init({}) -end - -function TestGridLayout:tearDown() - Gui.destroy() -end - --- ==================== --- Basic Grid Layout Tests --- ==================== - -function TestGridLayout:test_simple_grid_creation() - local grid = Gui.new({ - x = 0, - y = 0, - width = 600, - height = 400, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 3, - }) - - lu.assertEquals(grid.positioning, enums.Positioning.GRID) - lu.assertEquals(grid.gridRows, 2) - lu.assertEquals(grid.gridColumns, 3) -end - -function TestGridLayout:test_grid_with_gaps() - local grid = Gui.new({ - x = 0, - y = 0, - width = 600, - height = 400, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - columnGap = 10, - rowGap = 20, - }) - - lu.assertEquals(grid.columnGap, 10) - lu.assertEquals(grid.rowGap, 20) -end - -function TestGridLayout:test_grid_auto_placement() - local grid = Gui.new({ - x = 0, - y = 0, - width = 300, - height = 200, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 3, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - -- Add 6 items that should auto-place in a 3x2 grid - local items = {} - for i = 1, 6 do - items[i] = Gui.new({ - parent = grid, - width = 50, - height = 50, - }) - end - - -- Check first item (top-left) - lu.assertAlmostEquals(items[1].x, 0, 1) - lu.assertAlmostEquals(items[1].y, 0, 1) - - -- Check second item (top-middle) - lu.assertAlmostEquals(items[2].x, 100, 1) - lu.assertAlmostEquals(items[2].y, 0, 1) - - -- Check fourth item (bottom-left) - lu.assertAlmostEquals(items[4].x, 0, 1) - lu.assertAlmostEquals(items[4].y, 100, 1) -end - -function TestGridLayout:test_grid_equal_distribution() - local grid = Gui.new({ - x = 0, - y = 0, - width = 300, - height = 200, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item1 = Gui.new({ - parent = grid, - width = 50, - height = 50, - }) - - local item2 = Gui.new({ - parent = grid, - width = 50, - height = 50, - }) - - -- Each cell should be 150x100 (300/2 x 200/2) - lu.assertAlmostEquals(item1.width, 150, 1) - lu.assertAlmostEquals(item1.height, 100, 1) - lu.assertAlmostEquals(item2.x, 150, 1) - lu.assertAlmostEquals(item2.width, 150, 1) -end - -function TestGridLayout:test_grid_stretch_behavior() - local grid = Gui.new({ - x = 0, - y = 0, - width = 400, - height = 200, - positioning = enums.Positioning.GRID, - gridRows = 1, - gridColumns = 3, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item1 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item2 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item3 = Gui.new({ parent = grid, width = 50, height = 50 }) - - -- Each cell should be ~133.33px wide (400/3) - -- Items should stretch to fill cells - lu.assertAlmostEquals(item1.width, 133.33, 1) - lu.assertAlmostEquals(item2.width, 133.33, 1) - lu.assertAlmostEquals(item3.width, 133.33, 1) -end - --- ==================== --- Alignment Tests --- ==================== - -function TestGridLayout:test_align_items_stretch() - local grid = Gui.new({ - x = 0, - y = 0, - width = 300, - height = 200, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 1, - alignItems = enums.AlignItems.STRETCH, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item = Gui.new({ - parent = grid, - width = 50, - }) - - -- Item should stretch to fill cell height (200/2 = 100) - lu.assertAlmostEquals(item.height, 100, 1) -end - --- ==================== --- Gap Tests --- ==================== - -function TestGridLayout:test_column_gap() - local grid = Gui.new({ - x = 0, - y = 0, - width = 320, - height = 100, - positioning = enums.Positioning.GRID, - gridRows = 1, - gridColumns = 3, - columnGap = 10, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item1 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item2 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item3 = Gui.new({ parent = grid, width = 50, height = 50 }) - - -- Total width: 320, gaps: 2*10=20, available: 300, per cell: 100 - lu.assertAlmostEquals(item1.x, 0, 1) - lu.assertAlmostEquals(item2.x, 110, 1) -- 100 + 10 gap - lu.assertAlmostEquals(item3.x, 220, 1) -- 100 + 10 + 100 + 10 -end - -function TestGridLayout:test_row_gap() - local grid = Gui.new({ - x = 0, - y = 0, - width = 100, - height = 320, - positioning = enums.Positioning.GRID, - gridRows = 3, - gridColumns = 1, - columnGap = 0, - rowGap = 10, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item1 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item2 = Gui.new({ parent = grid, width = 50, height = 50 }) - local item3 = Gui.new({ parent = grid, width = 50, height = 50 }) - - -- Total height: 320, gaps: 2*10=20, available: 300, per cell: 100 - lu.assertAlmostEquals(item1.y, 0, 1) - lu.assertAlmostEquals(item2.y, 110, 1) -- 100 + 10 gap - lu.assertAlmostEquals(item3.y, 220, 1) -- 100 + 10 + 100 + 10 -end - --- ==================== --- Nested Grid Tests --- ==================== - -function TestGridLayout:test_nested_grids() - local outerGrid = Gui.new({ - x = 0, - y = 0, - width = 400, - height = 400, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local innerGrid = Gui.new({ - parent = outerGrid, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - -- Add items to inner grid - local item1 = Gui.new({ parent = innerGrid, width = 50, height = 50 }) - local item2 = Gui.new({ parent = innerGrid, width = 50, height = 50 }) - - -- Inner grid should be stretched to fill outer grid cell (200x200) - lu.assertAlmostEquals(innerGrid.width, 200, 1) - lu.assertAlmostEquals(innerGrid.height, 200, 1) - - -- Items in inner grid should be positioned correctly - -- Each cell in inner grid is 100x100 - lu.assertAlmostEquals(item1.x, 0, 1) - lu.assertAlmostEquals(item1.y, 0, 1) - lu.assertAlmostEquals(item2.x, 100, 1) - lu.assertAlmostEquals(item2.y, 0, 1) -end - --- ==================== --- Edge Cases --- ==================== - -function TestGridLayout:test_more_items_than_cells() - local grid = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - positioning = enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local items = {} - for i = 1, 6 do - items[i] = Gui.new({ - parent = grid, - width = 50, - height = 50, - }) - end - - -- First 4 items should be positioned - lu.assertAlmostEquals(items[1].x, 0, 1) - lu.assertAlmostEquals(items[4].x, 100, 1) - lu.assertAlmostEquals(items[4].y, 100, 1) - - -- Items 5 and 6 should not be laid out (remain at parent position) - -- This is acceptable behavior - they're just not visible in the grid -end - -function TestGridLayout:test_single_cell_grid() - local grid = Gui.new({ - x = 0, - y = 0, - width = 100, - height = 100, - positioning = enums.Positioning.GRID, - gridRows = 1, - gridColumns = 1, - columnGap = 0, - rowGap = 0, - padding = { horizontal = 0, vertical = 0 }, - }) - - local item = Gui.new({ - parent = grid, - width = 50, - height = 50, - }) - - -- Item should stretch to fill the entire grid - lu.assertAlmostEquals(item.x, 0, 1) - lu.assertAlmostEquals(item.y, 0, 1) - lu.assertAlmostEquals(item.width, 100, 1) - lu.assertAlmostEquals(item.height, 100, 1) -end - -print("Running Simplified Grid Layout Tests...") -lu.LuaUnit.run() diff --git a/testing/__tests__/16_event_system_tests.lua b/testing/__tests__/16_event_system_tests.lua deleted file mode 100644 index bbcda3d..0000000 --- a/testing/__tests__/16_event_system_tests.lua +++ /dev/null @@ -1,369 +0,0 @@ --- Event System Tests --- Tests for the enhanced callback system with InputEvent objects - -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui - -TestEventSystem = {} - -function TestEventSystem:setUp() - -- Clear all keyboard modifier states at start of each test - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - Gui.init({ baseScale = { width = 1920, height = 1080 } }) - love.window.setMode(1920, 1080) - Gui.resize(1920, 1080) -- Recalculate scale factors after setMode -end - -function TestEventSystem:tearDown() - -- Clean up after each test - Gui.destroy() - -- Reset keyboard state - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) -end - --- Test 1: Event object structure -function TestEventSystem:test_event_object_has_required_fields() - local eventReceived = nil - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - eventReceived = event - end, - }) - - -- Simulate mouse press and release - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - -- Verify event object structure - lu.assertNotNil(eventReceived, "Event should be received") - lu.assertNotNil(eventReceived.type, "Event should have type field") - lu.assertNotNil(eventReceived.button, "Event should have button field") - lu.assertNotNil(eventReceived.x, "Event should have x field") - lu.assertNotNil(eventReceived.y, "Event should have y field") - lu.assertNotNil(eventReceived.modifiers, "Event should have modifiers field") - lu.assertNotNil(eventReceived.clickCount, "Event should have clickCount field") - lu.assertNotNil(eventReceived.timestamp, "Event should have timestamp field") -end - --- Test 2: Left click event -function TestEventSystem:test_left_click_generates_click_event() - local eventsReceived = {} - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - table.insert(eventsReceived, { type = event.type, button = event.button }) - end, - }) - - -- Simulate left click - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - -- Should receive press, click, and release events - lu.assertTrue(#eventsReceived >= 2, "Should receive at least 2 events") - - -- Check for click event - local hasClickEvent = false - for _, evt in ipairs(eventsReceived) do - if evt.type == "click" and evt.button == 1 then - hasClickEvent = true - break - end - end - lu.assertTrue(hasClickEvent, "Should receive click event for left button") -end - --- Test 3: Right click event -function TestEventSystem:test_right_click_generates_rightclick_event() - local eventsReceived = {} - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - table.insert(eventsReceived, { type = event.type, button = event.button }) - end, - }) - - -- Simulate right click - love.mouse.setPosition(150, 150) - love.mouse.setDown(2, true) - button:update(0.016) - - love.mouse.setDown(2, false) - button:update(0.016) - - -- Check for rightclick event - local hasRightClickEvent = false - for _, evt in ipairs(eventsReceived) do - if evt.type == "rightclick" and evt.button == 2 then - hasRightClickEvent = true - break - end - end - lu.assertTrue(hasRightClickEvent, "Should receive rightclick event for right button") -end - --- Test 4: Middle click event -function TestEventSystem:test_middle_click_generates_middleclick_event() - local eventsReceived = {} - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - table.insert(eventsReceived, { type = event.type, button = event.button }) - end, - }) - - -- Simulate middle click - love.mouse.setPosition(150, 150) - love.mouse.setDown(3, true) - button:update(0.016) - - love.mouse.setDown(3, false) - button:update(0.016) - - -- Check for middleclick event - local hasMiddleClickEvent = false - for _, evt in ipairs(eventsReceived) do - if evt.type == "middleclick" and evt.button == 3 then - hasMiddleClickEvent = true - break - end - end - lu.assertTrue(hasMiddleClickEvent, "Should receive middleclick event for middle button") -end - --- Test 5: Modifier keys detection -function TestEventSystem:test_modifier_keys_are_detected() - local eventReceived = nil - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - if event.type == "click" then - eventReceived = event - end - end, - }) - - -- Simulate shift + click - love.keyboard.setDown("lshift", true) - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - lu.assertNotNil(eventReceived, "Should receive click event") - lu.assertTrue(eventReceived.modifiers.shift, "Shift modifier should be detected") -end - --- Test 6: Double click detection -function TestEventSystem:test_double_click_increments_click_count() - local clickEvents = {} - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - if event.type == "click" then - table.insert(clickEvents, event.clickCount) - end - end, - }) - - -- Simulate first click - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - love.mouse.setDown(1, false) - button:update(0.016) - - -- Simulate second click quickly (double-click) - love.timer.setTime(love.timer.getTime() + 0.1) -- 100ms later - love.mouse.setDown(1, true) - button:update(0.016) - love.mouse.setDown(1, false) - button:update(0.016) - - lu.assertEquals(#clickEvents, 2, "Should receive 2 click events") - lu.assertEquals(clickEvents[1], 1, "First click should have clickCount = 1") - lu.assertEquals(clickEvents[2], 2, "Second click should have clickCount = 2") -end - --- Test 7: Press and release events -function TestEventSystem:test_press_and_release_events_are_fired() - local eventsReceived = {} - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - table.insert(eventsReceived, event.type) - end, - }) - - -- Simulate click - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - -- Should receive press, click, and release - lu.assertTrue(#eventsReceived >= 2, "Should receive multiple events") - - local hasPress = false - local hasRelease = false - for _, eventType in ipairs(eventsReceived) do - if eventType == "press" then - hasPress = true - end - if eventType == "release" then - hasRelease = true - end - end - - lu.assertTrue(hasPress, "Should receive press event") - lu.assertTrue(hasRelease, "Should receive release event") -end - --- Test 8: Mouse position in event -function TestEventSystem:test_event_contains_mouse_position() - local eventReceived = nil - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - if event.type == "click" then - eventReceived = event - end - end, - }) - - -- Simulate click at specific position - local mouseX, mouseY = 175, 125 - love.mouse.setPosition(mouseX, mouseY) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - lu.assertNotNil(eventReceived, "Should receive click event") - lu.assertEquals(eventReceived.x, mouseX, "Event should contain correct mouse X position") - lu.assertEquals(eventReceived.y, mouseY, "Event should contain correct mouse Y position") -end - --- Test 9: No callback when mouse outside element -function TestEventSystem:test_no_callback_when_clicking_outside_element() - local callbackCalled = false - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - callbackCalled = true - end, - }) - - -- Click outside element - love.mouse.setPosition(50, 50) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - lu.assertFalse(callbackCalled, "Callback should not be called when clicking outside element") -end - --- Test 10: Multiple modifiers -function TestEventSystem:test_multiple_modifiers_detected() - local eventReceived = nil - - local button = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(element, event) - if event.type == "click" then - eventReceived = event - end - end, - }) - - -- Simulate shift + ctrl + click - love.keyboard.setDown("lshift", true) - love.keyboard.setDown("lctrl", true) - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - button:update(0.016) - - love.mouse.setDown(1, false) - button:update(0.016) - - lu.assertNotNil(eventReceived, "Should receive click event") - lu.assertTrue(eventReceived.modifiers.shift, "Shift modifier should be detected") - lu.assertTrue(eventReceived.modifiers.ctrl, "Ctrl modifier should be detected") -end - -print("Running Event System Tests...") -lu.LuaUnit.run() diff --git a/testing/__tests__/17_sibling_space_reservation_tests.lua b/testing/__tests__/17_sibling_space_reservation_tests.lua deleted file mode 100644 index 94fbe7e..0000000 --- a/testing/__tests__/17_sibling_space_reservation_tests.lua +++ /dev/null @@ -1,437 +0,0 @@ --- Test: Sibling Space Reservation in Flex and Grid Layouts --- Purpose: Verify that absolutely positioned siblings with explicit positioning --- properly reserve space in flex and grid containers -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui -local Color = FlexLove.Color - -TestSiblingSpaceReservation = {} - -function TestSiblingSpaceReservation:setUp() - -- Reset GUI state before each test - Gui.destroy() - -- Set up a standard viewport - love.window.setMode(1920, 1080) -end - -function TestSiblingSpaceReservation:tearDown() - Gui.destroy() -end - --- ==================== --- Flex Layout Tests --- ==================== - -function TestSiblingSpaceReservation:test_flex_horizontal_left_positioned_sibling_reserves_space() - -- Create a flex container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 200, - positioning = "flex", - flexDirection = "horizontal", - justifyContent = "flex-start", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add an absolutely positioned sibling with left positioning - local absoluteSibling = Gui.new({ - parent = container, - positioning = "absolute", - left = 10, -- 10px from left edge - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Add a flex child that should start after the absolutely positioned sibling - local flexChild = Gui.new({ - parent = container, - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- The absolutely positioned sibling reserves: left (10) + width (50) + padding (0) = 60px - -- The flex child should start at x = container.x + padding.left + reservedLeft - -- = 0 + 0 + 60 = 60 - lu.assertEquals(flexChild.x, 60, "Flex child should start after absolutely positioned sibling") -end - -function TestSiblingSpaceReservation:test_flex_horizontal_right_positioned_sibling_reserves_space() - -- Create a flex container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 200, - positioning = "flex", - flexDirection = "horizontal", - justifyContent = "flex-start", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add an absolutely positioned sibling with right positioning - local absoluteSibling = Gui.new({ - parent = container, - positioning = "absolute", - right = 10, -- 10px from right edge - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Add a flex child - local flexChild = Gui.new({ - parent = container, - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- The absolutely positioned sibling reserves: right (10) + width (50) + padding (0) = 60px - -- Available space = 1000 - 0 (padding) - 0 (reservedLeft) - 60 (reservedRight) = 940px - -- The flex child (width 100) should fit within this space - -- Child should start at x = 0 - lu.assertEquals(flexChild.x, 0, "Flex child should start at container left edge") - - -- The absolutely positioned sibling should be at the right edge - -- x = container.x + container.width + padding.left - right - (width + padding) - -- = 0 + 1000 + 0 - 10 - 50 = 940 - lu.assertEquals(absoluteSibling.x, 940, "Absolutely positioned sibling should be at right edge") -end - -function TestSiblingSpaceReservation:test_flex_vertical_top_positioned_sibling_reserves_space() - -- Create a vertical flex container - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 1000, - positioning = "flex", - flexDirection = "vertical", - justifyContent = "flex-start", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add an absolutely positioned sibling with top positioning - local absoluteSibling = Gui.new({ - parent = container, - positioning = "absolute", - top = 10, -- 10px from top edge - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Add a flex child that should start after the absolutely positioned sibling - local flexChild = Gui.new({ - parent = container, - width = 50, - height = 100, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- The absolutely positioned sibling reserves: top (10) + height (50) + padding (0) = 60px - -- The flex child should start at y = container.y + padding.top + reservedTop - -- = 0 + 0 + 60 = 60 - lu.assertEquals(flexChild.y, 60, "Flex child should start after absolutely positioned sibling") -end - -function TestSiblingSpaceReservation:test_flex_horizontal_multiple_positioned_siblings() - -- Create a flex container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 200, - positioning = "flex", - flexDirection = "horizontal", - justifyContent = "flex-start", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add two absolutely positioned siblings (left and right) - local leftSibling = Gui.new({ - parent = container, - positioning = "absolute", - left = 5, - width = 40, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - local rightSibling = Gui.new({ - parent = container, - positioning = "absolute", - right = 5, - width = 40, - height = 50, - backgroundColor = Color.new(0, 0, 1, 1), - }) - - -- Add flex children - local flexChild1 = Gui.new({ - parent = container, - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - local flexChild2 = Gui.new({ - parent = container, - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 1, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Reserved left: 5 + 40 = 45px - -- Reserved right: 5 + 40 = 45px - -- Available space: 1000 - 45 - 45 = 910px - -- First flex child should start at x = 0 + 0 + 45 = 45 - lu.assertEquals(flexChild1.x, 45, "First flex child should start after left sibling") - - -- Second flex child should start at x = 45 + 100 + gap = 145 (assuming gap=10) - lu.assertIsTrue(flexChild2.x >= 145, "Second flex child should be positioned after first") -end - --- ==================== --- Grid Layout Tests --- ==================== - -function TestSiblingSpaceReservation:test_grid_left_positioned_sibling_reserves_space() - -- Create a grid container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 500, - positioning = "grid", - gridRows = 2, - gridColumns = 3, - columnGap = 10, - rowGap = 10, - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add an absolutely positioned sibling with left positioning - local absoluteSibling = Gui.new({ - parent = container, - positioning = "absolute", - left = 10, - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Add grid children - local gridChild1 = Gui.new({ - parent = container, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Reserved left: 10 + 50 = 60px - -- Available width: 1000 - 60 = 940px - -- Column gaps: 2 * 10 = 20px - -- Cell width: (940 - 20) / 3 = 306.67px - -- First grid child should start at x = 0 + 0 + 60 = 60 - lu.assertEquals(gridChild1.x, 60, "Grid child should start after absolutely positioned sibling") -end - -function TestSiblingSpaceReservation:test_grid_top_positioned_sibling_reserves_space() - -- Create a grid container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 500, - positioning = "grid", - gridRows = 2, - gridColumns = 3, - columnGap = 10, - rowGap = 10, - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add an absolutely positioned sibling with top positioning - local absoluteSibling = Gui.new({ - parent = container, - positioning = "absolute", - top = 10, - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Add grid children - local gridChild1 = Gui.new({ - parent = container, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Reserved top: 10 + 50 = 60px - -- Available height: 500 - 60 = 440px - -- Row gaps: 1 * 10 = 10px - -- Cell height: (440 - 10) / 2 = 215px - -- First grid child should start at y = 0 + 0 + 60 = 60 - lu.assertEquals(gridChild1.y, 60, "Grid child should start after absolutely positioned sibling") -end - -function TestSiblingSpaceReservation:test_grid_multiple_positioned_siblings() - -- Create a grid container - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 500, - positioning = "grid", - gridRows = 2, - gridColumns = 2, - columnGap = 0, - rowGap = 0, - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Add absolutely positioned siblings at all corners - local topLeftSibling = Gui.new({ - parent = container, - positioning = "absolute", - left = 10, - top = 10, - width = 40, - height = 40, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - local bottomRightSibling = Gui.new({ - parent = container, - positioning = "absolute", - right = 10, - bottom = 10, - width = 40, - height = 40, - backgroundColor = Color.new(0, 0, 1, 1), - }) - - -- Add grid children - local gridChild1 = Gui.new({ - parent = container, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Reserved left: 10 + 40 = 50px - -- Reserved right: 10 + 40 = 50px - -- Reserved top: 10 + 40 = 50px - -- Reserved bottom: 10 + 40 = 50px - -- Available width: 1000 - 50 - 50 = 900px - -- Available height: 500 - 50 - 50 = 400px - -- Cell width: 900 / 2 = 450px - -- Cell height: 400 / 2 = 200px - -- First grid child should start at (50, 50) - lu.assertEquals(gridChild1.x, 50, "Grid child X should account for left sibling") - lu.assertEquals(gridChild1.y, 50, "Grid child Y should account for top sibling") - lu.assertEquals(gridChild1.width, 450, "Grid cell width should account for reserved space") - lu.assertEquals(gridChild1.height, 200, "Grid cell height should account for reserved space") -end - --- ==================== --- Edge Cases --- ==================== - -function TestSiblingSpaceReservation:test_non_explicitly_absolute_children_dont_reserve_space() - -- Children that default to absolute positioning (not explicitly set) - -- should NOT reserve space in flex layouts - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 200, - positioning = "flex", - flexDirection = "horizontal", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- This child has positioning="flex" so it participates in layout - local flexChild = Gui.new({ - parent = container, - positioning = "flex", - left = 10, -- This should be ignored since it's a flex child - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Flex child should start at x = 0 (no reserved space) - lu.assertEquals(flexChild.x, 0, "Flex children with positioning offsets should not reserve space") -end - -function TestSiblingSpaceReservation:test_absolute_without_positioning_offsets_doesnt_reserve_space() - -- Absolutely positioned children without left/right/top/bottom - -- should NOT reserve space - local container = Gui.new({ - x = 0, - y = 0, - width = 1000, - height = 200, - positioning = "flex", - flexDirection = "horizontal", - padding = { top = 0, right = 0, bottom = 0, left = 0 }, - }) - - -- Absolutely positioned but no positioning offsets - local absoluteChild = Gui.new({ - parent = container, - positioning = "absolute", - x = 50, - y = 50, - width = 50, - height = 50, - backgroundColor = Color.new(1, 0, 0, 1), - }) - - -- Flex child - local flexChild = Gui.new({ - parent = container, - width = 100, - height = 50, - backgroundColor = Color.new(0, 1, 0, 1), - }) - - -- Layout children - container:layoutChildren() - - -- Flex child should start at x = 0 (no reserved space) - lu.assertEquals(flexChild.x, 0, "Absolute children without positioning offsets should not reserve space") -end - -print("Running Sibling Space Reservation Tests...") -lu.LuaUnit.run() diff --git a/testing/__tests__/18_font_family_inheritance_tests.lua b/testing/__tests__/18_font_family_inheritance_tests.lua deleted file mode 100644 index 0ec9845..0000000 --- a/testing/__tests__/18_font_family_inheritance_tests.lua +++ /dev/null @@ -1,225 +0,0 @@ -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") - -TestFontFamilyInheritance = {} - -function TestFontFamilyInheritance:setUp() - FlexLove.Gui.destroy() - FlexLove.Gui.init({ baseScale = { width = 1920, height = 1080 } }) -end - -function TestFontFamilyInheritance:tearDown() - FlexLove.Gui.destroy() -end - -function TestFontFamilyInheritance:testBasicInheritanceFromParent() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - fontFamily = "Arial", - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child", - }) - - lu.assertEquals(child.fontFamily, "Arial", "Child should inherit fontFamily from parent") -end - -function TestFontFamilyInheritance:testInheritanceThroughMultipleLevels() - local grandparent = FlexLove.Element.new({ - width = 300, - height = 300, - fontFamily = "Times", - }) - - local parent = FlexLove.Element.new({ - parent = grandparent, - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Grandchild", - }) - - lu.assertEquals(parent.fontFamily, "Times", "Parent should inherit fontFamily from grandparent") - lu.assertEquals(child.fontFamily, "Times", "Child should inherit fontFamily through parent") -end - -function TestFontFamilyInheritance:testExplicitOverrideBreaksInheritance() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - fontFamily = "Arial", - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child", - fontFamily = "Helvetica", - }) - - lu.assertEquals(child.fontFamily, "Helvetica", "Child's explicit fontFamily should override parent's") -end - -function TestFontFamilyInheritance:testInheritanceWithNoParentFontFamily() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child", - }) - - lu.assertNil(child.fontFamily, "Child should have nil fontFamily when parent doesn't have one") -end - -function TestFontFamilyInheritance:testInheritanceInFlexContainer() - local flexParent = FlexLove.Element.new({ - width = 300, - height = 300, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL, - fontFamily = "Courier", - }) - - local child1 = FlexLove.Element.new({ - parent = flexParent, - width = 100, - height = 100, - text = "Child 1", - }) - - local child2 = FlexLove.Element.new({ - parent = flexParent, - width = 100, - height = 100, - text = "Child 2", - }) - - lu.assertEquals(child1.fontFamily, "Courier", "Child 1 should inherit fontFamily in flex container") - lu.assertEquals(child2.fontFamily, "Courier", "Child 2 should inherit fontFamily in flex container") -end - -function TestFontFamilyInheritance:testInheritanceInGridContainer() - local gridParent = FlexLove.Element.new({ - width = 300, - height = 300, - positioning = FlexLove.enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - fontFamily = "Verdana", - }) - - local child1 = FlexLove.Element.new({ - parent = gridParent, - text = "Cell 1", - }) - - local child2 = FlexLove.Element.new({ - parent = gridParent, - text = "Cell 2", - }) - - lu.assertEquals(child1.fontFamily, "Verdana", "Child 1 should inherit fontFamily in grid container") - lu.assertEquals(child2.fontFamily, "Verdana", "Child 2 should inherit fontFamily in grid container") -end - -function TestFontFamilyInheritance:testMixedInheritanceAndOverride() - local grandparent = FlexLove.Element.new({ - width = 400, - height = 400, - fontFamily = "Georgia", - }) - - local parent = FlexLove.Element.new({ - parent = grandparent, - width = 300, - height = 300, - }) - - local child1 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child 1", - }) - - local child2 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child 2", - fontFamily = "Impact", - }) - - lu.assertEquals(parent.fontFamily, "Georgia", "Parent should inherit from grandparent") - lu.assertEquals(child1.fontFamily, "Georgia", "Child 1 should inherit through parent") - lu.assertEquals(child2.fontFamily, "Impact", "Child 2 should use explicit fontFamily") -end - -function TestFontFamilyInheritance:testInheritanceWithAbsolutePositioning() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - fontFamily = "Comic Sans", - }) - - local child = FlexLove.Element.new({ - parent = parent, - positioning = FlexLove.enums.Positioning.ABSOLUTE, - x = 50, - y = 50, - width = 100, - height = 100, - text = "Absolute Child", - }) - - lu.assertEquals(child.fontFamily, "Comic Sans", "Absolutely positioned child should still inherit fontFamily") -end - -function TestFontFamilyInheritance:testInheritanceDoesNotAffectSiblings() - local parent = FlexLove.Element.new({ - width = 300, - height = 300, - fontFamily = "Tahoma", - }) - - local child1 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child 1", - fontFamily = "Trebuchet", - }) - - local child2 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - text = "Child 2", - }) - - lu.assertEquals(child1.fontFamily, "Trebuchet", "Child 1 should have its own fontFamily") - lu.assertEquals(child2.fontFamily, "Tahoma", "Child 2 should inherit parent's fontFamily") - lu.assertNotEquals(child2.fontFamily, child1.fontFamily, "Siblings should have independent fontFamily values") -end - -print("Running Font Family Inheritance Tests...") -lu.LuaUnit.run() diff --git a/testing/__tests__/19_negative_margin_tests.lua b/testing/__tests__/19_negative_margin_tests.lua deleted file mode 100644 index 9d0855e..0000000 --- a/testing/__tests__/19_negative_margin_tests.lua +++ /dev/null @@ -1,337 +0,0 @@ -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") - -TestNegativeMargin = {} - -function TestNegativeMargin:setUp() - FlexLove.Gui.destroy() - -- Don't call init to use 1:1 scaling (like other tests) -end - -function TestNegativeMargin:tearDown() - FlexLove.Gui.destroy() -end - -function TestNegativeMargin:testBasicNegativeMarginTop() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.VERTICAL, - }) - - local child1 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - }) - - local child2 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - margin = { top = -20 }, - }) - - parent:layoutChildren() - - lu.assertNotNil(child2.margin.top) - lu.assertEquals(child2.margin.top, -20, "Child2 should have -20 top margin") -end - -function TestNegativeMargin:testNegativeMarginLeft() - local parent = FlexLove.Element.new({ - width = 300, - height = 100, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL, - }) - - local child1 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - }) - - local child2 = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - margin = { left = -30 }, - }) - - parent:layoutChildren() - - lu.assertEquals(child2.margin.left, -30, "Child2 should have -30 left margin") -end - -function TestNegativeMargin:testNegativeMarginRight() - local parent = FlexLove.Element.new({ - width = 300, - height = 100, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - margin = { right = -15 }, - }) - - parent:layoutChildren() - - lu.assertEquals(child.margin.right, -15, "Child should have -15 right margin") -end - -function TestNegativeMargin:testNegativeMarginBottom() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.VERTICAL, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 50, - margin = { bottom = -10 }, - }) - - parent:layoutChildren() - - lu.assertEquals(child.margin.bottom, -10, "Child should have -10 bottom margin") -end - -function TestNegativeMargin:testMultipleNegativeMargins() - local parent = FlexLove.Element.new({ - width = 300, - height = 300, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { top = -20, right = -15, bottom = -10, left = -5 }, - }) - - lu.assertEquals(child.margin.top, -20) - lu.assertEquals(child.margin.right, -15) - lu.assertEquals(child.margin.bottom, -10) - lu.assertEquals(child.margin.left, -5) -end - -function TestNegativeMargin:testNegativeMarginWithPercentage() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { left = "-10%" }, - }) - - lu.assertEquals(child.margin.left, -20, "Negative 10% of 200 width should be -20") -end - -function TestNegativeMargin:testNegativeMarginWithVwUnit() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { left = "-2vw" }, - }) - - lu.assertNotNil(child.margin.left) - lu.assertTrue(child.margin.left < 0, "Negative vw margin should be negative") -end - -function TestNegativeMargin:testNegativeMarginWithVhUnit() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { top = "-2vh" }, - }) - - lu.assertNotNil(child.margin.top) - lu.assertTrue(child.margin.top < 0, "Negative vh margin should be negative") -end - -function TestNegativeMargin:testNegativeMarginInGridLayout() - local gridParent = FlexLove.Element.new({ - width = 300, - height = 300, - positioning = FlexLove.enums.Positioning.GRID, - gridRows = 2, - gridColumns = 2, - }) - - local child = FlexLove.Element.new({ - parent = gridParent, - width = 100, - height = 100, - margin = { top = -10, left = -10 }, - }) - - lu.assertEquals(child.margin.top, -10) - lu.assertEquals(child.margin.left, -10) -end - -function TestNegativeMargin:testNegativeMarginVerticalShorthand() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { vertical = -15 }, - }) - - lu.assertEquals(child.margin.top, -15, "Vertical shorthand should set top to -15") - lu.assertEquals(child.margin.bottom, -15, "Vertical shorthand should set bottom to -15") -end - -function TestNegativeMargin:testNegativeMarginHorizontalShorthand() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { horizontal = -20 }, - }) - - lu.assertEquals(child.margin.left, -20, "Horizontal shorthand should set left to -20") - lu.assertEquals(child.margin.right, -20, "Horizontal shorthand should set right to -20") -end - -function TestNegativeMargin:testMixedPositiveAndNegativeMargins() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { top = 20, right = -10, bottom = 15, left = -5 }, - }) - - lu.assertEquals(child.margin.top, 20) - lu.assertEquals(child.margin.right, -10) - lu.assertEquals(child.margin.bottom, 15) - lu.assertEquals(child.margin.left, -5) -end - -function TestNegativeMargin:testNegativeMarginWithAbsolutePositioning() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - positioning = FlexLove.enums.Positioning.ABSOLUTE, - x = 50, - y = 50, - width = 100, - height = 100, - margin = { top = -10, left = -10 }, - }) - - lu.assertEquals(child.margin.top, -10) - lu.assertEquals(child.margin.left, -10) -end - -function TestNegativeMargin:testNegativeMarginDoesNotAffectPadding() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - padding = { top = 10, left = 10 }, - margin = { top = -15, left = -15 }, - }) - - lu.assertEquals(child.padding.top, 10, "Padding should not be affected by negative margin") - lu.assertEquals(child.padding.left, 10, "Padding should not be affected by negative margin") - lu.assertEquals(child.margin.top, -15) - lu.assertEquals(child.margin.left, -15) -end - -function TestNegativeMargin:testExtremeNegativeMarginValues() - local parent = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { top = -1000, left = -1000 }, - }) - - lu.assertEquals(child.margin.top, -1000, "Extreme negative margin should be allowed") - lu.assertEquals(child.margin.left, -1000, "Extreme negative margin should be allowed") -end - -function TestNegativeMargin:testNegativeMarginInNestedElements() - local grandparent = FlexLove.Element.new({ - width = 300, - height = 300, - }) - - local parent = FlexLove.Element.new({ - parent = grandparent, - width = 200, - height = 200, - margin = { top = -20, left = -20 }, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - margin = { top = -10, left = -10 }, - }) - - lu.assertEquals(parent.margin.top, -20) - lu.assertEquals(parent.margin.left, -20) - lu.assertEquals(child.margin.top, -10) - lu.assertEquals(child.margin.left, -10) -end - -print("Running Negative Margin Tests...") -lu.LuaUnit.run() diff --git a/testing/__tests__/20_padding_resize_tests.lua b/testing/__tests__/20_padding_resize_tests.lua deleted file mode 100644 index fee44f7..0000000 --- a/testing/__tests__/20_padding_resize_tests.lua +++ /dev/null @@ -1,243 +0,0 @@ --- Test padding resize behavior with percentage units -package.path = package.path .. ";?.lua" -local luaunit = require("testing.luaunit") -local loveStub = require("testing.loveStub") -_G.love = loveStub -local FlexLove = require("FlexLove") - -TestPaddingResize = {} - -function TestPaddingResize:setUp() - -- Reset GUI state before each test - FlexLove.Gui.destroy() - - -- Set up a consistent viewport size - love.window.setMode(1920, 1080) - - -- Initialize with base scaling - FlexLove.Gui.init({ - baseScale = { width = 1920, height = 1080 }, - }) -end - -function TestPaddingResize:tearDown() - FlexLove.Gui.destroy() -end - --- Test that percentage padding recalculates on resize -function TestPaddingResize:testPercentagePaddingHorizontalResize() - -- Create parent with percentage padding - local parent = FlexLove.Element.new({ - x = 0, - y = 0, - width = "100%", - height = "100%", - padding = { horizontal = "10%", vertical = "5%" }, - }) - - -- Initial padding should be 10% of 1920 = 192px (horizontal), 5% of 1080 = 54px (vertical) - luaunit.assertAlmostEquals(parent.padding.left, 192, 1, "Initial left padding should be 10% of 1920") - luaunit.assertAlmostEquals(parent.padding.right, 192, 1, "Initial right padding should be 10% of 1920") - luaunit.assertAlmostEquals(parent.padding.top, 54, 1, "Initial top padding should be 5% of 1080") - luaunit.assertAlmostEquals(parent.padding.bottom, 54, 1, "Initial bottom padding should be 5% of 1080") - - -- Resize to larger viewport - parent:resize(2560, 1440) - - -- Padding should recalculate: 10% of 2560 = 256px (horizontal), 5% of 1440 = 72px (vertical) - luaunit.assertAlmostEquals(parent.padding.left, 256, 1, "After resize, left padding should be 10% of 2560") - luaunit.assertAlmostEquals(parent.padding.right, 256, 1, "After resize, right padding should be 10% of 2560") - luaunit.assertAlmostEquals(parent.padding.top, 72, 1, "After resize, top padding should be 5% of 1440") - luaunit.assertAlmostEquals(parent.padding.bottom, 72, 1, "After resize, bottom padding should be 5% of 1440") - - -- Resize to smaller viewport - parent:resize(1280, 720) - - -- Padding should recalculate: 10% of 1280 = 128px (horizontal), 5% of 720 = 36px (vertical) - luaunit.assertAlmostEquals(parent.padding.left, 128, 1, "After second resize, left padding should be 10% of 1280") - luaunit.assertAlmostEquals(parent.padding.right, 128, 1, "After second resize, right padding should be 10% of 1280") - luaunit.assertAlmostEquals(parent.padding.top, 36, 1, "After second resize, top padding should be 5% of 720") - luaunit.assertAlmostEquals(parent.padding.bottom, 36, 1, "After second resize, bottom padding should be 5% of 720") -end - --- Test that pixel padding with fixed dimensions doesn't shrink on resize -function TestPaddingResize:testPixelPaddingFixedDimensions() - -- Create element with pixel padding and fixed dimensions - local element = FlexLove.Element.new({ - x = 0, - y = 0, - width = 160, - height = 40, - padding = { horizontal = 12, vertical = 8 }, - }) - - -- Store initial dimensions - local initialWidth = element.width - local initialHeight = element.height - local initialPaddingLeft = element.padding.left - local initialPaddingTop = element.padding.top - - -- Resize multiple times - for i = 1, 5 do - element:resize(1920 + i * 100, 1080 + i * 50) - end - - -- Dimensions should scale with base scaling but not shrink progressively - luaunit.assertTrue( - element.width >= initialWidth * 0.9, - string.format("Width should not shrink significantly. Initial: %f, Current: %f", initialWidth, element.width) - ) - luaunit.assertTrue( - element.height >= initialHeight * 0.9, - string.format("Height should not shrink significantly. Initial: %f, Current: %f", initialHeight, element.height) - ) -end - --- Test nested elements with percentage padding -function TestPaddingResize:testNestedPercentagePadding() - -- Create parent with percentage padding - local parent = FlexLove.Element.new({ - x = 0, - y = 0, - width = "100%", - height = "100%", - padding = { horizontal = "10%", vertical = "10%" }, - positioning = FlexLove.enums.Positioning.FLEX, - flexDirection = FlexLove.enums.FlexDirection.VERTICAL, - }) - - -- Create child with percentage padding (relative to parent's content area) - local child = FlexLove.Element.new({ - parent = parent, - width = "80%", - height = "50%", - padding = { horizontal = "5%", vertical = "5%" }, - }) - - -- Store initial child padding - local initialChildPaddingLeft = child.padding.left - local initialChildPaddingTop = child.padding.top - - -- Resize - parent:resize(2560, 1440) - - -- Child padding should recalculate based on parent's new content area - -- Parent content width after padding: 2560 - 2*(10% of 2560) = 2560 - 512 = 2048 - -- Child width: 80% of 2048 = 1638.4 - -- Child horizontal padding: 5% of 1638.4 = 81.92 - luaunit.assertTrue( - child.padding.left > initialChildPaddingLeft, - string.format( - "Child left padding should increase after resize. Initial: %f, Current: %f", - initialChildPaddingLeft, - child.padding.left - ) - ) - luaunit.assertTrue( - child.padding.top > initialChildPaddingTop, - string.format( - "Child top padding should increase after resize. Initial: %f, Current: %f", - initialChildPaddingTop, - child.padding.top - ) - ) -end - --- Test that percentage padding doesn't cause progressive shrinkage -function TestPaddingResize:testNoProgressiveShrinkage() - -- Create element with percentage padding - local element = FlexLove.Element.new({ - x = 0, - y = 0, - width = "100%", - height = "100%", - padding = { horizontal = "10%", vertical = "10%" }, - }) - - -- Store initial content dimensions - local initialContentWidth = element.width - local initialContentHeight = element.height - - -- Resize back to original size multiple times - for i = 1, 10 do - element:resize(2560, 1440) -- Larger - element:resize(1920, 1080) -- Back to original - end - - -- Content dimensions should return to original (no progressive shrinkage) - luaunit.assertAlmostEquals( - element.width, - initialContentWidth, - 5, - string.format( - "Content width should return to original after multiple resizes. Initial: %f, Current: %f", - initialContentWidth, - element.width - ) - ) - luaunit.assertAlmostEquals( - element.height, - initialContentHeight, - 5, - string.format( - "Content height should return to original after multiple resizes. Initial: %f, Current: %f", - initialContentHeight, - element.height - ) - ) -end - --- Test viewport-relative padding (vw/vh) -function TestPaddingResize:testViewportRelativePadding() - -- Create element with viewport-relative padding - local element = FlexLove.Element.new({ - x = 0, - y = 0, - width = "50%", - height = "50%", - padding = { horizontal = "2vw", vertical = "3vh" }, - }) - - -- Initial padding: 2vw of 1920 = 38.4px, 3vh of 1080 = 32.4px - luaunit.assertAlmostEquals(element.padding.left, 38.4, 1, "Initial left padding should be 2vw of 1920") - luaunit.assertAlmostEquals(element.padding.top, 32.4, 1, "Initial top padding should be 3vh of 1080") - - -- Resize - element:resize(2560, 1440) - - -- Padding should recalculate: 2vw of 2560 = 51.2px, 3vh of 1440 = 43.2px - luaunit.assertAlmostEquals(element.padding.left, 51.2, 1, "After resize, left padding should be 2vw of 2560") - luaunit.assertAlmostEquals(element.padding.top, 43.2, 1, "After resize, top padding should be 3vh of 1440") -end - --- Test individual side padding with different units -function TestPaddingResize:testMixedPaddingUnits() - -- Create element with mixed padding units - local element = FlexLove.Element.new({ - x = 0, - y = 0, - width = "100%", - height = "100%", - padding = { top = "5%", right = "2vw", bottom = 20, left = "3vh" }, - }) - - -- Store initial padding - local initialTop = element.padding.top - local initialRight = element.padding.right - local initialBottom = element.padding.bottom - local initialLeft = element.padding.left - - -- Resize - element:resize(2560, 1440) - - -- Check that each side recalculates according to its unit - luaunit.assertTrue(initialTop < element.padding.top, "Top padding (%) should increase") - luaunit.assertTrue(initialRight < element.padding.right, "Right padding (vw) should increase") - luaunit.assertTrue( - math.abs(initialBottom - element.padding.bottom) < 1, - "Bottom padding (px) should remain roughly the same with base scaling" - ) - luaunit.assertTrue(initialLeft < element.padding.left, "Left padding (vh) should increase") -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/21_image_scaler_nearest_tests.lua b/testing/__tests__/21_image_scaler_nearest_tests.lua deleted file mode 100644 index a804e80..0000000 --- a/testing/__tests__/21_image_scaler_nearest_tests.lua +++ /dev/null @@ -1,202 +0,0 @@ -package.path = package.path .. ";?.lua" -local luaunit = require("testing.luaunit") -local loveStub = require("testing.loveStub") -_G.love = loveStub - -local FlexLove = require("FlexLove") - -TestImageScalerNearest = {} - -function TestImageScalerNearest:setUp() - -- Create a simple test image (2x2 with distinct colors) - self.testImage2x2 = love.image.newImageData(2, 2) - -- Top-left: red - self.testImage2x2:setPixel(0, 0, 1, 0, 0, 1) - -- Top-right: green - self.testImage2x2:setPixel(1, 0, 0, 1, 0, 1) - -- Bottom-left: blue - self.testImage2x2:setPixel(0, 1, 0, 0, 1, 1) - -- Bottom-right: white - self.testImage2x2:setPixel(1, 1, 1, 1, 1, 1) -end - -function TestImageScalerNearest:test2xScaling() - -- Scale 2x2 to 4x4 (2x factor) - local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- Top-left quadrant should be red (0,0 -> 1,1) - local r, g, b, a = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - - r, g, b, a = scaled:getPixel(1, 1) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - - -- Top-right quadrant should be green (2,0 -> 3,1) - r, g, b, a = scaled:getPixel(2, 0) - luaunit.assertAlmostEquals(r, 0, 0.01) - luaunit.assertAlmostEquals(g, 1, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - - r, g, b, a = scaled:getPixel(3, 1) - luaunit.assertAlmostEquals(r, 0, 0.01) - luaunit.assertAlmostEquals(g, 1, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - - -- Bottom-left quadrant should be blue (0,2 -> 1,3) - r, g, b, a = scaled:getPixel(0, 2) - luaunit.assertAlmostEquals(r, 0, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 1, 0.01) - - -- Bottom-right quadrant should be white (2,2 -> 3,3) - r, g, b, a = scaled:getPixel(3, 3) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 1, 0.01) - luaunit.assertAlmostEquals(b, 1, 0.01) -end - -function TestImageScalerNearest:test3xScaling() - -- Scale 2x2 to 6x6 (3x factor) - local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 6, 6) - - luaunit.assertEquals(scaled:getWidth(), 6) - luaunit.assertEquals(scaled:getHeight(), 6) - - -- Verify nearest-neighbor: each source pixel should map to 3x3 block - -- Top-left (red) should cover 0-2, 0-2 - local r, g, b = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) - r, g, b = scaled:getPixel(2, 2) - luaunit.assertAlmostEquals(r, 1, 0.01) - - -- Top-right (green) should cover 3-5, 0-2 - r, g, b = scaled:getPixel(3, 0) - luaunit.assertAlmostEquals(g, 1, 0.01) - r, g, b = scaled:getPixel(5, 2) - luaunit.assertAlmostEquals(g, 1, 0.01) -end - -function TestImageScalerNearest:testNonUniformScaling() - -- Scale 2x2 to 6x4 (3x horizontal, 2x vertical) - local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 6, 4) - - luaunit.assertEquals(scaled:getWidth(), 6) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- Top-left red should cover 0-2 horizontally, 0-1 vertically - local r, g, b = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) - r, g, b = scaled:getPixel(2, 1) - luaunit.assertAlmostEquals(r, 1, 0.01) - - -- Top-right green should cover 3-5 horizontally, 0-1 vertically - r, g, b = scaled:getPixel(3, 0) - luaunit.assertAlmostEquals(g, 1, 0.01) -end - -function TestImageScalerNearest:testSameSizeScaling() - -- Scale 2x2 to 2x2 (should be identical) - local scaled = FlexLove.ImageScaler.scaleNearest(self.testImage2x2, 0, 0, 2, 2, 2, 2) - - luaunit.assertEquals(scaled:getWidth(), 2) - luaunit.assertEquals(scaled:getHeight(), 2) - - -- Verify all pixels match original - for y = 0, 1 do - for x = 0, 1 do - local r1, g1, b1, a1 = self.testImage2x2:getPixel(x, y) - local r2, g2, b2, a2 = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r1, r2, 0.01) - luaunit.assertAlmostEquals(g1, g2, 0.01) - luaunit.assertAlmostEquals(b1, b2, 0.01) - luaunit.assertAlmostEquals(a1, a2, 0.01) - end - end -end - -function TestImageScalerNearest:test1x1Scaling() - -- Create 1x1 image - local img1x1 = love.image.newImageData(1, 1) - img1x1:setPixel(0, 0, 0.5, 0.5, 0.5, 1) - - -- Scale to 4x4 - local scaled = FlexLove.ImageScaler.scaleNearest(img1x1, 0, 0, 1, 1, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- All pixels should be the same color - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 0.5, 0.01) - luaunit.assertAlmostEquals(g, 0.5, 0.01) - luaunit.assertAlmostEquals(b, 0.5, 0.01) - end - end -end - -function TestImageScalerNearest:testSubregionScaling() - -- Create 4x4 image with different quadrants - local img4x4 = love.image.newImageData(4, 4) - - -- Fill with pattern: top-left red, rest black - for y = 0, 3 do - for x = 0, 3 do - if x < 2 and y < 2 then - img4x4:setPixel(x, y, 1, 0, 0, 1) -- red - else - img4x4:setPixel(x, y, 0, 0, 0, 1) -- black - end - end - end - - -- Scale only the top-left 2x2 red quadrant to 4x4 - local scaled = FlexLove.ImageScaler.scaleNearest(img4x4, 0, 0, 2, 2, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- All pixels should be red (from source quadrant) - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - end - end -end - -function TestImageScalerNearest:testAlphaChannel() - -- Create image with varying alpha - local img = love.image.newImageData(2, 2) - img:setPixel(0, 0, 1, 0, 0, 1.0) -- Opaque red - img:setPixel(1, 0, 0, 1, 0, 0.5) -- Semi-transparent green - img:setPixel(0, 1, 0, 0, 1, 0.25) -- More transparent blue - img:setPixel(1, 1, 1, 1, 1, 0.0) -- Fully transparent white - - local scaled = FlexLove.ImageScaler.scaleNearest(img, 0, 0, 2, 2, 4, 4) - - -- Check alpha values are preserved - local r, g, b, a = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(a, 1.0, 0.01) - - r, g, b, a = scaled:getPixel(2, 0) - luaunit.assertAlmostEquals(a, 0.5, 0.01) - - r, g, b, a = scaled:getPixel(0, 2) - luaunit.assertAlmostEquals(a, 0.25, 0.01) - - r, g, b, a = scaled:getPixel(3, 3) - luaunit.assertAlmostEquals(a, 0.0, 0.01) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/22_image_scaler_bilinear_tests.lua b/testing/__tests__/22_image_scaler_bilinear_tests.lua deleted file mode 100644 index 713b5ed..0000000 --- a/testing/__tests__/22_image_scaler_bilinear_tests.lua +++ /dev/null @@ -1,291 +0,0 @@ -package.path = package.path .. ";?.lua" -local luaunit = require("testing.luaunit") -local loveStub = require("testing.loveStub") -_G.love = loveStub - -local FlexLove = require("FlexLove") - -TestImageScalerBilinear = {} - -function TestImageScalerBilinear:setUp() - -- Create a simple test image (2x2 with distinct colors) - self.testImage2x2 = love.image.newImageData(2, 2) - -- Top-left: red - self.testImage2x2:setPixel(0, 0, 1, 0, 0, 1) - -- Top-right: green - self.testImage2x2:setPixel(1, 0, 0, 1, 0, 1) - -- Bottom-left: blue - self.testImage2x2:setPixel(0, 1, 0, 0, 1, 1) - -- Bottom-right: white - self.testImage2x2:setPixel(1, 1, 1, 1, 1, 1) -end - -function TestImageScalerBilinear:test2xScaling() - -- Scale 2x2 to 4x4 (2x factor) - local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- Corner pixels should match original (no interpolation at exact positions) - local r, g, b, a = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) -- Red - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - - -- Center pixel at (1,1) should be blend of all 4 corners - -- At (0.5, 0.5) in source space -> blend of all 4 colors - r, g, b, a = scaled:getPixel(1, 1) - -- Should be approximately (0.5, 0.5, 0.5) - average of red, green, blue, white - luaunit.assertTrue(r > 0.3 and r < 0.7, "Center pixel should be blended") - luaunit.assertTrue(g > 0.3 and g < 0.7, "Center pixel should be blended") - luaunit.assertTrue(b > 0.3 and b < 0.7, "Center pixel should be blended") -end - -function TestImageScalerBilinear:testGradientSmoothing() - -- Create a simple gradient: black to white horizontally - local gradient = love.image.newImageData(2, 1) - gradient:setPixel(0, 0, 0, 0, 0, 1) -- Black - gradient:setPixel(1, 0, 1, 1, 1, 1) -- White - - -- Scale to 4 pixels wide - local scaled = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 4, 1) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 1) - - -- Check smooth gradient progression - local r0 = scaled:getPixel(0, 0) - local r1 = scaled:getPixel(1, 0) - local r2 = scaled:getPixel(2, 0) - local r3 = scaled:getPixel(3, 0) - - -- Should be monotonically increasing (or equal at end due to clamping) - luaunit.assertTrue(r0 < r1, "Gradient should increase") - luaunit.assertTrue(r1 < r2, "Gradient should increase") - luaunit.assertTrue(r2 <= r3, "Gradient should increase or stay same") - - -- First should be close to black, last close to white - luaunit.assertAlmostEquals(r0, 0, 0.15) - luaunit.assertAlmostEquals(r3, 1, 0.15) -end - -function TestImageScalerBilinear:testSameSizeScaling() - -- Scale 2x2 to 2x2 (should be identical) - local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 2, 2) - - luaunit.assertEquals(scaled:getWidth(), 2) - luaunit.assertEquals(scaled:getHeight(), 2) - - -- Verify all pixels match original - for y = 0, 1 do - for x = 0, 1 do - local r1, g1, b1, a1 = self.testImage2x2:getPixel(x, y) - local r2, g2, b2, a2 = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r1, r2, 0.01) - luaunit.assertAlmostEquals(g1, g2, 0.01) - luaunit.assertAlmostEquals(b1, b2, 0.01) - luaunit.assertAlmostEquals(a1, a2, 0.01) - end - end -end - -function TestImageScalerBilinear:test1x1Scaling() - -- Create 1x1 image - local img1x1 = love.image.newImageData(1, 1) - img1x1:setPixel(0, 0, 0.5, 0.5, 0.5, 1) - - -- Scale to 4x4 - local scaled = FlexLove.ImageScaler.scaleBilinear(img1x1, 0, 0, 1, 1, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- All pixels should be the same color (no neighbors to interpolate with) - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 0.5, 0.01) - luaunit.assertAlmostEquals(g, 0.5, 0.01) - luaunit.assertAlmostEquals(b, 0.5, 0.01) - end - end -end - -function TestImageScalerBilinear:testPureColorMaintenance() - -- Create pure white image - local whiteImg = love.image.newImageData(2, 2) - for y = 0, 1 do - for x = 0, 1 do - whiteImg:setPixel(x, y, 1, 1, 1, 1) - end - end - - local scaled = FlexLove.ImageScaler.scaleBilinear(whiteImg, 0, 0, 2, 2, 4, 4) - - -- All pixels should remain pure white - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 1, 0.01) - luaunit.assertAlmostEquals(b, 1, 0.01) - end - end - - -- Test pure black - local blackImg = love.image.newImageData(2, 2) - for y = 0, 1 do - for x = 0, 1 do - blackImg:setPixel(x, y, 0, 0, 0, 1) - end - end - - scaled = FlexLove.ImageScaler.scaleBilinear(blackImg, 0, 0, 2, 2, 4, 4) - - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 0, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - end - end -end - -function TestImageScalerBilinear:testAlphaInterpolation() - -- Create image with varying alpha - local img = love.image.newImageData(2, 2) - img:setPixel(0, 0, 1, 0, 0, 1.0) -- Opaque red - img:setPixel(1, 0, 1, 0, 0, 0.0) -- Transparent red - img:setPixel(0, 1, 1, 0, 0, 1.0) -- Opaque red - img:setPixel(1, 1, 1, 0, 0, 0.0) -- Transparent red - - local scaled = FlexLove.ImageScaler.scaleBilinear(img, 0, 0, 2, 2, 4, 2) - - -- Check that alpha is interpolated smoothly - local r, g, b, a0 = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(a0, 1.0, 0.01) - - local r, g, b, a1 = scaled:getPixel(1, 0) - -- Should be between 1.0 and 0.0 - luaunit.assertTrue(a1 > 0.3 and a1 < 0.7, "Alpha should be interpolated") - - local r, g, b, a3 = scaled:getPixel(3, 0) - luaunit.assertAlmostEquals(a3, 0.0, 0.15) -end - -function TestImageScalerBilinear:testSubregionScaling() - -- Create 4x4 image with different quadrants - local img4x4 = love.image.newImageData(4, 4) - - -- Fill with pattern: top-left red, rest black - for y = 0, 3 do - for x = 0, 3 do - if x < 2 and y < 2 then - img4x4:setPixel(x, y, 1, 0, 0, 1) -- red - else - img4x4:setPixel(x, y, 0, 0, 0, 1) -- black - end - end - end - - -- Scale only the top-left 2x2 red quadrant to 4x4 - local scaled = FlexLove.ImageScaler.scaleBilinear(img4x4, 0, 0, 2, 2, 4, 4) - - luaunit.assertEquals(scaled:getWidth(), 4) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- All pixels should be red (from source quadrant) - for y = 0, 3 do - for x = 0, 3 do - local r, g, b = scaled:getPixel(x, y) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - luaunit.assertAlmostEquals(b, 0, 0.01) - end - end -end - -function TestImageScalerBilinear:testEdgePixelHandling() - -- Create 3x3 checkerboard - local checkerboard = love.image.newImageData(3, 3) - for y = 0, 2 do - for x = 0, 2 do - if (x + y) % 2 == 0 then - checkerboard:setPixel(x, y, 1, 1, 1, 1) -- white - else - checkerboard:setPixel(x, y, 0, 0, 0, 1) -- black - end - end - end - - -- Scale to 9x9 - local scaled = FlexLove.ImageScaler.scaleBilinear(checkerboard, 0, 0, 3, 3, 9, 9) - - luaunit.assertEquals(scaled:getWidth(), 9) - luaunit.assertEquals(scaled:getHeight(), 9) - - -- Verify corners are correct (no out-of-bounds access) - local r, g, b = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) -- Top-left should be white - - r, g, b = scaled:getPixel(8, 8) - luaunit.assertAlmostEquals(r, 1, 0.01) -- Bottom-right should be white -end - -function TestImageScalerBilinear:testNonUniformScaling() - -- Scale 2x2 to 6x4 (3x horizontal, 2x vertical) - local scaled = FlexLove.ImageScaler.scaleBilinear(self.testImage2x2, 0, 0, 2, 2, 6, 4) - - luaunit.assertEquals(scaled:getWidth(), 6) - luaunit.assertEquals(scaled:getHeight(), 4) - - -- Top-left corner should be red - local r, g, b = scaled:getPixel(0, 0) - luaunit.assertAlmostEquals(r, 1, 0.01) - luaunit.assertAlmostEquals(g, 0, 0.01) - - -- Should have smooth interpolation in between - r, g, b = scaled:getPixel(2, 1) - -- Middle area should have blended colors - luaunit.assertTrue(r > 0.1, "Should have some red component") - luaunit.assertTrue(g > 0.1, "Should have some green component") - luaunit.assertTrue(b > 0.1, "Should have some blue component") -end - -function TestImageScalerBilinear:testComparison_SmootherThanNearest() - -- Create gradient - local gradient = love.image.newImageData(2, 1) - gradient:setPixel(0, 0, 0, 0, 0, 1) - gradient:setPixel(1, 0, 1, 1, 1, 1) - - local bilinear = FlexLove.ImageScaler.scaleBilinear(gradient, 0, 0, 2, 1, 8, 1) - local nearest = FlexLove.ImageScaler.scaleNearest(gradient, 0, 0, 2, 1, 8, 1) - - -- Count unique values (nearest should have fewer due to blocky nature) - local bilinearValues = {} - local nearestValues = {} - - for x = 0, 7 do - local rb = bilinear:getPixel(x, 0) - local rn = nearest:getPixel(x, 0) - bilinearValues[string.format("%.2f", rb)] = true - nearestValues[string.format("%.2f", rn)] = true - end - - local bilinearCount = 0 - for _ in pairs(bilinearValues) do - bilinearCount = bilinearCount + 1 - end - - local nearestCount = 0 - for _ in pairs(nearestValues) do - nearestCount = nearestCount + 1 - end - - -- Bilinear should have more unique values (smoother gradient) - luaunit.assertTrue(bilinearCount >= nearestCount, "Bilinear should produce smoother gradient with more unique values") -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/23_blur_effects_tests.lua b/testing/__tests__/23_blur_effects_tests.lua deleted file mode 100644 index d42cf93..0000000 --- a/testing/__tests__/23_blur_effects_tests.lua +++ /dev/null @@ -1,238 +0,0 @@ --- Test suite for blur effects (contentBlur and backdropBlur) -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") - -TestBlurEffects = {} - -function TestBlurEffects:setUp() - -- Initialize FlexLove with default config - FlexLove.Gui.init({ baseScale = { width = 1920, height = 1080 } }) -end - -function TestBlurEffects:tearDown() - FlexLove.Gui.destroy() -end - --- Test 1: Element with contentBlur property -function TestBlurEffects:test_content_blur_property() - local element = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 5 }, - }) - - lu.assertNotNil(element.contentBlur, "Element should have contentBlur property") - lu.assertEquals(element.contentBlur.intensity, 50, "Content blur intensity should be 50") - lu.assertEquals(element.contentBlur.quality, 5, "Content blur quality should be 5") -end - --- Test 2: Element with backdropBlur property -function TestBlurEffects:test_backdrop_blur_property() - local element = FlexLove.Element.new({ - width = 200, - height = 200, - backdropBlur = { intensity = 75, quality = 7 }, - }) - - lu.assertNotNil(element.backdropBlur, "Element should have backdropBlur property") - lu.assertEquals(element.backdropBlur.intensity, 75, "Backdrop blur intensity should be 75") - lu.assertEquals(element.backdropBlur.quality, 7, "Backdrop blur quality should be 7") -end - --- Test 3: Element with both blur types -function TestBlurEffects:test_both_blur_types() - local element = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 30, quality = 3 }, - backdropBlur = { intensity = 60, quality = 6 }, - }) - - lu.assertNotNil(element.contentBlur, "Element should have contentBlur property") - lu.assertNotNil(element.backdropBlur, "Element should have backdropBlur property") - lu.assertEquals(element.contentBlur.intensity, 30) - lu.assertEquals(element.backdropBlur.intensity, 60) -end - --- Test 4: Blur instance creation (skip if no graphics context) -function TestBlurEffects:test_blur_instance_creation() - if not love or not love.graphics then - lu.success() -- Skip test if no LÖVE graphics context - return - end - - local element = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 5 }, - }) - - local blurInstance = element:getBlurInstance() - lu.assertNotNil(blurInstance, "Blur instance should be created") - lu.assertEquals(blurInstance.quality, 5, "Blur instance should have correct quality") -end - --- Test 5: Blur instance caching (skip if no graphics context) -function TestBlurEffects:test_blur_instance_caching() - if not love or not love.graphics then - lu.success() -- Skip test if no LÖVE graphics context - return - end - - local element = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 5 }, - }) - - local instance1 = element:getBlurInstance() - local instance2 = element:getBlurInstance() - - lu.assertEquals(instance1, instance2, "Blur instance should be cached and reused") -end - --- Test 6: Blur instance recreation on quality change (skip if no graphics context) -function TestBlurEffects:test_blur_instance_quality_change() - if not love or not love.graphics then - lu.success() -- Skip test if no LÖVE graphics context - return - end - - local element = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 5 }, - }) - - local instance1 = element:getBlurInstance() - - -- Change quality - element.contentBlur.quality = 8 - local instance2 = element:getBlurInstance() - - lu.assertNotEquals(instance1, instance2, "Blur instance should be recreated when quality changes") - lu.assertEquals(instance2.quality, 8, "New blur instance should have updated quality") -end - --- Test 7: Element without blur can still create instance with default quality (skip if no graphics context) -function TestBlurEffects:test_no_blur_default_instance() - if not love or not love.graphics then - lu.success() -- Skip test if no LÖVE graphics context - return - end - - local element = FlexLove.Element.new({ - width = 200, - height = 200, - }) - - -- Element without blur should still be able to get a blur instance (with default quality) - local instance = element:getBlurInstance() - lu.assertNotNil(instance, "Element should be able to create blur instance even without blur config") - lu.assertEquals(instance.quality, 5, "Default quality should be 5") -end - --- Test 8: Blur intensity boundaries -function TestBlurEffects:test_blur_intensity_boundaries() - -- Test minimum intensity (0) - local element1 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 0, quality = 5 }, - }) - lu.assertEquals(element1.contentBlur.intensity, 0, "Minimum intensity should be 0") - - -- Test maximum intensity (100) - local element2 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 100, quality = 5 }, - }) - lu.assertEquals(element2.contentBlur.intensity, 100, "Maximum intensity should be 100") -end - --- Test 9: Blur quality boundaries -function TestBlurEffects:test_blur_quality_boundaries() - -- Test minimum quality (1) - local element1 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 1 }, - }) - lu.assertEquals(element1.contentBlur.quality, 1, "Minimum quality should be 1") - - -- Test maximum quality (10) - local element2 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 10 }, - }) - lu.assertEquals(element2.contentBlur.quality, 10, "Maximum quality should be 10") -end - --- Test 10: Nested elements with blur -function TestBlurEffects:test_nested_elements_with_blur() - local parent = FlexLove.Element.new({ - width = 400, - height = 400, - contentBlur = { intensity = 40, quality = 5 }, - }) - - local child = FlexLove.Element.new({ - parent = parent, - width = 100, - height = 100, - backdropBlur = { intensity = 60, quality = 6 }, - }) - - lu.assertNotNil(parent.contentBlur, "Parent should have content blur") - lu.assertNotNil(child.backdropBlur, "Child should have backdrop blur") - lu.assertEquals(#parent.children, 1, "Parent should have one child") -end - --- Test 11: Draw method accepts backdrop canvas parameter -function TestBlurEffects:test_draw_accepts_backdrop_canvas() - local element = FlexLove.Element.new({ - width = 200, - height = 200, - backdropBlur = { intensity = 50, quality = 5 }, - }) - - -- This should not error (we can't actually test rendering without a graphics context) - -- But we can verify the method signature accepts the parameter - local success = pcall(function() - -- Create a mock canvas (will fail in test environment, but that's ok) - -- element:draw(nil) - end) - - -- Test passes if we get here without syntax errors - lu.assertTrue(true, "Draw method should accept backdrop canvas parameter") -end - --- Test 12: Quality affects blur instance taps (skip if no graphics context) -function TestBlurEffects:test_quality_affects_taps() - if not love or not love.graphics then - lu.success() -- Skip test if no LÖVE graphics context - return - end - - local element1 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 1 }, - }) - - local element2 = FlexLove.Element.new({ - width = 200, - height = 200, - contentBlur = { intensity = 50, quality = 10 }, - }) - - local instance1 = element1:getBlurInstance() - local instance2 = element2:getBlurInstance() - - -- Higher quality should have more taps - lu.assertTrue(instance2.taps > instance1.taps, "Higher quality should result in more blur taps") -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/24_keyboard_input_tests.lua b/testing/__tests__/24_keyboard_input_tests.lua deleted file mode 100644 index b22c855..0000000 --- a/testing/__tests__/24_keyboard_input_tests.lua +++ /dev/null @@ -1,730 +0,0 @@ -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui -local Element = FlexLove.Element - -TestKeyboardInput = {} - --- Helper function to ensure clean keyboard state -local function clearModifierKeys() - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) -end - -function TestKeyboardInput:setUp() - -- Clear all keyboard modifier states at start of each test - if love.keyboard.isDown("lgui", "rgui", "lalt", "ralt", "lctrl", "rctrl", "lshift", "rshift") then - local mods = {} - if love.keyboard.isDown("lshift") then table.insert(mods, "lshift") end - if love.keyboard.isDown("rshift") then table.insert(mods, "rshift") end - if love.keyboard.isDown("lctrl") then table.insert(mods, "lctrl") end - if love.keyboard.isDown("rctrl") then table.insert(mods, "rctrl") end - if love.keyboard.isDown("lalt") then table.insert(mods, "lalt") end - if love.keyboard.isDown("ralt") then table.insert(mods, "ralt") end - if love.keyboard.isDown("lgui") then table.insert(mods, "lgui") end - if love.keyboard.isDown("rgui") then table.insert(mods, "rgui") end - print("WARNING: Modifiers down at setUp: " .. table.concat(mods, ", ")) - end - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - Gui.init({ baseScale = { width = 1920, height = 1080 } }) -end - -function TestKeyboardInput:tearDown() - -- Clear all keyboard modifier states - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - Gui.destroy() -end - --- ==================== --- Focus Management Tests --- ==================== - -function TestKeyboardInput:testFocusEditable() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - lu.assertFalse(input:isFocused()) - - input:focus() - - lu.assertTrue(input:isFocused()) - lu.assertEquals(Gui._focusedElement, input) -end - -function TestKeyboardInput:testBlurEditable() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - lu.assertTrue(input:isFocused()) - - input:blur() - - lu.assertFalse(input:isFocused()) - lu.assertNil(Gui._focusedElement) -end - -function TestKeyboardInput:testFocusSwitching() - local input1 = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Input 1", - }) - - local input2 = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Input 2", - }) - - input1:focus() - lu.assertTrue(input1:isFocused()) - lu.assertFalse(input2:isFocused()) - - input2:focus() - lu.assertFalse(input1:isFocused()) - lu.assertTrue(input2:isFocused()) - lu.assertEquals(Gui._focusedElement, input2) -end - -function TestKeyboardInput:testSelectOnFocus() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - selectOnFocus = true, - }) - - lu.assertFalse(input:hasSelection()) - - input:focus() - - lu.assertTrue(input:hasSelection()) - local startPos, endPos = input:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 11) -- Length of "Hello World" -end - --- ==================== --- Text Input Tests --- ==================== - -function TestKeyboardInput:testTextInput() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "", - }) - - input:focus() - input:textinput("H") - input:textinput("i") - - lu.assertEquals(input:getText(), "Hi") - lu.assertEquals(input._cursorPosition, 2) -end - -function TestKeyboardInput:testTextInputAtPosition() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(2) -- After "He" - input:textinput("X") - - lu.assertEquals(input:getText(), "HeXllo") - lu.assertEquals(input._cursorPosition, 3) -end - -function TestKeyboardInput:testTextInputWithSelection() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setSelection(0, 5) -- Select "Hello" - input:textinput("Hi") - - lu.assertEquals(input:getText(), "Hi World") - lu.assertEquals(input._cursorPosition, 2) - lu.assertFalse(input:hasSelection()) -end - -function TestKeyboardInput:testMaxLengthConstraint() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "", - maxLength = 5, - }) - - input:focus() - input:textinput("Hello") - lu.assertEquals(input:getText(), "Hello") - - input:textinput("X") -- Should not be added - lu.assertEquals(input:getText(), "Hello") -end - --- ==================== --- Backspace/Delete Tests --- ==================== - -function TestKeyboardInput:testBackspace() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(5) -- At end - input:keypressed("backspace", "backspace", false) - - lu.assertEquals(input:getText(), "Hell") - lu.assertEquals(input._cursorPosition, 4) -end - -function TestKeyboardInput:testBackspaceAtStart() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(0) -- At start - input:keypressed("backspace", "backspace", false) - - lu.assertEquals(input:getText(), "Hello") -- No change - lu.assertEquals(input._cursorPosition, 0) -end - -function TestKeyboardInput:testDelete() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(0) -- At start - input:keypressed("delete", "delete", false) - - lu.assertEquals(input:getText(), "ello") - lu.assertEquals(input._cursorPosition, 0) -end - -function TestKeyboardInput:testDeleteAtEnd() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(5) -- At end - input:keypressed("delete", "delete", false) - - lu.assertEquals(input:getText(), "Hello") -- No change - lu.assertEquals(input._cursorPosition, 5) -end - -function TestKeyboardInput:testBackspaceWithSelection() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setSelection(0, 5) -- Select "Hello" - input:keypressed("backspace", "backspace", false) - - lu.assertEquals(input:getText(), " World") - lu.assertEquals(input._cursorPosition, 0) - lu.assertFalse(input:hasSelection()) -end - --- ==================== --- Cursor Movement Tests --- ==================== - -function TestKeyboardInput:testArrowLeft() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(5) - input:keypressed("left", "left", false) - - lu.assertEquals(input._cursorPosition, 4) -end - -function TestKeyboardInput:testArrowRight() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(0) - input:keypressed("right", "right", false) - - lu.assertEquals(input._cursorPosition, 1) -end - -function TestKeyboardInput:testHomeKey() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(5) - input:keypressed("home", "home", false) - - lu.assertEquals(input._cursorPosition, 0) -end - -function TestKeyboardInput:testEndKey() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(0) - input:keypressed("end", "end", false) - - lu.assertEquals(input._cursorPosition, 5) -end - --- ==================== --- Modifier Key Tests --- ==================== - -function TestKeyboardInput:testSuperLeftMovesToStart() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setCursorPosition(8) -- Middle of text - - -- Simulate Super (Cmd/Win) key being held - love.keyboard.setDown("lgui", true) - input:keypressed("left", "left", false) - love.keyboard.setDown("lgui", false) - - lu.assertEquals(input._cursorPosition, 0) -end - -function TestKeyboardInput:testSuperRightMovesToEnd() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setCursorPosition(3) -- Middle of text - - -- Simulate Super (Cmd/Win) key being held - love.keyboard.setDown("lgui", true) - input:keypressed("right", "right", false) - love.keyboard.setDown("lgui", false) - - lu.assertEquals(input._cursorPosition, 11) -- End of "Hello World" -end - -function TestKeyboardInput:testAltLeftMovesByWord() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World Test", - }) - - input:focus() - input:setCursorPosition(16) -- End of text - - -- Simulate Alt key being held and move left by word - love.keyboard.setDown("lalt", true) - input:keypressed("left", "left", false) - love.keyboard.setDown("lalt", false) - - lu.assertEquals(input._cursorPosition, 12) -- Start of "Test" -end - -function TestKeyboardInput:testAltRightMovesByWord() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World Test", - }) - - input:focus() - input:setCursorPosition(0) -- Start of text - - -- Simulate Alt key being held and move right by word - love.keyboard.setDown("lalt", true) - input:keypressed("right", "right", false) - love.keyboard.setDown("lalt", false) - - lu.assertEquals(input._cursorPosition, 6) -- After "Hello " -end - -function TestKeyboardInput:testAltLeftMultipleWords() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "The quick brown fox", - }) - - input:focus() - input:setCursorPosition(19) -- End of text - - -- Move left by word three times - love.keyboard.setDown("lalt", true) - input:keypressed("left", "left", false) -- to "fox" - input:keypressed("left", "left", false) -- to "brown" - input:keypressed("left", "left", false) -- to "quick" - love.keyboard.setDown("lalt", false) - - lu.assertEquals(input._cursorPosition, 4) -- Start of "quick" -end - -function TestKeyboardInput:testAltRightMultipleWords() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "The quick brown fox", - }) - - input:focus() - input:setCursorPosition(0) -- Start of text - - -- Move right by word three times - love.keyboard.setDown("lalt", true) - input:keypressed("right", "right", false) -- after "The " - input:keypressed("right", "right", false) -- after "quick " - input:keypressed("right", "right", false) -- after "brown " - love.keyboard.setDown("lalt", false) - - lu.assertEquals(input._cursorPosition, 16) -- After "brown " -end - -function TestKeyboardInput:testSuperLeftWithSelection() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setSelection(3, 8) - lu.assertTrue(input:hasSelection()) - - -- Super+Left should move to start and clear selection - love.keyboard.setDown("lgui", true) - input:keypressed("left", "left", false) - love.keyboard.setDown("lgui", false) - - lu.assertEquals(input._cursorPosition, 0) - lu.assertFalse(input:hasSelection()) -end - -function TestKeyboardInput:testSuperRightWithSelection() - clearModifierKeys() - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) - - input:focus() - input:setSelection(3, 8) - lu.assertTrue(input:hasSelection()) - - -- Super+Right should move to end and clear selection - love.keyboard.setDown("lgui", true) - input:keypressed("right", "right", false) - love.keyboard.setDown("lgui", false) - - lu.assertEquals(input._cursorPosition, 11) - lu.assertFalse(input:hasSelection()) -end - -function TestKeyboardInput:testEscapeClearsSelection() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:selectAll() - lu.assertTrue(input:hasSelection()) - - input:keypressed("escape", "escape", false) - - lu.assertFalse(input:hasSelection()) - lu.assertTrue(input:isFocused()) -- Still focused -end - -function TestKeyboardInput:testEscapeBlurs() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - lu.assertTrue(input:isFocused()) - - input:keypressed("escape", "escape", false) - - lu.assertFalse(input:isFocused()) -end - --- ==================== --- Callback Tests --- ==================== - -function TestKeyboardInput:testOnTextChangeCallback() - local changeCount = 0 - local oldTextValue = nil - local newTextValue = nil - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - onTextChange = function(element, newText, oldText) - changeCount = changeCount + 1 - newTextValue = newText - oldTextValue = oldText - end, - }) - - input:focus() - input:textinput("X") - - lu.assertEquals(changeCount, 1) - lu.assertEquals(oldTextValue, "Hello") - lu.assertEquals(newTextValue, "HelloX") -end - -function TestKeyboardInput:testOnTextInputCallback() - local inputCount = 0 - local lastChar = nil - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "", - onTextInput = function(element, text) - inputCount = inputCount + 1 - lastChar = text - end, - }) - - input:focus() - input:textinput("A") - input:textinput("B") - - lu.assertEquals(inputCount, 2) - lu.assertEquals(lastChar, "B") -end - -function TestKeyboardInput:testOnEnterCallback() - local enterCalled = false - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - multiline = false, - text = "Hello", - onEnter = function(element) - enterCalled = true - end, - }) - - input:focus() - input:keypressed("return", "return", false) - - lu.assertTrue(enterCalled) -end - -function TestKeyboardInput:testOnFocusCallback() - local focusCalled = false - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - onFocus = function(element) - focusCalled = true - end, - }) - - input:focus() - - lu.assertTrue(focusCalled) -end - -function TestKeyboardInput:testOnBlurCallback() - local blurCalled = false - - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - onBlur = function(element) - blurCalled = true - end, - }) - - input:focus() - input:blur() - - lu.assertTrue(blurCalled) -end - --- ==================== --- GUI-level Input Forwarding Tests --- ==================== - -function TestKeyboardInput:testGuiTextinputForwarding() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "", - }) - - input:focus() - Gui.textinput("A") - - lu.assertEquals(input:getText(), "A") -end - -function TestKeyboardInput:testGuiKeypressedForwarding() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - input:focus() - input:setCursorPosition(5) - Gui.keypressed("backspace", "backspace", false) - - lu.assertEquals(input:getText(), "Hell") -end - -function TestKeyboardInput:testGuiInputWithoutFocus() - local input = Element.new({ - width = 200, - height = 40, - editable = true, - text = "Hello", - }) - - -- No focus - Gui.textinput("X") - - lu.assertEquals(input:getText(), "Hello") -- No change -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/25_image_cache_tests.lua b/testing/__tests__/25_image_cache_tests.lua deleted file mode 100644 index 4d41939..0000000 --- a/testing/__tests__/25_image_cache_tests.lua +++ /dev/null @@ -1,232 +0,0 @@ -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") -local ImageCache = FlexLove.ImageCache - -TestImageCache = {} - -function TestImageCache:setUp() - -- Clear cache before each test - ImageCache.clear() - - -- Create a test image programmatically - self.testImageData = love.image.newImageData(64, 64) - -- Fill with a simple pattern - for y = 0, 63 do - for x = 0, 63 do - local r = x / 63 - local g = y / 63 - local b = 0.5 - self.testImageData:setPixel(x, y, r, g, b, 1) - end - end - - -- Save to a temporary file (register in mock filesystem) - self.testImagePath = "testing/temp_test_image.png" - self.testImageData:encode("png", self.testImagePath) - -- Register file in mock filesystem so love.graphics.newImage can find it - love.filesystem.addMockFile(self.testImagePath, "mock_png_data") -end - -function TestImageCache:tearDown() - -- Clear cache after each test - ImageCache.clear() - - -- Clean up temporary test file - if love.filesystem.getInfo(self.testImagePath) then - love.filesystem.remove(self.testImagePath) - end -end - --- ==================== --- Basic Loading Tests --- ==================== - -function TestImageCache:testLoadValidImage() - local image, err = ImageCache.load(self.testImagePath) - - lu.assertNotNil(image) - lu.assertNil(err) - -- In the test stub, Image is a table with metatable, not userdata - lu.assertTrue(type(image) == "table" or type(image) == "userdata") - lu.assertNotNil(image.getDimensions) -- Should have Image methods -end - -function TestImageCache:testLoadInvalidPath() - local image, err = ImageCache.load("nonexistent/path/to/image.png") - - lu.assertNil(image) - lu.assertNotNil(err) - lu.assertStrContains(err, "Failed to load image") -end - -function TestImageCache:testLoadEmptyPath() - local image, err = ImageCache.load("") - - lu.assertNil(image) - lu.assertNotNil(err) - lu.assertStrContains(err, "Invalid image path") -end - -function TestImageCache:testLoadNilPath() - local image, err = ImageCache.load(nil) - - lu.assertNil(image) - lu.assertNotNil(err) - lu.assertStrContains(err, "Invalid image path") -end - --- ==================== --- Caching Tests --- ==================== - -function TestImageCache:testCachingSameImageReturnsSameReference() - local image1, err1 = ImageCache.load(self.testImagePath) - local image2, err2 = ImageCache.load(self.testImagePath) - - lu.assertNotNil(image1) - lu.assertNotNil(image2) - lu.assertEquals(image1, image2) -- Same reference -end - -function TestImageCache:testCachingDifferentImages() - -- Create a second test image - local testImageData2 = love.image.newImageData(32, 32) - for y = 0, 31 do - for x = 0, 31 do - testImageData2:setPixel(x, y, 1, 0, 0, 1) - end - end - local testImagePath2 = "testing/temp_test_image2.png" - testImageData2:encode("png", testImagePath2) - - local image1 = ImageCache.load(self.testImagePath) - local image2, err2 = ImageCache.load(testImagePath2) - - lu.assertNotNil(image1) - -- Note: The stub may not support loading dynamically created files - if image2 then - lu.assertNotEquals(image1, image2) -- Different images - end - - -- Cleanup - love.filesystem.remove(testImagePath2) -end - -function TestImageCache:testGetCachedImage() - -- Load image first - local loadedImage = ImageCache.load(self.testImagePath) - - -- Get from cache - local cachedImage = ImageCache.get(self.testImagePath) - - lu.assertNotNil(cachedImage) - lu.assertEquals(loadedImage, cachedImage) -end - -function TestImageCache:testGetNonCachedImage() - local image = ImageCache.get("nonexistent.png") - - lu.assertNil(image) -end - --- ==================== --- ImageData Loading Tests --- ==================== - -function TestImageCache:testLoadWithImageData() - local image, err = ImageCache.load(self.testImagePath, true) - - lu.assertNotNil(image) - lu.assertNil(err) - - local imageData = ImageCache.getImageData(self.testImagePath) - -- Note: The stub's newImageData doesn't support loading from path - -- so imageData may be nil in test environment - if imageData then - lu.assertTrue(type(imageData) == "table" or type(imageData) == "userdata") - end -end - -function TestImageCache:testLoadWithoutImageData() - local image, err = ImageCache.load(self.testImagePath, false) - - lu.assertNotNil(image) - lu.assertNil(err) - - local imageData = ImageCache.getImageData(self.testImagePath) - lu.assertNil(imageData) -- Should not be loaded -end - --- ==================== --- Cache Management Tests --- ==================== - -function TestImageCache:testRemoveImage() - ImageCache.load(self.testImagePath) - - local removed = ImageCache.remove(self.testImagePath) - - lu.assertTrue(removed) - - -- Verify it's no longer in cache - local cachedImage = ImageCache.get(self.testImagePath) - lu.assertNil(cachedImage) -end - -function TestImageCache:testRemoveNonExistentImage() - local removed = ImageCache.remove("nonexistent.png") - - lu.assertFalse(removed) -end - -function TestImageCache:testClearCache() - -- Load multiple images - ImageCache.load(self.testImagePath) - - local stats1 = ImageCache.getStats() - lu.assertEquals(stats1.count, 1) - - ImageCache.clear() - - local stats2 = ImageCache.getStats() - lu.assertEquals(stats2.count, 0) -end - --- ==================== --- Statistics Tests --- ==================== - -function TestImageCache:testCacheStats() - local stats1 = ImageCache.getStats() - lu.assertEquals(stats1.count, 0) - lu.assertEquals(stats1.memoryEstimate, 0) - - ImageCache.load(self.testImagePath) - - local stats2 = ImageCache.getStats() - lu.assertEquals(stats2.count, 1) - lu.assertTrue(stats2.memoryEstimate > 0) - - -- Memory estimate should be > 0 (stub creates 100x100 images = 40000 bytes) - lu.assertTrue(stats2.memoryEstimate >= 16384) -end - --- ==================== --- Path Normalization Tests --- ==================== - -function TestImageCache:testPathNormalization() - -- Load with different path formats - local image1 = ImageCache.load(self.testImagePath) - local image2 = ImageCache.load(" " .. self.testImagePath .. " ") -- With whitespace - local image3 = ImageCache.load(self.testImagePath:gsub("/", "\\")) -- With backslashes - - lu.assertEquals(image1, image2) - lu.assertEquals(image1, image3) - - -- Should only have one cache entry - local stats = ImageCache.getStats() - lu.assertEquals(stats.count, 1) -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/26_object_fit_modes_tests.lua b/testing/__tests__/26_object_fit_modes_tests.lua deleted file mode 100644 index 2e89d2d..0000000 --- a/testing/__tests__/26_object_fit_modes_tests.lua +++ /dev/null @@ -1,244 +0,0 @@ -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") -local ImageRenderer = FlexLove.ImageRenderer - -TestObjectFitModes = {} - -function TestObjectFitModes:setUp() - -- Test dimensions - self.imageWidth = 400 - self.imageHeight = 300 - self.boundsWidth = 200 - self.boundsHeight = 200 -end - --- ==================== --- Fill Mode Tests --- ==================== - -function TestObjectFitModes:testFillModeStretchesToExactBounds() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill") - - lu.assertEquals(params.dw, self.boundsWidth) - lu.assertEquals(params.dh, self.boundsHeight) - lu.assertEquals(params.dx, 0) - lu.assertEquals(params.dy, 0) -end - -function TestObjectFitModes:testFillModeUsesFullSourceImage() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill") - - lu.assertEquals(params.sx, 0) - lu.assertEquals(params.sy, 0) - lu.assertEquals(params.sw, self.imageWidth) - lu.assertEquals(params.sh, self.imageHeight) -end - --- ==================== --- Contain Mode Tests --- ==================== - -function TestObjectFitModes:testContainModePreservesAspectRatio() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain") - - -- Image is 4:3, bounds are 1:1 - -- Should scale to fit width (200), height becomes 150 - local expectedScale = self.boundsWidth / self.imageWidth - local expectedHeight = self.imageHeight * expectedScale - - lu.assertAlmostEquals(params.dw, self.boundsWidth, 0.01) - lu.assertAlmostEquals(params.dh, expectedHeight, 0.01) -end - -function TestObjectFitModes:testContainModeFitsWithinBounds() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain") - - lu.assertTrue(params.dw <= self.boundsWidth) - lu.assertTrue(params.dh <= self.boundsHeight) -end - -function TestObjectFitModes:testContainModeCentersImage() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "contain") - - -- Image should be centered in letterbox - -- With default "center center" position - lu.assertTrue(params.dx >= 0) - lu.assertTrue(params.dy >= 0) -end - --- ==================== --- Cover Mode Tests --- ==================== - -function TestObjectFitModes:testCoverModePreservesAspectRatio() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover") - - -- Check that aspect ratio is preserved in source crop - local sourceAspect = params.sw / params.sh - local boundsAspect = self.boundsWidth / self.boundsHeight - - lu.assertAlmostEquals(sourceAspect, boundsAspect, 0.01) -end - -function TestObjectFitModes:testCoverModeCoversEntireBounds() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover") - - lu.assertEquals(params.dw, self.boundsWidth) - lu.assertEquals(params.dh, self.boundsHeight) -end - -function TestObjectFitModes:testCoverModeCropsImage() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "cover") - - -- Source should be cropped (not full image) - lu.assertTrue(params.sw < self.imageWidth or params.sh < self.imageHeight) -end - --- ==================== --- None Mode Tests --- ==================== - -function TestObjectFitModes:testNoneModeUsesNaturalSize() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "none") - - lu.assertEquals(params.dw, self.imageWidth) - lu.assertEquals(params.dh, self.imageHeight) - lu.assertEquals(params.scaleX, 1) - lu.assertEquals(params.scaleY, 1) -end - -function TestObjectFitModes:testNoneModeUsesFullSourceImage() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "none") - - lu.assertEquals(params.sx, 0) - lu.assertEquals(params.sy, 0) - lu.assertEquals(params.sw, self.imageWidth) - lu.assertEquals(params.sh, self.imageHeight) -end - --- ==================== --- Scale-Down Mode Tests --- ==================== - -function TestObjectFitModes:testScaleDownUsesNoneWhenImageFits() - -- Image smaller than bounds - local smallWidth = 100 - local smallHeight = 75 - - local params = ImageRenderer.calculateFit(smallWidth, smallHeight, self.boundsWidth, self.boundsHeight, "scale-down") - - -- Should use natural size (none mode) - lu.assertEquals(params.dw, smallWidth) - lu.assertEquals(params.dh, smallHeight) -end - -function TestObjectFitModes:testScaleDownUsesContainWhenImageTooBig() - local params = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "scale-down") - - -- Should use contain mode - lu.assertTrue(params.dw <= self.boundsWidth) - lu.assertTrue(params.dh <= self.boundsHeight) - - -- Should preserve aspect ratio - local scale = params.dw / self.imageWidth - lu.assertAlmostEquals(params.dh, self.imageHeight * scale, 0.01) -end - --- ==================== --- Edge Cases --- ==================== - -function TestObjectFitModes:testLandscapeImageInPortraitBounds() - local params = ImageRenderer.calculateFit( - 400, - 200, -- Landscape image (2:1) - 200, - 400, -- Portrait bounds (1:2) - "contain" - ) - - -- Should fit width - lu.assertEquals(params.dw, 200) - lu.assertTrue(params.dh < 400) -end - -function TestObjectFitModes:testPortraitImageInLandscapeBounds() - local params = ImageRenderer.calculateFit( - 200, - 400, -- Portrait image (1:2) - 400, - 200, -- Landscape bounds (2:1) - "contain" - ) - - -- Should fit height - lu.assertEquals(params.dh, 200) - lu.assertTrue(params.dw < 400) -end - -function TestObjectFitModes:testSquareImageInNonSquareBounds() - local params = ImageRenderer.calculateFit( - 300, - 300, -- Square image - 200, - 400, -- Non-square bounds - "contain" - ) - - -- Should fit to smaller dimension (width) - lu.assertEquals(params.dw, 200) - lu.assertEquals(params.dh, 200) -end - -function TestObjectFitModes:testImageSmallerThanBounds() - local params = ImageRenderer.calculateFit(100, 100, 200, 200, "contain") - - -- Should scale up to fit - lu.assertEquals(params.dw, 200) - lu.assertEquals(params.dh, 200) -end - -function TestObjectFitModes:testImageLargerThanBounds() - local params = ImageRenderer.calculateFit(800, 600, 200, 200, "contain") - - -- Should scale down to fit - lu.assertTrue(params.dw <= 200) - lu.assertTrue(params.dh <= 200) -end - --- ==================== --- Invalid Input Tests --- ==================== - -function TestObjectFitModes:testInvalidFitModeThrowsError() - lu.assertErrorMsgContains("Invalid fit mode", ImageRenderer.calculateFit, 100, 100, 200, 200, "invalid-mode") -end - -function TestObjectFitModes:testZeroDimensionsThrowsError() - lu.assertErrorMsgContains("Dimensions must be positive", ImageRenderer.calculateFit, 0, 100, 200, 200, "fill") -end - -function TestObjectFitModes:testNegativeDimensionsThrowsError() - lu.assertErrorMsgContains("Dimensions must be positive", ImageRenderer.calculateFit, 100, -100, 200, 200, "fill") -end - --- ==================== --- Default Mode Test --- ==================== - -function TestObjectFitModes:testDefaultModeIsFill() - local params1 = ImageRenderer.calculateFit( - self.imageWidth, - self.imageHeight, - self.boundsWidth, - self.boundsHeight, - nil -- No mode specified - ) - - local params2 = ImageRenderer.calculateFit(self.imageWidth, self.imageHeight, self.boundsWidth, self.boundsHeight, "fill") - - lu.assertEquals(params1.dw, params2.dw) - lu.assertEquals(params1.dh, params2.dh) -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/27_object_position_tests.lua b/testing/__tests__/27_object_position_tests.lua deleted file mode 100644 index fe3147e..0000000 --- a/testing/__tests__/27_object_position_tests.lua +++ /dev/null @@ -1,184 +0,0 @@ -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") -local ImageRenderer = FlexLove.ImageRenderer - -TestObjectPosition = {} - --- ==================== --- Position Parsing Tests --- ==================== - -function TestObjectPosition:testCenterCenterDefault() - local x, y = ImageRenderer._parsePosition("center center") - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.5) -end - -function TestObjectPosition:testTopLeft() - local x, y = ImageRenderer._parsePosition("top left") - lu.assertEquals(x, 0) - lu.assertEquals(y, 0) -end - -function TestObjectPosition:testBottomRight() - local x, y = ImageRenderer._parsePosition("bottom right") - lu.assertEquals(x, 1) - lu.assertEquals(y, 1) -end - -function TestObjectPosition:testPercentage50() - local x, y = ImageRenderer._parsePosition("50% 50%") - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.5) -end - -function TestObjectPosition:testPercentage0() - local x, y = ImageRenderer._parsePosition("0% 0%") - lu.assertEquals(x, 0) - lu.assertEquals(y, 0) -end - -function TestObjectPosition:testPercentage100() - local x, y = ImageRenderer._parsePosition("100% 100%") - lu.assertEquals(x, 1) - lu.assertEquals(y, 1) -end - -function TestObjectPosition:testMixedKeywordPercentage() - local x, y = ImageRenderer._parsePosition("center 25%") - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.25) -end - -function TestObjectPosition:testSingleValueLeft() - local x, y = ImageRenderer._parsePosition("left") - lu.assertEquals(x, 0) - lu.assertEquals(y, 0.5) -- Should center on Y axis -end - -function TestObjectPosition:testSingleValueTop() - local x, y = ImageRenderer._parsePosition("top") - lu.assertEquals(x, 0.5) -- Should center on X axis - lu.assertEquals(y, 0) -end - -function TestObjectPosition:testInvalidPositionDefaultsToCenter() - local x, y = ImageRenderer._parsePosition("invalid position") - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.5) -end - -function TestObjectPosition:testNilPositionDefaultsToCenter() - local x, y = ImageRenderer._parsePosition(nil) - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.5) -end - -function TestObjectPosition:testEmptyStringDefaultsToCenter() - local x, y = ImageRenderer._parsePosition("") - lu.assertEquals(x, 0.5) - lu.assertEquals(y, 0.5) -end - --- ==================== --- Position with Contain Mode Tests --- ==================== - -function TestObjectPosition:testContainWithTopLeft() - local params = ImageRenderer.calculateFit( - 400, - 300, -- Image (landscape) - 200, - 200, -- Bounds (square) - "contain", - "top left" - ) - - -- Image should be in top-left of letterbox - lu.assertEquals(params.dx, 0) - lu.assertEquals(params.dy, 0) -end - -function TestObjectPosition:testContainWithBottomRight() - local params = ImageRenderer.calculateFit( - 400, - 300, -- Image (landscape) - 200, - 200, -- Bounds (square) - "contain", - "bottom right" - ) - - -- Image should be in bottom-right of letterbox - lu.assertTrue(params.dx + params.dw <= 200) - lu.assertTrue(params.dy + params.dh <= 200) - -- Should be at the bottom right - lu.assertAlmostEquals(params.dx + params.dw, 200, 0.01) - lu.assertAlmostEquals(params.dy + params.dh, 200, 0.01) -end - -function TestObjectPosition:testContainWithCenter() - local params = ImageRenderer.calculateFit(400, 300, 200, 200, "contain", "center center") - - -- Image (400x300) will be scaled to fit width (200x150) - -- Should be centered horizontally (dx=0) and vertically (dy=25) - lu.assertEquals(params.dx, 0) - lu.assertTrue(params.dy > 0) -end - --- ==================== --- Position with Cover Mode Tests --- ==================== - -function TestObjectPosition:testCoverWithTopLeft() - local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "top left") - - -- Crop should start from top-left - lu.assertEquals(params.sx, 0) - lu.assertEquals(params.sy, 0) -end - -function TestObjectPosition:testCoverWithBottomRight() - local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "bottom right") - - -- Crop should be from bottom-right - lu.assertTrue(params.sx > 0) - lu.assertTrue(params.sy >= 0) -end - -function TestObjectPosition:testCoverWithCenter() - local params = ImageRenderer.calculateFit(400, 300, 200, 200, "cover", "center center") - - -- Crop should be centered - lu.assertTrue(params.sx > 0) -end - --- ==================== --- Position with None Mode Tests --- ==================== - -function TestObjectPosition:testNoneWithTopLeft() - local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "top left") - - -- Image should be at top-left - lu.assertEquals(params.dx, 0) - lu.assertEquals(params.dy, 0) -end - -function TestObjectPosition:testNoneWithBottomRight() - local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "bottom right") - - -- Image should be at bottom-right - lu.assertEquals(params.dx, 100) -- 200 - 100 - lu.assertEquals(params.dy, 100) -- 200 - 100 -end - -function TestObjectPosition:testNoneWithCenter() - local params = ImageRenderer.calculateFit(100, 100, 200, 200, "none", "center center") - - -- Image should be centered - lu.assertEquals(params.dx, 50) -- (200 - 100) / 2 - lu.assertEquals(params.dy, 50) -- (200 - 100) / 2 -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/28_element_image_integration_tests.lua b/testing/__tests__/28_element_image_integration_tests.lua deleted file mode 100644 index 8b47429..0000000 --- a/testing/__tests__/28_element_image_integration_tests.lua +++ /dev/null @@ -1,391 +0,0 @@ -local lu = require("testing.luaunit") -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui -local Element = FlexLove.Element -local ImageCache = FlexLove.ImageCache - -TestElementImageIntegration = {} - -function TestElementImageIntegration:setUp() - Gui.init({ baseScale = { width = 1920, height = 1080 } }) - - -- Create a test image programmatically - self.testImageData = love.image.newImageData(400, 300) - -- Fill with a gradient pattern - for y = 0, 299 do - for x = 0, 399 do - local r = x / 399 - local g = y / 299 - local b = 0.5 - self.testImageData:setPixel(x, y, r, g, b, 1) - end - end - - -- Save to a temporary file (mock filesystem) - self.testImagePath = "testing/temp_element_test_image.png" - self.testImageData:encode("png", self.testImagePath) - love.filesystem.addMockFile(self.testImagePath, "mock_image_data") - - -- Create test image object - self.testImage = love.graphics.newImage(self.testImageData) -end - -function TestElementImageIntegration:tearDown() - Gui.destroy() - ImageCache.clear() - - -- Clean up temporary test file - if love.filesystem.getInfo(self.testImagePath) then - love.filesystem.remove(self.testImagePath) - end -end - --- ==================== --- Element Creation Tests --- ==================== - -function TestElementImageIntegration:testElementWithImagePath() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element.imagePath, self.testImagePath) -end - -function TestElementImageIntegration:testElementWithImageObject() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - image = self.testImage, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element._loadedImage, self.testImage) -end - -function TestElementImageIntegration:testElementWithInvalidImagePath() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = "nonexistent/image.png", - }) - - -- Should not crash, just not have a loaded image - lu.assertNil(element._loadedImage) -end - -function TestElementImageIntegration:testElementWithoutImage() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - }) - - lu.assertNil(element._loadedImage) - lu.assertNil(element.imagePath) - lu.assertNil(element.image) -end - --- ==================== --- Property Tests --- ==================== - -function TestElementImageIntegration:testObjectFitProperty() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - objectFit = "contain", - }) - - lu.assertEquals(element.objectFit, "contain") -end - -function TestElementImageIntegration:testObjectFitDefaultValue() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - lu.assertEquals(element.objectFit, "fill") -end - -function TestElementImageIntegration:testObjectPositionProperty() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - objectPosition = "top left", - }) - - lu.assertEquals(element.objectPosition, "top left") -end - -function TestElementImageIntegration:testObjectPositionDefaultValue() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - lu.assertEquals(element.objectPosition, "center center") -end - -function TestElementImageIntegration:testImageOpacityProperty() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - imageOpacity = 0.5, - }) - - lu.assertEquals(element.imageOpacity, 0.5) -end - -function TestElementImageIntegration:testImageOpacityDefaultValue() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - lu.assertEquals(element.imageOpacity, 1) -end - --- ==================== --- Image Caching Tests --- ==================== - -function TestElementImageIntegration:testMultipleElementsShareCachedImage() - local element1 = Element.new({ - x = 0, - y = 0, - width = 100, - height = 100, - imagePath = self.testImagePath, - }) - - local element2 = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - -- Both should have the same cached image reference - lu.assertEquals(element1._loadedImage, element2._loadedImage) - - -- Cache should only have one entry - local stats = ImageCache.getStats() - lu.assertEquals(stats.count, 1) -end - --- ==================== --- Rendering Tests (Basic Validation) --- ==================== - -function TestElementImageIntegration:testDrawDoesNotCrashWithImage() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - -- Should not crash when drawing - lu.assertNotNil(function() - element:draw() - end) -end - -function TestElementImageIntegration:testDrawDoesNotCrashWithoutImage() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - }) - - -- Should not crash when drawing without image - lu.assertNotNil(function() - element:draw() - end) -end - -function TestElementImageIntegration:testDrawWithZeroOpacity() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - opacity = 0, - }) - - -- Should not crash (early exit in draw) - lu.assertNotNil(function() - element:draw() - end) -end - --- ==================== --- Combined Properties Tests --- ==================== - -function TestElementImageIntegration:testImageWithPadding() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - padding = { top = 10, right = 10, bottom = 10, left = 10 }, - imagePath = self.testImagePath, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element.padding.top, 10) - lu.assertEquals(element.padding.left, 10) - -- Image should render in content area (180x180 = 200 - 10 - 10) - lu.assertEquals(element.width, 180) - lu.assertEquals(element.height, 180) -end - -function TestElementImageIntegration:testImageWithCornerRadius() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - cornerRadius = 20, - imagePath = self.testImagePath, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element.cornerRadius.topLeft, 20) - lu.assertEquals(element.cornerRadius.topRight, 20) - lu.assertEquals(element.cornerRadius.bottomLeft, 20) - lu.assertEquals(element.cornerRadius.bottomRight, 20) -end - -function TestElementImageIntegration:testImageWithBackgroundColor() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - backgroundColor = FlexLove.Color.new(1, 0, 0, 1), - imagePath = self.testImagePath, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element.backgroundColor.r, 1) - lu.assertEquals(element.backgroundColor.g, 0) - lu.assertEquals(element.backgroundColor.b, 0) -end - -function TestElementImageIntegration:testImageWithAllObjectFitModes() - local modes = { "fill", "contain", "cover", "scale-down", "none" } - - for _, mode in ipairs(modes) do - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - objectFit = mode, - }) - - lu.assertEquals(element.objectFit, mode) - lu.assertNotNil(element._loadedImage) - end -end - -function TestElementImageIntegration:testImageWithCombinedOpacity() - local element = Element.new({ - x = 100, - y = 100, - width = 200, - height = 200, - imagePath = self.testImagePath, - opacity = 0.5, - imageOpacity = 0.8, - }) - - lu.assertEquals(element.opacity, 0.5) - lu.assertEquals(element.imageOpacity, 0.8) - -- Combined opacity should be 0.5 * 0.8 = 0.4 (tested in rendering) -end - --- ==================== --- Layout Integration Tests --- ==================== - -function TestElementImageIntegration:testImageWithFlexLayout() - local container = Element.new({ - x = 0, - y = 0, - width = 600, - height = 200, - flexDirection = FlexLove.enums.FlexDirection.HORIZONTAL, - }) - - local imageElement = Element.new({ - width = 200, - height = 200, - imagePath = self.testImagePath, - parent = container, - }) - - table.insert(container.children, imageElement) - - lu.assertNotNil(imageElement._loadedImage) - lu.assertEquals(imageElement.width, 200) - lu.assertEquals(imageElement.height, 200) -end - -function TestElementImageIntegration:testImageWithAbsolutePositioning() - local element = Element.new({ - positioning = FlexLove.enums.Positioning.ABSOLUTE, - top = 50, - left = 50, - width = 200, - height = 200, - imagePath = self.testImagePath, - }) - - lu.assertNotNil(element._loadedImage) - lu.assertEquals(element.positioning, FlexLove.enums.Positioning.ABSOLUTE) -end - --- Run tests if executed directly -if arg and arg[0]:match("28_element_image_integration_tests.lua$") then - os.exit(lu.LuaUnit.run()) -end - -return TestElementImageIntegration diff --git a/testing/__tests__/29_drag_event_tests.lua b/testing/__tests__/29_drag_event_tests.lua deleted file mode 100644 index 8573ee8..0000000 --- a/testing/__tests__/29_drag_event_tests.lua +++ /dev/null @@ -1,282 +0,0 @@ --- Drag Event Tests --- Tests for the new drag event functionality - -package.path = package.path .. ";?.lua" - -local lu = require("testing.luaunit") -require("testing.loveStub") -local FlexLove = require("FlexLove") -local Gui = FlexLove.Gui - -TestDragEvent = {} - -function TestDragEvent:setUp() - -- Initialize GUI before each test - Gui.init({ baseScale = { width = 1920, height = 1080 } }) - love.window.setMode(1920, 1080) - Gui.resize(1920, 1080) -- Recalculate scale factors after setMode -end - -function TestDragEvent:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- Test 1: Drag event is fired when mouse moves while pressed -function TestDragEvent:test_drag_event_fired_on_mouse_movement() - local dragEventReceived = false - local dragEvent = nil - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - dragEventReceived = true - dragEvent = event - end - end, - }) - - -- Simulate mouse press - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - -- Move mouse while pressed (drag) - love.mouse.setPosition(160, 155) - element:update(0.016) - - lu.assertTrue(dragEventReceived, "Drag event should be fired when mouse moves while pressed") - lu.assertNotNil(dragEvent, "Drag event object should exist") -end - --- Test 2: Drag event contains dx and dy fields -function TestDragEvent:test_drag_event_contains_delta_values() - local dragEvent = nil - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - dragEvent = event - end - end, - }) - - -- Simulate mouse press at (150, 150) - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - -- Move mouse to (160, 155) - delta should be (10, 5) - love.mouse.setPosition(160, 155) - element:update(0.016) - - lu.assertNotNil(dragEvent, "Drag event should be received") - lu.assertNotNil(dragEvent.dx, "Drag event should have dx field") - lu.assertNotNil(dragEvent.dy, "Drag event should have dy field") - lu.assertEquals(dragEvent.dx, 10, "dx should be 10 (160 - 150)") - lu.assertEquals(dragEvent.dy, 5, "dy should be 5 (155 - 150)") -end - --- Test 3: Drag event updates dx/dy as mouse continues to move -function TestDragEvent:test_drag_event_updates_delta_continuously() - local dragEvents = {} - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - table.insert(dragEvents, { dx = event.dx, dy = event.dy }) - end - end, - }) - - -- Press at (150, 150) - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - -- Move to (160, 155) - love.mouse.setPosition(160, 155) - element:update(0.016) - - -- Move to (170, 160) - love.mouse.setPosition(170, 160) - element:update(0.016) - - lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events") - lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10") - lu.assertEquals(dragEvents[1].dy, 5, "First drag dy should be 5") - lu.assertEquals(dragEvents[2].dx, 20, "Second drag dx should be 20 (170 - 150)") - lu.assertEquals(dragEvents[2].dy, 10, "Second drag dy should be 10 (160 - 150)") -end - --- Test 4: No drag event when mouse doesn't move -function TestDragEvent:test_no_drag_event_when_mouse_stationary() - local dragEventCount = 0 - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - dragEventCount = dragEventCount + 1 - end - end, - }) - - -- Press at (150, 150) - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - -- Update again without moving mouse - element:update(0.016) - element:update(0.016) - - lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse doesn't move") -end - --- Test 5: Drag tracking is cleaned up on release -function TestDragEvent:test_drag_tracking_cleaned_up_on_release() - local dragEvents = {} - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - table.insert(dragEvents, { dx = event.dx, dy = event.dy }) - end - end, - }) - - -- First drag sequence - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - love.mouse.setPosition(160, 155) - element:update(0.016) - - -- Release - love.mouse.setDown(1, false) - element:update(0.016) - - -- Second drag sequence - should start fresh - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - love.mouse.setPosition(155, 152) - element:update(0.016) - - lu.assertEquals(#dragEvents, 2, "Should receive 2 drag events total") - lu.assertEquals(dragEvents[1].dx, 10, "First drag dx should be 10") - lu.assertEquals(dragEvents[2].dx, 5, "Second drag dx should be 5 (new drag start)") -end - --- Test 6: Drag works with different mouse buttons -function TestDragEvent:test_drag_works_with_different_buttons() - local dragEvents = {} - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - table.insert(dragEvents, { button = event.button, dx = event.dx }) - end - end, - }) - - -- Right button drag - -- Make sure no other buttons are down - love.mouse.setDown(1, false) - love.mouse.setDown(3, false) - - love.mouse.setPosition(150, 150) - love.mouse.setDown(2, true) - element:update(0.016) - - love.mouse.setPosition(160, 150) - element:update(0.016) - - lu.assertEquals(#dragEvents, 1, "Should receive drag event for right button") - lu.assertEquals(dragEvents[1].button, 2, "Drag event should be for button 2") - lu.assertEquals(dragEvents[1].dx, 10, "Drag dx should be 10") -end - --- Test 7: Drag event contains correct mouse position -function TestDragEvent:test_drag_event_contains_mouse_position() - local dragEvent = nil - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - dragEvent = event - end - end, - }) - - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - love.mouse.setPosition(175, 165) - element:update(0.016) - - lu.assertNotNil(dragEvent, "Drag event should be received") - lu.assertEquals(dragEvent.x, 175, "Drag event x should match current mouse x") - lu.assertEquals(dragEvent.y, 165, "Drag event y should match current mouse y") -end - --- Test 8: No drag event when mouse leaves element -function TestDragEvent:test_no_drag_when_mouse_leaves_element() - local dragEventCount = 0 - - local element = Gui.new({ - x = 100, - y = 100, - width = 200, - height = 100, - onEvent = function(el, event) - if event.type == "drag" then - dragEventCount = dragEventCount + 1 - end - end, - }) - - -- Press inside element - love.mouse.setPosition(150, 150) - love.mouse.setDown(1, true) - element:update(0.016) - - -- Move outside element - love.mouse.setPosition(50, 50) - element:update(0.016) - - lu.assertEquals(dragEventCount, 0, "Should not receive drag events when mouse leaves element") -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/30_scrollbar_features_tests.lua b/testing/__tests__/30_scrollbar_features_tests.lua deleted file mode 100644 index e370923..0000000 --- a/testing/__tests__/30_scrollbar_features_tests.lua +++ /dev/null @@ -1,552 +0,0 @@ -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") -local Gui, enums, Color = FlexLove.Gui, FlexLove.enums, FlexLove.Color - -local Positioning = enums.Positioning - --- Create test cases for scrollbar features -TestScrollbarFeatures = {} - -function TestScrollbarFeatures:setUp() - -- Clean up before each test - Gui.destroy() -end - -function TestScrollbarFeatures:tearDown() - -- Clean up after each test - Gui.destroy() -end - --- ======================================== --- Test 1: hideScrollbars with boolean value --- ======================================== -function TestScrollbarFeatures:testHideScrollbarsBooleanTrue() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - hideScrollbars = true, - }) - - -- Verify hideScrollbars is properly initialized - luaunit.assertNotNil(container.hideScrollbars) - luaunit.assertEquals(type(container.hideScrollbars), "table") - luaunit.assertEquals(container.hideScrollbars.vertical, true) - luaunit.assertEquals(container.hideScrollbars.horizontal, true) -end - -function TestScrollbarFeatures:testHideScrollbarsBooleanFalse() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - hideScrollbars = false, - }) - - -- Verify hideScrollbars defaults to showing scrollbars - luaunit.assertNotNil(container.hideScrollbars) - luaunit.assertEquals(container.hideScrollbars.vertical, false) - luaunit.assertEquals(container.hideScrollbars.horizontal, false) -end - --- ======================================== --- Test 2: hideScrollbars with table configuration --- ======================================== -function TestScrollbarFeatures:testHideScrollbarsTableVerticalOnly() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - hideScrollbars = { vertical = true, horizontal = false }, - }) - - -- Verify only vertical scrollbar is hidden - luaunit.assertEquals(container.hideScrollbars.vertical, true) - luaunit.assertEquals(container.hideScrollbars.horizontal, false) -end - -function TestScrollbarFeatures:testHideScrollbarsTableHorizontalOnly() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - hideScrollbars = { vertical = false, horizontal = true }, - }) - - -- Verify only horizontal scrollbar is hidden - luaunit.assertEquals(container.hideScrollbars.vertical, false) - luaunit.assertEquals(container.hideScrollbars.horizontal, true) -end - -function TestScrollbarFeatures:testHideScrollbarsTableBothHidden() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - hideScrollbars = { vertical = false, horizontal = false }, - }) - - -- Verify both scrollbars are shown - luaunit.assertEquals(container.hideScrollbars.vertical, false) - luaunit.assertEquals(container.hideScrollbars.horizontal, false) -end - --- ======================================== --- Test 3: Default hideScrollbars behavior --- ======================================== -function TestScrollbarFeatures:testHideScrollbarsDefault() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - }) - - -- Verify default is to show scrollbars (backward compatibility) - luaunit.assertNotNil(container.hideScrollbars) - luaunit.assertEquals(container.hideScrollbars.vertical, false) - luaunit.assertEquals(container.hideScrollbars.horizontal, false) -end - --- ======================================== --- Test 4: Independent hover states initialization --- ======================================== -function TestScrollbarFeatures:testIndependentHoverStatesInitialization() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - }) - - -- Verify independent hover states are initialized - luaunit.assertNotNil(container._scrollbarHoveredVertical) - luaunit.assertNotNil(container._scrollbarHoveredHorizontal) - luaunit.assertEquals(container._scrollbarHoveredVertical, false) - luaunit.assertEquals(container._scrollbarHoveredHorizontal, false) -end - --- ======================================== --- Test 5: Scrollbar dimensions calculation --- ======================================== -function TestScrollbarFeatures:testScrollbarDimensionsCalculation() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Calculate scrollbar dimensions - local dims = container:_calculateScrollbarDimensions() - - -- Verify dimensions structure - luaunit.assertNotNil(dims.vertical) - luaunit.assertNotNil(dims.horizontal) - luaunit.assertNotNil(dims.vertical.visible) - luaunit.assertNotNil(dims.horizontal.visible) -end - --- ======================================== --- Test 6: Scroll position management --- ======================================== -function TestScrollbarFeatures:testScrollPositionSetAndGet() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow to set max scroll - container:_detectOverflow() - - -- Set scroll position - container:setScrollPosition(50, 100) - - -- Get scroll position - local scrollX, scrollY = container:getScrollPosition() - luaunit.assertEquals(scrollX, 50) - luaunit.assertEquals(scrollY, 100) -end - -function TestScrollbarFeatures:testScrollPositionClamping() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow to set max scroll - container:_detectOverflow() - - -- Try to set scroll position beyond max - container:setScrollPosition(1000, 1000) - - -- Get scroll position - should be clamped to max - local scrollX, scrollY = container:getScrollPosition() - local maxScrollX, maxScrollY = container:getMaxScroll() - luaunit.assertEquals(scrollX, maxScrollX) - luaunit.assertEquals(scrollY, maxScrollY) -end - --- ======================================== --- Test 7: Scroll by delta --- ======================================== -function TestScrollbarFeatures:testScrollByDelta() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Initial scroll position - container:setScrollPosition(50, 50) - - -- Scroll by delta - container:scrollBy(10, 20) - - -- Verify new position - local scrollX, scrollY = container:getScrollPosition() - luaunit.assertEquals(scrollX, 60) - luaunit.assertEquals(scrollY, 70) -end - --- ======================================== --- Test 8: Scroll to top/bottom/left/right --- ======================================== -function TestScrollbarFeatures:testScrollToTop() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Set initial scroll position - container:setScrollPosition(50, 50) - - -- Scroll to top - container:scrollToTop() - - -- Verify position - local scrollX, scrollY = container:getScrollPosition() - luaunit.assertEquals(scrollY, 0) -end - -function TestScrollbarFeatures:testScrollToBottom() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Scroll to bottom - container:scrollToBottom() - - -- Verify position - local scrollX, scrollY = container:getScrollPosition() - local maxScrollX, maxScrollY = container:getMaxScroll() - luaunit.assertEquals(scrollY, maxScrollY) -end - -function TestScrollbarFeatures:testScrollToLeft() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Set initial scroll position - container:setScrollPosition(50, 50) - - -- Scroll to left - container:scrollToLeft() - - -- Verify position - local scrollX, scrollY = container:getScrollPosition() - luaunit.assertEquals(scrollX, 0) -end - -function TestScrollbarFeatures:testScrollToRight() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Scroll to right - container:scrollToRight() - - -- Verify position - local scrollX, scrollY = container:getScrollPosition() - local maxScrollX, maxScrollY = container:getMaxScroll() - luaunit.assertEquals(scrollX, maxScrollX) -end - --- ======================================== --- Test 9: Get scroll percentage --- ======================================== -function TestScrollbarFeatures:testGetScrollPercentage() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Scroll to middle - local maxScrollX, maxScrollY = container:getMaxScroll() - container:setScrollPosition(maxScrollX / 2, maxScrollY / 2) - - -- Get scroll percentage - local percentX, percentY = container:getScrollPercentage() - luaunit.assertAlmostEquals(percentX, 0.5, 0.01) - luaunit.assertAlmostEquals(percentY, 0.5, 0.01) -end - --- ======================================== --- Test 10: Has overflow detection --- ======================================== -function TestScrollbarFeatures:testHasOverflow() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows vertically - local child = Gui.new({ - parent = container, - width = 150, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Check overflow - local hasOverflowX, hasOverflowY = container:hasOverflow() - luaunit.assertEquals(hasOverflowX, false) - luaunit.assertEquals(hasOverflowY, true) -end - --- ======================================== --- Test 11: Get content size --- ======================================== -function TestScrollbarFeatures:testGetContentSize() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child with specific size - local child = Gui.new({ - parent = container, - width = 300, - height = 400, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Get content size - local contentWidth, contentHeight = container:getContentSize() - luaunit.assertEquals(contentWidth, 300) - luaunit.assertEquals(contentHeight, 400) -end - --- ======================================== --- Test 12: Scrollbar configuration options --- ======================================== -function TestScrollbarFeatures:testScrollbarConfigurationOptions() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - scrollbarWidth = 20, - scrollbarRadius = 10, - scrollbarPadding = 5, - scrollSpeed = 30, - scrollbarColor = Color.new(1, 0, 0, 1), - scrollbarTrackColor = Color.new(0, 1, 0, 1), - }) - - -- Verify custom configuration - luaunit.assertEquals(container.scrollbarWidth, 20) - luaunit.assertEquals(container.scrollbarRadius, 10) - luaunit.assertEquals(container.scrollbarPadding, 5) - luaunit.assertEquals(container.scrollSpeed, 30) - luaunit.assertEquals(container.scrollbarColor.r, 1) - luaunit.assertEquals(container.scrollbarTrackColor.g, 1) -end - --- ======================================== --- Test 13: Wheel scroll handling --- ======================================== -function TestScrollbarFeatures:testWheelScrollHandling() - local container = Gui.new({ - x = 0, - y = 0, - width = 200, - height = 200, - overflow = "scroll", - positioning = Positioning.FLEX, - }) - - -- Add child that overflows - local child = Gui.new({ - parent = container, - width = 300, - height = 300, - }) - - -- Detect overflow - container:_detectOverflow() - - -- Set initial position away from top so we can scroll up - container:setScrollPosition(nil, 50) - local initialScrollX, initialScrollY = container:getScrollPosition() - - -- Handle wheel scroll (vertical) - positive y means scroll up - local handled = container:_handleWheelScroll(0, 1) - - -- Verify scroll was handled and position changed (scrolled up means lower scroll value) - luaunit.assertEquals(handled, true) - local scrollX, scrollY = container:getScrollPosition() - luaunit.assertTrue(scrollY < initialScrollY, "Expected scroll position to decrease when scrolling up") -end - --- Run the tests -luaunit.LuaUnit.run() diff --git a/testing/__tests__/31_immediate_mode_basic_tests.lua b/testing/__tests__/31_immediate_mode_basic_tests.lua deleted file mode 100644 index c7430b9..0000000 --- a/testing/__tests__/31_immediate_mode_basic_tests.lua +++ /dev/null @@ -1,273 +0,0 @@ --- Test: Immediate Mode Basic Functionality -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") - -local Gui = FlexLove.Gui - -TestImmediateModeBasic = {} - -function TestImmediateModeBasic:setUp() - -- Reset GUI state - if Gui.destroy then - Gui.destroy() - end - - -- Initialize with immediate mode enabled - Gui.init({ - baseScale = { width = 1920, height = 1080 }, - immediateMode = true, - }) -end - -function TestImmediateModeBasic:tearDown() - -- Clear all states - if Gui.clearAllStates then - Gui.clearAllStates() - end - - -- Reset immediate mode state - if Gui._immediateModeState then - Gui._immediateModeState.reset() - end - - if Gui.destroy then - Gui.destroy() - end - - -- Reset immediate mode flag - Gui._immediateMode = false - Gui._frameNumber = 0 -end - -function TestImmediateModeBasic:test_immediate_mode_enabled() - luaunit.assertTrue(Gui._immediateMode, "Immediate mode should be enabled") - luaunit.assertNotNil(Gui._immediateModeState, "Immediate mode state should be initialized") -end - -function TestImmediateModeBasic:test_frame_lifecycle() - -- Begin frame - Gui.beginFrame() - - luaunit.assertEquals(Gui._frameNumber, 1, "Frame number should increment to 1") - luaunit.assertEquals(#Gui.topElements, 0, "Top elements should be empty at frame start") - - -- Create an element - local button = Gui.new({ - id = "test_button", - width = 100, - height = 50, - text = "Click me", - }) - - luaunit.assertNotNil(button, "Button should be created") - luaunit.assertEquals(button.id, "test_button", "Button should have correct ID") - - -- End frame - Gui.endFrame() - - -- State should persist - luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state entry") -end - -function TestImmediateModeBasic:test_auto_id_generation() - Gui.beginFrame() - - -- Create element without explicit ID - local element1 = Gui.new({ - width = 100, - height = 50, - }) - - luaunit.assertNotNil(element1.id, "Element should have auto-generated ID") - luaunit.assertNotEquals(element1.id, "", "Auto-generated ID should not be empty") - - Gui.endFrame() -end - -function TestImmediateModeBasic:test_state_persistence() - -- Frame 1: Create button and simulate click - Gui.beginFrame() - - local button = Gui.new({ - id = "persistent_button", - width = 100, - height = 50, - text = "Click me", - }) - - -- Simulate some state - button._clickCount = 5 - button._lastClickTime = 123.45 - - Gui.endFrame() - - -- Frame 2: Recreate button - state should persist - Gui.beginFrame() - - local button2 = Gui.new({ - id = "persistent_button", - width = 100, - height = 50, - text = "Click me", - }) - - luaunit.assertEquals(button2._clickCount, 5, "Click count should persist") - luaunit.assertEquals(button2._lastClickTime, 123.45, "Last click time should persist") - - Gui.endFrame() -end - -function TestImmediateModeBasic:test_helper_functions() - Gui.beginFrame() - - -- Test button helper - local button = Gui.button({ - id = "helper_button", - width = 100, - height = 50, - text = "Button", - }) - - luaunit.assertNotNil(button, "Button helper should create element") - luaunit.assertEquals(button.themeComponent, "button", "Button should have theme component") - - -- Test panel helper - local panel = Gui.panel({ - id = "helper_panel", - width = 200, - height = 200, - }) - - luaunit.assertNotNil(panel, "Panel helper should create element") - - -- Test text helper - local text = Gui.text({ - id = "helper_text", - text = "Hello", - }) - - luaunit.assertNotNil(text, "Text helper should create element") - - -- Test input helper - local input = Gui.input({ - id = "helper_input", - width = 150, - height = 30, - }) - - luaunit.assertNotNil(input, "Input helper should create element") - luaunit.assertTrue(input.editable, "Input should be editable") - - Gui.endFrame() -end - -function TestImmediateModeBasic:test_state_cleanup() - Gui.init({ - immediateMode = true, - stateRetentionFrames = 2, -- Very short retention for testing - }) - - -- Frame 1: Create temporary element - Gui.beginFrame() - Gui.new({ - id = "temp_element", - width = 100, - height = 50, - }) - Gui.endFrame() - - luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state after frame 1") - - -- Frame 2: Don't create the element - Gui.beginFrame() - Gui.endFrame() - - luaunit.assertEquals(Gui.getStateCount(), 1, "Should still have 1 state after frame 2") - - -- Frame 3: Still don't create it - Gui.beginFrame() - Gui.endFrame() - - luaunit.assertEquals(Gui.getStateCount(), 1, "Should still have 1 state after frame 3") - - -- Frame 4: Should be cleaned up now (retention = 2 frames) - Gui.beginFrame() - Gui.endFrame() - - luaunit.assertEquals(Gui.getStateCount(), 0, "State should be cleaned up after retention period") -end - -function TestImmediateModeBasic:test_manual_state_management() - Gui.beginFrame() - - Gui.new({ - id = "element1", - width = 100, - height = 50, - }) - - Gui.new({ - id = "element2", - width = 100, - height = 50, - }) - - Gui.endFrame() - - luaunit.assertEquals(Gui.getStateCount(), 2, "Should have 2 states") - - -- Clear specific state - Gui.clearState("element1") - luaunit.assertEquals(Gui.getStateCount(), 1, "Should have 1 state after clearing element1") - - -- Clear all states - Gui.clearAllStates() - luaunit.assertEquals(Gui.getStateCount(), 0, "Should have 0 states after clearing all") -end - -function TestImmediateModeBasic:test_retained_mode_still_works() - -- Reinitialize without immediate mode - Gui.destroy() - Gui.init({ - baseScale = { width = 1920, height = 1080 }, - immediateMode = false, -- Explicitly disable - }) - - luaunit.assertFalse(Gui._immediateMode, "Immediate mode should be disabled") - - -- Create element in retained mode - local element = Gui.new({ - width = 100, - height = 50, - text = "Retained", - }) - - luaunit.assertNotNil(element, "Element should be created in retained mode") - luaunit.assertEquals(#Gui.topElements, 1, "Should have 1 top element") - - -- Element should persist without beginFrame/endFrame - luaunit.assertEquals(#Gui.topElements, 1, "Element should still exist") -end - -function TestImmediateModeBasic:test_state_stats() - Gui.beginFrame() - - Gui.new({ - id = "stats_test", - width = 100, - height = 50, - }) - - Gui.endFrame() - - local stats = Gui.getStateStats() - - luaunit.assertNotNil(stats, "Stats should be returned") - luaunit.assertEquals(stats.stateCount, 1, "Stats should show 1 state") - luaunit.assertNotNil(stats.frameNumber, "Stats should include frame number") -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/32_state_manager_tests.lua b/testing/__tests__/32_state_manager_tests.lua deleted file mode 100644 index 561e877..0000000 --- a/testing/__tests__/32_state_manager_tests.lua +++ /dev/null @@ -1,322 +0,0 @@ --- ==================== --- StateManager Module Tests --- ==================== - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local StateManager = require("modules.StateManager") - -TestStateManager = {} - -function TestStateManager:setUp() - -- Reset StateManager before each test - StateManager.clearAllStates() -end - -function TestStateManager:tearDown() - -- Clean up after each test - StateManager.clearAllStates() -end - --- ==================== --- Basic State Operations --- ==================== - -function TestStateManager:test_getState_createsNewState() - local state = StateManager.getState("test-element") - - luaunit.assertNotNil(state) - luaunit.assertEquals(state.hover, false) - luaunit.assertEquals(state.pressed, false) - luaunit.assertEquals(state.focused, false) - luaunit.assertEquals(state.disabled, false) - luaunit.assertEquals(state.active, false) -end - -function TestStateManager:test_getState_returnsExistingState() - local state1 = StateManager.getState("test-element") - state1.hover = true - - local state2 = StateManager.getState("test-element") - - luaunit.assertEquals(state2.hover, true) - luaunit.assertTrue(state1 == state2) -- Same reference -end - -function TestStateManager:test_updateState_modifiesState() - StateManager.updateState("test-element", { - hover = true, - pressed = false, - }) - - local state = StateManager.getState("test-element") - luaunit.assertEquals(state.hover, true) - luaunit.assertEquals(state.pressed, false) -end - -function TestStateManager:test_updateState_mergesPartialState() - StateManager.updateState("test-element", { hover = true }) - StateManager.updateState("test-element", { pressed = true }) - - local state = StateManager.getState("test-element") - luaunit.assertEquals(state.hover, true) - luaunit.assertEquals(state.pressed, true) -end - -function TestStateManager:test_clearState_removesState() - StateManager.updateState("test-element", { hover = true }) - StateManager.clearState("test-element") - - local state = StateManager.getState("test-element") - luaunit.assertEquals(state.hover, false) -- New state created with defaults -end - --- ==================== --- Scrollbar State Tests --- ==================== - -function TestStateManager:test_scrollbarStates_initialization() - local state = StateManager.getState("test-element") - - luaunit.assertEquals(state.scrollbarHoveredVertical, false) - luaunit.assertEquals(state.scrollbarHoveredHorizontal, false) - luaunit.assertEquals(state.scrollbarDragging, false) - luaunit.assertNil(state.hoveredScrollbar) - luaunit.assertEquals(state.scrollbarDragOffset, 0) -end - -function TestStateManager:test_scrollbarStates_updates() - StateManager.updateState("test-element", { - scrollbarHoveredVertical = true, - scrollbarDragging = true, - hoveredScrollbar = "vertical", - scrollbarDragOffset = 25, - }) - - local state = StateManager.getState("test-element") - luaunit.assertEquals(state.scrollbarHoveredVertical, true) - luaunit.assertEquals(state.scrollbarDragging, true) - luaunit.assertEquals(state.hoveredScrollbar, "vertical") - luaunit.assertEquals(state.scrollbarDragOffset, 25) -end - --- ==================== --- Frame Management Tests --- ==================== - -function TestStateManager:test_frameNumber_increments() - local frame1 = StateManager.getFrameNumber() - StateManager.incrementFrame() - local frame2 = StateManager.getFrameNumber() - - luaunit.assertEquals(frame2, frame1 + 1) -end - -function TestStateManager:test_updateState_updatesFrameNumber() - StateManager.incrementFrame() - StateManager.incrementFrame() - local currentFrame = StateManager.getFrameNumber() - - StateManager.updateState("test-element", { hover = true }) - - -- State should exist and be accessible - local state = StateManager.getState("test-element") - luaunit.assertNotNil(state) -end - --- ==================== --- Cleanup Tests --- ==================== - -function TestStateManager:test_cleanup_removesStaleStates() - -- Configure short retention - StateManager.configure({ stateRetentionFrames = 5 }) - - -- Create state - StateManager.updateState("test-element", { hover = true }) - - -- Advance frames beyond retention - for i = 1, 10 do - StateManager.incrementFrame() - end - - -- Cleanup should remove the state - local cleanedCount = StateManager.cleanup() - luaunit.assertEquals(cleanedCount, 1) - - -- Reset config - StateManager.configure({ stateRetentionFrames = 60 }) -end - -function TestStateManager:test_cleanup_keepsActiveStates() - StateManager.configure({ stateRetentionFrames = 5 }) - - StateManager.updateState("test-element", { hover = true }) - - -- Update state within retention period - for i = 1, 3 do - StateManager.incrementFrame() - StateManager.updateState("test-element", { hover = true }) - end - - local cleanedCount = StateManager.cleanup() - luaunit.assertEquals(cleanedCount, 0) -- Should not clean active state - - StateManager.configure({ stateRetentionFrames = 60 }) -end - -function TestStateManager:test_forceCleanupIfNeeded_activatesWhenOverLimit() - StateManager.configure({ maxStateEntries = 5 }) - - -- Create more states than limit - for i = 1, 10 do - StateManager.updateState("element-" .. i, { hover = true }) - end - - -- Advance frames - for i = 1, 15 do - StateManager.incrementFrame() - end - - local cleanedCount = StateManager.forceCleanupIfNeeded() - luaunit.assertTrue(cleanedCount > 0) - - StateManager.configure({ maxStateEntries = 1000 }) -end - --- ==================== --- State Count Tests --- ==================== - -function TestStateManager:test_getStateCount_returnsCorrectCount() - luaunit.assertEquals(StateManager.getStateCount(), 0) - - StateManager.getState("element-1") - StateManager.getState("element-2") - StateManager.getState("element-3") - - luaunit.assertEquals(StateManager.getStateCount(), 3) -end - --- ==================== --- Active State Tests --- ==================== - -function TestStateManager:test_getActiveState_returnsOnlyActiveProperties() - StateManager.updateState("test-element", { - hover = true, - pressed = false, - focused = true, - }) - - local activeState = StateManager.getActiveState("test-element") - - luaunit.assertEquals(activeState.hover, true) - luaunit.assertEquals(activeState.pressed, false) - luaunit.assertEquals(activeState.focused, true) - luaunit.assertNil(activeState.lastUpdateFrame) -- Should not include frame tracking -end - --- ==================== --- Helper Function Tests --- ==================== - -function TestStateManager:test_isHovered_returnsTrueWhenHovered() - StateManager.updateState("test-element", { hover = true }) - luaunit.assertTrue(StateManager.isHovered("test-element")) -end - -function TestStateManager:test_isHovered_returnsFalseWhenNotHovered() - StateManager.updateState("test-element", { hover = false }) - luaunit.assertFalse(StateManager.isHovered("test-element")) -end - -function TestStateManager:test_isPressed_returnsTrueWhenPressed() - StateManager.updateState("test-element", { pressed = true }) - luaunit.assertTrue(StateManager.isPressed("test-element")) -end - -function TestStateManager:test_isFocused_returnsTrueWhenFocused() - StateManager.updateState("test-element", { focused = true }) - luaunit.assertTrue(StateManager.isFocused("test-element")) -end - -function TestStateManager:test_isDisabled_returnsTrueWhenDisabled() - StateManager.updateState("test-element", { disabled = true }) - luaunit.assertTrue(StateManager.isDisabled("test-element")) -end - -function TestStateManager:test_isActive_returnsTrueWhenActive() - StateManager.updateState("test-element", { active = true }) - luaunit.assertTrue(StateManager.isActive("test-element")) -end - --- ==================== --- ID Generation Tests --- ==================== - -function TestStateManager:test_generateID_createsUniqueID() - local id1 = StateManager.generateID({ test = "value1" }) - local id2 = StateManager.generateID({ test = "value2" }) - - luaunit.assertNotNil(id1) - luaunit.assertNotNil(id2) - luaunit.assertTrue(type(id1) == "string") - luaunit.assertTrue(type(id2) == "string") -end - -function TestStateManager:test_generateID_withoutProps() - local id = StateManager.generateID(nil) - - luaunit.assertNotNil(id) - luaunit.assertTrue(type(id) == "string") -end - --- ==================== --- Scroll Position Tests --- ==================== - -function TestStateManager:test_scrollPosition_initialization() - local state = StateManager.getState("test-element") - - luaunit.assertEquals(state.scrollX, 0) - luaunit.assertEquals(state.scrollY, 0) -end - -function TestStateManager:test_scrollPosition_updates() - StateManager.updateState("test-element", { - scrollX = 100, - scrollY = 200, - }) - - local state = StateManager.getState("test-element") - luaunit.assertEquals(state.scrollX, 100) - luaunit.assertEquals(state.scrollY, 200) -end - --- ==================== --- Configuration Tests --- ==================== - -function TestStateManager:test_configure_updatesSettings() - StateManager.configure({ - stateRetentionFrames = 30, - maxStateEntries = 500, - }) - - -- Test that configuration was applied by creating many states - -- and checking cleanup behavior (indirect test) - for i = 1, 10 do - StateManager.updateState("element-" .. i, { hover = true }) - end - - luaunit.assertEquals(StateManager.getStateCount(), 10) - - -- Reset to defaults - StateManager.configure({ - stateRetentionFrames = 60, - maxStateEntries = 1000, - }) -end - -luaunit.LuaUnit.run() diff --git a/testing/__tests__/33_input_field_tests.lua b/testing/__tests__/33_input_field_tests.lua deleted file mode 100644 index 0bb361f..0000000 --- a/testing/__tests__/33_input_field_tests.lua +++ /dev/null @@ -1,2112 +0,0 @@ --- ==================== --- Input Field Tests --- ==================== --- Test suite for text input functionality in FlexLove - -local lu = require("testing.luaunit") -local loveStub = require("testing.loveStub") - --- Setup LÖVE environment -_G.love = loveStub - --- Load FlexLove after setting up love stub -local FlexLove = require("FlexLove") -local StateManager = require("modules.StateManager") - --- Test fixtures -local testElement - -TestInputField = {} - -function TestInputField:setUp() - -- Clear all keyboard modifier states at start of each test - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - -- Reset FlexLove state - FlexLove.Gui.topElements = {} - FlexLove.Gui._focusedElement = nil - - -- Create a test input element - testElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - text = "Hello World", - }) -end - -function TestInputField:tearDown() - -- Clear all keyboard modifier states - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - -- Clear StateManager to prevent test contamination - StateManager.reset() - - testElement = nil - FlexLove.Gui.topElements = {} - FlexLove.Gui._focusedElement = nil -end - --- ==================== --- Focus Management Tests --- ==================== - -function TestInputField:testFocusElement() - -- Initially not focused - lu.assertFalse(testElement:isFocused()) - - -- Focus element - testElement:focus() - - -- Should be focused - lu.assertTrue(testElement:isFocused()) - lu.assertEquals(FlexLove.Gui._focusedElement, testElement) -end - -function TestInputField:testBlurElement() - -- Focus element first - testElement:focus() - lu.assertTrue(testElement:isFocused()) - - -- Blur element - testElement:blur() - - -- Should not be focused - lu.assertFalse(testElement:isFocused()) - lu.assertNil(FlexLove.Gui._focusedElement) -end - -function TestInputField:testFocusSwitchBetweenElements() - local element1 = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Element 1", - }) - - local element2 = FlexLove.Element.new({ - x = 10, - y = 50, - width = 100, - height = 30, - editable = true, - text = "Element 2", - }) - - -- Focus element1 - element1:focus() - lu.assertTrue(element1:isFocused()) - lu.assertFalse(element2:isFocused()) - - -- Focus element2 (should blur element1) - element2:focus() - lu.assertFalse(element1:isFocused()) - lu.assertTrue(element2:isFocused()) - lu.assertEquals(FlexLove.Gui._focusedElement, element2) -end - -function TestInputField:testSelectOnFocus() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Test Text", - selectOnFocus = true, - }) - - -- Focus element with selectOnFocus enabled - element:focus() - - -- Should select all text - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 9) -- "Test Text" has 9 characters -end - --- ==================== --- Cursor Management Tests --- ==================== - -function TestInputField:testSetCursorPosition() - testElement:focus() - - -- Set cursor to position 5 - testElement:setCursorPosition(5) - - lu.assertEquals(testElement:getCursorPosition(), 5) -end - -function TestInputField:testCursorPositionBounds() - testElement:focus() - - -- Try to set cursor beyond text length - testElement:setCursorPosition(999) - - -- Should clamp to text length - lu.assertEquals(testElement:getCursorPosition(), 11) -- "Hello World" has 11 characters - - -- Try to set negative cursor position - testElement:setCursorPosition(-5) - - -- Should clamp to 0 - lu.assertEquals(testElement:getCursorPosition(), 0) -end - -function TestInputField:testMoveCursor() - testElement:focus() - testElement:setCursorPosition(5) - - -- Move cursor right - testElement:moveCursorBy(2) - lu.assertEquals(testElement:getCursorPosition(), 7) - - -- Move cursor left - testElement:moveCursorBy(-3) - lu.assertEquals(testElement:getCursorPosition(), 4) -end - -function TestInputField:testMoveCursorToStartEnd() - testElement:focus() - testElement:setCursorPosition(5) - - -- Move to end - testElement:moveCursorToEnd() - lu.assertEquals(testElement:getCursorPosition(), 11) - - -- Move to start - testElement:moveCursorToStart() - lu.assertEquals(testElement:getCursorPosition(), 0) -end - --- ==================== --- Text Buffer Management Tests --- ==================== - -function TestInputField:testGetText() - lu.assertEquals(testElement:getText(), "Hello World") -end - -function TestInputField:testSetText() - testElement:setText("New Text") - - lu.assertEquals(testElement:getText(), "New Text") - lu.assertEquals(testElement.text, "New Text") -end - -function TestInputField:testInsertTextAtCursor() - testElement:focus() - testElement:setCursorPosition(5) -- After "Hello" - - testElement:insertText(" Beautiful") - - lu.assertEquals(testElement:getText(), "Hello Beautiful World") - lu.assertEquals(testElement:getCursorPosition(), 15) -- Cursor after inserted text -end - -function TestInputField:testInsertTextAtSpecificPosition() - testElement:insertText("Super ", 6) -- Before "World" - - lu.assertEquals(testElement:getText(), "Hello Super World") -end - -function TestInputField:testDeleteText() - testElement:deleteText(0, 6) -- Delete "Hello " - - lu.assertEquals(testElement:getText(), "World") -end - -function TestInputField:testReplaceText() - testElement:replaceText(0, 5, "Hi") -- Replace "Hello" with "Hi" - - lu.assertEquals(testElement:getText(), "Hi World") -end - -function TestInputField:testMaxLengthConstraint() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Test", - maxLength = 10, - }) - - element:focus() - element:moveCursorToEnd() - - -- Try to insert text that would exceed maxLength - element:insertText(" Very Long Text") - - -- Should not insert text that exceeds maxLength - lu.assertEquals(element:getText(), "Test") - - -- Insert text that fits within maxLength - element:insertText(" Text") - lu.assertEquals(element:getText(), "Test Text") -end - --- ==================== --- Selection Management Tests --- ==================== - -function TestInputField:testSetSelection() - testElement:setSelection(0, 5) -- Select "Hello" - - lu.assertTrue(testElement:hasSelection()) - local startPos, endPos = testElement:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 5) -end - -function TestInputField:testGetSelectedText() - testElement:setSelection(0, 5) -- Select "Hello" - - local selectedText = testElement:getSelectedText() - lu.assertEquals(selectedText, "Hello") -end - -function TestInputField:testSelectAll() - testElement:selectAll() - - lu.assertTrue(testElement:hasSelection()) - local startPos, endPos = testElement:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 11) -- Full text length -end - -function TestInputField:testClearSelection() - testElement:setSelection(0, 5) - lu.assertTrue(testElement:hasSelection()) - - testElement:clearSelection() - - lu.assertFalse(testElement:hasSelection()) - local startPos, endPos = testElement:getSelection() - lu.assertNil(startPos) - lu.assertNil(endPos) -end - -function TestInputField:testDeleteSelection() - testElement:focus() - testElement:setSelection(0, 6) -- Select "Hello " - - local deleted = testElement:deleteSelection() - - lu.assertTrue(deleted) - lu.assertEquals(testElement:getText(), "World") - lu.assertFalse(testElement:hasSelection()) - lu.assertEquals(testElement:getCursorPosition(), 0) -end - -function TestInputField:testMouseDownPositionSetOnUnfocusedElement() - -- Test that _mouseDownPosition is set on mouse press even when element is not focused - -- This is critical for text selection to work on the first click - - -- Ensure element is not focused - lu.assertFalse(testElement:isFocused()) - - -- Simulate mouse press at position (150, 120) inside the element - love.mouse.setPosition(150, 120) - love.mouse.setDown(1, true) - testElement:update(0.016) - - -- Verify that _mouseDownPosition was set - lu.assertNotNil(testElement._mouseDownPosition, - "_mouseDownPosition should be set on press even when unfocused") -end - -function TestInputField:testMouseDownPositionSetOnFocusedElement() - -- Test that _mouseDownPosition is set on mouse press when element is focused - - testElement:focus() - lu.assertTrue(testElement:isFocused()) - - -- Simulate mouse press at position (150, 120) - love.mouse.setPosition(150, 120) - love.mouse.setDown(1, true) - testElement:update(0.016) - - -- Verify that _mouseDownPosition was set - lu.assertNotNil(testElement._mouseDownPosition, - "_mouseDownPosition should be set on press when focused") -end - -function TestInputField:testMouseDownPositionNotSetOnNonEditableElement() - -- Test that _mouseDownPosition is NOT set for non-editable elements - - local nonEditableElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = false, - text = "Not Editable", - }) - - -- Simulate mouse press at position (150, 120) - love.mouse.setPosition(150, 120) - love.mouse.setDown(1, true) - nonEditableElement:update(0.016) - - -- Verify that _mouseDownPosition was NOT set - lu.assertNil(nonEditableElement._mouseDownPosition, - "_mouseDownPosition should not be set for non-editable elements") -end - -function TestInputField:testDragSelectionPreservedOnRelease() - -- Test that text selection created by dragging is preserved when releasing over the element - -- This is the fix for the bug where selections were dropped on mouse release - - testElement:focus() - - -- Simulate mouse press at position (110, 120) - love.mouse.setPosition(110, 120) - love.mouse.setDown(1, true) - testElement:update(0.016) - - -- Drag to another position to create a selection - love.mouse.setPosition(150, 120) - testElement:update(0.016) - - -- Verify selection exists after drag - lu.assertTrue(testElement:hasSelection(), "Selection should exist after drag") - - -- Release mouse while still over the element - love.mouse.setDown(1, false) - testElement:update(0.016) - - -- The key test: selection should STILL be there after release - lu.assertTrue(testElement:hasSelection(), - "Selection should be preserved after releasing mouse over element") -end - -function TestInputField:testClickWithoutDragClearsSelection() - -- Test that a click (press and release without drag) still clears selection as expected - - testElement:focus() - testElement:setSelection(0, 5) -- Select "Hello" - lu.assertTrue(testElement:hasSelection()) - - -- Click at a position (press and release without moving) - love.mouse.setPosition(120, 120) - love.mouse.setDown(1, true) - testElement:update(0.016) - - -- Release at same position (no drag occurred) - love.mouse.setDown(1, false) - testElement:update(0.016) - - -- Selection should be cleared since it was a click, not a drag - lu.assertFalse(testElement:hasSelection(), - "Selection should be cleared by click without drag") -end - -function TestInputField:testDragSelectionOnInitialClick() - -- Test that drag selection works even on the first interaction with an unfocused element - - local newElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - text = "Test Text", - }) - - lu.assertFalse(newElement:isFocused(), "Element should start unfocused") - - -- First interaction: press (will focus on release) - love.mouse.setPosition(110, 120) - love.mouse.setDown(1, true) - newElement:update(0.016) - - -- Release to complete the focus - love.mouse.setDown(1, false) - newElement:update(0.016) - - lu.assertTrue(newElement:isFocused(), "Element should be focused after click") - - -- Now perform a drag selection on the focused element - love.mouse.setPosition(110, 120) - love.mouse.setDown(1, true) - newElement:update(0.016) - - -- Drag to create selection - love.mouse.setPosition(150, 120) - newElement:update(0.016) - - lu.assertTrue(newElement:hasSelection(), "Selection should exist after drag") - - -- Release over element - love.mouse.setDown(1, false) - newElement:update(0.016) - - -- Selection should be preserved - lu.assertTrue(newElement:hasSelection(), - "Selection should be preserved after drag release") -end - --- ==================== --- Text Input Tests --- ==================== - -function TestInputField:testTextInput() - testElement:focus() - testElement:setCursorPosition(5) -- After "Hello" - - -- Simulate text input - testElement:textinput(",") - - lu.assertEquals(testElement:getText(), "Hello, World") - lu.assertEquals(testElement:getCursorPosition(), 6) -end - -function TestInputField:testTextInputWithSelection() - testElement:focus() - testElement:setSelection(0, 5) -- Select "Hello" - - -- Simulate text input (should replace selection) - testElement:textinput("Hi") - - lu.assertEquals(testElement:getText(), "Hi World") - lu.assertFalse(testElement:hasSelection()) - lu.assertEquals(testElement:getCursorPosition(), 2) -end - -function TestInputField:testTextInputCallbacks() - local inputCalled = false - local changeCalled = false - local inputText = nil - local newText = nil - local oldText = nil - - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Test", - onTextInput = function(_, text) - inputCalled = true - inputText = text - end, - onTextChange = function(_, new, old) - changeCalled = true - newText = new - oldText = old - end, - }) - - element:focus() - element:moveCursorToEnd() - element:textinput("!") - - lu.assertTrue(inputCalled) - lu.assertTrue(changeCalled) - lu.assertEquals(inputText, "!") - lu.assertEquals(newText, "Test!") - lu.assertEquals(oldText, "Test") -end - --- ==================== --- Keyboard Input Tests --- ==================== - -function TestInputField:testBackspaceKey() - testElement:focus() - testElement:setCursorPosition(5) -- After "Hello" - - -- Simulate backspace key - testElement:keypressed("backspace", nil, false) - - lu.assertEquals(testElement:getText(), "Hell World") - lu.assertEquals(testElement:getCursorPosition(), 4) -end - -function TestInputField:testDeleteKey() - testElement:focus() - testElement:setCursorPosition(5) -- After "Hello", before " " - - -- Simulate delete key - testElement:keypressed("delete", nil, false) - - lu.assertEquals(testElement:getText(), "HelloWorld") - lu.assertEquals(testElement:getCursorPosition(), 5) -end - -function TestInputField:testArrowKeys() - testElement:focus() - testElement:setCursorPosition(5) - - -- Right arrow - testElement:keypressed("right", nil, false) - lu.assertEquals(testElement:getCursorPosition(), 6) - - -- Left arrow - testElement:keypressed("left", nil, false) - lu.assertEquals(testElement:getCursorPosition(), 5) -end - -function TestInputField:testHomeEndKeys() - testElement:focus() - testElement:setCursorPosition(5) - - -- End key - testElement:keypressed("end", nil, false) - lu.assertEquals(testElement:getCursorPosition(), 11) - - -- Home key - testElement:keypressed("home", nil, false) - lu.assertEquals(testElement:getCursorPosition(), 0) -end - -function TestInputField:testEscapeKey() - testElement:focus() - testElement:setSelection(0, 5) - lu.assertTrue(testElement:hasSelection()) - - -- Escape should clear selection - testElement:keypressed("escape", nil, false) - - lu.assertFalse(testElement:hasSelection()) - lu.assertTrue(testElement:isFocused()) -- Still focused - - -- Another escape should blur - testElement:keypressed("escape", nil, false) - - lu.assertFalse(testElement:isFocused()) -end - -function TestInputField:testCtrlA() - testElement:focus() - - -- Simulate Ctrl+A (need to mock modifiers) - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - testElement:keypressed("a", "", false) - - lu.assertTrue(testElement:hasSelection()) - local startPos, endPos = testElement:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 11) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testEnterKeyMultiline() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 60, - editable = true, - multiline = true, - text = "Line 1", - }) - - element:focus() - element:moveCursorToEnd() - - -- Simulate Enter key - element:keypressed("return", nil, false) - - -- Should insert newline - lu.assertEquals(element:getText(), "Line 1\n") -end - -function TestInputField:testEnterKeySingleline() - local enterCalled = false - - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - multiline = false, - text = "Test", - onEnter = function() - enterCalled = true - end, - }) - - element:focus() - element:moveCursorToEnd() - - -- Simulate Enter key - element:keypressed("return", nil, false) - - -- Should trigger onEnter callback, not insert newline - lu.assertTrue(enterCalled) - lu.assertEquals(element:getText(), "Test") -end - --- ==================== --- Multi-line Tests --- ==================== - -function TestInputField:testMultilineTextSplitting() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 80, - editable = true, - multiline = true, - text = "Line 1\nLine 2\nLine 3", - }) - - -- Trigger line splitting - element._textEditor:_splitLines() - - lu.assertEquals(#element._textEditor._lines, 3) - lu.assertEquals(element._textEditor._lines[1], "Line 1") - lu.assertEquals(element._textEditor._lines[2], "Line 2") - lu.assertEquals(element._textEditor._lines[3], "Line 3") -end - --- ==================== --- UTF-8 Support Tests --- ==================== - -function TestInputField:testUTF8Characters() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello 世界", - }) - - element:focus() - element:moveCursorToEnd() - - -- Insert UTF-8 character - element:insertText("!") - - lu.assertEquals(element:getText(), "Hello 世界!") -end - -function TestInputField:testUTF8Selection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello 世界", - }) - - -- Select UTF-8 characters - element:setSelection(6, 8) -- Select "世界" - - local selected = element:getSelectedText() - lu.assertEquals(selected, "世界") -end - --- ==================== --- Password Mode Tests --- ==================== - -function TestInputField:testPasswordModeDisablesMultiline() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - multiline = true, - passwordMode = true, - text = "password", - }) - - -- Password mode should override multiline - lu.assertFalse(element.multiline) -end - --- ==================== --- Keyboard Selection Tests --- ==================== - -function TestInputField:testShiftRightSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(0) - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Shift+Right should select one character - element:keypressed("right", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 1) - - -- Another Shift+Right should extend selection - element:keypressed("right", nil, false) - lu.assertTrue(element:hasSelection()) - startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 2) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testShiftLeftSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(5) -- Position after "Hello" - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Shift+Left should select one character backwards - element:keypressed("left", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 4) - lu.assertEquals(endPos, 5) - - -- Another Shift+Left should extend selection - element:keypressed("left", nil, false) - lu.assertTrue(element:hasSelection()) - startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 3) - lu.assertEquals(endPos, 5) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testShiftHomeSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(5) - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Shift+Home should select from cursor to start - element:keypressed("home", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 5) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testShiftEndSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(5) - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Shift+End should select from cursor to end - element:keypressed("end", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 5) - lu.assertEquals(endPos, 11) -- "Hello World" has 11 characters - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testSelectionDirectionChange() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(5) - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Select right - element:keypressed("right", nil, false) - element:keypressed("right", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 5) - lu.assertEquals(endPos, 7) - - -- Now select left (should shrink selection) - element:keypressed("left", nil, false) - lu.assertTrue(element:hasSelection()) - startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 5) - lu.assertEquals(endPos, 6) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testArrowWithoutShiftClearsSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setSelection(0, 5) - lu.assertTrue(element:hasSelection()) - - -- Arrow key without Shift should clear selection and move cursor - element:keypressed("right", nil, false) - lu.assertFalse(element:hasSelection()) - lu.assertEquals(element._cursorPosition, 5) -- Should move to end of selection -end - -function TestInputField:testTypingReplacesSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setSelection(0, 5) -- Select "Hello" - - -- Type a character - should replace selection - element:textinput("X") - lu.assertEquals(element:getText(), "X World") - lu.assertFalse(element:hasSelection()) - lu.assertEquals(element._cursorPosition, 1) -end - --- ==================== --- Mouse Selection Tests --- ==================== - -function TestInputField:testMouseClickSetsCursorPosition() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - - -- Simulate single click (this would normally be done through the event system) - -- We'll test the _handleTextClick method directly - element:_handleTextClick(15, 15, 1) -- Single click near start - - -- Cursor should be set (exact position depends on font, so we just check it's valid) - lu.assertTrue(element._cursorPosition >= 0) - lu.assertTrue(element._cursorPosition <= 11) - lu.assertFalse(element:hasSelection()) -end - -function TestInputField:testMouseDoubleClickSelectsWord() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setCursorPosition(3) -- Position in "Hello" - - -- Simulate double click to select word - element:_handleTextClick(15, 15, 2) -- Double click - - -- Should have selected a word (we can't test exact positions without font metrics) - -- But we can verify a selection was created - lu.assertTrue(element:hasSelection() or element._cursorPosition >= 0) -end - -function TestInputField:testMouseTripleClickSelectsAll() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - - -- Simulate triple click - element:_handleTextClick(15, 15, 3) -- Triple click - - -- Should select all text - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 11) -end - -function TestInputField:testMouseDragCreatesSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - - -- Simulate mouse down at position 0 - element._mouseDownPosition = 0 - - -- Simulate drag to position 5 - element:_handleTextDrag(50, 15) - - -- Should have created a selection (exact positions depend on font metrics) - -- We just verify the drag handler works - lu.assertTrue(element._cursorPosition >= 0) -end - -function TestInputField:testSelectWordAtPosition() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World Test", - }) - - element:focus() - - -- Select word at position 6 (in "World") - element:_selectWordAtPosition(6) - - -- Should have selected "World" - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 6) - lu.assertEquals(endPos, 11) - lu.assertEquals(element:getSelectedText(), "World") -end - -function TestInputField:testSelectWordWithNonAlphanumeric() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello, World!", - }) - - element:focus() - - -- Select word at position 0 (in "Hello") - element:_selectWordAtPosition(2) - - -- Should have selected "Hello" (not including comma) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 5) - lu.assertEquals(element:getSelectedText(), "Hello") -end - -function TestInputField:testMouseToTextPosition() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello", - }) - - element:focus() - - -- Test conversion at start of element - local pos = element._textEditor:mouseToTextPosition(10, 10) - lu.assertEquals(pos, 0) - - -- Test conversion far to the right (should be at end) - pos = element._textEditor:mouseToTextPosition(200, 10) - lu.assertEquals(pos, 5) -- "Hello" has 5 characters -end - --- ==================== --- Clipboard Operations Tests --- ==================== - -function TestInputField:testCtrlCCopiesSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setSelection(0, 5) -- Select "Hello" - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+C - element:keypressed("c", nil, false) - - -- Check clipboard content - lu.assertEquals(love.system.getClipboardText(), "Hello") - - -- Text should remain unchanged - lu.assertEquals(element:getText(), "Hello World") - lu.assertTrue(element:hasSelection()) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlXCutsSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setSelection(0, 5) -- Select "Hello" - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+X - element:keypressed("x", nil, false) - - -- Check clipboard content - lu.assertEquals(love.system.getClipboardText(), "Hello") - - -- Text should be cut - lu.assertEquals(element:getText(), " World") - lu.assertFalse(element:hasSelection()) - lu.assertEquals(element._cursorPosition, 0) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlVPastesFromClipboard() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "World", - }) - - element:focus() - element:setCursorPosition(0) - - -- Set clipboard content - love.system.setClipboardText("Hello ") - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+V - element:keypressed("v", nil, false) - - -- Text should be pasted - lu.assertEquals(element:getText(), "Hello World") - lu.assertEquals(element._cursorPosition, 6) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlVReplacesSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - element:setSelection(6, 11) -- Select "World" - - -- Set clipboard content - love.system.setClipboardText("Everyone") - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+V - element:keypressed("v", nil, false) - - -- Selection should be replaced - lu.assertEquals(element:getText(), "Hello Everyone") - lu.assertFalse(element:hasSelection()) - lu.assertEquals(element._cursorPosition, 14) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCopyWithoutSelectionDoesNothing() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - - -- Clear clipboard - love.system.setClipboardText("") - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+C without selection - element:keypressed("c", nil, false) - - -- Clipboard should remain empty - lu.assertEquals(love.system.getClipboardText(), "") - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCutWithoutSelectionDoesNothing() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World", - }) - - element:focus() - - -- Clear clipboard - love.system.setClipboardText("") - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+X without selection - element:keypressed("x", nil, false) - - -- Clipboard should remain empty and text unchanged - lu.assertEquals(love.system.getClipboardText(), "") - lu.assertEquals(element:getText(), "Hello World") - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testPasteEmptyClipboard() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello", - }) - - element:focus() - element:setCursorPosition(5) - - -- Clear clipboard - love.system.setClipboardText("") - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Simulate Ctrl+V with empty clipboard - element:keypressed("v", nil, false) - - -- Text should remain unchanged - lu.assertEquals(element:getText(), "Hello") - lu.assertEquals(element._cursorPosition, 5) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - --- ==================== --- Word Navigation Tests --- ==================== - -function TestInputField:testCtrlLeftMovesToPreviousWord() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World Test", - }) - - element:focus() - element:setCursorPosition(16) -- At end of text - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Ctrl+Left should move to start of "Test" - element:keypressed("left", nil, false) - lu.assertEquals(element._cursorPosition, 12) - - -- Another Ctrl+Left should move to start of "World" - element:keypressed("left", nil, false) - lu.assertEquals(element._cursorPosition, 6) - - -- Another Ctrl+Left should move to start of "Hello" - element:keypressed("left", nil, false) - lu.assertEquals(element._cursorPosition, 0) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlRightMovesToNextWord() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World Test", - }) - - element:focus() - element:setCursorPosition(0) -- At start of text - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Ctrl+Right should move to start of "World" - element:keypressed("right", nil, false) - lu.assertEquals(element._cursorPosition, 6) - - -- Another Ctrl+Right should move to start of "Test" - element:keypressed("right", nil, false) - lu.assertEquals(element._cursorPosition, 12) - - -- Another Ctrl+Right should move to end - element:keypressed("right", nil, false) - lu.assertEquals(element._cursorPosition, 16) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlShiftLeftSelectsWord() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World Test", - }) - - element:focus() - element:setCursorPosition(16) -- At end of text - - -- Mock Ctrl+Shift keys - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" or key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Ctrl+Shift+Left should select "Test" - element:keypressed("left", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 12) - lu.assertEquals(endPos, 16) - lu.assertEquals(element:getSelectedText(), "Test") - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testCtrlShiftRightSelectsWord() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello World Test", - }) - - element:focus() - element:setCursorPosition(0) -- At start of text - - -- Mock Ctrl+Shift keys - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" or key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Ctrl+Shift+Right should select "Hello" - element:keypressed("right", nil, false) - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - lu.assertEquals(startPos, 0) - lu.assertEquals(endPos, 6) - lu.assertEquals(element:getSelectedText(), "Hello ") - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testWordNavigationWithPunctuation() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 100, - height = 30, - editable = true, - text = "Hello, World! Test.", - }) - - element:focus() - element:setCursorPosition(0) - - -- Mock Ctrl key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lctrl" or key == "rctrl" then - return true - end - end - return false - end - - -- Ctrl+Right should skip punctuation and move to "World" - element:keypressed("right", nil, false) - lu.assertEquals(element._cursorPosition, 7) - - -- Another Ctrl+Right should move to "Test" - element:keypressed("right", nil, false) - lu.assertEquals(element._cursorPosition, 14) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - --- ==================== --- Text Scrolling Tests --- ==================== - -function TestInputField:testTextScrollInitiallyZero() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, -- Small width to force scrolling - height = 30, - editable = true, - text = "", - }) - - element:focus() - - -- Initial scroll should be 0 - lu.assertEquals(element._textScrollX, 0) -end - -function TestInputField:testTextScrollUpdatesOnCursorMove() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, -- Small width to force scrolling - height = 30, - editable = true, - text = "This is a very long text that will overflow", - }) - - element:focus() - element:setCursorPosition(0) - - -- Scroll should be 0 at start - lu.assertEquals(element._textScrollX, 0) - - -- Move cursor to end - element:moveCursorToEnd() - - -- Scroll should have increased to keep cursor visible - lu.assertTrue(element._textScrollX > 0) -end - -function TestInputField:testTextScrollKeepsCursorVisible() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, -- Small width - height = 30, - editable = true, - text = "", - }) - - element:focus() - element:setCursorPosition(0) - - -- Set long text directly - element:setText("This is a very long text that will definitely overflow the bounds") - element:moveCursorToEnd() - - -- Cursor should be at end and scroll should be adjusted - lu.assertTrue(element._textScrollX > 0) - - -- Move cursor back to start - element:moveCursorToStart() - - -- Scroll should reset to 0 - lu.assertEquals(element._textScrollX, 0) -end - -function TestInputField:testTextScrollWithSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, - height = 30, - editable = true, - text = "This is a very long text for testing", - }) - - element:focus() - element:setCursorPosition(0) - - -- Move to end and check scroll - element:moveCursorToEnd() - local scrollAtEnd = element._textScrollX - lu.assertTrue(scrollAtEnd > 0) - - -- Select from end backwards - element:setSelection(20, 37) - element._cursorPosition = 20 - element:_updateTextScroll() - - -- Scroll should adjust to show cursor at position 20 - lu.assertTrue(element._textScrollX < scrollAtEnd) -end - -function TestInputField:testTextScrollDoesNotAffectMultiline() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, - height = 60, - editable = true, - multiline = true, - text = "This is a very long text", - }) - - element:focus() - element:moveCursorToEnd() - - -- Multiline should not use horizontal scroll - lu.assertEquals(element._textScrollX, 0) -end - -function TestInputField:testTextScrollResetsOnClear() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, - height = 30, - editable = true, - text = "This is a very long text that overflows", - }) - - element:focus() - element:moveCursorToEnd() - - -- Should have scrolled - lu.assertTrue(element._textScrollX > 0) - - -- Clear text - element:setText("") - element:setCursorPosition(0) - - -- Scroll should reset - lu.assertEquals(element._textScrollX, 0) -end - -function TestInputField:testTextScrollWithBackspace() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 50, - height = 30, - editable = true, - text = "XXXXXXXXXXXXXXXXXXXXXXXXXX", -- Long text - }) - - element:focus() - element:moveCursorToEnd() - - local initialScroll = element._textScrollX - lu.assertTrue(initialScroll > 0) - - -- Delete characters from end - element:keypressed("backspace", nil, false) - element:keypressed("backspace", nil, false) - element:keypressed("backspace", nil, false) - - -- Scroll should decrease as text gets shorter - lu.assertTrue(element._textScrollX <= initialScroll) -end - --- ==================== --- Multiline Text Selection Tests --- ==================== - -function TestInputField:testMultilineMouseToTextPositionBasic() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "Line 1\nLine 2\nLine 3", - }) - - element:focus() - - -- Get font to calculate positions - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Click at start (should be position 0) - local pos = element._textEditor:mouseToTextPosition(10, 10) - lu.assertEquals(pos, 0) - - -- Click on second line start (should be after "Line 1\n" = position 7) - pos = element._textEditor:mouseToTextPosition(10, 10 + lineHeight) - lu.assertEquals(pos, 7) - - -- Click on third line start (should be after "Line 1\nLine 2\n" = position 14) - pos = element._textEditor:mouseToTextPosition(10, 10 + lineHeight * 2) - lu.assertEquals(pos, 14) -end - -function TestInputField:testMultilineMouseToTextPositionXCoordinate() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "ABC\nDEF\nGHI", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - local charWidth = font:getWidth("A") - - -- Click in middle of first line (should be around position 1-2) - local pos = element._textEditor:mouseToTextPosition(10 + charWidth * 1.5, 10) - lu.assertTrue(pos >= 1 and pos <= 2) - - -- Click at end of second line (should be around position 6-7) - -- Text is "ABC\nDEF\nGHI", so second line "DEF" ends at position 6 or 7 (newline) - pos = element._textEditor:mouseToTextPosition(10 + charWidth * 3, 10 + lineHeight) - lu.assertTrue(pos >= 6 and pos <= 7) -end - -function TestInputField:testMultilineMouseDragSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "Line 1\nLine 2\nLine 3", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Simulate mouse click on first line (sets _mouseDownPosition) - element:_handleTextClick(10, 10, 1) - lu.assertEquals(element._cursorPosition, 0) - lu.assertFalse(element:hasSelection()) - - -- Drag to second line - element:_handleTextDrag(50, 10 + lineHeight) - lu.assertTrue(element:hasSelection()) - - -- Selection should span from first line to second line - local startPos, endPos = element:getSelection() - lu.assertTrue(startPos == 0 or endPos == 0) - lu.assertTrue(startPos > 6 or endPos > 6) -- Past first newline - - -- After drag, selection should be preserved - lu.assertTrue(element:hasSelection()) -end - -function TestInputField:testMultilineMouseDragAcrossThreeLines() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 150, - editable = true, - multiline = true, - text = "First\nSecond\nThird\nFourth", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Click on first line, then drag to third line - element:_handleTextClick(10, 10, 1) - element:_handleTextDrag(50, 10 + lineHeight * 2.5) - - lu.assertTrue(element:hasSelection()) - local startPos, endPos = element:getSelection() - - -- Should select across multiple lines - local minPos = math.min(startPos, endPos) - local maxPos = math.max(startPos, endPos) - lu.assertEquals(minPos, 0) -- From start - lu.assertTrue(maxPos > 12) -- Past "First\nSecond\n" - - -- Selection should persist after drag - lu.assertTrue(element:hasSelection()) -end - -function TestInputField:testMultilineClickOnDifferentLines() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "AAA\nBBB\nCCC", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Click on first line - element:_handleTextClick(10, 10, 1) - local pos1 = element._cursorPosition - lu.assertEquals(pos1, 0) - - -- Click on second line - element:_handleTextClick(10, 10 + lineHeight, 1) - local pos2 = element._cursorPosition - lu.assertEquals(pos2, 4) -- After "AAA\n" - - -- Click on third line - element:_handleTextClick(10, 10 + lineHeight * 2, 1) - local pos3 = element._cursorPosition - lu.assertEquals(pos3, 8) -- After "AAA\nBBB\n" -end - -function TestInputField:testMultilineSelectionWithKeyboard() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "Line 1\nLine 2\nLine 3", - }) - - element:focus() - element:setCursorPosition(0) - - -- Mock Shift key - local oldIsDown = _G.love.keyboard.isDown - _G.love.keyboard.isDown = function(...) - local keys = {...} - for _, key in ipairs(keys) do - if key == "lshift" or key == "rshift" then - return true - end - end - return false - end - - -- Test Shift+Right selection (horizontal movement works) - element:keypressed("right", nil, false) - lu.assertTrue(element:hasSelection()) - - local startPos, endPos = element:getSelection() - lu.assertTrue(math.abs(endPos - startPos) > 0) - - -- Reset mock - _G.love.keyboard.isDown = oldIsDown -end - -function TestInputField:testMultilineMouseSelectionPreservedAfterRelease() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "First line\nSecond line\nThird line", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Create a selection by dragging - element:_handleTextClick(10, 10, 1) - element:_handleTextDrag(100, 10 + lineHeight * 1.5) - - local startPos, endPos = element:getSelection() - local hadSelection = element:hasSelection() - - lu.assertTrue(hadSelection) - - -- Note: There's no mouse release handler that affects selection - -- The drag creates the selection and it persists - - -- Selection should still exist - lu.assertTrue(element:hasSelection()) - local startPos2, endPos2 = element:getSelection() - lu.assertEquals(startPos, startPos2) - lu.assertEquals(endPos, endPos2) -end - -function TestInputField:testMultilineClickDoesNotPreserveSelection() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 100, - editable = true, - multiline = true, - text = "First line\nSecond line", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Create a selection - element:setSelection(0, 5) - lu.assertTrue(element:hasSelection()) - - -- Click somewhere else (should clear selection) - element:_handleTextClick(10, 10 + lineHeight, 1) - - -- Selection should be cleared - lu.assertFalse(element:hasSelection()) -end - -function TestInputField:testMultilineEmptyLinesHandling() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 150, - editable = true, - multiline = true, - text = "Line 1\n\nLine 3", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Click on empty line (second line) - local pos = element._textEditor:mouseToTextPosition(10, 10 + lineHeight) - lu.assertEquals(pos, 7) -- After "Line 1\n" - - -- Click on third line - pos = element._textEditor:mouseToTextPosition(10, 10 + lineHeight * 2) - lu.assertEquals(pos, 8) -- After "Line 1\n\n" -end - -function TestInputField:testMultilineYCoordinateBeyondText() - local element = FlexLove.Element.new({ - x = 10, - y = 10, - width = 300, - height = 200, - editable = true, - multiline = true, - text = "Line 1\nLine 2", - }) - - element:focus() - - local font = element:_getFont() - local lineHeight = font:getHeight() - - -- Click way below the text (should clamp to last line) - local pos = element._textEditor:mouseToTextPosition(10, 10 + lineHeight * 10) - local textLen = utf8.len(element.text) - - -- Should be at or near end of text - lu.assertTrue(pos >= textLen - 6) -- Within last line -end - --- Run tests -lu.LuaUnit.run() diff --git a/testing/__tests__/34_password_mode_tests.lua b/testing/__tests__/34_password_mode_tests.lua deleted file mode 100644 index cbb1a0c..0000000 --- a/testing/__tests__/34_password_mode_tests.lua +++ /dev/null @@ -1,438 +0,0 @@ --- ==================== --- Password Mode Tests --- ==================== --- Test suite for password mode functionality in FlexLove input fields - -local lu = require("testing.luaunit") -local loveStub = require("testing.loveStub") -local utf8 = require("utf8") - --- Setup LÖVE environment -_G.love = loveStub - --- Load FlexLove after setting up love stub -local FlexLove = require("FlexLove") -local StateManager = require("modules.StateManager") - --- Test fixtures -local testElement - -TestPasswordMode = {} - -function TestPasswordMode:setUp() - -- Clear all keyboard modifier states at start of each test - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - -- Reset FlexLove state - FlexLove.Gui.topElements = {} - FlexLove.Gui._focusedElement = nil - - -- Create a test password input element - testElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - passwordMode = true, - text = "", - }) -end - -function TestPasswordMode:tearDown() - -- Clear all keyboard modifier states - love.keyboard.setDown("lshift", false) - love.keyboard.setDown("rshift", false) - love.keyboard.setDown("lctrl", false) - love.keyboard.setDown("rctrl", false) - love.keyboard.setDown("lalt", false) - love.keyboard.setDown("ralt", false) - love.keyboard.setDown("lgui", false) - love.keyboard.setDown("rgui", false) - - -- Clear StateManager to prevent test contamination - StateManager.reset() - - testElement = nil - FlexLove.Gui.topElements = {} - FlexLove.Gui._focusedElement = nil -end - --- ==================== --- Property Tests --- ==================== - -function TestPasswordMode:testPasswordModePropertyExists() - -- Test that passwordMode property exists and can be set - lu.assertNotNil(testElement.passwordMode) - lu.assertTrue(testElement.passwordMode) -end - -function TestPasswordMode:testPasswordModeDefaultIsFalse() - -- Test that passwordMode defaults to false - local normalElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - text = "Normal text", - }) - - lu.assertFalse(normalElement.passwordMode or false) -end - -function TestPasswordMode:testPasswordModeIsSingleLineOnly() - -- Password mode should only work with single-line inputs - -- The constraint is enforced in Element.lua line 292-293 - local multilinePassword = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 100, - editable = true, - multiline = true, - passwordMode = true, - text = "Password", - }) - - -- Based on the constraint, multiline should be set to false - lu.assertFalse(multilinePassword.multiline) -end - --- ==================== --- Text Buffer Tests --- ==================== - -function TestPasswordMode:testActualTextContentRemains() - -- Insert text into password field - testElement:focus() - testElement:insertText("S") - testElement:insertText("e") - testElement:insertText("c") - testElement:insertText("r") - testElement:insertText("e") - testElement:insertText("t") - - -- Verify actual text buffer contains the real text - lu.assertEquals(testElement._textBuffer, "Secret") - lu.assertEquals(testElement:getText(), "Secret") -end - -function TestPasswordMode:testPasswordTextIsNotModified() - -- Set initial text - testElement:setText("MyPassword123") - - -- The actual buffer should contain the real password - lu.assertEquals(testElement._textBuffer, "MyPassword123") - lu.assertEquals(testElement:getText(), "MyPassword123") -end - --- ==================== --- Cursor Position Tests --- ==================== - -function TestPasswordMode:testCursorPositionWithPasswordMode() - testElement:setText("test") - testElement:focus() - - -- Set cursor to end - testElement:setCursorPosition(4) - lu.assertEquals(testElement._cursorPosition, 4) - - -- Move cursor to middle - testElement:setCursorPosition(2) - lu.assertEquals(testElement._cursorPosition, 2) - - -- Move cursor to start - testElement:setCursorPosition(0) - lu.assertEquals(testElement._cursorPosition, 0) -end - -function TestPasswordMode:testCursorMovementInPasswordField() - testElement:setText("password") - testElement:focus() - testElement:setCursorPosition(0) - - -- Move right - testElement:moveCursorBy(1) - lu.assertEquals(testElement._cursorPosition, 1) - - -- Move right again - testElement:moveCursorBy(1) - lu.assertEquals(testElement._cursorPosition, 2) - - -- Move left - testElement:moveCursorBy(-1) - lu.assertEquals(testElement._cursorPosition, 1) -end - --- ==================== --- Text Editing Tests --- ==================== - -function TestPasswordMode:testInsertTextInPasswordMode() - testElement:focus() - testElement:setCursorPosition(0) - - testElement:insertText("a") - lu.assertEquals(testElement._textBuffer, "a") - - testElement:insertText("b") - lu.assertEquals(testElement._textBuffer, "ab") - - testElement:insertText("c") - lu.assertEquals(testElement._textBuffer, "abc") -end - -function TestPasswordMode:testBackspaceInPasswordMode() - testElement:setText("password") - testElement:focus() - testElement:setCursorPosition(8) -- End of text - - -- Delete last character - testElement:keypressed("backspace", nil, false) - lu.assertEquals(testElement._textBuffer, "passwor") - - -- Delete another character - testElement:keypressed("backspace", nil, false) - lu.assertEquals(testElement._textBuffer, "passwo") -end - -function TestPasswordMode:testDeleteInPasswordMode() - testElement:setText("password") - testElement:focus() - testElement:setCursorPosition(0) -- Start of text - - -- Delete first character - testElement:keypressed("delete", nil, false) - lu.assertEquals(testElement._textBuffer, "assword") - - -- Delete another character - testElement:keypressed("delete", nil, false) - lu.assertEquals(testElement._textBuffer, "ssword") -end - -function TestPasswordMode:testInsertTextAtPosition() - testElement:setText("pass") - testElement:focus() - testElement:setCursorPosition(2) -- Between 'pa' and 'ss' - - testElement:insertText("x") - lu.assertEquals(testElement._textBuffer, "paxss") - lu.assertEquals(testElement._cursorPosition, 3) -end - --- ==================== --- Selection Tests --- ==================== - -function TestPasswordMode:testTextSelectionInPasswordMode() - testElement:setText("password") - testElement:focus() - - -- Select from position 2 to 5 - testElement:setSelection(2, 5) - - local selStart, selEnd = testElement:getSelection() - lu.assertEquals(selStart, 2) - lu.assertEquals(selEnd, 5) - lu.assertTrue(testElement:hasSelection()) -end - -function TestPasswordMode:testDeleteSelectionInPasswordMode() - testElement:setText("password") - testElement:focus() - - -- Select "sswo" (positions 2-6) - testElement:setSelection(2, 6) - - -- Delete selection - testElement:deleteSelection() - lu.assertEquals(testElement._textBuffer, "pard") - lu.assertFalse(testElement:hasSelection()) -end - -function TestPasswordMode:testReplaceSelectionInPasswordMode() - testElement:setText("password") - testElement:focus() - - -- Select "sswo" (positions 2-6) - testElement:setSelection(2, 6) - - -- Type new text (should replace selection) - testElement:textinput("X") - lu.assertEquals(testElement._textBuffer, "paXrd") -end - -function TestPasswordMode:testSelectAllInPasswordMode() - testElement:setText("secret") - testElement:focus() - - testElement:selectAll() - - local selStart, selEnd = testElement:getSelection() - lu.assertEquals(selStart, 0) - lu.assertEquals(selEnd, 6) - lu.assertTrue(testElement:hasSelection()) -end - --- ==================== --- Integration Tests --- ==================== - -function TestPasswordMode:testPasswordModeWithMaxLength() - testElement.maxLength = 5 - testElement:focus() - - testElement:insertText("1") - testElement:insertText("2") - testElement:insertText("3") - testElement:insertText("4") - testElement:insertText("5") - testElement:insertText("6") -- Should be rejected - - lu.assertEquals(testElement._textBuffer, "12345") - lu.assertEquals(utf8.len(testElement._textBuffer), 5) -end - -function TestPasswordMode:testPasswordModeWithPlaceholder() - local passwordWithPlaceholder = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - passwordMode = true, - placeholder = "Enter password", - text = "", - }) - - -- When empty and not focused, placeholder should be available - lu.assertEquals(passwordWithPlaceholder.placeholder, "Enter password") - lu.assertEquals(passwordWithPlaceholder._textBuffer, "") - - -- When text is added, actual text should be stored - passwordWithPlaceholder:focus() - passwordWithPlaceholder:insertText("secret") - lu.assertEquals(passwordWithPlaceholder._textBuffer, "secret") -end - -function TestPasswordMode:testPasswordModeClearText() - testElement:setText("password123") - lu.assertEquals(testElement._textBuffer, "password123") - - -- Clear text - testElement:setText("") - lu.assertEquals(testElement._textBuffer, "") - lu.assertEquals(testElement:getText(), "") -end - -function TestPasswordMode:testPasswordModeToggle() - -- Start with password mode off - local toggleElement = FlexLove.Element.new({ - x = 100, - y = 100, - width = 200, - height = 40, - editable = true, - passwordMode = false, - text = "visible", - }) - - lu.assertEquals(toggleElement._textBuffer, "visible") - lu.assertFalse(toggleElement.passwordMode) - - -- Enable password mode - toggleElement.passwordMode = true - lu.assertTrue(toggleElement.passwordMode) - - -- Text buffer should remain unchanged - lu.assertEquals(toggleElement._textBuffer, "visible") - - -- Disable password mode again - toggleElement.passwordMode = false - lu.assertFalse(toggleElement.passwordMode) - lu.assertEquals(toggleElement._textBuffer, "visible") -end - --- ==================== --- UTF-8 Support Tests --- ==================== - -function TestPasswordMode:testPasswordModeWithUTF8Characters() - testElement:focus() - - -- Insert UTF-8 characters - testElement:insertText("h") - testElement:insertText("é") - testElement:insertText("l") - testElement:insertText("l") - testElement:insertText("ö") - - -- Text buffer should contain actual UTF-8 text - lu.assertEquals(testElement._textBuffer, "héllö") - lu.assertEquals(utf8.len(testElement._textBuffer), 5) -end - -function TestPasswordMode:testPasswordModeCursorWithUTF8() - testElement:setText("café") - testElement:focus() - - -- Move cursor through UTF-8 text - testElement:setCursorPosition(0) - lu.assertEquals(testElement._cursorPosition, 0) - - testElement:moveCursorBy(1) - lu.assertEquals(testElement._cursorPosition, 1) - - testElement:moveCursorBy(1) - lu.assertEquals(testElement._cursorPosition, 2) - - testElement:setCursorPosition(4) - lu.assertEquals(testElement._cursorPosition, 4) -end - --- ==================== --- Edge Cases --- ==================== - -function TestPasswordMode:testPasswordModeWithEmptyString() - testElement:setText("") - lu.assertEquals(testElement._textBuffer, "") - lu.assertEquals(testElement:getText(), "") -end - -function TestPasswordMode:testPasswordModeWithSingleCharacter() - testElement:setText("x") - lu.assertEquals(testElement._textBuffer, "x") - lu.assertEquals(utf8.len(testElement._textBuffer), 1) -end - -function TestPasswordMode:testPasswordModeWithLongPassword() - local longPassword = string.rep("a", 100) - testElement:setText(longPassword) - - lu.assertEquals(testElement._textBuffer, longPassword) - lu.assertEquals(utf8.len(testElement._textBuffer), 100) -end - -function TestPasswordMode:testPasswordModeSetTextUpdatesBuffer() - testElement:setText("initial") - lu.assertEquals(testElement._textBuffer, "initial") - - testElement:setText("updated") - lu.assertEquals(testElement._textBuffer, "updated") - - testElement:setText("") - lu.assertEquals(testElement._textBuffer, "") -end - -lu.LuaUnit.run() diff --git a/testing/__tests__/35_stable_id_generation_tests.lua b/testing/__tests__/35_stable_id_generation_tests.lua deleted file mode 100644 index d1fb163..0000000 --- a/testing/__tests__/35_stable_id_generation_tests.lua +++ /dev/null @@ -1,423 +0,0 @@ --- Test: Stable ID Generation in Immediate Mode -package.path = package.path .. ";./?.lua;./game/?.lua;./game/utils/?.lua;./game/components/?.lua;./game/systems/?.lua" - -local luaunit = require("testing.luaunit") -require("testing.loveStub") -- Required to mock LOVE functions -local FlexLove = require("FlexLove") - -local Gui = FlexLove.Gui - -TestStableIDGeneration = {} - -function TestStableIDGeneration:setUp() - -- Reset GUI state - if Gui.destroy then - Gui.destroy() - end - - -- Initialize with immediate mode enabled - Gui.init({ - baseScale = { width = 1920, height = 1080 }, - immediateMode = true, - }) -end - -function TestStableIDGeneration:tearDown() - -- Clear all states - if Gui.clearAllStates then - Gui.clearAllStates() - end - - -- Reset immediate mode state - if Gui._immediateModeState then - Gui._immediateModeState.reset() - end - - if Gui.destroy then - Gui.destroy() - end - - -- Reset immediate mode flag - Gui._immediateMode = false - Gui._frameNumber = 0 -end - -function TestStableIDGeneration:test_child_ids_stable_across_frames() - -- Frame 1: Create parent with children - Gui.beginFrame() - - local parent = Gui.new({ - id = "test_parent", - width = 400, - height = 300, - }) - - local child1 = Gui.new({ - parent = parent, - width = 100, - height = 50, - text = "Child 1", - }) - - local child2 = Gui.new({ - parent = parent, - width = 100, - height = 50, - text = "Child 2", - }) - - local child1Id = child1.id - local child2Id = child2.id - - Gui.endFrame() - - -- Frame 2: Recreate same structure - Gui.beginFrame() - - local parent2 = Gui.new({ - id = "test_parent", - width = 400, - height = 300, - }) - - local child1_2 = Gui.new({ - parent = parent2, - width = 100, - height = 50, - text = "Child 1", - }) - - local child2_2 = Gui.new({ - parent = parent2, - width = 100, - height = 50, - text = "Child 2", - }) - - Gui.endFrame() - - -- IDs should be stable - luaunit.assertEquals(child1_2.id, child1Id, "Child 1 ID should be stable across frames") - luaunit.assertEquals(child2_2.id, child2Id, "Child 2 ID should be stable across frames") -end - -function TestStableIDGeneration:test_conditional_rendering_does_not_affect_siblings() - -- Frame 1: Create parent with 3 children - Gui.beginFrame() - - local parent1 = Gui.new({ - id = "test_parent2", - width = 400, - height = 300, - }) - - local child1 = Gui.new({ - parent = parent1, - width = 100, - height = 50, - text = "Child 1", - }) - - local child2 = Gui.new({ - parent = parent1, - width = 100, - height = 50, - text = "Child 2", - }) - - local child3 = Gui.new({ - parent = parent1, - width = 100, - height = 50, - text = "Child 3", - }) - - local child1Id = child1.id - local child3Id = child3.id - - Gui.endFrame() - - -- Frame 2: Skip child 2 (conditional rendering) - Gui.beginFrame() - - local parent2 = Gui.new({ - id = "test_parent2", - width = 400, - height = 300, - }) - - local child1_2 = Gui.new({ - parent = parent2, - width = 100, - height = 50, - text = "Child 1", - }) - - -- Child 2 not rendered this frame - - local child3_2 = Gui.new({ - parent = parent2, - width = 100, - height = 50, - text = "Child 3", - }) - - Gui.endFrame() - - -- Child 1 should keep its ID - luaunit.assertEquals(child1_2.id, child1Id, "Child 1 ID should remain stable") - - -- Child 3 will have a different ID because it's now at sibling index 1 instead of 2 - -- This is EXPECTED behavior - the position in the tree changed - luaunit.assertNotEquals(child3_2.id, child3Id, "Child 3 ID changes because its sibling position changed") -end - -function TestStableIDGeneration:test_input_field_maintains_state_across_frames() - -- Frame 1: Create input field and simulate text entry - Gui.beginFrame() - - local container = Gui.new({ - id = "test_container", - width = 400, - height = 300, - }) - - local input1 = Gui.new({ - parent = container, - width = 200, - height = 40, - editable = true, - text = "", - }) - - -- Simulate text input - input1._textBuffer = "Hello World" - input1._focused = true - - local inputId = input1.id - - Gui.endFrame() - - -- Frame 2: Recreate same structure - Gui.beginFrame() - - local container2 = Gui.new({ - id = "test_container", - width = 400, - height = 300, - }) - - local input2 = Gui.new({ - parent = container2, - width = 200, - height = 40, - editable = true, - text = "", - }) - - Gui.endFrame() - - -- Input should have same ID and restored state - luaunit.assertEquals(input2.id, inputId, "Input field ID should be stable") - luaunit.assertEquals(input2._textBuffer, "Hello World", "Input text should be restored") - luaunit.assertTrue(input2._focused, "Input focus state should be restored") -end - -function TestStableIDGeneration:test_nested_children_stable_ids() - -- Frame 1: Create nested hierarchy - Gui.beginFrame() - - local root = Gui.new({ - id = "test_root", - width = 400, - height = 300, - }) - - local level1 = Gui.new({ - parent = root, - width = 300, - height = 200, - }) - - local level2 = Gui.new({ - parent = level1, - width = 200, - height = 100, - }) - - local deepChild = Gui.new({ - parent = level2, - width = 100, - height = 50, - text = "Deep Child", - }) - - local deepChildId = deepChild.id - - Gui.endFrame() - - -- Frame 2: Recreate same nested structure - Gui.beginFrame() - - local root2 = Gui.new({ - id = "test_root", - width = 400, - height = 300, - }) - - local level1_2 = Gui.new({ - parent = root2, - width = 300, - height = 200, - }) - - local level2_2 = Gui.new({ - parent = level1_2, - width = 200, - height = 100, - }) - - local deepChild2 = Gui.new({ - parent = level2_2, - width = 100, - height = 50, - text = "Deep Child", - }) - - Gui.endFrame() - - -- Deep child ID should be stable - luaunit.assertEquals(deepChild2.id, deepChildId, "Deeply nested child ID should be stable") -end - -function TestStableIDGeneration:test_siblings_with_different_props_have_different_ids() - -- Frame 1: Create siblings with different properties - Gui.beginFrame() - - local parent = Gui.new({ - width = 400, - height = 300, - }) - - local child1 = Gui.new({ - parent = parent, - width = 100, - height = 50, - text = "Button 1", - }) - - local child2 = Gui.new({ - parent = parent, - width = 100, - height = 50, - text = "Button 2", - }) - - Gui.endFrame() - - -- Siblings should have different IDs due to different sibling indices and props - luaunit.assertNotEquals(child1.id, child2.id, "Siblings should have different IDs") -end - --- Helper function to create elements from consistent location (simulates real usage) -local function createTopLevelElements() - local elements = {} - for i = 1, 3 do - elements[i] = Gui.new({ width = 100, height = 50, text = "Element " .. i }) - end - return elements -end - -function TestStableIDGeneration:test_top_level_elements_use_call_site_counter() - -- Frame 1: Create multiple top-level elements at same location (in loop) - Gui.beginFrame() - - local elements = createTopLevelElements() - - local ids = {} - for i = 1, 3 do - ids[i] = elements[i].id - end - - Gui.endFrame() - - -- Frame 2: Recreate same elements from SAME line (via helper) - Gui.beginFrame() - - local elements2 = createTopLevelElements() - - Gui.endFrame() - - -- IDs should be stable for top-level elements when called from same location - for i = 1, 3 do - luaunit.assertEquals(elements2[i].id, ids[i], "Top-level element " .. i .. " ID should be stable") - end -end - -function TestStableIDGeneration:test_mixed_conditional_and_stable_elements() - -- Simulate a real-world scenario: navigation with conditional screens - - -- Frame 1: Screen A with input field - Gui.beginFrame() - - local backdrop1 = Gui.new({ - id = "backdrop", - width = "100%", - height = "100%", - }) - - local window1 = Gui.new({ - parent = backdrop1, - width = "80%", - height = "80%", - }) - - -- Screen A content - local inputA = Gui.new({ - parent = window1, - width = 200, - height = 40, - editable = true, - text = "Screen A Input", - }) - - inputA._textBuffer = "User typed this" - inputA._focused = true - - local inputAId = inputA.id - - Gui.endFrame() - - -- Frame 2: Same screen structure (user is still on Screen A) - Gui.beginFrame() - - local backdrop2 = Gui.new({ - id = "backdrop", - width = "100%", - height = "100%", - }) - - local window2 = Gui.new({ - parent = backdrop2, - width = "80%", - height = "80%", - }) - - -- Screen A content (same position in tree) - local inputA2 = Gui.new({ - parent = window2, - width = 200, - height = 40, - editable = true, - text = "Screen A Input", - }) - - Gui.endFrame() - - -- Input field should maintain ID and state - luaunit.assertEquals(inputA2.id, inputAId, "Input field ID should be stable within same screen") - luaunit.assertEquals(inputA2._textBuffer, "User typed this", "Input text should be preserved") - luaunit.assertTrue(inputA2._focused, "Input focus should be preserved") -end - -luaunit.LuaUnit.run() diff --git a/testing/runAll.lua b/testing/runAll.lua index 24dd221..247022f 100644 --- a/testing/runAll.lua +++ b/testing/runAll.lua @@ -6,43 +6,7 @@ _G.RUNNING_ALL_TESTS = true local luaunit = require("testing.luaunit") -- Run all tests in the __tests__ directory -local testFiles = { - "testing/__tests__/01_absolute_positioning_basic_tests.lua", - "testing/__tests__/02_absolute_positioning_child_layout_tests.lua", - "testing/__tests__/03_flex_direction_horizontal_tests.lua", - "testing/__tests__/04_flex_direction_vertical_tests.lua", - "testing/__tests__/05_justify_content_tests.lua", - "testing/__tests__/06_align_items_tests.lua", - "testing/__tests__/07_flex_wrap_tests.lua", - "testing/__tests__/08_comprehensive_flex_tests.lua", - "testing/__tests__/09_layout_validation_tests.lua", - "testing/__tests__/10_performance_tests.lua", - "testing/__tests__/11_auxiliary_functions_tests.lua", - "testing/__tests__/12_units_system_tests.lua", - "testing/__tests__/13_relative_positioning_tests.lua", - "testing/__tests__/14_text_scaling_basic_tests.lua", - "testing/__tests__/15_grid_layout_tests.lua", - "testing/__tests__/16_event_system_tests.lua", - "testing/__tests__/17_sibling_space_reservation_tests.lua", - "testing/__tests__/18_font_family_inheritance_tests.lua", - "testing/__tests__/19_negative_margin_tests.lua", - "testing/__tests__/20_padding_resize_tests.lua", - "testing/__tests__/21_image_scaler_nearest_tests.lua", - "testing/__tests__/22_image_scaler_bilinear_tests.lua", - "testing/__tests__/23_blur_effects_tests.lua", - "testing/__tests__/24_keyboard_input_tests.lua", - "testing/__tests__/25_image_cache_tests.lua", - "testing/__tests__/26_object_fit_modes_tests.lua", - "testing/__tests__/27_object_position_tests.lua", - "testing/__tests__/28_element_image_integration_tests.lua", - "testing/__tests__/29_drag_event_tests.lua", - "testing/__tests__/30_scrollbar_features_tests.lua", - "testing/__tests__/31_immediate_mode_basic_tests.lua", - "testing/__tests__/32_state_manager_tests.lua", - "testing/__tests__/33_input_field_tests.lua", - "testing/__tests__/34_password_mode_tests.lua", - "testing/__tests__/35_stable_id_generation_tests.lua", -} +local testFiles = {} local success = true print("========================================")