use super::{TurnKey, TurnKeyDecoder};
#[test]
fn sgr_wheel_up_and_down_map_to_scroll_keys() {
let mut d = TurnKeyDecoder::default();
assert_eq!(d.feed(b"\x1b[<64;10;5M"), vec![TurnKey::Up]);
assert_eq!(d.feed(b"\x1b[<65;10;5M"), vec![TurnKey::Down]);
}
#[test]
fn sgr_non_wheel_events_are_ignored_by_the_wheel_tier() {
let mut d = TurnKeyDecoder::default();
assert_eq!(d.feed(b"\x1b[<0;3;3m"), vec![]);
assert_eq!(d.feed(b"\x1b[<2;3;3M"), vec![]);
}
#[test]
fn left_click_press_toggles_expand() {
let mut d = TurnKeyDecoder::default();
assert_eq!(d.feed(b"\x1b[<0;3;3M"), vec![TurnKey::ToggleExpanded]);
assert_eq!(d.feed(b"\x1b[<0;3;3m"), vec![]);
}
#[test]
fn wheel_sequence_split_across_reads_still_decodes() {
let mut d = TurnKeyDecoder::default();
assert_eq!(d.feed(b"\x1b[<64;"), vec![]);
assert_eq!(d.feed(b"10;5M"), vec![TurnKey::Up]);
}
#[test]
fn arrow_and_space_still_decode_alongside_mouse_params() {
let mut d = TurnKeyDecoder::default();
assert_eq!(d.feed(b"\x1b[A"), vec![TurnKey::Up]);
assert_eq!(d.feed(b"\x1b[B"), vec![TurnKey::Down]);
assert_eq!(d.feed(b" "), vec![TurnKey::ToggleExpanded]);
}
#[test]
fn vi_mode_maps_jk_gg_g_and_halfpage() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"j"), vec![TurnKey::Down]);
assert_eq!(d.feed(b"k"), vec![TurnKey::Up]);
assert_eq!(d.feed(b"gg"), vec![TurnKey::Top]);
assert_eq!(d.feed(b"G"), vec![TurnKey::Bottom]);
assert_eq!(d.feed(b"\x04"), vec![TurnKey::HalfPageDown]); assert_eq!(d.feed(b"\x15"), vec![TurnKey::HalfPageUp]); }
#[test]
fn vi_single_g_waits_for_the_second_g() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"g"), vec![]); assert_eq!(d.feed(b"g"), vec![TurnKey::Top]);
}
#[test]
fn emacs_mode_maps_ctrl_np_not_vi_letters() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Emacs);
assert_eq!(d.feed(b"\x0e"), vec![TurnKey::Down]); assert_eq!(d.feed(b"\x10"), vec![TurnKey::Up]); assert_eq!(d.feed(b"j"), vec![]);
}
#[test]
fn base_arrows_space_and_enter_work_in_every_mode() {
for mode in [
newt_core::EditMode::Vi,
newt_core::EditMode::Emacs,
newt_core::EditMode::Nano,
] {
let label = format!("{mode:?}");
let mut d = TurnKeyDecoder::with_mode(mode);
assert_eq!(d.feed(b"\x1b[A"), vec![TurnKey::Up], "{label} up-arrow");
assert_eq!(d.feed(b"\x1b[B"), vec![TurnKey::Down], "{label} down-arrow");
assert_eq!(d.feed(b" "), vec![TurnKey::ToggleExpanded], "{label} space");
assert_eq!(
d.feed(b"\r"),
vec![TurnKey::ToggleExpanded],
"{label} enter"
);
}
}
#[test]
fn mode_nav_off_ignores_editor_keys_even_in_vi_mode() {
let mut d = TurnKeyDecoder {
mode: newt_core::EditMode::Vi,
mode_nav: false,
..Default::default()
};
assert_eq!(d.feed(b"j"), vec![], "opt-in off: vi `j` is not nav");
assert_eq!(d.feed(b"k"), vec![], "opt-in off: vi `k` is not nav");
assert_eq!(d.feed(b"gg"), vec![], "opt-in off: `gg` is not nav");
assert_eq!(d.feed(b"\x04"), vec![], "opt-in off: C-d does nothing");
assert_eq!(d.take_text(), b"jkgg");
assert_eq!(d.feed(b" "), vec![TurnKey::ToggleExpanded], "space");
assert_eq!(d.feed(b"\r"), vec![TurnKey::ToggleExpanded], "enter");
assert_eq!(d.feed(b"\x1b[A"), vec![TurnKey::Up], "up-arrow");
assert_eq!(d.feed(b"\x1b[B"), vec![TurnKey::Down], "down-arrow");
}
#[test]
fn mode_nav_on_activates_vi_scroll() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"j"), vec![TurnKey::Down]);
}
#[test]
fn pending_g_cleared_by_intervening_arrow() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"g"), vec![], "arms pending_g");
assert_eq!(
d.feed(b"\x1b[A"),
vec![TurnKey::Up],
"arrow clears the latch"
);
assert_eq!(d.feed(b"g"), vec![], "lone `g` again — pending, NOT Top");
assert_eq!(
d.feed(b"g"),
vec![TurnKey::Top],
"a real `gg` still fires Top"
);
}
#[test]
fn pending_g_cleared_by_intervening_mouse_wheel() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"g"), vec![]);
assert_eq!(d.feed(b"\x1b[<64;10;5M"), vec![TurnKey::Up]);
assert_eq!(d.feed(b"g"), vec![], "wheel cleared the latch — no misfire");
}
#[test]
fn x10_mouse_left_press_consumes_three_bytes() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"\x1b[M\x20\x21\x21"), vec![TurnKey::ToggleExpanded]);
assert_eq!(d.feed(b" "), vec![TurnKey::ToggleExpanded]);
}
#[test]
fn x10_mouse_coordinate_bytes_never_leak_as_nav() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"\x1b[M\x60jg"), vec![TurnKey::Up]);
assert_eq!(d.feed(b"g"), vec![]);
assert_eq!(d.feed(b"g"), vec![TurnKey::Top]);
}
#[test]
fn x10_mouse_bytes_split_across_reads_are_consumed() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert_eq!(d.feed(b"\x1b[M"), vec![], "header only");
assert_eq!(d.feed(b"\x20"), vec![TurnKey::ToggleExpanded], "Cb (btn 0)");
assert_eq!(d.feed(b"j"), vec![], "Cx consumed, not a Down");
assert_eq!(d.feed(b"j"), vec![], "Cy consumed — sequence complete");
assert_eq!(d.feed(b" "), vec![TurnKey::ToggleExpanded]);
}
#[test]
fn csi_params_are_length_capped_and_resync() {
let mut d = TurnKeyDecoder::default();
d.feed(b"\x1b[");
for _ in 0..1000 {
d.feed(b";");
}
assert!(
d.params.len() <= super::MAX_CSI_PARAM_BYTES,
"params bounded at the cap, was {}",
d.params.len()
);
assert_eq!(d.feed(b"\x1b[A"), vec![TurnKey::Up]);
assert!(d.take_text().is_empty(), "no CSI tail leaks into text");
}
#[test]
fn csi_overflow_tail_never_becomes_type_ahead_text() {
let mut d = TurnKeyDecoder::default();
let mut seq = b"\x1b[".to_vec();
seq.extend(std::iter::repeat_n(b'1', 40));
seq.push(b'~'); seq.extend_from_slice(b"hi"); assert!(d.feed(&seq).is_empty());
assert_eq!(d.take_text(), b"hi");
}
#[test]
fn broken_gg_latch_keeps_the_typed_g() {
let mut d = TurnKeyDecoder::with_mode(newt_core::EditMode::Vi);
assert!(d.feed(b"great").is_empty());
assert_eq!(d.take_text(), b"great");
assert_eq!(d.feed(b"g\x1b[A"), vec![TurnKey::Up]);
assert_eq!(d.take_text(), b"g");
assert_eq!(d.feed(b"gg"), vec![TurnKey::Top]);
assert!(d.take_text().is_empty());
}