aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/caps_word/test.mk2
-rw-r--r--tests/caps_word/test_caps_word.cpp52
-rw-r--r--tests/pointing/test_pointing.cpp20
-rw-r--r--tests/repeat_key/test.mk1
-rw-r--r--tests/repeat_key/test_repeat_key.cpp33
-rw-r--r--tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/config.h23
-rw-r--r--tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test.mk17
-rw-r--r--tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_keymap.c22
-rw-r--r--tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_one_shot_keys.cpp174
-rw-r--r--tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_tap_hold.cpp911
-rw-r--r--tests/tap_hold_configurations/flow_tap/config.h23
-rw-r--r--tests/tap_hold_configurations/flow_tap/test.mk18
-rw-r--r--tests/tap_hold_configurations/flow_tap/test_keymap.c23
-rw-r--r--tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp844
-rw-r--r--tests/test_common/pointing_device_driver.c3
15 files changed, 2158 insertions, 8 deletions
diff --git a/tests/caps_word/test.mk b/tests/caps_word/test.mk
index 2509b01858..6d5664aa05 100644
--- a/tests/caps_word/test.mk
+++ b/tests/caps_word/test.mk
@@ -15,5 +15,7 @@
CAPS_WORD_ENABLE = yes
COMMAND_ENABLE = no
+LAYER_LOCK_ENABLE = yes
SPACE_CADET_ENABLE = yes
+TRI_LAYER_ENABLE = yes
diff --git a/tests/caps_word/test_caps_word.cpp b/tests/caps_word/test_caps_word.cpp
index 28d86e9324..4b58790915 100644
--- a/tests/caps_word/test_caps_word.cpp
+++ b/tests/caps_word/test_caps_word.cpp
@@ -156,21 +156,22 @@ TEST_F(CapsWord, IdleTimeout) {
// Turn on Caps Word and tap "A".
caps_word_on();
tap_key(key_a);
-
VERIFY_AND_CLEAR(driver);
+ EXPECT_EMPTY_REPORT(driver);
idle_for(CAPS_WORD_IDLE_TIMEOUT);
run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
// Caps Word should be off and mods should be clear.
EXPECT_EQ(is_caps_word_on(), false);
EXPECT_EQ(get_mods() | get_weak_mods(), 0);
- EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
// Expect unshifted "A".
EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
tap_key(key_a);
-
+ run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
@@ -244,6 +245,7 @@ TEST_F(CapsWord, ShiftsAltGrSymbols) {
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
+ KeyboardReport(KC_LSFT),
KeyboardReport(KC_RALT),
KeyboardReport(KC_LSFT, KC_RALT))))
.Times(AnyNumber());
@@ -259,6 +261,9 @@ TEST_F(CapsWord, ShiftsAltGrSymbols) {
tap_key(key_a);
run_one_scan_loop();
key_altgr.release();
+ run_one_scan_loop();
+
+ idle_for(CAPS_WORD_IDLE_TIMEOUT);
VERIFY_AND_CLEAR(driver);
}
@@ -274,6 +279,7 @@ TEST_F(CapsWord, ShiftsModTapAltGrSymbols) {
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
+ KeyboardReport(KC_LSFT),
KeyboardReport(KC_RALT),
KeyboardReport(KC_LSFT, KC_RALT))))
.Times(AnyNumber());
@@ -289,8 +295,11 @@ TEST_F(CapsWord, ShiftsModTapAltGrSymbols) {
tap_key(key_a);
run_one_scan_loop();
key_altgr_t.release();
-
+ run_one_scan_loop();
EXPECT_TRUE(is_caps_word_on());
+
+ idle_for(CAPS_WORD_IDLE_TIMEOUT);
+
VERIFY_AND_CLEAR(driver);
}
@@ -535,7 +544,11 @@ TEST_P(CapsWordDoubleTapShift, Activation) {
// machine at this point. This due to imperfect test isolation which can't
// reset the caps word double shift timer on test case setup.
idle_for(CAPS_WORD_IDLE_TIMEOUT);
+
+ EXPECT_REPORT(driver, (KC_ESC));
+ EXPECT_EMPTY_REPORT(driver);
tap_key(esc);
+ VERIFY_AND_CLEAR(driver);
}
// Double tap doesn't count if another key is pressed between the taps.
@@ -589,6 +602,7 @@ TEST_P(CapsWordDoubleTapShift, SlowTaps) {
EXPECT_EQ(is_caps_word_on(), false); // Caps Word is still off.
clear_oneshot_mods();
+ send_keyboard_report();
VERIFY_AND_CLEAR(driver);
}
@@ -626,7 +640,7 @@ TEST_F(CapsWord, IgnoresOSLHold) {
run_one_scan_loop();
tap_key(key_b);
key_osl.release();
- run_one_scan_loop();
+ idle_for(CAPS_WORD_IDLE_TIMEOUT + 1);
VERIFY_AND_CLEAR(driver);
}
@@ -645,15 +659,39 @@ TEST_F(CapsWord, IgnoresOSLTap) {
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
+ // clang-format on
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
caps_word_on();
tap_key(key_osl);
tap_key(key_b);
- run_one_scan_loop();
+ idle_for(CAPS_WORD_IDLE_TIMEOUT);
+
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(CapsWord, IgnoresLayerLockKey) {
+ TestDriver driver;
+ KeymapKey key_llock(0, 1, 0, QK_LAYER_LOCK);
+ KeymapKey key_b(0, 0, 0, KC_B);
+ set_keymap({key_llock, key_b});
+
+ // Allow any number of reports with no keys or only modifiers.
+ // clang-format off
+ EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+ KeyboardReport(),
+ KeyboardReport(KC_LSFT))))
+ .Times(AnyNumber());
+ // clang-format on
+
+ EXPECT_REPORT(driver, (KC_LSFT, KC_B));
+ caps_word_on();
+
+ tap_key(key_llock);
+ tap_key(key_b);
+ idle_for(CAPS_WORD_IDLE_TIMEOUT);
VERIFY_AND_CLEAR(driver);
}
-// clang-format on
} // namespace
diff --git a/tests/pointing/test_pointing.cpp b/tests/pointing/test_pointing.cpp
index d59d014925..bd886b18a2 100644
--- a/tests/pointing/test_pointing.cpp
+++ b/tests/pointing/test_pointing.cpp
@@ -12,6 +12,26 @@ using testing::_;
class Pointing : public TestFixture {};
class PointingButtonsViaMousekeysParametrized : public ::testing::WithParamInterface<std::pair<KeymapKey, uint8_t>>, public Pointing {};
+TEST_F(Pointing, NoMovementOnInitFailure) {
+ TestDriver driver;
+
+ pointing_device_set_status(POINTING_DEVICE_STATUS_INIT_FAILED);
+ pd_set_x(-50);
+ pd_set_y(100);
+ EXPECT_NO_MOUSE_REPORT(driver);
+ run_one_scan_loop();
+
+ pointing_device_set_status(POINTING_DEVICE_STATUS_SUCCESS);
+ EXPECT_MOUSE_REPORT(driver, (-50, 100, 0, 0, 0));
+ run_one_scan_loop();
+
+ pd_clear_movement();
+ // EXPECT_EMPTY_MOUSE_REPORT(driver);
+ run_one_scan_loop();
+
+ VERIFY_AND_CLEAR(driver);
+}
+
TEST_F(Pointing, SendMouseIsNotCalledWithNoInput) {
TestDriver driver;
EXPECT_NO_MOUSE_REPORT(driver);
diff --git a/tests/repeat_key/test.mk b/tests/repeat_key/test.mk
index aec8ff3bfb..186207ffc2 100644
--- a/tests/repeat_key/test.mk
+++ b/tests/repeat_key/test.mk
@@ -16,3 +16,4 @@
REPEAT_KEY_ENABLE = yes
AUTO_SHIFT_ENABLE = yes
+LAYER_LOCK_ENABLE = yes
diff --git a/tests/repeat_key/test_repeat_key.cpp b/tests/repeat_key/test_repeat_key.cpp
index eee44fc104..ed5d618761 100644
--- a/tests/repeat_key/test_repeat_key.cpp
+++ b/tests/repeat_key/test_repeat_key.cpp
@@ -751,4 +751,37 @@ TEST_F(RepeatKey, RepeatKeyInvoke) {
testing::Mock::VerifyAndClearExpectations(&driver);
}
+// Check that mods and Layer Lock are not remembered.
+TEST_F(RepeatKey, IgnoredKeys) {
+ TestDriver driver;
+ KeymapKey regular_key(0, 0, 0, KC_A);
+ KeymapKey key_repeat(0, 1, 0, QK_REP);
+ KeymapKey key_lsft(0, 2, 0, KC_LSFT);
+ KeymapKey key_lctl(0, 3, 0, KC_LCTL);
+ KeymapKey key_llck(0, 4, 0, QK_LAYER_LOCK);
+ set_keymap({regular_key, key_repeat, key_lsft, key_lctl, key_llck});
+
+ // Allow any number of empty reports.
+ EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
+ {
+ InSequence seq;
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_LSFT));
+ EXPECT_REPORT(driver, (KC_LCTL));
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A));
+ }
+
+ tap_key(regular_key); // Taps the KC_A key.
+
+ // Tap Shift, Ctrl, and Layer Lock keys, which should not be remembered.
+ tap_keys(key_lsft, key_lctl, key_llck);
+ EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
+
+ // Tapping the Repeat Key should still reproduce KC_A.
+ tap_keys(key_repeat, key_repeat);
+
+ testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
} // namespace
diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/config.h b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/config.h
new file mode 100644
index 0000000000..9267b94bec
--- /dev/null
+++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/config.h
@@ -0,0 +1,23 @@
+/* Copyright 2022 Vladislav Kucheriavykh
+ * Copyright 2024-2025 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 CHORDAL_HOLD
+#define PERMISSIVE_HOLD
+#define FLOW_TAP_TERM 150
diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test.mk b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test.mk
new file mode 100644
index 0000000000..2b049cea3b
--- /dev/null
+++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test.mk
@@ -0,0 +1,17 @@
+# Copyright 2022 Vladislav Kucheriavykh
+# Copyright 2024 Google LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+INTROSPECTION_KEYMAP_C = test_keymap.c
diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_keymap.c
new file mode 100644
index 0000000000..8a6a2c59b0
--- /dev/null
+++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_keymap.c
@@ -0,0 +1,22 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "quantum.h"
+
+const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = {
+ {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
+ {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
+ {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
+ {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'},
+};
diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_one_shot_keys.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_one_shot_keys.cpp
new file mode 100644
index 0000000000..e48cba73e2
--- /dev/null
+++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_one_shot_keys.cpp
@@ -0,0 +1,174 @@
+/* Copyright 2021 Stefan Kerkmann
+ * Copyright 2024 Google LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "action_util.h"
+#include "keyboard_report_util.hpp"
+#include "test_common.hpp"
+
+using testing::_;
+using testing::InSequence;
+
+class OneShot : public TestFixture {};
+class OneShotParametrizedTestFixture : public ::testing::WithParamInterface<std::pair<KeymapKey, KeymapKey>>, public OneShot {};
+
+TEST_P(OneShotParametrizedTestFixture, OSMWithAdditionalKeypress) {
+ TestDriver driver;
+ KeymapKey osm_key = GetParam().first;
+ KeymapKey regular_key = GetParam().second;
+
+ set_keymap({osm_key, regular_key});
+
+ // Press and release OSM.
+ EXPECT_NO_REPORT(driver);
+ tap_key(osm_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code));
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_P(OneShotParametrizedTestFixture, OSMAsRegularModifierWithAdditionalKeypress) {
+ TestDriver driver;
+ KeymapKey osm_key = GetParam().first;
+ KeymapKey regular_key = GetParam().second;
+
+ set_keymap({osm_key, regular_key});
+
+ // Press OSM.
+ EXPECT_NO_REPORT(driver);
+ osm_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_NO_REPORT(driver);
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_REPORT(driver, (osm_key.report_code)).Times(2);
+ EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release OSM.
+ EXPECT_EMPTY_REPORT(driver);
+ osm_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+// clang-format off
+
+INSTANTIATE_TEST_CASE_P(
+ OneShotModifierTests,
+ OneShotParametrizedTestFixture,
+ ::testing::Values(
+ // First is osm key, second is regular key.
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LCTL), KC_LCTL}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LALT), KC_LALT}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LGUI), KC_LGUI}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RCTL), KC_RCTL}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RSFT), KC_RSFT}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RALT), KC_RALT}, KeymapKey{0, 1, 1, KC_A}),
+ std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RGUI), KC_RGUI}, KeymapKey{0, 1, 1, KC_A})
+ ));
+// clang-format on
+
+TEST_F(OneShot, OSLWithAdditionalKeypress) {
+ TestDriver driver;
+ InSequence s;
+ KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)};
+ KeymapKey osl_key1 = KeymapKey{1, 0, 0, KC_X};
+ KeymapKey regular_key0 = KeymapKey{0, 1, 0, KC_Y};
+ KeymapKey regular_key1 = KeymapKey{1, 1, 0, KC_A};
+
+ set_keymap({osl_key, osl_key1, regular_key0, regular_key1});
+
+ // Press OSL key.
+ EXPECT_NO_REPORT(driver);
+ osl_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release OSL key.
+ EXPECT_NO_REPORT(driver);
+ osl_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_REPORT(driver, (regular_key1.report_code));
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key1.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_NO_REPORT(driver);
+ regular_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(OneShot, OSLWithOsmAndAdditionalKeypress) {
+ TestDriver driver;
+ InSequence s;
+ KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)};
+ KeymapKey osm_key = KeymapKey{1, 1, 0, OSM(MOD_LSFT), KC_LSFT};
+ KeymapKey regular_key = KeymapKey{1, 1, 1, KC_A};
+ KeymapKey blank_key = KeymapKey{1, 0, 0, KC_NO};
+
+ set_keymap({osl_key, osm_key, regular_key, blank_key});
+
+ // Press OSL key.
+ EXPECT_NO_REPORT(driver);
+ osl_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release OSL key.
+ EXPECT_NO_REPORT(driver);
+ osl_key.release();
+ run_one_scan_loop();
+ EXPECT_TRUE(layer_state_is(1));
+ VERIFY_AND_CLEAR(driver);
+
+ // Press and release OSM.
+ EXPECT_NO_REPORT(driver);
+ tap_key(osm_key);
+ EXPECT_TRUE(layer_state_is(1));
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+}
diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_tap_hold.cpp
new file mode 100644
index 0000000000..05acb6f416
--- /dev/null
+++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold_flow_tap/test_tap_hold.cpp
@@ -0,0 +1,911 @@
+// Copyright 2024-2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "keyboard_report_util.hpp"
+#include "keycode.h"
+#include "test_common.hpp"
+#include "action_tapping.h"
+#include "test_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+using testing::_;
+using testing::InSequence;
+
+class ChordalHoldPermissiveHoldFlowTap : public TestFixture {};
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, chordal_hold_handedness) {
+ EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 0}), 'L');
+ EXPECT_EQ(chordal_hold_handedness({.col = MATRIX_COLS - 1, .row = 0}), 'R');
+ EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 2}), '*');
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, get_chordal_hold_default) {
+ auto make_record = [](uint8_t row, uint8_t col, keyevent_type_t type = KEY_EVENT) {
+ return keyrecord_t{
+ .event =
+ {
+ .key = {.col = col, .row = row},
+ .type = type,
+ .pressed = true,
+ },
+ };
+ };
+ // Create two records on the left hand.
+ keyrecord_t record_l0 = make_record(0, 0);
+ keyrecord_t record_l1 = make_record(1, 0);
+ // Create a record on the right hand.
+ keyrecord_t record_r = make_record(0, MATRIX_COLS - 1);
+
+ // Function should return true when records are on opposite hands.
+ EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_r));
+ EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_l0));
+ // ... and false when on the same hand.
+ EXPECT_FALSE(get_chordal_hold_default(&record_l0, &record_l1));
+ EXPECT_FALSE(get_chordal_hold_default(&record_l1, &record_l0));
+ // But (2, 0) has handedness '*', for which true is returned for chords
+ // with either hand.
+ keyrecord_t record_l2 = make_record(2, 0);
+ EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_l0));
+ EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_r));
+
+ // Create a record resulting from a combo.
+ keyrecord_t record_combo = make_record(0, 0, COMBO_EVENT);
+ // Function returns true in all cases.
+ EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_combo));
+ EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_combo));
+ EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_l0));
+ EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_r));
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, chord_nested_press_settled_as_hold) {
+ TestDriver driver;
+ InSequence s;
+ // Mod-tap key on the left hand.
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
+ // Regular key on the right hand.
+ auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
+
+ set_keymap({mod_tap_key, regular_key});
+
+ // Press mod-tap key.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, chord_rolled_press_settled_as_tap) {
+ TestDriver driver;
+ InSequence s;
+ // Mod-tap key on the left hand.
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
+ // Regular key on the right hand.
+ auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
+
+ set_keymap({mod_tap_key, regular_key});
+
+ // Press mod-tap key and regular key.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap key.
+ EXPECT_REPORT(driver, (KC_P));
+ EXPECT_REPORT(driver, (KC_P, KC_A));
+ EXPECT_REPORT(driver, (KC_A));
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, non_chord_with_mod_tap_settled_as_tap) {
+ TestDriver driver;
+ InSequence s;
+ // Mod-tap key and regular key both on the left hand.
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
+ auto regular_key = KeymapKey(0, 2, 0, KC_A);
+
+ set_keymap({mod_tap_key, regular_key});
+
+ // Press mod-tap-hold key.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_REPORT(driver, (KC_P));
+ EXPECT_REPORT(driver, (KC_P, KC_A));
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_REPORT(driver, (KC_P));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap-hold key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, tap_mod_tap_key) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
+
+ set_keymap({mod_tap_key});
+
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM - 1);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_P));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, hold_mod_tap_key) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
+
+ set_keymap({mod_tap_key});
+
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, two_mod_taps_same_hand_hold_til_timeout) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
+
+ set_keymap({mod_tap_key1, mod_tap_key2});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Continue holding til the tapping term.
+ EXPECT_REPORT(driver, (KC_RIGHT_CTRL));
+ EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT));
+ idle_for(TAPPING_TERM);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, two_mod_taps_nested_press_opposite_hands) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B));
+
+ set_keymap({mod_tap_key1, mod_tap_key2});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, two_mod_taps_nested_press_same_hand) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B));
+
+ set_keymap({mod_tap_key1, mod_tap_key2});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, three_mod_taps_same_hand_streak_roll) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 1, 2, 3.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, three_mod_taps_same_hand_streak_orders) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 3, 2, 1.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_A));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 3, 1, 2.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 2, 3, 1.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_A, KC_C));
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, three_mod_taps_opposite_hands_roll) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 1, 2, 3.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, three_mod_taps_two_left_one_right) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release key 3.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release key 2, then key 1.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release key 3.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release key 1, then key 2.
+ EXPECT_REPORT(driver, (KC_LEFT_CTRL));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, three_mod_taps_one_held_two_tapped) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 3, 2, 1.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys 3, 1, 2.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, two_mod_taps_one_regular_key) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B));
+ auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_C);
+
+ set_keymap({mod_tap_key1, mod_tap_key2, regular_key});
+
+ // Press keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys.
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, tap_regular_key_while_layer_tap_key_is_held) {
+ TestDriver driver;
+ InSequence s;
+ auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P));
+ auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
+ auto no_key = KeymapKey(1, 1, 0, XXXXXXX);
+ auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B);
+
+ set_keymap({layer_tap_hold_key, regular_key, no_key, layer_key});
+
+ // Press layer-tap-hold key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_hold_key.press();
+ run_one_scan_loop();
+ // Press regular key.
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_REPORT(driver, (KC_B));
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(layer_state, 2);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release layer-tap-hold key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_hold_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(layer_state, 0);
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, nested_tap_of_layer_0_layer_tap_keys) {
+ TestDriver driver;
+ InSequence s;
+ // The keys are layer-taps on layer 2 but regular keys on layer 1.
+ auto first_layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
+ auto second_layer_tap_key = KeymapKey(0, MATRIX_COLS - 1, 0, LT(1, KC_P));
+ auto first_key_on_layer = KeymapKey(1, 1, 0, KC_B);
+ auto second_key_on_layer = KeymapKey(1, MATRIX_COLS - 1, 0, KC_Q);
+
+ set_keymap({first_layer_tap_key, second_layer_tap_key, first_key_on_layer, second_key_on_layer});
+
+ // Press first layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ first_layer_tap_key.press();
+ run_one_scan_loop();
+ // Press second layer-tap key.
+ second_layer_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release second layer-tap key.
+ EXPECT_REPORT(driver, (KC_Q));
+ EXPECT_EMPTY_REPORT(driver);
+ second_layer_tap_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(layer_state, 2);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release first layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ first_layer_tap_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(layer_state, 0);
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, lt_mt_one_regular_key) {
+ TestDriver driver;
+ InSequence s;
+ auto lt_key = KeymapKey(0, 1, 0, LT(1, KC_A));
+ auto mt_key0 = KeymapKey(0, 2, 0, SFT_T(KC_B));
+ auto mt_key1 = KeymapKey(1, 2, 0, CTL_T(KC_C));
+ auto regular_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_X);
+ auto no_key0 = KeymapKey(0, MATRIX_COLS - 1, 0, XXXXXXX);
+ auto no_key1 = KeymapKey(1, 1, 0, XXXXXXX);
+
+ set_keymap({lt_key, mt_key0, mt_key1, regular_key, no_key0, no_key1});
+
+ // Press LT, MT, and regular key.
+ EXPECT_NO_REPORT(driver);
+ lt_key.press();
+ run_one_scan_loop();
+ mt_key1.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release the regular key.
+ EXPECT_REPORT(driver, (KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LCTL, KC_X));
+ EXPECT_REPORT(driver, (KC_LCTL));
+ regular_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(get_mods(), MOD_BIT_LCTRL);
+ EXPECT_EQ(layer_state, 2);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release MT key.
+ EXPECT_EMPTY_REPORT(driver);
+ mt_key1.release();
+ run_one_scan_loop();
+ EXPECT_EQ(get_mods(), 0);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release LT key.
+ EXPECT_NO_REPORT(driver);
+ lt_key.release();
+ run_one_scan_loop();
+ EXPECT_EQ(layer_state, 0);
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, nested_tap_of_layer_tap_keys) {
+ TestDriver driver;
+ InSequence s;
+ // The keys are layer-taps on all layers.
+ auto first_key_layer_0 = KeymapKey(0, 1, 0, LT(1, KC_A));
+ auto second_key_layer_0 = KeymapKey(0, MATRIX_COLS - 1, 0, LT(1, KC_P));
+ auto first_key_layer_1 = KeymapKey(1, 1, 0, LT(2, KC_B));
+ auto second_key_layer_1 = KeymapKey(1, MATRIX_COLS - 1, 0, LT(2, KC_Q));
+ auto first_key_layer_2 = KeymapKey(2, 1, 0, KC_TRNS);
+ auto second_key_layer_2 = KeymapKey(2, MATRIX_COLS - 1, 0, KC_TRNS);
+
+ set_keymap({first_key_layer_0, second_key_layer_0, first_key_layer_1, second_key_layer_1, first_key_layer_2, second_key_layer_2});
+
+ // Press first layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ first_key_layer_0.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press second layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ second_key_layer_0.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release second layer-tap key.
+ EXPECT_REPORT(driver, (KC_Q));
+ EXPECT_EMPTY_REPORT(driver);
+ second_key_layer_0.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release first layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ first_key_layer_0.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, roll_layer_tap_key_with_regular_key) {
+ TestDriver driver;
+ InSequence s;
+
+ auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P));
+ auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
+ auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B);
+
+ set_keymap({layer_tap_hold_key, regular_key, layer_key});
+
+ // Press layer-tap-hold key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_hold_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_NO_REPORT(driver);
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release layer-tap-hold key.
+ EXPECT_REPORT(driver, (KC_P));
+ EXPECT_REPORT(driver, (KC_P, KC_A));
+ EXPECT_REPORT(driver, (KC_A));
+ layer_tap_hold_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release regular key.
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(ChordalHoldPermissiveHoldFlowTap, two_mod_tap_keys_stuttered_press) {
+ TestDriver driver;
+ InSequence s;
+
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, LSFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, LCTL_T(KC_B));
+
+ set_keymap({mod_tap_key1, mod_tap_key2});
+
+ // Hold first mod-tap key until the tapping term.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ mod_tap_key1.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press the second mod-tap key, then quickly release and press the first.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LSFT, KC_B));
+ EXPECT_REPORT(driver, (KC_B));
+ EXPECT_REPORT(driver, (KC_B, KC_A));
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ EXPECT_EQ(get_mods(), 0); // Verify that Shift was released.
+ VERIFY_AND_CLEAR(driver);
+
+ // Release both keys.
+ EXPECT_REPORT(driver, (KC_A));
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
diff --git a/tests/tap_hold_configurations/flow_tap/config.h b/tests/tap_hold_configurations/flow_tap/config.h
new file mode 100644
index 0000000000..d6f385d8d4
--- /dev/null
+++ b/tests/tap_hold_configurations/flow_tap/config.h
@@ -0,0 +1,23 @@
+/* Copyright 2022 Vladislav Kucheriavykh
+ * Copyright 2025 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 FLOW_TAP_TERM 150
+#define PERMISSIVE_HOLD
diff --git a/tests/tap_hold_configurations/flow_tap/test.mk b/tests/tap_hold_configurations/flow_tap/test.mk
new file mode 100644
index 0000000000..81ba8da66d
--- /dev/null
+++ b/tests/tap_hold_configurations/flow_tap/test.mk
@@ -0,0 +1,18 @@
+# Copyright 2022 Vladislav Kucheriavykh
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+COMBO_ENABLE = yes
+
+INTROSPECTION_KEYMAP_C = test_keymap.c
diff --git a/tests/tap_hold_configurations/flow_tap/test_keymap.c b/tests/tap_hold_configurations/flow_tap/test_keymap.c
new file mode 100644
index 0000000000..4dfe5e4cb6
--- /dev/null
+++ b/tests/tap_hold_configurations/flow_tap/test_keymap.c
@@ -0,0 +1,23 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "quantum.h"
+
+uint16_t const mt_lt_combo[] = {SFT_T(KC_X), LT(1, KC_Y), COMBO_END};
+
+// clang-format off
+combo_t key_combos[] = {
+ COMBO(mt_lt_combo, KC_Z),
+};
+// clang-format on
diff --git a/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp b/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp
new file mode 100644
index 0000000000..d419a3b313
--- /dev/null
+++ b/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp
@@ -0,0 +1,844 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "keyboard_report_util.hpp"
+#include "keycode.h"
+#include "test_common.hpp"
+#include "action_tapping.h"
+#include "test_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+
+class FlowTapTest : public TestFixture {};
+
+// Test an input of quick distinct taps. All should be settled as tapped.
+TEST_F(FlowTapTest, distinct_taps) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
+ auto mod_tap_key3 = KeymapKey(0, 3, 0, ALT_T(KC_D));
+
+ set_keymap({regular_key, mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key, FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap mod-tap 1.
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap mod-tap 2.
+ EXPECT_REPORT(driver, (KC_C));
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap mod-tap 3.
+ EXPECT_REPORT(driver, (KC_D));
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key3.release();
+ idle_for(FLOW_TAP_TERM + 1); // Pause between taps.
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap mod-tap 1.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_B));
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap mod-tap 2.
+ EXPECT_REPORT(driver, (KC_C));
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ mod_tap_key2.release();
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+}
+
+// By default, Flow Tap is disabled when mods other than Shift and AltGr are on.
+TEST_F(FlowTapTest, hotkey_taps) {
+ TestDriver driver;
+ InSequence s;
+ auto ctrl_key = KeymapKey(0, 0, 0, KC_LCTL);
+ auto shft_key = KeymapKey(0, 1, 0, KC_LSFT);
+ auto alt_key = KeymapKey(0, 2, 0, KC_LALT);
+ auto gui_key = KeymapKey(0, 3, 0, KC_LGUI);
+ auto regular_key = KeymapKey(0, 4, 0, KC_A);
+ auto mod_tap_key = KeymapKey(0, 5, 0, RCTL_T(KC_B));
+
+ set_keymap({ctrl_key, shft_key, alt_key, gui_key, regular_key, mod_tap_key});
+
+ for (KeymapKey* mod_key : {&ctrl_key, &alt_key, &gui_key}) {
+ // Hold mod key.
+ EXPECT_REPORT(driver, (mod_key->code));
+ mod_key->press();
+ run_one_scan_loop();
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (mod_key->code, KC_A));
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (mod_key->code));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap, where Flow Tap is disabled due to the held mod.
+ EXPECT_REPORT(driver, (mod_key->code, KC_RCTL));
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap.
+ EXPECT_REPORT(driver, (mod_key->code));
+ mod_tap_key.release();
+ run_one_scan_loop();
+
+ // Release mod key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_key->release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+ }
+
+ // Hold Shift key.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ shft_key.press();
+ run_one_scan_loop();
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_LSFT, KC_A));
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LSFT));
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap, where Flow Tap applies to settle as tapped.
+ EXPECT_REPORT(driver, (KC_LSFT, KC_B));
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ mod_tap_key.release();
+ run_one_scan_loop();
+
+ // Release Shift key.
+ EXPECT_EMPTY_REPORT(driver);
+ shft_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+// Test input with two mod-taps in a rolled press quickly after a regular key.
+TEST_F(FlowTapTest, rolled_press) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
+
+ set_keymap({regular_key, mod_tap_key1, mod_tap_key2});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap key 1 quickly after regular key. The mod-tap should settle
+ // immediately as tapped, sending `KC_B`.
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap key 2 quickly.
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for longer than the tapping term.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, long_flow_tap_settled_as_held) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B));
+
+ set_keymap({regular_key, mod_tap_key});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap key.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for the tapping term.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ idle_for(TAPPING_TERM);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, holding_multiple_mod_taps) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
+
+ set_keymap({regular_key, mod_tap_key1, mod_tap_key2});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ idle_for(TAPPING_TERM - 5); // Hold almost until tapping term.
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL, KC_A));
+ regular_key.press();
+ idle_for(10);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys.
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LCTL));
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, holding_mod_tap_with_regular_mod) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_key = KeymapKey(0, 1, 0, KC_LSFT);
+ auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C));
+
+ set_keymap({regular_key, mod_key, mod_tap_key});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod and mod-tap keys.
+ EXPECT_REPORT(driver, (KC_LSFT));
+ mod_key.press();
+ run_one_scan_loop();
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM - 5); // Hold almost until tapping term.
+ VERIFY_AND_CLEAR(driver);
+
+ // Press regular key.
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL, KC_A));
+ regular_key.press();
+ idle_for(10);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release keys.
+ EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LCTL));
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ mod_key.release();
+ run_one_scan_loop();
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, layer_tap_key) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_B));
+ auto regular_key2 = KeymapKey(1, 0, 0, KC_C);
+
+ set_keymap({regular_key, layer_tap_key, regular_key2});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press layer-tap key, quickly after the regular key.
+ EXPECT_REPORT(driver, (KC_B));
+ layer_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release layer-tap key.
+ EXPECT_EMPTY_REPORT(driver);
+ layer_tap_key.release();
+ run_one_scan_loop();
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press layer-tap key, slowly after the regular key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ EXPECT_EQ(layer_state, 1 << 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap regular key2.
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, layer_tap_ignored_with_disabled_key) {
+ TestDriver driver;
+ InSequence s;
+ auto no_key = KeymapKey(0, 0, 0, KC_NO);
+ auto regular_key = KeymapKey(1, 0, 0, KC_ESC);
+ auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
+ auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
+
+ set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
+
+ EXPECT_REPORT(driver, (KC_ESC));
+ EXPECT_EMPTY_REPORT(driver);
+ layer_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ tap_key(regular_key);
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LCTL));
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, layer_tap_ignored_with_disabled_key_complex) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key1 = KeymapKey(0, 0, 0, KC_Q);
+ auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_SPC));
+ auto mod_tap_key1 = KeymapKey(0, 2, 0, CTL_T(KC_T));
+ // Place RALT_T(KC_I), where Flow Tap is enabled, in the same position on
+ // layer 0 as KC_RGHT, where Flow Tap is disabled. This tests that Flow Tap
+ // tracks the keycode from the correct layer.
+ auto mod_tap_key2 = KeymapKey(0, 3, 0, RALT_T(KC_I));
+ auto regular_key2 = KeymapKey(1, 3, 0, KC_RGHT);
+
+ set_keymap({regular_key1, layer_tap_key, mod_tap_key1, mod_tap_key2, regular_key2});
+
+ // Tap regular key 1.
+ EXPECT_REPORT(driver, (KC_Q));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key1);
+ idle_for(FLOW_TAP_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_key.press();
+ run_one_scan_loop();
+ // idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap regular key 2.
+ EXPECT_REPORT(driver, (KC_RGHT));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key2);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release layer-tap key.
+ EXPECT_NO_REPORT(driver);
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Quickly hold mod-tap key 1.
+ EXPECT_NO_REPORT(driver);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_LCTL));
+ EXPECT_REPORT(driver, (KC_LCTL, KC_Q));
+ EXPECT_REPORT(driver, (KC_LCTL));
+ tap_key(regular_key1);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, layer_tap_ignored_with_enabled_key) {
+ TestDriver driver;
+ InSequence s;
+ auto no_key = KeymapKey(0, 0, 0, KC_NO);
+ auto regular_key = KeymapKey(1, 0, 0, KC_C);
+ auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
+ auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
+
+ set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
+
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ layer_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ tap_key(regular_key);
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_EMPTY_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, combo_key) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_X));
+ auto layer_tap_key = KeymapKey(0, 2, 0, LT(1, KC_Y));
+
+ set_keymap({regular_key, mod_tap_key, layer_tap_key});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Press combo keys quickly after regular key.
+ EXPECT_REPORT(driver, (KC_Z));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_combo({mod_tap_key, layer_tap_key});
+ VERIFY_AND_CLEAR(driver);
+
+ // Press mod-tap key quickly.
+ EXPECT_REPORT(driver, (KC_X));
+ mod_tap_key.press();
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, oneshot_mod_key) {
+ TestDriver driver;
+ InSequence s;
+ auto regular_key = KeymapKey(0, 0, 0, KC_A);
+ auto osm_key = KeymapKey(0, 1, 0, OSM(MOD_LSFT));
+
+ set_keymap({regular_key, osm_key});
+
+ // Tap regular key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Tap OSM, tap regular key.
+ EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
+ EXPECT_REPORT(driver, (KC_LSFT, KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(osm_key);
+ tap_key(regular_key);
+ VERIFY_AND_CLEAR(driver);
+
+ // Nested press of OSM and regular keys.
+ EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
+ EXPECT_REPORT(driver, (KC_LSFT, KC_A));
+ EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
+ EXPECT_EMPTY_REPORT(driver);
+ osm_key.press();
+ run_one_scan_loop();
+ tap_key(regular_key);
+ osm_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, quick_tap) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_A));
+
+ set_keymap({mod_tap_key});
+
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_EMPTY_REPORT(driver);
+ tap_key(mod_tap_key);
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_A));
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap key.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, rolling_mt_mt) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B));
+
+ set_keymap({mod_tap_key1, mod_tap_key2});
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for longer than the tapping term.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, rolling_lt_mt_regular) {
+ TestDriver driver;
+ InSequence s;
+ auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
+ auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_B));
+ auto regular_key = KeymapKey(0, 2, 0, KC_C);
+
+ set_keymap({layer_tap_key, mod_tap_key, regular_key});
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ layer_tap_key.press();
+ run_one_scan_loop();
+ mod_tap_key.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for longer than the tapping term.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key.release();
+ run_one_scan_loop();
+ regular_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, rolling_lt_regular_mt) {
+ TestDriver driver;
+ InSequence s;
+ auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
+ auto regular_key = KeymapKey(0, 1, 0, KC_B);
+ auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C));
+
+ set_keymap({layer_tap_key, regular_key, mod_tap_key});
+
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ layer_tap_key.press();
+ run_one_scan_loop();
+ regular_key.press();
+ run_one_scan_loop();
+ mod_tap_key.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ layer_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for longer than the tapping term.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release mod-tap keys.
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ regular_key.release();
+ run_one_scan_loop();
+ mod_tap_key.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, rolling_mt_mt_mt) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ run_one_scan_loop();
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release first mod-tap key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_A, KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Hold for longer than the tapping term.
+ EXPECT_NO_REPORT(driver);
+ idle_for(TAPPING_TERM + 1);
+ VERIFY_AND_CLEAR(driver);
+
+ // Release other mod-tap keys.
+ EXPECT_REPORT(driver, (KC_C));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
+
+TEST_F(FlowTapTest, roll_release_132) {
+ TestDriver driver;
+ InSequence s;
+ auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A));
+ auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B));
+ auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C));
+
+ set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
+
+ // Press mod-tap keys.
+ EXPECT_NO_REPORT(driver);
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key1.press();
+ run_one_scan_loop();
+ mod_tap_key2.press();
+ idle_for(FLOW_TAP_TERM + 1);
+ mod_tap_key3.press();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release first mod-tap key.
+ EXPECT_REPORT(driver, (KC_A));
+ EXPECT_REPORT(driver, (KC_A, KC_B));
+ EXPECT_REPORT(driver, (KC_B));
+ mod_tap_key1.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+
+ // Release other mod-tap keys.
+ EXPECT_REPORT(driver, (KC_B, KC_C));
+ EXPECT_REPORT(driver, (KC_B));
+ EXPECT_EMPTY_REPORT(driver);
+ mod_tap_key3.release();
+ run_one_scan_loop();
+ mod_tap_key2.release();
+ run_one_scan_loop();
+ VERIFY_AND_CLEAR(driver);
+}
diff --git a/tests/test_common/pointing_device_driver.c b/tests/test_common/pointing_device_driver.c
index b64dbad506..42c7a814a4 100644
--- a/tests/test_common/pointing_device_driver.c
+++ b/tests/test_common/pointing_device_driver.c
@@ -22,8 +22,9 @@ typedef struct {
static pd_config_t pd_config = {0};
-void pointing_device_driver_init(void) {
+bool pointing_device_driver_init(void) {
pd_set_init(true);
+ return pd_config.initiated;
}
report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {