// Snapshot testing utilities use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::style::{Color, Modifier}; use ratatui::Terminal; /// Конвертирует Buffer в читаемую строку для snapshot тестов pub fn buffer_to_string(buffer: &Buffer) -> String { let area = buffer.area(); let mut result = String::new(); for y in 0..area.height { let mut line = String::new(); for x in 0..area.width { line.push_str(buffer[(x, y)].symbol()); } // Убираем trailing spaces в конце строки result.push_str(line.trim_end()); if y < area.height - 1 { result.push('\n'); } } result } /// Serializes only cells with non-default style, grouped by row and style. pub fn buffer_to_style_snapshot(buffer: &Buffer) -> String { let area = buffer.area(); let mut rows = Vec::new(); for y in 0..area.height { let mut segments = Vec::new(); let mut x = 0; while x < area.width { let cell = &buffer[(x, y)]; if is_default_style(cell) { x += 1; continue; } let start = x; let fg = cell.fg; let bg = cell.bg; let modifier = cell.modifier; let mut text = String::new(); while x < area.width { let next = &buffer[(x, y)]; if is_default_style(next) || next.fg != fg || next.bg != bg || next.modifier != modifier { break; } text.push_str(next.symbol()); x += 1; } segments.push(format!( "{}..{} {:?}/{:?}/{:?}: {:?}", start, x.saturating_sub(1), fg, bg, modifier, text.trim_end() )); } if !segments.is_empty() { rows.push(format!("y={}: {}", y, segments.join(" | "))); } } rows.join("\n") } fn is_default_style(cell: &ratatui::buffer::Cell) -> bool { cell.fg == Color::Reset && cell.bg == Color::Reset && cell.modifier == Modifier::empty() } /// Создаёт TestBackend с заданным размером и рендерит UI pub fn render_to_buffer(width: u16, height: u16, render_fn: F) -> Buffer where F: FnOnce(&mut ratatui::Frame), { let backend = TestBackend::new(width, height); let mut terminal = Terminal::new(backend).unwrap(); terminal.draw(render_fn).unwrap(); terminal.backend().buffer().clone() } /// Макрос для упрощения snapshot тестов #[macro_export] macro_rules! assert_ui_snapshot { ($name:expr, $width:expr, $height:expr, $render_fn:expr) => {{ use $crate::helpers::snapshot_utils::{buffer_to_string, render_to_buffer}; let buffer = render_to_buffer($width, $height, $render_fn); let output = buffer_to_string(&buffer); insta::assert_snapshot!($name, output); }}; } #[cfg(test)] mod tests { use super::*; use ratatui::layout::Rect; use ratatui::widgets::{Block, Borders}; #[test] fn test_buffer_to_string_simple() { let buffer = render_to_buffer(10, 3, |f| { let block = Block::default().borders(Borders::ALL).title("Hi"); f.render_widget(block, f.area()); }); let result = buffer_to_string(&buffer); assert!(result.contains("Hi")); assert!(result.contains("┌")); assert!(result.contains("└")); } #[test] fn test_buffer_to_string_removes_trailing_spaces() { let buffer = render_to_buffer(20, 3, |f| { let block = Block::default().title("Test"); f.render_widget(block, Rect::new(0, 0, 10, 3)); }); let result = buffer_to_string(&buffer); let lines: Vec<&str> = result.lines().collect(); // Проверяем что trailing spaces убраны for line in lines { assert!(!line.ends_with(' ') || line.trim().is_empty()); } } }