Skip to content

Commit

Permalink
Add Harness::new_eframe
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Dec 29, 2024
1 parent 2356ae8 commit e0798b1
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 13 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,7 @@ dependencies = [
"egui",
"egui_demo_lib",
"egui_extras",
"egui_kittest",
"ehttp",
"env_logger",
"image",
Expand Down Expand Up @@ -1391,6 +1392,7 @@ version = "0.30.0"
dependencies = [
"dify",
"document-features",
"eframe",
"egui",
"egui-wgpu",
"image",
Expand Down
35 changes: 35 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,25 @@ impl HasDisplayHandle for CreationContext<'_> {
}
}

impl CreationContext<'_> {
/// Create a new empty [CreationContext] for testing [App]s in kittest.
#[doc(hidden)]
pub fn _new_kittest(egui_ctx: egui::Context) -> Self {
Self {
egui_ctx,
integration_info: IntegrationInfo {
cpu_usage: None,
},
storage: None,
gl: None,
get_proc_address: None,
wgpu_render_state: None,
raw_window_handle: Err(HandleError::NotSupported),
raw_display_handle: Err(HandleError::NotSupported),
}
}
}

// ----------------------------------------------------------------------------

/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe).
Expand Down Expand Up @@ -651,6 +670,22 @@ impl HasDisplayHandle for Frame {
}

