Skip to main content

WidgetHarness

Struct WidgetHarness 

Source
pub struct WidgetHarness<Id> { /* private fields */ }
Expand description

Drives a bare Interaction<Id> against a Headless backend: queues synthetic input, steps a frame through a caller-supplied draw closure, and reads back the rendered view.

The sibling of retroglyph_core::testing::TestHarness for testing a widget (or a bare Interaction<Id>) directly, without wrapping it in an App first. Where TestHarness hands each frame to App::update, WidgetHarness hands each frame a Ui<'_, '_, Id> built from its own Interaction<Id> and Headless surface, and leaves whatever state a widget needs (a MenuState, a selection index, …) owned by the caller’s closure instead of an App impl.

§Frame timing differs from TestHarness

TestHarness::step drains at most one queued Event per call, because it’s reproducing how a real App sees input trickle in one Backend push at a time. WidgetHarness::step feeds Interaction::handle_event directly, with no Backend queue in between, so nothing forces that one-event-per-frame pacing: step drains its entire queue in one call, between begin_frame and the draw closure – Interaction’s own docs call this “the realistic call pattern” (see Interaction’s press_and_release_in_separate_frames_still_resolves_a_click test), and it’s load-bearing for Tab: FocusRing’s registration order only finalizes inside begin_frame, so a Tab fed before it would still cycle through the previous frame’s stale order. One consequence: a pointer gesture (click, hover) fed to step resolves on the following step call, not the one that fed it, since the flags handle_event sets aren’t read until the next begin_frame; a key press that moves focus (Tab) or a live-state field like wants_pointer update immediately, within the same step. A gesture that needs to be observed mid-flight (a drag, a hover that switches mid-scroll) still needs its own mouse_move/step pair per intermediate position, exactly like driving Interaction by hand.

§Examples

use retroglyph_core::grid::Rect;
use retroglyph_ui::testing::WidgetHarness;
use retroglyph_ui::widget::{Button, InteractiveWidget};

#[derive(Clone, Copy, PartialEq, Eq)]
enum Id {
    Save,
}

let mut harness = WidgetHarness::<Id>::new(10, 3);
let area = Rect::new(0, 0, 7, 1);
let button = Button::new("Save");

// Frame 1: register the button's hit rect (nothing queued yet).
harness.step(|ui| ui.show(area, Id::Save, &button));

harness.click(2, 0);
// Frame 2: feeds the click; the flags it sets aren't read until the next `begin_frame`.
harness.step(|ui| ui.show(area, Id::Save, &button));
// Frame 3: resolves it.
let response = harness.step(|ui| ui.show(area, Id::Save, &button));
assert!(response.clicked());

Implementations§

Source§

impl<Id> WidgetHarness<Id>

Source

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

Creates a harness with a width x height Headless backend and a fresh Interaction<Id>.

Source

pub fn push_event(&mut self, event: Event)

Queues a synthetic event for the next step call.

Only queues: nothing sees 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.

Both events are drained by the same step call, but (see “Frame timing” on WidgetHarness) that only sets the underlying flags: the click itself resolves on the step after that one, once its begin_frame reads them.

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 const fn interaction(&self) -> &Interaction<Id>

The underlying Interaction<Id>, for anything not wrapped directly (focus queries, Interaction::hovered).

Source

pub fn view(&self) -> String

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

Source

pub fn readable_view(&self) -> String

view, with Headless::SPACE_GLYPH converted back to a literal space.

See retroglyph_core::testing::TestHarness::readable_view for why this exists: view renders a blank cell as · so layout is visible in text diffs, which defeats an ordinary plain-text assertion like view.contains("Save").

Source§

impl<Id: Copy + PartialEq> WidgetHarness<Id>

Source

pub fn step<R>(&mut self, f: impl FnOnce(&mut Ui<'_, '_, Id>) -> R) -> R

Runs exactly one frame: drains every currently queued event into Interaction::handle_event, then runs f inside Interaction::begin_frame/end_frame and presents.

See “Frame timing” on WidgetHarness for why the whole queue drains at once rather than one event per call.

Auto Trait Implementations§

§

impl<Id> Freeze for WidgetHarness<Id>
where Id: Freeze,

§

impl<Id> RefUnwindSafe for WidgetHarness<Id>
where Id: RefUnwindSafe,

§

impl<Id> Send for WidgetHarness<Id>
where Id: Send,

§

impl<Id> Sync for WidgetHarness<Id>
where Id: Sync,

§

impl<Id> Unpin for WidgetHarness<Id>
where Id: Unpin,

§

impl<Id> UnsafeUnpin for WidgetHarness<Id>
where Id: UnsafeUnpin,

§

impl<Id> UnwindSafe for WidgetHarness<Id>
where Id: UnwindSafe,

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.