textual_rs/testing/
mod.rs1pub mod assertions;
4pub mod pilot;
5
6use ratatui::backend::TestBackend;
7use ratatui::Terminal;
8
9use crate::app::App;
10use crate::event::AppEvent;
11use crate::widget::context::AppContext;
12use crate::widget::Widget;
13
14pub use pilot::Pilot;
15
16pub struct TestApp {
27 pub(crate) app: App,
28 pub(crate) terminal: Terminal<TestBackend>,
29 #[allow(dead_code)]
31 pub(crate) tx: flume::Sender<AppEvent>,
32 pub(crate) rx: flume::Receiver<AppEvent>,
33}
34
35impl TestApp {
36 pub fn new(cols: u16, rows: u16, factory: impl FnOnce() -> Box<dyn Widget> + 'static) -> Self {
41 let _ = any_spawner::Executor::init_tokio();
43
44 let mut app = App::new_bare(factory);
45 app.set_skip_animations(true);
47
48 let (tx, rx) = flume::unbounded::<AppEvent>();
49 app.set_event_tx(tx.clone());
50
51 let backend = TestBackend::new(cols, rows);
52 let mut terminal = Terminal::new(backend).expect("failed to create TestBackend terminal");
53
54 app.mount_root_screen();
56 app.render_to_terminal(&mut terminal)
57 .expect("failed initial render");
58
59 TestApp {
60 app,
61 terminal,
62 tx,
63 rx,
64 }
65 }
66
67 pub fn new_styled(
69 cols: u16,
70 rows: u16,
71 css: &str,
72 factory: impl FnOnce() -> Box<dyn Widget> + 'static,
73 ) -> Self {
74 let _ = any_spawner::Executor::init_tokio();
75 let mut app = App::new(factory).with_css(css);
76 app.set_skip_animations(true);
77 let (tx, rx) = flume::unbounded::<AppEvent>();
78 app.set_event_tx(tx.clone());
79 let backend = TestBackend::new(cols, rows);
80 let mut terminal = Terminal::new(backend).expect("failed to create TestBackend terminal");
81 app.mount_root_screen();
82 app.render_to_terminal(&mut terminal)
83 .expect("failed initial render");
84 TestApp {
85 app,
86 terminal,
87 tx,
88 rx,
89 }
90 }
91
92 pub fn pilot(&mut self) -> Pilot<'_> {
94 Pilot::new(self)
95 }
96
97 pub fn ctx(&self) -> &AppContext {
99 self.app.ctx()
100 }
101
102 pub fn buffer(&self) -> &ratatui::buffer::Buffer {
104 self.terminal.backend().buffer()
105 }
106
107 pub fn backend(&self) -> &TestBackend {
109 self.terminal.backend()
110 }
111
112 pub fn inject_key_event(&mut self, key: crossterm::event::KeyEvent) {
117 self.app.handle_key_event(key);
118 }
119
120 pub fn drain_messages(&self) {
122 self.app.drain_message_queue();
123 }
124
125 pub fn process_event(&mut self, event: AppEvent) {
129 match &event {
130 AppEvent::Key(k) if k.kind == crossterm::event::KeyEventKind::Press => {
131 let k = *k;
132 self.app.handle_key_event(k);
133 }
134 AppEvent::Mouse(m) => {
135 let m = *m;
136 self.app.handle_mouse_event(m);
137 }
138 AppEvent::RenderRequest | AppEvent::Resize(_, _) => {
139 }
141 _ => {}
142 }
143 self.app.drain_message_queue();
144 self.app.process_deferred_screens();
145 self.app
146 .render_to_terminal(&mut self.terminal)
147 .expect("failed to render in process_event");
148 }
149}