/** * @file dungeon_object_drawing_test.cc * @brief E2E tests for dungeon object drawing and manipulation * * Tests object placement, deletion, and repositioning in the dungeon editor. * Uses ImGuiTestEngine to automate UI interactions and verify object state. */ #define IMGUI_DEFINE_MATH_OPERATORS #include "e2e/dungeon_object_drawing_test.h" #include "app/controller.h" #include "rom/rom.h" #include "gtest/gtest.h" #include "imgui.h" #include "imgui_test_engine/imgui_te_context.h" #include "test_utils.h" #include "zelda3/dungeon/room.h" namespace yaze { namespace test { // ============================================================================= // Helper Functions // ============================================================================= namespace { /** * @brief Open a specific dungeon room by ID * @param ctx Test context * @param room_id The room ID to open (0x00-0x127) * @return True if the room was successfully opened */ bool OpenDungeonRoom(ImGuiTestContext* ctx, int room_id) { char room_label[32]; snprintf(room_label, sizeof(room_label), "[%03X]", room_id); // Enable room selector if not visible if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); if (!ctx->WindowInfo("Rooms List").Window) { ctx->ItemClick("Rooms"); ctx->Yield(5); } } // Open room from list if (ctx->WindowInfo("Rooms List").Window != nullptr) { ctx->SetRef("Rooms List"); ctx->Yield(5); // Use the child window for room list ctx->SetRef("Rooms List/##RoomsList"); // Scroll to and click the room // Room entries are formatted as "[XXX] Room Name" char full_label[64]; snprintf(full_label, sizeof(full_label), "[%03X]*", room_id); // Try clicking on any selectable starting with the room ID ctx->ItemClick(full_label); ctx->Yield(20); return true; } return false; } /** * @brief Enable the object editor panel * @param ctx Test context * @return True if the object editor was enabled */ bool EnableObjectEditor(ImGuiTestContext* ctx) { if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Objects"); ctx->Yield(10); return ctx->WindowInfo("Object Editor").Window != nullptr; } return false; } /** * @brief Get the current room object count from the controller * @param ctx Test context * @param room_id Room ID to check * @return Number of objects in the room, or -1 on error */ int GetRoomObjectCount(ImGuiTestContext* ctx, int room_id) { Controller* controller = static_cast(ctx->Test->UserData); if (!controller) { ctx->LogError("Controller is null"); return -1; } // Access the dungeon editor's room data via the editor manager // For now, return -1 as direct room access requires editor state // In a full implementation, this would query the DungeonEditorV2 return -1; } /** * @brief Click at a position relative to the canvas zero point * @param ctx Test context * @param room_x X position in room coordinates (8px per tile) * @param room_y Y position in room coordinates (8px per tile) */ void ClickOnCanvas(ImGuiTestContext* ctx, int room_x, int room_y) { // Canvas coordinates are in pixels (8px per tile for dungeons) // The canvas has a zero_point() that we need to account for // Get the current window position for the room card ImGuiWindow* window = ctx->GetWindowByRef(""); if (!window) { ctx->LogWarning("Could not find active window for canvas click"); return; } // Calculate canvas position (assuming scale = 1.0) // Room tiles are 8x8 pixels float canvas_x = room_x * 8.0f; float canvas_y = room_y * 8.0f; // Move to position and click ctx->MouseMoveToPos(ImVec2(canvas_x, canvas_y)); ctx->MouseClick(0); } } // namespace // ============================================================================= // Test Implementations // ============================================================================= void E2ETest_DungeonObjectDrawing_BasicPlacement(ImGuiTestContext* ctx) { ctx->LogInfo("=== Test: Basic Object Placement ==="); // Step 1: Load ROM ctx->LogInfo("Step 1: Loading ROM..."); gui::LoadRomInTest(ctx, yaze::test::TestRomManager::GetTestRomPath()); ctx->Yield(10); // Step 2: Open Dungeon Editor ctx->LogInfo("Step 2: Opening Dungeon Editor..."); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Verify dungeon controls are visible if (ctx->WindowInfo("Dungeon Controls").Window == nullptr) { ctx->LogError("Dungeon Controls panel not found - aborting test"); return; } ctx->LogInfo("Dungeon Controls panel visible"); // Step 3: Open Room Selector ctx->LogInfo("Step 3: Opening Room Selector..."); ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(10); // Step 4: Open a room (Room 0 - Ganon's Room) ctx->LogInfo("Step 4: Opening Room 0x00..."); if (ctx->WindowInfo("Rooms List").Window != nullptr) { ctx->WindowFocus("Rooms List"); ctx->Yield(5); // Find and click Room 0 // Room list items are selectables in a child window ctx->SetRef("Rooms List"); ctx->Yield(5); // Room entries use format "[XXX] Room Name" // For room 0, it would be "[000] Ganon" or similar ctx->ItemClick("**/[000]*"); ctx->Yield(20); } else { ctx->LogError("Rooms List window not found"); return; } // Verify room card opened char room_window_name[32]; snprintf(room_window_name, sizeof(room_window_name), "[000]*"); ctx->Yield(10); // Step 5: Enable Object Editor ctx->LogInfo("Step 5: Enabling Object Editor..."); ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Objects"); ctx->Yield(10); if (ctx->WindowInfo("Object Editor").Window != nullptr) { ctx->LogInfo("Object Editor panel is visible"); } else { ctx->LogWarning("Object Editor panel not visible - may be differently named"); } // Step 6: Verify canvas is present in the room card ctx->LogInfo("Step 6: Verifying canvas is present..."); // The room card should contain a canvas // Canvas is drawn in DrawRoomTab() via canvas_viewer_.DrawDungeonCanvas() // The canvas uses "##DungeonCanvas" or similar ID ctx->LogInfo("=== Basic Object Placement Test Complete ==="); ctx->LogInfo("Note: Full placement verification requires direct object state access"); } void E2ETest_DungeonObjectDrawing_MultiLayerObjects(ImGuiTestContext* ctx) { ctx->LogInfo("=== Test: Multi-Layer Object Placement ==="); // Step 1: Load ROM and open editor ctx->LogInfo("Step 1: Loading ROM and opening editor..."); gui::LoadRomInTest(ctx, yaze::test::TestRomManager::GetTestRomPath()); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Step 2: Open room selector and a room ctx->LogInfo("Step 2: Opening a dungeon room..."); if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(10); } // Open Room 1 (often has multi-layer content) if (ctx->WindowInfo("Rooms List").Window != nullptr) { ctx->SetRef("Rooms List"); ctx->ItemClick("**/[001]*"); ctx->Yield(20); } // Step 3: Test layer visibility controls ctx->LogInfo("Step 3: Testing layer visibility controls..."); // The room card should have layer controls // These are checkboxes: "BG1", "BG2" in the LayerControls table // Find the room window (format varies based on session) ImGuiWindow* room_window = nullptr; for (int i = 0; i < ctx->UiContext->Windows.Size; i++) { ImGuiWindow* w = ctx->UiContext->Windows[i]; if (w && w->Name && strstr(w->Name, "[001]") != nullptr) { room_window = w; break; } } if (room_window) { ctx->WindowFocus(room_window->Name); ctx->SetRef(room_window->Name); ctx->Yield(5); // Toggle BG1 visibility if (ctx->ItemExists("BG1")) { ctx->LogInfo("Toggling BG1 layer..."); ctx->ItemClick("BG1"); ctx->Yield(10); ctx->ItemClick("BG1"); // Toggle back ctx->Yield(5); } // Toggle BG2 visibility if (ctx->ItemExists("BG2")) { ctx->LogInfo("Toggling BG2 layer..."); ctx->ItemClick("BG2"); ctx->Yield(10); ctx->ItemClick("BG2"); // Toggle back ctx->Yield(5); } ctx->LogInfo("Layer visibility controls functional"); } else { ctx->LogWarning("Could not find room window for layer tests"); } ctx->LogInfo("=== Multi-Layer Object Placement Test Complete ==="); } void E2ETest_DungeonObjectDrawing_ObjectDeletion(ImGuiTestContext* ctx) { ctx->LogInfo("=== Test: Object Deletion ==="); // Step 1: Load ROM and open editor ctx->LogInfo("Step 1: Loading ROM and opening editor..."); gui::LoadRomInTest(ctx, yaze::test::TestRomManager::GetTestRomPath()); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Step 2: Open a room with objects ctx->LogInfo("Step 2: Opening room with objects..."); if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(10); } // Open Room 2 (Hyrule Castle Entrance - usually has objects) if (ctx->WindowInfo("Rooms List").Window != nullptr) { ctx->SetRef("Rooms List"); ctx->ItemClick("**/[002]*"); ctx->Yield(30); // Allow room to load and render } // Step 3: Enable object editor to see object list ctx->LogInfo("Step 3: Enabling object editor..."); ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Objects"); ctx->Yield(10); // Step 4: Attempt to select an object on the canvas ctx->LogInfo("Step 4: Testing object selection..."); // Find the room window ImGuiWindow* room_window = nullptr; for (int i = 0; i < ctx->UiContext->Windows.Size; i++) { ImGuiWindow* w = ctx->UiContext->Windows[i]; if (w && w->Name && strstr(w->Name, "[002]") != nullptr) { room_window = w; break; } } if (room_window) { ctx->WindowFocus(room_window->Name); ctx->Yield(5); // Click in the canvas area to attempt object selection // Canvas starts after the properties tables ImVec2 window_pos = room_window->Pos; ImVec2 click_pos = ImVec2(window_pos.x + 256, window_pos.y + 200); ctx->LogInfo("Clicking at canvas position (256, 200)..."); ctx->MouseMoveToPos(click_pos); ctx->MouseClick(0); ctx->Yield(5); // Step 5: Press Delete key ctx->LogInfo("Step 5: Pressing Delete key..."); ctx->KeyPress(ImGuiKey_Delete); ctx->Yield(10); ctx->LogInfo("Delete key pressed - object deletion attempted"); } else { ctx->LogWarning("Could not find room window for deletion test"); } ctx->LogInfo("=== Object Deletion Test Complete ==="); } void E2ETest_DungeonObjectDrawing_ObjectRepositioning(ImGuiTestContext* ctx) { ctx->LogInfo("=== Test: Object Repositioning ==="); // Step 1: Load ROM and open editor ctx->LogInfo("Step 1: Loading ROM and opening editor..."); gui::LoadRomInTest(ctx, yaze::test::TestRomManager::GetTestRomPath()); gui::OpenEditorInTest(ctx, "Dungeon"); ctx->Yield(20); // Step 2: Open a room with objects ctx->LogInfo("Step 2: Opening room with objects..."); if (ctx->WindowInfo("Dungeon Controls").Window != nullptr) { ctx->SetRef("Dungeon Controls"); ctx->ItemClick("Rooms"); ctx->Yield(10); } // Open Room 3 if (ctx->WindowInfo("Rooms List").Window != nullptr) { ctx->SetRef("Rooms List"); ctx->ItemClick("**/[003]*"); ctx->Yield(30); } // Step 3: Find and focus the room window ctx->LogInfo("Step 3: Finding room window..."); ImGuiWindow* room_window = nullptr; for (int i = 0; i < ctx->UiContext->Windows.Size; i++) { ImGuiWindow* w = ctx->UiContext->Windows[i]; if (w && w->Name && strstr(w->Name, "[003]") != nullptr) { room_window = w; break; } } if (room_window) { ctx->WindowFocus(room_window->Name); ctx->Yield(5); // Step 4: Perform drag operation ctx->LogInfo("Step 4: Testing drag operation..."); // Calculate canvas area positions ImVec2 window_pos = room_window->Pos; ImVec2 start_pos = ImVec2(window_pos.x + 200, window_pos.y + 200); ImVec2 end_pos = ImVec2(window_pos.x + 300, window_pos.y + 250); // Perform drag ctx->MouseMoveToPos(start_pos); ctx->MouseDown(0); ctx->Yield(2); ctx->MouseMoveToPos(end_pos); ctx->Yield(2); ctx->MouseUp(0); ctx->Yield(10); ctx->LogInfo("Drag operation completed from (200,200) to (300,250)"); } else { ctx->LogWarning("Could not find room window for drag test"); } ctx->LogInfo("=== Object Repositioning Test Complete ==="); } // ============================================================================= // GTest Integration - Unit tests for object drawing infrastructure // ============================================================================= /** * @class DungeonObjectDrawingTest * @brief GTest fixture for object drawing unit tests */ class DungeonObjectDrawingTest : public ::testing::Test { protected: void SetUp() override { // Check if ROM testing is enabled if (!TestRomManager::IsRomTestingEnabled()) { skip_rom_tests_ = true; } } bool skip_rom_tests_ = false; }; TEST_F(DungeonObjectDrawingTest, RoomObjectStructure) { // Test that RoomObject can be created with valid parameters zelda3::RoomObject obj(0x01, 10, 20, 1, 0); EXPECT_EQ(obj.id_, 0x01); EXPECT_EQ(obj.x_, 10); EXPECT_EQ(obj.y_, 20); EXPECT_EQ(obj.size_, 1); } TEST_F(DungeonObjectDrawingTest, RoomObjectLayerTypes) { // Test layer type enumeration zelda3::RoomObject obj(0x01, 0, 0, 1, 0); // Default layer should be BG1 EXPECT_EQ(obj.layer_, zelda3::RoomObject::LayerType::BG1); // Test setting different layers obj.layer_ = zelda3::RoomObject::LayerType::BG2; EXPECT_EQ(obj.layer_, zelda3::RoomObject::LayerType::BG2); obj.layer_ = zelda3::RoomObject::LayerType::BG3; EXPECT_EQ(obj.layer_, zelda3::RoomObject::LayerType::BG3); } TEST_F(DungeonObjectDrawingTest, ObjectPositionBounds) { // Test object position validation // Room dimensions are 64x64 tiles (512x512 pixels at 8px per tile) constexpr int kMaxTileX = 63; constexpr int kMaxTileY = 63; // Valid position zelda3::RoomObject valid_obj(0x01, 32, 32, 1, 0); EXPECT_GE(valid_obj.x_, 0); EXPECT_LE(valid_obj.x_, kMaxTileX); EXPECT_GE(valid_obj.y_, 0); EXPECT_LE(valid_obj.y_, kMaxTileY); // Edge positions zelda3::RoomObject corner_obj(0x01, kMaxTileX, kMaxTileY, 1, 0); EXPECT_EQ(corner_obj.x_, kMaxTileX); EXPECT_EQ(corner_obj.y_, kMaxTileY); } } // namespace test } // namespace yaze