1use crate::buffer::Buffer;
8use crate::context::Context;
9use crate::event::{
10 Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseKind,
11};
12use crate::layout;
13use crate::rect::Rect;
14use crate::style::Theme;
15use crate::FrameState;
16
17pub struct EventBuilder {
36 events: Vec<Event>,
37}
38
39impl EventBuilder {
40 pub fn new() -> Self {
42 Self { events: Vec::new() }
43 }
44
45 pub fn key(mut self, c: char) -> Self {
47 self.events.push(Event::Key(KeyEvent {
48 code: KeyCode::Char(c),
49 modifiers: KeyModifiers::NONE,
50 kind: KeyEventKind::Press,
51 }));
52 self
53 }
54
55 pub fn key_code(mut self, code: KeyCode) -> Self {
57 self.events.push(Event::Key(KeyEvent {
58 code,
59 modifiers: KeyModifiers::NONE,
60 kind: KeyEventKind::Press,
61 }));
62 self
63 }
64
65 pub fn key_with(mut self, code: KeyCode, modifiers: KeyModifiers) -> Self {
67 self.events.push(Event::Key(KeyEvent {
68 code,
69 modifiers,
70 kind: KeyEventKind::Press,
71 }));
72 self
73 }
74
75 pub fn click(mut self, x: u32, y: u32) -> Self {
77 self.events.push(Event::Mouse(MouseEvent {
78 kind: MouseKind::Down(MouseButton::Left),
79 x,
80 y,
81 modifiers: KeyModifiers::NONE,
82 pixel_x: None,
83 pixel_y: None,
84 }));
85 self
86 }
87
88 pub fn scroll_up(mut self, x: u32, y: u32) -> Self {
90 self.events.push(Event::Mouse(MouseEvent {
91 kind: MouseKind::ScrollUp,
92 x,
93 y,
94 modifiers: KeyModifiers::NONE,
95 pixel_x: None,
96 pixel_y: None,
97 }));
98 self
99 }
100
101 pub fn scroll_down(mut self, x: u32, y: u32) -> Self {
103 self.events.push(Event::Mouse(MouseEvent {
104 kind: MouseKind::ScrollDown,
105 x,
106 y,
107 modifiers: KeyModifiers::NONE,
108 pixel_x: None,
109 pixel_y: None,
110 }));
111 self
112 }
113
114 pub fn paste(mut self, text: impl Into<String>) -> Self {
116 self.events.push(Event::Paste(text.into()));
117 self
118 }
119
120 pub fn resize(mut self, width: u32, height: u32) -> Self {
122 self.events.push(Event::Resize(width, height));
123 self
124 }
125
126 pub fn build(self) -> Vec<Event> {
128 self.events
129 }
130}
131
132impl Default for EventBuilder {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138pub struct TestBackend {
157 buffer: Buffer,
158 width: u32,
159 height: u32,
160 hook_states: Vec<Box<dyn std::any::Any>>,
161}
162
163impl TestBackend {
164 pub fn new(width: u32, height: u32) -> Self {
166 let area = Rect::new(0, 0, width, height);
167 Self {
168 buffer: Buffer::empty(area),
169 width,
170 height,
171 hook_states: Vec::new(),
172 }
173 }
174
175 pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
177 let mut frame_state = FrameState {
178 hook_states: std::mem::take(&mut self.hook_states),
179 ..FrameState::default()
180 };
181 let mut ctx = Context::new(
182 Vec::new(),
183 self.width,
184 self.height,
185 &mut frame_state,
186 Theme::dark(),
187 );
188 f(&mut ctx);
189 ctx.render_notifications();
190 ctx.emit_pending_tooltips();
191 let mut tree = layout::build_tree(&ctx.commands);
192 self.hook_states = ctx.hook_states;
193 let mut deferred = ctx.deferred_draws;
194 let area = Rect::new(0, 0, self.width, self.height);
195 layout::compute(&mut tree, area);
196 self.buffer.reset();
197 layout::render(&tree, &mut self.buffer);
198 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
199 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
200 self.buffer.push_clip(rect);
201 cb(&mut self.buffer, rect);
202 self.buffer.pop_clip();
203 }
204 }
205 }
206
207 pub fn render_with_events(
209 &mut self,
210 events: Vec<Event>,
211 focus_index: usize,
212 prev_focus_count: usize,
213 f: impl FnOnce(&mut Context),
214 ) {
215 let mut frame_state = FrameState {
216 hook_states: std::mem::take(&mut self.hook_states),
217 focus_index,
218 prev_focus_count,
219 ..FrameState::default()
220 };
221 let mut ctx = Context::new(
222 events,
223 self.width,
224 self.height,
225 &mut frame_state,
226 Theme::dark(),
227 );
228 ctx.process_focus_keys();
229 f(&mut ctx);
230 ctx.render_notifications();
231 ctx.emit_pending_tooltips();
232 let mut tree = layout::build_tree(&ctx.commands);
233 self.hook_states = ctx.hook_states;
234 let mut deferred = ctx.deferred_draws;
235 let area = Rect::new(0, 0, self.width, self.height);
236 layout::compute(&mut tree, area);
237 self.buffer.reset();
238 layout::render(&tree, &mut self.buffer);
239 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
240 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
241 self.buffer.push_clip(rect);
242 cb(&mut self.buffer, rect);
243 self.buffer.pop_clip();
244 }
245 }
246 }
247
248 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
250 self.render_with_events(events, 0, 0, f);
251 }
252
253 pub fn line(&self, y: u32) -> String {
255 let mut s = String::new();
256 for x in 0..self.width {
257 s.push_str(&self.buffer.get(x, y).symbol);
258 }
259 s.trim_end().to_string()
260 }
261
262 pub fn assert_line(&self, y: u32, expected: &str) {
264 let line = self.line(y);
265 assert_eq!(
266 line, expected,
267 "Line {y}: expected {expected:?}, got {line:?}"
268 );
269 }
270
271 pub fn assert_line_contains(&self, y: u32, expected: &str) {
273 let line = self.line(y);
274 assert!(
275 line.contains(expected),
276 "Line {y}: expected to contain {expected:?}, got {line:?}"
277 );
278 }
279
280 pub fn assert_contains(&self, expected: &str) {
282 for y in 0..self.height {
283 if self.line(y).contains(expected) {
284 return;
285 }
286 }
287 let mut all_lines = String::new();
288 for y in 0..self.height {
289 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
290 }
291 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
292 }
293
294 pub fn buffer(&self) -> &Buffer {
296 &self.buffer
297 }
298
299 pub fn width(&self) -> u32 {
301 self.width
302 }
303
304 pub fn height(&self) -> u32 {
306 self.height
307 }
308
309 pub fn to_string_trimmed(&self) -> String {
314 let mut lines = Vec::with_capacity(self.height as usize);
315 for y in 0..self.height {
316 lines.push(self.line(y));
317 }
318 while lines.last().is_some_and(|l| l.is_empty()) {
319 lines.pop();
320 }
321 lines.join("\n")
322 }
323}
324
325impl std::fmt::Display for TestBackend {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 write!(f, "{}", self.to_string_trimmed())
328 }
329}