Skip to main content

slt/
test_utils.rs

1use crate::buffer::Buffer;
2use crate::context::Context;
3use crate::event::Event;
4use crate::layout;
5use crate::rect::Rect;
6use crate::style::Theme;
7
8pub struct TestBackend {
9    buffer: Buffer,
10    width: u32,
11    height: u32,
12}
13
14impl TestBackend {
15    pub fn new(width: u32, height: u32) -> Self {
16        let area = Rect::new(0, 0, width, height);
17        Self {
18            buffer: Buffer::empty(area),
19            width,
20            height,
21        }
22    }
23
24    /// Run a closure as if it were one frame, render to internal buffer
25    pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
26        let mut ctx = Context::new(
27            Vec::new(),
28            self.width,
29            self.height,
30            0,
31            0,
32            0,
33            Vec::new(),
34            Vec::new(),
35            false,
36            Theme::dark(),
37            None,
38        );
39        f(&mut ctx);
40        let mut tree = layout::build_tree(&ctx.commands);
41        let area = Rect::new(0, 0, self.width, self.height);
42        layout::compute(&mut tree, area);
43        self.buffer.reset();
44        layout::render(&tree, &mut self.buffer);
45    }
46
47    /// Render with specific events (for testing keyboard/mouse interaction)
48    pub fn render_with_events(
49        &mut self,
50        events: Vec<Event>,
51        focus_index: usize,
52        prev_focus_count: usize,
53        f: impl FnOnce(&mut Context),
54    ) {
55        let mut ctx = Context::new(
56            events,
57            self.width,
58            self.height,
59            0,
60            focus_index,
61            prev_focus_count,
62            Vec::new(),
63            Vec::new(),
64            false,
65            Theme::dark(),
66            None,
67        );
68        ctx.process_focus_keys();
69        f(&mut ctx);
70        let mut tree = layout::build_tree(&ctx.commands);
71        let area = Rect::new(0, 0, self.width, self.height);
72        layout::compute(&mut tree, area);
73        self.buffer.reset();
74        layout::render(&tree, &mut self.buffer);
75    }
76
77    /// Get the rendered text content of row y (trimmed trailing spaces)
78    pub fn line(&self, y: u32) -> String {
79        let mut s = String::new();
80        for x in 0..self.width {
81            s.push_str(&self.buffer.get(x, y).symbol);
82        }
83        s.trim_end().to_string()
84    }
85
86    /// Assert that row y contains `expected` as a substring
87    pub fn assert_line(&self, y: u32, expected: &str) {
88        let line = self.line(y);
89        assert_eq!(
90            line, expected,
91            "Line {y}: expected {expected:?}, got {line:?}"
92        );
93    }
94
95    /// Assert that row y contains `expected` as a substring
96    pub fn assert_line_contains(&self, y: u32, expected: &str) {
97        let line = self.line(y);
98        assert!(
99            line.contains(expected),
100            "Line {y}: expected to contain {expected:?}, got {line:?}"
101        );
102    }
103
104    /// Assert that any line in the buffer contains `expected`
105    pub fn assert_contains(&self, expected: &str) {
106        for y in 0..self.height {
107            if self.line(y).contains(expected) {
108                return;
109            }
110        }
111        let mut all_lines = String::new();
112        for y in 0..self.height {
113            all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
114        }
115        panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
116    }
117
118    pub fn buffer(&self) -> &Buffer {
119        &self.buffer
120    }
121
122    pub fn width(&self) -> u32 {
123        self.width
124    }
125
126    pub fn height(&self) -> u32 {
127        self.height
128    }
129}