use justerm_core::Engine;
fn doc(e: &Engine) -> Vec<usize> {
e.command_lines().iter().map(|c| c.line).collect()
}
fn abs(e: &Engine) -> Vec<usize> {
e.command_marks().into_iter().map(|(_, l, _)| l).collect()
}
#[test]
fn evicting_a_continuation_row_moves_the_absolute_space_and_not_the_document() {
let mut e = Engine::with_scrollback(8, 4, 12);
e.feed(b"AAAAAAAAAAAA\r\n");
for _ in 0..10 {
e.feed(b"x\r\n");
}
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls\x1b]133;C\x07o\r\n\x1b]133;D;0\x07");
let rows_total = e.scrollback_len() + 4;
assert!(
e.accessible_text().lines().count() < rows_total,
"the fixture must actually contain a soft-wrapped line, or the divergence \
it is built to catch cannot occur"
);
assert_eq!(abs(&e), vec![12, 12, 12, 13], "absolute, as first answered");
assert_eq!(doc(&e), vec![11], "document, as first answered");
while e.marker_index().evicted_total == 0 {
e.feed(b"y\r\n");
}
assert_eq!(
e.marker_index().evicted_total,
1,
"exactly one row left the buffer"
);
assert_eq!(
abs(&e),
vec![11, 11, 11, 12],
"the absolute space moved by that one row, which is what `evicted_total` dates"
);
assert_eq!(
doc(&e),
vec![11],
"and the document space did not move at all: the evicted row was a \
continuation, so it was never a document line. A caller rebasing by the \
`evicted_total` delta computes 10 here and reveals the wrong command."
);
e.feed(b"y\r\n");
assert_eq!(e.marker_index().evicted_total, 2);
assert_eq!(abs(&e), vec![10, 10, 10, 11], "absolute, again by one");
assert_eq!(
doc(&e),
vec![10],
"document, this time by one as well — the case that makes the naive rebase \
look correct"
);
}
#[test]
fn the_line_indexes_accessible_text_while_the_primary_screen_is_active() {
let mut e = Engine::with_scrollback(8, 5, 20);
e.feed(b"AAAAAAAAAAAA\r\n");
e.feed(b"b\r\n");
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls\x1b]133;C\x07o\r\n\x1b]133;D;0\x07");
let cmd = &e.command_lines()[0];
let text = e.accessible_text();
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines, vec!["AAAAAAAAAAAA", "b", "$ lso"]);
assert_eq!(
e.command_marks()[0].1,
3,
"the command sits on absolute row 3 — the wrapped pair took rows 0 and 1"
);
assert_eq!(
cmd.line, 2,
"but on document line 2, because the pair collapsed into one. The two spaces \
are different integers here, which is what makes the next assertion mean \
something"
);
assert_eq!(
lines[cmd.line], "$ lso",
"the document line is an index into this document, not into the buffer"
);
}
#[test]
fn on_the_alt_screen_the_same_line_names_entirely_different_content() {
let mut e = Engine::with_scrollback(16, 6, 40);
e.feed(b"a\r\n");
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls\x1b]133;C\x07out\r\n\x1b]133;D;0\x07");
for i in 0..20 {
e.feed(format!("out {i}\r\n").as_bytes());
}
let on_primary = doc(&e);
assert_eq!(on_primary, vec![1]);
assert_eq!(
e.scrollback_len(),
17,
"the floor below has something to do"
);
assert_eq!(
e.accessible_text().lines().nth(1),
Some("$ lsout"),
"on the primary screen the held line names the command"
);
e.feed(b"\x1b[?1049h");
for i in 0..5 {
e.feed(format!("TUI row {i}\r\n").as_bytes());
}
assert_eq!(
doc(&e),
on_primary,
"unchanged: this query is primary-scoped and does not follow the active \
buffer, exactly like `command_marks` (#742) — which is what keeps a re-ask \
answering"
);
let text = e.accessible_text();
let lines: Vec<&str> = text.lines().collect();
assert_eq!(
lines.len(),
5,
"the document that line now indexes is the alt screen's, with the primary \
history correctly floored out of it because the AT must read what is on screen"
);
assert_eq!(
lines[on_primary[0]], "TUI row 1",
"and the SAME index resolves — in range, to unrelated content. This is the \
reachable shape and the dangerous one: a consumer reveals successfully, moves \
the reading cursor onto a TUI row, and announces the command's text beside it. \
No bounds check can catch this, which is why the contract has to be re-ask \
rather than a guard"
);
}
#[test]
fn on_a_short_alt_screen_the_line_falls_off_the_end_instead() {
let mut e = Engine::with_scrollback(16, 2, 8);
e.feed(b"a\r\nb\r\nc\r\n");
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls\x1b]133;C\x07out\r\n\x1b]133;D;0\x07");
let on_primary = doc(&e);
assert_eq!(on_primary, vec![3]);
assert_eq!(e.scrollback_len(), 3, "the floor below has something to do");
e.feed(b"\x1b[?1049h");
e.feed(b"TUI");
assert_eq!(doc(&e), on_primary, "still primary-scoped");
assert_eq!(e.accessible_text(), "\nTUI");
assert!(
e.accessible_text().lines().count() <= on_primary[0],
"line 3 of a two-line document names nothing at all"
);
}
#[test]
fn a_re_ask_answers_on_either_screen_and_absence_never_means_the_other_screen() {
let mut e = Engine::with_scrollback(8, 2, 8);
e.feed(b"p\r\nq\r\n");
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls");
assert!(
e.command_lines().is_empty(),
"absence #1 — the command has no output yet, so its text has no bound"
);
assert_eq!(
e.command_marks().len(),
2,
"and the marks are alive, so this absence is not disposal"
);
e.feed(b"\x1b]133;C\x07o\r\n\x1b]133;D;0\x07");
assert_eq!(e.command_lines().len(), 1, "output started: now navigable");
assert!(e.scrollback_len() > 0, "the floor has something to do");
e.feed(b"\x1b[?1049h");
assert_eq!(
e.command_lines().len(),
1,
"re-asking on the alt screen still answers — absence never means \
'you are on the other screen', which is the meaning no re-ask could undo"
);
e.feed(b"\x1b[?1049l");
for _ in 0..12 {
e.feed(b"z\r\n");
}
assert!(
e.command_lines().is_empty(),
"absence #2 — evicted off the top. Both absences are ones a re-ask resolves \
on its own, which is what D3.2 needs of them"
);
}
#[test]
fn flipping_a_wrap_bit_moves_the_document_line_and_nothing_else() {
let mut e = Engine::with_scrollback(8, 6, 40);
e.feed(b"ab\r\ncd\r\n");
e.feed(b"\x1b]133;A\x07$ \x1b]133;B\x07ls\x1b]133;C\x07o\r\n\x1b]133;D;0\x07");
let before = e.marker_index();
assert_eq!(doc(&e), vec![2], "two document lines above the command");
assert_eq!(abs(&e), vec![2, 2, 2, 3]);
e.feed(b"\x1b[1;1HAAAAAAAAAAAA");
let after = e.marker_index();
assert_eq!(
doc(&e),
vec![1],
"the document line moved by one, from the byte stream alone"
);
assert_eq!(
abs(&e),
vec![2, 2, 2, 3],
"while the absolute lines did not move — this axis is the document space's own"
);
assert_eq!(
after.evicted_total, before.evicted_total,
"and `evicted_total` cannot see it: nothing was evicted"
);
assert_eq!(
after.epoch, before.epoch,
"nor can the epoch: no reflow, rotate, alt switch or RIS happened. A holder \
watching both of ADR-0029 D5's scalars is still wrong here"
);
}