#include "gtest/gtest.h" #include "lumacs/window_manager.hpp" #include "lumacs/editor_core.hpp" #include "lumacs/window.hpp" #include "lumacs/layout_node.hpp" #include #include #include // For std::iota using namespace lumacs; // Fixture for WindowManager tests class WindowManagerTest : public ::testing::Test { protected: std::unique_ptr core; WindowManager* wm; // Raw pointer for convenience, owned by core void SetUp() override { // Instantiate EditorCore which in turn initializes WindowManager and BufferManager core = std::make_unique(); wm = &core->window_manager(); // Get reference to the manager owned by core // Ensure a clean slate for some tests by explicitly creating a new scratch buffer // if the default one from init.lua is not suitable. // For these tests, we mostly care about window *layout*, not buffer content. core->buffer().clear(); core->set_cursor({0,0}); } void TearDown() override { core.reset(); } // Helper to count windows in the layout tree size_t count_windows_in_layout() { std::vector> windows; core->collect_windows(wm->root_layout().get(), windows); return windows.size(); } }; TEST_F(WindowManagerTest, InitialState) { // Initially, there should be one active window ASSERT_NE(wm->active_window(), nullptr); ASSERT_EQ(count_windows_in_layout(), 1); ASSERT_EQ(wm->root_layout()->type, LayoutNode::Type::Leaf); ASSERT_EQ(wm->root_layout()->window, wm->active_window()); } TEST_F(WindowManagerTest, SplitHorizontally) { auto original_active_window = wm->active_window(); wm->split_horizontally(); // Should now have two windows ASSERT_EQ(count_windows_in_layout(), 2); ASSERT_EQ(wm->root_layout()->type, LayoutNode::Type::HorizontalSplit); ASSERT_NE(wm->root_layout()->child1, nullptr); ASSERT_NE(wm->root_layout()->child2, nullptr); // New window should be active ASSERT_NE(wm->active_window(), original_active_window); } TEST_F(WindowManagerTest, SplitVertically) { auto original_active_window = wm->active_window(); wm->split_vertically(); // Should now have two windows ASSERT_EQ(count_windows_in_layout(), 2); ASSERT_EQ(wm->root_layout()->type, LayoutNode::Type::VerticalSplit); ASSERT_NE(wm->root_layout()->child1, nullptr); ASSERT_NE(wm->root_layout()->child2, nullptr); // New window should be active ASSERT_NE(wm->active_window(), original_active_window); } TEST_F(WindowManagerTest, CloseActiveWindowMergesLayout) { wm->split_horizontally(); // Creates 2 windows ASSERT_EQ(count_windows_in_layout(), 2); auto window_before_close = wm->active_window(); wm->close_active_window(); // Closes the newly created window ASSERT_EQ(count_windows_in_layout(), 1); // Should merge back to one window ASSERT_NE(wm->active_window(), window_before_close); // Active window should be the remaining one ASSERT_EQ(wm->root_layout()->type, LayoutNode::Type::Leaf); } TEST_F(WindowManagerTest, CloseLastWindowDoesNothing) { ASSERT_EQ(count_windows_in_layout(), 1); auto original_active = wm->active_window(); wm->close_active_window(); // Attempt to close the only window ASSERT_EQ(count_windows_in_layout(), 1); // Should still be one window ASSERT_EQ(wm->active_window(), original_active); // Should still be the same window } TEST_F(WindowManagerTest, NextWindowCyclesFocus) { wm->split_horizontally(); // Window 1 (original), Window 2 (newly active) auto w1 = wm->root_layout()->child1->window; // Original window auto w2 = wm->root_layout()->child2->window; // New window, active ASSERT_EQ(wm->active_window(), w2); wm->next_window(); // Should cycle to w1 ASSERT_EQ(wm->active_window(), w1); wm->next_window(); // Should cycle back to w2 ASSERT_EQ(wm->active_window(), w2); } TEST_F(WindowManagerTest, ComplexSplitAndClose) { // Initial: W1 wm->split_vertically(); // Root: |W1|W2| (W2 active) wm->split_horizontally(); // Root: |W1| [W3;W2] (W2 active remains W2, so W3 active) // The previous split makes the new window active. So if W2 was active, after split_horizontally on W2, W3 is active. // Layout: |W1| // ---- // |W3| // ---- (W3 active) // |W2| ASSERT_EQ(count_windows_in_layout(), 3); auto w1 = wm->root_layout()->child1->window; auto w2_node = wm->root_layout()->child2; // This is a split node auto w3 = w2_node->child1->window; auto w2 = w2_node->child2->window; wm->next_window(); // Should cycle to W1 (or W2, depends on traversal order) wm->next_window(); // Cycle again wm->close_active_window(); // Close the active window (W2 or W3) ASSERT_EQ(count_windows_in_layout(), 2); // Should have 2 windows left }