use vterm::Term;
fn row_text(t: &Term, row: usize) -> String {
let s = t.screen();
(0..s.cols()).map(|c| s.cell(row, c).ch).collect()
}
fn run(rows: usize, cols: usize, bytes: &[u8]) -> Term {
let mut t = Term::new(rows, cols);
t.feed(bytes);
t
}
#[test]
fn prints_text_and_advances_cursor() {
let t = run(4, 10, b"hi");
assert_eq!(t.screen().cell(0, 0).ch, 'h');
assert_eq!(t.screen().cell(0, 1).ch, 'i');
assert_eq!(t.screen().cursor, (0, 2));
}
#[test]
fn cup_and_erase_then_print() {
let t = run(24, 80, b"\x1b[2J\x1b[3;5HX");
assert_eq!(t.screen().cell(2, 4).ch, 'X');
assert_eq!(t.screen().cursor, (2, 5));
}
#[test]
fn hvp_f_is_same_as_cup() {
let t = run(10, 10, b"\x1b[2;3fZ");
assert_eq!(t.screen().cell(1, 2).ch, 'Z');
assert_eq!(t.screen().cursor, (1, 3));
}
#[test]
fn cup_defaults_and_clamps() {
assert_eq!(run(5, 5, b"\x1b[H").screen().cursor, (0, 0));
assert_eq!(run(5, 5, b"\x1b[99;99H").screen().cursor, (4, 4));
}
#[test]
fn carriage_return_to_col0() {
let t = run(4, 10, b"abc\rX");
assert_eq!(t.screen().cell(0, 0).ch, 'X');
assert_eq!(t.screen().cell(0, 1).ch, 'b');
assert_eq!(t.screen().cursor, (0, 1));
}
#[test]
fn line_feed_moves_down_same_column() {
let t = run(4, 10, b"ab\nc");
assert_eq!(t.screen().cell(0, 0).ch, 'a');
assert_eq!(t.screen().cell(1, 2).ch, 'c');
assert_eq!(t.screen().cursor, (1, 3));
}
#[test]
fn vt_and_ff_behave_like_lf() {
assert_eq!(run(4, 5, b"a\x0bb").screen().cursor, (1, 2));
assert_eq!(run(4, 5, b"a\x0cb").screen().cursor, (1, 2));
}
#[test]
fn backspace_moves_left_and_clamps() {
let t = run(4, 10, b"abc\x08\x08");
assert_eq!(t.screen().cursor, (0, 1));
assert_eq!(run(4, 10, b"\x08").screen().cursor, (0, 0)); }
#[test]
fn tab_advances_to_next_stop() {
let t = run(4, 40, b"\tX");
assert_eq!(t.screen().cell(0, 8).ch, 'X');
assert_eq!(t.screen().cursor, (0, 9));
let t2 = run(4, 40, b"abc\tY");
assert_eq!(t2.screen().cell(0, 8).ch, 'Y');
}
#[test]
fn cursor_up_down_left_right() {
let t = run(10, 10, b"\x1b[H\x1b[2B\x1b[3C\x1b[A\x1b[D");
assert_eq!(t.screen().cursor, (1, 2));
}
#[test]
fn cnl_and_cpl_go_to_col0() {
let t = run(10, 10, b"\x1b[5;5H\x1b[2EX");
assert_eq!(t.screen().cursor, (6, 1)); assert_eq!(t.screen().cell(6, 0).ch, 'X');
let t2 = run(10, 10, b"\x1b[5;5H\x1b[2F");
assert_eq!(t2.screen().cursor, (2, 0));
}
#[test]
fn cha_and_vpa_absolute() {
let t = run(10, 10, b"\x1b[5;5H\x1b[3G"); assert_eq!(t.screen().cursor, (4, 2));
let t2 = run(10, 10, b"\x1b[5;5H\x1b[2d"); assert_eq!(t2.screen().cursor, (1, 4));
}
#[test]
fn autowrap_at_right_edge() {
let t = run(4, 3, b"abcd");
assert_eq!(row_text(&t, 0), "abc");
assert_eq!(t.screen().cell(1, 0).ch, 'd');
assert_eq!(t.screen().cursor, (1, 1));
}
#[test]
fn pending_wrap_is_deferred() {
let t = run(4, 3, b"abc");
assert_eq!(t.screen().cursor, (0, 2));
assert_eq!(row_text(&t, 0), "abc");
}
#[test]
fn autowrap_off_overwrites_last_column() {
let t = run(4, 3, b"\x1b[?7labcd");
assert_eq!(row_text(&t, 0), "abd");
assert_eq!(t.screen().cursor, (0, 2));
}
#[test]
fn scroll_when_lf_at_bottom() {
let t = run(2, 3, b"A\r\nB\r\n");
assert_eq!(t.screen().cell(0, 0).ch, 'B');
assert_eq!(t.screen().cell(1, 0).ch, ' ');
assert_eq!(t.screen().cursor, (1, 0));
}
#[test]
fn scroll_loses_top_line() {
let t = run(3, 3, b"1\r\n2\r\n3\r\n4");
assert_eq!(t.screen().cell(0, 0).ch, '2');
assert_eq!(t.screen().cell(1, 0).ch, '3');
assert_eq!(t.screen().cell(2, 0).ch, '4');
}
#[test]
fn erase_display_variants() {
let t = run(2, 3, b"XXX\rXXX"); let mut t = t;
t.feed(b"\x1b[1;2H\x1b[0J"); assert_eq!(t.screen().cell(0, 0).ch, 'X');
assert_eq!(t.screen().cell(0, 1).ch, ' ');
assert_eq!(t.screen().cell(0, 2).ch, ' ');
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;3H\x1b[1J"); assert_eq!(row_text(&t, 0), " DE");
let mut t = run(2, 3, b"abcdef");
t.feed(b"\x1b[2J");
assert_eq!(row_text(&t, 0), " ");
assert_eq!(row_text(&t, 1), " ");
}
#[test]
fn erase_line_variants() {
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;3H\x1b[0K"); assert_eq!(row_text(&t, 0), "AB ");
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;3H\x1b[1K"); assert_eq!(row_text(&t, 0), " DE");
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;3H\x1b[2K"); assert_eq!(row_text(&t, 0), " ");
}
#[test]
fn sgr_sets_style_on_written_cells() {
let t = run(2, 10, b"\x1b[1;31mR\x1b[0mN");
let r = t.screen().cell(0, 0);
assert!(r.style.bold);
assert_eq!(r.style.fg, ansi::Color::Indexed(1));
let n = t.screen().cell(0, 1);
assert!(!n.style.bold);
assert_eq!(n.style.fg, ansi::Color::Default);
}
#[test]
fn bare_sgr_m_resets() {
let t = run(2, 10, b"\x1b[1m\x1b[mX"); assert!(!t.screen().cell(0, 0).style.bold);
}
#[test]
fn scroll_region_confines_scrolling() {
let mut t = run(4, 3, b"top");
t.feed(b"\x1b[2;3r"); t.feed(b"\x1b[3;1HA\r\nB\r\nC");
assert_eq!(row_text(&t, 0), "top"); assert_eq!(t.screen().cell(1, 0).ch, 'B');
assert_eq!(t.screen().cell(2, 0).ch, 'C');
}
#[test]
fn scroll_region_resets_cursor_to_origin() {
let mut t = run(6, 6, b"\x1b[3;4H");
t.feed(b"\x1b[2;5r");
assert_eq!(t.screen().cursor, (0, 0));
}
#[test]
fn insert_lines_shifts_down() {
let mut t = run(3, 3, b"A\r\nB\r\nC");
t.feed(b"\x1b[2;1H\x1b[L");
assert_eq!(t.screen().cell(0, 0).ch, 'A');
assert_eq!(t.screen().cell(1, 0).ch, ' ');
assert_eq!(t.screen().cell(2, 0).ch, 'B');
}
#[test]
fn delete_lines_shifts_up() {
let mut t = run(3, 3, b"A\r\nB\r\nC");
t.feed(b"\x1b[2;1H\x1b[M");
assert_eq!(t.screen().cell(0, 0).ch, 'A');
assert_eq!(t.screen().cell(1, 0).ch, 'C');
assert_eq!(t.screen().cell(2, 0).ch, ' ');
}
#[test]
fn insert_chars_shifts_right() {
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;2H\x1b[2@"); assert_eq!(row_text(&t, 0), "A BC");
}
#[test]
fn delete_chars_shifts_left() {
let mut t = run(1, 5, b"ABCDE");
t.feed(b"\x1b[1;2H\x1b[2P"); assert_eq!(row_text(&t, 0), "ADE ");
}
#[test]
fn csi_save_restore_cursor() {
let t = run(10, 10, b"\x1b[3;4H\x1b[s\x1b[8;8H\x1b[u");
assert_eq!(t.screen().cursor, (2, 3));
}
#[test]
fn esc_decsc_decrc() {
let t = run(10, 10, b"\x1b[3;4H\x1b7\x1b[8;8H\x1b8");
assert_eq!(t.screen().cursor, (2, 3));
}
#[test]
fn restore_also_restores_style() {
let t = run(2, 10, b"\x1b[1m\x1b7\x1b[0m\x1b8X");
assert!(t.screen().cell(0, 0).style.bold); }
#[test]
fn alt_screen_hides_and_restores_primary() {
let mut t = run(4, 5, b"PRIME");
assert_eq!(t.screen().cell(0, 0).ch, 'P');
t.feed(b"\x1b[?1049h"); assert_eq!(t.screen().cell(0, 0).ch, ' ');
t.feed(b"\x1b[HALT");
assert_eq!(t.screen().cell(0, 0).ch, 'A');
t.feed(b"\x1b[?1049l"); assert_eq!(t.screen().cell(0, 0).ch, 'P');
}
#[test]
fn alt_screen_47_and_1047_also_work() {
let mut t = run(4, 5, b"X");
t.feed(b"\x1b[?47h");
assert_eq!(t.screen().cell(0, 0).ch, ' ');
t.feed(b"\x1b[?47l");
assert_eq!(t.screen().cell(0, 0).ch, 'X');
}
#[test]
fn cursor_hide_show_is_accepted() {
let t = run(4, 5, b"\x1b[?25lAB\x1b[?25h");
assert_eq!(t.screen().cell(0, 0).ch, 'A');
assert_eq!(t.screen().cursor, (0, 2));
}
#[test]
fn resize_preserves_top_left_and_clamps_cursor() {
let mut t = run(4, 4, b"\x1b[4;4Hxy"); t.feed(b"\x1b[1;1HAB"); t.resize(2, 2);
assert_eq!(t.screen().rows(), 2);
assert_eq!(t.screen().cols(), 2);
assert_eq!(t.screen().cell(0, 0).ch, 'A');
assert_eq!(t.screen().cell(0, 1).ch, 'B');
let (r, c) = t.screen().cursor;
assert!(r < 2 && c < 2);
}
#[test]
fn chunk_split_yields_identical_screen() {
let script = b"\x1b[2J\x1b[3;5m\x1b[1;1mHello\r\n\x1b[1;31mWorld\x1b[0m\x1b[2;3r\nmore\ttext\x1b[?1049h\x1b[HALT\x1b[?1049l";
let oneshot = run(8, 20, script);
let mut split = Term::new(8, 20);
for &b in script.iter() {
split.feed(&[b]);
}
let (a, b) = (oneshot.screen(), split.screen());
assert_eq!(a.rows(), b.rows());
assert_eq!(a.cols(), b.cols());
assert_eq!(a.cursor, b.cursor);
for r in 0..a.rows() {
for c in 0..a.cols() {
assert_eq!(a.cell(r, c), b.cell(r, c), "mismatch at ({r},{c})");
}
}
let mut split3 = Term::new(8, 20);
for chunk in script.chunks(3) {
split3.feed(chunk);
}
let c = split3.screen();
assert_eq!(a.cursor, c.cursor);
for r in 0..a.rows() {
for col in 0..a.cols() {
assert_eq!(a.cell(r, col), c.cell(r, col));
}
}
}
#[test]
fn sync_hides_partial_frame_until_close() {
let mut t = run(2, 5, b"OLD");
assert_eq!(row_text(&t, 0), "OLD ");
assert!(!t.in_sync());
t.feed(b"\x1b[?2026h");
assert!(t.in_sync());
t.feed(b"\x1b[2J\x1b[HNE"); assert_eq!(row_text(&t, 0), "OLD ", "partial frame leaked during sync");
t.feed(b"W"); assert_eq!(row_text(&t, 0), "OLD ", "still mid-update");
t.feed(b"\x1b[?2026l");
assert!(!t.in_sync());
assert_eq!(row_text(&t, 0), "NEW ");
}
#[test]
fn sync_nested_open_keeps_first_snapshot() {
let mut t = run(1, 5, b"AAA");
t.feed(b"\x1b[?2026h");
t.feed(b"\x1b[2J\x1b[HB"); t.feed(b"\x1b[?2026h"); t.feed(b"BB");
assert_eq!(row_text(&t, 0), "AAA ");
t.feed(b"\x1b[?2026l");
assert_eq!(row_text(&t, 0), "BBB ");
}
#[test]
fn resize_during_sync_reveals_live_buffer() {
let mut t = run(2, 5, b"OLD");
t.feed(b"\x1b[?2026h");
t.feed(b"\x1b[2J\x1b[HNEW");
assert!(t.in_sync());
t.resize(3, 8);
assert!(!t.in_sync());
assert_eq!(t.screen().cols(), 8);
assert_eq!(row_text(&t, 0), "NEW ");
}
#[test]
fn ech_blanks_cells_without_shifting() {
let t = run(1, 5, b"ABCDE\x1b[1;2H\x1b[2X");
assert_eq!(row_text(&t, 0), "A DE");
assert_eq!(t.screen().cursor, (0, 1)); }
#[test]
fn ech_defaults_to_one_and_clamps() {
assert_eq!(row_text(&run(1, 4, b"WXYZ\x1b[1;1H\x1b[X"), 0), " XYZ");
assert_eq!(row_text(&run(1, 4, b"WXYZ\x1b[1;2H\x1b[99X"), 0), "W ");
}
#[test]
fn su_scrolls_the_screen_up() {
let t = run(3, 3, b"AAA\r\nBBB\r\nCCC\x1b[S");
assert_eq!(row_text(&t, 0), "BBB");
assert_eq!(row_text(&t, 1), "CCC");
assert_eq!(row_text(&t, 2), " ");
}
#[test]
fn sd_scrolls_the_screen_down() {
let t = run(3, 3, b"AAA\r\nBBB\r\nCCC\x1b[T");
assert_eq!(row_text(&t, 0), " ");
assert_eq!(row_text(&t, 1), "AAA");
assert_eq!(row_text(&t, 2), "BBB");
}