Skip to content

Commit

Permalink
Add Harness::new_eframe and TestRenderer trait (#5539)
Browse files Browse the repository at this point in the history
Co-authored-by: Andreas Reich <r_andreas2@web.de>
  • Loading branch information
lucasmerlin and Wumpf authored Jan 2, 2025
1 parent ee4ab08 commit 46b58e5
Show file tree
Hide file tree
Showing 28 changed files with 591 additions and 178 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,7 @@ dependencies = [
"egui",
"egui_demo_lib",
"egui_extras",
"egui_kittest",
"ehttp",
"env_logger",
"image",
Expand Down Expand Up @@ -1395,6 +1396,7 @@ version = "0.30.0"
dependencies = [
"dify",
"document-features",
"eframe",
"egui",
"egui-wgpu",
"image",
Expand Down
67 changes: 66 additions & 1 deletion crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,28 @@ 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::mock(),
storage: None,
#[cfg(feature = "glow")]
gl: None,
#[cfg(feature = "glow")]
get_proc_address: None,
#[cfg(feature = "wgpu")]
wgpu_render_state: None,
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
#[cfg(not(target_arch = "wasm32"))]
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 @@ -617,7 +639,8 @@ pub struct Frame {

/// Can be used to manage GPU resources for custom rendering with WGPU using [`egui::PaintCallback`]s.
#[cfg(feature = "wgpu")]
pub(crate) wgpu_render_state: Option<egui_wgpu::RenderState>,
#[doc(hidden)]
pub wgpu_render_state: Option<egui_wgpu::RenderState>,

/// Raw platform window handle
#[cfg(not(target_arch = "wasm32"))]
Expand Down Expand Up @@ -651,6 +674,25 @@ 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 {
#[cfg(feature = "glow")]
gl: None,
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
glow_register_native_texture: None,
info: IntegrationInfo::mock(),
#[cfg(not(target_arch = "wasm32"))]
raw_display_handle: Err(HandleError::NotSupported),
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
storage: None,
#[cfg(feature = "wgpu")]
wgpu_render_state: None,
}
}

/// True if you are in a web environment.
///
/// Equivalent to `cfg!(target_arch = "wasm32")`
Expand Down Expand Up @@ -794,6 +836,29 @@ pub struct IntegrationInfo {
pub cpu_usage: Option<f32>,
}

impl IntegrationInfo {
fn mock() -> Self {
Self {
#[cfg(target_arch = "wasm32")]
web_info: WebInfo {
user_agent: "kittest".to_owned(),
location: Location {
url: "http://localhost".to_owned(),
protocol: "http:".to_owned(),
host: "localhost".to_owned(),
hostname: "localhost".to_owned(),
port: "80".to_owned(),
hash: String::new(),
query: String::new(),
query_map: Default::default(),
origin: "http://localhost".to_owned(),
},
},
cpu_usage: None,
}
}
}

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

/// A place where you can store custom data in a way that persists when you restart the app.
Expand Down
2 changes: 1 addition & 1 deletion crates/eframe/src/web/web_painter_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl WebPainterWgpu {
let render_state = RenderState::create(
&options.wgpu_options,
&instance,
&surface,
Some(&surface),
depth_format,
1,
options.dithering,
Expand Down
15 changes: 9 additions & 6 deletions crates/egui-wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub use wgpu;
mod renderer;

pub use renderer::*;
use wgpu::{Adapter, Device, Instance, Queue};
use wgpu::{Adapter, Device, Instance, Queue, TextureFormat};

/// Helpers for capturing screenshots of the UI.
pub mod capture;
Expand Down Expand Up @@ -91,7 +91,7 @@ impl RenderState {
pub async fn create(
config: &WgpuConfiguration,
instance: &wgpu::Instance,
surface: &wgpu::Surface<'static>,
compatible_surface: Option<&wgpu::Surface<'static>>,
depth_format: Option<wgpu::TextureFormat>,
msaa_samples: u32,
dithering: bool,
Expand All @@ -113,7 +113,7 @@ impl RenderState {
instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference,
compatible_surface: Some(surface),
compatible_surface,
force_fallback_adapter: false,
})
.await
Expand Down Expand Up @@ -186,11 +186,14 @@ impl RenderState {
} => (adapter, device, queue),
};

let capabilities = {
let surface_formats = {
profiling::scope!("get_capabilities");
surface.get_capabilities(&adapter).formats
compatible_surface.map_or_else(
|| vec![TextureFormat::Rgba8Unorm],
|s| s.get_capabilities(&adapter).formats,
)
};
let target_format = crate::preferred_framebuffer_format(&capabilities)?;
let target_format = crate::preferred_framebuffer_format(&surface_formats)?;

let renderer = Renderer::new(
&device,
Expand Down
2 changes: 1 addition & 1 deletion crates/egui-wgpu/src/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl Painter {
let render_state = RenderState::create(
&self.configuration,
&self.instance,
&surface,
Some(&surface),
self.depth_format,
self.msaa_samples,
self.dithering,
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"] }
5 changes: 3 additions & 2 deletions crates/egui_demo_app/src/apps/image_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ impl eframe::App for ImageViewer {
fn update(&mut self, ctx: &egui::Context, _: &mut eframe::Frame) {
egui::TopBottomPanel::new(TopBottomSide::Top, "url bar").show(ctx, |ui| {
ui.horizontal_centered(|ui| {
ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text);
let label = ui.label("URI:");
ui.text_edit_singleline(&mut self.uri_edit_text)
.labelled_by(label.id);
if ui.small_button("✔").clicked() {
ctx.forget_image(&self.current_uri);
self.uri_edit_text = self.uri_edit_text.trim().to_owned();
Expand Down
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::{Anchor, WrapApp};

/// 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
3 changes: 3 additions & 0 deletions crates/egui_demo_app/tests/snapshots/clock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions crates/egui_demo_app/tests/snapshots/custom3d.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions crates/egui_demo_app/tests/snapshots/easymarkeditor.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions crates/egui_demo_app/tests/snapshots/imageviewer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 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,78 @@
use egui::accesskit::Role;
use egui::Vec2;
use egui_demo_app::{Anchor, WrapApp};
use egui_kittest::kittest::Queryable;

#[test]
fn test_demo_app() {
let mut harness = egui_kittest::Harness::builder()
.with_size(Vec2::new(900.0, 600.0))
.wgpu()
.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<_>>();

#[cfg(feature = "wgpu")]
assert!(
apps.iter()
.any(|(_, anchor)| matches!(anchor, Anchor::Custom3d)),
"Expected to find the Custom3d app.",
);

let mut results = vec![];

for (name, anchor) in apps {
harness.get_by_role_and_label(Role::Button, name).click();

match anchor {
// The widget gallery demo shows the current date, so we can't use it for snapshot testing
Anchor::Demo => {
continue;
}
// This is already tested extensively elsewhere
Anchor::Rendering => {
continue;
}
// We don't want to rely on a network connection for tests
#[cfg(feature = "http")]
Anchor::Http => {
continue;
}
// Load a local image where we know it exists and loads quickly
#[cfg(feature = "image_viewer")]
Anchor::ImageViewer => {
harness.run();

harness
.get_by_role_and_label(Role::TextInput, "URI:")
.focus();
harness.press_key_modifiers(egui::Modifiers::COMMAND, egui::Key::A);

harness
.get_by_role_and_label(Role::TextInput, "URI:")
.type_text("file://../eframe/data/icon.png");

harness.get_by_role_and_label(Role::Button, "✔").click();
}
_ => {}
}

harness.run();

if let Err(e) = harness.try_snapshot(&anchor.to_string()) {
results.push(e);
}
}

if let Some(error) = results.first() {
panic!("{error}");
}
}
2 changes: 1 addition & 1 deletion crates/egui_demo_lib/src/demo/demo_app_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ mod tests {
options.threshold = 2.1;
}

let result = harness.try_wgpu_snapshot_options(&format!("demos/{name}"), &options);
let result = harness.try_snapshot_options(&format!("demos/{name}"), &options);
if let Err(err) = result {
errors.push(err.to_string());
}
Expand Down
8 changes: 4 additions & 4 deletions crates/egui_demo_lib/src/demo/modals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,21 +235,21 @@ mod tests {
let mut results = Vec::new();

harness.run();
results.push(harness.try_wgpu_snapshot("modals_1"));
results.push(harness.try_snapshot("modals_1"));

harness.get_by_label("Save").click();
// TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests
harness.run();
harness.run();
harness.run();
results.push(harness.try_wgpu_snapshot("modals_2"));
results.push(harness.try_snapshot("modals_2"));

harness.get_by_label("Yes Please").click();
// TODO(lucasmerlin): Remove these extra runs once run checks for repaint requests
harness.run();
harness.run();
harness.run();
results.push(harness.try_wgpu_snapshot("modals_3"));
results.push(harness.try_snapshot("modals_3"));

for result in results {
result.unwrap();
Expand Down Expand Up @@ -282,6 +282,6 @@ mod tests {
harness.run();

// This snapshots should show the progress bar modal on top of the save modal.
harness.wgpu_snapshot("modals_backdrop_should_prevent_focusing_lower_area");
harness.snapshot("modals_backdrop_should_prevent_focusing_lower_area");
}
}
Loading

0 comments on commit 46b58e5

Please sign in to comment.