test(evil-keys): add integration and property-based tests
20 integration scenarios (5gg, SPC leader, escape progression, timeout, mode switch, which-key, stress tests) and 8 proptest invariants (roundtrip, never-panic, non-Press ignored, count >= 1, escape clears state). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,403 @@
|
|||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
|
||||||
|
use evil_keys::{DispatchResult, Dispatcher, KeyTrie};
|
||||||
|
|
||||||
|
fn press(code: KeyCode) -> KeyEvent {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_char(c: char) -> KeyEvent {
|
||||||
|
press(KeyCode::Char(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_spc() -> KeyEvent {
|
||||||
|
press_char(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_esc() -> KeyEvent {
|
||||||
|
press(KeyCode::Esc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vim_dispatcher() -> Dispatcher<&'static str> {
|
||||||
|
let mut trie = KeyTrie::new("normal");
|
||||||
|
trie.bind("j", "move_down").unwrap();
|
||||||
|
trie.bind("k", "move_up").unwrap();
|
||||||
|
trie.bind("G", "goto_last").unwrap();
|
||||||
|
trie.bind("Esc", "escape").unwrap();
|
||||||
|
trie.bind("0", "goto_start").unwrap();
|
||||||
|
trie.group("g", "+goto", |g| {
|
||||||
|
g.bind("g", "goto_first")?;
|
||||||
|
g.bind("t", "next_tab")?;
|
||||||
|
g.bind("T", "prev_tab")?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
trie.group("SPC", "+leader", |g| {
|
||||||
|
g.group("b", "+buffer", |b| {
|
||||||
|
b.bind_desc("l", "goto_library", "library")?;
|
||||||
|
b.bind_desc("w", "goto_wanted", "wanted")?;
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
g.bind_desc("h", "show_help", "help")?;
|
||||||
|
g.bind_desc("q", "quit", "quit")?;
|
||||||
|
g.bind_desc("n", "notifications", "notifications")?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let insert = KeyTrie::new("insert");
|
||||||
|
let mut d = Dispatcher::new();
|
||||||
|
d.add_mode("normal", trie).unwrap();
|
||||||
|
d.add_mode("insert", insert).unwrap();
|
||||||
|
d.set_active("normal").unwrap();
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vim_workflow_5gg() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('5')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('g')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_first",
|
||||||
|
count: 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leader_spc_b_l() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_spc()), DispatchResult::Pending);
|
||||||
|
assert_eq!(d.dispatch(press_char('b')), DispatchResult::Pending);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('l')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_library",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_progression() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('5')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_esc()), DispatchResult::Cancelled);
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(d.dispatch(press_esc()), DispatchResult::Cancelled);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('3')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(d.dispatch(press_esc()), DispatchResult::Cancelled);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout_resets_sequence() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
d.set_timeout(Duration::from_millis(50));
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
thread::sleep(Duration::from_millis(80));
|
||||||
|
assert!(d.check_timeout());
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('g')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_first",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn timeout_clears_count_too() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
d.set_timeout(Duration::from_millis(50));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('5')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
thread::sleep(Duration::from_millis(80));
|
||||||
|
assert!(d.check_timeout());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_switch_mid_sequence() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('3')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
|
||||||
|
d.set_active("insert").unwrap();
|
||||||
|
d.set_active("normal").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn which_key_after_leader() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_spc()), DispatchResult::Pending);
|
||||||
|
let entries = d.which_key_entries().unwrap();
|
||||||
|
assert!(entries.iter().any(|e| e.key == "b" && e.is_group));
|
||||||
|
assert!(entries
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.key == "h" && e.description == "help"));
|
||||||
|
assert!(entries
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.key == "q" && e.description == "quit"));
|
||||||
|
assert!(entries
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.key == "n" && e.description == "notifications"));
|
||||||
|
|
||||||
|
assert_eq!(d.dispatch(press_char('b')), DispatchResult::Pending);
|
||||||
|
let entries = d.which_key_entries().unwrap();
|
||||||
|
assert!(entries
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.key == "l" && e.description == "library"));
|
||||||
|
assert!(entries
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.key == "w" && e.description == "wanted"));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('l')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_library",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert!(d.which_key_entries().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stress_rapid_fire() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
for _ in 0..1000 {
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert!(d.pending_keys().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stress_alternating_sequences() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
for _ in 0..100 {
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('g')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_first",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('t')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "next_tab",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_123_j() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('1')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('2')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('3')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 123
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_as_binding_not_count() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('0')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "goto_start",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zero_after_digit_is_count() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('1')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('0')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('j')),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count: 10
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_overflow_saturates() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
for _ in 0..20 {
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_char('9')),
|
||||||
|
DispatchResult::CountAccumulated
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let result = d.dispatch(press_char('j'));
|
||||||
|
assert!(matches!(
|
||||||
|
result,
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "move_down",
|
||||||
|
count
|
||||||
|
} if count == usize::MAX
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pending_display_shows_keys() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(d.pending_display(), "");
|
||||||
|
|
||||||
|
d.dispatch(press_spc());
|
||||||
|
assert_eq!(d.pending_display(), "SPC");
|
||||||
|
|
||||||
|
d.dispatch(press_char('b'));
|
||||||
|
assert_eq!(d.pending_display(), "SPC b");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_with_nothing_pending_matches_binding() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(
|
||||||
|
d.dispatch(press_esc()),
|
||||||
|
DispatchResult::Matched {
|
||||||
|
action: "escape",
|
||||||
|
count: 1
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_mode() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert!(d.set_active("nonexistent").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dispatch_with_no_modes() {
|
||||||
|
let mut d: Dispatcher<&str> = Dispatcher::new();
|
||||||
|
assert_eq!(d.dispatch(press_char('j')), DispatchResult::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_release_ignored() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
let release = KeyEvent {
|
||||||
|
code: KeyCode::Char('j'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Release,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
};
|
||||||
|
assert_eq!(d.dispatch(release), DispatchResult::Ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_repeat_ignored() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
let repeat = KeyEvent {
|
||||||
|
code: KeyCode::Char('j'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Repeat,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
};
|
||||||
|
assert_eq!(d.dispatch(repeat), DispatchResult::Ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrong_key_mid_sequence_resets() {
|
||||||
|
let mut d = vim_dispatcher();
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
assert_eq!(d.dispatch(press_char('h')), DispatchResult::NotFound);
|
||||||
|
assert_eq!(d.dispatch(press_char('g')), DispatchResult::Pending);
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
|
||||||
|
use evil_keys::key::{parse_key, parse_sequence};
|
||||||
|
use evil_keys::{DispatchResult, Dispatcher, Key, KeyTrie};
|
||||||
|
use proptest::prelude::*;
|
||||||
|
|
||||||
|
fn valid_key_string() -> impl Strategy<Value = String> {
|
||||||
|
prop_oneof![
|
||||||
|
prop::char::range('a', 'z').prop_map(|c| c.to_string()),
|
||||||
|
prop::char::range('A', 'Z').prop_map(|c| c.to_string()),
|
||||||
|
prop::char::range('0', '9').prop_map(|c| c.to_string()),
|
||||||
|
Just("SPC".to_string()),
|
||||||
|
Just("Esc".to_string()),
|
||||||
|
Just("Tab".to_string()),
|
||||||
|
Just("Enter".to_string()),
|
||||||
|
Just("-".to_string()),
|
||||||
|
Just("[".to_string()),
|
||||||
|
Just("]".to_string()),
|
||||||
|
Just("/".to_string()),
|
||||||
|
Just("?".to_string()),
|
||||||
|
(1u8..=12).prop_map(|n| format!("F{n}")),
|
||||||
|
prop::char::range('a', 'z').prop_map(|c| format!("C-{c}")),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_dispatcher() -> Dispatcher<&'static str> {
|
||||||
|
let mut trie = KeyTrie::new("normal");
|
||||||
|
trie.bind("j", "down").unwrap();
|
||||||
|
trie.bind("k", "up").unwrap();
|
||||||
|
trie.bind("Esc", "escape").unwrap();
|
||||||
|
trie.group("g", "+goto", |g| {
|
||||||
|
g.bind("g", "goto_first")?;
|
||||||
|
g.bind("t", "next_tab")?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut d = Dispatcher::new();
|
||||||
|
d.add_mode("normal", trie).unwrap();
|
||||||
|
d.set_active("normal").unwrap();
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press(code: KeyCode) -> KeyEvent {
|
||||||
|
KeyEvent {
|
||||||
|
code,
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Press,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_char(c: char) -> KeyEvent {
|
||||||
|
press(KeyCode::Char(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn key_parse_display_roundtrip(s in valid_key_string()) {
|
||||||
|
let key = parse_key(&s).unwrap();
|
||||||
|
let displayed = key.to_string();
|
||||||
|
let reparsed = parse_key(&displayed).unwrap();
|
||||||
|
prop_assert_eq!(key, reparsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_parse_never_panics(s in "\\PC{0,20}") {
|
||||||
|
let _ = parse_key(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn key_display_never_panics(c in any::<char>().prop_filter("ascii", |c| c.is_ascii() && !c.is_control())) {
|
||||||
|
let key = Key { code: KeyCode::Char(c), modifiers: KeyModifiers::NONE };
|
||||||
|
let _ = key.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_press_always_ignored(code_char in prop::char::range('a', 'z')) {
|
||||||
|
let mut d = test_dispatcher();
|
||||||
|
let release = KeyEvent {
|
||||||
|
code: KeyCode::Char(code_char),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Release,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
};
|
||||||
|
prop_assert_eq!(d.dispatch(release), DispatchResult::Ignored);
|
||||||
|
|
||||||
|
let repeat = KeyEvent {
|
||||||
|
code: KeyCode::Char(code_char),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
kind: KeyEventKind::Repeat,
|
||||||
|
state: KeyEventState::NONE,
|
||||||
|
};
|
||||||
|
prop_assert_eq!(d.dispatch(repeat), DispatchResult::Ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_take_always_at_least_1(digits in prop::collection::vec(prop::char::range('1', '9'), 0..10)) {
|
||||||
|
let mut count = evil_keys::count::CountState::new();
|
||||||
|
for d in digits {
|
||||||
|
count.push_digit(d);
|
||||||
|
}
|
||||||
|
prop_assert!(count.take() >= 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn escape_always_clears_pending(keys_before in prop::collection::vec(prop::char::range('a', 'z'), 0..5)) {
|
||||||
|
let mut d = test_dispatcher();
|
||||||
|
for c in keys_before {
|
||||||
|
let _ = d.dispatch(press_char(c));
|
||||||
|
}
|
||||||
|
let _ = d.dispatch(press(KeyCode::Esc));
|
||||||
|
prop_assert!(d.pending_keys().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trie_bound_keys_always_found(keys in prop::collection::vec(valid_key_string(), 1..10)) {
|
||||||
|
let mut trie: KeyTrie<usize> = KeyTrie::new("test");
|
||||||
|
let mut successful = vec![];
|
||||||
|
|
||||||
|
for (i, key) in keys.iter().enumerate() {
|
||||||
|
if trie.bind(key, i).is_ok() {
|
||||||
|
successful.push(key.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in &successful {
|
||||||
|
let seq = parse_sequence(key).unwrap();
|
||||||
|
prop_assert!(matches!(
|
||||||
|
trie.search(&seq),
|
||||||
|
evil_keys::SearchResult::Found(_)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn search_empty_trie_never_panics(keys in prop::collection::vec(valid_key_string(), 0..5)) {
|
||||||
|
let trie: KeyTrie<()> = KeyTrie::new("test");
|
||||||
|
let parsed: Vec<Key> = keys
|
||||||
|
.iter()
|
||||||
|
.filter_map(|k| parse_key(k).ok())
|
||||||
|
.collect();
|
||||||
|
let _ = trie.search(&parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user