use justerm_core::{Engine, SelectionSpan, SelectionType, Side};
#[test]
fn char_select_one_line() {
let mut term = Engine::new(80, 24);
term.feed(b"hello");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 4, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("hello"));
}
#[test]
fn char_select_multi_line_trims_and_joins_with_newline() {
let mut term = Engine::new(80, 24);
term.feed(b"ab\r\ncd");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(1, 1, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("ab\ncd"));
}
#[test]
fn char_select_soft_wrap_joins_and_keeps_interior_spaces() {
let mut term = Engine::new(4, 4);
term.feed(b"ab cd");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(1, 1, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("ab cd"));
}
#[test]
fn char_select_across_scrollback_boundary() {
let mut term = Engine::new(4, 2);
term.feed(b"L0\r\nL1\r\nL2\r\nL3");
term.scroll_up(1);
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(1, 1, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("L1\nL2"));
}
#[test]
fn selection_side_excludes_and_degenerate_is_empty() {
let mut term = Engine::new(80, 24);
term.feed(b"hello");
term.selection_begin(0, 0, Side::Right, SelectionType::Char);
term.selection_extend(0, 4, Side::Left);
assert_eq!(term.selection_text().as_deref(), Some("ell"));
term.selection_begin(0, 2, Side::Right, SelectionType::Char);
term.selection_extend(0, 2, Side::Left);
assert_eq!(term.selection_text().as_deref(), Some(""));
}
#[test]
fn word_select_expands_to_word_boundaries() {
let mut term = Engine::new(80, 24);
term.feed(b"foo bar baz");
term.selection_begin(0, 5, Side::Left, SelectionType::Word);
assert_eq!(term.selection_text().as_deref(), Some("bar"));
}
#[test]
fn word_select_crosses_soft_wrap() {
let mut term = Engine::new(4, 4);
term.feed(b"abcdef");
term.selection_begin(1, 0, Side::Left, SelectionType::Word);
assert_eq!(term.selection_text().as_deref(), Some("abcdef"));
}
#[test]
fn line_select_takes_whole_lines() {
let mut term = Engine::new(80, 24);
term.feed(b"hello world");
term.selection_begin(0, 3, Side::Left, SelectionType::Line);
assert_eq!(term.selection_text().as_deref(), Some("hello world"));
}
#[test]
fn block_select_is_rectangular() {
let mut term = Engine::new(80, 24);
term.feed(b"abcd\r\nefgh");
term.selection_begin(0, 1, Side::Left, SelectionType::Block);
term.selection_extend(1, 2, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("bc\nfg"));
}
#[test]
fn selection_skips_wide_char_spacer() {
let mut term = Engine::new(80, 24);
term.feed("한국".as_bytes());
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 3, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("한국"));
}
#[test]
fn selection_range_reports_viewport_spans() {
let mut term = Engine::new(80, 24);
term.feed(b"hello");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 4, Side::Right);
assert_eq!(
term.selection_range(),
vec![SelectionSpan {
row: 0,
left: 0,
right: 4
}]
);
}
#[test]
fn selection_range_clips_to_viewport_when_scrolled() {
let mut term = Engine::new(4, 3);
term.feed(b"L0\r\nL1\r\nL2\r\nL3\r\nL4");
term.selection_begin(0, 0, Side::Left, SelectionType::Line); term.selection_extend(2, 0, Side::Left);
assert_eq!(
term.selection_range(),
vec![
SelectionSpan {
row: 0,
left: 0,
right: 3
},
SelectionSpan {
row: 1,
left: 0,
right: 3
},
SelectionSpan {
row: 2,
left: 0,
right: 3
},
]
);
term.scroll_up(1); assert_eq!(
term.selection_range(),
vec![
SelectionSpan {
row: 1,
left: 0,
right: 3
}, SelectionSpan {
row: 2,
left: 0,
right: 3
}, ]
);
}
#[test]
fn selection_follows_cap_eviction() {
let mut term = Engine::with_scrollback(4, 2, 2); term.feed(b"L0\r\nL1\r\nL2\r\nL3");
term.selection_begin(0, 0, Side::Left, SelectionType::Line);
assert_eq!(term.selection_text().as_deref(), Some("L2"));
term.feed(b"\r\nL4");
assert_eq!(term.selection_text().as_deref(), Some("L2"));
}
#[test]
fn selection_rotates_with_region_scroll() {
let mut term = Engine::new(4, 4);
term.feed(b"\x1b[2;4r"); term.feed(b"A\r\nB\r\nC\r\nD");
term.selection_begin(2, 0, Side::Left, SelectionType::Line); assert_eq!(term.selection_text().as_deref(), Some("C"));
term.feed(b"\r\n");
assert_eq!(term.selection_text().as_deref(), Some("C"));
}
#[test]
fn selection_rotates_with_reverse_index() {
let mut term = Engine::new(4, 4);
term.feed(b"\x1b[2;4r");
term.feed(b"A\r\nB\r\nC\r\nD"); term.feed(b"\x1b[2;1H");
term.selection_begin(2, 0, Side::Left, SelectionType::Line); assert_eq!(term.selection_text().as_deref(), Some("C"));
term.feed(b"\x1bM");
assert_eq!(term.selection_text().as_deref(), Some("C"));
}
#[test]
fn selection_survives_reflow_on_resize() {
let mut term = Engine::new(6, 4);
term.feed(b"abcdef");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 5, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("abcdef"));
term.resize(3, 4);
assert_eq!(term.selection_text().as_deref(), Some("abcdef"));
}
#[test]
fn selection_clears_on_alt_screen_switch() {
let mut term = Engine::new(80, 24);
term.feed(b"hello");
term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 4, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("hello"));
term.feed(b"\x1b[?1049h"); assert_eq!(term.selection_text(), None);
term.feed(b"\x1b[Hworld"); term.selection_begin(0, 0, Side::Left, SelectionType::Char);
term.selection_extend(0, 4, Side::Right);
assert_eq!(term.selection_text().as_deref(), Some("world"));
term.feed(b"\x1b[?1049l"); assert_eq!(term.selection_text(), None);
}