impl Frame {
/// Create a new empty [Frame] for testing [App]s in kittest.
#[doc(hidden)]
pub fn _new_kittest() -> Self {
Self {
gl: None,
glow_register_native_texture: None,
info: IntegrationInfo {
cpu_usage: None,
},
raw_display_handle: Err(HandleError::NotSupported),
raw_window_handle: Err(HandleError::NotSupported),
storage: None,
wgpu_render_state: None,
}
}

/// True if you are in a web environment.
///
/// Equivalent to `cfg!(target_arch = "wasm32")`
Expand Down
3 changes: 3 additions & 0 deletions crates/egui_demo_app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,6 @@ rfd = { version = "0.15", optional = true }
wasm-bindgen = "=0.2.95"
wasm-bindgen-futures.workspace = true
web-sys.workspace = true

[dev-dependencies]
egui_kittest = { workspace = true, features = ["eframe", "snapshot", "wgpu"] }
2 changes: 1 addition & 1 deletion crates/egui_demo_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mod backend_panel;
mod frame_history;
mod wrap_app;

pub use wrap_app::WrapApp;
pub use wrap_app::{WrapApp, Anchor};

/// Time of day as seconds since midnight. Used for clock in demo app.
pub(crate) fn seconds_since_midnight() -> f64 {
Expand Down
13 changes: 8 additions & 5 deletions crates/egui_demo_app/src/wrap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl eframe::App for DemoApp {
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct FractalClockApp {
fractal_clock: crate::apps::FractalClock,
pub mock_time: Option<f64>,
}

impl eframe::App for FractalClockApp {
Expand All @@ -46,7 +47,7 @@ impl eframe::App for FractalClockApp {
.frame(egui::Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| {
self.fractal_clock
.ui(ui, Some(crate::seconds_since_midnight()));
.ui(ui, self.mock_time.or(Some(crate::seconds_since_midnight())));
});
}
}
Expand Down Expand Up @@ -77,7 +78,7 @@ impl eframe::App for ColorTestApp {

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
enum Anchor {
pub enum Anchor {
Demo,

EasyMarkEditor,
Expand Down Expand Up @@ -161,7 +162,7 @@ pub struct State {
http: crate::apps::HttpApp,
#[cfg(feature = "image_viewer")]
image_viewer: crate::apps::ImageViewer,
clock: FractalClockApp,
pub clock: FractalClockApp,
rendering_test: ColorTestApp,

selected_anchor: Anchor,
Expand All @@ -170,7 +171,7 @@ pub struct State {

/// Wraps many demo/test apps into one.
pub struct WrapApp {
state: State,
pub state: State,

#[cfg(any(feature = "glow", feature = "wgpu"))]
custom3d: Option<crate::apps::Custom3d>,
Expand Down Expand Up @@ -203,7 +204,9 @@ impl WrapApp {
slf
}

fn apps_iter_mut(&mut self) -> impl Iterator<Item = (&str, Anchor, &mut dyn eframe::App)> {
pub fn apps_iter_mut(
&mut self,
) -> impl Iterator<Item = (&'static str, Anchor, &mut dyn eframe::App)> {
let mut vec = vec![
(
"✨ Demos",
Expand Down
37 changes: 37 additions & 0 deletions crates/egui_demo_app/tests/test_demo_app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use egui::accesskit::Role;
use egui_demo_app::{Anchor, WrapApp};
use egui_kittest::kittest::Queryable;

#[test]
fn test_demo_app() {
let mut harness = egui_kittest::Harness::builder().build_eframe(|cc| WrapApp::new(cc));

let app = harness.state_mut();

// Mock the fractal clock time so snapshots are consistent.
app.state.clock.mock_time = Some(36383.0);

let apps = app
.apps_iter_mut()
.map(|(name, anchor, _)| (name, anchor))
.collect::<Vec<_>>();

let mut results = vec![];

for (name, anchor) in apps {
// The widget gallery demo shows the current date, so we can't use it for snapshot testing
if matches!(anchor, Anchor::Demo) {
continue;
}

harness.get_by_role_and_label(Role::Button, name).click();

harness.run();

results.push(harness.try_wgpu_snapshot(&anchor.to_string()));
}

for result in results {
result.unwrap()
}
}
6 changes: 5 additions & 1 deletion crates/egui_kittest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,19 @@ include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]

[features]
# Adds a wgpu-based test renderer.
wgpu = ["dep:egui-wgpu", "dep:pollster", "dep:image"]
wgpu = ["dep:egui-wgpu", "dep:pollster", "dep:image", "eframe?/wgpu"]

# Adds a dify-based image snapshot utility.
snapshot = ["dep:dify", "dep:image", "image/png"]

# Allows testing eframe::App
eframe = ["dep:eframe", "eframe/accesskit"]


[dependencies]
kittest.workspace = true
egui = { workspace = true, features = ["accesskit"] }
eframe = { workspace = true, optional = true }

# wgpu dependencies
egui-wgpu = { workspace = true, optional = true }
Expand Down
14 changes: 13 additions & 1 deletion crates/egui_kittest/src/app_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ type AppKindContextState<'a, State> = Box<dyn FnMut(&egui::Context, &mut State)
type AppKindUiState<'a, State> = Box<dyn FnMut(&mut egui::Ui, &mut State) + 'a>;
type AppKindContext<'a> = Box<dyn FnMut(&egui::Context) + 'a>;
type AppKindUi<'a> = Box<dyn FnMut(&mut egui::Ui) + 'a>;
#[cfg(feature = "eframe")]
type AppKindEframe<'a, State> = (fn(&mut State) -> &mut dyn eframe::App, eframe::Frame);

pub(crate) enum AppKind<'a, State> {
Context(AppKindContext<'a>),
Ui(AppKindUi<'a>),
ContextState(AppKindContextState<'a, State>),
UiState(AppKindUiState<'a, State>),
#[cfg(feature = "eframe")]
Eframe(AppKindEframe<'a, State>),
}

// TODO(lucasmerlin): These aren't working unfortunately :(
Expand Down Expand Up @@ -54,6 +58,12 @@ impl<'a, State> AppKind<'a, State> {
f(ctx, state);
None
}
#[cfg(feature = "eframe")]
AppKind::Eframe((get_app, frame)) => {
let app = get_app(state);
app.update(ctx, frame);
None
}
kind_ui => Some(kind_ui.run_ui(ctx, state, sizing_pass)),
}
}
Expand All @@ -78,7 +88,9 @@ impl<'a, State> AppKind<'a, State> {
.show(ui, |ui| match self {
AppKind::Ui(f) => f(ui),
AppKind::UiState(f) => f(ui, state),
_ => unreachable!(),
_ => unreachable!(
"run_ui should only be called with AppKind::Ui or AppKind UiState"
),
});
})
.response
Expand Down
31 changes: 27 additions & 4 deletions crates/egui_kittest/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl<State> HarnessBuilder<State> {
app: impl FnMut(&egui::Context, &mut State) + 'a,
state: State,
) -> Harness<'a, State> {
Harness::from_builder(&self, AppKind::ContextState(Box::new(app)), state)
Harness::from_builder(&self, AppKind::ContextState(Box::new(app)), state, None)
}

/// Create a new Harness with the given ui closure and a state.
Expand Down Expand Up @@ -95,7 +95,30 @@ impl<State> HarnessBuilder<State> {
app: impl FnMut(&mut egui::Ui, &mut State) + 'a,
state: State,
) -> Harness<'a, State> {
Harness::from_builder(&self, AppKind::UiState(Box::new(app)), state)
Harness::from_builder(&self, AppKind::UiState(Box::new(app)), state, None)
}

/// Create a new [Harness] from the given eframe creation closure.
/// If the wgpu feature is enabled and [HarnessBuilder::wgpu] was called, the
/// [eframe::CreationContext] and [eframe::Frame] will have a [egui_wgpu::RenderState]
/// attached.
pub fn build_eframe<'a>(
self,
build: impl FnOnce(&mut eframe::CreationContext) -> State,
) -> Harness<'a, State>
where
State: eframe::App,
{
let ctx = egui::Context::default();

let mut cc = eframe::CreationContext::_new_kittest(ctx.clone());

let app = build(&mut cc);

let frame = eframe::Frame::_new_kittest();

let kind = AppKind::Eframe((|state| state, frame));
Harness::from_builder(&self, kind, app, Some(ctx))
}
}

Expand All @@ -119,7 +142,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)), ())
Harness::from_builder(&self, AppKind::Context(Box::new(app)), (), None)
}

/// Create a new Harness with the given ui closure.
Expand All @@ -138,6 +161,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)), ())
Harness::from_builder(&self, AppKind::Ui(Box::new(app)), (), None)
}
}
11 changes: 10 additions & 1 deletion crates/egui_kittest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ impl<'a, State> Harness<'a, State> {
builder: &HarnessBuilder<State>,
mut app: AppKind<'a, State>,
mut state: State,
ctx: Option<egui::Context>,
) -> Self {
let ctx = egui::Context::default();
let ctx = ctx.unwrap_or_default();
ctx.enable_accesskit();
let mut input = egui::RawInput {
screen_rect: Some(builder.screen_rect),
Expand Down Expand Up @@ -153,6 +154,14 @@ impl<'a, State> Harness<'a, State> {
Self::builder().build_ui_state(app, state)
}

/// Create a new [Harness] from the given eframe creation closure.
pub fn new_eframe(builder: impl FnOnce(&mut eframe::CreationContext) -> State) -> Self
where
State: eframe::App,
{
Self::builder().build_eframe(builder)
}

/// Set the size of the window.
/// Note: If you only want to set the size once at the beginning,
/// prefer using [`HarnessBuilder::with_size`].
Expand Down

0 comments on commit e0798b1

Please sign in to comment.