1use crate::buffer::Buffer;
2use crate::context::Context;
3use crate::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseKind};
4use crate::layout;
5use crate::rect::Rect;
6use crate::style::Theme;
7
8pub struct EventBuilder {
9 events: Vec<Event>,
10}
11
12impl EventBuilder {
13 pub fn new() -> Self {
14 Self { events: Vec::new() }
15 }
16
17 pub fn key(mut self, c: char) -> Self {
18 self.events.push(Event::Key(KeyEvent {
19 code: KeyCode::Char(c),
20 modifiers: KeyModifiers::NONE,
21 }));
22 self
23 }
24
25 pub fn key_code(mut self, code: KeyCode) -> Self {
26 self.events.push(Event::Key(KeyEvent {
27 code,
28 modifiers: KeyModifiers::NONE,
29 }));
30 self
31 }
32
33 pub fn key_with(mut self, code: KeyCode, modifiers: KeyModifiers) -> Self {
34 self.events.push(Event::Key(KeyEvent { code, modifiers }));
35 self
36 }
37
38 pub fn click(mut self, x: u32, y: u32) -> Self {
39 self.events.push(Event::Mouse(MouseEvent {
40 kind: MouseKind::Down(MouseButton::Left),
41 x,
42 y,
43 modifiers: KeyModifiers::NONE,
44 }));
45 self
46 }
47
48 pub fn scroll_up(mut self, x: u32, y: u32) -> Self {
49 self.events.push(Event::Mouse(MouseEvent {
50 kind: MouseKind::ScrollUp,
51 x,
52 y,
53 modifiers: KeyModifiers::NONE,
54 }));
55 self
56 }
57
58 pub fn scroll_down(mut self, x: u32, y: u32) -> Self {
59 self.events.push(Event::Mouse(MouseEvent {
60 kind: MouseKind::ScrollDown,
61 x,
62 y,
63 modifiers: KeyModifiers::NONE,
64 }));
65 self
66 }
67
68 pub fn paste(mut self, text: impl Into<String>) -> Self {
69 self.events.push(Event::Paste(text.into()));
70 self
71 }
72
73 pub fn resize(mut self, width: u32, height: u32) -> Self {
74 self.events.push(Event::Resize(width, height));
75 self
76 }
77
78 pub fn build(self) -> Vec<Event> {
79 self.events
80 }
81}
82
83impl Default for EventBuilder {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89pub struct TestBackend {
90 buffer: Buffer,
91 width: u32,
92 height: u32,
93}
94
95impl TestBackend {
96 pub fn new(width: u32, height: u32) -> Self {
97 let area = Rect::new(0, 0, width, height);
98 Self {
99 buffer: Buffer::empty(area),
100 width,
101 height,
102 }
103 }
104
105 pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
107 let mut ctx = Context::new(
108 Vec::new(),
109 self.width,
110 self.height,
111 0,
112 0,
113 0,
114 Vec::new(),
115 Vec::new(),
116 Vec::new(),
117 false,
118 Theme::dark(),
119 None,
120 false,
121 );
122 f(&mut ctx);
123 let mut tree = layout::build_tree(&ctx.commands);
124 let area = Rect::new(0, 0, self.width, self.height);
125 layout::compute(&mut tree, area);
126 self.buffer.reset();
127 layout::render(&tree, &mut self.buffer);
128 }
129
130 pub fn render_with_events(
132 &mut self,
133 events: Vec<Event>,
134 focus_index: usize,
135 prev_focus_count: usize,
136 f: impl FnOnce(&mut Context),
137 ) {
138 let mut ctx = Context::new(
139 events,
140 self.width,
141 self.height,
142 0,
143 focus_index,
144 prev_focus_count,
145 Vec::new(),
146 Vec::new(),
147 Vec::new(),
148 false,
149 Theme::dark(),
150 None,
151 false,
152 );
153 ctx.process_focus_keys();
154 f(&mut ctx);
155 let mut tree = layout::build_tree(&ctx.commands);
156 let area = Rect::new(0, 0, self.width, self.height);
157 layout::compute(&mut tree, area);
158 self.buffer.reset();
159 layout::render(&tree, &mut self.buffer);
160 }
161
162 pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
163 self.render_with_events(events, 0, 0, f);
164 }
165
166 pub fn line(&self, y: u32) -> String {
168 let mut s = String::new();
169 for x in 0..self.width {
170 s.push_str(&self.buffer.get(x, y).symbol);
171 }
172 s.trim_end().to_string()
173 }
174
175 pub fn assert_line(&self, y: u32, expected: &str) {
177 let line = self.line(y);
178 assert_eq!(
179 line, expected,
180 "Line {y}: expected {expected:?}, got {line:?}"
181 );
182 }
183
184 pub fn assert_line_contains(&self, y: u32, expected: &str) {
186 let line = self.line(y);
187 assert!(
188 line.contains(expected),
189 "Line {y}: expected to contain {expected:?}, got {line:?}"
190 );
191 }
192
193 pub fn assert_contains(&self, expected: &str) {
195 for y in 0..self.height {
196 if self.line(y).contains(expected) {
197 return;
198 }
199 }
200 let mut all_lines = String::new();
201 for y in 0..self.height {
202 all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
203 }
204 panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
205 }
206
207 pub fn buffer(&self) -> &Buffer {
208 &self.buffer
209 }
210
211 pub fn width(&self) -> u32 {
212 self.width
213 }
214
215 pub fn height(&self) -> u32 {
216 self.height
217 }
218
219 pub fn to_string_trimmed(&self) -> String {
224 let mut lines = Vec::with_capacity(self.height as usize);
225 for y in 0..self.height {
226 lines.push(self.line(y));
227 }
228 while lines.last().is_some_and(|l| l.is_empty()) {
229 lines.pop();
230 }
231 lines.join("\n")
232 }
233}
234
235impl std::fmt::Display for TestBackend {
236 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237 write!(f, "{}", self.to_string_trimmed())
238 }
239}