diff --git a/data/mappings/defaults.json b/data/mappings/defaults.json
index b62e5450b34..5e25405c37c 100644
--- a/data/mappings/defaults.json
+++ b/data/mappings/defaults.json
@@ -34,6 +34,12 @@
"board": "QMK_PM2040",
"pin_compatible": "promicro"
},
+ "bit_c_pro": {
+ "processor": "RP2040",
+ "bootloader": "rp2040",
+ "board": "QMK_PM2040",
+ "pin_compatible": "promicro"
+ },
"bluepill": {
"processor": "STM32F103",
"bootloader": "stm32duino",
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index 14f84c55959..c55d66e6bbf 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -34,7 +34,7 @@
},
"development_board": {
"type": "string",
- "enum": ["promicro", "elite_c", "proton_c", "kb2040", "promicro_rp2040", "blok", "bluepill", "blackpill_f401", "blackpill_f411"]
+ "enum": ["promicro", "elite_c", "proton_c", "kb2040", "promicro_rp2040", "blok", "bit_c_pro", "bluepill", "blackpill_f401", "blackpill_f411"]
},
"pin_compatible": {
"type": "string",
diff --git a/docs/feature_converters.md b/docs/feature_converters.md
index 11dcc62b2a3..569cf0f17d5 100644
--- a/docs/feature_converters.md
+++ b/docs/feature_converters.md
@@ -14,6 +14,7 @@ Currently the following converters are available:
| `promicro` | `kb2040` |
| `promicro` | `promicro_rp2040` |
| `promicro` | `blok` |
+| `promicro` | `bit_c_pro` |
See below for more in depth information on each converter.
@@ -54,6 +55,7 @@ If a board currently supported in QMK uses a [Pro Micro](https://www.sparkfun.co
| [Adafruit KB2040](https://learn.adafruit.com/adafruit-kb2040) | `kb2040` |
| [SparkFun Pro Micro - RP2040](https://www.sparkfun.com/products/18288) | `promicro_rp2040` |
| [Blok](https://boardsource.xyz/store/628b95b494dfa308a6581622) | `blok` |
+| [Bit-C PRO](https://nullbits.co/bit-c-pro) | `bit_c_pro` |
Converter summary:
@@ -63,6 +65,7 @@ Converter summary:
| `kb2040` | `-e CONVERT_TO=kb2040` | `CONVERT_TO=kb2040` | `#ifdef CONVERT_TO_KB2040` |
| `promicro_rp2040` | `-e CONVERT_TO=promicro_rp2040` | `CONVERT_TO=promicro_rp2040` | `#ifdef CONVERT_TO_PROMICRO_RP2040` |
| `blok` | `-e CONVERT_TO=blok` | `CONVERT_TO=blok` | `#ifdef CONVERT_TO_BLOK` |
+| `bit_c_pro` | `-e CONVERT_TO=bit_c_pro` | `CONVERT_TO=bit_c_pro` | `#ifdef CONVERT_TO_BIT_C_PRO` |
### Proton C :id=proton_c
@@ -93,6 +96,6 @@ The following defaults are based on what has been implemented for [RP2040](platf
| USB Host (e.g. USB-USB converter) | Not supported (USB host code is AVR specific and is not currently supported on ARM) |
| [Split keyboards](feature_split_keyboard.md) | Partial via `PIO` vendor driver - heavily dependent on enabled features |
-### SparkFun Pro Micro - RP2040 and Blok :id=promicro_rp2040
+### SparkFun Pro Micro - RP2040, Blok, and Bit-C PRO :id=promicro_rp2040
Currently identical to [Adafruit KB2040](#kb2040).
diff --git a/platforms/chibios/converters/promicro_to_bit_c_pro/_pin_defs.h b/platforms/chibios/converters/promicro_to_bit_c_pro/_pin_defs.h
new file mode 100644
index 00000000000..92e8fbfa1a9
--- /dev/null
+++ b/platforms/chibios/converters/promicro_to_bit_c_pro/_pin_defs.h
@@ -0,0 +1,39 @@
+// Copyright 2022 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+// Left side (front)
+#define D3 0U
+#define D2 1U
+// GND
+// GND
+#define D1 2U
+#define D0 3U
+#define D4 4U
+#define C6 5U
+#define D7 6U
+#define E6 7U
+#define B4 8U
+#define B5 9U
+
+// Right side (front)
+// RAW
+// GND
+// RESET
+// VCC
+#define F4 29U
+#define F5 28U
+#define F6 27U
+#define F7 26U
+#define B1 22U
+#define B3 20U
+#define B2 23U
+#define B6 21U
+
+// LEDs (Mapped to R and G channel of the Bit-C PRO's RGB led)
+#define D5 16U
+#define B0 17U
+
+// Bit-C LED (mapped to B channel of the Bit-C PRO's RGB led)
+#define F0 18U
diff --git a/platforms/chibios/converters/promicro_to_bit_c_pro/converter.mk b/platforms/chibios/converters/promicro_to_bit_c_pro/converter.mk
new file mode 100644
index 00000000000..c0eaee85934
--- /dev/null
+++ b/platforms/chibios/converters/promicro_to_bit_c_pro/converter.mk
@@ -0,0 +1,12 @@
+# nullbits Bit-C PRO MCU settings for converting AVR projects
+MCU := RP2040
+BOARD := QMK_PM2040
+BOOTLOADER := rp2040
+
+# These are defaults based on what has been implemented for RP2040 boards
+SERIAL_DRIVER ?= vendor
+WS2812_DRIVER ?= vendor
+BACKLIGHT_DRIVER ?= software
+
+# Tell QMK to use the correct 2nd stage bootloader
+OPT_DEFS += -DRP2040_FLASH_W25X10CL
diff --git a/quantum/process_keycode/process_auto_shift.c b/quantum/process_keycode/process_auto_shift.c
index 8cb45bc0ae1..3ff188ba7eb 100644
--- a/quantum/process_keycode/process_auto_shift.c
+++ b/quantum/process_keycode/process_auto_shift.c
@@ -123,7 +123,12 @@ bool get_autoshift_shift_state(uint16_t keycode) {
/** \brief Restores the shift key if it was cancelled by Auto Shift */
static void autoshift_flush_shift(void) {
autoshift_flags.holding_shift = false;
- del_weak_mods(MOD_BIT(KC_LSFT));
+# ifdef CAPS_WORD_ENABLE
+ if (!is_caps_word_on())
+# endif // CAPS_WORD_ENABLE
+ {
+ del_weak_mods(MOD_BIT(KC_LSFT));
+ }
if (autoshift_flags.cancelling_lshift) {
autoshift_flags.cancelling_lshift = false;
add_mods(MOD_BIT(KC_LSFT));
diff --git a/quantum/process_keycode/process_caps_word.c b/quantum/process_keycode/process_caps_word.c
index ffd509a9142..bc4369846cb 100644
--- a/quantum/process_keycode/process_caps_word.c
+++ b/quantum/process_keycode/process_caps_word.c
@@ -131,7 +131,11 @@ bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
#endif // SWAP_HANDS_ENABLE
}
+#ifdef AUTO_SHIFT_ENABLE
+ del_weak_mods(get_autoshift_state() ? ~MOD_BIT(KC_LSFT) : 0xff);
+#else
clear_weak_mods();
+#endif // AUTO_SHIFT_ENABLE
if (caps_word_press_user(keycode)) {
send_keyboard_report();
return true;
diff --git a/quantum/process_keycode/process_combo.c b/quantum/process_keycode/process_combo.c
index d5a649adb36..e5135e5a645 100644
--- a/quantum/process_keycode/process_combo.c
+++ b/quantum/process_keycode/process_combo.c
@@ -227,7 +227,16 @@ static inline void dump_key_buffer(void) {
#endif
}
record->event.time = 0;
+
+#if defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
+ // Edge case: preserve the weak Left Shift mod if both Caps Word and
+ // Auto Shift are on. Caps Word capitalizes by setting the weak Left
+ // Shift mod during the press event, but Auto Shift doesn't send the
+ // key until it receives the release event.
+ del_weak_mods((is_caps_word_on() && get_autoshift_state()) ? ~MOD_BIT(KC_LSFT) : 0xff);
+#else
clear_weak_mods();
+#endif // defined(CAPS_WORD_ENABLE) && defined(AUTO_SHIFT_ENABLE)
#if TAP_CODE_DELAY > 0
// only delay once and for a non-tapping key
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 deb4d95766e..ba21c527a67 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 00000000000..92dbe045b29
--- /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 .
+
+#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 00000000000..9f2e157189f
--- /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 .
+
+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 00000000000..3a0530b8543
--- /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 .
+
+// Test Caps Word + Combos, with and without Auto Shift.
+
+#include
+#include
+#include
+
+#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& info) {
+ return info.param.name;
+ }
+};
+
+class CapsWord : public ::testing::WithParamInterface, 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/test_common/test_fixture.cpp b/tests/test_common/test_fixture.cpp
index 5fc6964054b..44694cd390c 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& 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 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 81906f76c7c..2590acd006e 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& chord_keys, unsigned delay_ms = 1);
+
void run_one_scan_loop();
void idle_for(unsigned ms);