summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
authorNick Brassel <nick@tzarc.org>2022-08-28 14:23:01 +1000
committerNick Brassel <nick@tzarc.org>2022-08-28 14:23:01 +1000
commit0a3f7e48690bb2b7b008300a54554979a55be19a (patch)
tree7499d52f20040ed7d5a56496ecb81ed114f80719 /tests
parentfc0bf67f372c38f72c303cdec21b1d4afb5e8cb4 (diff)
parent9b5b0722555891ba94f240760ef3a6d4c870fd13 (diff)
Merge remote-tracking branch 'upstream/develop'
Diffstat (limited to 'tests')
-rw-r--r--tests/basic/test_keypress.cpp22
-rw-r--r--tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp64
-rw-r--r--tests/caps_word/caps_word_combo/config.h20
-rw-r--r--tests/caps_word/caps_word_combo/test.mk19
-rw-r--r--tests/caps_word/caps_word_combo/test_caps_word_combo.cpp212
-rw-r--r--tests/caps_word/test_caps_word.cpp159
-rw-r--r--tests/tap_dance/config.h19
-rw-r--r--tests/tap_dance/examples.c199
-rw-r--r--tests/tap_dance/examples.h33
-rw-r--r--tests/tap_dance/test.mk22
-rw-r--r--tests/tap_dance/test_examples.cpp318
-rw-r--r--tests/tap_hold_configurations/default_mod_tap/test_tap_hold.cpp4
-rw-r--r--tests/test_common/test_fixture.cpp16
-rw-r--r--tests/test_common/test_fixture.hpp7
14 files changed, 1074 insertions, 40 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/caps_word/test_caps_word.cpp b/tests/caps_word/test_caps_word.cpp
index 0af4b0175d..3f59ed3744 100644
--- a/tests/caps_word/test_caps_word.cpp
+++ b/tests/caps_word/test_caps_word.cpp
@@ -25,10 +25,47 @@ using ::testing::AnyOf;
 using ::testing::InSequence;
 using ::testing::TestParamInfo;
 
+namespace {
+
+bool press_user_default(uint16_t keycode) {
+    switch (keycode) {
+        // Keycodes that continue Caps Word, with shift applied.
+        case KC_A ... KC_Z:
+        case KC_MINS:
+            add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to next key.
+            return true;
+
+        // Keycodes that continue Caps Word, without shifting.
+        case KC_1 ... KC_0:
+        case KC_BSPC:
+        case KC_DEL:
+        case KC_UNDS:
+            return true;
+
+        default:
+            return false; // Deactivate Caps Word.
+    }
+}
+
+uint16_t passed_keycode;
+bool     press_user_save_passed_keycode(uint16_t keycode) {
+    passed_keycode = keycode;
+    return true;
+}
+
+bool (*press_user_fun)(uint16_t) = press_user_default;
+
+extern "C" {
+bool caps_word_press_user(uint16_t keycode) {
+    return press_user_fun(keycode);
+}
+} // extern "C"
+
 class CapsWord : public TestFixture {
    public:
     void SetUp() override {
         caps_word_off();
+        press_user_fun = press_user_default;
     }
 };
 
@@ -226,6 +263,126 @@ TEST_F(CapsWord, ShiftsAltGrSymbols) {
     testing::Mock::VerifyAndClearExpectations(&driver);
 }
 
+// Tests typing "AltGr + A" using a mod-tap key.
+TEST_F(CapsWord, ShiftsModTapAltGrSymbols) {
+    TestDriver driver;
+    KeymapKey  key_a(0, 0, 0, KC_A);
+    KeymapKey  key_altgr_t(0, 1, 0, RALT_T(KC_B));
+    set_keymap({key_a, key_altgr_t});
+
+    // Allow any number of reports with no keys or only modifiers.
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(),
+                KeyboardReport(KC_RALT),
+                KeyboardReport(KC_LSFT, KC_RALT))))
+        .Times(AnyNumber());
+    // Expect "Shift + AltGr + A".
+    EXPECT_REPORT(driver, (KC_LSFT, KC_RALT, KC_A));
+    // clang-format on
+
+    // Turn on Caps Word and type "AltGr + A".
+    caps_word_on();
+
+    key_altgr_t.press();
+    idle_for(TAPPING_TERM + 1);
+    tap_key(key_a);
+    run_one_scan_loop();
+    key_altgr_t.release();
+
+    EXPECT_TRUE(is_caps_word_on());
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+struct CapsWordPressUserParams {
+    std::string name;
+    uint16_t    keycode;
+    uint16_t    delay_ms;
+    uint16_t    expected_passed_keycode;
+    bool        continues_caps_word;
+
+    static const std::string& GetName(const TestParamInfo<CapsWordPressUserParams>& info) {
+        return info.param.name;
+    }
+};
+
+class CapsWordPressUser : public ::testing::WithParamInterface<CapsWordPressUserParams>, public CapsWord {
+    void SetUp() override {
+        caps_word_on();
+        passed_keycode = KC_NO;
+        press_user_fun = press_user_save_passed_keycode;
+    }
+};
+
+// Tests keycodes passed to caps_word_press_user() function for various keys.
+TEST_P(CapsWordPressUser, KeyCode) {
+    TestDriver driver;
+    KeymapKey  key(0, 0, 0, GetParam().keycode);
+    set_keymap({key});
+
+    EXPECT_ANY_REPORT(driver).Times(AnyNumber());
+    tap_key(key, GetParam().delay_ms);
+
+    EXPECT_EQ(passed_keycode, GetParam().expected_passed_keycode);
+    EXPECT_EQ(is_caps_word_on(), GetParam().continues_caps_word);
+    clear_oneshot_mods();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+const uint16_t LT_1_KC_A = LT(1, KC_A);
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+    PressUser,
+    CapsWordPressUser,
+    ::testing::Values(
+        CapsWordPressUserParams{
+            "KC_A", KC_A, 1, KC_A, true},
+        CapsWordPressUserParams{
+            "KC_HASH", KC_HASH, 1, KC_HASH, true},
+        CapsWordPressUserParams{
+            "KC_LSFT", KC_LSFT, 1, KC_LSFT, true},
+        CapsWordPressUserParams{
+            "KC_RSFT", KC_RSFT, 1, KC_RSFT, true},
+        CapsWordPressUserParams{
+            "LSFT_T_tapped", LSFT_T(KC_A), 1, KC_A, true},
+        CapsWordPressUserParams{
+            "LSFT_T_held", LSFT_T(KC_A), TAPPING_TERM + 1, KC_LSFT, true},
+        CapsWordPressUserParams{
+            "RSFT_T_held", RSFT_T(KC_A), TAPPING_TERM + 1, KC_RSFT, true},
+        CapsWordPressUserParams{
+            "RSA_T_held", RSA_T(KC_A), TAPPING_TERM + 1, RSFT(KC_RALT), true},
+        // Holding a mod-tap other than Shift or AltGr stops Caps Word.
+        CapsWordPressUserParams{
+            "LCTL_T_held", LCTL_T(KC_A), TAPPING_TERM + 1, KC_NO, false},
+        CapsWordPressUserParams{
+            "LALT_T_held", LALT_T(KC_A), TAPPING_TERM + 1, KC_NO, false},
+        CapsWordPressUserParams{
+            "LGUI_T_held", LGUI_T(KC_A), TAPPING_TERM + 1, KC_NO, false},
+        // Layer keys are ignored and continue Caps Word.
+        CapsWordPressUserParams{
+            "MO", MO(1), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "TO", TO(1), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "TG", TG(1), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "TT", TT(1), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "OSL", OSL(1), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "LT_held", LT_1_KC_A, TAPPING_TERM + 1, KC_NO, true},
+        // AltGr keys are ignored and continue Caps Word.
+        CapsWordPressUserParams{
+            "KC_RALT", KC_RALT, 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "OSM_MOD_RALT", OSM(MOD_RALT), 1, KC_NO, true},
+        CapsWordPressUserParams{
+            "RALT_T_held", RALT_T(KC_A), TAPPING_TERM + 1, KC_NO, true}
+        ),
+    CapsWordPressUserParams::GetName
+    );
+// clang-format on
+
 struct CapsWordBothShiftsParams {
     std::string name;
     uint16_t    left_shift_keycode;
@@ -435,3 +592,5 @@ INSTANTIATE_TEST_CASE_P(
     CapsWordDoubleTapShiftParams::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/tap_hold_configurations/default_mod_tap/test_tap_hold.cpp b/tests/tap_hold_configurations/default_mod_tap/test_tap_hold.cpp
index 687a4e0318..e798265623 100644
--- a/tests/tap_hold_configurations/default_mod_tap/test_tap_hold.cpp
+++ b/tests/tap_hold_configurations/default_mod_tap/test_tap_hold.cpp
@@ -140,8 +140,6 @@ TEST_F(DefaultTapHold, tap_regular_key_while_layer_tap_key_is_held) {
 }
 
 TEST_F(DefaultTapHold, tap_mod_tap_hold_key_two_times) {
-    GTEST_SKIP() << "TODO:Holding a modtap key results in out of bounds access to the keymap, this is a bug in QMK.";
-
     TestDriver driver;
     InSequence s;
     auto       mod_tap_hold_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
@@ -175,8 +173,6 @@ TEST_F(DefaultTapHold, tap_mod_tap_hold_key_two_times) {
 }
 
 TEST_F(DefaultTapHold, tap_mod_tap_hold_key_twice_and_hold_on_second_time) {
-    GTEST_SKIP() << "TODO:Holding a modtap key results in out of bounds access to the keymap, this is a bug in QMK.";
-
     TestDriver driver;
     InSequence s;
     auto       mod_tap_hold_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
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);