use ratatui::backend::TestBackend;
use ratatui::layout::Position;
use ratatui::style::Color;
use ratatui::style::Style;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use ratatui::Terminal;
use supercode_frontend_tui::foundation::wrapping::adaptive_wrap_line;
use supercode_frontend_tui::foundation::wrapping::RtOptions;
use supercode_frontend_tui::terminal::palette::ColorCapabilities;
fn render_snapshot(width: u16) -> (String, Position) {
let backend = TestBackend::new(width, 6);
let mut terminal = Terminal::new(backend).expect("terminal");
let source = Line::from(
"assistant │ Unicode 東京 stays aligned while tools report progress without splitting grapheme clusters.",
);
let wrapped = adaptive_wrap_line(&source, RtOptions::new(usize::from(width)));
terminal
.draw(|frame| {
frame.render_widget(Paragraph::new(wrapped.clone()), frame.area());
frame.set_cursor_position(Position::new(width.saturating_sub(2), 3));
})
.expect("draw");
let backend = terminal.backend();
let rows = (0..6)
.map(|y| {
(0..width)
.map(|x| backend.buffer()[(x, y)].symbol())
.collect::<String>()
.trim_end()
.to_string()
})
.collect::<Vec<_>>()
.join("\n");
(rows, backend.cursor_position())
}
#[test]
fn stable_buffers_and_cursor_at_representative_widths() {
let cases = [
(
40,
"assistant │ Unicode 東 京 stays aligned\nwhile tools report progress without\nsplitting grapheme clusters.\n\n\n",
),
(
80,
"assistant │ Unicode 東 京 stays aligned while tools report progress without\nsplitting grapheme clusters.\n\n\n\n",
),
(
160,
"assistant │ Unicode 東 京 stays aligned while tools report progress without splitting grapheme clusters.\n\n\n\n\n",
),
];
for (width, expected) in cases {
let (snapshot, cursor) = render_snapshot(width);
assert_eq!(snapshot, expected, "buffer changed at width {width}");
assert_eq!(cursor, Position::new(width - 2, 3));
}
}
fn rendered_foreground(capabilities: ColorCapabilities) -> Vec<Color> {
let backend = TestBackend::new(8, 1);
let mut terminal = Terminal::new(backend).expect("terminal");
let color = capabilities.best_color((12, 34, 56));
terminal
.draw(|frame| {
frame.render_widget(
Paragraph::new("status").style(Style::default().fg(color)),
frame.area(),
);
})
.expect("draw");
(0..6)
.map(|x| terminal.backend().buffer()[(x, 0)].fg)
.collect()
}
#[test]
fn color_capability_buffers_are_deterministic() {
let truecolor =
ColorCapabilities::resolve(false, Some("xterm-256color"), Some("truecolor"), false);
let ansi256 = ColorCapabilities::resolve(false, Some("xterm-256color"), None, false);
let no_color =
ColorCapabilities::resolve(true, Some("xterm-256color"), Some("truecolor"), false);
let dumb = ColorCapabilities::resolve(false, Some("dumb"), Some("truecolor"), false);
let monochrome = ColorCapabilities::resolve(false, Some("xterm"), None, false);
assert_eq!(
rendered_foreground(truecolor),
vec![Color::Rgb(12, 34, 56); 6]
);
assert_eq!(rendered_foreground(ansi256), vec![Color::Indexed(234); 6]);
assert_eq!(rendered_foreground(no_color), vec![Color::Reset; 6]);
assert_eq!(rendered_foreground(dumb), vec![Color::Reset; 6]);
assert_eq!(rendered_foreground(monochrome), vec![Color::Reset; 6]);
}