use abstracttui::base::{Point, Rgba, Size};
use abstracttui::testing::{xterm_256, CellContent, VtScreen};
fn screen(w: i32, h: i32) -> VtScreen {
VtScreen::new(Size::new(w, h))
}
fn ch(s: &VtScreen, x: i32, y: i32) -> char {
s.cell(x, y).unwrap().ch()
}
#[test]
fn plain_text_and_crlf() {
let mut s = screen(10, 3);
s.feed(b"hello\r\nwd");
assert_eq!(s.to_text(), "hello\nwd\n\n");
assert_eq!(s.cursor(), Point::new(2, 1));
assert_eq!(s.unknown_seq_count(), 0);
}
#[test]
fn lf_alone_moves_down_keeping_column() {
let mut s = screen(10, 3);
s.feed(b"ab\ncd");
assert_eq!(ch(&s, 2, 1), 'c');
assert_eq!(ch(&s, 0, 1), ' ');
}
#[test]
fn backspace_and_tab() {
let mut s = screen(20, 2);
s.feed(b"ab\x08X");
assert_eq!(s.to_text(), "aX\n\n");
let mut s = screen(20, 2);
s.feed(b"a\tb");
assert_eq!(ch(&s, 8, 0), 'b');
let mut s = screen(10, 2);
s.feed(b"\t\t\tx");
assert_eq!(ch(&s, 9, 0), 'x');
}
#[test]
fn deferred_autowrap_matches_xterm() {
let mut s = screen(5, 3);
s.feed(b"abcde");
assert_eq!(s.cursor(), Point::new(4, 0));
s.feed(b"f");
assert_eq!(s.to_text(), "abcde\nf\n\n");
assert_eq!(s.cursor(), Point::new(1, 1));
}
#[test]
fn cr_cancels_pending_wrap() {
let mut s = screen(5, 2);
s.feed(b"abcde\rX");
assert_eq!(s.to_text(), "Xbcde\n\n");
}
#[test]
fn cursor_motion_cancels_pending_wrap() {
let mut s = screen(5, 2);
s.feed(b"abcde\x1b[1;1HX");
assert_eq!(s.to_text(), "Xbcde\n\n");
}
#[test]
fn autowrap_off_pins_last_column() {
let mut s = screen(5, 2);
s.feed(b"\x1b[?7labcdefg");
assert_eq!(s.to_text(), "abcdg\n\n");
assert_eq!(s.cursor(), Point::new(4, 0));
}
#[test]
fn wrap_at_bottom_scrolls() {
let mut s = screen(3, 2);
s.feed(b"abcdef");
assert_eq!(s.to_text(), "abc\ndef\n");
s.feed(b"g");
assert_eq!(s.to_text(), "def\ng\n");
}
#[test]
fn wide_glyph_occupies_leader_and_continuation() {
let mut s = screen(6, 1);
s.feed("日本".as_bytes());
assert_eq!(ch(&s, 0, 0), '日');
assert!(s.cell(1, 0).unwrap().is_continuation());
assert_eq!(ch(&s, 2, 0), '本');
assert!(s.cell(3, 0).unwrap().is_continuation());
assert_eq!(s.cursor(), Point::new(4, 0));
assert_eq!(s.to_text(), "日本\n");
}
#[test]
fn overwriting_continuation_blanks_leader() {
let mut s = screen(6, 1);
s.feed("世".as_bytes());
s.feed(b"\x1b[1;2Hx"); assert_eq!(s.cell(0, 0).unwrap().content, CellContent::Blank);
assert_eq!(ch(&s, 1, 0), 'x');
}
#[test]
fn overwriting_leader_blanks_continuation() {
let mut s = screen(6, 1);
s.feed("世".as_bytes());
s.feed(b"\x1b[1;1Hx");
assert_eq!(ch(&s, 0, 0), 'x');
assert_eq!(s.cell(1, 0).unwrap().content, CellContent::Blank);
}
#[test]
fn wide_over_wide_repairs_both_pairs() {
let mut s = screen(6, 1);
s.feed("你好".as_bytes()); s.feed("\x1b[1;2H中".as_bytes()); assert_eq!(s.cell(0, 0).unwrap().content, CellContent::Blank);
assert_eq!(ch(&s, 1, 0), '中');
assert!(s.cell(2, 0).unwrap().is_continuation());
assert_eq!(s.cell(3, 0).unwrap().content, CellContent::Blank);
}
#[test]
fn wide_glyph_at_last_column_wraps_and_blanks_orphan() {
let mut s = screen(5, 2);
s.feed(b"abcd");
s.feed("界".as_bytes()); assert_eq!(s.cell(4, 0).unwrap().content, CellContent::Blank);
assert_eq!(ch(&s, 0, 1), '界');
assert!(s.cell(1, 1).unwrap().is_continuation());
}
#[test]
fn wide_glyph_at_last_column_without_autowrap_is_dropped() {
let mut s = screen(5, 1);
s.feed(b"\x1b[?7labcd");
s.feed("界".as_bytes());
assert_eq!(ch(&s, 4, 0), ' ');
assert_eq!(s.to_text(), "abcd\n");
}
#[test]
fn combining_mark_attaches_to_previous_glyph() {
let mut s = screen(5, 1);
s.feed("e\u{301}".as_bytes());
assert_eq!(s.cell(0, 0).unwrap().display(), "e\u{301}");
assert_eq!(s.cursor(), Point::new(1, 0));
}
#[test]
fn vs16_widens_narrow_base_to_emoji_pair() {
let mut s = screen(6, 1);
s.feed("\u{2601}\u{fe0f}x".as_bytes());
assert!(s.cell(0, 0).unwrap().is_wide_leader());
assert!(s.cell(1, 0).unwrap().is_continuation());
assert_eq!(ch(&s, 2, 0), 'x');
}
#[test]
fn zwj_family_joins_into_one_wide_cell() {
let family = "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F466}";
let mut s = screen(8, 1);
s.feed(family.as_bytes());
s.feed(b"x");
assert_eq!(s.cell(0, 0).unwrap().display(), family);
assert!(s.cell(0, 0).unwrap().is_wide_leader());
assert!(s.cell(1, 0).unwrap().is_continuation());
assert_eq!(
ch(&s, 2, 0),
'x',
"cursor advanced exactly 2 for the whole family"
);
}
#[test]
fn skin_tone_modifier_fuses_when_adjacent() {
let mut s = screen(8, 1);
s.feed("\u{1F44D}\u{1F3FD}x".as_bytes()); assert_eq!(s.cell(0, 0).unwrap().display(), "\u{1F44D}\u{1F3FD}");
assert!(s.cell(1, 0).unwrap().is_continuation());
assert_eq!(ch(&s, 2, 0), 'x');
let mut s = screen(8, 1);
s.feed("\u{1F44D}".as_bytes());
s.feed(b"\x1b[1;5H");
s.feed("\u{1F3FD}".as_bytes());
assert!(s.cell(4, 0).unwrap().is_wide_leader());
}
#[test]
fn zwj_broken_by_escape_does_not_join() {
let mut s = screen(10, 1);
s.feed("\u{1F468}\u{200D}".as_bytes());
s.feed(b"\x1b[1;7H");
s.feed("\u{1F469}".as_bytes());
assert!(s.cell(0, 0).unwrap().is_wide_leader());
assert!(s.cell(6, 0).unwrap().is_wide_leader());
assert_eq!(s.cell(6, 0).unwrap().display(), "\u{1F469}");
}
#[test]
fn el_variants() {
let mut s = screen(6, 1);
s.feed(b"abcdef\x1b[1;4H"); s.feed(b"\x1b[K"); assert_eq!(s.to_text(), "abc\n");
let mut s = screen(6, 1);
s.feed(b"abcdef\x1b[1;4H\x1b[1K"); assert_eq!(s.to_text(), " ef\n");
let mut s = screen(6, 1);
s.feed(b"abcdef\x1b[2K");
assert_eq!(s.to_text(), "\n");
}
#[test]
fn ed_variants() {
let make = || {
let mut s = screen(3, 3);
s.feed(b"abc\r\ndef\r\nghi\x1b[2;2H"); s
};
let mut s = make();
s.feed(b"\x1b[J"); assert_eq!(s.to_text(), "abc\nd\n\n");
let mut s = make();
s.feed(b"\x1b[1J"); assert_eq!(s.to_text(), "\n f\nghi\n");
let mut s = make();
s.feed(b"\x1b[2J"); assert_eq!(s.to_text(), "\n\n\n");
assert_eq!(s.cursor(), Point::new(1, 1));
}
#[test]
fn erase_uses_bce_background() {
let mut s = screen(4, 1);
s.feed(b"\x1b[48;2;9;8;7mab\x1b[1;1H\x1b[K");
let cell = s.cell(0, 0).unwrap();
assert_eq!(cell.content, CellContent::Blank);
assert_eq!(cell.paint.bg, Some(Rgba::rgb(9, 8, 7)));
assert_eq!(cell.paint.fg, None);
assert!(cell.paint.attrs.is_empty());
}
#[test]
fn el_through_wide_pair_repairs_boundary() {
let mut s = screen(6, 1);
s.feed("ab你cd".as_bytes()); s.feed(b"\x1b[1;4H\x1b[K"); assert_eq!(s.cell(2, 0).unwrap().content, CellContent::Blank);
assert_eq!(s.to_text(), "ab\n");
}
#[test]
fn ech_erases_without_moving() {
let mut s = screen(6, 1);
s.feed(b"abcdef\x1b[1;2H\x1b[3X");
assert_eq!(s.to_text(), "a ef\n");
assert_eq!(s.cursor(), Point::new(1, 0));
}
#[test]
fn cup_is_one_based_and_clamped() {
let mut s = screen(10, 5);
s.feed(b"\x1b[3;4H");
assert_eq!(s.cursor(), Point::new(3, 2));
s.feed(b"\x1b[999;999H");
assert_eq!(s.cursor(), Point::new(9, 4));
s.feed(b"\x1b[H");
assert_eq!(s.cursor(), Point::ZERO);
s.feed(b"\x1b[0;0H"); assert_eq!(s.cursor(), Point::ZERO);
}
#[test]
fn relative_motion_clamps_at_edges() {
let mut s = screen(4, 3);
s.feed(b"\x1b[99A\x1b[99D");
assert_eq!(s.cursor(), Point::ZERO);
s.feed(b"\x1b[99B\x1b[99C");
assert_eq!(s.cursor(), Point::new(3, 2));
s.feed(b"\x1b[A\x1b[D");
assert_eq!(s.cursor(), Point::new(2, 1));
s.feed(b"\x1b[0A");
assert_eq!(s.cursor(), Point::new(2, 0));
}
#[test]
fn cha_vpa_cnl_cpl() {
let mut s = screen(8, 4);
s.feed(b"\x1b[2;2H\x1b[5G");
assert_eq!(s.cursor(), Point::new(4, 1));
s.feed(b"\x1b[3d");
assert_eq!(s.cursor(), Point::new(4, 2));
s.feed(b"\x1b[E");
assert_eq!(s.cursor(), Point::new(0, 3));
s.feed(b"\x1b[2F");
assert_eq!(s.cursor(), Point::new(0, 1));
}
#[test]
fn save_restore_cursor_and_paint() {
let mut s = screen(10, 3);
s.feed(b"\x1b[31m\x1b[2;5H\x1b7\x1b[0m\x1b[1;1Hx\x1b8y");
assert_eq!(ch(&s, 0, 0), 'x');
assert_eq!(ch(&s, 4, 1), 'y');
assert_eq!(s.cell(4, 1).unwrap().paint.fg, Some(xterm_256(1)));
assert_eq!(s.cell(0, 0).unwrap().paint.fg, None);
}
#[test]
fn linefeed_at_bottom_scrolls_up() {
let mut s = screen(3, 2);
s.feed(b"ab\r\ncd\r\nef");
assert_eq!(s.to_text(), "cd\nef\n");
assert_eq!(s.cursor(), Point::new(2, 1));
}
#[test]
fn reverse_index_at_top_scrolls_down() {
let mut s = screen(3, 2);
s.feed(b"ab\x1b[1;1H\x1bMx");
assert_eq!(s.to_text(), "x\nab\n");
}
#[test]
fn explicit_scroll_regions_su_sd() {
let mut s = screen(3, 3);
s.feed(b"a\r\nb\r\nc\x1b[2S");
assert_eq!(s.to_text(), "c\n\n\n");
let mut s = screen(3, 3);
s.feed(b"a\r\nb\r\nc\x1b[1T");
assert_eq!(s.to_text(), "\na\nb\n");
}
#[test]
fn scroll_fill_uses_bce() {
let mut s = screen(2, 2);
s.feed(b"\x1b[48;5;196mab\r\ncd\r\n"); let fill = s.cell(0, 1).unwrap();
assert_eq!(fill.content, CellContent::Blank);
assert_eq!(fill.paint.bg, Some(xterm_256(196)));
}