use justerm_core::Engine;
#[test]
fn reverse_wrap_backspaces_to_the_previous_soft_wrapped_row() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45h"); t.feed(b"abcd"); t.feed(b"\x08"); t.feed(b"\x08"); t.feed(b"X"); assert_eq!(t.grid().cell(0, 2).c(), 'X');
}
#[test]
fn backspace_clamps_at_column_zero_by_default() {
let mut t = Engine::new(3, 2);
t.feed(b"abcd"); t.feed(b"\x08\x08"); t.feed(b"X");
assert_eq!(
t.grid().cell(1, 0).c(),
'X',
"default: BS clamps at column 0"
);
}
#[test]
fn reverse_wrap_does_not_cross_a_hard_newline() {
let mut t = Engine::new(5, 2);
t.feed(b"\x1b[?45h");
t.feed(b"ab\r\nc"); t.feed(b"\x08\x08"); t.feed(b"X");
assert_eq!(t.grid().cell(1, 0).c(), 'X');
}
#[test]
fn cursor_left_reverse_wraps_to_the_previous_soft_wrapped_row() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45h");
t.feed(b"abcd"); t.feed(b"\x1b[2;1H"); t.feed(b"\x1b[D"); t.feed(b"Y");
assert_eq!(
t.grid().cell(0, 2).c(),
'Y',
"CUB walked onto the previous row"
);
assert_eq!(t.grid().cell(1, 0).c(), 'd', "and did not clamp in place");
}
#[test]
fn cursor_left_clamps_at_column_zero_by_default() {
let mut t = Engine::new(3, 2);
t.feed(b"abcd"); t.feed(b"\x1b[2;1H");
t.feed(b"\x1b[D");
t.feed(b"Y");
assert_eq!(
t.grid().cell(1, 0).c(),
'Y',
"default: CUB clamps at column 0"
);
}
#[test]
fn cursor_left_does_not_cross_a_hard_newline() {
let mut t = Engine::new(5, 2);
t.feed(b"\x1b[?45h");
t.feed(b"ab\x1b[2;1H"); t.feed(b"\x1b[D");
t.feed(b"X");
assert_eq!(
t.grid().cell(1, 0).c(),
'X',
"CUB clamped: the previous row is not a soft wrap"
);
}
#[test]
fn cursor_left_keeps_moving_after_it_walks() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45h");
t.feed(b"abcd");
t.feed(b"\x1b[2;1H"); t.feed(b"\x1b[2D");
assert_eq!((t.cursor().row, t.cursor().col), (0, 1));
}
#[test]
fn reverse_wrap_at_home_has_no_effect() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45h");
t.feed(b"\x1b[1;1H"); t.feed(b"\x08"); t.feed(b"Z");
assert_eq!(t.grid().cell(0, 0).c(), 'Z');
}
#[test]
fn decrqm_and_ris_for_reverse_wrap() {
let mut t = Engine::new(10, 2);
t.feed(b"\x1b[?45$p"); assert_eq!(t.drain_replies(), b"\x1b[?45;2$y");
t.feed(b"\x1b[?45h\x1b[?45$p"); assert_eq!(t.drain_replies(), b"\x1b[?45;1$y");
t.feed(b"\x1bc\x1b[?45$p"); assert_eq!(t.drain_replies(), b"\x1b[?45;2$y");
}
#[test]
fn reverse_wrap_backspace_spends_a_deferred_wrap_instead_of_moving() {
let mut parked = Engine::new(3, 2);
parked.feed(b"\x1b[?45h");
parked.feed(b"abcdef");
assert!(parked.cursor().pending_wrap, "precondition: parked");
parked.feed(b"\x08");
assert_eq!(
(parked.cursor().row, parked.cursor().col),
(1, 2),
"the backspace spent the park and did not move"
);
assert!(
!parked.cursor().pending_wrap,
"and the park is spent, not still owed"
);
let mut control = Engine::new(3, 2);
control.feed(b"\x1b[?45h");
control.feed(b"abcde");
assert!(!control.cursor().pending_wrap, "precondition: not parked");
control.feed(b"\x08");
assert_eq!(
(control.cursor().row, control.cursor().col),
(1, 1),
"an unparked backspace still moves"
);
}
#[test]
fn a_parked_backspace_without_reverse_wrap_still_moves() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45l");
t.feed(b"abcdef");
assert!(t.cursor().pending_wrap, "precondition: parked");
t.feed(b"\x08");
assert_eq!((t.cursor().row, t.cursor().col), (1, 1));
}
#[test]
fn a_parked_backspace_with_autowrap_off_moves() {
let mut fresh = Engine::new(3, 2);
fresh.feed(b"\x1b[?7l\x1b[?45h");
fresh.feed(b"abc");
assert!(
fresh.cursor().pending_wrap,
"precondition: parked under ?7l"
);
fresh.feed(b"\x08");
assert_eq!(
(fresh.cursor().row, fresh.cursor().col),
(0, 1),
"?7l: the park is spent by moving"
);
let mut carried = Engine::new(3, 2);
carried.feed(b"\x1b[?45h");
carried.feed(b"abc");
carried.feed(b"\x1b[?7l");
assert!(
carried.cursor().pending_wrap,
"precondition: park carried in"
);
carried.feed(b"\x08");
assert_eq!(
(carried.cursor().row, carried.cursor().col),
(0, 1),
"a park carried into ?7l is spent by moving too"
);
}
#[test]
fn cursor_left_spends_a_park() {
for (seq, want) in [(&b"\x1b[D"[..], 3usize), (&b"\x1b[3D"[..], 1)] {
let mut t = Engine::new(4, 2);
t.feed(b"\x1b[?45h");
t.feed(b"abcd"); assert!(t.cursor().pending_wrap, "precondition: parked");
t.feed(seq);
assert_eq!(
(t.cursor().row, t.cursor().col),
(0, want),
"CUB {seq:?} spent the park as its first unit"
);
assert!(
!t.cursor().pending_wrap,
"and the park is spent, not still owed"
);
}
for (seq, want) in [(&b"\x1b[D"[..], 2usize), (&b"\x1b[3D"[..], 0)] {
let mut t = Engine::new(4, 2);
t.feed(b"\x1b[?45h");
t.feed(b"abc"); assert!(!t.cursor().pending_wrap, "precondition: not parked");
t.feed(seq);
assert_eq!(
(t.cursor().row, t.cursor().col),
(0, want),
"an unparked CUB {seq:?} moves its full count"
);
}
}
#[test]
fn a_parked_cursor_left_with_autowrap_off_moves() {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?7l\x1b[?45h");
t.feed(b"abc");
assert!(t.cursor().pending_wrap, "precondition: parked under ?7l");
t.feed(b"\x1b[D");
assert_eq!(
(t.cursor().row, t.cursor().col),
(0, 1),
"?7l: the park is spent by moving"
);
}
#[test]
fn the_reverse_wrap_walk_needs_autowrap_too() {
for seq in [&b"\x08"[..], &b"\x1b[D"[..]] {
let mut t = Engine::new(3, 2);
t.feed(b"\x1b[?45h");
t.feed(b"abcd"); t.feed(b"\x1b[?7l"); t.feed(b"\x1b[2;1H"); t.feed(seq);
assert_eq!(
(t.cursor().row, t.cursor().col),
(1, 0),
"?7l: {seq:?} clamps at column 0 rather than walking"
);
}
}
#[test]
fn a_reverse_wrap_keeps_the_two_rows_one_logical_line() {
let text = |e: &Engine| {
e.viewport_logical_lines()
.iter()
.map(|l| l.text.clone())
.collect::<Vec<_>>()
};
let mut t = Engine::new(3, 3);
t.feed(b"\x1b[?45h");
t.feed(b"abcd");
t.feed(b"\x08\x08"); t.feed(b"X"); assert_eq!(text(&t), vec!["abXd".to_string()]);
let mut control = Engine::new(3, 3);
control.feed(b"abXd");
assert_eq!(
text(&control),
text(&t),
"identical cells, identical reading"
);
t.resize(6, 3);
control.resize(6, 3);
assert_eq!(text(&control), text(&t), "and identical through a reflow");
}
#[test]
fn a_spent_park_lands_on_a_wide_pairs_spacer() {
let mut t = Engine::new(4, 2);
t.feed(b"\x1b[?45h");
t.feed("ab\u{4e00}".as_bytes()); assert!(t.cursor().pending_wrap, "precondition: parked");
t.feed(b"\x08");
assert_eq!((t.cursor().row, t.cursor().col), (0, 3), "on the spacer");
t.feed(b"X");
let row: String = (0..4).map(|c| t.grid().cell(0, c).c()).collect();
assert_eq!(row, "ab X", "the print blanks the orphaned lead beside it");
}