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 resize(mut self, width: u32, height: u32) -> Self {
69        self.events.push(Event::Resize(width, height));
70        self
71    }
72
73    pub fn build(self) -> Vec<Event> {
74        self.events
75    }
76}
77
78impl Default for EventBuilder {
79    fn default() -> Self {
80        Self::new()
81    }
82}
83
84pub struct TestBackend {
85    buffer: Buffer,
86    width: u32,
87    height: u32,
88}
89
90impl TestBackend {
91    pub fn new(width: u32, height: u32) -> Self {
92        let area = Rect::new(0, 0, width, height);
93        Self {
94            buffer: Buffer::empty(area),
95            width,
96            height,
97        }
98    }
99
100    /// Run a closure as if it were one frame, render to internal buffer
101    pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
102        let mut ctx = Context::new(
103            Vec::new(),
104            self.width,
105            self.height,
106            0,
107            0,
108            0,
109            Vec::new(),
110            Vec::new(),
111            false,
112            Theme::dark(),
113            None,
114        );
115        f(&mut ctx);
116        let mut tree = layout::build_tree(&ctx.commands);
117        let area = Rect::new(0, 0, self.width, self.height);
118        layout::compute(&mut tree, area);
119        self.buffer.reset();
120        layout::render(&tree, &mut self.buffer);
121    }
122
123    /// Render with specific events (for testing keyboard/mouse interaction)
124    pub fn render_with_events(
125        &mut self,
126        events: Vec<Event>,
127        focus_index: usize,
128        prev_focus_count: usize,
129        f: impl FnOnce(&mut Context),
130    ) {
131        let mut ctx = Context::new(
132            events,
133            self.width,
134            self.height,
135            0,
136            focus_index,
137            prev_focus_count,
138            Vec::new(),
139            Vec::new(),
140            false,
141            Theme::dark(),
142            None,
143        );
144        ctx.process_focus_keys();
145        f(&mut ctx);
146        let mut tree = layout::build_tree(&ctx.commands);
147        let area = Rect::new(0, 0, self.width, self.height);
148        layout::compute(&mut tree, area);
149        self.buffer.reset();
150        layout::render(&tree, &mut self.buffer);
151    }
152
153    pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
154        self.render_with_events(events, 0, 0, f);
155    }
156
157    /// Get the rendered text content of row y (trimmed trailing spaces)
158    pub fn line(&self, y: u32) -> String {
159        let mut s = String::new();
160        for x in 0..self.width {
161            s.push_str(&self.buffer.get(x, y).symbol);
162        }
163        s.trim_end().to_string()
164    }
165
166    /// Assert that row y contains `expected` as a substring
167    pub fn assert_line(&self, y: u32, expected: &str) {
168        let line = self.line(y);
169        assert_eq!(
170            line, expected,
171            "Line {y}: expected {expected:?}, got {line:?}"
172        );
173    }
174
175    /// Assert that row y contains `expected` as a substring
176    pub fn assert_line_contains(&self, y: u32, expected: &str) {
177        let line = self.line(y);
178        assert!(
179            line.contains(expected),
180            "Line {y}: expected to contain {expected:?}, got {line:?}"
181        );
182    }
183
184    /// Assert that any line in the buffer contains `expected`
185    pub fn assert_contains(&self, expected: &str) {
186        for y in 0..self.height {
187            if self.line(y).contains(expected) {
188                return;
189            }
190        }
191        let mut all_lines = String::new();
192        for y in 0..self.height {
193            all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
194        }
195        panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
196    }
197
198    pub fn buffer(&self) -> &Buffer {
199        &self.buffer
200    }
201
202    pub fn width(&self) -> u32 {
203        self.width
204    }
205
206    pub fn height(&self) -> u32 {
207        self.height
208    }
209}