Skip to content

Commit

Permalink
Tweak TestHarness API and docs (#790)
Browse files Browse the repository at this point in the history
  • Loading branch information
PoignardAzur authored Dec 19, 2024
1 parent ee44114 commit 8e12858
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 58 deletions.
130 changes: 74 additions & 56 deletions masonry/src/testing/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ use crate::tracing_backend::try_init_test_tracing;
use crate::widget::{WidgetMut, WidgetRef};
use crate::{Color, Handled, Point, Size, Vec2, Widget, WidgetId};

/// Default canvas size for tests.
pub const HARNESS_DEFAULT_SIZE: Size = Size::new(400., 400.);

/// Default background color for tests.
pub const HARNESS_DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29);

/// A safe headless environment to test widgets in.
///
/// `TestHarness` is a type that simulates a [`RenderRoot`] for testing.
Expand Down Expand Up @@ -118,6 +112,11 @@ pub struct TestHarness {
title: String,
}

pub struct TestHarnessParams {
pub window_size: Size,
pub background_color: Color,
}

/// Assert a snapshot of a rendered frame of your app.
///
/// This macro takes a test harness and a name, renders the current state of the app,
Expand All @@ -141,33 +140,50 @@ macro_rules! assert_render_snapshot {
};
}

impl TestHarnessParams {
/// Default canvas size for tests.
pub const DEFAULT_SIZE: Size = Size::new(400., 400.);

/// Default background color for tests.
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::rgb8(0x29, 0x29, 0x29);
}

impl Default for TestHarnessParams {
fn default() -> Self {
Self {
window_size: Self::DEFAULT_SIZE,
background_color: Self::DEFAULT_BACKGROUND_COLOR,
}
}
}

