use crate::buffer::Buffer;
use crate::context::Context;
use crate::event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseKind,
};
use crate::rect::Rect;
use crate::{run_frame_kernel, FrameState, RunConfig};
pub struct EventBuilder {
events: Vec<Event>,
}
impl EventBuilder {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn key(mut self, c: char) -> Self {
self.events.push(Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
}));
self
}
pub fn key_code(mut self, code: KeyCode) -> Self {
self.events.push(Event::Key(KeyEvent {
code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
}));
self
}
pub fn key_with(mut self, code: KeyCode, modifiers: KeyModifiers) -> Self {
self.events.push(Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
}));
self
}
pub fn click(mut self, x: u32, y: u32) -> Self {
self.events.push(Event::Mouse(MouseEvent {
kind: MouseKind::Down(MouseButton::Left),
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
}));
self
}
pub fn scroll_up(mut self, x: u32, y: u32) -> Self {
self.events.push(Event::Mouse(MouseEvent {
kind: MouseKind::ScrollUp,
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
}));
self
}
pub fn scroll_down(mut self, x: u32, y: u32) -> Self {
self.events.push(Event::Mouse(MouseEvent {
kind: MouseKind::ScrollDown,
x,
y,
modifiers: KeyModifiers::NONE,
pixel_x: None,
pixel_y: None,
}));
self
}
pub fn paste(mut self, text: impl Into<String>) -> Self {
self.events.push(Event::Paste(text.into()));
self
}
pub fn resize(mut self, width: u32, height: u32) -> Self {
self.events.push(Event::Resize(width, height));
self
}
pub fn build(self) -> Vec<Event> {
self.events
}
}
impl Default for EventBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct TestBackend {
buffer: Buffer,
width: u32,
height: u32,
frame_state: FrameState,
}
impl TestBackend {
pub fn new(width: u32, height: u32) -> Self {
let area = Rect::new(0, 0, width, height);
Self {
buffer: Buffer::empty(area),
width,
height,
frame_state: FrameState::default(),
}
}
fn render_frame(
&mut self,
events: Vec<Event>,
setup_state: impl FnOnce(&mut FrameState),
f: impl FnOnce(&mut Context),
) {
setup_state(&mut self.frame_state);
self.buffer.reset();
let mut once = Some(f);
let mut render = |ui: &mut Context| {
if let Some(f) = once.take() {
f(ui);
} else {
panic!("render closure called twice");
}
};
let _ = run_frame_kernel(
&mut self.buffer,
&mut self.frame_state,
&RunConfig::default(),
(self.width, self.height),
events,
false,
&mut render,
);
}
pub fn render(&mut self, f: impl FnOnce(&mut Context)) {
self.render_frame(Vec::new(), |_| {}, f);
}
pub fn render_with_events(
&mut self,
events: Vec<Event>,
focus_index: usize,
prev_focus_count: usize,
f: impl FnOnce(&mut Context),
) {
self.render_frame(
events,
|state| {
state.focus.focus_index = focus_index;
state.focus.prev_focus_count = prev_focus_count;
},
f,
);
}
pub fn run_with_events(&mut self, events: Vec<Event>, f: impl FnOnce(&mut crate::Context)) {
self.render_with_events(events, 0, 0, f);
}
pub fn line(&self, y: u32) -> String {
let mut s = String::new();
for x in 0..self.width {
s.push_str(&self.buffer.get(x, y).symbol);
}
s.trim_end().to_string()
}
pub fn assert_line(&self, y: u32, expected: &str) {
let line = self.line(y);
assert_eq!(
line, expected,
"Line {y}: expected {expected:?}, got {line:?}"
);
}
pub fn assert_line_contains(&self, y: u32, expected: &str) {
let line = self.line(y);
assert!(
line.contains(expected),
"Line {y}: expected to contain {expected:?}, got {line:?}"
);
}
pub fn assert_contains(&self, expected: &str) {
for y in 0..self.height {
if self.line(y).contains(expected) {
return;
}
}
let mut all_lines = String::new();
for y in 0..self.height {
all_lines.push_str(&format!("{}: {}\n", y, self.line(y)));
}
panic!("Buffer does not contain {expected:?}.\nBuffer:\n{all_lines}");
}
pub fn buffer(&self) -> &Buffer {
&self.buffer
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn to_string_trimmed(&self) -> String {
let mut lines = Vec::with_capacity(self.height as usize);
for y in 0..self.height {
lines.push(self.line(y));
}
while lines.last().is_some_and(|l| l.is_empty()) {
lines.pop();
}
lines.join("\n")
}
}
impl std::fmt::Display for TestBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string_trimmed())
}
}