diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/basic/test_keypress.cpp | 22 | ||||
| -rw-r--r-- | tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp | 64 | ||||
| -rw-r--r-- | tests/caps_word/caps_word_combo/config.h | 20 | ||||
| -rw-r--r-- | tests/caps_word/caps_word_combo/test.mk | 19 | ||||
| -rw-r--r-- | tests/caps_word/caps_word_combo/test_caps_word_combo.cpp | 212 | ||||
| -rw-r--r-- | tests/tap_dance/config.h | 19 | ||||
| -rw-r--r-- | tests/tap_dance/examples.c | 199 | ||||
| -rw-r--r-- | tests/tap_dance/examples.h | 33 | ||||
| -rw-r--r-- | tests/tap_dance/test.mk | 22 | ||||
| -rw-r--r-- | tests/tap_dance/test_examples.cpp | 318 | ||||
| -rw-r--r-- | tests/test_common/test_fixture.cpp | 16 | ||||
| -rw-r--r-- | tests/test_common/test_fixture.hpp | 7 | 
12 files changed, 915 insertions, 36 deletions
| diff --git a/tests/basic/test_keypress.cpp b/tests/basic/test_keypress.cpp index bb68ced557..6d5b502a00 100644 --- a/tests/basic/test_keypress.cpp +++ b/tests/basic/test_keypress.cpp @@ -64,11 +64,7 @@ TEST_F(KeyPress, CorrectKeysAreReportedWhenTwoKeysArePressed) {      key_b.press();      key_c.press(); -    // Note that QMK only processes one key at a time -    // See issue #1476 for more information      EXPECT_REPORT(driver, (key_b.report_code)); -    keyboard_task(); -      EXPECT_REPORT(driver, (key_b.report_code, key_c.report_code));      keyboard_task(); @@ -76,8 +72,6 @@ TEST_F(KeyPress, CorrectKeysAreReportedWhenTwoKeysArePressed) {      key_c.release();      // Note that the first key released is the first one in the matrix order      EXPECT_REPORT(driver, (key_c.report_code)); -    keyboard_task(); -      EXPECT_EMPTY_REPORT(driver);      keyboard_task();  } @@ -92,10 +86,7 @@ TEST_F(KeyPress, LeftShiftIsReportedCorrectly) {      key_lsft.press();      key_a.press(); -    // Unfortunately modifiers are also processed in the wrong order -    // See issue #1476 for more information      EXPECT_REPORT(driver, (key_a.report_code)); -    keyboard_task();      EXPECT_REPORT(driver, (key_a.report_code, key_lsft.report_code));      keyboard_task(); @@ -118,11 +109,7 @@ TEST_F(KeyPress, PressLeftShiftAndControl) {      key_lsft.press();      key_lctrl.press(); -    // Unfortunately modifiers are also processed in the wrong order -    // See issue #1476 for more information      EXPECT_REPORT(driver, (key_lsft.report_code)); -    keyboard_task(); -      EXPECT_REPORT(driver, (key_lsft.report_code, key_lctrl.report_code));      keyboard_task(); @@ -130,8 +117,6 @@ TEST_F(KeyPress, PressLeftShiftAndControl) {      key_lctrl.release();      EXPECT_REPORT(driver, (key_lctrl.report_code)); -    keyboard_task(); -      EXPECT_EMPTY_REPORT(driver);      keyboard_task();  } @@ -145,20 +130,13 @@ TEST_F(KeyPress, LeftAndRightShiftCanBePressedAtTheSameTime) {      key_lsft.press();      key_rsft.press(); -    // Unfortunately modifiers are also processed in the wrong order -    // See issue #1476 for more information      EXPECT_REPORT(driver, (key_lsft.report_code)); -    keyboard_task(); -      EXPECT_REPORT(driver, (key_lsft.report_code, key_rsft.report_code));      keyboard_task();      key_lsft.release();      key_rsft.release(); -      EXPECT_REPORT(driver, (key_rsft.report_code)); -    keyboard_task(); -      EXPECT_EMPTY_REPORT(driver);      keyboard_task();  } diff --git a/tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp b/tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp index deb4d95766..ba21c527a6 100644 --- a/tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp +++ b/tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp @@ -19,6 +19,14 @@  #include "test_fixture.hpp"  #include "test_keymap_key.hpp" +// Allow reports with no keys or only KC_LSFT. +// clang-format off +#define EXPECT_EMPTY_OR_LSFT(driver)              \ +    EXPECT_CALL(driver, send_keyboard_mock(AnyOf( \ +            KeyboardReport(),                     \ +            KeyboardReport(KC_LSFT)))) +// clang-format on +  using ::testing::_;  using ::testing::AnyNumber;  using ::testing::AnyOf; @@ -39,13 +47,7 @@ TEST_F(CapsWord, AutoShiftKeys) {      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_EMPTY_OR_LSFT(driver).Times(AnyNumber());      { // Expect: "A, A, space, a".          InSequence s;          EXPECT_REPORT(driver, (KC_LSFT, KC_A)); @@ -65,6 +67,46 @@ TEST_F(CapsWord, AutoShiftKeys) {      testing::Mock::VerifyAndClearExpectations(&driver);  } +// Test Caps Word + Auto Shift where keys A and B are rolled. +TEST_F(CapsWord, AutoShiftRolledShiftedKeys) { +    TestDriver driver; +    KeymapKey  key_a(0, 0, 0, KC_A); +    KeymapKey  key_b(0, 0, 1, KC_B); +    set_keymap({key_a, key_b}); + +    EXPECT_EMPTY_OR_LSFT(driver).Times(AnyNumber()); +    { // Expect: "A, B, A, B". +        InSequence s; +        EXPECT_REPORT(driver, (KC_LSFT, KC_A)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_B)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_A)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_B)); +    } + +    caps_word_on(); + +    key_a.press(); // Overlapping taps: A down, B down, A up, B up. +    run_one_scan_loop(); +    key_b.press(); +    run_one_scan_loop(); +    key_a.release(); +    run_one_scan_loop(); +    key_b.release(); +    run_one_scan_loop(); + +    key_a.press(); // Nested taps: A down, B down, B up, A up. +    run_one_scan_loop(); +    key_b.press(); +    run_one_scan_loop(); +    key_b.release(); +    run_one_scan_loop(); +    key_a.release(); +    run_one_scan_loop(); + +    caps_word_off(); +    testing::Mock::VerifyAndClearExpectations(&driver); +} +  // Tests that with tap-hold keys with Retro Shift, letter keys are shifted by  // Caps Word regardless of whether they are retroshifted.  TEST_F(CapsWord, RetroShiftKeys) { @@ -73,13 +115,7 @@ TEST_F(CapsWord, RetroShiftKeys) {      KeymapKey  key_layertap_b(0, 1, 0, LT(1, KC_B));      set_keymap({key_modtap_a, key_layertap_b}); -    // 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_EMPTY_OR_LSFT(driver).Times(AnyNumber());      { // Expect: "B, A, B, A".          InSequence s;          EXPECT_REPORT(driver, (KC_LSFT, KC_B)); diff --git a/tests/caps_word/caps_word_combo/config.h b/tests/caps_word/caps_word_combo/config.h new file mode 100644 index 0000000000..92dbe045b2 --- /dev/null +++ b/tests/caps_word/caps_word_combo/config.h @@ -0,0 +1,20 @@ +// 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/>. + +#pragma once + +#include "test_common.h" + +#define TAPPING_TERM 200 diff --git a/tests/caps_word/caps_word_combo/test.mk b/tests/caps_word/caps_word_combo/test.mk new file mode 100644 index 0000000000..9f2e157189 --- /dev/null +++ b/tests/caps_word/caps_word_combo/test.mk @@ -0,0 +1,19 @@ +# 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/>. + +CAPS_WORD_ENABLE = yes +COMBO_ENABLE = yes +AUTO_SHIFT_ENABLE = yes + diff --git a/tests/caps_word/caps_word_combo/test_caps_word_combo.cpp b/tests/caps_word/caps_word_combo/test_caps_word_combo.cpp new file mode 100644 index 0000000000..3a0530b854 --- /dev/null +++ b/tests/caps_word/caps_word_combo/test_caps_word_combo.cpp @@ -0,0 +1,212 @@ +// 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/>. + +// Test Caps Word + Combos, with and without Auto Shift. + +#include <algorithm> +#include <numeric> +#include <vector> + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +// Allow reports with no keys or only KC_LSFT. +// clang-format off +#define EXPECT_EMPTY_OR_LSFT(driver)              \ +    EXPECT_CALL(driver, send_keyboard_mock(AnyOf( \ +            KeyboardReport(),                     \ +            KeyboardReport(KC_LSFT)))) +// clang-format on + +using ::testing::AnyNumber; +using ::testing::AnyOf; +using ::testing::InSequence; +using ::testing::TestParamInfo; + +extern "C" { +// Define some combos to use for the test, including overlapping combos and +// combos that chord tap-hold keys. +enum combo_events { AB_COMBO, BC_COMBO, AD_COMBO, DE_COMBO, FGHI_COMBO, COMBO_LENGTH }; +uint16_t COMBO_LEN = COMBO_LENGTH; + +const uint16_t ab_combo[] PROGMEM   = {KC_A, KC_B, COMBO_END}; +const uint16_t bc_combo[] PROGMEM   = {KC_B, KC_C, COMBO_END}; +const uint16_t ad_combo[] PROGMEM   = {KC_A, LCTL_T(KC_D), COMBO_END}; +const uint16_t de_combo[] PROGMEM   = {LCTL_T(KC_D), LT(1, KC_E), COMBO_END}; +const uint16_t fghi_combo[] PROGMEM = {KC_F, KC_G, KC_H, KC_I, COMBO_END}; + +// clang-format off +combo_t key_combos[] = { +    [AB_COMBO] = COMBO(ab_combo, KC_SPC),  // KC_A + KC_B = KC_SPC +    [BC_COMBO] = COMBO(bc_combo, KC_X),    // KC_B + KC_C = KC_X +    [AD_COMBO] = COMBO(ad_combo, KC_Y),    // KC_A + LCTL_T(KC_D) = KC_Y +    [DE_COMBO] = COMBO(de_combo, KC_Z),    // LCTL_T(KC_D) + LT(1, KC_E) = KC_Z +    [FGHI_COMBO] = COMBO(fghi_combo, KC_W) // KC_F + KC_G + KC_H + KC_I = KC_W +}; +// clang-format on +} // extern "C" + +namespace { + +// To test combos thorougly, we test them with pressing the chord keys with +// a few different orders and timings. +struct TestParams { +    std::string name; +    bool        autoshift_on; + +    static const std::string& GetName(const TestParamInfo<TestParams>& info) { +        return info.param.name; +    } +}; + +class CapsWord : public ::testing::WithParamInterface<TestParams>, public TestFixture { +   public: +    void SetUp() override { +        caps_word_off(); +        if (GetParam().autoshift_on) { +            autoshift_enable(); +        } else { +            autoshift_disable(); +        } +    } +}; + +// Test pressing the keys in a combo with different orders and timings. +TEST_P(CapsWord, SingleCombo) { +    TestDriver driver; +    KeymapKey  key_b(0, 0, 1, KC_B); +    KeymapKey  key_c(0, 0, 2, KC_C); +    set_keymap({key_b, key_c}); + +    EXPECT_EMPTY_OR_LSFT(driver).Times(AnyNumber()); +    EXPECT_REPORT(driver, (KC_LSFT, KC_X)); + +    caps_word_on(); +    tap_combo({key_b, key_c}); + +    EXPECT_TRUE(is_caps_word_on()); +    caps_word_off(); + +    testing::Mock::VerifyAndClearExpectations(&driver); +} + +// Test a longer 4-key combo. +TEST_P(CapsWord, LongerCombo) { +    TestDriver driver; +    KeymapKey  key_f(0, 0, 0, KC_F); +    KeymapKey  key_g(0, 0, 1, KC_G); +    KeymapKey  key_h(0, 0, 2, KC_H); +    KeymapKey  key_i(0, 0, 3, KC_I); +    set_keymap({key_f, key_g, key_h, key_i}); + +    EXPECT_EMPTY_OR_LSFT(driver).Times(AnyNumber()); +    EXPECT_REPORT(driver, (KC_LSFT, KC_W)); + +    caps_word_on(); +    tap_combo({key_f, key_g, key_h, key_i}); + +    EXPECT_TRUE(is_caps_word_on()); +    caps_word_off(); + +    testing::Mock::VerifyAndClearExpectations(&driver); +} + +// Test with two overlapping combos on regular keys: +// KC_A + KC_B = KC_SPC, +// KC_B + KC_C = KC_X. +TEST_P(CapsWord, ComboRegularKeys) { +    TestDriver driver; +    KeymapKey  key_a(0, 0, 0, KC_A); +    KeymapKey  key_b(0, 0, 1, KC_B); +    KeymapKey  key_c(0, 0, 2, KC_C); +    KeymapKey  key_1(0, 0, 3, KC_1); +    set_keymap({key_a, key_b, key_c, key_1}); + +    EXPECT_EMPTY_OR_LSFT(driver).Times(AnyNumber()); +    { // Expect: "A, B, 1, X, 1, C, space, a". +        InSequence s; +        EXPECT_REPORT(driver, (KC_LSFT, KC_A)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_B)); +        EXPECT_REPORT(driver, (KC_1)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_X)); +        EXPECT_REPORT(driver, (KC_1)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_C)); +        EXPECT_REPORT(driver, (KC_SPC)); +        EXPECT_REPORT(driver, (KC_A)); +    } + +    caps_word_on(); +    tap_key(key_a); +    tap_key(key_b); +    tap_key(key_1); +    tap_combo({key_b, key_c}); // BC combo types "x". +    tap_key(key_1); +    tap_key(key_c); +    tap_combo({key_a, key_b}); // AB combo types space. +    tap_key(key_a); + +    EXPECT_FALSE(is_caps_word_on()); +    testing::Mock::VerifyAndClearExpectations(&driver); +} + +// Test where combo chords involve tap-hold keys: +// KC_A + LCTL_T(KC_D) = KC_Y, +// LCTL_T(KC_D) + LT(1, KC_E) = KC_Z, +TEST_P(CapsWord, ComboModTapKey) { +    TestDriver driver; +    KeymapKey  key_a(0, 0, 0, KC_A); +    KeymapKey  key_modtap_d(0, 0, 1, LCTL_T(KC_D)); +    KeymapKey  key_layertap_e(0, 0, 2, LT(1, KC_E)); +    set_keymap({key_a, key_modtap_d, key_layertap_e}); + +    EXPECT_EMPTY_OR_LSFT(driver).Times(AnyNumber()); +    { // Expect: "A, D, E, Y, Z". +        InSequence s; +        EXPECT_REPORT(driver, (KC_LSFT, KC_A)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_D)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_E)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_Y)); +        EXPECT_REPORT(driver, (KC_LSFT, KC_Z)); +    } + +    caps_word_on(); +    tap_key(key_a); +    tap_key(key_modtap_d); +    tap_key(key_layertap_e); +    tap_combo({key_a, key_modtap_d});          // AD combo types "y". +    tap_combo({key_modtap_d, key_layertap_e}); // DE combo types "z". + +    EXPECT_TRUE(is_caps_word_on()); +    caps_word_off(); + +    testing::Mock::VerifyAndClearExpectations(&driver); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( +    Combos, +    CapsWord, +    ::testing::Values( +        TestParams{"AutoshiftDisabled", false}, +        TestParams{"AutoshiftEnabled", true} +        ), +    TestParams::GetName +    ); +// clang-format on + +} // namespace diff --git a/tests/tap_dance/config.h b/tests/tap_dance/config.h new file mode 100644 index 0000000000..6aada3efd3 --- /dev/null +++ b/tests/tap_dance/config.h @@ -0,0 +1,19 @@ +/* Copyright 2022 Jouke Witteveen + * + * 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/>. + */ + +#pragma once + +#include "test_common.h" diff --git a/tests/tap_dance/examples.c b/tests/tap_dance/examples.c new file mode 100644 index 0000000000..4a5be41b08 --- /dev/null +++ b/tests/tap_dance/examples.c @@ -0,0 +1,199 @@ +/* Copyright 2022 Jouke Witteveen + * + * 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 "quantum.h" +#include "examples.h" + +// Example code from the tap dance documentation, adapted for testing + +// clang-format off + +// Example 1 + +void dance_egg(qk_tap_dance_state_t *state, void *user_data) { +    if (state->count >= 100) { +        // SEND_STRING("Safety dance!"); +        tap_code(KC_C); +        reset_tap_dance(state); +    } +} + + +// Example 2 + +void dance_flsh_each(qk_tap_dance_state_t *state, void *user_data) { +    switch (state->count) { +        case 1: +            register_code(KC_3); +            break; +        case 2: +            register_code(KC_2); +            break; +        case 3: +            register_code(KC_1); +            break; +        case 4: +            unregister_code(KC_3); +            // wait_ms(50); +            unregister_code(KC_2); +            // wait_ms(50); +            unregister_code(KC_1); +    } +} + +void dance_flsh_finished(qk_tap_dance_state_t *state, void *user_data) { +    if (state->count >= 4) { +        // reset_keyboard(); +        tap_code(KC_R); +    } +} + +void dance_flsh_reset(qk_tap_dance_state_t *state, void *user_data) { +    unregister_code(KC_1); +    // wait_ms(50); +    unregister_code(KC_2); +    // wait_ms(50); +    unregister_code(KC_3); +} + + +// Example 3 + +typedef struct { +    uint16_t tap; +    uint16_t hold; +    uint16_t held; +} tap_dance_tap_hold_t; + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { +    qk_tap_dance_action_t *action; + +    switch (keycode) { +        case TD(CT_CLN): +            action = &tap_dance_actions[TD_INDEX(keycode)]; +            if (!record->event.pressed && action->state.count && !action->state.finished) { +                tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)action->user_data; +                tap_code16(tap_hold->tap); +            } +    } +    return true; +} + +void tap_dance_tap_hold_finished(qk_tap_dance_state_t *state, void *user_data) { +    tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data; + +    if (state->pressed) { +        if (state->count == 1 +#ifndef PERMISSIVE_HOLD +            && !state->interrupted +#endif +        ) { +            register_code16(tap_hold->hold); +            tap_hold->held = tap_hold->hold; +        } else { +            register_code16(tap_hold->tap); +            tap_hold->held = tap_hold->tap; +        } +    } +} + +void tap_dance_tap_hold_reset(qk_tap_dance_state_t *state, void *user_data) { +    tap_dance_tap_hold_t *tap_hold = (tap_dance_tap_hold_t *)user_data; + +    if (tap_hold->held) { +        unregister_code16(tap_hold->held); +        tap_hold->held = 0; +    } +} + +#define ACTION_TAP_DANCE_TAP_HOLD(tap, hold) \ +    { .fn = {NULL, tap_dance_tap_hold_finished, tap_dance_tap_hold_reset}, .user_data = (void *)&((tap_dance_tap_hold_t){tap, hold, 0}), } + + +// Example 4 + +typedef enum { +    TD_NONE, +    TD_UNKNOWN, +    TD_SINGLE_TAP, +    TD_SINGLE_HOLD, +    TD_DOUBLE_TAP, +    TD_DOUBLE_HOLD, +    TD_DOUBLE_SINGLE_TAP, +    TD_TRIPLE_TAP, +    TD_TRIPLE_HOLD +} td_state_t; + +typedef struct { +    bool is_press_action; +    td_state_t state; +} td_tap_t; + +td_state_t cur_dance(qk_tap_dance_state_t *state) { +    if (state->count == 1) { +        if (state->interrupted || !state->pressed) return TD_SINGLE_TAP; +        else return TD_SINGLE_HOLD; +    } else if (state->count == 2) { +        if (state->interrupted) return TD_DOUBLE_SINGLE_TAP; +        else if (state->pressed) return TD_DOUBLE_HOLD; +        else return TD_DOUBLE_TAP; +    } + +    if (state->count == 3) { +        if (state->interrupted || !state->pressed) return TD_TRIPLE_TAP; +        else return TD_TRIPLE_HOLD; +    } else return TD_UNKNOWN; +} + +static td_tap_t xtap_state = { +    .is_press_action = true, +    .state = TD_NONE +}; + +void x_finished(qk_tap_dance_state_t *state, void *user_data) { +    xtap_state.state = cur_dance(state); +    switch (xtap_state.state) { +        case TD_SINGLE_TAP: register_code(KC_X); break; +        case TD_SINGLE_HOLD: register_code(KC_LCTL); break; +        case TD_DOUBLE_TAP: register_code(KC_ESC); break; +        case TD_DOUBLE_HOLD: register_code(KC_LALT); break; +        case TD_DOUBLE_SINGLE_TAP: tap_code(KC_X); register_code(KC_X); +        default: break;  // Not present in documentation +    } +} + +void x_reset(qk_tap_dance_state_t *state, void *user_data) { +    switch (xtap_state.state) { +        case TD_SINGLE_TAP: unregister_code(KC_X); break; +        case TD_SINGLE_HOLD: unregister_code(KC_LCTL); break; +        case TD_DOUBLE_TAP: unregister_code(KC_ESC); break; +        case TD_DOUBLE_HOLD: unregister_code(KC_LALT); +        case TD_DOUBLE_SINGLE_TAP: unregister_code(KC_X); +        default: break;  // Not present in documentation +    } +    xtap_state.state = TD_NONE; +} + + +qk_tap_dance_action_t tap_dance_actions[] = { +    [TD_ESC_CAPS] = ACTION_TAP_DANCE_DOUBLE(KC_ESC, KC_CAPS), +    [CT_EGG]      = ACTION_TAP_DANCE_FN(dance_egg), +    [CT_FLSH]     = ACTION_TAP_DANCE_FN_ADVANCED(dance_flsh_each, dance_flsh_finished, dance_flsh_reset), +    [CT_CLN]      = ACTION_TAP_DANCE_TAP_HOLD(KC_COLN, KC_SCLN), +    [X_CTL]       = ACTION_TAP_DANCE_FN_ADVANCED(NULL, x_finished, x_reset) +}; + +// clang-format on diff --git a/tests/tap_dance/examples.h b/tests/tap_dance/examples.h new file mode 100644 index 0000000000..2622af6b2f --- /dev/null +++ b/tests/tap_dance/examples.h @@ -0,0 +1,33 @@ +/* Copyright 2022 Jouke Witteveen + * + * 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/>. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +enum { +    TD_ESC_CAPS, +    CT_EGG, +    CT_FLSH, +    CT_CLN, +    X_CTL, +}; + +#ifdef __cplusplus +} +#endif diff --git a/tests/tap_dance/test.mk b/tests/tap_dance/test.mk new file mode 100644 index 0000000000..041d9b4dc9 --- /dev/null +++ b/tests/tap_dance/test.mk @@ -0,0 +1,22 @@ +# Copyright 2022 Jouke Witteveen +# +# 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/>. + +# -------------------------------------------------------------------------------- +# Keep this file, even if it is empty, as a marker that this folder contains tests +# -------------------------------------------------------------------------------- + +TAP_DANCE_ENABLE = yes + +SRC += examples.c diff --git a/tests/tap_dance/test_examples.cpp b/tests/tap_dance/test_examples.cpp new file mode 100644 index 0000000000..6dabc45513 --- /dev/null +++ b/tests/tap_dance/test_examples.cpp @@ -0,0 +1,318 @@ +/* Copyright 2022 Jouke Witteveen + * + * 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 "action_tapping.h" +#include "test_keymap_key.hpp" +#include "examples.h" + +using testing::_; +using testing::InSequence; + +class TapDance : public TestFixture {}; + +TEST_F(TapDance, DoubleTap) { +    TestDriver driver; +    InSequence s; +    auto       key_esc_caps = KeymapKey{0, 1, 0, TD(TD_ESC_CAPS)}; + +    set_keymap({key_esc_caps}); + +    /* The tap dance key does nothing on the first press */ +    key_esc_caps.press(); +    run_one_scan_loop(); +    key_esc_caps.release(); +    EXPECT_NO_REPORT(driver); + +    /* We get the key press and the release on timeout */ +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_ESC)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Double tap gets us the second key */ +    tap_key(key_esc_caps); +    EXPECT_NO_REPORT(driver); +    key_esc_caps.press(); +    EXPECT_REPORT(driver, (KC_CAPS)); +    run_one_scan_loop(); +    key_esc_caps.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, DoubleTapWithMod) { +    TestDriver driver; +    InSequence s; +    auto       key_esc_caps = KeymapKey{0, 1, 0, TD(TD_ESC_CAPS)}; +    auto       key_shift    = KeymapKey{0, 2, 0, KC_LSFT}; + +    set_keymap({key_esc_caps, key_shift}); + +    /* The tap dance key does nothing on the first press */ +    key_shift.press(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    run_one_scan_loop(); +    key_esc_caps.press(); +    run_one_scan_loop(); + +    key_esc_caps.release(); +    key_shift.release(); +    EXPECT_EMPTY_REPORT(driver); + +    /* We get the key press and the release */ +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_REPORT(driver, (KC_LSFT, KC_ESC)); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Double tap gets us the second key */ +    key_shift.press(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    run_one_scan_loop(); +    tap_key(key_esc_caps); +    EXPECT_NO_REPORT(driver); +    key_shift.release(); +    key_esc_caps.press(); +    EXPECT_REPORT(driver, (KC_LSFT, KC_CAPS)); +    EXPECT_REPORT(driver, (KC_CAPS)); +    run_one_scan_loop(); +    key_esc_caps.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, DoubleTapInterrupted) { +    TestDriver driver; +    InSequence s; +    auto       key_esc_caps = KeymapKey{0, 1, 0, TD(TD_ESC_CAPS)}; +    auto       regular_key  = KeymapKey(0, 2, 0, KC_A); + +    set_keymap({key_esc_caps, regular_key}); + +    /* Interrupted double tap */ +    tap_key(key_esc_caps); +    regular_key.press(); +    /* Immediate tap of the first key */ +    EXPECT_REPORT(driver, (KC_ESC)); +    EXPECT_EMPTY_REPORT(driver); +    /* Followed by the interrupting key */ +    EXPECT_REPORT(driver, (KC_A)); +    run_one_scan_loop(); +    regular_key.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Second tap after being interrupted acts as a single tap */ +    key_esc_caps.press(); +    run_one_scan_loop(); +    key_esc_caps.release(); +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_ESC)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, DanceFn) { +    TestDriver driver; +    InSequence s; +    auto       key_egg = KeymapKey(0, 1, 0, TD(CT_EGG)); + +    set_keymap({key_egg}); + +    /* 99 taps do nothing */ +    for (int i = 0; i < 99; i++) { +        run_one_scan_loop(); +        key_egg.press(); +        run_one_scan_loop(); +        key_egg.release(); +    } +    idle_for(TAPPING_TERM); +    EXPECT_NO_REPORT(driver); +    run_one_scan_loop(); + +    /* 100 taps trigger the action */ +    for (int i = 0; i < 100; i++) { +        run_one_scan_loop(); +        key_egg.press(); +        run_one_scan_loop(); +        key_egg.release(); +    } +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_C)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* 250 taps act the same as 100 taps */ +    /* Taps are counted in an uint8_t, so the count overflows after 255 taps */ +    for (int i = 0; i < 250; i++) { +        run_one_scan_loop(); +        key_egg.press(); +        run_one_scan_loop(); +        key_egg.release(); +    } +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_C)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, DanceFnAdvanced) { +    TestDriver driver; +    InSequence s; +    auto       key_flsh = KeymapKey(0, 1, 0, TD(CT_FLSH)); + +    set_keymap({key_flsh}); + +    /* Three taps don't trigger a reset */ +    EXPECT_REPORT(driver, (KC_3)); +    EXPECT_REPORT(driver, (KC_3, KC_2)); +    EXPECT_REPORT(driver, (KC_3, KC_2, KC_1)); +    for (int i = 0; i < 3; i++) { +        run_one_scan_loop(); +        key_flsh.press(); +        run_one_scan_loop(); +        key_flsh.release(); +    } +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_3, KC_2)); +    EXPECT_REPORT(driver, (KC_3)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Four taps trigger a reset */ +    EXPECT_REPORT(driver, (KC_3)); +    EXPECT_REPORT(driver, (KC_3, KC_2)); +    EXPECT_REPORT(driver, (KC_3, KC_2, KC_1)); +    EXPECT_REPORT(driver, (KC_2, KC_1)); +    EXPECT_REPORT(driver, (KC_1)); +    EXPECT_EMPTY_REPORT(driver); +    for (int i = 0; i < 4; i++) { +        run_one_scan_loop(); +        key_flsh.press(); +        run_one_scan_loop(); +        key_flsh.release(); +    } +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_R)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, TapHold) { +    TestDriver driver; +    InSequence s; +    auto       key_cln = KeymapKey{0, 1, 0, TD(CT_CLN)}; + +    set_keymap({key_cln}); + +    /* Short taps fire on release */ +    key_cln.press(); +    run_one_scan_loop(); +    key_cln.release(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_REPORT(driver, (KC_LSFT, KC_SCLN)); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Holds immediate following a tap apply to the tap key */ +    key_cln.press(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_REPORT(driver, (KC_LSFT, KC_SCLN)); +    idle_for(TAPPING_TERM * 2); +    key_cln.release(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Holds trigger the hold key */ +    key_cln.press(); +    idle_for(TAPPING_TERM); +    run_one_scan_loop(); +    EXPECT_REPORT(driver, (KC_SCLN)); +    run_one_scan_loop(); +    key_cln.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} + +TEST_F(TapDance, QuadFunction) { +    TestDriver driver; +    InSequence s; +    auto       key_quad    = KeymapKey{0, 1, 0, TD(X_CTL)}; +    auto       regular_key = KeymapKey(0, 2, 0, KC_A); + +    set_keymap({key_quad, regular_key}); + +    /* Single tap */ +    key_quad.press(); +    run_one_scan_loop(); +    key_quad.release(); +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_X)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Single hold */ +    key_quad.press(); +    run_one_scan_loop(); +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_LCTL)); +    run_one_scan_loop(); +    key_quad.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Double tap */ +    tap_key(key_quad); +    key_quad.press(); +    run_one_scan_loop(); +    key_quad.release(); +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_ESC)); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Double tap and hold */ +    tap_key(key_quad); +    key_quad.press(); +    run_one_scan_loop(); +    idle_for(TAPPING_TERM); +    EXPECT_REPORT(driver, (KC_LALT)); +    run_one_scan_loop(); +    key_quad.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); + +    /* Double single tap */ +    tap_key(key_quad); +    tap_key(key_quad); +    regular_key.press(); +    EXPECT_REPORT(driver, (KC_X)); +    EXPECT_EMPTY_REPORT(driver); +    EXPECT_REPORT(driver, (KC_X)); +    EXPECT_EMPTY_REPORT(driver); +    EXPECT_REPORT(driver, (KC_A)); +    run_one_scan_loop(); +    regular_key.release(); +    EXPECT_EMPTY_REPORT(driver); +    run_one_scan_loop(); +} diff --git a/tests/test_common/test_fixture.cpp b/tests/test_common/test_fixture.cpp index 5fc6964054..44694cd390 100644 --- a/tests/test_common/test_fixture.cpp +++ b/tests/test_common/test_fixture.cpp @@ -108,6 +108,22 @@ void TestFixture::tap_key(KeymapKey key, unsigned delay_ms) {      run_one_scan_loop();  } +void TestFixture::tap_combo(const std::vector<KeymapKey>& chord_keys, unsigned delay_ms) { +    for (KeymapKey key : chord_keys) { // Press each key. +        key.press(); +        run_one_scan_loop(); +    } + +    if (delay_ms > 1) { +        idle_for(delay_ms - 1); +    } + +    for (KeymapKey key : chord_keys) { // Release each key. +        key.release(); +        run_one_scan_loop(); +    } +} +  void TestFixture::set_keymap(std::initializer_list<KeymapKey> keys) {      this->keymap.clear();      for (auto& key : keys) { diff --git a/tests/test_common/test_fixture.hpp b/tests/test_common/test_fixture.hpp index 81906f76c7..2590acd006 100644 --- a/tests/test_common/test_fixture.hpp +++ b/tests/test_common/test_fixture.hpp @@ -53,6 +53,13 @@ class TestFixture : public testing::Test {          }      } +    /** +     * @brief Taps a combo with `delay_ms` delay between press and release. +     * +     * Example: `tap_combo({key_a, key_b})` to tap the chord A + B. +     */ +    void tap_combo(const std::vector<KeymapKey>& chord_keys, unsigned delay_ms = 1); +      void run_one_scan_loop();      void idle_for(unsigned ms); | 
