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 ctx.render_notifications();
184 ctx.emit_pending_tooltips();
185 let mut tree = layout::build_tree(&ctx.commands);
186 self.hook_states = ctx.hook_states;
187 let mut deferred = ctx.deferred_draws;
188 let area = Rect::new(0, 0, self.width, self.height);
189 layout::compute(&mut tree, area);
190 self.buffer.reset();
191 layout::render(&tree, &mut self.buffer);
192 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
193 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
194 self.buffer.push_clip(rect);
195 cb(&mut self.buffer, rect);
196 self.buffer.pop_clip();
197 }
198 }
199 }
200
201 pub fn render_with_events(
203 &mut self,
204 events: Vec<Event>,
205 focus_index: usize,
206 prev_focus_count: usize,
207 f: impl FnOnce(&mut Context),
208 ) {
209 let mut frame_state = FrameState {
210 hook_states: std::mem::take(&mut self.hook_states),
211 focus_index,
212 prev_focus_count,
213 ..FrameState::default()
214 };
215 let mut ctx = Context::new(
216 events,
217 self.width,
218 self.height,
219 &mut frame_state,
220 Theme::dark(),
221 );
222 ctx.process_focus_keys();
223 f(&mut ctx);
224 ctx.render_notifications();
225 ctx.emit_pending_tooltips();
226 let mut tree = layout::build_tree(&ctx.commands);
227 self.hook_states = ctx.hook_states;
228 let mut deferred = ctx.deferred_draws;
229 let area = Rect::new(0, 0, self.width, self.height);
230 layout::compute(&mut tree, area);
231 self.buffer.reset();
232 layout::render(&tree, &mut self.buffer);
233 for (draw_id, rect) in layout::collect_raw_draw_rects(&tree) {
234 if let Some(cb) = deferred.get_mut(draw_id).and_then(|c| c.take()) {
235 self.buffer.push_clip(rect);
236 cb(&mut self.buffer, rect);
237 self.buffer.pop_clip();
238 }
239 }
240 }
241
242 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
244 self.render_with_events(events, 0, 0, f);
245 }
246
247 pub fn line(&self, y: u32) -> String {
249 let mut s = String::new();
250 for x in 0..self.width {
251 s.push_str(&self.buffer.get(x, y).symbol);
252 }
253 s.trim_end().to_string()
254 }
255
256 pub fn assert_line(&self, y: u32, expected: &str) {
258 let line = self.line(y);
259 assert_eq!(
260 line, expected,
261 "Line {y}: expected {expected:?}, got {line:?}"
262 );
263 }
264
265 pub fn assert_line_contains(&self, y: u32, expected: &str) {
267 let line = self.line(y);
268 assert!(
269 line.contains(expected),
270 "Line {y}: expected to contain {expected:?}, got {line:?}"
271 );
272 }
273
274 pub fn assert_contains(&self, expected: &str) {
276 for y in 0..self.height {
277 if self.line(y).contains(expected) {
278 return;
279 }
280 }
281 let mut all_lines = String::new();
282 for y in 0..self.height {
283 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
284 }
285 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
286 }
287
288 pub fn buffer(&self) -> &Buffer {
290 &self.buffer
291 }
292
293 pub fn width(&self) -> u32 {
295 self.width
296 }
297
298 pub fn height(&self) -> u32 {
300 self.height
301 }
302
303 pub fn to_string_trimmed(&self) -> String {
308 let mut lines = Vec::with_capacity(self.height as usize);
309 for y in 0..self.height {
310 lines.push(self.line(y));
311 }
312 while lines.last().is_some_and(|l| l.is_empty()) {
313 lines.pop();
314 }
315 lines.join("\n")
316 }
317}
318
319impl std::fmt::Display for TestBackend {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 write!(f, "{}", self.to_string_trimmed())
322 }
323}