diff --git a/docs/tap_hold.md b/docs/tap_hold.md index a246ea97939..84d0a17ab88 100644 --- a/docs/tap_hold.md +++ b/docs/tap_hold.md @@ -453,10 +453,6 @@ Notes: * Chordal Hold has no effect after the tapping term. -* Chordal Hold has no effect when the other key is also a tap-hold key. This is - so that multiple tap-hold keys may be held on the same hand, which is common - to do with home row mods. - * Combos are exempt from the opposite hands rule, since "handedness" is ill-defined in this case. Even so, Chordal Hold's behavior involving combos may be customized through the `get_chordal_hold()` callback. diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 4f232d34e2b..22e0e23b2d0 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -50,42 +50,17 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re # endif # ifdef CHORDAL_HOLD -__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) { - return get_chordal_hold_default(tap_hold_record, other_record); -} +// Tracks whether multiple tap-hold keys are simultaenously unsettled. +static bool multi_tap_sequence = false; -bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) { - if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) { - return true; // Return true on combos or other non-key events. - } - - char tap_hold_hand = chordal_hold_handedness_user(tap_hold_record->event.key); - if (tap_hold_hand == '*') { - return true; - } - char other_hand = chordal_hold_handedness_user(other_record->event.key); - return other_hand == '*' || tap_hold_hand != other_hand; -} - -__attribute__((weak)) char chordal_hold_handedness_kb(keypos_t key) { -# if defined(SPLIT_KEYBOARD) || ((MATRIX_ROWS) > (MATRIX_COLS)) - // If the keyboard is split or if MATRIX_ROWS > MATRIX_COLS, assume that the - // first half of the rows are left and the latter half are right. - return (key.row < (MATRIX_ROWS) / 2) ? /*left*/ 'L' : /*right*/ 'R'; -# else - // Otherwise, assume the first half of the cols are left, others are right. - return (key.col < (MATRIX_COLS) / 2) ? /*left*/ 'L' : /*right*/ 'R'; -# endif -} - -__attribute__((weak)) char chordal_hold_handedness_user(keypos_t key) { -# if defined(CHORDAL_HOLD_LAYOUT) - // If given, read handedness from `chordal_hold_layout` array. - return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]); -# else - return chordal_hold_handedness_kb(key); -# endif +static bool is_one_shot(uint16_t keycode) { + return IS_QK_ONE_SHOT_MOD(keycode) || IS_QK_ONE_SHOT_LAYER(keycode); } +static uint8_t waiting_buffer_find_chordal_hold_tap(void); +static void waiting_buffer_process_until(uint8_t new_tail); +static void waiting_buffer_process_regular(void); +# else +# define multi_tap_sequence false # endif // CHORDAL_HOLD # ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY @@ -149,6 +124,11 @@ void action_tapping_process(keyrecord_t record) { if (IS_EVENT(record.event)) { ac_dprintf("\n"); } +# ifdef CHORDAL_HOLD + if (waiting_buffer_tail == waiting_buffer_head) { + multi_tap_sequence = false; + } +# endif } /* Some conditionally defined helper macros to keep process_tapping more @@ -219,6 +199,20 @@ bool process_tapping(keyrecord_t *keyp) { waiting_buffer_scan_tap(); debug_tapping_key(); } else { +# if defined(CHORDAL_HOLD) + // If keyp is a tap-hold key release, and the tail waiting buffer + // is the corresponding press, settle the key as tapped. + if (waiting_buffer_tail != waiting_buffer_head && is_tap_record(keyp)) { + keyrecord_t *tail = &waiting_buffer[waiting_buffer_tail]; + if (IS_EVENT(tail->event) && KEYEQ(tail->event.key, keyp->event.key) && tail->event.pressed) { + tail->tap.count = 1; + keyp->tap.count = 1; + process_record(tail); + // Pop tail from the queue. + waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE; + } + } +# endif // the current key is just a regular key, pass it on for regular // processing process_record(keyp); @@ -238,7 +232,25 @@ bool process_tapping(keyrecord_t *keyp) { // early return for tick events return true; } - if (tapping_key.tap.count == 0) { +# ifdef CHORDAL_HOLD + if (!event.pressed && multi_tap_sequence && waiting_buffer_typed(event)) { + const uint8_t first_tap = waiting_buffer_find_chordal_hold_tap(); + + // Settle and process the tapping key and waiting events + // preceding first_tap as *held*. + if (first_tap < WAITING_BUFFER_SIZE) { + ac_dprintf("Tapping: End. No tap. Interfered by typing key\n"); + process_record(&tapping_key); + tapping_key = (keyrecord_t){0}; + debug_tapping_key(); + + waiting_buffer_process_until(first_tap); + } + // enqueue + return false; + } else +# endif + if (tapping_key.tap.count == 0) { if (IS_TAPPING_RECORD(keyp) && !event.pressed) { // first tap! ac_dprintf("Tapping: First tap(0->1).\n"); @@ -277,7 +289,7 @@ bool process_tapping(keyrecord_t *keyp) { * Without this unexpected repeating will occur with having fast repeating setting * https://github.com/tmk/tmk_keyboard/issues/60 */ - else if (!event.pressed && !waiting_buffer_typed(event)) { + else if (!multi_tap_sequence && !event.pressed && !waiting_buffer_typed(event)) { // Modifier/Layer should be retained till end of this tapping. action_t action = layer_switch_get_action(event.key); switch (action.kind.id) { @@ -312,17 +324,26 @@ bool process_tapping(keyrecord_t *keyp) { tapping_key.tap.interrupted = true; # if defined(CHORDAL_HOLD) - if (!is_tap_record(keyp) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, true), keyp)) { - // Settle the tapping key as *tapped*, since it - // is not considered a held chord with keyp. - ac_dprintf("Tapping: End. Chord considered a tap\n"); - tapping_key.tap.count = 1; + if (!is_one_shot(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { // In process_action(), HOLD_ON_OTHER_KEY_PRESS // will revert interrupted events to holds, so // this needs to be set false. tapping_key.tap.interrupted = false; - process_record(&tapping_key); - debug_tapping_key(); + + if (is_tap_record(keyp)) { + // Multiple tap-hold keys are unsettled. + multi_tap_sequence = true; + } else { + // Settle the tapping key as *tapped*, since it + // is not considered a held chord with keyp. + ac_dprintf("Tapping: End. Chord considered a tap\n"); + tapping_key.tap.count = 1; + process_record(&tapping_key); + debug_tapping_key(); + + // Process regular keys in the waiting buffer. + waiting_buffer_process_regular(); + } } else # endif if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS @@ -574,26 +595,112 @@ void waiting_buffer_scan_tap(void) { } } -/** \brief Tapping key debug print +# ifdef CHORDAL_HOLD +__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) { + return get_chordal_hold_default(tap_hold_record, other_record); +} + +bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) { + if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) { + return true; // Return true on combos or other non-key events. + } + + char tap_hold_hand = chordal_hold_handedness_user(tap_hold_record->event.key); + if (tap_hold_hand == '*') { + return true; + } + char other_hand = chordal_hold_handedness_user(other_record->event.key); + return other_hand == '*' || tap_hold_hand != other_hand; +} + +__attribute__((weak)) char chordal_hold_handedness_kb(keypos_t key) { +# if defined(SPLIT_KEYBOARD) || ((MATRIX_ROWS) > (MATRIX_COLS)) + // If the keyboard is split or if MATRIX_ROWS > MATRIX_COLS, assume that the + // first half of the rows are left and the latter half are right. + return (key.row < (MATRIX_ROWS) / 2) ? /*left*/ 'L' : /*right*/ 'R'; +# else + // Otherwise, assume the first half of the cols are left, others are right. + return (key.col < (MATRIX_COLS) / 2) ? /*left*/ 'L' : /*right*/ 'R'; +# endif +} + +__attribute__((weak)) char chordal_hold_handedness_user(keypos_t key) { +# if defined(CHORDAL_HOLD_LAYOUT) + // If given, read handedness from `chordal_hold_layout` array. + return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]); +# else + return chordal_hold_handedness_kb(key); +# endif +} + +/** \brief Finds which queued events should be held according to Chordal Hold. * - * FIXME: Needs docs + * In a situation with multiple unsettled tap-hold key presses, scan the queue + * up until the first release, non-tap-hold, or one-shot event and find the + * lastest event in the queue that settles as held according to + * get_chordal_hold(). + * + * \return Index of the first tap, or equivalently, one past the latest hold. */ +static uint8_t waiting_buffer_find_chordal_hold_tap(void) { + keyrecord_t *prev = &tapping_key; + uint16_t prev_keycode = get_record_keycode(&tapping_key, false); + uint8_t first_tap = WAITING_BUFFER_SIZE; + + for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { + keyrecord_t *cur = &waiting_buffer[i]; + const uint16_t cur_keycode = get_record_keycode(cur, false); + + if (!cur->event.pressed || !is_tap_record(prev) || is_one_shot(prev_keycode)) { + break; + } else if (get_chordal_hold(prev_keycode, prev, cur_keycode, cur)) { + first_tap = i; // Track one index past the latest hold. + } + + prev = cur; + prev_keycode = cur_keycode; + } + + return first_tap; +} + +/** \brief Processes and pops buffered events preceding `new_tail`. */ +static void waiting_buffer_process_until(uint8_t new_tail) { + for (; waiting_buffer_tail != new_tail; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { + ac_dprintf("waiting_buffer_process_until: processing [%u]\n", waiting_buffer_tail); + process_record(&waiting_buffer[waiting_buffer_tail]); + } + + debug_waiting_buffer(); +} + +/** \brief Processes and pops buffered events until the first tap-hold event. */ +static void waiting_buffer_process_regular(void) { + for (; waiting_buffer_tail != waiting_buffer_head; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { + if (is_tap_record(&waiting_buffer[waiting_buffer_tail])) { + break; // Stop once a tap-hold key event is reached. + } + ac_dprintf("waiting_buffer_process_regular: processing [%u]\n", waiting_buffer_tail); + process_record(&waiting_buffer[waiting_buffer_tail]); + } + + debug_waiting_buffer(); +} +# endif // CHORDAL_HOLD + +/** \brief Logs tapping key if ACTION_DEBUG is enabled. */ static void debug_tapping_key(void) { ac_dprintf("TAPPING_KEY="); debug_record(tapping_key); ac_dprintf("\n"); } -/** \brief Waiting buffer debug print - * - * FIXME: Needs docs - */ +/** \brief Logs waiting buffer if ACTION_DEBUG is enabled. */ static void debug_waiting_buffer(void) { - ac_dprintf("{ "); + ac_dprintf("{"); for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { - ac_dprintf("[%u]=", i); + ac_dprintf(" [%u]=", i); debug_record(waiting_buffer[i]); - ac_dprintf(" "); } ac_dprintf("}\n"); } diff --git a/tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/config.h b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h similarity index 100% rename from tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/config.h rename to tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h diff --git a/tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/test.mk b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk similarity index 100% rename from tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/test.mk rename to tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk diff --git a/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp new file mode 100644 index 00000000000..1c9e6de2ec0 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp @@ -0,0 +1,499 @@ +// Copyright 2024 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 . + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +class ChordalHoldHoldOnOtherKeyPress : public TestFixture {}; + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_with_mod_tap_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_nested_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_rolled_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key and release mod-tap key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + EXPECT_REPORT(driver, (KC_A)); + regular_key.press(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, non_chord_with_mod_tap_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key and regular key both on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_P)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, tap_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, hold_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_opposite_hands) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap second mod-tap key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + // Release first mod-tap key. + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_same_hand) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_orders) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 3, 2, 1. + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 3, 1, 2. + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_C)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 2, 3, 1. + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_two_held_one_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 2, then key 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 1, then key 2. + EXPECT_REPORT(driver, (KC_LEFT_CTRL)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_one_held_two_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.press(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.press(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/config.h b/tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h similarity index 100% rename from tests/tap_hold_configurations/chordal_hold_and_permissive_hold/config.h rename to tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h diff --git a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test.mk b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk similarity index 97% rename from tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test.mk rename to tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk index 81ba8da66d4..86e45bc5bce 100644 --- a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test.mk +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk @@ -13,6 +13,4 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -COMBO_ENABLE = yes - INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c new file mode 100644 index 00000000000..ffc914b645e --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c @@ -0,0 +1,8 @@ +#include "quantum.h" + +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, +}; diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp new file mode 100644 index 00000000000..e9436919f5d --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp @@ -0,0 +1,148 @@ +/* Copyright 2021 Stefan Kerkmann + * + * 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 . + */ + +#include "action_util.h" +#include "keyboard_report_util.hpp" +#include "test_common.hpp" + +using testing::_; +using testing::InSequence; + +class OneShot : public TestFixture {}; +class OneShotParametrizedTestFixture : public ::testing::WithParamInterface>, public OneShot {}; + +TEST_P(OneShotParametrizedTestFixture, OSMAsRegularModifierWithAdditionalKeypress) { + TestDriver driver; + KeymapKey osm_key = GetParam().first; + KeymapKey regular_key = GetParam().second; + + set_keymap({osm_key, regular_key}); + + // Press OSM. + EXPECT_NO_REPORT(driver); + osm_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (osm_key.report_code)).Times(2); + EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSM. + EXPECT_EMPTY_REPORT(driver); + osm_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// clang-format off + +INSTANTIATE_TEST_CASE_P( + OneShotModifierTests, + OneShotParametrizedTestFixture, + ::testing::Values( + // First is osm key, second is regular key. + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LCTL), KC_LCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LALT), KC_LALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LGUI), KC_LGUI}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RCTL), KC_RCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RSFT), KC_RSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RALT), KC_RALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RGUI), KC_RGUI}, KeymapKey{0, 1, 1, KC_A}) + )); +// clang-format on + +TEST_F(OneShot, OSLWithAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osl_key1 = KeymapKey{1, 0, 0, KC_X}; + KeymapKey regular_key0 = KeymapKey{0, 1, 0, KC_Y}; + KeymapKey regular_key1 = KeymapKey{1, 1, 0, KC_A}; + + set_keymap({osl_key, osl_key1, regular_key0, regular_key1}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (regular_key1.report_code)); + EXPECT_EMPTY_REPORT(driver); + regular_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_NO_REPORT(driver); + regular_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSLWithOsmAndAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osm_key = KeymapKey{1, 1, 0, OSM(MOD_LSFT), KC_LSFT}; + KeymapKey regular_key = KeymapKey{1, 1, 1, KC_A}; + KeymapKey blank_key = KeymapKey{1, 0, 0, KC_NO}; + + set_keymap({osl_key, osm_key, regular_key, blank_key}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Press and release OSM. + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code)); + EXPECT_EMPTY_REPORT(driver); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp new file mode 100644 index 00000000000..c600573be42 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp @@ -0,0 +1,576 @@ +// Copyright 2024 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 . + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +class ChordalHoldPermissiveHold : public TestFixture {}; + +TEST_F(ChordalHoldPermissiveHold, chordal_hold_handedness) { + EXPECT_EQ(chordal_hold_handedness_user({.col = 0, .row = 0}), 'L'); + EXPECT_EQ(chordal_hold_handedness_user({.col = MATRIX_COLS - 1, .row = 0}), 'R'); + EXPECT_EQ(chordal_hold_handedness_user({.col = 0, .row = 2}), '*'); +} + +TEST_F(ChordalHoldPermissiveHold, get_chordal_hold_default) { + auto make_record = [](uint8_t row, uint8_t col, keyevent_type_t type = KEY_EVENT) { + return keyrecord_t{ + .event = + { + .key = {.col = col, .row = row}, + .type = type, + .pressed = true, + }, + }; + }; + // Create two records on the left hand. + keyrecord_t record_l0 = make_record(0, 0); + keyrecord_t record_l1 = make_record(1, 0); + // Create a record on the right hand. + keyrecord_t record_r = make_record(0, MATRIX_COLS - 1); + + // Function should return true when records are on opposite hands. + EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_r)); + EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_l0)); + // ... and false when on the same hand. + EXPECT_FALSE(get_chordal_hold_default(&record_l0, &record_l1)); + EXPECT_FALSE(get_chordal_hold_default(&record_l1, &record_l0)); + // But (2, 0) has handedness '*', for which true is returned for chords + // with either hand. + keyrecord_t record_l2 = make_record(2, 0); + EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_l0)); + EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_r)); + + // Create a record resulting from a combo. + keyrecord_t record_combo = make_record(0, 0, COMBO_EVENT); + // Function returns true in all cases. + EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_combo)); + EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_combo)); + EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_l0)); + EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_r)); +} + +TEST_F(ChordalHoldPermissiveHold, chord_nested_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, chord_rolled_press_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key and release mod-tap key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_A)); + regular_key.press(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, non_chord_with_mod_tap_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key and regular key both on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_P)); + tap_key(regular_key); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, tap_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, hold_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_opposite_hands) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_same_hand) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key2.release(); + run_one_scan_loop(); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 2, 3, 1. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_C)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_two_held_one_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 2, then key 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 1, then key 2. + EXPECT_REPORT(driver, (KC_LEFT_CTRL)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_one_held_two_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_one_regular_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_C); + + set_keymap({mod_tap_key1, mod_tap_key2, regular_key}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 2, then key 1. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + // Release keys. + regular_key.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h new file mode 100644 index 00000000000..8a4e8d07a07 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h @@ -0,0 +1,27 @@ +/* Copyright 2022 Isaac Elenbaas + * + * 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 . + */ + +#pragma once + +#include "test_common.h" + +#define CHORDAL_HOLD +#define PERMISSIVE_HOLD + +#define RETRO_SHIFT 2 * TAPPING_TERM +// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested +#define AUTO_SHIFT_TIMEOUT TAPPING_TERM +#define AUTO_SHIFT_MODIFIERS diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk new file mode 100644 index 00000000000..0660eb3e6b8 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk @@ -0,0 +1,16 @@ +# Copyright 2022 Isaac Elenbaas +# +# 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 . + +AUTO_SHIFT_ENABLE = yes diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp new file mode 100644 index 00000000000..8d56fcc266e --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp @@ -0,0 +1,419 @@ +/* Copyright 2022 Isaac Elenbaas + * + * 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 . + */ + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + return true; +} + +using testing::_; +using testing::AnyNumber; +using testing::AnyOf; +using testing::InSequence; + +class RetroShiftPermissiveHold : public TestFixture {}; + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_EMPTY_REPORT(driver); + idle_for(AUTO_SHIFT_TIMEOUT); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/test_tap_hold.cpp deleted file mode 100644 index c18d568798a..00000000000 --- a/tests/tap_hold_configurations/chordal_hold_and_hold_on_other_key_press/test_tap_hold.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/* Copyright 2022 Vladislav Kucheriavykh - * - * 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 . - */ - -#include "keyboard_report_util.hpp" -#include "keycode.h" -#include "test_common.hpp" -#include "action_tapping.h" -#include "test_fixture.hpp" -#include "test_keymap_key.hpp" - -using testing::_; -using testing::InSequence; - -class ChordalHoldAndHoldOnOtherKeypress : public TestFixture {}; - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, chord_with_mod_tap_settled_as_hold) { - TestDriver driver; - InSequence s; - // Mod-tap key on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - // Regular key on the right hand. - auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press regular key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); - regular_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release regular key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - regular_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release mod-tap-hold key. - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, chord_nested_press_settled_as_hold) { - TestDriver driver; - InSequence s; - // Mod-tap key on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - // Regular key on the right hand. - auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Tap regular key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - tap_key(regular_key); - VERIFY_AND_CLEAR(driver); - - // Release mod-tap-hold key. - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, chord_rolled_press_settled_as_hold) { - TestDriver driver; - InSequence s; - // Mod-tap key on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - // Regular key on the right hand. - auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press regular key and release mod-tap key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); - EXPECT_REPORT(driver, (KC_A)); - regular_key.press(); - run_one_scan_loop(); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release regular key. - EXPECT_EMPTY_REPORT(driver); - regular_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, non_chord_with_mod_tap_settled_as_tap) { - TestDriver driver; - InSequence s; - // Mod-tap key and regular key both on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - auto regular_key = KeymapKey(0, 2, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press regular key. - EXPECT_REPORT(driver, (KC_P)); - EXPECT_REPORT(driver, (KC_P, KC_A)); - regular_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release regular key. - EXPECT_REPORT(driver, (KC_P)); - regular_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release mod-tap-hold key. - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, tap_mod_tap_key) { - TestDriver driver; - InSequence s; - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - - set_keymap({mod_tap_key}); - - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - idle_for(TAPPING_TERM - 1); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_P)); - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, hold_mod_tap_key) { - TestDriver driver; - InSequence s; - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - - set_keymap({mod_tap_key}); - - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - mod_tap_key.press(); - idle_for(TAPPING_TERM + 1); - VERIFY_AND_CLEAR(driver); - - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndHoldOnOtherKeypress, chordal_hold_ignores_multiple_mod_taps) { - TestDriver driver; - InSequence s; - auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); - auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); - - set_keymap({mod_tap_key1, mod_tap_key2}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key1.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press second mod-tap key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_RIGHT_SHIFT)); - mod_tap_key2.press(); - idle_for(TAPPING_TERM + 1); - VERIFY_AND_CLEAR(driver); - - // Release keys. - EXPECT_REPORT(driver, (KC_RIGHT_SHIFT)); - EXPECT_EMPTY_REPORT(driver); - mod_tap_key1.release(); - run_one_scan_loop(); - mod_tap_key2.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} diff --git a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_keymap.c b/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_keymap.c deleted file mode 100644 index 195f970a799..00000000000 --- a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_keymap.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "quantum.h" - -const uint16_t ab_combo[] = {KC_A, KC_B, COMBO_END}; - -combo_t key_combos[] = { - COMBO(ab_combo, KC_X), -}; diff --git a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_tap_hold.cpp deleted file mode 100644 index df506b9c099..00000000000 --- a/tests/tap_hold_configurations/chordal_hold_and_permissive_hold/test_tap_hold.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* Copyright 2022 Vladislav Kucheriavykh - * - * 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 . - */ - -#include "keyboard_report_util.hpp" -#include "keycode.h" -#include "test_common.hpp" -#include "action_tapping.h" -#include "test_fixture.hpp" -#include "test_keymap_key.hpp" - -using testing::_; -using testing::InSequence; - -extern "C" { -const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { - {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, - {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, - {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, - {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, -}; -} // extern "C" - -class ChordalHoldAndPermissiveHold : public TestFixture {}; - -TEST_F(ChordalHoldAndPermissiveHold, chordal_hold_handedness) { - EXPECT_EQ(chordal_hold_handedness_user({.col = 0, .row = 0}), 'L'); - EXPECT_EQ(chordal_hold_handedness_user({.col = MATRIX_COLS - 1, .row = 0}), 'R'); - EXPECT_EQ(chordal_hold_handedness_user({.col = 0, .row = 2}), '*'); -} - -TEST_F(ChordalHoldAndPermissiveHold, get_chordal_hold_default) { - auto make_record = [](uint8_t row, uint8_t col, keyevent_type_t type = KEY_EVENT, uint16_t keycode = KC_NO) { - return keyrecord_t{ - .event = - { - .key = {.col = col, .row = row}, - .type = type, - .pressed = true, - }, - .keycode = keycode, - }; - }; - // Create two records on the left hand. - keyrecord_t record_l0 = make_record(0, 0); - keyrecord_t record_l1 = make_record(1, 0); - // Create a record on the right hand. - keyrecord_t record_r = make_record(0, MATRIX_COLS - 1); - - // Function should return true when records are on opposite hands. - EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_r)); - EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_l0)); - // ... and false when on the same hand. - EXPECT_FALSE(get_chordal_hold_default(&record_l0, &record_l1)); - EXPECT_FALSE(get_chordal_hold_default(&record_l1, &record_l0)); - // But (2, 0) has handedness '*', for which true is returned for chords - // with either hand. - keyrecord_t record_l2 = make_record(2, 0); - EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_l0)); - EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_r)); - - // Create a record resulting from a combo. - keyrecord_t record_combo = make_record(0, 0, COMBO_EVENT, KC_X); - // Function returns true in all cases. - EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_combo)); - EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_combo)); - EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_l0)); - EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_r)); -} - -TEST_F(ChordalHoldAndPermissiveHold, chord_nested_press_settled_as_hold) { - TestDriver driver; - InSequence s; - // Mod-tap key on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - // Regular key on the right hand. - auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Tap regular key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - tap_key(regular_key); - VERIFY_AND_CLEAR(driver); - - // Release mod-tap key. - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndPermissiveHold, chord_rolled_press_settled_as_tap) { - TestDriver driver; - InSequence s; - // Mod-tap key on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - // Regular key on the right hand. - auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press regular key and release mod-tap key. - EXPECT_REPORT(driver, (KC_P)); - EXPECT_REPORT(driver, (KC_P, KC_A)); - EXPECT_REPORT(driver, (KC_A)); - regular_key.press(); - run_one_scan_loop(); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release regular key. - EXPECT_EMPTY_REPORT(driver); - regular_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndPermissiveHold, non_chord_with_mod_tap_settled_as_tap) { - TestDriver driver; - InSequence s; - // Mod-tap key and regular key both on the left hand. - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - auto regular_key = KeymapKey(0, 2, 0, KC_A); - - set_keymap({mod_tap_key, regular_key}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Tap regular key. - EXPECT_REPORT(driver, (KC_P)); - EXPECT_REPORT(driver, (KC_P, KC_A)); - EXPECT_REPORT(driver, (KC_P)); - tap_key(regular_key); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Release mod-tap-hold key. - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndPermissiveHold, tap_mod_tap_key) { - TestDriver driver; - InSequence s; - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - - set_keymap({mod_tap_key}); - - EXPECT_NO_REPORT(driver); - mod_tap_key.press(); - idle_for(TAPPING_TERM - 1); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_P)); - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndPermissiveHold, hold_mod_tap_key) { - TestDriver driver; - InSequence s; - auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); - - set_keymap({mod_tap_key}); - - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - mod_tap_key.press(); - idle_for(TAPPING_TERM + 1); - VERIFY_AND_CLEAR(driver); - - EXPECT_EMPTY_REPORT(driver); - mod_tap_key.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -} - -TEST_F(ChordalHoldAndPermissiveHold, chordal_hold_ignores_multiple_mod_taps) { - TestDriver driver; - InSequence s; - auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); - auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); - - set_keymap({mod_tap_key1, mod_tap_key2}); - - // Press mod-tap-hold key. - EXPECT_NO_REPORT(driver); - mod_tap_key1.press(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - // Press second mod-tap key. - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_RIGHT_SHIFT)); - mod_tap_key2.press(); - idle_for(TAPPING_TERM + 1); - VERIFY_AND_CLEAR(driver); - - // Release keys. - EXPECT_REPORT(driver, (KC_RIGHT_SHIFT)); - EXPECT_EMPTY_REPORT(driver); - mod_tap_key1.release(); - run_one_scan_loop(); - mod_tap_key2.release(); - run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); -}