impl TestHarness {
/// Builds harness with given root widget.
///
/// Window size will be [`HARNESS_DEFAULT_SIZE`].
/// Background color will be [`HARNESS_DEFAULT_BACKGROUND_COLOR`].
/// Window size will be [`Self::DEFAULT_SIZE`].
/// Background color will be [`Self::DEFAULT_BACKGROUND_COLOR`].
pub fn create(root_widget: impl Widget) -> Self {
Self::create_with(
root_widget,
HARNESS_DEFAULT_SIZE,
HARNESS_DEFAULT_BACKGROUND_COLOR,
)
Self::create_with(root_widget, TestHarnessParams::default())
}

// TODO - Remove
/// Builds harness with given root widget and window size.
pub fn create_with_size(root_widget: impl Widget, window_size: Size) -> Self {
Self::create_with(root_widget, window_size, HARNESS_DEFAULT_BACKGROUND_COLOR)
Self::create_with(
root_widget,
TestHarnessParams {
window_size,
..Default::default()
},
)
}

/// Builds harness with given root widget, canvas size and background color.
pub fn create_with(
root_widget: impl Widget,
window_size: Size,
background_color: Color,
) -> Self {
pub fn create_with(root_widget: impl Widget, params: TestHarnessParams) -> Self {
let mouse_state = PointerState::empty();
let window_size = PhysicalSize::new(window_size.width as _, window_size.height as _);
let window_size = PhysicalSize::new(
params.window_size.width as _,
params.window_size.height as _,
);

// If there is no default tracing subscriber, we set our own. If one has
// already been set, we get an error which we swallow.
Expand All @@ -194,7 +210,7 @@ impl TestHarness {
),
mouse_state,
window_size,
background_color,
background_color: params.background_color,
action_queue: VecDeque::new(),
has_ime_session: false,
ime_rect: Default::default(),
Expand Down Expand Up @@ -306,7 +322,6 @@ impl TestHarness {
// TODO - fix window_size
let (width, height) = (self.window_size.width, self.window_size.height);
let render_params = vello::RenderParams {
// TODO - Parameterize
base_color: self.background_color,
width,
height,
Expand Down Expand Up @@ -417,22 +432,50 @@ impl TestHarness {
/// Send events that lead to a given widget being clicked.
///
/// Combines [`mouse_move`](Self::mouse_move), [`mouse_button_press`](Self::mouse_button_press), and [`mouse_button_release`](Self::mouse_button_release).
///
/// ## Panics
///
/// - If the widget is not found in the tree.
/// - If the widget is stashed.
/// - If the widget doesn't accept pointer events.
/// - If the widget is scrolled out of view.
#[track_caller]
pub fn mouse_click_on(&mut self, id: WidgetId) {
let widget_rect = self.get_widget(id).ctx().window_layout_rect();
let widget_center = widget_rect.center();

self.mouse_move(widget_center);
self.mouse_move_to(id);
self.mouse_button_press(PointerButton::Primary);
self.mouse_button_release(PointerButton::Primary);
}

/// Use [`mouse_move`](Self::mouse_move) to set the internal mouse pos to the center of the given widget.
///
/// ## Panics
///
/// - If the widget is not found in the tree.
/// - If the widget is stashed.
/// - If the widget doesn't accept pointer events.
/// - If the widget is scrolled out of view.
#[track_caller]
pub fn mouse_move_to(&mut self, id: WidgetId) {
// FIXME - handle case where the widget isn't visible
// FIXME - assert that the widget correctly receives the event otherwise?
let widget_rect = self.get_widget(id).ctx().window_layout_rect();
let widget = self.get_widget(id);
let widget_rect = widget.ctx().window_layout_rect();
let widget_center = widget_rect.center();

if !widget.ctx().accepts_pointer_interaction() {
panic!("Widget {id} doesn't accept pointer events");
}
if widget.ctx().is_disabled() {
panic!("Widget {id} is disabled");
}
if self
.render_root
.get_root_widget()
.find_widget_at_pos(widget_center)
.map(|w| w.id())
!= Some(id)
{
panic!("Widget {id} is not visible");
}

self.mouse_move(widget_center);
}

Expand Down Expand Up @@ -472,36 +515,13 @@ impl TestHarness {
self.process_signals();
}

// TODO - Fold into move_timers_forward
/// Run an animation pass on the widget tree.
pub fn animate_ms(&mut self, ms: u64) {
run_update_anim_pass(&mut self.render_root, ms * 1_000_000);
self.render_root.run_rewrite_passes();
self.process_signals();
}

#[cfg(FALSE)]
/// Simulate the passage of time.
///
/// If you create any timer in a widget, this method is the only way to trigger
/// them in unit tests. The testing model assumes that everything else executes
/// instantly, and timers are never triggered "spontaneously".
///
/// **(TODO - Doesn't move animations forward.)**
pub fn move_timers_forward(&mut self, duration: Duration) {
// TODO - handle animations
let tokens = self
.mock_app
.window
.mock_timer_queue
.as_mut()
.unwrap()
.move_forward(duration);
for token in tokens {
self.process_event(Event::Timer(token));
}
}

// --- MARK: GETTERS ---

/// Return a [`WidgetRef`] to the root widget.
Expand Down Expand Up @@ -582,9 +602,7 @@ impl TestHarness {
self.render_root.edit_widget(id, f)
}

/// Pop the next action from the queue.
///
/// **Note:** Actions are still a WIP feature.
/// Pop the oldest [`Action`] emitted by the widget tree.
pub fn pop_action(&mut self) -> Option<(Action, WidgetId)> {
self.action_queue.pop_front()
}
Expand Down
4 changes: 2 additions & 2 deletions masonry/src/testing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ mod screenshots;
#[cfg(not(tarpaulin_include))]
mod snapshot_utils;

pub use harness::{TestHarness, HARNESS_DEFAULT_BACKGROUND_COLOR, HARNESS_DEFAULT_SIZE};
pub use harness::{TestHarness, TestHarnessParams};
pub use helper_widgets::{ModularWidget, Record, Recorder, Recording, ReplaceChild, TestWidgetExt};

use crate::WidgetId;

/// Convenience function to return an arrays of unique widget ids.
/// Convenience function to return an array of unique widget ids.
pub fn widget_ids<const N: usize>() -> [WidgetId; N] {
std::array::from_fn(|_| WidgetId::next())
}

0 comments on commit 8e12858

Please sign in to comment.