use justerm_core::{Engine, SelectionType, Side};
const W3: char = '\u{17D8}';
const W2: char = '\u{D55C}';
fn engine_with(text: &str) -> Engine {
let mut e = Engine::new(12, 1);
e.feed(text.as_bytes());
e
}
fn assert_pair_layout(label: &str, glyph: char) {
let e = engine_with(&format!("a{glyph}b"));
let cells = e.viewport_line(0);
assert_eq!(cells[0].c(), 'a', "{label}: cell 0");
assert_eq!(cells[1].c(), glyph, "{label}: the glyph sits at cell 1");
assert!(cells[1].is_wide(), "{label}: the lead carries WIDE_CHAR");
assert!(
cells[2].is_spacer(),
"{label}: cell 2 is the pair's spacer, not an ordinary blank"
);
assert_eq!(cells[3].c(), 'b', "{label}: 'b' follows the pair at cell 3");
assert!(
!cells[4].is_spacer() && cells[4].c() == ' ',
"{label}: cell 4 belongs to no glyph"
);
assert_eq!(
e.cursor().col,
4,
"{label}: the cursor advanced by 2 for the glyph, not by its raw width"
);
}
#[test]
fn a_wide_glyph_occupies_exactly_one_pair() {
assert_pair_layout("width-3", W3);
assert_pair_layout("width-2 control", W2);
}
#[test]
fn search_finds_a_run_containing_a_wide_glyph() {
for (label, glyph) in [("width-3", W3), ("width-2 control", W2)] {
let query = format!("a{glyph}b");
let e = engine_with(&query);
let hits = e.search(&query);
assert_eq!(hits.len(), 1, "{label}: exactly one match for {query:?}");
assert_eq!(hits[0].start_col, 0, "{label}: match starts at column 0");
assert_eq!(
hits[0].end_col, 3,
"{label}: match ends on 'b' at column 3, past the pair"
);
}
}
#[test]
fn word_selection_covers_the_whole_run_from_every_column() {
for (label, glyph) in [("width-3", W3), ("width-2 control", W2)] {
let whole = format!("a{glyph}b");
for col in 0..=3 {
let mut e = engine_with(&whole);
e.selection_begin(0, col, Side::Left, SelectionType::Word);
e.selection_extend(0, col, Side::Right);
assert_eq!(
e.selection_text().as_deref(),
Some(whole.as_str()),
"{label}: word selection at column {col}"
);
let spans = e.selection_range();
assert_eq!(spans.len(), 1, "{label}: one span at column {col}");
assert_eq!(
(spans[0].left, spans[0].right),
(0, 3),
"{label}: the span covers the whole run at column {col}"
);
}
}
}
#[test]
fn a_wide_glyph_fills_a_two_column_grid_as_a_pair() {
for (label, glyph) in [("width-3", W3), ("width-2 control", W2)] {
let mut e = Engine::new(1, 1); e.feed(glyph.to_string().as_bytes());
assert_eq!(e.grid().cols(), 2, "{label}: the grid is MIN_COLUMNS wide");
let cells = e.viewport_line(0);
assert_eq!(cells[0].c(), glyph, "{label}: the lead occupies column 0");
assert!(cells[0].is_wide(), "{label}: the lead carries WIDE_CHAR");
assert!(
cells[1].is_spacer(),
"{label}: column 1 is the pair's spacer — the row holds exactly one pair"
);
}
}
#[test]
fn the_clamp_holds_under_grapheme_cluster_mode() {
const ON: &str = "\x1b[?2027h";
const MARK: char = '\u{0301}';
for (label, glyph) in [("width-3", W3), ("width-2 control", W2)] {
let mut e = Engine::new(12, 1);
e.feed(format!("{ON}a{glyph}b").as_bytes());
let cells = e.viewport_line(0);
assert!(cells[1].is_wide(), "{label}: lead is a pair under 2027");
assert!(
cells[2].is_spacer(),
"{label}: spacer follows it under 2027"
);
assert_eq!(cells[3].c(), 'b', "{label}: 'b' at column 3 under 2027");
}
for (label, glyph) in [("width-3", W3), ("width-2 control", W2)] {
let mut e = Engine::new(12, 1);
e.feed(format!("{ON}{glyph}{MARK}b").as_bytes());
let cells = e.viewport_line(0);
assert!(cells[0].is_wide(), "{label}: the joined base stays a pair");
assert!(
cells[1].is_spacer(),
"{label}: its spacer survives the join"
);
assert_eq!(
cells[2].c(),
'b',
"{label}: the join consumed no extra column"
);
assert_eq!(
e.cursor().col,
3,
"{label}: the joined mark consumed no column of its own"
);
}
}