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            false,
117            Theme::dark(),
118            None,
119        );
120        f(&mut ctx);
121        let mut tree = layout::build_tree(&ctx.commands);
122        let area = Rect::new(0, 0, self.width, self.height);
123        layout::compute(&mut tree, area);
124        self.buffer.reset();
125        layout::render(&tree, &mut self.buffer);
126    }
127
128    /// Render with specific events (for testing keyboard/mouse interaction)
129    pub fn render_with_events(
130        &mut self,
131        events: Vec<Event>,
132        focus_index: usize,
133        prev_focus_count: usize,
134        f: impl FnOnce(&mut Context),
135    ) {
136        let mut ctx = Context::new(
137            events,
138            self.width,
139            self.height,
140            0,
141            focus_index,
142            prev_focus_count,
143            Vec::new(),
144            Vec::new(),
145            false,
146            Theme::dark(),
147            None,
148        );
149        ctx.process_focus_keys();
150        f(&mut ctx);
151        let mut tree = layout::build_tree(&ctx.commands);
152        let area = Rect::new(0, 0, self.width, self.height);
153        layout::compute(&mut tree, area);
154        self.buffer.reset();
155        layout::render(&tree, &mut self.buffer);
156    }
157
158    pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
159        self.render_with_events(events, 0, 0, f);
160    }
161
162    /// Get the rendered text content of row y (trimmed trailing spaces)
163    pub fn line(&self, y: u32) -> String {
164        let mut s = String::new();
165        for x in 0..self.width {
166            s.push_str(&self.buffer.get(x, y).symbol);
167        }
168        s.trim_end().to_string()
169    }
170
171    /// Assert that row y contains `expected` as a substring
172    pub fn assert_line(&self, y: u32, expected: &str) {
173        let line = self.line(y);
174        assert_eq!(
175            line, expected,
176            "Line {y}: expected {expected:?}, got {line:?}"
177        );
178    }
179
180    /// Assert that row y contains `expected` as a substring
181    pub fn assert_line_contains(&self, y: u32, expected: &str) {
182        let line = self.line(y);
183        assert!(
184            line.contains(expected),
185            "Line {y}: expected to contain {expected:?}, got {line:?}"
186        );
187    }
188
189    /// Assert that any line in the buffer contains `expected`
190    pub fn assert_contains(&self, expected: &str) {
191        for y in 0..self.height {
192            if self.line(y).contains(expected) {
193                return;
194            }
195        }
196        let mut all_lines = String::new();
197        for y in 0..self.height {
198            all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
199        }
200        panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
201    }
202
203    pub fn buffer(&self) -> &Buffer {
204        &self.buffer
205    }
206
207    pub fn width(&self) -> u32 {
208        self.width
209    }
210
211    pub fn height(&self) -> u32 {
212        self.height
213    }
214}