From dd7845d847a3d6f961f0302551c33245d1f12bdc Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 2 Aug 2026 17:02:18 +0200 Subject: feat: add KeyMap with defaults and INI overrides Maps key sequences to action name strings, with hardcoded vim-style defaults and QSettings-based [keys] overrides. Unknown actions and unparseable sequences are collected as warnings rather than treated as fatal, so a typo in the config cannot silently misbind or crash. Note: QKeySequence::fromString() on Qt 6.11 does not return an empty sequence for unparseable input (e.g. "NotAKey++") -- it returns a non-empty sequence whose toString() is empty. Detection uses that instead of isEmpty(). --- tests/test_keymap.cpp | 131 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 tests/test_keymap.cpp (limited to 'tests/test_keymap.cpp') diff --git a/tests/test_keymap.cpp b/tests/test_keymap.cpp new file mode 100644 index 0000000..10863f2 --- /dev/null +++ b/tests/test_keymap.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include "keymap.h" + +class TestKeyMap : public QObject +{ + Q_OBJECT +private slots: + void defaultsAreLoaded(); + void iniOverridesDefault(); + void iniAddsNewBinding(); + void chordSequenceParses(); + void unknownActionIsReported(); + void invalidSequenceIsReported(); +}; + +void TestKeyMap::defaultsAreLoaded() +{ + KeyMap map; + map.loadDefaults(); + QCOMPARE(map.actionFor(QKeySequence(QStringLiteral("j"))), + QStringLiteral("next_thread")); + QCOMPARE(map.actionFor(QKeySequence(QStringLiteral("a"))), + QStringLiteral("archive")); +} + +void TestKeyMap::iniOverridesDefault() +{ + QTemporaryDir dir; + const QString path = dir.filePath(QStringLiteral("t.conf")); + { + QSettings s(path, QSettings::IniFormat); + s.beginGroup(QStringLiteral("keys")); + s.setValue(QStringLiteral("j"), QStringLiteral("archive")); + s.endGroup(); + } + + KeyMap map; + map.loadDefaults(); + QSettings s(path, QSettings::IniFormat); + map.loadOverrides(s); + + QCOMPARE(map.actionFor(QKeySequence(QStringLiteral("j"))), + QStringLiteral("archive")); + // An untouched default survives. + QCOMPARE(map.actionFor(QKeySequence(QStringLiteral("k"))), + QStringLiteral("prev_thread")); +} + +void TestKeyMap::iniAddsNewBinding() +{ + QTemporaryDir dir; + const QString path = dir.filePath(QStringLiteral("t.conf")); + { + QSettings s(path, QSettings::IniFormat); + s.beginGroup(QStringLiteral("keys")); + s.setValue(QStringLiteral("Ctrl+Shift+A"), QStringLiteral("archive")); + s.endGroup(); + } + + KeyMap map; + map.loadDefaults(); + QSettings s(path, QSettings::IniFormat); + map.loadOverrides(s); + + QCOMPARE(map.actionFor(QKeySequence(QStringLiteral("Ctrl+Shift+A"))), + QStringLiteral("archive")); +} + +void TestKeyMap::chordSequenceParses() +{ + QTemporaryDir dir; + const QString path = dir.filePath(QStringLiteral("t.conf")); + { + QSettings s(path, QSettings::IniFormat); + s.beginGroup(QStringLiteral("keys")); + s.setValue(QStringLiteral("g,i"), QStringLiteral("focus_query")); + s.endGroup(); + } + + KeyMap map; + QSettings s(path, QSettings::IniFormat); + map.loadOverrides(s); + + const QKeySequence chord = QKeySequence::fromString(QStringLiteral("g,i")); + QCOMPARE(chord.count(), 2); + QCOMPARE(map.actionFor(chord), QStringLiteral("focus_query")); +} + +void TestKeyMap::unknownActionIsReported() +{ + QTemporaryDir dir; + const QString path = dir.filePath(QStringLiteral("t.conf")); + { + QSettings s(path, QSettings::IniFormat); + s.beginGroup(QStringLiteral("keys")); + s.setValue(QStringLiteral("z"), QStringLiteral("no_such_action")); + s.endGroup(); + } + + KeyMap map; + QSettings s(path, QSettings::IniFormat); + map.loadOverrides(s); + + // Reported, not fatal, and not bound. + QCOMPARE(map.warnings().size(), 1); + QVERIFY(map.warnings().first().contains(QStringLiteral("no_such_action"))); + QVERIFY(map.actionFor(QKeySequence(QStringLiteral("z"))).isEmpty()); +} + +void TestKeyMap::invalidSequenceIsReported() +{ + QTemporaryDir dir; + const QString path = dir.filePath(QStringLiteral("t.conf")); + { + QSettings s(path, QSettings::IniFormat); + s.beginGroup(QStringLiteral("keys")); + s.setValue(QStringLiteral("NotAKey++"), QStringLiteral("archive")); + s.endGroup(); + } + + KeyMap map; + QSettings s(path, QSettings::IniFormat); + map.loadOverrides(s); + + QCOMPARE(map.warnings().size(), 1); +} + +QTEST_MAIN(TestKeyMap) +#include "test_keymap.moc" -- cgit v1.2.3