Skip to main content

TestHarness

Struct TestHarness 

Source
pub struct TestHarness { /* private fields */ }
Available on crate feature 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-uiInteraction) 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

Source

pub fn new(width: u16, height: u16) -> Self

Creates a harness with a width x height Headless backend.

Source

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.

Source

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.

Source

pub fn click_button(&mut self, x: u16, y: u16, button: MouseButton)

Queues a click (press then release) with button at (x, y), with no modifiers.

Source

pub fn mouse_move(&mut self, x: u16, y: u16)

Queues a pointer move to (x, y), with no buttons held.

Source

pub fn key(&mut self, code: KeyCode)

Queues a key press of code, with no modifiers.

Source

pub fn key_with(&mut self, code: KeyCode, modifiers: KeyModifiers)

Queues a key press of code with modifiers.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn run_steps<A: App<Headless>>(&mut self, app: &mut A, steps: u32)

Runs a fixed number of frames, regardless of queue state or the Flow each one returns.

For tests asserting on the app still running after N frames (e.g. an idle animation) rather than on input settling; run/settle cover the input-resolution case.

Source

pub fn view(&self) -> String

The rendered view as of the last step call (see Headless::format_view).

Source

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).

Source

pub const fn term_mut(&mut self) -> &mut Terminal<Headless>

The underlying Terminal, mutably.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.