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 area = Rect::new(0, 0, self.width, self.height);
193 layout::compute(&mut tree, area);
194 self.buffer.reset();
195 layout::render(&tree, &mut self.buffer);
196 }
197
198 pub fn render_with_events(
200 &mut self,
201 events: Vec<Event>,
202 focus_index: usize,
203 prev_focus_count: usize,
204 f: impl FnOnce(&mut Context),
205 ) {
206 let mut ctx = Context::new(
207 events,
208 self.width,
209 self.height,
210 0,
211 focus_index,
212 prev_focus_count,
213 Vec::new(),
214 Vec::new(),
215 Vec::new(),
216 Vec::new(),
217 Vec::new(),
218 Vec::new(),
219 std::mem::take(&mut self.hook_states),
220 false,
221 Theme::dark(),
222 None,
223 false,
224 );
225 ctx.process_focus_keys();
226 f(&mut ctx);
227 let mut tree = layout::build_tree(&ctx.commands);
228 self.hook_states = ctx.hook_states;
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 }
234
235 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
237 self.render_with_events(events, 0, 0, f);
238 }
239
240 pub fn line(&self, y: u32) -> String {
242 let mut s = String::new();
243 for x in 0..self.width {
244 s.push_str(&self.buffer.get(x, y).symbol);
245 }
246 s.trim_end().to_string()
247 }
248
249 pub fn assert_line(&self, y: u32, expected: &str) {
251 let line = self.line(y);
252 assert_eq!(
253 line, expected,
254 "Line {y}: expected {expected:?}, got {line:?}"
255 );
256 }
257
258 pub fn assert_line_contains(&self, y: u32, expected: &str) {
260 let line = self.line(y);
261 assert!(
262 line.contains(expected),
263 "Line {y}: expected to contain {expected:?}, got {line:?}"
264 );
265 }
266
267 pub fn assert_contains(&self, expected: &str) {
269 for y in 0..self.height {
270 if self.line(y).contains(expected) {
271 return;
272 }
273 }
274 let mut all_lines = String::new();
275 for y in 0..self.height {
276 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
277 }
278 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
279 }
280
281 pub fn buffer(&self) -> &Buffer {
283 &self.buffer
284 }
285
286 pub fn width(&self) -> u32 {
288 self.width
289 }
290
291 pub fn height(&self) -> u32 {
293 self.height
294 }
295
296 pub fn to_string_trimmed(&self) -> String {
301 let mut lines = Vec::with_capacity(self.height as usize);
302 for y in 0..self.height {
303 lines.push(self.line(y));
304 }
305 while lines.last().is_some_and(|l| l.is_empty()) {
306 lines.pop();
307 }
308 lines.join("\n")
309 }
310}
311
312impl std::fmt::Display for TestBackend {
313 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314 write!(f, "{}", self.to_string_trimmed())
315 }
316}