diff options
Diffstat (limited to 'tests')
| -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 | 319 | 
5 files changed, 592 insertions, 0 deletions
| 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..e67e6cb907 --- /dev/null +++ b/tests/tap_dance/test_examples.cpp @@ -0,0 +1,319 @@ +/* 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)); +    run_one_scan_loop(); +    key_esc_caps.release(); +    EXPECT_REPORT(driver, (KC_LSFT)); +    run_one_scan_loop(); +    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(); +} | 
