Skip to content

Commit

Permalink
Store state in the harness
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Oct 29, 2024
1 parent 1c60bb5 commit e38fe1d
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 65 deletions.
12 changes: 6 additions & 6 deletions crates/egui_demo_lib/src/demo/text_edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,32 +121,32 @@ mod tests {

#[test]
pub fn should_type() {
let mut text = "Hello, world!".to_owned();
let text = "Hello, world!".to_owned();
let mut harness = Harness::new_state(
move |ctx, text| {
CentralPanel::default().show(ctx, |ui| {
ui.text_edit_singleline(text);
});
},
&mut text,
text,
);

harness.run_state(&mut text);
harness.run();

let text_edit = harness.get_by_role(accesskit::Role::TextInput);
assert_eq!(text_edit.value().as_deref(), Some("Hello, world!"));

text_edit.key_combination(&[Key::Command, Key::A]);
text_edit.type_text("Hi ");

harness.run_state(&mut text);
harness.run();
harness
.get_by_role(accesskit::Role::TextInput)
.type_text("there!");

harness.run_state(&mut text);
harness.run();
let text_edit = harness.get_by_role(accesskit::Role::TextInput);
assert_eq!(text_edit.value().as_deref(), Some("Hi there!"));
assert_eq!(text, "Hi there!");
assert_eq!(harness.state(), "Hi there!");
}
}
30 changes: 11 additions & 19 deletions crates/egui_kittest/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ impl<S> HarnessBuilder<S> {
}

/// Create a new Harness with the given app closure and a state.
/// Use the [`Harness::run_state`], [`Harness::step_state`], etc... methods to run the app with
/// your state.
///
/// The app closure will immediately be called once to create the initial ui.
///
Expand All @@ -49,31 +47,29 @@ impl<S> HarnessBuilder<S> {
/// ```rust
/// # use egui::CentralPanel;
/// # use egui_kittest::{Harness, kittest::Queryable};
/// let mut checked = false;
/// let checked = false;
/// let mut harness = Harness::builder()
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build_state(|ctx, checked| {
/// CentralPanel::default().show(ctx, |ui| {
/// ui.checkbox(checked, "Check me!");
/// });
/// }, &mut checked);
/// }, checked);
///
/// harness.get_by_name("Check me!").click();
/// harness.run_state(&mut checked);
/// harness.run();
///
/// assert_eq!(checked, true);
/// assert_eq!(*harness.state(), true);
/// ```
pub fn build_state<'a>(
self,
app: impl FnMut(&egui::Context, &mut S) + 'a,
state: &mut S,
state: S,
) -> Harness<'a, S> {
Harness::from_builder(&self, AppKind::ContextState(Box::new(app)), state)
}

/// Create a new Harness with the given ui closure and a state.
/// Use the [`Harness::run_state`], [`Harness::step_state`], etc... methods to run the app with
/// your state.
///
/// The ui closure will immediately be called once to create the initial ui.
///
Expand All @@ -87,17 +83,17 @@ impl<S> HarnessBuilder<S> {
/// .with_size(egui::Vec2::new(300.0, 200.0))
/// .build_ui_state(|ui, checked| {
/// ui.checkbox(checked, "Check me!");
/// }, &mut checked);
/// }, checked);
///
/// harness.get_by_name("Check me!").click();
/// harness.run_state(&mut checked);
/// harness.run();
///
/// assert_eq!(checked, true);
/// assert_eq!(*harness.state(), true);
/// ```
pub fn build_ui_state<'a>(
self,
app: impl FnMut(&mut egui::Ui, &mut S) + 'a,
state: &mut S,
state: S,
) -> Harness<'a, S> {
Harness::from_builder(&self, AppKind::UiState(Box::new(app)), state)
}
Expand All @@ -123,11 +119,7 @@ impl HarnessBuilder {
/// });
/// ```
pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> {
Harness::from_builder(
&self,
AppKind::Context(Box::new(app)),
&mut Default::default(),
)
Harness::from_builder(&self, AppKind::Context(Box::new(app)), ())
}

/// Create a new Harness with the given ui closure.
Expand All @@ -146,6 +138,6 @@ impl HarnessBuilder {
/// });
/// ```
pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> {
Harness::from_builder(&self, AppKind::Ui(Box::new(app)), &mut Default::default())
Harness::from_builder(&self, AppKind::Ui(Box::new(app)), ())
}
}
72 changes: 32 additions & 40 deletions crates/egui_kittest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct Harness<'a, S = ()> {
app: AppKind<'a, S>,
event_state: EventState,
response: Option<egui::Response>,
state: S,
}

