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