Files
radiantwave/tests/test_keybind_manager.cpp
2026-08-07 14:51:41 -05:00

141 lines
3.9 KiB
C++

#include "test_harness.h"
#include "../src/keybind_manager.h"
// Build a SDL_KEYDOWN event for the given key + modifier set.
static SDL_Event keyDownEvent(SDL_Keycode key, SDL_Keymod mod)
{
SDL_Event ev{};
ev.type = SDL_EVENT_KEY_DOWN;
ev.key.key = key;
ev.key.mod = mod;
return ev;
}
TEST(bare_bind_fires_without_modifiers)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_RETURN, "enter", [&calls]() { calls++; });
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_RETURN, SDL_KMOD_NONE)));
ASSERT_EQ(calls, 1);
}
TEST(bare_bind_ignores_modifiers)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_RETURN, "enter", [&calls]() { calls++; });
// A plain bind fires even while a modifier is held.
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_RETURN, SDL_KMOD_CTRL)));
ASSERT_EQ(calls, 1);
}
TEST(combo_requires_modifier)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
// Without Ctrl held the combo must not fire.
ASSERT_FALSE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_NONE)));
ASSERT_EQ(calls, 0);
// With Ctrl held it fires.
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_CTRL)));
ASSERT_EQ(calls, 1);
}
// Regression: a physical press only sets ONE side of a paired modifier
// (e.g. SDL_KMOD_LCTRL), never the combined SDL_KMOD_CTRL mask. The raw
// bitwise check (pressed & required) == required therefore never matched and
// Ctrl+D silently did nothing on real hardware.
TEST(combo_fires_with_single_ctrl_key)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_LCTRL)));
ASSERT_EQ(calls, 1);
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_RCTRL)));
ASSERT_EQ(calls, 2);
}
TEST(combo_tolerates_extra_modifiers)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
// CapsLock being stuck on should not block the combo.
const SDL_Keymod extra = static_cast<SDL_Keymod>(SDL_KMOD_CTRL | SDL_KMOD_CAPS);
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_D, extra)));
ASSERT_EQ(calls, 1);
}
TEST(combo_partial_modifiers_do_not_fire)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, static_cast<SDL_Keymod>(SDL_KMOD_CTRL | SDL_KMOD_SHIFT), "clear", [&calls]() { calls++; });
// Only Ctrl held, Shift missing: must not fire.
ASSERT_FALSE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_CTRL)));
ASSERT_EQ(calls, 0);
// Both held: fires.
ASSERT_TRUE(kb.handleEvent(keyDownEvent(SDLK_D, static_cast<SDL_Keymod>(SDL_KMOD_CTRL | SDL_KMOD_SHIFT))));
ASSERT_EQ(calls, 1);
}
TEST(unbind_removes_binding)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
kb.unbind(SDLK_D);
ASSERT_FALSE(kb.isBound(SDLK_D));
ASSERT_FALSE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_CTRL)));
ASSERT_EQ(calls, 0);
}
TEST(unbind_all_removes_bindings)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
kb.bind(SDLK_UP, "up", [&calls]() { calls++; });
kb.unbindAll();
ASSERT_FALSE(kb.isBound(SDLK_D));
ASSERT_FALSE(kb.isBound(SDLK_UP));
ASSERT_FALSE(kb.handleEvent(keyDownEvent(SDLK_D, SDL_KMOD_CTRL)));
ASSERT_EQ(calls, 0);
}
TEST(is_bound_reflects_bindings)
{
KeybindManager kb;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", []() {});
ASSERT_TRUE(kb.isBound(SDLK_D));
ASSERT_FALSE(kb.isBound(SDLK_UP));
}
TEST(non_keydown_event_is_ignored)
{
KeybindManager kb;
int calls = 0;
kb.bind(SDLK_D, SDL_KMOD_CTRL, "clear", [&calls]() { calls++; });
SDL_Event ev{};
ev.type = SDL_EVENT_KEY_UP;
ev.key.key = SDLK_D;
ev.key.mod = SDL_KMOD_CTRL;
ASSERT_FALSE(kb.handleEvent(ev));
ASSERT_EQ(calls, 0);
}