/* * qtmaildir - a Qt6 mail client for notmuch-indexed Maildirs * Copyright (C) 2026 Danilo M. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #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 collidingOverridesAreReported(); }; 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); } void TestKeyMap::collidingOverridesAreReported() { // "y" and "Y" both normalize to the same QKeySequence ("Y"), so binding // both in [keys] is a genuine collision that must not silently drop one. { QTemporaryDir dir; const QString path = dir.filePath(QStringLiteral("t.conf")); { QSettings s(path, QSettings::IniFormat); s.beginGroup(QStringLiteral("keys")); s.setValue(QStringLiteral("y"), QStringLiteral("archive")); s.setValue(QStringLiteral("Y"), QStringLiteral("delete")); s.endGroup(); } KeyMap map; QSettings s(path, QSettings::IniFormat); map.loadOverrides(s); QCOMPARE(map.warnings().size(), 1); QVERIFY(map.warnings().first().contains(QStringLiteral("archive"))); QVERIFY(map.warnings().first().contains(QStringLiteral("delete"))); } // Overriding a default is not a collision: loadDefaults() puts 'j' in // the map first, then the override pass rebinds the same 'j'. That must // stay silent (regression check for iniOverridesDefault's scenario). { 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.warnings().size(), 0); } } QTEST_MAIN(TestKeyMap) #include "test_keymap.moc"