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