pub struct TestHarness { /* private fields */ }testing only.Expand description
Drives an App against a Headless backend: queues synthetic input, steps frames, and
reads back the rendered view.
§The two-frame rule
A press and a release queued together resolve a frame later than the same gesture arriving
from real input, because hit-testing (e.g. retroglyph-ui’ Interaction) snapshots the
previous frame’s pointer state before this frame’s queued events are applied. click queues both events for you, but resolving them still costs two frames: call
run or settle after queuing input, not a single
step.
§Presenting
step presents automatically: skipped on Flow::Idle, skipped as a no-op if
update already presented (mirroring run_blocking’s own
behavior). Nothing queued is visible in view until a step call has run.
§Examples
use retroglyph_core::testing::TestHarness;
use retroglyph_core::app::{App, Flow, Frame};
use retroglyph_core::backend::Backend;
use retroglyph_core::color::Style;
use retroglyph_core::terminal::Terminal;
struct Counter(u32);
impl<B: Backend> App<B> for Counter {
fn update(&mut self, term: &mut Terminal<B>, frame: &Frame) -> Flow {
if term.has_input() {
self.0 += 1;
}
term.surface()
.put((0, 0), char::from_digit(self.0, 10).unwrap_or('?'), Style::default());
if frame.frame > 10 {
Flow::Exit
} else {
Flow::Continue
}
}
}
let mut harness = TestHarness::new(10, 1);
let mut app = Counter(0);
harness.key(retroglyph_core::event::KeyCode::Char(' '));
harness.run(&mut app);
assert_eq!(app.0, 1);
assert!(harness.view().starts_with('1'));Implementations§
Source§impl TestHarness
impl TestHarness
Sourcepub fn new(width: u16, height: u16) -> Self
pub fn new(width: u16, height: u16) -> Self
Creates a harness with a width x height Headless backend.
Sourcepub fn push_event(&mut self, event: Event)
pub fn push_event(&mut self, event: Event)
Queues a synthetic event for the next step call.
Only queues: the app does not see it until a frame runs. Prefer the typed helpers
(click, key, mouse_move) unless the
Event variant needed isn’t one of them.
Sourcepub fn click(&mut self, x: u16, y: u16)
pub fn click(&mut self, x: u16, y: u16)
Queues a left-button click (press then release) at (x, y), with no modifiers.
See “the two-frame rule” on TestHarness before asserting after a single
step: use run/settle instead.
Queues a click (press then release) with button at (x, y), with no modifiers.
Sourcepub fn mouse_move(&mut self, x: u16, y: u16)
pub fn mouse_move(&mut self, x: u16, y: u16)
Queues a pointer move to (x, y), with no buttons held.
Sourcepub fn key_with(&mut self, code: KeyCode, modifiers: KeyModifiers)
pub fn key_with(&mut self, code: KeyCode, modifiers: KeyModifiers)
Queues a key press of code with modifiers.
Sourcepub fn resize(&mut self, width: u16, height: u16)
pub fn resize(&mut self, width: u16, height: u16)
Resizes the backend and queues the matching Event::Resize a real terminal would also
deliver.
Unlike calling term_mut().resize
directly, this also queues the event, matching what a real backend delivers alongside its
own resize.
Sourcepub fn step<A: App<Headless>>(&mut self, app: &mut A) -> Flow
pub fn step<A: App<Headless>>(&mut self, app: &mut A) -> Flow
Runs exactly one frame: pops at most one queued event into the backend, calls
App::update, and presents unless update returned Flow::Idle or already presented.
Draining only one queued event per call, rather than the whole queue at once, is what
reproduces the two-frame rule described on TestHarness instead of masking it.
Sourcepub fn settle<A: App<Headless>>(
&mut self,
app: &mut A,
max_steps: u32,
) -> Result<u32, RunError>
pub fn settle<A: App<Headless>>( &mut self, app: &mut A, max_steps: u32, ) -> Result<u32, RunError>
Runs step until the event queue is empty (with at least one frame run),
stopping early on Flow::Exit, bounded by max_steps.
This is the “run until settled” primitive: queuing input only stages it, settle resolves
the two-frame rule (see TestHarness) instead of requiring two manual step calls per
gesture.
§Errors
Returns RunError::ExceededMaxSteps if the queue is still non-empty after max_steps
steps: an app that never drains its input is a bug in the test or the app, not a case to
loop on forever.
Sourcepub fn run<A: App<Headless>>(&mut self, app: &mut A) -> u32
pub fn run<A: App<Headless>>(&mut self, app: &mut A) -> u32
settle with DEFAULT_MAX_STEPS, panicking instead of returning an
error.
§Panics
Panics if the queue is still non-empty after DEFAULT_MAX_STEPS steps.
Sourcepub fn view(&self) -> String
pub fn view(&self) -> String
The rendered view as of the last step call (see
Headless::format_view).
Sourcepub const fn term(&self) -> &Terminal<Headless>
pub const fn term(&self) -> &Terminal<Headless>
The underlying Terminal, for anything not wrapped directly (cursor position,
Terminal::grid, a manual Terminal::draw outside the App loop).