|
1 | 1 | use crate::{
|
2 | 2 | component::{Component, ComponentHelper, ComponentHelperExt},
|
| 3 | + mock_terminal_render_loop, |
3 | 4 | props::AnyProps,
|
4 |
| - render, terminal_render_loop, Canvas, Terminal, |
| 5 | + render, terminal_render_loop, Canvas, MockTerminalConfig, Terminal, |
5 | 6 | };
|
6 | 7 | use any_key::AnyHash;
|
7 | 8 | use crossterm::{terminal, tty::IsTty};
|
| 9 | +use futures::Stream; |
8 | 10 | use std::{
|
9 | 11 | fmt::Debug,
|
10 | 12 | future::Future,
|
@@ -198,6 +200,63 @@ pub trait ElementExt: private::Sealed + Sized {
|
198 | 200 | /// is a TTY with [`stdout_is_tty`](crate::stdout_is_tty).
|
199 | 201 | fn render_loop(&mut self) -> impl Future<Output = io::Result<()>>;
|
200 | 202 |
|
| 203 | + /// Renders the element in a loop using a mock terminal, allowing you to simulate terminal |
| 204 | + /// events for testing purposes. |
| 205 | + /// |
| 206 | + /// A stream of canvases is returned, allowing you to inspect the output of each render pass. |
| 207 | + /// |
| 208 | + /// # Example |
| 209 | + /// |
| 210 | + /// ``` |
| 211 | + /// # use iocraft::prelude::*; |
| 212 | + /// # use futures::stream::StreamExt; |
| 213 | + /// # #[component] |
| 214 | + /// # fn MyTextInput() -> impl Into<AnyElement<'static>> { |
| 215 | + /// # element!(Box) |
| 216 | + /// # } |
| 217 | + /// async fn test_text_input() { |
| 218 | + /// let actual = element!(MyTextInput) |
| 219 | + /// .mock_terminal_render_loop(MockTerminalConfig::with_events(futures::stream::iter( |
| 220 | + /// vec![ |
| 221 | + /// TerminalEvent::Key(KeyEvent { |
| 222 | + /// code: KeyCode::Char('f'), |
| 223 | + /// modifiers: KeyModifiers::empty(), |
| 224 | + /// kind: KeyEventKind::Press, |
| 225 | + /// }), |
| 226 | + /// TerminalEvent::Key(KeyEvent { |
| 227 | + /// code: KeyCode::Char('f'), |
| 228 | + /// modifiers: KeyModifiers::empty(), |
| 229 | + /// kind: KeyEventKind::Release, |
| 230 | + /// }), |
| 231 | + /// TerminalEvent::Key(KeyEvent { |
| 232 | + /// code: KeyCode::Char('o'), |
| 233 | + /// modifiers: KeyModifiers::empty(), |
| 234 | + /// kind: KeyEventKind::Press, |
| 235 | + /// }), |
| 236 | + /// TerminalEvent::Key(KeyEvent { |
| 237 | + /// code: KeyCode::Char('o'), |
| 238 | + /// modifiers: KeyModifiers::empty(), |
| 239 | + /// kind: KeyEventKind::Repeat, |
| 240 | + /// }), |
| 241 | + /// TerminalEvent::Key(KeyEvent { |
| 242 | + /// code: KeyCode::Char('o'), |
| 243 | + /// modifiers: KeyModifiers::empty(), |
| 244 | + /// kind: KeyEventKind::Release, |
| 245 | + /// }), |
| 246 | + /// ], |
| 247 | + /// ))) |
| 248 | + /// .map(|c| c.to_string()) |
| 249 | + /// .collect::<Vec<_>>() |
| 250 | + /// .await; |
| 251 | + /// let expected = vec!["\n", "foo\n"]; |
| 252 | + /// assert_eq!(actual, expected); |
| 253 | + /// } |
| 254 | + /// ``` |
| 255 | + fn mock_terminal_render_loop( |
| 256 | + &mut self, |
| 257 | + config: MockTerminalConfig, |
| 258 | + ) -> impl Stream<Item = Canvas>; |
| 259 | + |
201 | 260 | /// Renders the element as fullscreen in a loop, allowing it to be dynamic and interactive.
|
202 | 261 | ///
|
203 | 262 | /// This method should only be used if when stdio is a TTY terminal. If for example, stdout is
|
@@ -228,6 +287,13 @@ impl<'a> ElementExt for AnyElement<'a> {
|
228 | 287 | terminal_render_loop(self, Terminal::new()?).await
|
229 | 288 | }
|
230 | 289 |
|
| 290 | + fn mock_terminal_render_loop( |
| 291 | + &mut self, |
| 292 | + config: MockTerminalConfig, |
| 293 | + ) -> impl Stream<Item = Canvas> { |
| 294 | + mock_terminal_render_loop(self, config) |
| 295 | + } |
| 296 | + |
231 | 297 | async fn fullscreen(&mut self) -> io::Result<()> {
|
232 | 298 | terminal_render_loop(self, Terminal::fullscreen()?).await
|
233 | 299 | }
|
@@ -255,6 +321,13 @@ impl<'a> ElementExt for &mut AnyElement<'a> {
|
255 | 321 | terminal_render_loop(&mut **self, Terminal::new()?).await
|
256 | 322 | }
|
257 | 323 |
|
| 324 | + fn mock_terminal_render_loop( |
| 325 | + &mut self, |
| 326 | + config: MockTerminalConfig, |
| 327 | + ) -> impl Stream<Item = Canvas> { |
| 328 | + mock_terminal_render_loop(&mut **self, config) |
| 329 | + } |
| 330 | + |
258 | 331 | async fn fullscreen(&mut self) -> io::Result<()> {
|
259 | 332 | terminal_render_loop(&mut **self, Terminal::fullscreen()?).await
|
260 | 333 | }
|
@@ -285,6 +358,12 @@ where
|
285 | 358 | terminal_render_loop(self, Terminal::new()?).await
|
286 | 359 | }
|
287 | 360 |
|
| 361 | + fn mock_terminal_render_loop( |
| 362 | + &mut self, |
| 363 | + config: MockTerminalConfig, |
| 364 | + ) -> impl Stream<Item = Canvas> { |
| 365 | + mock_terminal_render_loop(self, config) |
| 366 | + } |
288 | 367 | async fn fullscreen(&mut self) -> io::Result<()> {
|
289 | 368 | terminal_render_loop(self, Terminal::fullscreen()?).await
|
290 | 369 | }
|
@@ -315,6 +394,13 @@ where
|
315 | 394 | terminal_render_loop(&mut **self, Terminal::new()?).await
|
316 | 395 | }
|
317 | 396 |
|
| 397 | + fn mock_terminal_render_loop( |
| 398 | + &mut self, |
| 399 | + config: MockTerminalConfig, |
| 400 | + ) -> impl Stream<Item = Canvas> { |
| 401 | + mock_terminal_render_loop(&mut **self, config) |
| 402 | + } |
| 403 | + |
318 | 404 | async fn fullscreen(&mut self) -> io::Result<()> {
|
319 | 405 | terminal_render_loop(&mut **self, Terminal::fullscreen()?).await
|
320 | 406 | }
|
|
0 commit comments