|
1 | 1 | /// Test cases for tap-dance like morses |
2 | 2 | pub mod common; |
3 | 3 |
|
| 4 | +use embassy_time::Duration; |
4 | 5 | use heapless::Vec; |
5 | 6 | use rmk::config::{BehaviorConfig, MorsesConfig, PositionalConfig}; |
6 | 7 | use rmk::keyboard::Keyboard; |
@@ -575,3 +576,77 @@ fn test_early_fire_then_fire_on_second_tap_with_no_double_tap_config() { |
575 | 576 | ] |
576 | 577 | }; |
577 | 578 | } |
| 579 | + |
| 580 | +/// Regression test: rapid repeat tapping a FlowTap+EarlyFire key must not jam the key. |
| 581 | +/// |
| 582 | +/// When flow_tap is enabled and a key has early-fire behaviour (tap == hold_after_tap, |
| 583 | +/// no double_tap), the first quick tap fires the action immediately and leaves the key |
| 584 | +/// in `EarlyFired` state in the held buffer. A second tap that arrives within |
| 585 | +/// `prior_idle_time` triggers `FlowTap`, which sends the key-press report and pushes a |
| 586 | +/// new `ProcessedButReleaseNotReportedYet` entry; without the fix it would push on |
| 587 | +/// top of the stale `EarlyFired` entry. On release `find_pos_mut` would then find the |
| 588 | +/// `EarlyFired` entry first and skip the release report, leaving the key held down (jam). |
| 589 | +/// |
| 590 | +/// The fix drops any existing held entry at this position before pushing in the |
| 591 | +/// `FlowTap` handler, so the buffer keeps its one-entry-per-position invariant and |
| 592 | +/// the release is always reported. |
| 593 | +fn create_flow_tap_early_fire_keyboard() -> Keyboard<'static> { |
| 594 | + // td!(0): tap=Backspace, hold=RShift, hold_after_tap=Backspace (no double_tap). |
| 595 | + // tap == hold_after_tap with no double_tap makes can_fire_early() return true for TAP. |
| 596 | + let keymap = [[[td!(0), k!(A)]], [[k!(Kp1), k!(Kp2)]]]; |
| 597 | + |
| 598 | + let behavior_config = BehaviorConfig { |
| 599 | + morse: MorsesConfig { |
| 600 | + enable_flow_tap: true, |
| 601 | + prior_idle_time: Duration::from_millis(120), |
| 602 | + default_profile: MorseProfile::new(Some(false), Some(MorseMode::HoldOnOtherPress), Some(250), Some(250)), |
| 603 | + morses: Vec::from_slice(&[Morse::new_from_vial( |
| 604 | + Action::Key(KeyCode::Hid(HidKeyCode::Backspace)), |
| 605 | + Action::Modifier(ModifierCombination::RSHIFT), |
| 606 | + Action::Key(KeyCode::Hid(HidKeyCode::Backspace)), |
| 607 | + Action::No, |
| 608 | + MorseProfile::const_default(), |
| 609 | + )]) |
| 610 | + .unwrap(), |
| 611 | + ..Default::default() |
| 612 | + }, |
| 613 | + ..Default::default() |
| 614 | + }; |
| 615 | + |
| 616 | + let behavior_config: &'static mut BehaviorConfig = Box::leak(Box::new(behavior_config)); |
| 617 | + let per_key_config: &'static PositionalConfig<1, 2> = Box::leak(Box::new(PositionalConfig::default())); |
| 618 | + Keyboard::new(wrap_keymap(keymap, per_key_config, behavior_config)) |
| 619 | +} |
| 620 | + |
| 621 | +#[test] |
| 622 | +fn test_flow_tap_after_early_fire_does_not_jam() { |
| 623 | + key_sequence_test! { |
| 624 | + keyboard: create_flow_tap_early_fire_keyboard(), |
| 625 | + sequence: [ |
| 626 | + // First tap arrives after >prior_idle_time of idle, so it takes the normal morse |
| 627 | + // path: a quick release early-fires Backspace and leaves an EarlyFired entry behind. |
| 628 | + [0, 0, true, 150], |
| 629 | + [0, 0, false, 30], |
| 630 | + // Second tap lands within prior_idle_time of the early-fired Backspace press, so it |
| 631 | + // takes the FlowTap path. FlowTap must replace the stale EarlyFired entry, not stack |
| 632 | + // a new one on top of it, or the release report below is dropped and Backspace jams. |
| 633 | + [0, 0, true, 50], |
| 634 | + [0, 0, false, 30], |
| 635 | + // Press an unrelated key well after the morse gap timeout to confirm nothing is stuck. |
| 636 | + [0, 1, true, 300], |
| 637 | + [0, 1, false, 10], |
| 638 | + ], |
| 639 | + expected_reports: [ |
| 640 | + // First tap: early-fired Backspace press, then release 10ms later (process_key_action_tap). |
| 641 | + [0, [kc_to_u8!(Backspace), 0, 0, 0, 0, 0]], |
| 642 | + [0, [0, 0, 0, 0, 0, 0]], |
| 643 | + // Second tap via FlowTap: Backspace press, then release on key-up. |
| 644 | + // The release report was missing before the fix (Backspace stayed held -> jam). |
| 645 | + [0, [kc_to_u8!(Backspace), 0, 0, 0, 0, 0]], |
| 646 | + [0, [0, 0, 0, 0, 0, 0]], |
| 647 | + // Unrelated key A, cleanly pressed and released. |
| 648 | + [0, [kc_to_u8!(A), 0, 0, 0, 0, 0]], |
| 649 | + [0, [0, 0, 0, 0, 0, 0]], |
| 650 | + ] |
| 651 | + }; |
| 652 | +} |
0 commit comments