| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #include "gtest/gtest.h"
- #include "lumacs/command_system.hpp"
- #include "lumacs/editor_core.hpp"
- #include "lumacs/minibuffer_manager.hpp" // For interactive commands
- #include <string>
- #include <vector>
- #include <memory>
- #include <algorithm> // For std::find
- using namespace lumacs;
- // Test fixture for CommandSystem tests
- class CommandSystemTest : public ::testing::Test {
- protected:
- EditorCore core;
- CommandSystem* command_system; // Raw pointer to manager owned by core
- void SetUp() override {
- // EditorCore's constructor performs full initialization.
- // We get references to the managers owned by 'core'.
- command_system = &core.command_system();
-
- // Ensure initial clean state by clearing all commands registered by init.lua (if any)
- command_system->clear_commands();
- // Clear buffer and reset cursor
- if (core.buffer().line_count() > 0) {
- core.buffer().clear();
- }
- core.set_cursor({0,0});
- }
- void TearDown() override {
- // Nothing specific needed. core will clean up its managers.
- }
- };
- TEST_F(CommandSystemTest, RegisterAndExecuteBasicCommand) {
- bool command_executed = false;
- command_system->register_command("test-command",
- [&command_executed](CommandContext& ctx) -> CommandResult {
- (void)ctx; // Mark ctx as unused
- command_executed = true;
- return {CommandStatus::Success, "Command executed"};
- },
- "A simple test command.");
-
- CommandResult result = command_system->execute("test-command", {});
- ASSERT_EQ(result.status, CommandStatus::Success);
- ASSERT_EQ(result.message, "Command executed");
- ASSERT_TRUE(command_executed);
- }
- TEST_F(CommandSystemTest, ExecuteNonExistentCommand) {
- CommandResult result = command_system->execute("non-existent-command", {});
- ASSERT_EQ(result.status, CommandStatus::Failure);
- ASSERT_EQ(result.message, "Unknown command or alias: non-existent-command");
- }
- TEST_F(CommandSystemTest, RegisterWithAliasAndExecute) {
- bool command_executed = false;
- command_system->register_command("aliased-command",
- [&command_executed](CommandContext& ctx) -> CommandResult {
- (void)ctx; // Mark ctx as unused
- command_executed = true;
- return {CommandStatus::Success, "Aliased command executed"};
- },
- "A command with an alias.", false, "", {"alias1", "alias2"});
-
- CommandResult result = command_system->execute("alias1", {});
- ASSERT_EQ(result.status, CommandStatus::Success);
- ASSERT_EQ(result.message, "Aliased command executed");
- ASSERT_TRUE(command_executed);
- command_executed = false; // Reset for next execution
- result = command_system->execute("alias2", {});
- ASSERT_EQ(result.status, CommandStatus::Success);
- ASSERT_EQ(result.message, "Aliased command executed");
- ASSERT_TRUE(command_executed);
- // Should also work with canonical name
- command_executed = false;
- result = command_system->execute("aliased-command", {});
- ASSERT_EQ(result.status, CommandStatus::Success);
- ASSERT_EQ(result.message, "Aliased command executed");
- ASSERT_TRUE(command_executed);
- }
- TEST_F(CommandSystemTest, RegisterCommandOverwrite) {
- bool command_executed_v1 = false;
- command_system->register_command("overwrite-cmd",
- [&command_executed_v1](CommandContext& ctx) -> CommandResult {
- (void)ctx;
- command_executed_v1 = true;
- return {CommandStatus::Success, "V1"};
- });
-
- // Overwrite with V2
- bool command_executed_v2 = false;
- command_system->register_command("overwrite-cmd",
- [&command_executed_v2](CommandContext& ctx) -> CommandResult {
- (void)ctx;
- command_executed_v2 = true;
- return {CommandStatus::Success, "V2"};
- });
- CommandResult result = command_system->execute("overwrite-cmd", {});
- ASSERT_EQ(result.status, CommandStatus::Success);
- ASSERT_EQ(result.message, "V2");
- ASSERT_FALSE(command_executed_v1); // V1 should not have been called
- ASSERT_TRUE(command_executed_v2); // V2 should have been called
- }
- TEST_F(CommandSystemTest, AliasConflictResolution) {
- // Register a command that will be canonical
- command_system->register_command("master-cmd",
- [](CommandContext& ctx) {
- (void)ctx;
- return CommandResult{CommandStatus::Success, ""};
- },
- "Master command doc.", false, "", {"alias-conflict"});
- // Try to register another command whose alias conflicts with an existing canonical command
- // Note: The current implementation logs a warning to cerr, which we don't capture here,
- // but we verify the state of command registration.
- command_system->register_command("other-cmd",
- [](CommandContext& ctx) {
- (void)ctx;
- return CommandResult{CommandStatus::Success, ""};
- },
- "Other command doc.", false, "", {"alias-conflict"}); // "alias-conflict" clashes with "master-cmd" alias
- std::vector<std::string> names = command_system->get_command_names();
- ASSERT_TRUE(std::find(names.begin(), names.end(), "master-cmd") != names.end());
- ASSERT_TRUE(std::find(names.begin(), names.end(), "other-cmd") != names.end());
- ASSERT_TRUE(std::find(names.begin(), names.end(), "alias-conflict") != names.end());
- }
- TEST_F(CommandSystemTest, GetCommandNames) {
- command_system->register_command("cmd-a",
- [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; },
- "Cmd A");
- command_system->register_command("cmd-b",
- [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; },
- "Cmd B", false, "", {"alias-b"});
-
- std::vector<std::string> names = command_system->get_command_names();
-
- ASSERT_EQ(names.size(), 3);
- ASSERT_TRUE(std::find(names.begin(), names.end(), "cmd-a") != names.end());
- ASSERT_TRUE(std::find(names.begin(), names.end(), "cmd-b") != names.end());
- ASSERT_TRUE(std::find(names.begin(), names.end(), "alias-b") != names.end());
- }
- TEST_F(CommandSystemTest, GetCommandDocString) {
- command_system->register_command("doc-cmd",
- [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; },
- "This is documentation.");
-
- std::optional<std::string> doc = command_system->get_command_doc_string("doc-cmd");
- ASSERT_TRUE(doc.has_value());
- ASSERT_EQ(doc.value(), "This is documentation.");
- // For alias
- command_system->register_command("cmd-alias",
- [](CommandContext&){ return CommandResult{CommandStatus::Success, ""}; },
- "Doc for alias", false, "", {"alias-for-doc"});
-
- doc = command_system->get_command_doc_string("alias-for-doc");
- ASSERT_TRUE(doc.has_value());
- ASSERT_EQ(doc.value(), "Doc for alias");
- // Non-existent
- doc = command_system->get_command_doc_string("non-existent");
- ASSERT_FALSE(doc.has_value());
- }
- // Tests for CommandContext argument accessors
- TEST_F(CommandSystemTest, CommandContextGetStringArg) {
- // Use the fixture's core.minibuffer_manager()
- CommandContext ctx(core, core.minibuffer_manager(), {"arg1", "arg2"});
- ASSERT_EQ(ctx.get_string_arg(0).value(), "arg1");
- ASSERT_EQ(ctx.get_string_arg(1).value(), "arg2");
- ASSERT_FALSE(ctx.get_string_arg(2).has_value()); // Out of bounds
- }
- TEST_F(CommandSystemTest, CommandContextGetIntArg) {
- // Use the fixture's core.minibuffer_manager()
- CommandContext ctx(core, core.minibuffer_manager(), {"123", "-45", "not-a-number", "2147483648"}); // 2^31 for out of int range
- ASSERT_EQ(ctx.get_int_arg(0).value(), 123);
- ASSERT_EQ(ctx.get_int_arg(1).value(), -45);
- ASSERT_FALSE(ctx.get_int_arg(2).has_value()); // Invalid argument
- // Test for out of range (std::stoull can handle it, but stoi might throw)
- // std::stoi throws std::out_of_range for 2147483648
- ASSERT_FALSE(ctx.get_int_arg(3).has_value());
- ASSERT_FALSE(ctx.get_int_arg(4).has_value()); // Out of bounds
- // Ensure the message set by stoi failure is not propagating or crashing
- }
- TEST_F(CommandSystemTest, ExecuteInteractiveBasic) {
- // Register a simple interactive command that expects one string argument
- command_system->register_command("interactive-string-cmd",
- [](CommandContext& ctx) -> CommandResult {
- auto arg = ctx.get_string_arg(0);
- if (arg) {
- return {CommandStatus::Success, "Got string: " + arg.value()};
- }
- return {CommandStatus::Failure, "No argument provided"};
- },
- "An interactive command.", true, "s"); // 's' for string argument
- // Execute interactive command - should return PendingInput
- CommandResult result = command_system->execute_interactive("interactive-string-cmd");
- ASSERT_EQ(result.status, CommandStatus::PendingInput);
- ASSERT_EQ(result.message, "Waiting for user input.");
- // MinibufferManager should now be active and waiting for input
- ASSERT_TRUE(core.minibuffer_manager().is_active());
- ASSERT_EQ(core.minibuffer_manager().get_prompt(), "String: ");
- // Simulate input via MinibufferManager
- core.minibuffer_manager().handle_key_event("t");
- core.minibuffer_manager().handle_key_event("e");
- core.minibuffer_manager().handle_key_event("s");
- core.minibuffer_manager().handle_key_event("t");
- core.minibuffer_manager().handle_key_event("Return"); // Submit
- // After submission, the command should have executed
- ASSERT_FALSE(core.minibuffer_manager().is_active());
-
- // The result is communicated via side-effects (EditorCore message) since it's async
- ASSERT_EQ(core.last_message(), "Got string: test");
- }
- TEST_F(CommandSystemTest, ExecuteInteractiveCancel) {
- command_system->register_command("interactive-cancel-cmd",
- [](CommandContext& ctx) -> CommandResult {
- (void)ctx; // Mark ctx as unused
- return {CommandStatus::Success, "Should not be executed"};
- },
- "An interactive command that can be cancelled.", true, "s");
- CommandResult result = command_system->execute_interactive("interactive-cancel-cmd");
- ASSERT_EQ(result.status, CommandStatus::PendingInput);
- // Simulate cancel via MinibufferManager
- core.minibuffer_manager().handle_key_event("Escape"); // Cancel
- ASSERT_FALSE(core.minibuffer_manager().is_active());
-
- // We expect the command NOT to be executed
- ASSERT_NE(core.last_message(), "Should not be executed");
- }
|