use justerm_core::{CellFlags, Color, Engine};
#[test]
fn red_hi_stored_as_reference() {
let mut term = Engine::new(80, 24);
term.feed(b"\x1b[31mhi\x1b[0m");
let grid = term.grid();
assert_eq!(grid.cell(0, 0).c(), 'h');
assert_eq!(grid.cell(0, 1).c(), 'i');
assert_eq!(grid.cell(0, 0).fg(), Color::Indexed(1));
assert_eq!(grid.cell(0, 1).fg(), Color::Indexed(1));
assert_eq!(term.cursor().pen.fg, Color::Default);
}
#[test]
fn indexed_background() {
let mut term = Engine::new(10, 2);
term.feed(b"\x1b[42mX");
assert_eq!(term.grid().cell(0, 0).bg(), Color::Indexed(2));
}
#[test]
fn bright_colors_map_to_high_palette() {
let mut term = Engine::new(10, 2);
term.feed(b"\x1b[91mA\x1b[102mB");
assert_eq!(term.grid().cell(0, 0).fg(), Color::Indexed(9)); assert_eq!(term.grid().cell(0, 1).bg(), Color::Indexed(10)); }
#[test]
fn extended_color_semicolon_form() {
let mut term = Engine::new(20, 2);
term.feed(b"\x1b[38;2;10;20;30mA\x1b[48;5;200mB");
assert_eq!(term.grid().cell(0, 0).fg(), Color::Rgb(10, 20, 30));
assert_eq!(term.grid().cell(0, 1).bg(), Color::Indexed(200));
}
#[test]
fn extended_color_colon_form() {
let mut term = Engine::new(20, 2);
term.feed(b"\x1b[38:2:1:2:3mA\x1b[38:5:5mB\x1b[48:2::4:5:6mC");
assert_eq!(term.grid().cell(0, 0).fg(), Color::Rgb(1, 2, 3));
assert_eq!(term.grid().cell(0, 1).fg(), Color::Indexed(5));
assert_eq!(term.grid().cell(0, 2).bg(), Color::Rgb(4, 5, 6)); }
#[test]
fn default_color_reset() {
let mut term = Engine::new(10, 2);
term.feed(b"\x1b[31;41;1mA\x1b[39;49mB");
let b = *term.grid().cell(0, 1);
assert_eq!(b.fg(), Color::Default);
assert_eq!(b.bg(), Color::Default);
assert!(b.flags().contains(CellFlags::BOLD));
}
#[test]
fn attributes_set_and_full_reset() {
let mut term = Engine::new(40, 2);
term.feed(b"\x1b[1;2;3;4;5;7;8;9mX");
let x = *term.grid().cell(0, 0);
for f in [
CellFlags::BOLD,
CellFlags::DIM,
CellFlags::ITALIC,
CellFlags::UNDERLINE,
CellFlags::BLINK,
CellFlags::INVERSE,
CellFlags::HIDDEN,
CellFlags::STRIKETHROUGH,
] {
assert!(x.flags().contains(f), "missing {f:?}");
}
term.feed(b"\x1b[0mY");
assert_eq!(term.grid().cell(0, 1).flags(), CellFlags::empty());
}
#[test]
fn attribute_removal() {
let mut term = Engine::new(40, 2);
term.feed(b"\x1b[1;2;3;4m\x1b[22;23mX");
let x = *term.grid().cell(0, 0);
assert!(!x.flags().contains(CellFlags::BOLD));
assert!(!x.flags().contains(CellFlags::DIM));
assert!(!x.flags().contains(CellFlags::ITALIC));
assert!(x.flags().contains(CellFlags::UNDERLINE));
}
#[test]
fn absolute_positioning() {
let mut term = Engine::new(10, 5);
term.feed(b"\x1b[2;4Hx"); assert_eq!(term.grid().cell(1, 3).c(), 'x');
term.feed(b"\x1b[3Gy"); assert_eq!(term.grid().cell(1, 2).c(), 'y');
term.feed(b"\x1b[4dz"); assert_eq!(term.grid().cell(3, 3).c(), 'z');
}
#[test]
fn relative_movement_all_directions() {
let mut term = Engine::new(10, 5);
term.feed(b"\x1b[2B\x1b[4C*"); assert_eq!(term.grid().cell(2, 4).c(), '*');
term.feed(b"\x1b[1A\x1b[3D#");
assert_eq!(term.grid().cell(1, 2).c(), '#');
}
#[test]
fn movement_clamps_at_edges() {
let mut term = Engine::new(4, 3);
term.feed(b"\x1b[99A\x1b[99D"); assert_eq!((term.cursor().row, term.cursor().col), (0, 0));
term.feed(b"\x1b[99B\x1b[99C"); assert_eq!((term.cursor().row, term.cursor().col), (2, 3));
}
#[test]
fn cursor_move_clears_pending_wrap() {
let mut term = Engine::new(3, 3);
term.feed(b"abc"); assert!(term.cursor().pending_wrap);
term.feed(b"\x1b[1;1HX"); assert_eq!(term.grid().cell(0, 0).c(), 'X');
assert_eq!(term.cursor().row, 0); }
#[test]
fn erase_line_modes() {
let setup = || {
let mut t = Engine::new(5, 1);
t.feed(b"abcde\x1b[1;3H"); t
};
let mut t0 = setup();
t0.feed(b"\x1b[0K"); assert_eq!(t0.grid().cell(0, 1).c(), 'b'); assert_eq!(t0.grid().cell(0, 2).c(), ' '); assert_eq!(t0.grid().cell(0, 4).c(), ' ');
let mut t1 = setup();
t1.feed(b"\x1b[1K"); assert_eq!(t1.grid().cell(0, 0).c(), ' ');
assert_eq!(t1.grid().cell(0, 2).c(), ' ');
assert_eq!(t1.grid().cell(0, 3).c(), 'd');
let mut t2 = setup();
t2.feed(b"\x1b[2K"); assert_eq!(t2.grid().cell(0, 0).c(), ' ');
assert_eq!(t2.grid().cell(0, 4).c(), ' ');
}
#[test]
fn erase_display_modes() {
let setup = || {
let mut t = Engine::new(3, 3);
t.feed(b"aaa\r\nbbb\r\nccc\x1b[2;2H"); t
};
let mut t0 = setup();
t0.feed(b"\x1b[0J"); assert_eq!(t0.grid().cell(0, 0).c(), 'a'); assert_eq!(t0.grid().cell(1, 0).c(), 'b'); assert_eq!(t0.grid().cell(1, 1).c(), ' '); assert_eq!(t0.grid().cell(2, 0).c(), ' ');
let mut t1 = setup();
t1.feed(b"\x1b[1J"); assert_eq!(t1.grid().cell(0, 0).c(), ' '); assert_eq!(t1.grid().cell(1, 1).c(), ' '); assert_eq!(t1.grid().cell(1, 2).c(), 'b'); assert_eq!(t1.grid().cell(2, 0).c(), 'c');
let mut t2 = setup();
t2.feed(b"\x1b[2J"); for r in 0..3 {
for c in 0..3 {
assert_eq!(t2.grid().cell(r, c).c(), ' ');
}
}
}
#[test]
fn carriage_return_and_linefeed() {
let mut term = Engine::new(10, 3);
term.feed(b"ab\r\nc");
assert_eq!(term.grid().cell(0, 0).c(), 'a');
assert_eq!(term.grid().cell(1, 0).c(), 'c'); }
#[test]
fn linefeed_scrolls_at_bottom() {
let mut term = Engine::new(4, 2);
term.feed(b"top\r\nbot");
assert_eq!(term.grid().cell(0, 0).c(), 't');
term.feed(b"\r\nnew");
assert_eq!(term.grid().cell(0, 0).c(), 'b'); assert_eq!(term.grid().cell(1, 0).c(), 'n');
}
#[test]
fn backspace_moves_left() {
let mut term = Engine::new(10, 2);
term.feed(b"ab\x08c"); assert_eq!(term.grid().cell(0, 1).c(), 'c');
assert_eq!(term.cursor().col, 2);
}
#[test]
fn tab_to_next_stop() {
let mut term = Engine::new(20, 2);
term.feed(b"\tx");
assert_eq!(term.grid().cell(0, 8).c(), 'x');
}
#[test]
fn pending_wrap_is_deferred() {
let mut term = Engine::new(3, 3);
term.feed(b"abc");
assert_eq!(term.grid().cell(0, 2).c(), 'c');
assert_eq!(term.cursor().row, 0); assert_eq!(term.cursor().col, 2);
assert!(term.cursor().pending_wrap);
term.feed(b"d"); assert_eq!(term.cursor().row, 1);
assert_eq!(term.grid().cell(1, 0).c(), 'd');
assert!(!term.cursor().pending_wrap);
}
#[test]
fn overwriting_a_wide_char_clears_its_other_half() {
let mut a = Engine::new(10, 1);
a.feed("한".as_bytes()); a.feed(b"\x1b[1;1Hx"); assert_eq!(a.grid().cell(0, 0).c(), 'x');
assert!(
!a.grid()
.cell(0, 1)
.flags()
.contains(CellFlags::WIDE_CHAR_SPACER)
);
let mut b = Engine::new(10, 1);
b.feed("한".as_bytes());
b.feed(b"\x1b[1;2Hy"); assert_eq!(b.grid().cell(0, 1).c(), 'y');
assert!(!b.grid().cell(0, 0).flags().contains(CellFlags::WIDE_CHAR));
}
#[test]
fn erasing_part_of_a_wide_char_clears_its_other_half() {
let mut a = Engine::new(10, 1);
a.feed("a한b".as_bytes()); a.feed(b"\x1b[1;3H\x1b[0K"); assert!(!a.grid().cell(0, 1).flags().contains(CellFlags::WIDE_CHAR));
let mut b = Engine::new(10, 1);
b.feed("a한b".as_bytes());
b.feed(b"\x1b[1;2H\x1b[1K"); assert!(
!b.grid()
.cell(0, 2)
.flags()
.contains(CellFlags::WIDE_CHAR_SPACER)
);
}
#[test]
fn wide_char_marks_spacer() {
let mut term = Engine::new(10, 2);
term.feed("한".as_bytes());
let grid = term.grid();
assert_eq!(grid.cell(0, 0).c(), '한');
assert!(grid.cell(0, 0).flags().contains(CellFlags::WIDE_CHAR));
assert!(grid.cell(0, 1).is_wide_spacer());
assert_eq!(term.cursor().col, 2);
}
#[test]
fn wide_char_wraps_when_it_does_not_fit() {
let mut term = Engine::new(3, 2);
term.feed("ab한".as_bytes());
let grid = term.grid();
assert_eq!(grid.cell(0, 0).c(), 'a');
assert_eq!(grid.cell(0, 1).c(), 'b');
assert_eq!(grid.cell(0, 2).c(), ' '); assert_eq!(grid.cell(1, 0).c(), '한');
assert!(grid.cell(1, 0).flags().contains(CellFlags::WIDE_CHAR));
assert!(grid.cell(1, 1).is_wide_spacer());
}
#[test]
fn zero_width_is_dropped_for_now() {
let mut term = Engine::new(10, 2);
term.feed("e\u{0301}".as_bytes());
assert_eq!(term.grid().cell(0, 0).c(), 'e');
assert_eq!(term.cursor().col, 1);
assert_eq!(term.grid().cell(0, 1).c(), ' ');
}
#[test]
fn private_mode_sequences_are_ignored() {
let mut term = Engine::new(10, 2);
term.feed(b"A\x1b[?25lB\x1b[?2004hC"); assert_eq!(term.grid().cell(0, 0).c(), 'A');
assert_eq!(term.grid().cell(0, 1).c(), 'B');
assert_eq!(term.grid().cell(0, 2).c(), 'C');
}
#[test]
fn escape_split_across_feeds() {
let mut term = Engine::new(10, 2);
term.feed(b"\x1b[3"); term.feed(b"1mZ"); assert_eq!(term.grid().cell(0, 0).c(), 'Z');
assert_eq!(term.grid().cell(0, 0).fg(), Color::Indexed(1));
}