impl<'a> Debug for Harness<'a> {
Expand All @@ -53,7 +54,7 @@ impl<'a, S> Harness<'a, S> {
pub(crate) fn from_builder(
builder: &HarnessBuilder<S>,
mut app: AppKind<'a, S>,
state: &mut S,
mut state: S,
) -> Self {
let ctx = egui::Context::default();
ctx.enable_accesskit();
Expand All @@ -69,7 +70,7 @@ impl<'a, S> Harness<'a, S> {
// We need to run egui for a single frame so that the AccessKit state can be initialized
// and users can immediately start querying for widgets.
let mut output = ctx.run(input.clone(), |ctx| {
response = app.run(ctx, state, false);
response = app.run(ctx, &mut state, false);
});

let mut harness = Self {
Expand All @@ -87,9 +88,10 @@ impl<'a, S> Harness<'a, S> {
output,
response,
event_state: EventState::default(),
state,
};
// Run the harness until it is stable, ensuring that all Areas are shown and animations are done
harness.run_state(state);
harness.run();
harness
}

Expand All @@ -99,8 +101,6 @@ impl<'a, S> Harness<'a, S> {
}

/// Create a new Harness with the given app closure and a state.
/// Use the [`Harness::run_state`], [`Harness::step_state`], etc... methods to run the app with
/// your state.
///
/// The app closure will immediately be called once to create the initial ui.
///
Expand All @@ -117,20 +117,18 @@ impl<'a, S> Harness<'a, S> {
/// CentralPanel::default().show(ctx, |ui| {
/// ui.checkbox(checked, "Check me!");
/// });
/// }, &mut checked);
/// }, checked);
///
/// harness.get_by_name("Check me!").click();
/// harness.run_state(&mut checked);
/// harness.run();
///
/// assert_eq!(checked, true);
/// assert_eq!(*harness.state(), true);
/// ```
pub fn new_state(app: impl FnMut(&egui::Context, &mut S) + 'a, state: &mut S) -> Self {
pub fn new_state(app: impl FnMut(&egui::Context, &mut S) + 'a, state: S) -> Self {
Self::builder().build_state(app, state)
}

/// Create a new Harness with the given ui closure and a state.
/// Use the [`Harness::run_state`], [`Harness::step_state`], etc... methods to run the app with
/// your state.
///
/// The ui closure will immediately be called once to create the initial ui.
///
Expand All @@ -144,14 +142,14 @@ impl<'a, S> Harness<'a, S> {
/// let mut checked = false;
/// let mut harness = Harness::new_ui_state(|ui, checked| {
/// ui.checkbox(checked, "Check me!");
/// }, &mut checked);
/// }, checked);
///
/// harness.get_by_name("Check me!").click();
/// harness.run_state(&mut checked);
/// harness.run();
///
/// assert_eq!(checked, true);
/// assert_eq!(*harness.state(), true);
/// ```
pub fn new_ui_state(app: impl FnMut(&mut egui::Ui, &mut S) + 'a, state: &mut S) -> Self {
pub fn new_ui_state(app: impl FnMut(&mut egui::Ui, &mut S) + 'a, state: S) -> Self {
Self::builder().build_ui_state(app, state)
}

Expand All @@ -175,19 +173,19 @@ impl<'a, S> Harness<'a, S> {

/// Run a frame.
/// This will call the app closure with the current context and update the Harness.
pub fn step_state(&mut self, state: &mut S) {
self._step(state, false);
pub fn step(&mut self) {
self._step(false);
}

fn _step(&mut self, state: &mut S, sizing_pass: bool) {
fn _step(&mut self, sizing_pass: bool) {
for event in self.kittest.take_events() {
if let Some(event) = self.event_state.kittest_event_to_egui(event) {
self.input.events.push(event);
}
}

let mut output = self.ctx.run(self.input.take(), |ctx| {
self.response = self.app.run(ctx, state, sizing_pass);
self.response = self.app.run(ctx, &mut self.state, sizing_pass);
});
self.kittest.update(
output
Expand All @@ -203,22 +201,22 @@ impl<'a, S> Harness<'a, S> {

/// Resize the test harness to fit the contents. This only works when creating the Harness via
/// [`Harness::new_ui`] or [`HarnessBuilder::build_ui`].
pub fn fit_contents_state(&mut self, state: &mut S) {
self._step(state, true);
pub fn fit_contents(&mut self) {
self._step(true);
if let Some(response) = &self.response {
self.set_size(response.rect.size());
}
self.run_state(state);
self.run();
}

/// Run a few frames.
/// This will soon be changed to run the app until it is "stable", meaning
/// - all animations are done
/// - no more repaints are requested
pub fn run_state(&mut self, state: &mut S) {
pub fn run(&mut self) {
const STEPS: usize = 2;
for _ in 0..STEPS {
self.step_state(state);
self.step();
}
}

Expand All @@ -241,6 +239,16 @@ impl<'a, S> Harness<'a, S> {
pub fn kittest_state(&self) -> &kittest::State {
&self.kittest
}

/// Access the state.
pub fn state(&self) -> &S {
&self.state
}

/// Access the state mutably.
pub fn state_mut(&mut self) -> &mut S {
&mut self.state
}
}

/// Utilities for stateless harnesses.
Expand Down Expand Up @@ -287,22 +295,6 @@ impl<'a> Harness<'a> {
pub fn new_ui(app: impl FnMut(&mut egui::Ui) + 'a) -> Self {
Self::builder().build_ui(app)
}

/// Run a frame.
pub fn step(&mut self) {
self.step_state(&mut Default::default());
}

/// Run a few frames.
pub fn run(&mut self) {
self.run_state(&mut Default::default());
}

/// Resize the test harness to fit the contents. This only works when creating the Harness via
/// [`Harness::new_ui`] or [`HarnessBuilder::build_ui`].
pub fn fit_contents(&mut self) {
self.fit_contents_state(&mut Default::default());
}
}

impl<'t, 'n, 'h, S> Queryable<'t, 'n> for Harness<'h, S>
Expand Down

0 comments on commit e38fe1d

Please sign in to comment.