Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Options:
--no-backspace Disable backspace/delete during test
--no-shuffle Don't shuffle word order
--no-limit Use entire word list (ignore --words limit)
--look-ahead <N> Show only the next N upcoming words (past and current word always visible)
--history Show history of past results
--last <N> Show only the last N history entries
--history-lang <LANG> Filter history by language
Expand Down
10 changes: 10 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ struct Opt {
#[arg(long)]
no_limit: bool,

/// Show only the next N upcoming words (past and current word always visible)
#[arg(long, value_name = "N")]
look_ahead: Option<usize>,

/// Show history of past results
#[arg(long)]
history: bool,
Expand Down Expand Up @@ -408,6 +412,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));

state.render_into(&mut terminal, &config)?;
Expand Down Expand Up @@ -458,6 +463,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));
}
_ => continue,
Expand Down Expand Up @@ -493,6 +499,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));
}
_ => continue,
Expand All @@ -518,6 +525,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));
}
Event::Key(KeyEvent {
Expand All @@ -535,6 +543,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));
}
Event::Key(KeyEvent {
Expand All @@ -558,6 +567,7 @@ fn main() -> io::Result<()> {
opt.sudden_death,
opt.case_insensitive,
opt.no_backspace,
opt.look_ahead,
));
}
Event::Key(KeyEvent {
Expand Down
87 changes: 71 additions & 16 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub struct Test {
pub sudden_death_enabled: bool,
pub case_insensitive: bool,
pub no_backspace: bool,
pub look_ahead: Option<usize>,
pending_presses: HashMap<KeyCode, (usize, usize)>,
}

Expand All @@ -67,6 +68,7 @@ impl Test {
sudden_death_enabled: bool,
case_insensitive: bool,
no_backspace: bool,
look_ahead: Option<usize>,
) -> Self {
Self {
words: words.into_iter().map(TestWord::from).collect(),
Expand All @@ -76,6 +78,7 @@ impl Test {
sudden_death_enabled,
case_insensitive,
no_backspace,
look_ahead,
pending_presses: HashMap::new(),
}
}
Expand Down Expand Up @@ -302,7 +305,7 @@ mod tests {

#[test]
fn ctrl_h_deletes_single_character() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "hel");
assert_eq!(test.words[0].progress, "hel");

Expand All @@ -321,6 +324,7 @@ mod tests {
false,
false,
false,
None,
);
// Complete word 1, move to word 2
type_string(&mut test, "ab");
Expand All @@ -343,6 +347,7 @@ mod tests {
false,
false,
false,
None,
);
type_string(&mut test, "ab");
test.handle_key(press(KeyCode::Char(' ')));
Expand All @@ -358,7 +363,7 @@ mod tests {

#[test]
fn ctrl_letter_is_ignored() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "he");
assert_eq!(test.words[0].progress, "he");

Expand All @@ -379,7 +384,7 @@ mod tests {

#[test]
fn ctrl_letter_no_event_added() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "he");
let events_before = test.words[0].events.len();

Expand All @@ -393,7 +398,7 @@ mod tests {

#[test]
fn shift_letter_still_types() {
let mut test = Test::new(vec!["Hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["Hello".to_string()], true, false, false, false, None);

let shift_h = KeyEvent {
code: KeyCode::Char('H'),
Expand All @@ -410,7 +415,7 @@ mod tests {

#[test]
fn ctrl_shift_letter_is_ignored() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "he");

let ctrl_shift_a = KeyEvent {
Expand All @@ -434,6 +439,7 @@ mod tests {
false,
false,
false,
None,
);
type_string(&mut test, "ab");
assert_eq!(test.current_word, 0);
Expand All @@ -448,7 +454,7 @@ mod tests {

#[test]
fn tab_does_not_affect_progress() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "he");

test.handle_key(press(KeyCode::Tab));
Expand All @@ -461,7 +467,7 @@ mod tests {

#[test]
fn ctrl_w_still_clears_entire_word() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, false, None);
type_string(&mut test, "hel");
assert_eq!(test.words[0].progress, "hel");

Expand All @@ -474,7 +480,7 @@ mod tests {

#[test]
fn case_insensitive_lowercase_matches_uppercase_word() {
let mut test = Test::new(vec!["Hello".to_string()], true, false, true, false);
let mut test = Test::new(vec!["Hello".to_string()], true, false, true, false, None);
type_string(&mut test, "hello");
assert_eq!(
test.words[0].progress, "hello",
Expand All @@ -490,7 +496,7 @@ mod tests {

#[test]
fn case_insensitive_uppercase_matches_lowercase_word() {
let mut test = Test::new(vec!["hello".to_string()], true, false, true, false);
let mut test = Test::new(vec!["hello".to_string()], true, false, true, false, None);
let shift_h = KeyEvent {
code: KeyCode::Char('H'),
modifiers: KeyModifiers::SHIFT,
Expand All @@ -507,7 +513,7 @@ mod tests {

#[test]
fn case_insensitive_correct_flag_on_events() {
let mut test = Test::new(vec!["World".to_string()], true, false, true, false);
let mut test = Test::new(vec!["World".to_string()], true, false, true, false, None);
type_string(&mut test, "world");
// All events should be marked correct (case-insensitive comparison)
assert!(
Expand All @@ -518,7 +524,7 @@ mod tests {

#[test]
fn case_sensitive_uppercase_mismatch() {
let mut test = Test::new(vec!["Hello".to_string()], true, false, false, false);
let mut test = Test::new(vec!["Hello".to_string()], true, false, false, false, None);
type_string(&mut test, "hello");
test.handle_key(press(KeyCode::Char(' ')));
// In case-sensitive mode, 'hello' != 'Hello', so the word event should be incorrect
Expand All @@ -532,7 +538,7 @@ mod tests {

#[test]
fn case_insensitive_auto_complete_last_word() {
let mut test = Test::new(vec!["ABC".to_string()], true, false, true, false);
let mut test = Test::new(vec!["ABC".to_string()], true, false, true, false, None);
type_string(&mut test, "abc");
assert!(
test.complete,
Expand All @@ -542,7 +548,7 @@ mod tests {

#[test]
fn no_backspace_blocks_backspace() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true, None);
type_string(&mut test, "hel");
assert_eq!(test.words[0].progress, "hel");

Expand All @@ -555,7 +561,7 @@ mod tests {

#[test]
fn no_backspace_blocks_ctrl_h() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true, None);
type_string(&mut test, "hel");

test.handle_key(press_ctrl(KeyCode::Char('h')));
Expand All @@ -567,7 +573,7 @@ mod tests {

#[test]
fn no_backspace_blocks_ctrl_w() {
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true);
let mut test = Test::new(vec!["hello".to_string()], true, false, false, true, None);
type_string(&mut test, "hel");

test.handle_key(press_ctrl(KeyCode::Char('w')));
Expand All @@ -579,12 +585,61 @@ mod tests {

#[test]
fn no_backspace_still_allows_typing() {
let mut test = Test::new(vec!["hi".to_string()], true, false, false, true);
let mut test = Test::new(vec!["hi".to_string()], true, false, false, true, None);
type_string(&mut test, "hi");
test.handle_key(press(KeyCode::Char(' ')));
assert!(
test.complete,
"Normal typing and word completion should still work with no_backspace"
);
}

#[test]
fn look_ahead_stores_value() {
let test = Test::new(
vec!["a".to_string(), "b".to_string(), "c".to_string()],
true,
false,
false,
false,
Some(2),
);
assert_eq!(test.look_ahead, Some(2));
}

#[test]
fn look_ahead_none_by_default() {
let test = Test::new(vec!["a".to_string()], true, false, false, false, None);
assert_eq!(test.look_ahead, None);
}

#[test]
fn look_ahead_does_not_affect_typing() {
let mut test = Test::new(
vec!["ab".to_string(), "cd".to_string(), "ef".to_string()],
true,
false,
false,
false,
Some(1),
);
type_string(&mut test, "ab");
test.handle_key(press(KeyCode::Char(' ')));
assert_eq!(
test.current_word, 1,
"Should advance to next word with look_ahead"
);
type_string(&mut test, "cd");
test.handle_key(press(KeyCode::Char(' ')));
assert_eq!(
test.current_word, 2,
"Should advance to third word with look_ahead"
);
type_string(&mut test, "ef");
test.handle_key(press(KeyCode::Char(' ')));
assert!(
test.complete,
"Should complete test with look_ahead enabled"
);
}
}
Loading