qmk_firmware/tests/caps_word/test_caps_word.cpp
Pascal Getreuer b5608cbb6d
Continue Caps Word when AltGr (right Alt) is held. (#17156)
This is a minor bug fix for Caps Word. Currently, Caps Word turns off
whenever a non-shift mod becomes active. This is done to avoid
interfering with hotkeys.

This commit makes an exception to continue Caps Word when AltGr (right
Alt) is held. Outside the US, the AltGr key is used to type additional
symbols (https://en.wikipedia.org/wiki/AltGr_key). Depending on the
language, these may include symbols used within words like accented
letters where it would be desirable to continue Caps Word.
2022-05-20 01:39:00 +01:00

453 lines
14 KiB
C++

// Copyright 2022 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// 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, see <http://www.gnu.org/licenses/>.
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AnyOf;
using ::testing::InSequence;
using ::testing::TestParamInfo;
class CapsWord : public TestFixture {
public:
void SetUp() override {
caps_word_off();
}
// Convenience function to tap `key`.
void TapKey(KeymapKey key) {
key.press();
run_one_scan_loop();
key.release();
run_one_scan_loop();
}
// Taps in order each key in `keys`.
template <typename... Ts>
void TapKeys(Ts... keys) {
for (KeymapKey key : {keys...}) {
TapKey(key);
}
}
};
// Tests caps_word_on(), _off(), and _toggle() functions.
TEST_F(CapsWord, OnOffToggleFuns) {
TestDriver driver;
EXPECT_EQ(is_caps_word_on(), false);
caps_word_on();
EXPECT_EQ(is_caps_word_on(), true);
caps_word_on();
EXPECT_EQ(is_caps_word_on(), true);
caps_word_off();
EXPECT_EQ(is_caps_word_on(), false);
caps_word_off();
EXPECT_EQ(is_caps_word_on(), false);
caps_word_toggle();
EXPECT_EQ(is_caps_word_on(), true);
caps_word_toggle();
EXPECT_EQ(is_caps_word_on(), false);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests the default `caps_word_press_user()` function.
TEST_F(CapsWord, DefaultCapsWordPressUserFun) {
// Spot check some keycodes that continue Caps Word, with shift applied.
for (uint16_t keycode : {KC_A, KC_B, KC_Z, KC_MINS}) {
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
clear_weak_mods();
EXPECT_TRUE(caps_word_press_user(keycode));
EXPECT_EQ(get_weak_mods(), MOD_BIT(KC_LSFT));
}
// Some keycodes that continue Caps Word, without shifting.
for (uint16_t keycode : {KC_1, KC_9, KC_0, KC_BSPC, KC_DEL}) {
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
clear_weak_mods();
EXPECT_TRUE(caps_word_press_user(keycode));
EXPECT_EQ(get_weak_mods(), 0);
}
// Some keycodes that turn off Caps Word.
for (uint16_t keycode : {KC_SPC, KC_DOT, KC_COMM, KC_TAB, KC_ESC, KC_ENT}) {
SCOPED_TRACE("keycode: " + testing::PrintToString(keycode));
EXPECT_FALSE(caps_word_press_user(keycode));
}
}
// Tests that `CAPSWRD` key toggles Caps Word.
TEST_F(CapsWord, CapswrdKey) {
TestDriver driver;
KeymapKey key_capswrd(0, 0, 0, CAPSWRD);
set_keymap({key_capswrd});
// No keyboard reports should be sent.
EXPECT_CALL(driver, send_keyboard_mock(_)).Times(0);
TapKey(key_capswrd); // Tap the CAPSWRD key.
EXPECT_EQ(is_caps_word_on(), true);
TapKey(key_capswrd); // Tap the CAPSWRD key again.
EXPECT_EQ(is_caps_word_on(), false);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests that being idle for CAPS_WORD_IDLE_TIMEOUT turns off Caps Word.
TEST_F(CapsWord, IdleTimeout) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
set_keymap({key_a});
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
// Expect "Shift+A".
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
// Turn on Caps Word and tap "A".
caps_word_on();
TapKey(key_a);
testing::Mock::VerifyAndClearExpectations(&driver);
idle_for(CAPS_WORD_IDLE_TIMEOUT);
run_one_scan_loop();
// Caps Word should be off and mods should be clear.
EXPECT_EQ(is_caps_word_on(), false);
EXPECT_EQ(get_mods() | get_weak_mods(), 0);
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(AnyNumber());
// Expect unshifted "A".
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
TapKey(key_a);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests that typing "A, 4, A, 4" produces "Shift+A, 4, Shift+A, 4".
TEST_F(CapsWord, ShiftsLettersButNotDigits) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_4(0, 1, 0, KC_4);
set_keymap({key_a, key_4});
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "Shift+A, 4, Shift+A, 4".
InSequence s;
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_4)));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_4)));
}
// Turn on Caps Word and tap "A, 4, A, 4".
caps_word_on();
TapKeys(key_a, key_4, key_a, key_4);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests that typing "A, Space, A" produces "Shift+A, Space, A".
TEST_F(CapsWord, SpaceTurnsOffCapsWord) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_spc(0, 1, 0, KC_SPC);
set_keymap({key_a, key_spc});
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "Shift+A, Space, A".
InSequence seq;
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_A)));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_SPC)));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_A)));
}
// Turn on Caps Word and tap "A, Space, A".
caps_word_on();
TapKeys(key_a, key_spc, key_a);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests that typing "AltGr + A" produces "Shift + AltGr + A".
TEST_F(CapsWord, ShiftsAltGrSymbols) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_altgr(0, 1, 0, KC_RALT);
set_keymap({key_a, key_altgr});
// Allow any number of reports with no keys or only modifiers.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_RALT),
KeyboardReport(KC_LSFT, KC_RALT))))
.Times(AnyNumber());
// Expect "Shift + AltGr + A, Space".
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT, KC_RALT, KC_A)));
// clang-format on
// Turn on Caps Word and type "AltGr + A".
caps_word_on();
key_altgr.press();
run_one_scan_loop();
TapKeys(key_a);
run_one_scan_loop();
key_altgr.release();
testing::Mock::VerifyAndClearExpectations(&driver);
}
struct CapsWordBothShiftsParams {
std::string name;
uint16_t left_shift_keycode;
uint16_t right_shift_keycode;
static const std::string& GetName(const TestParamInfo<CapsWordBothShiftsParams>& info) {
return info.param.name;
}
};
// Tests the BOTH_SHIFTS_TURNS_ON_CAPS_WORD method to turn on Caps Word.
class CapsWordBothShifts : public ::testing::WithParamInterface<CapsWordBothShiftsParams>, public CapsWord {};
// Pressing shifts as "Left down, Right down, Left up, Right up".
TEST_P(CapsWordBothShifts, PressLRLR) {
TestDriver driver;
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
KeymapKey right_shift(0, 1, 0, GetParam().right_shift_keycode);
set_keymap({left_shift, right_shift});
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_RSFT),
KeyboardReport(KC_LSFT, KC_RSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_EQ(is_caps_word_on(), false);
left_shift.press(); // Press both shifts.
run_one_scan_loop();
right_shift.press();
// For mod-tap and Space Cadet keys, wait for the tapping term.
if (left_shift.code == LSFT_T(KC_A) || left_shift.code == KC_LSPO) {
idle_for(TAPPING_TERM);
}
run_one_scan_loop();
left_shift.release(); // Release both.
run_one_scan_loop();
right_shift.release();
run_one_scan_loop();
EXPECT_EQ(is_caps_word_on(), true);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Pressing shifts as "Left down, Right down, Right up, Left up".
TEST_P(CapsWordBothShifts, PressLRRL) {
TestDriver driver;
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
KeymapKey right_shift(0, 1, 0, GetParam().right_shift_keycode);
set_keymap({left_shift, right_shift});
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_RSFT),
KeyboardReport(KC_LSFT, KC_RSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_EQ(is_caps_word_on(), false);
left_shift.press(); // Press both shifts.
run_one_scan_loop();
right_shift.press();
if (left_shift.code == LSFT_T(KC_A) || left_shift.code == KC_LSPO) {
idle_for(TAPPING_TERM);
}
run_one_scan_loop();
right_shift.release(); // Release both.
run_one_scan_loop();
left_shift.release();
run_one_scan_loop();
EXPECT_EQ(is_caps_word_on(), true);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
ShiftPairs,
CapsWordBothShifts,
::testing::Values(
CapsWordBothShiftsParams{
"PlainShifts", KC_LSFT, KC_RSFT},
CapsWordBothShiftsParams{
"OneshotShifts", OSM(MOD_LSFT), OSM(MOD_RSFT)},
CapsWordBothShiftsParams{
"SpaceCadetShifts", KC_LSPO, KC_RSPC},
CapsWordBothShiftsParams{
"ModTapShifts", LSFT_T(KC_A), RSFT_T(KC_B)}
),
CapsWordBothShiftsParams::GetName
);
// clang-format on
struct CapsWordDoubleTapShiftParams {
std::string name;
uint16_t left_shift_keycode;
static const std::string& GetName(const TestParamInfo<CapsWordDoubleTapShiftParams>& info) {
return info.param.name;
}
};
// Tests the DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD method to turn on Caps Word.
class CapsWordDoubleTapShift : public ::testing::WithParamInterface<CapsWordDoubleTapShiftParams>, public CapsWord {};
// Tests that double tapping activates Caps Word.
TEST_P(CapsWordDoubleTapShift, Activation) {
TestDriver driver;
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
set_keymap({left_shift});
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_EQ(is_caps_word_on(), false);
// Tapping shift twice within the tapping term turns on Caps Word.
TapKey(left_shift);
idle_for(TAPPING_TERM - 10);
TapKey(left_shift);
EXPECT_EQ(is_caps_word_on(), true);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Double tap doesn't count if another key is pressed between the taps.
TEST_P(CapsWordDoubleTapShift, Interrupted) {
TestDriver driver;
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
KeymapKey key_a(0, 1, 0, KC_A);
set_keymap({left_shift, key_a});
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LSFT, KC_A))))
.Times(AnyNumber());
// clang-format on
left_shift.press();
run_one_scan_loop();
TapKey(key_a); // 'A' key interrupts the double tap.
left_shift.release();
run_one_scan_loop();
idle_for(TAPPING_TERM - 10);
TapKey(left_shift);
EXPECT_EQ(is_caps_word_on(), false); // Caps Word is still off.
clear_oneshot_mods();
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Double tap doesn't count if taps are more than tapping term apart.
TEST_P(CapsWordDoubleTapShift, SlowTaps) {
TestDriver driver;
KeymapKey left_shift(0, 0, 0, GetParam().left_shift_keycode);
set_keymap({left_shift});
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
TapKey(left_shift);
idle_for(TAPPING_TERM + 1);
TapKey(left_shift);
EXPECT_EQ(is_caps_word_on(), false); // Caps Word is still off.
clear_oneshot_mods();
testing::Mock::VerifyAndClearExpectations(&driver);
}
// clang-format off
INSTANTIATE_TEST_CASE_P(
Shifts,
CapsWordDoubleTapShift,
::testing::Values(
CapsWordDoubleTapShiftParams{"PlainShift", KC_LSFT},
CapsWordDoubleTapShiftParams{"OneshotShift", OSM(MOD_LSFT)}
),
CapsWordDoubleTapShiftParams::GetName
);
// clang-format on