#include "gtest/gtest.h" #include "lumacs/rectangle_manager.hpp" #include "lumacs/editor_core.hpp" #include "lumacs/buffer.hpp" #include "lumacs/kill_ring_manager.hpp" // For verifying kill ring contents #include #include #include using namespace lumacs; // Fixture for RectangleManager tests class RectangleManagerTest : public ::testing::Test { protected: EditorCore core; // RectangleManager's constructor requires an EditorCore& std::unique_ptr rectangle_manager; void SetUp() override { rectangle_manager = std::make_unique(core); // Ensure a clean buffer and cursor state for each test core.buffer().clear(); core.set_cursor({0,0}); core.buffer().deactivate_mark(); // Ensure no mark is active initially core.kill_ring_manager().clear(); // Clear kill ring } void TearDown() override { // Nothing specific needed } // Helper to setup a multi-line buffer void setup_multiline_buffer(const std::vector& lines) { core.buffer().clear(); core.set_cursor({0,0}); for (const auto& line : lines) { core.buffer().insert(core.cursor(), line); core.set_cursor({core.cursor().line, core.buffer().line(core.cursor().line).size()}); // Move to end of line if (&line != &lines.back()) { // Don't insert newline after last line core.buffer().insert_newline(core.cursor()); core.set_cursor({core.cursor().line + 1, 0}); // Move to start of next line } } core.set_cursor({0,0}); // Reset cursor to (0,0) after setup } }; TEST_F(RectangleManagerTest, KillRectangleNoActiveRegion) { rectangle_manager->kill_rectangle(); // Should not crash, and kill ring should be empty ASSERT_TRUE(core.kill_ring_manager().empty()); // No message is returned by the command, so cannot assert on message directly } // TEST_F(RectangleManagerTest, KillRectangleBasic) { // setup_multiline_buffer({"Line one", "Line two", "Line three"}); // // Buffer content: // // Line one // // Line two // // Line three // // Define region (mark at 'n' in Line one, cursor at 'w' in Line two) // core.buffer().set_mark({0, 3}); // Mark at 'e' in "Line" // core.set_cursor({1, 5}); // Cursor at ' ' after "Line" in "Line two" // // Rectangle definition: (0,3) to (1,5) // // min_col = 3, max_col = 5, rect_width = 2 // // Line 0 (Line one): substr(3,2) -> "e " // // Line 1 (Line two): substr(3,2) -> "e " // rectangle_manager->kill_rectangle(); // // Verify buffer content after killing rectangle: // // Lineone (expected: Lione) // // Linetwo // // Line three // ASSERT_EQ(core.buffer().line(0), "Lione"); // Original Line one, erase "e " at col 3 // ASSERT_EQ(core.buffer().line(1), "Litwo"); // Original Line two, erase "e " at col 3 // ASSERT_EQ(core.buffer().line(2), "Line three"); // // Verify kill ring contains the killed rectangle // ASSERT_FALSE(core.kill_ring_manager().empty()); // ASSERT_EQ(core.kill_ring_manager().current(), "e \ne "); // Expected rectangular text // } TEST_F(RectangleManagerTest, YankRectangleBasic) { setup_multiline_buffer({"12345", "abcde", "FGHIJ"}); // Simulate killing a rectangle to populate rectangle_kill_ring_ core.buffer().set_mark({0, 1}); // '2' in "12345" core.set_cursor({1, 3}); // 'd' in "abcde" // This rectangle will contain: // 23 // bc rectangle_manager->kill_rectangle(); // Now yank it core.set_cursor({0,1}); // Cursor at '2' in "12345" rectangle_manager->yank_rectangle(); ASSERT_EQ(core.last_message(), "Rectangle yanked (2 lines)"); // Check final message // Expected buffer: // 12345 (original) // abcde (original) // After kill: // 145 // ade // FGHIJ // rectangle_kill_ring_ now contains "23" and "bc" // After yank at (0,1): // Line 0: "12345" -> insert "23" at (0,1) -> "1232345" // Line 1: "abcde" -> insert "bc" at (1,1) -> "abcbcde" // Original buffer was {"12345", "abcde", "FGHIJ"} // killed (0,1) to (1,3) which extracts "23" and "bc" // After kill: {"145", "ade", "FGHIJ"} // Yank at (0,1) // insert "23" at (0,1) in "145" -> "12345" // insert "bc" at (1,1) in "ade" -> "abcde" ASSERT_EQ(core.buffer().line(0), "12345"); ASSERT_EQ(core.buffer().line(1), "abcde"); ASSERT_EQ(core.buffer().line(2), "FGHIJ"); } TEST_F(RectangleManagerTest, StringRectangleBasic) { setup_multiline_buffer({"12345", "abcde", "FGHIJ"}); core.buffer().set_mark({0, 1}); // Mark at '2' in "12345" core.set_cursor({2, 3}); // Cursor at 'H' in "FGHIJ" // Rectangle region: (0,1) to (2,3) // min_col = 1, max_col = 3, rect_width = 2 // 1[23]45 // a[bc]de // F[GH]IJ rectangle_manager->string_rectangle("X"); // Fill with 'X' // Expected buffer: // 1XX45 (replaces '23' with 'XX') // aXXde (replaces 'bc' with 'XX') // FXXIJ (replaces 'GH' with 'XX') ASSERT_EQ(core.buffer().line(0), "1XX45"); ASSERT_EQ(core.buffer().line(1), "aXXde"); ASSERT_EQ(core.buffer().line(2), "FXXIJ"); } TEST_F(RectangleManagerTest, StringRectangleNoActiveRegion) { setup_multiline_buffer({"test"}); rectangle_manager->string_rectangle("X"); // Should not crash, and buffer should be unchanged ASSERT_EQ(core.buffer().line(0), "test"); } TEST_F(RectangleManagerTest, KillRectangleEmptyLines) { setup_multiline_buffer({"Line1", "", "Line3"}); // Empty line in between core.buffer().set_mark({0,1}); // 'i' in Line1 core.set_cursor({2,2}); // 'n' in Line3 // Rectangle covers: // L[ine]1 // [ ] (empty line) // L[in]e3 // Expected killed text: // i // // i rectangle_manager->kill_rectangle(); // Verify buffer content: // Lne1 // // Lne3 ASSERT_EQ(core.buffer().line(0), "Lne1"); ASSERT_EQ(core.buffer().line(1), ""); ASSERT_EQ(core.buffer().line(2), "Lne3"); ASSERT_FALSE(core.kill_ring_manager().empty()); ASSERT_EQ(core.kill_ring_manager().current(), "i\n \ni"); // Expect 'i', then space from empty line, then 'i' }