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 }));
83 self
84 }
85
86 pub fn scroll_up(mut self, x: u32, y: u32) -> Self {
88 self.events.push(Event::Mouse(MouseEvent {
89 kind: MouseKind::ScrollUp,
90 x,
91 y,
92 modifiers: KeyModifiers::NONE,
93 }));
94 self
95 }
96
97 pub fn scroll_down(mut self, x: u32, y: u32) -> Self {
99 self.events.push(Event::Mouse(MouseEvent {
100 kind: MouseKind::ScrollDown,
101 x,
102 y,
103 modifiers: KeyModifiers::NONE,
104 }));
105 self
106 }
107
108 pub fn paste(mut self, text: impl Into<String>) -> Self {
110 self.events.push(Event::Paste(text.into()));
111 self
112 }
113
114 pub fn resize(mut self, width: u32, height: u32) -> Self {
116 self.events.push(Event::Resize(width, height));
117 self
118 }
119
120 pub fn build(self) -> Vec<Event> {
122 self.events
123 }
124}
125
126impl Default for EventBuilder {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132pub struct TestBackend {
151 buffer: Buffer,
152 width: u32,
153 height: u32,
154 hook_states: Vec<Box<dyn std::any::Any>>,
155}
156
157impl TestBackend {
158 pub fn new(width: u32, height: u32) -> Self {
160 let area = Rect::new(0, 0, width, height);
161 Self {
162 buffer: Buffer::empty(area),
163 width,
164 height,
165 hook_states: Vec::new(),
166 }
167 }
168
169 pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
171 let mut frame_state = FrameState {
172 hook_states: std::mem::take(&mut self.hook_states),
173 ..FrameState::default()
174 };
175 let mut ctx = Context::new(
176 Vec::new(),
177 self.width,
178 self.height,
179 &mut frame_state,
180 Theme::dark(),
181 );
182 f(&mut ctx);
183 let mut tree = layout::build_tree(&ctx.commands);
184 self.hook_states = ctx.hook_states;
185 let mut deferred = ctx.deferred_draws;
186 let area = Rect::new(0, 0, self.width, self.height);
187 layout::compute(&mut tree, area);
188 self.buffer.reset();
189 layout::render(&tree, &mut self.buffer);
190 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
191 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
192 self.buffer.push_clip(rect);
193 cb(&mut self.buffer, rect);
194 self.buffer.pop_clip();
195 }
196 }
197 }
198
199 pub fn render_with_events(
201 &mut self,
202 events: Vec<Event>,
203 focus_index: usize,
204 prev_focus_count: usize,
205 f: impl FnOnce(&mut Context),
206 ) {
207 let mut frame_state = FrameState {
208 hook_states: std::mem::take(&mut self.hook_states),
209 focus_index,
210 prev_focus_count,
211 ..FrameState::default()
212 };
213 let mut ctx = Context::new(
214 events,
215 self.width,
216 self.height,
217 &mut frame_state,
218 Theme::dark(),
219 );
220 ctx.process_focus_keys();
221 f(&mut ctx);
222 let mut tree = layout::build_tree(&ctx.commands);
223 self.hook_states = ctx.hook_states;
224 let mut deferred = ctx.deferred_draws;
225 let area = Rect::new(0, 0, self.width, self.height);
226 layout::compute(&mut tree, area);
227 self.buffer.reset();
228 layout::render(&tree, &mut self.buffer);
229 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
230 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
231 self.buffer.push_clip(rect);
232 cb(&mut self.buffer, rect);
233 self.buffer.pop_clip();
234 }
235 }
236 }
237
238 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
240 self.render_with_events(events, 0, 0, f);
241 }
242
243 pub fn line(&self, y: u32) -> String {
245 let mut s = String::new();
246 for x in 0..self.width {
247 s.push_str(&self.buffer.get(x, y).symbol);
248 }
249 s.trim_end().to_string()
250 }
251
252 pub fn assert_line(&self, y: u32, expected: &str) {
254 let line = self.line(y);
255 assert_eq!(
256 line, expected,
257 "Line {y}: expected {expected:?}, got {line:?}"
258 );
259 }
260
261 pub fn assert_line_contains(&self, y: u32, expected: &str) {
263 let line = self.line(y);
264 assert!(
265 line.contains(expected),
266 "Line {y}: expected to contain {expected:?}, got {line:?}"
267 );
268 }
269
270 pub fn assert_contains(&self, expected: &str) {
272 for y in 0..self.height {
273 if self.line(y).contains(expected) {
274 return;
275 }
276 }
277 let mut all_lines = String::new();
278 for y in 0..self.height {
279 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
280 }
281 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
282 }
283
284 pub fn buffer(&self) -> &Buffer {
286 &self.buffer
287 }
288
289 pub fn width(&self) -> u32 {
291 self.width
292 }
293
294 pub fn height(&self) -> u32 {
296 self.height
297 }
298
299 pub fn to_string_trimmed(&self) -> String {
304 let mut lines = Vec::with_capacity(self.height as usize);
305 for y in 0..self.height {
306 lines.push(self.line(y));
307 }
308 while lines.last().is_some_and(|l| l.is_empty()) {
309 lines.pop();
310 }
311 lines.join("\n")
312 }
313}
314
315impl std::fmt::Display for TestBackend {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 write!(f, "{}", self.to_string_trimmed())
318 }
319}