use abstracttui::base::{Rgba, Size};
use abstracttui::render::{Cell, FrameDiff, PresentCaps, Presenter, Style, Surface};
use abstracttui::testing::frames::assert_screen_matches;
use abstracttui::testing::VtScreen;
use abstracttui::text;
fn torture_strings() -> Vec<String> {
let mut v: Vec<String> = vec![
format!("e{}", "\u{0301}".repeat(20)),
"a\u{0301}b\u{0302}c\u{0303}d\u{0304}e\u{0308}".to_string(),
"abcمرحباשלוםxyz".to_string(),
"\u{1F1EB}\u{1F1F7}\u{1F1EF}\u{1F1F5}\u{1F1FA}\u{1F1F8}".to_string(),
"\u{1F44B}\u{1F3FF}\u{1F44D}\u{1F3FB}".to_string(),
"\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}".to_string(),
"\u{2601}\u{FE0F}\u{2764}\u{FE0F}".to_string(),
"日本語のテキスト表示".to_string(),
"x\u{200B}y\u{200D}z\u{FEFF}w".to_string(),
"a\u{0007}b\u{001b}c\u{0000}d".to_string(),
];
let raw: &[u8] = &[0x61, 0xED, 0xA0, 0x80, 0x62, 0xFF, 0xFE, 0x63];
v.push(String::from_utf8_lossy(raw).into_owned());
v
}
#[test]
fn drawing_torture_strings_keeps_pairs_valid_and_width_consistent() {
let style = Style::new()
.fg(Rgba::rgb(220, 220, 230))
.bg(Rgba::rgb(10, 10, 14));
for s in torture_strings() {
let mut surf = Surface::new(Size::new(80, 3), Cell::EMPTY);
let advanced = surf.draw_text(0, 0, &s, style);
surf.debug_validate()
.unwrap_or_else(|e| panic!("pair invariant broke on {s:?}: {e}"));
let model_w = text::width(&s).min(80);
assert!(
advanced <= 80,
"draw advanced {advanced} past the 80-col surface on {s:?}"
);
assert!(
advanced <= model_w.max(0),
"draw advanced {advanced} > model width {model_w} on {s:?}"
);
}
}
#[test]
fn torture_strings_roundtrip_through_present_and_model() {
let caps = PresentCaps::FULL;
let size = Size::new(40, 3);
let style = Style::new()
.fg(Rgba::rgb(200, 200, 210))
.bg(Rgba::rgb(12, 12, 18));
for (si, s) in torture_strings().into_iter().enumerate() {
for x in 0..size.w {
let mut screen = VtScreen::new(size);
let mut diff = FrameDiff::new();
let mut presenter = Presenter::new();
let prev = Surface::new(size, Cell::EMPTY);
let mut next = Surface::new(size, Cell::EMPTY);
next.draw_text(x, 1, &s, style);
next.debug_validate()
.unwrap_or_else(|e| panic!("string {si} @x{x}: surface invalid: {e}"));
let mut bytes = Vec::new();
let runs = diff.compute_full(&prev, &next);
presenter.emit(runs, &next, &caps, &mut bytes);
screen.feed(&bytes);
assert_eq!(
screen.unknown_seq_count(),
0,
"string {si} @x{x}: unmodeled bytes: {:?}",
screen.unknown_samples()
);
assert_screen_matches(&screen, &next, &caps, &format!("unicode string {si} @x{x}"));
}
}
}
#[test]
fn width_model_is_cluster_additive_on_torture_corpus() {
for s in torture_strings() {
let mut sum = 0i32;
for seg in text::segments(&s) {
let w = seg.width;
assert!(
(0..=2).contains(&w),
"cluster {:?} has width {w} (not 0/1/2)",
seg.cluster
);
sum += w;
}
assert_eq!(
sum,
text::width(&s),
"string width != sum of cluster widths for {s:?}"
);
}
}