Files
XCEngine/tests/NewEditor/test_xcui_editor_command_router.cpp

206 lines
8.2 KiB
C++

#include "XCUIBackend/XCUIEditorCommandRouter.h"
#include <XCEngine/Input/InputTypes.h>
#include <gtest/gtest.h>
namespace {
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandAccelerator;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandDefinition;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandInputSnapshot;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandKeyState;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandRouter;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandShortcutMatch;
using XCEngine::Editor::XCUIBackend::XCUIEditorCommandShortcutQuery;
using XCEngine::Input::KeyCode;
XCUIEditorCommandKeyState MakeKeyState(std::int32_t keyCode, bool down, bool repeat = false) {
XCUIEditorCommandKeyState state = {};
state.keyCode = keyCode;
state.down = down;
state.repeat = repeat;
return state;
}
XCUIEditorCommandInputSnapshot MakeSnapshot(
std::initializer_list<XCUIEditorCommandKeyState> keys,
const XCEngine::UI::UIInputModifiers& modifiers = {},
bool windowFocused = true) {
XCUIEditorCommandInputSnapshot snapshot = {};
snapshot.keys.assign(keys.begin(), keys.end());
snapshot.modifiers = modifiers;
snapshot.windowFocused = windowFocused;
return snapshot;
}
TEST(XCUIEditorCommandRouterTest, RegisterInvokeAndUnregisterTrackCommandsById) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.file.save";
definition.invoke = [&invokeCount]() { ++invokeCount; };
EXPECT_TRUE(router.RegisterCommand(definition));
EXPECT_EQ(router.GetCommandCount(), 1u);
EXPECT_TRUE(router.HasCommand("xcui.file.save"));
EXPECT_TRUE(router.IsCommandEnabled("xcui.file.save"));
EXPECT_TRUE(router.InvokeCommand("xcui.file.save"));
EXPECT_EQ(invokeCount, 1);
EXPECT_TRUE(router.UnregisterCommand("xcui.file.save"));
EXPECT_FALSE(router.HasCommand("xcui.file.save"));
EXPECT_FALSE(router.InvokeCommand("xcui.file.save"));
EXPECT_EQ(router.GetCommandCount(), 0u);
}
TEST(XCUIEditorCommandRouterTest, RegisterRejectsMissingIdOrHandlerAndDuplicateIdReplacesEntry) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCUIEditorCommandDefinition invalid = {};
invalid.commandId = "xcui.invalid";
EXPECT_FALSE(router.RegisterCommand(invalid));
invalid = {};
invalid.invoke = []() {};
EXPECT_FALSE(router.RegisterCommand(invalid));
XCUIEditorCommandDefinition first = {};
first.commandId = "xcui.edit.rename";
first.invoke = [&invokeCount]() { invokeCount += 1; };
first.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::F2), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(first));
XCUIEditorCommandDefinition replacement = {};
replacement.commandId = "xcui.edit.rename";
replacement.invoke = [&invokeCount]() { invokeCount += 10; };
replacement.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::Enter), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(replacement));
EXPECT_EQ(router.GetCommandCount(), 1u);
EXPECT_TRUE(router.InvokeCommand("xcui.edit.rename"));
EXPECT_EQ(invokeCount, 10);
const XCUIEditorCommandInputSnapshot f2Snapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::F2), true) });
const auto f2Match = router.MatchShortcut({ &f2Snapshot });
EXPECT_FALSE(f2Match.matched);
const XCUIEditorCommandInputSnapshot enterSnapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::Enter), true) });
const auto enterMatch = router.MatchShortcut({ &enterSnapshot });
EXPECT_TRUE(enterMatch.matched);
EXPECT_EQ(enterMatch.commandId, "xcui.edit.rename");
}
TEST(XCUIEditorCommandRouterTest, DisabledPredicateBlocksDirectAndShortcutInvocation) {
XCUIEditorCommandRouter router = {};
bool enabled = false;
int invokeCount = 0;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.edit.delete";
definition.invoke = [&invokeCount]() { ++invokeCount; };
definition.isEnabled = [&enabled]() { return enabled; };
definition.accelerators.push_back({ static_cast<std::int32_t>(KeyCode::Delete), {}, true, false });
ASSERT_TRUE(router.RegisterCommand(definition));
const XCUIEditorCommandInputSnapshot snapshot =
MakeSnapshot({ MakeKeyState(static_cast<std::int32_t>(KeyCode::Delete), true) });
EXPECT_FALSE(router.IsCommandEnabled("xcui.edit.delete"));
EXPECT_FALSE(router.InvokeCommand("xcui.edit.delete"));
EXPECT_FALSE(router.MatchShortcut({ &snapshot }).matched);
EXPECT_FALSE(router.InvokeMatchingShortcut({ &snapshot }));
EXPECT_EQ(invokeCount, 0);
enabled = true;
EXPECT_TRUE(router.IsCommandEnabled("xcui.edit.delete"));
EXPECT_TRUE(router.InvokeMatchingShortcut({ &snapshot }));
EXPECT_EQ(invokeCount, 1);
}
TEST(XCUIEditorCommandRouterTest, ShortcutMatchingRespectsModifiersRepeatAndPolicyFlags) {
XCUIEditorCommandRouter router = {};
int invokeCount = 0;
XCEngine::UI::UIInputModifiers modifiers = {};
modifiers.control = true;
modifiers.shift = true;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.search.command_palette";
definition.invoke = [&invokeCount]() { ++invokeCount; };
definition.accelerators.push_back({
static_cast<std::int32_t>(KeyCode::P),
modifiers,
true,
false });
ASSERT_TRUE(router.RegisterCommand(definition));
const XCUIEditorCommandInputSnapshot exactSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::P), true) },
modifiers);
auto match = router.MatchShortcut({ &exactSnapshot });
ASSERT_TRUE(match.matched);
EXPECT_EQ(match.commandId, "xcui.search.command_palette");
const XCUIEditorCommandInputSnapshot repeatedSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::P), true, true) },
modifiers);
EXPECT_FALSE(router.MatchShortcut({ &repeatedSnapshot }).matched);
XCUIEditorCommandInputSnapshot captureSnapshot = exactSnapshot;
captureSnapshot.wantCaptureKeyboard = true;
EXPECT_FALSE(router.MatchShortcut({ &captureSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &captureSnapshot, true, true, false }).matched);
XCUIEditorCommandInputSnapshot textInputSnapshot = exactSnapshot;
textInputSnapshot.wantTextInput = true;
EXPECT_FALSE(router.MatchShortcut({ &textInputSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &textInputSnapshot, true, false, true }).matched);
XCUIEditorCommandInputSnapshot unfocusedSnapshot = exactSnapshot;
unfocusedSnapshot.windowFocused = false;
EXPECT_FALSE(router.MatchShortcut({ &unfocusedSnapshot }).matched);
EXPECT_TRUE(router.MatchShortcut({ &unfocusedSnapshot, false, false, false }).matched);
XCUIEditorCommandShortcutMatch invokedMatch = {};
EXPECT_TRUE(router.InvokeMatchingShortcut({ &exactSnapshot }, &invokedMatch));
EXPECT_TRUE(invokedMatch.matched);
EXPECT_EQ(invokedMatch.commandId, "xcui.search.command_palette");
EXPECT_EQ(invokeCount, 1);
}
TEST(XCUIEditorCommandRouterTest, NonExactModifierAcceleratorsAllowAdditionalPressedModifiers) {
XCUIEditorCommandRouter router = {};
XCEngine::UI::UIInputModifiers requiredModifiers = {};
requiredModifiers.control = true;
XCUIEditorCommandDefinition definition = {};
definition.commandId = "xcui.edit.duplicate";
definition.invoke = []() {};
definition.accelerators.push_back({
static_cast<std::int32_t>(KeyCode::D),
requiredModifiers,
false,
false });
ASSERT_TRUE(router.RegisterCommand(definition));
XCEngine::UI::UIInputModifiers actualModifiers = requiredModifiers;
actualModifiers.shift = true;
const XCUIEditorCommandInputSnapshot permissiveSnapshot = MakeSnapshot(
{ MakeKeyState(static_cast<std::int32_t>(KeyCode::D), true) },
actualModifiers);
EXPECT_TRUE(router.MatchShortcut({ &permissiveSnapshot }).matched);
XCUIEditorCommandRouter exactRouter = {};
definition.accelerators.front().exactModifiers = true;
ASSERT_TRUE(exactRouter.RegisterCommand(definition));
EXPECT_FALSE(exactRouter.MatchShortcut({ &permissiveSnapshot }).matched);
}
} // namespace