diff --git a/CMakeLists.txt b/CMakeLists.txt index abfe4dd4..7693cddd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,9 @@ file(COPY ${AGENT_FILES} DESTINATION "${CMAKE_BINARY_DIR}/assets/agent/") add_subdirectory(src) +# Tools +add_subdirectory(tools) + # Tests if (YAZE_BUILD_TESTS) add_subdirectory(test) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 83723a07..f9b9cd41 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,7 +43,7 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") unit/zelda3/test_dungeon_objects.cc unit/zelda3/dungeon_component_unit_test.cc zelda3/dungeon/room_object_encoding_test.cc - zelda3/dungeon/room_integration_test.cc + integration/zelda3/room_integration_test.cc zelda3/dungeon/room_manipulation_test.cc # CLI Services (for catalog serialization tests) @@ -62,12 +62,17 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") e2e/rom_dependent/e2e_rom_test.cc e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc - # Legacy Integration Tests (to be migrated) - unit/zelda3/comprehensive_integration_test.cc - unit/zelda3/overworld_integration_test.cc - unit/zelda3/dungeon_integration_test.cc - unit/zelda3/dungeon_editor_system_integration_test.cc - unit/zelda3/dungeon_object_renderer_integration_test.cc + # Deprecated Tests (formerly legacy) + deprecated/comprehensive_integration_test.cc + deprecated/dungeon_integration_test.cc + + # Integration Tests (Zelda3) + integration/zelda3/overworld_integration_test.cc + integration/zelda3/dungeon_editor_system_integration_test.cc + integration/zelda3/dungeon_object_renderer_integration_test.cc + integration/zelda3/room_integration_test.cc + + # Mock/Unit Tests for Zelda3 unit/zelda3/dungeon_object_renderer_mock_test.cc unit/zelda3/dungeon_object_rendering_tests.cc unit/zelda3/dungeon_room_test.cc @@ -100,7 +105,6 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") unit/zelda3/test_dungeon_objects.cc unit/zelda3/dungeon_component_unit_test.cc zelda3/dungeon/room_object_encoding_test.cc - zelda3/dungeon/room_integration_test.cc zelda3/dungeon/room_manipulation_test.cc # CLI Services (for catalog serialization tests) @@ -121,110 +125,27 @@ if(YAZE_BUILD_TESTS AND NOT YAZE_BUILD_TESTS STREQUAL "OFF") e2e/rom_dependent/e2e_rom_test.cc e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc - # Legacy Integration Tests (to be migrated) - unit/zelda3/comprehensive_integration_test.cc - unit/zelda3/overworld_integration_test.cc - unit/zelda3/dungeon_integration_test.cc - unit/zelda3/dungeon_editor_system_integration_test.cc - unit/zelda3/dungeon_object_renderer_integration_test.cc + # Deprecated Tests (formerly legacy) + deprecated/comprehensive_integration_test.cc + deprecated/dungeon_integration_test.cc + + # Integration Tests (Zelda3) + integration/zelda3/overworld_integration_test.cc + integration/zelda3/dungeon_editor_system_integration_test.cc + integration/zelda3/dungeon_object_renderer_integration_test.cc + integration/zelda3/room_integration_test.cc + + # Mock/Unit Tests for Zelda3 unit/zelda3/dungeon_object_renderer_mock_test.cc unit/zelda3/dungeon_object_rendering_tests.cc unit/zelda3/dungeon_room_test.cc + + # Benchmarks + benchmarks/gfx_optimization_benchmarks.cc ) endif() -# Add vanilla value extraction utility (only for local development with ROM access) -# IMPORTANT: Do not build in CI/release - this is a development-only utility -if(NOT YAZE_MINIMAL_BUILD AND YAZE_ENABLE_ROM_TESTS AND NOT DEFINED ENV{GITHUB_ACTIONS}) - add_executable( - extract_vanilla_values - unit/zelda3/extract_vanilla_values.cc - ${YAZE_SRC_FILES} - ) - target_include_directories( - extract_vanilla_values PUBLIC - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${SDL2_INCLUDE_DIR} - ${PNG_INCLUDE_DIRS} - ${PROJECT_BINARY_DIR} - ) - - target_link_libraries( - extract_vanilla_values - yaze_core - ${SDL_TARGETS} - asar-static - ${ABSL_TARGETS} - ${PNG_LIBRARIES} - ${OPENGL_LIBRARIES} - ${CMAKE_DL_LIBS} - ImGui - ) - - # Windows stack size configuration for extract_vanilla_values - if(WIN32) - if(MSVC) - target_link_options(extract_vanilla_values PRIVATE /STACK:16777216) - elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_link_options(extract_vanilla_values PRIVATE -Wl,--stack,16777216) - else() - target_link_options(extract_vanilla_values PRIVATE -Wl,--stack,16777216) - endif() - endif() - - # Add rom_patch_utility as a separate executable - add_executable( - rom_patch_utility - unit/zelda3/rom_patch_utility.cc - ${YAZE_SRC_FILES} - ) - - target_include_directories( - rom_patch_utility PUBLIC - ${CMAKE_SOURCE_DIR}/src/app/ - ${CMAKE_SOURCE_DIR}/src/lib/ - ${CMAKE_SOURCE_DIR}/incl/ - ${CMAKE_SOURCE_DIR}/src/ - ${CMAKE_SOURCE_DIR}/src/lib/imgui_test_engine - ${CMAKE_SOURCE_DIR}/src/lib/asar/src - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar - ${CMAKE_SOURCE_DIR}/src/lib/asar/src/asar-dll-bindings/c - ${SDL2_INCLUDE_DIR} - ${PNG_INCLUDE_DIRS} - ${PROJECT_BINARY_DIR} - ) - - target_link_libraries( - rom_patch_utility - yaze_core - ${SDL_TARGETS} - asar-static - ${ABSL_TARGETS} - ${PNG_LIBRARIES} - ${OPENGL_LIBRARIES} - ${CMAKE_DL_LIBS} - ImGui - ) - - # Windows stack size configuration for rom_patch_utility - if(WIN32) - if(MSVC) - target_link_options(rom_patch_utility PRIVATE /STACK:16777216) - elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - target_link_options(rom_patch_utility PRIVATE -Wl,--stack,16777216) - else() - target_link_options(rom_patch_utility PRIVATE -Wl,--stack,16777216) - endif() - endif() -endif() # Configure test executable only when tests are enabled target_include_directories( @@ -386,60 +307,81 @@ source_group("Tests\\Framework" FILES testing.h yaze_test.cc yaze_test_ci.cc + test_editor.cc + test_editor.h ) # Unit Tests source_group("Tests\\Unit" FILES - unit/test_asar_wrapper.cc - unit/test_rom_loading.cc - unit/test_snes_tiles.cc - unit/test_palettes.cc - unit/test_hex_utils.cc - unit/test_flag_utils.cc - unit/test_bps_utils.cc - unit/test_color_conversion.cc - unit/test_tile_compression.cc - unit/test_memory_management.cc - unit/test_project_structure.cc - unit/test_editor_basic.cc - unit/test_dungeon_data.cc - unit/test_overworld_data.cc - unit/test_sprite_data.cc - unit/test_music_data.cc - unit/test_graphics_rendering.cc - unit/test_gui_components.cc - unit/test_emulator_core.cc - unit/test_cpu_instructions.cc - unit/test_ppu_rendering.cc - unit/test_audio_processing.cc - unit/test_compression_algorithms.cc - unit/test_hex_editor.cc + unit/core/asar_wrapper_test.cc + unit/core/hex_test.cc + unit/cli/resource_catalog_test.cc + unit/rom/rom_test.cc + unit/gfx/snes_tile_test.cc + unit/gfx/compression_test.cc + unit/gfx/snes_palette_test.cc + unit/zelda3/message_test.cc + unit/zelda3/overworld_test.cc + unit/zelda3/object_parser_test.cc + unit/zelda3/object_parser_structs_test.cc + unit/zelda3/sprite_builder_test.cc + unit/zelda3/sprite_position_test.cc + unit/zelda3/test_dungeon_objects.cc + unit/zelda3/dungeon_component_unit_test.cc + zelda3/dungeon/room_object_encoding_test.cc + zelda3/dungeon/room_manipulation_test.cc + unit/zelda3/dungeon_object_renderer_mock_test.cc + unit/zelda3/dungeon_object_rendering_tests.cc + unit/zelda3/dungeon_room_test.cc ) # Integration Tests source_group("Tests\\Integration" FILES - integration/test_editor_integration.cc - integration/test_rom_integration.cc - integration/test_project_workflow.cc - integration/test_asar_integration.cc - integration/test_graphics_pipeline.cc - integration/test_emulator_integration.cc + integration/asar_integration_test.cc + integration/asar_rom_test.cc + integration/dungeon_editor_test.cc + integration/dungeon_editor_test.h + integration/editor/tile16_editor_test.cc + integration/editor/editor_integration_test.cc + integration/editor/editor_integration_test.h +) + +# Integration Tests (Zelda3) +source_group("Tests\\Integration\\Zelda3" FILES + integration/zelda3/overworld_integration_test.cc + integration/zelda3/dungeon_editor_system_integration_test.cc + integration/zelda3/dungeon_object_renderer_integration_test.cc + integration/zelda3/room_integration_test.cc ) # End-to-End Tests source_group("Tests\\E2E" FILES - e2e/test_full_workflow.cc - e2e/test_user_scenarios.cc + e2e/canvas_selection_test.cc + e2e/framework_smoke_test.cc + e2e/rom_dependent/e2e_rom_test.cc + e2e/zscustomoverworld/zscustomoverworld_upgrade_test.cc +) + +# Deprecated Tests +source_group("Tests\\Deprecated" FILES + deprecated/comprehensive_integration_test.cc + deprecated/dungeon_integration_test.cc +) + +# Benchmarks +source_group("Tests\\Benchmarks" FILES + benchmarks/gfx_optimization_benchmarks.cc ) # Test Utilities and Mocks source_group("Tests\\Utilities" FILES test_utils.h + test_utils.cc mocks/mock_rom.h - mocks/mock_editor.h + mocks/mock_memory.h ) # Test Assets source_group("Tests\\Assets" FILES - assets/test_rom.asm + assets/test_patch.asm ) \ No newline at end of file diff --git a/test/gfx_optimization_benchmarks.cc b/test/benchmarks/gfx_optimization_benchmarks.cc similarity index 100% rename from test/gfx_optimization_benchmarks.cc rename to test/benchmarks/gfx_optimization_benchmarks.cc diff --git a/test/unit/zelda3/comprehensive_integration_test.cc b/test/deprecated/comprehensive_integration_test.cc similarity index 100% rename from test/unit/zelda3/comprehensive_integration_test.cc rename to test/deprecated/comprehensive_integration_test.cc diff --git a/test/unit/zelda3/dungeon_integration_test.cc b/test/deprecated/dungeon_integration_test.cc similarity index 100% rename from test/unit/zelda3/dungeon_integration_test.cc rename to test/deprecated/dungeon_integration_test.cc diff --git a/test/e2e/dungeon_object_rendering_e2e_tests.cc b/test/e2e/dungeon_object_rendering_e2e_tests.cc new file mode 100644 index 00000000..4bfd316b --- /dev/null +++ b/test/e2e/dungeon_object_rendering_e2e_tests.cc @@ -0,0 +1,1102 @@ +/** + * @file dungeon_object_rendering_e2e_tests.cc + * @brief End-to-end tests for dungeon object rendering system using imgui test engine + * + * These tests orchestrate complete user workflows for the dungeon editor, validating: + * - Object browser and selection + * - Object placement on canvas + * - Object manipulation (move, delete, edit) + * - Layer management + * - Save/load workflows + * - Rendering quality across different scenarios + * + * Created: October 4, 2025 + * Related: docs/dungeon_editing_implementation_plan.md + */ + +#define IMGUI_DEFINE_MATH_OPERATORS + +#include + +#include "imgui.h" +#include "imgui_test_engine/imgui_te_context.h" +#include "imgui_test_engine/imgui_te_engine.h" +#include "imgui_test_engine/imgui_te_ui.h" + +#include "app/core/controller.h" +#include "app/core/window.h" +#include "app/editor/dungeon/dungeon_editor.h" +#include "app/rom.h" +#include "app/zelda3/dungeon/room.h" +#include "app/zelda3/dungeon/room_object.h" + +namespace yaze { +namespace test { + +/** + * @class DungeonObjectRenderingE2ETests + * @brief Comprehensive E2E test fixture for dungeon object rendering system + */ +class DungeonObjectRenderingE2ETests : public ::testing::Test { + protected: + void SetUp() override { + // Initialize test environment + rom_ = std::make_shared(); + ASSERT_TRUE(rom_->LoadFromFile("zelda3.sfc").ok()); + + dungeon_editor_ = std::make_unique(); + dungeon_editor_->SetRom(rom_); + ASSERT_TRUE(dungeon_editor_->Load().ok()); + + // Initialize imgui test engine + engine_ = ImGuiTestEngine_CreateContext(); + ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine_); + test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info; + test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug; + test_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast; + + ImGuiTestEngine_Start(engine_, ImGui::GetCurrentContext()); + + // Register all test cases + RegisterAllTests(); + } + + void TearDown() override { + if (engine_) { + ImGuiTestEngine_Stop(engine_); + ImGuiTestEngine_DestroyContext(engine_); + engine_ = nullptr; + } + dungeon_editor_.reset(); + rom_.reset(); + } + + void RegisterAllTests(); + + // Test registration helpers + void RegisterObjectBrowserTests(); + void RegisterObjectPlacementTests(); + void RegisterObjectSelectionTests(); + void RegisterLayerManagementTests(); + void RegisterSaveWorkflowTests(); + void RegisterRenderingQualityTests(); + void RegisterPerformanceTests(); + + ImGuiTestEngine* engine_ = nullptr; + std::shared_ptr rom_; + std::unique_ptr dungeon_editor_; +}; + +// ============================================================================= +// OBJECT BROWSER TESTS +// ============================================================================= + +/** + * @brief Test: Navigate object browser categories + * + * Validates: + * - Tab navigation works + * - Each category displays objects + * - Object list is scrollable + */ +void DungeonObjectRenderingE2ETests::RegisterObjectBrowserTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "ObjectBrowser_NavigateCategories"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + // Render dungeon editor UI + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Open dungeon editor window + ctx->SetRef("Dungeon Editor"); + + // Navigate to object selector + ctx->ItemClick("Object Selector##tab"); + ctx->Yield(); + + // Test category tabs + const char* categories[] = { + "Type1##tab", + "Type2##tab", + "Type3##tab", + "All##tab" + }; + + for (const char* category : categories) { + ctx->ItemClick(category); + ctx->Yield(); + + // Verify object list is visible and has content + ctx->ItemVerifyExists("AssetBrowser##child"); + + // Try scrolling the list + ctx->ItemClick("AssetBrowser##child"); + ctx->KeyPress(ImGuiKey_DownArrow, 5); + ctx->Yield(); + } + }; + test->UserData = this; +} + +/** + * @brief Test: Select object from browser + * + * Validates: + * - Object can be selected by clicking + * - Preview updates when object selected + * - Object details window shows correct info + */ +void RegisterObjectBrowserTests_SelectObject(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectBrowser_SelectObject"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Navigate to object selector + ctx->ItemClick("Object Selector##tab"); + ctx->Yield(); + + // Select Type1 category + ctx->ItemClick("Type1##tab"); + ctx->Yield(); + + // Click on first object in list (wall object 0x10) + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + // Verify object details window appears + ctx->SetRef("Object Details"); + ctx->ItemVerifyExists("Object ID: 0x10"); + + // Verify preview canvas shows object + ctx->SetRef("Dungeon Editor/PreviewCanvas"); + ctx->ItemVerifyExists("**/canvas##child"); + }; + test->UserData = self; +} + +/** + * @brief Test: Search and filter objects + * + * Validates: + * - Search box filters object list + * - Filtering by ID works + * - Clearing search restores full list + */ +void RegisterObjectBrowserTests_SearchFilter(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectBrowser_SearchFilter"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor/Object Selector"); + + // Type in search box + ctx->ItemClick("Search##input"); + ctx->KeyCharsAppend("0x10"); + ctx->Yield(); + + // Verify filtered results + ctx->ItemVerifyExists("Object_0x10"); + ctx->ItemVerifyNotExists("Object_0x20"); + + // Clear search + ctx->ItemClick("Clear##button"); + ctx->Yield(); + + // Verify full list restored + ctx->ItemVerifyExists("Object_0x10"); + ctx->ItemVerifyExists("Object_0x20"); + }; + test->UserData = self; +} + +// ============================================================================= +// OBJECT PLACEMENT TESTS +// ============================================================================= + +/** + * @brief Test: Place object on canvas with mouse click + * + * Validates: + * - Object preview follows mouse cursor + * - Click places object at correct position + * - Placed object appears in room object list + * - Canvas renders placed object + */ +void DungeonObjectRenderingE2ETests::RegisterObjectPlacementTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "ObjectPlacement_MouseClick"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Select an object (wall 0x10) + ctx->ItemClick("Object Selector##tab"); + ctx->Yield(); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + // Switch to main canvas + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Canvas##tab"); + ctx->Yield(); + + // Click on canvas to place object + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->Yield(); + + // Verify preview is visible + // (Actual verification would check rendering) + + ctx->MouseClick(); + ctx->Yield(); + + // Verify object was placed + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Room Objects##tab"); + ctx->Yield(); + + // Check object appears in list + ctx->ItemVerifyExists("Object ID: 0x10"); + }; + test->UserData = this; +} + +/** + * @brief Test: Place object with snap to grid + * + * Validates: + * - Snap to grid option works + * - Object positions align to grid + * - Grid size can be changed + */ +void RegisterObjectPlacementTests_SnapToGrid(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectPlacement_SnapToGrid"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Enable snap to grid + ctx->ItemClick("Options##menu"); + ctx->ItemClick("Snap to Grid##checkbox"); + ctx->Yield(); + + // Select object + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + // Place object at arbitrary position + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + 37, canvas_pos.y + 49)); + ctx->MouseClick(); + ctx->Yield(); + + // Verify object was placed at snapped position (32, 48) + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyValue("X Position", 32); + ctx->ItemVerifyValue("Y Position", 48); + }; + test->UserData = self; +} + +/** + * @brief Test: Place multiple objects sequentially + * + * Validates: + * - Multiple objects can be placed + * - Each placement is independent + * - All placed objects render correctly + */ +void RegisterObjectPlacementTests_MultipleObjects(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectPlacement_MultipleObjects"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Place 5 different objects + const uint16_t object_ids[] = {0x10, 0x11, 0x20, 0x100, 0xF99}; + + for (int i = 0; i < 5; i++) { + // Select object + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick(ImGuiTestRef_Str("Object_0x%02X", object_ids[i])); + ctx->Yield(); + + // Place at different position + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + (i * 32), canvas_pos.y + (i * 32))); + ctx->MouseClick(); + ctx->Yield(); + } + + // Verify all 5 objects in room + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyExists("Object Count: 5"); + }; + test->UserData = self; +} + +// ============================================================================= +// OBJECT SELECTION AND MANIPULATION TESTS +// ============================================================================= + +/** + * @brief Test: Select object by clicking on canvas + * + * Validates: + * - Click on object selects it + * - Selection highlight appears + * - Object details update + */ +void DungeonObjectRenderingE2ETests::RegisterObjectSelectionTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "ObjectSelection_Click"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // First place an object + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + + // Now try to select it + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + + // Verify object is selected + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyExists("Selected Object"); + ctx->ItemVerifyValue("Object ID", 0x10); + }; + test->UserData = this; +} + +/** + * @brief Test: Multi-select objects with Ctrl+drag + * + * Validates: + * - Ctrl+drag creates selection box + * - All objects in box are selected + * - Selection count is correct + */ +void RegisterObjectSelectionTests_MultiSelect(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectSelection_MultiSelect"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place 3 objects in a line + ctx->SetRef("Dungeon Editor"); + for (int i = 0; i < 3; i++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + (i * 32) + 16, canvas_pos.y + 16)); + ctx->MouseClick(); + ctx->Yield(); + } + + // Ctrl+drag to select all + ctx->KeyDown(ImGuiKey_LeftCtrl); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x, canvas_pos.y)); + ctx->MouseDown(); + ctx->MouseMove(ImVec2(canvas_pos.x + 100, canvas_pos.y + 50)); + ctx->MouseUp(); + ctx->KeyUp(ImGuiKey_LeftCtrl); + ctx->Yield(); + + // Verify 3 objects selected + ctx->SetRef("Dungeon Editor"); + ctx->ItemVerifyValue("Selected Objects", 3); + }; + test->UserData = self; +} + +/** + * @brief Test: Move selected object with drag + * + * Validates: + * - Selected object can be dragged + * - Object position updates during drag + * - Final position is correct + */ +void RegisterObjectSelectionTests_MoveObject(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectSelection_MoveObject"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place object at (16, 16) + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ImVec2 initial_pos = ImVec2(canvas_pos.x + 16, canvas_pos.y + 16); + ctx->MouseMove(initial_pos); + ctx->MouseClick(); + ctx->Yield(); + + // Select and drag to (48, 48) + ctx->MouseMove(initial_pos); + ctx->MouseClick(); + ctx->MouseDown(); + ctx->MouseMove(ImVec2(canvas_pos.x + 48, canvas_pos.y + 48)); + ctx->MouseUp(); + ctx->Yield(); + + // Verify new position + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyValue("X Position", 48); + ctx->ItemVerifyValue("Y Position", 48); + }; + test->UserData = self; +} + +/** + * @brief Test: Delete selected object + * + * Validates: + * - Delete key removes selected object + * - Object no longer in room list + * - Canvas no longer renders object + */ +void RegisterObjectSelectionTests_DeleteObject(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "ObjectSelection_DeleteObject"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place object + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + + // Select object + ctx->MouseClick(); + ctx->Yield(); + + // Delete with Delete key + ctx->KeyPress(ImGuiKey_Delete); + ctx->Yield(); + + // Verify object removed + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 0); + }; + test->UserData = self; +} + +// ============================================================================= +// LAYER MANAGEMENT TESTS +// ============================================================================= + +/** + * @brief Test: Toggle layer visibility + * + * Validates: + * - Layer visibility checkboxes work + * - Hidden layers don't render + * - Layer can be shown again + */ +void DungeonObjectRenderingE2ETests::RegisterLayerManagementTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "LayerManagement_ToggleVisibility"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Open layer management panel + ctx->ItemClick("Layers##tab"); + ctx->Yield(); + + // Toggle Layer 1 visibility + ctx->ItemClick("Layer 1 Visible##checkbox"); + ctx->Yield(); + + // Verify checkbox state + ctx->ItemVerifyValue("Layer 1 Visible##checkbox", false); + + // Toggle back on + ctx->ItemClick("Layer 1 Visible##checkbox"); + ctx->Yield(); + + ctx->ItemVerifyValue("Layer 1 Visible##checkbox", true); + }; + test->UserData = this; +} + +/** + * @brief Test: Place objects on different layers + * + * Validates: + * - Active layer can be changed + * - Objects placed on correct layer + * - Layer indicator shows current layer + */ +void RegisterLayerManagementTests_PlaceOnLayers(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "LayerManagement_PlaceOnLayers"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Place object on Layer 0 + ctx->ItemClick("Layers##tab"); + ctx->ItemClick("Layer 0##radio"); + ctx->Yield(); + + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + 16, canvas_pos.y + 16)); + ctx->MouseClick(); + ctx->Yield(); + + // Verify object on Layer 0 + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyValue("Layer", 0); + + // Switch to Layer 1 and place another object + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Layers##tab"); + ctx->ItemClick("Layer 1##radio"); + ctx->Yield(); + + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x20"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ctx->MouseMove(ImVec2(canvas_pos.x + 48, canvas_pos.y + 48)); + ctx->MouseClick(); + ctx->Yield(); + + // Verify object on Layer 1 + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyValue("Layer", 1); + }; + test->UserData = self; +} + +/** + * @brief Test: Layer rendering order + * + * Validates: + * - Layers render in correct order (BG1 < BG2 < BG3) + * - Overlapping objects render correctly + * - Visual inspection of layer order + */ +void RegisterLayerManagementTests_RenderingOrder(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "LayerManagement_RenderingOrder"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place 3 objects at same position on different layers + ctx->SetRef("Dungeon Editor"); + + for (int layer = 0; layer < 3; layer++) { + ctx->ItemClick("Layers##tab"); + ctx->ItemClick(ImGuiTestRef_Str("Layer %d##radio", layer)); + ctx->Yield(); + + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick(ImGuiTestRef_Str("Object_0x%02X", 0x10 + layer)); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + } + + // Verify all 3 objects exist + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 3); + + // Visual verification would be done with snapshot comparison + // Here we just verify the objects are in the right layers + ctx->ItemVerifyExists("Layer 0: 1 object"); + ctx->ItemVerifyExists("Layer 1: 1 object"); + ctx->ItemVerifyExists("Layer 2: 1 object"); + }; + test->UserData = self; +} + +// ============================================================================= +// SAVE/LOAD WORKFLOW TESTS +// ============================================================================= + +/** + * @brief Test: Save room with objects + * + * Validates: + * - Objects can be saved to ROM + * - Save operation succeeds + * - No errors during save + */ +void DungeonObjectRenderingE2ETests::RegisterSaveWorkflowTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "SaveWorkflow_SaveRoom"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place an object + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + + // Save room + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("File##menu"); + ctx->ItemClick("Save Room##menuitem"); + ctx->Yield(); + + // Verify save success message + ctx->ItemVerifyExists("Save successful"); + }; + test->UserData = this; +} + +/** + * @brief Test: Save and reload room (round-trip) + * + * Validates: + * - Objects persist after save/reload + * - Object properties are preserved + * - No data corruption + */ +void RegisterSaveWorkflowTests_RoundTrip(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "SaveWorkflow_RoundTrip"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place object with specific properties + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + 64, canvas_pos.y + 96)); + ctx->MouseClick(); + ctx->Yield(); + + // Verify initial state + ctx->SetRef("Dungeon Editor/Object Details"); + int initial_x = ctx->ItemGetValue("X Position"); + int initial_y = ctx->ItemGetValue("Y Position"); + int initial_id = ctx->ItemGetValue("Object ID"); + + // Save room + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("File##menu"); + ctx->ItemClick("Save Room##menuitem"); + ctx->Yield(); + + // Reload room + ctx->ItemClick("File##menu"); + ctx->ItemClick("Reload Room##menuitem"); + ctx->Yield(); + + // Verify object persisted with same properties + ctx->SetRef("Dungeon Editor/Object Details"); + ctx->ItemVerifyValue("X Position", initial_x); + ctx->ItemVerifyValue("Y Position", initial_y); + ctx->ItemVerifyValue("Object ID", initial_id); + }; + test->UserData = self; +} + +/** + * @brief Test: Save with multiple object types + * + * Validates: + * - Type1, Type2, Type3 objects all save correctly + * - Encoding is correct for each type + * - All objects reload correctly + */ +void RegisterSaveWorkflowTests_MultipleTypes(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "SaveWorkflow_MultipleTypes"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place one of each object type + const uint16_t object_ids[] = { + 0x10, // Type1 + 0x125, // Type2 + 0xF99 // Type3 (chest) + }; + + ctx->SetRef("Dungeon Editor"); + + for (int i = 0; i < 3; i++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick(ImGuiTestRef_Str("Object_0x%02X", object_ids[i])); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + (i * 32), canvas_pos.y + (i * 32))); + ctx->MouseClick(); + ctx->Yield(); + } + + // Save and reload + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("File##menu"); + ctx->ItemClick("Save Room##menuitem"); + ctx->Yield(); + + ctx->ItemClick("File##menu"); + ctx->ItemClick("Reload Room##menuitem"); + ctx->Yield(); + + // Verify all 3 objects reloaded + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 3); + + // Verify each object type + for (int i = 0; i < 3; i++) { + ctx->ItemVerifyExists(ImGuiTestRef_Str("Object ID: 0x%02X", object_ids[i])); + } + }; + test->UserData = self; +} + +// ============================================================================= +// RENDERING QUALITY TESTS +// ============================================================================= + +/** + * @brief Test: Render all object types correctly + * + * Validates: + * - Type1 objects render + * - Type2 objects render + * - Type3 objects render + * - All render at correct positions + */ +void DungeonObjectRenderingE2ETests::RegisterRenderingQualityTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "RenderingQuality_AllTypes"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // This would typically involve visual snapshot comparison + // For now, we verify objects are placed and rendered without errors + + const uint16_t object_ids[] = { + 0x10, 0x11, 0x20, // Type1 objects + 0x100, 0x125, // Type2 objects + 0xF99, 0xFB1 // Type3 objects + }; + + ctx->SetRef("Dungeon Editor"); + + for (int i = 0; i < 7; i++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick(ImGuiTestRef_Str("Object_0x%02X", object_ids[i])); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + ((i % 4) * 48), canvas_pos.y + ((i / 4) * 48))); + ctx->MouseClick(); + ctx->Yield(); + } + + // Verify all objects rendered without errors + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 7); + ctx->ItemVerifyNotExists("Rendering Error"); + }; + test->UserData = this; +} + +/** + * @brief Test: Render with different palettes + * + * Validates: + * - Palette switching works + * - Objects render with correct colors + * - No palette-related rendering errors + */ +void RegisterRenderingQualityTests_Palettes(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "RenderingQuality_Palettes"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place an object + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_center = ctx->ItemRectCenter("canvas##child"); + ctx->MouseMove(canvas_center); + ctx->MouseClick(); + ctx->Yield(); + + // Switch palettes and verify rendering + const int palette_ids[] = {0, 1, 2, 3, 4, 5}; + + for (int palette : palette_ids) { + ctx->SetRef("Dungeon Editor"); + ctx->ItemClick("Options##menu"); + ctx->ItemInput("Palette##input", palette); + ctx->Yield(); + + // Verify no rendering errors + ctx->ItemVerifyNotExists("Rendering Error"); + } + }; + test->UserData = self; +} + +/** + * @brief Test: Complex room scenario rendering + * + * Validates: + * - Many objects render correctly + * - Performance is acceptable + * - No rendering artifacts + */ +void RegisterRenderingQualityTests_ComplexRoom(DungeonObjectRenderingE2ETests* self) { + ImGuiTest* test = IM_REGISTER_TEST(self->engine_, "DungeonObjectRendering", "RenderingQuality_ComplexRoom"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + // Place many objects to create complex room + ctx->SetRef("Dungeon Editor"); + + // Place wall perimeter (Type1 objects) + for (int x = 0; x < 16; x++) { + for (int side = 0; side < 2; side++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + int y = side == 0 ? 0 : 10; + ctx->MouseMove(ImVec2(canvas_pos.x + (x * 16), canvas_pos.y + (y * 16))); + ctx->MouseClick(); + ctx->Yield(); + } + } + + // Add some decorative objects + const uint16_t decorative_objects[] = {0x20, 0x21, 0xF99}; + for (int i = 0; i < 3; i++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick(ImGuiTestRef_Str("Object_0x%02X", decorative_objects[i])); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2(canvas_pos.x + (i * 64) + 32, canvas_pos.y + 80)); + ctx->MouseClick(); + ctx->Yield(); + } + + // Verify all objects rendered + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 35); // 32 walls + 3 decorative + + // Verify no performance issues (frame time < 16ms for 60fps) + ctx->SetRef("Dungeon Editor"); + ctx->ItemVerifyLessThan("Frame Time (ms)", 16.0f); + }; + test->UserData = self; +} + +// ============================================================================= +// PERFORMANCE TESTS +// ============================================================================= + +/** + * @brief Test: Large room with many objects performance + * + * Validates: + * - Rendering stays performant with 100+ objects + * - Frame time stays below threshold + * - Memory usage is reasonable + */ +void DungeonObjectRenderingE2ETests::RegisterPerformanceTests() { + ImGuiTest* test = IM_REGISTER_TEST(engine_, "DungeonObjectRendering", "Performance_LargeRoom"); + test->GuiFunc = [](ImGuiTestContext* ctx) { + auto* self = (DungeonObjectRenderingE2ETests*)ctx->UserData; + self->dungeon_editor_->Update(); + }; + test->TestFunc = [](ImGuiTestContext* ctx) { + ctx->SetRef("Dungeon Editor"); + + // Place 100 objects + for (int i = 0; i < 100; i++) { + ctx->ItemClick("Object Selector##tab"); + ctx->SetRef("AssetBrowser"); + ctx->ItemClick("Object_0x10"); + ctx->Yield(); + + ctx->SetRef("Dungeon Editor/Canvas"); + ImVec2 canvas_pos = ctx->ItemRectMin("canvas##child"); + ctx->MouseMove(ImVec2( + canvas_pos.x + ((i % 16) * 16), + canvas_pos.y + ((i / 16) * 16) + )); + ctx->MouseClick(); + ctx->Yield(); + } + + // Measure rendering performance + ctx->SetRef("Dungeon Editor"); + float frame_time = ctx->ItemGetValue("Frame Time (ms)"); + + // Verify acceptable performance (< 16ms for 60fps) + IM_CHECK_LT(frame_time, 16.0f); + + // Verify object count + ctx->SetRef("Dungeon Editor/Room Objects"); + ctx->ItemVerifyValue("Object Count", 100); + }; + test->UserData = this; +} + +// ============================================================================= +// TEST REGISTRATION +// ============================================================================= + +void DungeonObjectRenderingE2ETests::RegisterAllTests() { + RegisterObjectBrowserTests(); + RegisterObjectPlacementTests(); + RegisterObjectSelectionTests(); + RegisterLayerManagementTests(); + RegisterSaveWorkflowTests(); + RegisterRenderingQualityTests(); + RegisterPerformanceTests(); + + // Register additional helper tests + RegisterObjectBrowserTests_SelectObject(this); + RegisterObjectBrowserTests_SearchFilter(this); + RegisterObjectPlacementTests_SnapToGrid(this); + RegisterObjectPlacementTests_MultipleObjects(this); + RegisterObjectSelectionTests_MultiSelect(this); + RegisterObjectSelectionTests_MoveObject(this); + RegisterObjectSelectionTests_DeleteObject(this); + RegisterLayerManagementTests_PlaceOnLayers(this); + RegisterLayerManagementTests_RenderingOrder(this); + RegisterSaveWorkflowTests_RoundTrip(this); + RegisterSaveWorkflowTests_MultipleTypes(this); + RegisterRenderingQualityTests_Palettes(this); + RegisterRenderingQualityTests_ComplexRoom(this); +} + +// ============================================================================= +// TEST FIXTURE INTEGRATION +// ============================================================================= + +TEST_F(DungeonObjectRenderingE2ETests, RunAllTests) { + // Run all registered tests + ImGuiTestEngine_QueueTests(engine_, ImGuiTestGroup_Tests, nullptr, nullptr); + ImGuiTestEngine_Run(engine_); + + // Verify all tests passed + ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine_); + EXPECT_EQ(test_io.TestsFailedCount, 0) + << "Some E2E tests failed. Check test engine output for details."; +} + +} // namespace test +} // namespace yaze + diff --git a/test/unit/zelda3/dungeon_editor_system_integration_test.cc b/test/integration/zelda3/dungeon_editor_system_integration_test.cc similarity index 100% rename from test/unit/zelda3/dungeon_editor_system_integration_test.cc rename to test/integration/zelda3/dungeon_editor_system_integration_test.cc diff --git a/test/unit/zelda3/dungeon_object_renderer_integration_test.cc b/test/integration/zelda3/dungeon_object_renderer_integration_test.cc similarity index 100% rename from test/unit/zelda3/dungeon_object_renderer_integration_test.cc rename to test/integration/zelda3/dungeon_object_renderer_integration_test.cc diff --git a/test/unit/zelda3/overworld_integration_test.cc b/test/integration/zelda3/overworld_integration_test.cc similarity index 100% rename from test/unit/zelda3/overworld_integration_test.cc rename to test/integration/zelda3/overworld_integration_test.cc diff --git a/test/zelda3/dungeon/room_integration_test.cc b/test/integration/zelda3/room_integration_test.cc similarity index 100% rename from test/zelda3/dungeon/room_integration_test.cc rename to test/integration/zelda3/room_integration_test.cc diff --git a/test/unit/zelda3/CMakeLists.txt b/test/unit/zelda3/CMakeLists.txt deleted file mode 100644 index b0ac31f2..00000000 --- a/test/unit/zelda3/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Add golden data extractor tool -add_executable(overworld_golden_data_extractor - overworld_golden_data_extractor.cc -) - -target_link_libraries(overworld_golden_data_extractor - yaze_core - ${CMAKE_THREAD_LIBS_INIT} -) - -# Add vanilla values extractor tool -add_executable(extract_vanilla_values - extract_vanilla_values.cc -) - -target_link_libraries(extract_vanilla_values - yaze_core - ${CMAKE_THREAD_LIBS_INIT} -) - -# Install tools to bin directory -install(TARGETS overworld_golden_data_extractor extract_vanilla_values - DESTINATION bin -) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 00000000..d11de14e --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,2 @@ +# Process the test helpers subdirectory +add_subdirectory(test_helpers) diff --git a/tools/test_helpers/CMakeLists.txt b/tools/test_helpers/CMakeLists.txt new file mode 100644 index 00000000..07f5595b --- /dev/null +++ b/tools/test_helpers/CMakeLists.txt @@ -0,0 +1,53 @@ +# Add golden data extractor tool +add_executable(overworld_golden_data_extractor + overworld_golden_data_extractor.cc +) + +target_link_libraries(overworld_golden_data_extractor + yaze_core + ${CMAKE_THREAD_LIBS_INIT} +) + +# Add vanilla values extractor tool +add_executable(extract_vanilla_values + extract_vanilla_values.cc +) + +target_link_libraries(extract_vanilla_values + yaze_core + ${CMAKE_THREAD_LIBS_INIT} +) + +# Add rom_patch_utility tool +add_executable(rom_patch_utility + rom_patch_utility.cc +) + +target_link_libraries(rom_patch_utility + yaze_core + ${CMAKE_THREAD_LIBS_INIT} +) + +# Windows stack size configuration for helper tools +set(HELPER_TOOLS + overworld_golden_data_extractor + extract_vanilla_values + rom_patch_utility +) + +foreach(TOOL ${HELPER_TOOLS}) + if(WIN32) + if(MSVC) + target_link_options(${TOOL} PRIVATE /STACK:16777216) + elseif(MINGW OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_options(${TOOL} PRIVATE -Wl,--stack,16777216) + else() + target_link_options(${TOOL} PRIVATE -Wl,--stack,16777216) + endif() + endif() +endforeach() + +# Install tools to bin directory +install(TARGETS overworld_golden_data_extractor extract_vanilla_values rom_patch_utility + DESTINATION bin +) diff --git a/test/unit/zelda3/extract_vanilla_values.cc b/tools/test_helpers/extract_vanilla_values.cc similarity index 100% rename from test/unit/zelda3/extract_vanilla_values.cc rename to tools/test_helpers/extract_vanilla_values.cc diff --git a/test/unit/zelda3/overworld_golden_data_extractor.cc b/tools/test_helpers/overworld_golden_data_extractor.cc similarity index 100% rename from test/unit/zelda3/overworld_golden_data_extractor.cc rename to tools/test_helpers/overworld_golden_data_extractor.cc diff --git a/test/unit/zelda3/rom_patch_utility.cc b/tools/test_helpers/rom_patch_utility.cc similarity index 100% rename from test/unit/zelda3/rom_patch_utility.cc rename to tools/test_helpers/rom_patch_utility.cc