#include "app/editor/dungeon/object_selection.h" #include #include "zelda3/dungeon/room_object.h" namespace yaze::editor { namespace { // Helper to create test objects zelda3::RoomObject CreateTestObject(uint8_t x, uint8_t y, uint8_t size = 0x00, int16_t id = 0x01) { return zelda3::RoomObject(id, x, y, size, 0); } // ============================================================================ // Single Selection Tests // ============================================================================ TEST(ObjectSelectionTest, SelectSingleObject) { ObjectSelection selection; selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_EQ(selection.GetSelectionCount(), 1); EXPECT_EQ(selection.GetPrimarySelection().value(), 0); } TEST(ObjectSelectionTest, SelectSingleObjectReplacesExisting) { ObjectSelection selection; // Select object 0 selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.IsObjectSelected(0)); // Select object 1 (should replace object 0) selection.SelectObject(1, ObjectSelection::SelectionMode::Single); EXPECT_FALSE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(1)); EXPECT_EQ(selection.GetSelectionCount(), 1); } TEST(ObjectSelectionTest, ClearSelection) { ObjectSelection selection; selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.HasSelection()); selection.ClearSelection(); EXPECT_FALSE(selection.HasSelection()); EXPECT_EQ(selection.GetSelectionCount(), 0); } // ============================================================================ // Multi-Selection Tests (Shift+Click) // ============================================================================ TEST(ObjectSelectionTest, AddToSelectionMode) { ObjectSelection selection; // Select object 0 selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_EQ(selection.GetSelectionCount(), 1); // Add object 1 (Shift+click) selection.SelectObject(1, ObjectSelection::SelectionMode::Add); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(1)); EXPECT_EQ(selection.GetSelectionCount(), 2); // Add object 2 selection.SelectObject(2, ObjectSelection::SelectionMode::Add); EXPECT_EQ(selection.GetSelectionCount(), 3); } TEST(ObjectSelectionTest, AddToSelectionDoesNotRemoveExisting) { ObjectSelection selection; selection.SelectObject(0, ObjectSelection::SelectionMode::Single); selection.SelectObject(2, ObjectSelection::SelectionMode::Add); selection.SelectObject(4, ObjectSelection::SelectionMode::Add); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(2)); EXPECT_TRUE(selection.IsObjectSelected(4)); EXPECT_FALSE(selection.IsObjectSelected(1)); EXPECT_FALSE(selection.IsObjectSelected(3)); } // ============================================================================ // Toggle Selection Tests (Ctrl+Click) // ============================================================================ TEST(ObjectSelectionTest, ToggleSelectionMode) { ObjectSelection selection; // Select object 0 selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.IsObjectSelected(0)); // Toggle object 0 (should deselect) selection.SelectObject(0, ObjectSelection::SelectionMode::Toggle); EXPECT_FALSE(selection.IsObjectSelected(0)); EXPECT_EQ(selection.GetSelectionCount(), 0); // Toggle object 0 again (should select) selection.SelectObject(0, ObjectSelection::SelectionMode::Toggle); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_EQ(selection.GetSelectionCount(), 1); } TEST(ObjectSelectionTest, ToggleAddsToExistingSelection) { ObjectSelection selection; selection.SelectObject(0, ObjectSelection::SelectionMode::Single); selection.SelectObject(1, ObjectSelection::SelectionMode::Toggle); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(1)); EXPECT_EQ(selection.GetSelectionCount(), 2); } TEST(ObjectSelectionTest, ToggleRemovesFromExistingSelection) { ObjectSelection selection; selection.SelectObject(0, ObjectSelection::SelectionMode::Single); selection.SelectObject(1, ObjectSelection::SelectionMode::Add); selection.SelectObject(2, ObjectSelection::SelectionMode::Add); EXPECT_EQ(selection.GetSelectionCount(), 3); // Toggle object 1 (should remove it) selection.SelectObject(1, ObjectSelection::SelectionMode::Toggle); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_FALSE(selection.IsObjectSelected(1)); EXPECT_TRUE(selection.IsObjectSelected(2)); EXPECT_EQ(selection.GetSelectionCount(), 2); } // ============================================================================ // Select All Tests // ============================================================================ TEST(ObjectSelectionTest, SelectAll) { ObjectSelection selection; selection.SelectAll(5); EXPECT_EQ(selection.GetSelectionCount(), 5); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(1)); EXPECT_TRUE(selection.IsObjectSelected(2)); EXPECT_TRUE(selection.IsObjectSelected(3)); EXPECT_TRUE(selection.IsObjectSelected(4)); } TEST(ObjectSelectionTest, SelectAllReplacesExisting) { ObjectSelection selection; // Select a few objects selection.SelectObject(0, ObjectSelection::SelectionMode::Single); selection.SelectObject(1, ObjectSelection::SelectionMode::Add); // Select all (should replace) selection.SelectAll(10); EXPECT_EQ(selection.GetSelectionCount(), 10); for (size_t i = 0; i < 10; ++i) { EXPECT_TRUE(selection.IsObjectSelected(i)); } } // ============================================================================ // Rectangle Selection Tests // ============================================================================ TEST(ObjectSelectionTest, RectangleSelectionSingleMode) { ObjectSelection selection; // Create test objects in a grid std::vector objects; objects.push_back(CreateTestObject(5, 5)); // 0 objects.push_back(CreateTestObject(10, 5)); // 1 objects.push_back(CreateTestObject(15, 5)); // 2 objects.push_back(CreateTestObject(5, 10)); // 3 objects.push_back(CreateTestObject(10, 10)); // 4 objects.push_back(CreateTestObject(15, 10)); // 5 // Select objects in rectangle (5,5) to (10,10) // Should select objects at (5,5), (10,5), (5,10), (10,10) selection.SelectObjectsInRect(5, 5, 10, 10, objects, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.IsObjectSelected(0)); // (5,5) EXPECT_TRUE(selection.IsObjectSelected(1)); // (10,5) EXPECT_FALSE(selection.IsObjectSelected(2)); // (15,5) - outside EXPECT_TRUE(selection.IsObjectSelected(3)); // (5,10) EXPECT_TRUE(selection.IsObjectSelected(4)); // (10,10) EXPECT_FALSE(selection.IsObjectSelected(5)); // (15,10) - outside EXPECT_EQ(selection.GetSelectionCount(), 4); } TEST(ObjectSelectionTest, RectangleSelectionAddMode) { ObjectSelection selection; std::vector objects; objects.push_back(CreateTestObject(5, 5)); objects.push_back(CreateTestObject(10, 5)); objects.push_back(CreateTestObject(20, 20)); // Initial selection selection.SelectObject(2, ObjectSelection::SelectionMode::Single); EXPECT_EQ(selection.GetSelectionCount(), 1); // Add rectangle selection selection.SelectObjectsInRect(0, 0, 15, 15, objects, ObjectSelection::SelectionMode::Add); // Should have object 2 plus objects in rectangle EXPECT_TRUE(selection.IsObjectSelected(0)); // In rectangle EXPECT_TRUE(selection.IsObjectSelected(1)); // In rectangle EXPECT_TRUE(selection.IsObjectSelected(2)); // Previous selection EXPECT_EQ(selection.GetSelectionCount(), 3); } TEST(ObjectSelectionTest, RectangleSelectionNormalizesCoordinates) { ObjectSelection selection; std::vector objects; objects.push_back(CreateTestObject(5, 5)); objects.push_back(CreateTestObject(10, 10)); // Select with inverted coordinates (bottom-right to top-left) selection.SelectObjectsInRect(10, 10, 5, 5, objects, ObjectSelection::SelectionMode::Single); EXPECT_TRUE(selection.IsObjectSelected(0)); EXPECT_TRUE(selection.IsObjectSelected(1)); EXPECT_EQ(selection.GetSelectionCount(), 2); } // ============================================================================ // Rectangle Selection State Tests // ============================================================================ TEST(ObjectSelectionTest, RectangleSelectionStateLifecycle) { ObjectSelection selection; EXPECT_FALSE(selection.IsRectangleSelectionActive()); selection.BeginRectangleSelection(10, 10); EXPECT_TRUE(selection.IsRectangleSelectionActive()); selection.UpdateRectangleSelection(50, 50); EXPECT_TRUE(selection.IsRectangleSelectionActive()); std::vector objects; selection.EndRectangleSelection(objects, ObjectSelection::SelectionMode::Single); EXPECT_FALSE(selection.IsRectangleSelectionActive()); } TEST(ObjectSelectionTest, RectangleSelectionCancel) { ObjectSelection selection; selection.BeginRectangleSelection(10, 10); EXPECT_TRUE(selection.IsRectangleSelectionActive()); selection.CancelRectangleSelection(); EXPECT_FALSE(selection.IsRectangleSelectionActive()); } TEST(ObjectSelectionTest, RectangleSelectionBounds) { ObjectSelection selection; selection.BeginRectangleSelection(10, 20); selection.UpdateRectangleSelection(50, 40); auto [min_x, min_y, max_x, max_y] = selection.GetRectangleSelectionBounds(); EXPECT_EQ(min_x, 10); EXPECT_EQ(min_y, 20); EXPECT_EQ(max_x, 50); EXPECT_EQ(max_y, 40); } TEST(ObjectSelectionTest, RectangleSelectionBoundsNormalized) { ObjectSelection selection; // Start at bottom-right, drag to top-left selection.BeginRectangleSelection(50, 40); selection.UpdateRectangleSelection(10, 20); auto [min_x, min_y, max_x, max_y] = selection.GetRectangleSelectionBounds(); // Should be normalized EXPECT_EQ(min_x, 10); EXPECT_EQ(min_y, 20); EXPECT_EQ(max_x, 50); EXPECT_EQ(max_y, 40); } // ============================================================================ // Get Selected Indices Tests // ============================================================================ TEST(ObjectSelectionTest, GetSelectedIndicesSorted) { ObjectSelection selection; // Select in random order selection.SelectObject(5, ObjectSelection::SelectionMode::Single); selection.SelectObject(2, ObjectSelection::SelectionMode::Add); selection.SelectObject(8, ObjectSelection::SelectionMode::Add); selection.SelectObject(1, ObjectSelection::SelectionMode::Add); auto indices = selection.GetSelectedIndices(); // Should be sorted ASSERT_EQ(indices.size(), 4); EXPECT_EQ(indices[0], 1); EXPECT_EQ(indices[1], 2); EXPECT_EQ(indices[2], 5); EXPECT_EQ(indices[3], 8); } TEST(ObjectSelectionTest, GetPrimarySelection) { ObjectSelection selection; // No selection EXPECT_FALSE(selection.GetPrimarySelection().has_value()); // Select objects selection.SelectObject(5, ObjectSelection::SelectionMode::Single); selection.SelectObject(2, ObjectSelection::SelectionMode::Add); // Primary should be lowest index EXPECT_EQ(selection.GetPrimarySelection().value(), 2); } // ============================================================================ // Coordinate Conversion Tests // ============================================================================ TEST(ObjectSelectionTest, RoomToCanvasCoordinates) { auto [canvas_x, canvas_y] = ObjectSelection::RoomToCanvasCoordinates(5, 10); EXPECT_EQ(canvas_x, 40); // 5 tiles * 8 pixels EXPECT_EQ(canvas_y, 80); // 10 tiles * 8 pixels } TEST(ObjectSelectionTest, CanvasToRoomCoordinates) { auto [room_x, room_y] = ObjectSelection::CanvasToRoomCoordinates(40, 80); EXPECT_EQ(room_x, 5); // 40 pixels / 8 EXPECT_EQ(room_y, 10); // 80 pixels / 8 } TEST(ObjectSelectionTest, CoordinateConversionRoundTrip) { int room_x = 15; int room_y = 20; auto [canvas_x, canvas_y] = ObjectSelection::RoomToCanvasCoordinates(room_x, room_y); auto [back_room_x, back_room_y] = ObjectSelection::CanvasToRoomCoordinates(canvas_x, canvas_y); EXPECT_EQ(back_room_x, room_x); EXPECT_EQ(back_room_y, room_y); } // ============================================================================ // Object Bounds Tests // ============================================================================ TEST(ObjectSelectionTest, GetObjectBoundsSingleTile) { auto obj = CreateTestObject(10, 15, 0x00); // size 0x00 = 1x1 tiles auto [x, y, width, height] = ObjectSelection::GetObjectBounds(obj); EXPECT_EQ(x, 10); EXPECT_EQ(y, 15); EXPECT_EQ(width, 1); EXPECT_EQ(height, 1); } TEST(ObjectSelectionTest, GetObjectBoundsMultipleTiles) { // size 0x23 = horizontal 3+1=4 tiles, vertical 2+1=3 tiles auto obj = CreateTestObject(5, 8, 0x23); auto [x, y, width, height] = ObjectSelection::GetObjectBounds(obj); EXPECT_EQ(x, 5); EXPECT_EQ(y, 8); EXPECT_EQ(width, 4); // (0x3 & 0x0F) + 1 EXPECT_EQ(height, 3); // ((0x23 >> 4) & 0x0F) + 1 } // ============================================================================ // Callback Tests // ============================================================================ TEST(ObjectSelectionTest, SelectionChangedCallback) { ObjectSelection selection; int callback_count = 0; selection.SetSelectionChangedCallback([&callback_count]() { callback_count++; }); selection.SelectObject(0, ObjectSelection::SelectionMode::Single); EXPECT_EQ(callback_count, 1); selection.SelectObject(1, ObjectSelection::SelectionMode::Add); EXPECT_EQ(callback_count, 2); selection.ClearSelection(); EXPECT_EQ(callback_count, 3); } } // namespace } // namespace yaze::editor