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(std::mem::take(&mut 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 rdr in layout::collect_raw_draw_rects(&tree) {
199 if rdr.rect.width == 0 || rdr.rect.height == 0 {
200 continue;
201 }
202 if let Some(cb) = deferred.get_mut(rdr.draw_id).and_then(|c| c.take()) {
203 self.buffer.push_clip(rdr.rect);
204 self.buffer.kitty_clip_info = Some((rdr.top_clip_rows, rdr.original_height));
205 cb(&mut self.buffer, rdr.rect);
206 self.buffer.kitty_clip_info = None;
207 self.buffer.pop_clip();
208 }
209 }
210 }
211
212 pub fn render_with_events(
214 &mut self,
215 events: Vec<Event>,
216 focus_index: usize,
217 prev_focus_count: usize,
218 f: impl FnOnce(&mut Context),
219 ) {
220 let mut frame_state = FrameState {
221 hook_states: std::mem::take(&mut self.hook_states),
222 focus_index,
223 prev_focus_count,
224 ..FrameState::default()
225 };
226 let mut ctx = Context::new(
227 events,
228 self.width,
229 self.height,
230 &mut frame_state,
231 Theme::dark(),
232 );
233 f(&mut ctx);
234 ctx.process_focus_keys();
235 ctx.render_notifications();
236 ctx.emit_pending_tooltips();
237 let mut tree = layout::build_tree(std::mem::take(&mut ctx.commands));
238 self.hook_states = ctx.hook_states;
239 let mut deferred = ctx.deferred_draws;
240 let area = Rect::new(0, 0, self.width, self.height);
241 layout::compute(&mut tree, area);
242 self.buffer.reset();
243 layout::render(&tree, &mut self.buffer);
244 for rdr in layout::collect_raw_draw_rects(&tree) {
245 if rdr.rect.width == 0 || rdr.rect.height == 0 {
246 continue;
247 }
248 if let Some(cb) = deferred.get_mut(rdr.draw_id).and_then(|c| c.take()) {
249 self.buffer.push_clip(rdr.rect);
250 self.buffer.kitty_clip_info = Some((rdr.top_clip_rows, rdr.original_height));
251 cb(&mut self.buffer, rdr.rect);
252 self.buffer.kitty_clip_info = None;
253 self.buffer.pop_clip();
254 }
255 }
256 }
257
258 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
260 self.render_with_events(events, 0, 0, f);
261 }
262
263 pub fn line(&self, y: u32) -> String {
265 let mut s = String::new();
266 for x in 0..self.width {
267 s.push_str(&self.buffer.get(x, y).symbol);
268 }
269 s.trim_end().to_string()
270 }
271
272 pub fn assert_line(&self, y: u32, expected: &str) {
274 let line = self.line(y);
275 assert_eq!(
276 line, expected,
277 "Line {y}: expected {expected:?}, got {line:?}"
278 );
279 }
280
281 pub fn assert_line_contains(&self, y: u32, expected: &str) {
283 let line = self.line(y);
284 assert!(
285 line.contains(expected),
286 "Line {y}: expected to contain {expected:?}, got {line:?}"
287 );
288 }
289
290 pub fn assert_contains(&self, expected: &str) {
292 for y in 0..self.height {
293 if self.line(y).contains(expected) {
294 return;
295 }
296 }
297 let mut all_lines = String::new();
298 for y in 0..self.height {
299 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
300 }
301 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
302 }
303
304 pub fn buffer(&self) -> &Buffer {
306 &self.buffer
307 }
308
309 pub fn width(&self) -> u32 {
311 self.width
312 }
313
314 pub fn height(&self) -> u32 {
316 self.height
317 }
318
319 pub fn to_string_trimmed(&self) -> String {
324 let mut lines = Vec::with_capacity(self.height as usize);
325 for y in 0..self.height {
326 lines.push(self.line(y));
327 }
328 while lines.last().is_some_and(|l| l.is_empty()) {
329 lines.pop();
330 }
331 lines.join("\n")
332 }
333}
334
335impl std::fmt::Display for TestBackend {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 write!(f, "{}", self.to_string_trimmed())
338 }
339}