Browse Source

Improve test invocation, fix Retro Shift bugs, and add Auto+Retro Shift test cases (#15889)

Isaac Elenbaas 2 years ago
parent
commit
dd94877ec6
48 changed files with 2584 additions and 89 deletions
  1. 17 9
      Makefile
  2. 4 4
      builddefs/build_full_test.mk
  3. 8 8
      builddefs/build_test.mk
  4. 12 6
      docs/feature_auto_shift.md
  5. 3 1
      docs/unit_testing.md
  6. 1 1
      quantum/action.c
  7. 39 39
      quantum/action_tapping.c
  8. 17 12
      quantum/process_keycode/process_auto_shift.c
  9. 1 0
      quantum/process_keycode/process_auto_shift.h
  10. 22 0
      tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/config.h
  11. 20 0
      tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/test.mk
  12. 105 0
      tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/test_auto_shift.cpp
  13. 21 0
      tests/auto_shift/auto_shift_repeat/config.h
  14. 20 0
      tests/auto_shift/auto_shift_repeat/test.mk
  15. 107 0
      tests/auto_shift/auto_shift_repeat/test_auto_shift.cpp
  16. 1 1
      tests/auto_shift/config.h
  17. 24 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/config.h
  18. 21 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/config.h
  19. 20 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/test.mk
  20. 57 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/test_retro_shift.cpp
  21. 20 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/test.mk
  22. 485 0
      tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/test_retro_shift.cpp
  23. 26 0
      tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/config.h
  24. 20 0
      tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/test.mk
  25. 442 0
      tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/test_retro_shift.cpp
  26. 26 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/config.h
  27. 20 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/test.mk
  28. 419 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/test_retro_shift.cpp
  29. 27 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/config.h
  30. 20 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/test.mk
  31. 442 0
      tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/test_retro_shift.cpp
  32. 1 1
      tests/auto_shift/test.mk
  33. 1 1
      tests/basic/config.h
  34. 1 1
      tests/basic/test.mk
  35. 21 0
      tests/caps_word/auto_shift/config.h
  36. 0 0
      tests/caps_word/auto_shift/retro_shift/config.h
  37. 0 0
      tests/caps_word/auto_shift/retro_shift/test.mk
  38. 4 0
      tests/caps_word/auto_shift/retro_shift/test_caps_word_retroshift.cpp
  39. 18 0
      tests/caps_word/auto_shift/test.mk
  40. 66 0
      tests/caps_word/auto_shift/test_caps_word_autoshift.cpp
  41. 0 0
      tests/caps_word/unicodemap/config.h
  42. 0 0
      tests/caps_word/unicodemap/test.mk
  43. 0 0
      tests/caps_word/unicodemap/test_caps_word_unicodemap.cpp
  44. 1 1
      tests/tap_hold_configurations/default_mod_tap/test.mk
  45. 1 1
      tests/tap_hold_configurations/permissive_hold/config.h
  46. 1 1
      tests/test_common/build.mk
  47. 1 1
      tests/test_common/main.cpp
  48. 1 1
      tests/test_common/test_common.h

+ 17 - 9
Makefile

@@ -300,17 +300,18 @@ endef
 define BUILD_TEST
     TEST_PATH := $1
     TEST_NAME := $$(notdir $$(TEST_PATH))
+    TEST_FULL_NAME := $$(subst /,_,$$(patsubst $$(ROOT_DIR)tests/%,%,$$(TEST_PATH)))
     MAKE_TARGET := $2
     COMMAND := $1
     MAKE_CMD := $$(MAKE) -r -R -C $(ROOT_DIR) -f $(BUILDDEFS_PATH)/build_test.mk $$(MAKE_TARGET)
-    MAKE_VARS := TEST=$$(TEST_NAME) TEST_PATH=$$(TEST_PATH) FULL_TESTS="$$(FULL_TESTS)"
+    MAKE_VARS := TEST=$$(TEST_NAME) TEST_OUTPUT=$$(TEST_FULL_NAME) TEST_PATH=$$(TEST_PATH) FULL_TESTS="$$(FULL_TESTS)"
     MAKE_MSG := $$(MSG_MAKE_TEST)
     $$(eval $$(call BUILD))
     ifneq ($$(MAKE_TARGET),clean)
-        TEST_EXECUTABLE := $$(TEST_OUTPUT_DIR)/$$(TEST_NAME).elf
-        TESTS += $$(TEST_NAME)
+        TEST_EXECUTABLE := $$(TEST_OUTPUT_DIR)/$$(TEST_FULL_NAME).elf
+        TESTS += $$(TEST_FULL_NAME)
         TEST_MSG := $$(MSG_TEST)
-        $$(TEST_NAME)_COMMAND := \
+        $$(TEST_FULL_NAME)_COMMAND := \
             printf "$$(TEST_MSG)\n"; \
             $$(TEST_EXECUTABLE); \
             if [ $$$$? -gt 0 ]; \
@@ -322,15 +323,22 @@ endef
 
 define PARSE_TEST
     TESTS :=
-    TEST_NAME := $$(firstword $$(subst :, ,$$(RULE)))
-    TEST_TARGET := $$(subst $$(TEST_NAME),,$$(subst $$(TEST_NAME):,,$$(RULE)))
+    # list of possible targets, colon-delimited, to reassign to MAKE_TARGET and remove
+    TARGETS := :clean:
+    ifneq (,$$(findstring :$$(lastword $$(subst :, ,$$(RULE))):, $$(TARGETS)))
+        MAKE_TARGET := $$(lastword $$(subst :, ,$$(RULE)))
+        TEST_SUBPATH := $$(subst $$(eval) ,/,$$(wordlist 2, $$(words $$(subst :, ,$$(RULE))), _ $$(subst :, ,$$(RULE))))
+    else
+        MAKE_TARGET :=
+        TEST_SUBPATH := $$(subst :,/,$$(RULE))
+    endif
     include $(BUILDDEFS_PATH)/testlist.mk
-    ifeq ($$(TEST_NAME),all)
+    ifeq ($$(RULE),all)
         MATCHED_TESTS := $$(TEST_LIST)
     else
-        MATCHED_TESTS := $$(foreach TEST, $$(TEST_LIST),$$(if $$(findstring x$$(TEST_NAME)x, x$$(notdir $$(TEST))x), $$(TEST),))
+        MATCHED_TESTS := $$(foreach TEST, $$(TEST_LIST),$$(if $$(findstring /$$(TEST_SUBPATH)/, $$(patsubst %,%/,$$(TEST))), $$(TEST),))
     endif
-    $$(foreach TEST,$$(MATCHED_TESTS),$$(eval $$(call BUILD_TEST,$$(TEST),$$(TEST_TARGET))))
+    $$(foreach TEST,$$(MATCHED_TESTS),$$(eval $$(call BUILD_TEST,$$(TEST),$$(MAKE_TARGET))))
 endef
 
 

+ 4 - 4
builddefs/build_full_test.mk

@@ -13,10 +13,10 @@
 # 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)_INC := \
+$(TEST_OUTPUT)_INC := \
 	tests/test_common/common_config.h
 
-$(TEST)_SRC := \
+$(TEST_OUTPUT)_SRC := \
 	$(QUANTUM_SRC) \
 	$(SRC) \
 	$(QUANTUM_PATH)/keymap_introspection.c \
@@ -30,8 +30,8 @@ $(TEST)_SRC := \
 	tests/test_common/test_logger.cpp \
 	$(patsubst $(ROOTDIR)/%,%,$(wildcard $(TEST_PATH)/*.cpp))
 
-$(TEST)_DEFS := $(OPT_DEFS) "-DKEYMAP_C=\"keymap.c\""
+$(TEST_OUTPUT)_DEFS := $(OPT_DEFS) "-DKEYMAP_C=\"keymap.c\""
 
-$(TEST)_CONFIG := $(TEST_PATH)/config.h
+$(TEST_OUTPUT)_CONFIG := $(TEST_PATH)/config.h
 
 VPATH += $(TOP_DIR)/tests/test_common

+ 8 - 8
builddefs/build_test.mk

@@ -9,13 +9,13 @@ OPT = g
 include paths.mk
 include $(BUILDDEFS_PATH)/message.mk
 
-TARGET=test/$(TEST)
+TARGET=test/$(TEST_OUTPUT)
 
 GTEST_OUTPUT = $(BUILD_DIR)/gtest
 
 TEST_OBJ = $(BUILD_DIR)/test_obj
 
-OUTPUTS := $(TEST_OBJ)/$(TEST) $(GTEST_OUTPUT)
+OUTPUTS := $(TEST_OBJ)/$(TEST_OUTPUT) $(GTEST_OUTPUT)
 
 GTEST_INC := \
 	$(LIB_PATH)/googletest/googletest/include \
@@ -71,18 +71,18 @@ ifneq ($(filter $(FULL_TESTS),$(TEST)),)
 include $(BUILDDEFS_PATH)/build_full_test.mk
 endif
 
-$(TEST)_SRC += \
+$(TEST_OUTPUT)_SRC += \
 	tests/test_common/main.cpp \
 	$(QUANTUM_PATH)/logging/print.c
 
 ifneq ($(strip $(INTROSPECTION_KEYMAP_C)),)
-$(TEST)_DEFS += -DINTROSPECTION_KEYMAP_C=\"$(strip $(INTROSPECTION_KEYMAP_C))\"
+$(TEST_OUTPUT)_DEFS += -DINTROSPECTION_KEYMAP_C=\"$(strip $(INTROSPECTION_KEYMAP_C))\"
 endif
 
-$(TEST_OBJ)/$(TEST)_SRC := $($(TEST)_SRC)
-$(TEST_OBJ)/$(TEST)_INC := $($(TEST)_INC) $(VPATH) $(GTEST_INC)
-$(TEST_OBJ)/$(TEST)_DEFS := $($(TEST)_DEFS)
-$(TEST_OBJ)/$(TEST)_CONFIG := $($(TEST)_CONFIG)
+$(TEST_OBJ)/$(TEST_OUTPUT)_SRC := $($(TEST_OUTPUT)_SRC)
+$(TEST_OBJ)/$(TEST_OUTPUT)_INC := $($(TEST_OUTPUT)_INC) $(VPATH) $(GTEST_INC)
+$(TEST_OBJ)/$(TEST_OUTPUT)_DEFS := $($(TEST_OUTPUT)_DEFS)
+$(TEST_OBJ)/$(TEST_OUTPUT)_CONFIG := $($(TEST_OUTPUT)_CONFIG)
 
 include $(PLATFORM_PATH)/$(PLATFORM_KEY)/platform.mk
 include $(BUILDDEFS_PATH)/common_rules.mk

+ 12 - 6
docs/feature_auto_shift.md

@@ -180,18 +180,18 @@ For more granular control, there is `get_auto_shifted_key`. The default function
 bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
     switch (keycode) {
 #    ifndef NO_AUTO_SHIFT_ALPHA
-        case KC_A ... KC_Z:
+        case AUTO_SHIFT_ALPHA:
 #    endif
 #    ifndef NO_AUTO_SHIFT_NUMERIC
-        case KC_1 ... KC_0:
+        case AUTO_SHIFT_NUMERIC:
 #    endif
 #    ifndef NO_AUTO_SHIFT_SPECIAL
-#    ifndef NO_AUTO_SHIFT_TAB
+#        ifndef NO_AUTO_SHIFT_TAB
         case KC_TAB:
-#    endif
-#    ifndef NO_AUTO_SHIFT_SYMBOLS
+#        endif
+#        ifndef NO_AUTO_SHIFT_SYMBOLS
         case AUTO_SHIFT_SYMBOLS:
-#    endif
+#        endif
 #    endif
 #    ifdef AUTO_SHIFT_ENTER
         case KC_ENT:
@@ -310,10 +310,16 @@ generating taps on release. For example:
 #define RETRO_SHIFT 500
 ```
 
+Without a value set, holds of any length without an interrupting key will produce the shifted value.
+
 This value (if set) must be greater than one's `TAPPING_TERM`, as the key press
 must be designated as a 'hold' by `process_tapping` before we send the modifier.
+[Per-key tapping terms](tap_hold.md#tapping-term) can be used as a workaround.
 There is no such limitation in regards to `AUTO_SHIFT_TIMEOUT` for normal keys.
 
+**Note:** Tap Holds must be added to Auto Shift, see [here.](feature_auto_shift.md#auto-shift-per-key)
+`IS_RETRO` may be helpful if one wants all Tap Holds retro shifted.
+
 ### Retro Shift and Tap Hold Configurations
 
 Tap Hold Configurations work a little differently when using Retro Shift.

+ 3 - 1
docs/unit_testing.md

@@ -36,7 +36,9 @@ Note how there's several different tests, each mocking out a separate part. Also
 
 ## Running the Tests
 
-To run all the tests in the codebase, type `make test:all`. You can also run test matching a substring by typing `make test:matchingsubstring` Note that the tests are always compiled with the native compiler of your platform, so they are also run like any other program on your computer.
+To run all the tests in the codebase, type `make test:all`. You can also run test matching a substring by typing `make test:matchingsubstring`. `matchingsubstring` can contain colons to be more specific; `make test:tap_hold_configurations` will run the `tap_hold_configurations` tests for all features while `make test:retro_shift:tap_hold_configurations` will run the `tap_hold_configurations` tests for only the Retro Shift feature.
+
+Note that the tests are always compiled with the native compiler of your platform, so they are also run like any other program on your computer.
 
 ## Debugging the Tests
 

+ 1 - 1
quantum/action.c

@@ -497,7 +497,7 @@ void process_action(keyrecord_t *record, action_t action) {
                 default:
                     if (event.pressed) {
                         if (tap_count > 0) {
-#    ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
+#    ifdef HOLD_ON_OTHER_KEY_PRESS
                             if (
 #        ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
                                 get_hold_on_other_key_press(get_event_keycode(record->event, false), record) &&

+ 39 - 39
quantum/action_tapping.c

@@ -116,25 +116,26 @@ void action_tapping_process(keyrecord_t record) {
  * readable. The conditional definition of tapping_keycode and all the
  * conditional uses of it are hidden inside macros named TAP_...
  */
-#    if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
-#        define TAP_DEFINE_KEYCODE const uint16_t tapping_keycode = get_record_keycode(&tapping_key, false)
-#    else
-#        define TAP_DEFINE_KEYCODE
-#    endif
+#    define TAP_DEFINE_KEYCODE const uint16_t tapping_keycode = get_record_keycode(&tapping_key, false)
 
 #    if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
 #        ifdef RETRO_TAPPING_PER_KEY
-#            define TAP_GET_RETRO_TAPPING get_retro_tapping(tapping_keycode, &tapping_key)
+#            define TAP_GET_RETRO_TAPPING(keyp) get_auto_shifted_key(tapping_keycode, keyp) && get_retro_tapping(tapping_keycode, &tapping_key)
 #        else
-#            define TAP_GET_RETRO_TAPPING true
+#            define TAP_GET_RETRO_TAPPING(keyp) get_auto_shifted_key(tapping_keycode, keyp)
 #        endif
-#        define MAYBE_RETRO_SHIFTING(ev) (TAP_GET_RETRO_TAPPING && (RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16((ev).time, tapping_key.event.time) < (RETRO_SHIFT + 0))
+/* Used to extend TAPPING_TERM:
+ *     indefinitely if RETRO_SHIFT does not have a value
+ *     to RETRO_SHIFT if RETRO_SHIFT is set
+ * for possibly retro shifted keys.
+ */
+#        define MAYBE_RETRO_SHIFTING(ev, keyp) (get_auto_shifted_key(tapping_keycode, keyp) && TAP_GET_RETRO_TAPPING(keyp) && ((RETRO_SHIFT + 0) == 0 || TIMER_DIFF_16((ev).time, tapping_key.event.time) < (RETRO_SHIFT + 0)))
 #        define TAP_IS_LT IS_QK_LAYER_TAP(tapping_keycode)
 #        define TAP_IS_MT IS_QK_MOD_TAP(tapping_keycode)
 #        define TAP_IS_RETRO IS_RETRO(tapping_keycode)
 #    else
-#        define TAP_GET_RETRO_TAPPING false
-#        define MAYBE_RETRO_SHIFTING(ev) false
+#        define TAP_GET_RETRO_TAPPING(keyp) false
+#        define MAYBE_RETRO_SHIFTING(ev, kp) false
 #        define TAP_IS_LT false
 #        define TAP_IS_MT false
 #        define TAP_IS_RETRO false
@@ -187,20 +188,19 @@ bool process_tapping(keyrecord_t *keyp) {
         return true;
     }
 
+#    if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
     TAP_DEFINE_KEYCODE;
+#    endif
 
     // process "pressed" tapping key state
     if (tapping_key.event.pressed) {
-        if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event)) {
+        if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event, keyp)) {
             if (IS_NOEVENT(event)) {
                 // early return for tick events
                 return true;
             }
             if (tapping_key.tap.count == 0) {
                 if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
-#    if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
-                    retroshift_swap_times();
-#    endif
                     // first tap!
                     ac_dprintf("Tapping: First tap(0->1).\n");
                     tapping_key.tap.count = 1;
@@ -218,28 +218,12 @@ bool process_tapping(keyrecord_t *keyp) {
                  */
                 // clang-format off
                 else if (
+                    !event.pressed && waiting_buffer_typed(event) &&
                     (
-                        !event.pressed && waiting_buffer_typed(event) &&
-                        TAP_GET_PERMISSIVE_HOLD
-                    )
-                    // Causes nested taps to not wait past TAPPING_TERM/RETRO_SHIFT
-                    // unnecessarily and fixes them for Layer Taps.
-                    || (TAP_GET_RETRO_TAPPING &&
-                        (
-                            // Rolled over the two keys.
-                            (tapping_key.tap.interrupted == true && (
-                                (TAP_IS_LT && TAP_GET_HOLD_ON_OTHER_KEY_PRESS) ||
-                                (TAP_IS_MT && TAP_GET_HOLD_ON_OTHER_KEY_PRESS)
-                                )
-                            )
-                            // Makes Retro Shift ignore the default behavior of
-                            // MTs and LTs on nested taps below TAPPING_TERM or RETRO_SHIFT
-                            || (
-                                TAP_IS_RETRO
-                                && (event.key.col != tapping_key.event.key.col || event.key.row != tapping_key.event.key.row)
-                                && !event.pressed && waiting_buffer_typed(event)
-                            )
-                        )
+                        TAP_GET_PERMISSIVE_HOLD ||
+                        // Causes nested taps to not wait past TAPPING_TERM/RETRO_SHIFT
+                        // unnecessarily and fixes them for Layer Taps.
+                        TAP_GET_RETRO_TAPPING(keyp)
                     )
                 ) {
                     // clang-format on
@@ -284,10 +268,16 @@ bool process_tapping(keyrecord_t *keyp) {
                     process_record(keyp);
                     return true;
                 } else {
-                    // set interrupted flag when other key preesed during tapping
+                    // set interrupted flag when other key pressed during tapping
                     if (event.pressed) {
                         tapping_key.tap.interrupted = true;
-                        if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS) {
+                        if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS
+#    if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
+                            // Auto Shift cannot evaluate this early
+                            // Retro Shift uses the hold action for all nested taps even without HOLD_ON_OTHER_KEY_PRESS, so this is fine to skip
+                            && !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp))
+#    endif
+                        ) {
                             ac_dprintf("Tapping: End. No tap. Interfered by pressed key\n");
                             process_record(&tapping_key);
                             tapping_key = (keyrecord_t){0};
@@ -332,6 +322,9 @@ bool process_tapping(keyrecord_t *keyp) {
                     return true;
                 } else {
                     ac_dprintf("Tapping: key event while last tap(>0).\n");
+#    if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
+                    retroshift_swap_times();
+#    endif
                     process_record(keyp);
                     return true;
                 }
@@ -388,7 +381,7 @@ bool process_tapping(keyrecord_t *keyp) {
     }
     // process "released" tapping key state
     else {
-        if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event)) {
+        if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event, keyp)) {
             if (IS_NOEVENT(event)) {
                 // early return for tick events
                 return true;
@@ -506,9 +499,16 @@ void waiting_buffer_scan_tap(void) {
         return;
     }
 
+#    if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
+    TAP_DEFINE_KEYCODE;
+#    endif
     for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) {
         keyrecord_t *candidate = &waiting_buffer[i];
-        if (IS_EVENT(candidate->event) && KEYEQ(candidate->event.key, tapping_key.event.key) && !candidate->event.pressed && WITHIN_TAPPING_TERM(candidate->event)) {
+        // clang-format off
+        if (IS_EVENT(candidate->event) && KEYEQ(candidate->event.key, tapping_key.event.key) && !candidate->event.pressed && (
+            WITHIN_TAPPING_TERM(waiting_buffer[i].event) || MAYBE_RETRO_SHIFTING(waiting_buffer[i].event, &tapping_key)
+        )) {
+            // clang-format on
             tapping_key.tap.count = 1;
             candidate->tap.count  = 1;
             process_record(&tapping_key);

+ 17 - 12
quantum/process_keycode/process_auto_shift.c

@@ -66,7 +66,7 @@ __attribute__((weak)) bool get_custom_auto_shifted_key(uint16_t keycode, keyreco
     return false;
 }
 
-/** \brief Called on physical press, returns whether is Auto Shift key */
+/** \brief Called on physical press, returns whether key is an Auto Shift key */
 __attribute__((weak)) bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
     switch (keycode) {
 #ifndef NO_AUTO_SHIFT_ALPHA
@@ -178,9 +178,8 @@ static bool autoshift_press(uint16_t keycode, uint16_t now, keyrecord_t *record)
     }
 
     // Store record to be sent to user functions if there's no release record then.
-    autoshift_lastrecord               = *record;
-    autoshift_lastrecord.event.pressed = false;
-    autoshift_lastrecord.event.time    = 0;
+    autoshift_lastrecord            = *record;
+    autoshift_lastrecord.event.time = 0;
     // clang-format off
 #if defined(AUTO_SHIFT_REPEAT) || defined(AUTO_SHIFT_REPEAT_PER_KEY)
     if (keycode == autoshift_lastkey &&
@@ -409,8 +408,12 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
             // If Retro Shift is disabled, possible custom actions shouldn't happen.
             // clang-format off
 #if defined(RETRO_SHIFT) && !defined(NO_ACTION_TAPPING)
-#    ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
-            const bool is_hold_on_interrupt = get_hold_on_other_key_press(keycode, record);
+#    ifdef HOLD_ON_OTHER_KEY_PRESS
+            const bool is_hold_on_interrupt = (IS_QK_MOD_TAP(keycode)
+#        ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
+                && get_hold_on_other_key_press(keycode, record)
+#        endif
+            );
 #    else
             const bool is_hold_on_interrupt = false;
 #    endif
@@ -450,8 +453,12 @@ bool process_auto_shift(uint16_t keycode, keyrecord_t *record) {
 #endif
         ) {
             // Fixes modifiers not being applied to rolls with AUTO_SHIFT_MODIFIERS set.
-#ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
-            if (autoshift_flags.in_progress && get_hold_on_other_key_press(keycode, record)) {
+#ifdef HOLD_ON_OTHER_KEY_PRESS
+            if (autoshift_flags.in_progress
+#    ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
+                && get_hold_on_other_key_press(keycode, record)
+#    endif
+            ) {
                 autoshift_end(KC_NO, now, false, &autoshift_lastrecord);
             }
 #endif
@@ -488,10 +495,8 @@ void retroshift_poll_time(keyevent_t *event) {
 }
 // Used to swap the times of Retro Shifted key and Auto Shift key that interrupted it.
 void retroshift_swap_times(void) {
-    if (last_retroshift_time != 0 && autoshift_flags.in_progress) {
-        uint16_t temp        = retroshift_time;
-        retroshift_time      = last_retroshift_time;
-        last_retroshift_time = temp;
+    if (autoshift_flags.in_progress) {
+        autoshift_time = last_retroshift_time;
     }
 }
 #endif

+ 1 - 0
quantum/process_keycode/process_auto_shift.h

@@ -56,4 +56,5 @@ uint16_t (get_autoshift_timeout)(uint16_t keycode, keyrecord_t *record);
 void     set_autoshift_timeout(uint16_t timeout);
 void     autoshift_matrix_scan(void);
 bool     get_custom_auto_shifted_key(uint16_t keycode, keyrecord_t *record);
+bool     get_auto_shifted_key(uint16_t keycode, keyrecord_t *record);
 // clang-format on

+ 22 - 0
tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/config.h

@@ -0,0 +1,22 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 AUTO_SHIFT_REPEAT
+#define AUTO_SHIFT_NO_AUTO_REPEAT

+ 20 - 0
tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 105 - 0
tests/auto_shift/auto_shift_repeat/auto_shift_no_auto_repeat/test_auto_shift.cpp

@@ -0,0 +1,105 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class AutoShiftNoAutoRepeat : public TestFixture {};
+
+TEST_F(AutoShiftNoAutoRepeat, no_auto_repeat) {
+    TestDriver driver;
+    InSequence s;
+    auto       repeat_key = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({repeat_key});
+
+    /* Press repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Idle for auto-repeat to (not) kick in. */
+    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);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(AutoShiftNoAutoRepeat, tap_regular_key_while_another_key_repeats) {
+    TestDriver driver;
+    InSequence s;
+    auto       repeat_key  = KeymapKey(0, 1, 0, KC_P);
+    auto       regular_key = KeymapKey(0, 2, 0, KC_A);
+
+    set_keymap({repeat_key, regular_key});
+
+    /* Press repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release repeat key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    repeat_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press repeat key. */
+    EXPECT_REPORT(driver, (KC_P));
+    repeat_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_REPORT(driver, (KC_P, KC_A));
+    EXPECT_REPORT(driver, (KC_P));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release repeat key. */
+    EXPECT_EMPTY_REPORT(driver);
+    repeat_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 21 - 0
tests/auto_shift/auto_shift_repeat/config.h

@@ -0,0 +1,21 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 AUTO_SHIFT_REPEAT

+ 20 - 0
tests/auto_shift/auto_shift_repeat/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 107 - 0
tests/auto_shift/auto_shift_repeat/test_auto_shift.cpp

@@ -0,0 +1,107 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+using testing::_;
+using testing::AnyNumber;
+using testing::InSequence;
+
+class AutoShiftRepeat : public TestFixture {};
+
+TEST_F(AutoShiftRepeat, tap_regular_key_cancelling_another_key_hold) {
+    TestDriver driver;
+    InSequence s;
+    auto       repeat_key  = KeymapKey(0, 1, 0, KC_P);
+    auto       regular_key = KeymapKey(0, 2, 0, KC_A);
+
+    set_keymap({repeat_key, regular_key});
+
+    /* Press repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(testing::AnyNumber());
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(testing::AnyNumber());
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(testing::AnyNumber());
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport())).Times(testing::AnyNumber());
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(AutoShiftRepeat, tap_regular_key_while_another_key_is_held) {
+    TestDriver driver;
+    InSequence s;
+    auto       repeat_key  = KeymapKey(0, 1, 0, KC_P);
+    auto       regular_key = KeymapKey(0, 2, 0, KC_A);
+
+    set_keymap({repeat_key, regular_key});
+
+    /* Press repeat key. */
+    EXPECT_NO_REPORT(driver);
+    repeat_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Idle for auto-repeat to kick in. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LSFT, KC_P));
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_REPORT(driver, (KC_P, KC_A));
+    EXPECT_REPORT(driver, (KC_P));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release repeat key. */
+    EXPECT_EMPTY_REPORT(driver);
+    repeat_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 1 - 1
tests/auto_shift/config.h

@@ -16,4 +16,4 @@
 
 #pragma once
 
-#include "test_common.h"
+#include "test_common.h"

+ 24 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/config.h

@@ -0,0 +1,24 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 RETRO_SHIFT 2 * TAPPING_TERM
+// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
+#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
+#define AUTO_SHIFT_MODIFIERS

+ 21 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/config.h

@@ -0,0 +1,21 @@
+/* Copyright 2021 Stefan Kerkmann
+ *
+ * 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 RETRO_SHIFT

+ 20 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 57 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/no_timeout/test_retro_shift.cpp

@@ -0,0 +1,57 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class RetroShiftDefaultTapHold : public TestFixture {};
+
+TEST_F(RetroShiftDefaultTapHold, hold_mod_tap_key_for_long) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_A));
+
+    set_keymap({mod_tap_hold_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    idle_for(4 * TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    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);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 20 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 485 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/default_mod_tap/test_retro_shift.cpp

@@ -0,0 +1,485 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class RetroShiftDefaultTapHold : public TestFixture {};
+
+TEST_F(RetroShiftDefaultTapHold, tap_mod_tap_key) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_A));
+
+    set_keymap({mod_tap_hold_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, hold_mod_tap_key_under_retro_shift) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_A));
+
+    set_keymap({mod_tap_hold_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    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);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, hold_mod_tap_key_over_retro_shift) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_A));
+
+    set_keymap({mod_tap_hold_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    idle_for(RETRO_SHIFT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_EMPTY_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    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);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftDefaultTapHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    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);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 26 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/config.h

@@ -0,0 +1,26 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 HOLD_ON_OTHER_KEY_PRESS
+
+#define RETRO_SHIFT 2 * TAPPING_TERM
+// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
+#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
+#define AUTO_SHIFT_MODIFIERS

+ 20 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 442 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/hold_on_other_key_press/test_retro_shift.cpp

@@ -0,0 +1,442 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class RetroShiftHoldOnOtherKeyPress : public TestFixture {};
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    EXPECT_EMPTY_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_EMPTY_REPORT(driver);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftHoldOnOtherKeyPress, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_EMPTY_REPORT(driver);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 26 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/config.h

@@ -0,0 +1,26 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 PERMISSIVE_HOLD
+
+#define RETRO_SHIFT 2 * TAPPING_TERM
+// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
+#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
+#define AUTO_SHIFT_MODIFIERS

+ 20 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 419 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold/test_retro_shift.cpp

@@ -0,0 +1,419 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class RetroShiftPermissiveHold : public TestFixture {};
+
+TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_EMPTY_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_REPORT(driver, (KC_A));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    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);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_REPORT(driver, (KC_P));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    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);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 27 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/config.h

@@ -0,0 +1,27 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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 HOLD_ON_OTHER_KEY_PRESS
+#define PERMISSIVE_HOLD
+
+#define RETRO_SHIFT 2 * TAPPING_TERM
+// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
+#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
+#define AUTO_SHIFT_MODIFIERS

+ 20 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/test.mk

@@ -0,0 +1,20 @@
+# Copyright 2022 Isaac Elenbaas
+#
+# 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
+# --------------------------------------------------------------------------------
+
+AUTO_SHIFT_ENABLE = yes

+ 442 - 0
tests/auto_shift/retro_shift/tap_hold_configurations/permissive_hold_hold_on_other_key_press/test_retro_shift.cpp

@@ -0,0 +1,442 @@
+/* Copyright 2022 Isaac Elenbaas
+ *
+ * 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_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AnyOf;
+using testing::InSequence;
+
+class RetroShiftPermissiveHoldHoldOnOtherKeyPress : public TestFixture {};
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL));
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    idle_for(TAPPING_TERM);
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    EXPECT_EMPTY_REPORT(driver);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    EXPECT_REPORT(driver, (KC_LCTL, KC_A));
+    EXPECT_REPORT(driver, (KC_LCTL));
+    EXPECT_EMPTY_REPORT(driver);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       regular_key      = KeymapKey(0, 1, 0, KC_A);
+
+    set_keymap({mod_tap_hold_key, regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press regular key. */
+    EXPECT_NO_REPORT(driver);
+    regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_EMPTY_REPORT(driver);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}
+
+TEST_F(RetroShiftPermissiveHoldHoldOnOtherKeyPress, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
+    TestDriver driver;
+    InSequence s;
+    auto       mod_tap_hold_key    = KeymapKey(0, 0, 0, CTL_T(KC_P));
+    auto       mod_tap_regular_key = KeymapKey(0, 1, 0, ALT_T(KC_A));
+
+    set_keymap({mod_tap_hold_key, mod_tap_regular_key});
+
+    /* Press mod-tap-hold key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_hold_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Press mod-tap-regular key. */
+    EXPECT_NO_REPORT(driver);
+    mod_tap_regular_key.press();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-hold key. */
+    EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
+    mod_tap_hold_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+
+    /* Release mod-tap-regular key. */
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
+    // clang-format off
+    EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
+                KeyboardReport(KC_LCTL, KC_LSFT),
+                KeyboardReport(KC_LSFT),
+                KeyboardReport(KC_LCTL))))
+        .Times(AnyNumber());
+    // clang-format on
+    EXPECT_EMPTY_REPORT(driver);
+    idle_for(AUTO_SHIFT_TIMEOUT);
+    mod_tap_regular_key.release();
+    run_one_scan_loop();
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 1 - 1
tests/auto_shift/test.mk

@@ -17,4 +17,4 @@
 # Keep this file, even if it is empty, as a marker that this folder contains tests
 # --------------------------------------------------------------------------------
 
-AUTO_SHIFT_ENABLE = yes
+AUTO_SHIFT_ENABLE = yes

+ 1 - 1
tests/basic/config.h

@@ -16,4 +16,4 @@
 
 #pragma once
 
-#include "test_common.h"
+#include "test_common.h"

+ 1 - 1
tests/basic/test.mk

@@ -15,4 +15,4 @@
 
 # --------------------------------------------------------------------------------
 # Keep this file, even if it is empty, as a marker that this folder contains tests
-# --------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------

+ 21 - 0
tests/caps_word/auto_shift/config.h

@@ -0,0 +1,21 @@
+// 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
+#define AUTO_SHIFT_TIMEOUT 150

+ 0 - 0
tests/caps_word/caps_word_autoshift/config.h → tests/caps_word/auto_shift/retro_shift/config.h


+ 0 - 0
tests/caps_word/caps_word_autoshift/test.mk → tests/caps_word/auto_shift/retro_shift/test.mk


+ 4 - 0
tests/caps_word/caps_word_autoshift/test_caps_word_autoshift.cpp → tests/caps_word/auto_shift/retro_shift/test_caps_word_retroshift.cpp

@@ -27,6 +27,10 @@
             KeyboardReport(KC_LSFT))))
 // clang-format on
 
+bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
+    return true;
+}
+
 using ::testing::_;
 using ::testing::AnyNumber;
 using ::testing::AnyOf;

+ 18 - 0
tests/caps_word/auto_shift/test.mk

@@ -0,0 +1,18 @@
+# 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
+AUTO_SHIFT_ENABLE = yes
+

+ 66 - 0
tests/caps_word/auto_shift/test_caps_word_autoshift.cpp

@@ -0,0 +1,66 @@
+// 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/>.
+
+#include "keyboard_report_util.hpp"
+#include "keycode.h"
+#include "test_common.hpp"
+#include "test_fixture.hpp"
+#include "test_keymap_key.hpp"
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AnyOf;
+using ::testing::InSequence;
+
+class CapsWord : public TestFixture {
+   public:
+    void SetUp() override {
+        caps_word_off();
+    }
+};
+
+// Tests that with Auto Shift, letter keys are shifted by Caps Word
+// regardless of whether they are released before AUTO_SHIFT_TIMEOUT.
+TEST_F(CapsWord, AutoShiftKeys) {
+    TestDriver driver;
+    KeymapKey  key_a(0, 0, 0, KC_A);
+    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: "A, A, space, a".
+        InSequence s;
+        EXPECT_REPORT(driver, (KC_LSFT, KC_A));
+        EXPECT_REPORT(driver, (KC_LSFT, KC_A));
+        EXPECT_REPORT(driver, (KC_SPC));
+        EXPECT_REPORT(driver, (KC_A));
+    }
+
+    // Turn on Caps Word and type "A (quick tap), A (long press), space, A".
+    caps_word_on();
+
+    tap_key(key_a);                         // Tap A quickly.
+    tap_key(key_a, AUTO_SHIFT_TIMEOUT + 1); // Long press A.
+    tap_key(key_spc);
+    tap_key(key_a);
+
+    testing::Mock::VerifyAndClearExpectations(&driver);
+}

+ 0 - 0
tests/caps_word/caps_word_unicodemap/config.h → tests/caps_word/unicodemap/config.h


+ 0 - 0
tests/caps_word/caps_word_unicodemap/test.mk → tests/caps_word/unicodemap/test.mk


+ 0 - 0
tests/caps_word/caps_word_unicodemap/test_caps_word_unicodemap.cpp → tests/caps_word/unicodemap/test_caps_word_unicodemap.cpp


+ 1 - 1
tests/tap_hold_configurations/default_mod_tap/test.mk

@@ -15,4 +15,4 @@
 
 # --------------------------------------------------------------------------------
 # Keep this file, even if it is empty, as a marker that this folder contains tests
-# --------------------------------------------------------------------------------
+# --------------------------------------------------------------------------------

+ 1 - 1
tests/tap_hold_configurations/permissive_hold/config.h

@@ -18,4 +18,4 @@
 
 #include "test_common.h"
 
-#define PERMISSIVE_HOLD
+#define PERMISSIVE_HOLD

+ 1 - 1
tests/test_common/build.mk

@@ -13,4 +13,4 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-CUSTOM_MATRIX=yes
+CUSTOM_MATRIX=yes

+ 1 - 1
tests/test_common/main.cpp

@@ -29,4 +29,4 @@ int main(int argc, char **argv) {
     init_logging();
 
     return RUN_ALL_TESTS();
-}
+}

+ 1 - 1
tests/test_common/test_common.h

@@ -1,4 +1,4 @@
 #pragma once
 
 #define MATRIX_ROWS 4
-#define MATRIX_COLS 10
+#define MATRIX_COLS 10