Skip to main content

slt/
test_utils.rs

1use crate::buffer::Buffer;
2use crate::context::Context;
3use crate::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseKind};
4use crate::layout;
5use crate::rect::Rect;
6use crate::style::Theme;
7
8pub struct EventBuilder {
9    events: Vec<Event>,
10}
11
12impl EventBuilder {
13    pub fn new() -> Self {
14        Self { events: Vec::new() }
15    }
16
17    pub fn key(mut self, c: char) -> Self {
18        self.events.push(Event::Key(KeyEvent {
19            code: KeyCode::Char(c),
20            modifiers: KeyModifiers::NONE,
21        }));
22        self
23    }
24
25    pub fn key_code(mut self, code: KeyCode) -> Self {
26        self.events.push(Event::Key(KeyEvent {
27            code,
28            modifiers: KeyModifiers::NONE,
29        }));
30        self
31    }
32
33    pub fn key_with(mut self, code: KeyCode, modifiers: KeyModifiers) -> Self {
34        self.events.push(Event::Key(KeyEvent { code, modifiers }));
35        self
36    }
37
38    pub fn click(mut self, x: u32, y: u32) -> Self {
39        self.events.push(Event::Mouse(MouseEvent {
40            kind: MouseKind::Down(MouseButton::Left),
41            x,
42            y,
43            modifiers: KeyModifiers::NONE,
44        }));
45        self
46    }
47
48    pub fn scroll_up(mut self, x: u32, y: u32) -> Self {
49        self.events.push(Event::Mouse(MouseEvent {
50            kind: MouseKind::ScrollUp,
51            x,
52            y,
53            modifiers: KeyModifiers::NONE,
54        }));
55        self
56    }
57
58    pub fn scroll_down(mut self, x: u32, y: u32) -> Self {
59        self.events.push(Event::Mouse(MouseEvent {
60            kind: MouseKind::ScrollDown,
61            x,
62            y,
63            modifiers: KeyModifiers::NONE,
64        }));
65        self
66    }
67
68    pub fn paste(mut self, text: impl Into<String>) -> Self {
69        self.events.push(Event::Paste(text.into()));
70        self
71    }
72
73    pub fn resize(mut self, width: u32, height: u32) -> Self {
74        self.events.push(Event::Resize(width, height));
75        self
76    }
77
78    pub fn build(self) -> Vec<Event> {
79        self.events
80    }
81}
82
83impl Default for EventBuilder {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89pub struct TestBackend {
90    buffer: Buffer,
91    width: u32,
92    height: u32,
93}
94
95impl TestBackend {
96    pub fn new(width: u32, height: u32) -> Self {
97        let area = Rect::new(0, 0, width, height);
98        Self {
99            buffer: Buffer::empty(area),
100            width,
101            height,
102        }
103    }
104
105    /// Run a closure as if it were one frame, render to internal buffer
106    pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
107        let mut ctx = Context::new(
108            Vec::new(),
109            self.width,
110            self.height,
111            0,
112            0,
113            0,
114            Vec::new(),
115            Vec::new(),
116            Vec::new(),
117            false,
118            Theme::dark(),
119            None,
120        );
121        f(&mut ctx);
122        let mut tree = layout::build_tree(&ctx.commands);
123        let area = Rect::new(0, 0, self.width, self.height);
124        layout::compute(&mut tree, area);
125        self.buffer.reset();
126        layout::render(&tree, &mut self.buffer);
127    }
128
129    /// Render with specific events (for testing keyboard/mouse interaction)
130    pub fn render_with_events(
131        &mut self,
132        events: Vec<Event>,
133        focus_index: usize,
134        prev_focus_count: usize,
135        f: impl FnOnce(&mut Context),
136    ) {
137        let mut ctx = Context::new(
138            events,
139            self.width,
140            self.height,
141            0,
142            focus_index,
143            prev_focus_count,
144            Vec::new(),
145            Vec::new(),
146            Vec::new(),
147            false,
148            Theme::dark(),
149            None,
150        );
151        ctx.process_focus_keys();
152        f(&mut ctx);
153        let mut tree = layout::build_tree(&ctx.commands);
154        let area = Rect::new(0, 0, self.width, self.height);
155        layout::compute(&mut tree, area);
156        self.buffer.reset();
157        layout::render(&tree, &mut self.buffer);
158    }
159
160    pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
161        self.render_with_events(events, 0, 0, f);
162    }
163
164    /// Get the rendered text content of row y (trimmed trailing spaces)
165    pub fn line(&self, y: u32) -> String {
166        let mut s = String::new();
167        for x in 0..self.width {
168            s.push_str(&self.buffer.get(x, y).symbol);
169        }
170        s.trim_end().to_string()
171    }
172
173    /// Assert that row y contains `expected` as a substring
174    pub fn assert_line(&self, y: u32, expected: &str) {
175        let line = self.line(y);
176        assert_eq!(
177            line, expected,
178            "Line {y}: expected {expected:?}, got {line:?}"
179        );
180    }
181
182    /// Assert that row y contains `expected` as a substring
183    pub fn assert_line_contains(&self, y: u32, expected: &str) {
184        let line = self.line(y);
185        assert!(
186            line.contains(expected),
187            "Line {y}: expected to contain {expected:?}, got {line:?}"
188        );
189    }
190
191    /// Assert that any line in the buffer contains `expected`
192    pub fn assert_contains(&self, expected: &str) {
193        for y in 0..self.height {
194            if self.line(y).contains(expected) {
195                return;
196            }
197        }
198        let mut all_lines = String::new();
199        for y in 0..self.height {
200            all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
201        }
202        panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
203    }
204
205    pub fn buffer(&self) -> &Buffer {
206        &self.buffer
207    }
208
209    pub fn width(&self) -> u32 {
210        self.width
211    }
212
213    pub fn height(&self) -> u32 {
214        self.height
215    }
216}