lv-tui 0.3.0

A reactive TUI framework for Rust, inspired by Textual and React
Documentation
//! Test utilities for asserting component render output.

use crate::buffer::Buffer;
use crate::component::Component;
use crate::geom::{Rect, Size};
use crate::render::RenderCx;
use crate::style::Color;

/// A buffer wrapper for testing component render output.
pub struct TestBuffer {
    pub buffer: Buffer,
}

impl TestBuffer {
    /// Creates a test buffer of the given size.
    pub fn new(width: u16, height: u16) -> Self {
        Self { buffer: Buffer::new(Size { width, height }) }
    }

    /// Renders a component into this buffer.
    pub fn render<C: Component>(&mut self, component: &C) {
        let rect = Rect { x: 0, y: 0, width: self.buffer.size.width, height: self.buffer.size.height };
        let style = component.style();
        let mut cx = RenderCx::new(rect, &mut self.buffer, style);
        component.render(&mut cx);
    }

    /// Asserts that the cell at (x, y) contains the expected text.
    #[track_caller]
    pub fn assert_text(&self, x: u16, y: u16, expected: &str) {
        let index = y as usize * self.buffer.size.width as usize + x as usize;
        let cell = &self.buffer.cells[index];
        assert_eq!(cell.symbol, expected,
            "cell at ({}, {}) expected {:?}, got {:?}", x, y, expected, cell.symbol);
    }

    /// Asserts that a line starting from x=0 matches the expected text.
    /// Spaces at the end of rendered output are trimmed before comparison.
    #[track_caller]
    pub fn assert_line(&self, y: u16, expected: &str) {
        let mut actual = String::new();
        for x in 0..self.buffer.size.width {
            let index = y as usize * self.buffer.size.width as usize + x as usize;
            actual.push_str(&self.buffer.cells[index].symbol);
        }
        let actual = actual.trim_end();
        assert_eq!(actual, expected,
            "line {} expected {:?}, got {:?}", y, expected, actual);
    }

    /// Asserts entire buffer lines match expected strings (trailing spaces trimmed).
    #[track_caller]
    pub fn assert_lines(&self, expected: &[&str]) {
        for (y, exp) in expected.iter().enumerate() {
            self.assert_line(y as u16, exp);
        }
    }

    /// Returns the foreground color of the cell at (x, y).
    pub fn cell_fg(&self, x: u16, y: u16) -> Option<Color> {
        let index = y as usize * self.buffer.size.width as usize + x as usize;
        self.buffer.cells[index].style.fg
    }

    /// Returns the background color of the cell at (x, y).
    pub fn cell_bg(&self, x: u16, y: u16) -> Option<Color> {
        let index = y as usize * self.buffer.size.width as usize + x as usize;
        self.buffer.cells[index].style.bg
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::widgets::{Checkbox, Label};

    #[test]
    fn test_label_renders_text() {
        let mut tb = TestBuffer::new(20, 1);
        tb.render(&Label::new("hello"));
        tb.assert_text(0, 0, "h");
        tb.assert_line(0, "hello");
    }

    #[test]
    fn test_checkbox_unchecked() {
        let mut tb = TestBuffer::new(20, 1);
        tb.render(&Checkbox::new("opt"));
        tb.assert_text(1, 0, " "); // unchecked bracket
    }

    #[test]
    fn test_checkbox_checked() {
        let mut tb = TestBuffer::new(20, 1);
        tb.render(&Checkbox::new("opt").checked());
        tb.assert_text(1, 0, "");
    }
}