From e1a81969ffdb5070bb4673423a5e5a7005f93c83 Mon Sep 17 00:00:00 2001 From: Lucas Meurer Date: Sat, 5 Oct 2024 16:04:33 +0200 Subject: [PATCH] Add Harness::builder and add README.md --- .../src/demo/demo_app_windows.rs | 5 +- .../egui_demo_lib/src/demo/widget_gallery.rs | 8 +- .../tests/snapshots/widget_gallery.png | 4 +- crates/egui_kittest/README.md | 35 +++ crates/egui_kittest/examples/kittest.rs | 50 ----- crates/egui_kittest/kittest.png | Bin 4430 -> 0 bytes crates/egui_kittest/src/builder.rs | 33 +++ crates/egui_kittest/src/lib.rs | 199 ++++++++++-------- crates/egui_kittest/src/snapshot.rs | 71 +++++-- .../tests/snapshots/readme_example.png | 3 + 10 files changed, 244 insertions(+), 164 deletions(-) create mode 100644 crates/egui_kittest/README.md delete mode 100644 crates/egui_kittest/examples/kittest.rs delete mode 100644 crates/egui_kittest/kittest.png create mode 100644 crates/egui_kittest/src/builder.rs create mode 100644 crates/egui_kittest/tests/snapshots/readme_example.png diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index 621471ab45e..0bc7f23c45d 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -383,7 +383,7 @@ mod tests { use crate::demo::demo_app_windows::Demos; use egui::Vec2; use egui_kittest::kittest::Queryable; - use egui_kittest::snapshot::try_image_snapshot; + use egui_kittest::try_image_snapshot; use egui_kittest::wgpu::TestRenderer; use egui_kittest::Harness; @@ -406,14 +406,13 @@ mod tests { // We need to run the app for multiple frames before all windows have the right size harness.run(); harness.run(); - harness.run(); let window = harness.node().children().next().unwrap(); // TODO(lucasmerlin): Windows should probably have a label? //let window = harness.get_by_name(name); let size = window.raw_bounds().expect("window bounds").size(); - harness = harness.with_size(Vec2::new(size.width as f32, size.height as f32)); + harness.set_size(Vec2::new(size.width as f32, size.height as f32)); // We need to run the app for some more frames... harness.run(); diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index 2dfcd35820e..75d74a9ba1a 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -291,7 +291,7 @@ mod tests { use super::*; use crate::View; use egui::{CentralPanel, Context, Vec2}; - use egui_kittest::snapshot::image_snapshot; + use egui_kittest::image_snapshot; use egui_kittest::wgpu::TestRenderer; use egui_kittest::Harness; @@ -303,10 +303,12 @@ mod tests { demo.ui(ui); }); }; - let mut harness = Harness::new(app) + let mut harness = Harness::builder() .with_size(Vec2::new(380.0, 550.0)) - .with_dpi(2.0); + .with_dpi(2.0) + .build(app); + // The first and second frames are slightly different, so we take the second frame harness.run(); let image = TestRenderer::new().render(&harness); diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png index 29574beb03e..2a5b8108404 100644 --- a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png +++ b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1e3c37b58611bf14983b87320a220a8befa81273c05cbcb073399c13f6d5b630 -size 177431 +oid sha256:8ac74ddfafd31883f07a5474e30bbeafda197ef9c080d5e941122bbb6cfa4ef4 +size 177515 diff --git a/crates/egui_kittest/README.md b/crates/egui_kittest/README.md new file mode 100644 index 00000000000..d9cfc35a2f2 --- /dev/null +++ b/crates/egui_kittest/README.md @@ -0,0 +1,35 @@ +# egui_kittest + +Ui testing library for egui, based on [kittest](https://github.com/rerun-io/kittest) (a AccessKit based testing library). + +```rust +use egui::accesskit::{Role, Toggled}; +use egui::{CentralPanel, Context, TextEdit, Vec2}; +use egui_kittest::Harness; +use kittest::Queryable; +use std::cell::RefCell; + +fn main() { + let mut checked = false; + let app = |ctx: &Context| { + CentralPanel::default().show(ctx, |ui| { + ui.checkbox(&mut checked, "Check me!"); + }); + }; + + let mut harness = Harness::builder().with_size(egui::Vec2::new(200.0, 100.0)).build(app); + + let checkbox = harness.get_by_name("Check me!"); + assert_eq!(checkbox.toggled(), Some(Toggled::False)); + checkbox.click(); + + harness.run(); + + let checkbox = harness.get_by_name("Check me!"); + assert_eq!(checkbox.toggled(), Some(Toggled::True)); + + // You can even render the ui and do image snapshot tests + #[cfg(all(feature = "wgpu", feature = "snapshot"))] + egui_kittest::image_snapshot(&egui_kittest::wgpu::TestRenderer::new().render(&harness), "readme_example"); +} +``` diff --git a/crates/egui_kittest/examples/kittest.rs b/crates/egui_kittest/examples/kittest.rs deleted file mode 100644 index 6fd3e412ea3..00000000000 --- a/crates/egui_kittest/examples/kittest.rs +++ /dev/null @@ -1,50 +0,0 @@ -use egui::accesskit::{Role, Toggled}; -use egui::{CentralPanel, Context, TextEdit, Vec2}; -use egui_kittest::Harness; -use kittest::Queryable; -use std::cell::RefCell; - -fn main() { - let checked = RefCell::new(false); - let text = RefCell::new(String::new()); - let app = |ctx: &Context| { - CentralPanel::default().show(ctx, |ui| { - ui.checkbox(&mut checked.borrow_mut(), "Check me!"); - TextEdit::singleline(&mut *text.borrow_mut()) - .hint_text("Type here") - .show(ui); - }); - }; - - let mut harness = Harness::new(app).with_size(Vec2::new(200.0, 100.0)); - - harness.run(); - - harness.get_by_name("Check me!").click(); - - harness.run(); - - assert!(*checked.borrow()); - let checkbox = harness.get_by_name("Check me!"); - assert_eq!(checkbox.toggled(), Some(Toggled::True)); - - harness - .get_by_role(Role::TextInput) - .type_text("Hello, World!"); - - harness.run(); - - assert_eq!(&*text.borrow_mut(), "Hello, World!"); - assert_eq!( - harness.get_by_role(Role::TextInput).value().as_deref(), - Some("Hello, World!") - ); - - #[cfg(feature = "wgpu")] - { - let mut renderer = egui_kittest::wgpu::TestRenderer::new(); - let image = renderer.render(&harness); - - image.save("../kittest.png").unwrap(); - } -} diff --git a/crates/egui_kittest/kittest.png b/crates/egui_kittest/kittest.png deleted file mode 100644 index 3fa3303889a0beb9b8da7b092740c62c0e7119fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4430 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 Self { + Self { + screen_rect: Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0)), + dpi: 1.0, + } + } +} + +impl HarnessBuilder { + pub fn with_size(mut self, size: Vec2) -> Self { + self.screen_rect.set_width(size.x); + self.screen_rect.set_height(size.y); + self + } + + pub fn with_dpi(mut self, dpi: f32) -> Self { + self.dpi = dpi; + self + } + + pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> { + Harness::from_builder(&self, app) + } +} diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index f4afb7b6313..6593158efb1 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -1,24 +1,30 @@ +mod builder; +#[doc = include_str!("../README.md")] mod event; #[cfg(feature = "snapshot")] -pub mod snapshot; +mod snapshot; +#[cfg(feature = "snapshot")] +pub use snapshot::*; #[cfg(feature = "wgpu")] mod texture_to_bytes; #[cfg(feature = "wgpu")] pub mod wgpu; pub use kittest; +use std::mem; use crate::event::{kittest_key_to_egui, pointer_button_to_egui}; pub use accesskit_consumer; +pub use builder::*; use egui::accesskit::NodeId; -use egui::{Event, Modifiers, Pos2, Rect, TexturesDelta, Vec2}; +use egui::{Event, Modifiers, Pos2, Rect, TexturesDelta, Vec2, ViewportId}; use kittest::{ElementState, Node, Queryable, SimulatedEvent, State}; pub struct Harness<'a> { pub ctx: egui::Context, input: egui::RawInput, - tree: Option, - output: Option, + kittest: State, + output: egui::FullOutput, texture_deltas: Vec, update_fn: Box, @@ -27,122 +33,135 @@ pub struct Harness<'a> { } impl<'a> Harness<'a> { - pub fn new(app: impl FnMut(&egui::Context) + 'a) -> Self { + pub(crate) fn from_builder( + builder: &HarnessBuilder, + mut app: impl FnMut(&egui::Context) + 'a, + ) -> Self { let ctx = egui::Context::default(); ctx.enable_accesskit(); + let mut input = egui::RawInput { + screen_rect: Some(builder.screen_rect), + ..Default::default() + }; + let viewport = input.viewports.get_mut(&ViewportId::ROOT).unwrap(); + viewport.native_pixels_per_point = Some(builder.dpi); + + let mut output = ctx.run(input.clone(), &mut app); Self { update_fn: Box::new(app), ctx, - input: egui::RawInput { - screen_rect: Some(Rect::from_min_size(Pos2::ZERO, Vec2::new(800.0, 600.0))), - ..Default::default() - }, - tree: None, - output: None, - texture_deltas: Vec::new(), + input, + kittest: State::new( + output + .platform_output + .accesskit_update + .take() + .expect("AccessKit was disabled"), + ), + texture_deltas: vec![mem::take(&mut output.textures_delta)], + output, last_mouse_pos: Pos2::ZERO, modifiers: Modifiers::NONE, } } + pub fn builder() -> HarnessBuilder { + HarnessBuilder::default() + } + + pub fn new(app: impl FnMut(&egui::Context) + 'a) -> Self { + Self::builder().build(app) + } + + /// Set the size of the window. + /// Note: If you only want to set the size once at the beginning, + /// prefer using [`HarnessBuilder::with_size`]. #[inline] - pub fn with_size(mut self, size: Vec2) -> Self { + pub fn set_size(&mut self, size: Vec2) -> &mut Self { self.input.screen_rect = Some(Rect::from_min_size(Pos2::ZERO, size)); self } + /// Set the DPI of the window. + /// Note: If you only want to set the DPI once at the beginning, + /// prefer using [`HarnessBuilder::with_dpi`]. #[inline] - pub fn with_dpi(mut self, dpi: f32) -> Self { - self.input - .viewports - .get_mut(&self.input.viewport_id) - .unwrap() - .native_pixels_per_point = Some(dpi); + pub fn set_dpi(&mut self, dpi: f32) -> &mut Self { + self.ctx.set_pixels_per_point(dpi); self } pub fn run(&mut self) { - if let Some(tree) = &mut self.tree { - for event in tree.take_events() { - match event { - kittest::Event::ActionRequest(e) => { - self.input.events.push(Event::AccessKitActionRequest(e)); + for event in self.kittest.take_events() { + match event { + kittest::Event::ActionRequest(e) => { + self.input.events.push(Event::AccessKitActionRequest(e)); + } + kittest::Event::Simulated(e) => match e { + SimulatedEvent::CursorMoved { position } => { + self.input.events.push(Event::PointerMoved(Pos2::new( + position.x as f32, + position.y as f32, + ))); } - kittest::Event::Simulated(e) => match e { - SimulatedEvent::CursorMoved { position } => { - self.input.events.push(Event::PointerMoved(Pos2::new( - position.x as f32, - position.y as f32, - ))); + SimulatedEvent::MouseInput { state, button } => { + let button = pointer_button_to_egui(button); + if let Some(button) = button { + self.input.events.push(Event::PointerButton { + button, + modifiers: self.modifiers, + pos: self.last_mouse_pos, + pressed: matches!(state, ElementState::Pressed), + }); } - SimulatedEvent::MouseInput { state, button } => { - let button = pointer_button_to_egui(button); - if let Some(button) = button { - self.input.events.push(Event::PointerButton { - button, - modifiers: self.modifiers, - pos: self.last_mouse_pos, - pressed: matches!(state, ElementState::Pressed), - }); + } + SimulatedEvent::Ime(text) => { + self.input.events.push(Event::Text(text)); + } + SimulatedEvent::KeyInput { state, key } => { + match key { + kittest::Key::Alt => { + self.modifiers.alt = matches!(state, ElementState::Pressed); } - } - SimulatedEvent::Ime(text) => { - self.input.events.push(Event::Text(text)); - } - SimulatedEvent::KeyInput { state, key } => { - match key { - kittest::Key::Alt => { - self.modifiers.alt = matches!(state, ElementState::Pressed); - } - kittest::Key::Command => { - self.modifiers.command = matches!(state, ElementState::Pressed); - } - kittest::Key::Control => { - self.modifiers.ctrl = matches!(state, ElementState::Pressed); - } - kittest::Key::Shift => { - self.modifiers.shift = matches!(state, ElementState::Pressed); - } - _ => {} + kittest::Key::Command => { + self.modifiers.command = matches!(state, ElementState::Pressed); + } + kittest::Key::Control => { + self.modifiers.ctrl = matches!(state, ElementState::Pressed); } - let key = kittest_key_to_egui(key); - if let Some(key) = key { - self.input.events.push(Event::Key { - key, - modifiers: self.modifiers, - pressed: matches!(state, ElementState::Pressed), - repeat: false, - physical_key: None, - }); + kittest::Key::Shift => { + self.modifiers.shift = matches!(state, ElementState::Pressed); } + _ => {} } - }, - } + let key = kittest_key_to_egui(key); + if let Some(key) = key { + self.input.events.push(Event::Key { + key, + modifiers: self.modifiers, + pressed: matches!(state, ElementState::Pressed), + repeat: false, + physical_key: None, + }); + } + } + }, } } + let mut output = self.ctx.run(self.input.take(), self.update_fn.as_mut()); - if let Some(tree) = &mut self.tree { - tree.update( - output - .platform_output - .accesskit_update - .take() - .expect("AccessKit was disabled"), - ); - } else { - self.tree = Some(State::new( - output - .platform_output - .accesskit_update - .take() - .expect("AccessKit was disabled"), - )); - } - self.output = Some(output); + self.kittest.update( + output + .platform_output + .accesskit_update + .take() + .expect("AccessKit was disabled"), + ); self.texture_deltas - .push(self.output().textures_delta.clone()); + .push(mem::take(&mut output.textures_delta)); + self.output = output; } pub fn click(&mut self, id: NodeId) { @@ -193,11 +212,11 @@ impl<'a> Harness<'a> { } pub fn output(&self) -> &egui::FullOutput { - self.output.as_ref().expect("Not initialized") + &self.output } pub fn kittest_state(&self) -> &State { - self.tree.as_ref().expect("Not initialized") + &self.kittest } } diff --git a/crates/egui_kittest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs index 7afd7b65e64..be061c05dda 100644 --- a/crates/egui_kittest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -1,10 +1,22 @@ +use image::ImageError; use std::fmt::Display; +use std::io::ErrorKind; use std::path::{Path, PathBuf}; #[derive(Debug)] pub enum SnapshotError { - Diff { diff: i32, diff_path: PathBuf }, - MissingSnapshot { path: PathBuf }, + Diff { + diff: i32, + diff_path: PathBuf, + }, + OpenSnapshot { + path: PathBuf, + err: image::ImageError, + }, + SizeMismatch { + expected: (u32, u32), + actual: (u32, u32), + }, } impl Display for SnapshotError { @@ -16,8 +28,24 @@ impl Display for SnapshotError { "Image did not match snapshot. Diff: {diff}, {diff_path:?}" ) } - Self::MissingSnapshot { path } => { - write!(f, "Missing snapshot: {path:?}") + Self::OpenSnapshot { path, err } => match err { + ImageError::IoError(io) => match io.kind() { + ErrorKind::NotFound => { + write!(f, "Missing snapshot: {path:?}") + } + err => { + write!(f, "Error reading snapshot: {err:?}") + } + }, + err => { + write!(f, "Error decoding snapshot: {err:?}") + } + }, + Self::SizeMismatch { expected, actual } => { + write!( + f, + "Image size did not match snapshot. Expected: {expected:?}, Actual: {actual:?}" + ) } } } @@ -41,25 +69,25 @@ pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> Result<(), let previous = match image::open(&path) { Ok(image) => image.to_rgba8(), Err(err) => { - println!("Error opening image: {err}"); - println!("Saving current image as {path:?}"); - current.save(&path).unwrap(); - - return Err(SnapshotError::MissingSnapshot { path }); + maybe_update_snapshot(&path, current); + return Err(SnapshotError::OpenSnapshot { path, err }); } }; + if previous.dimensions() != current.dimensions() { + maybe_update_snapshot(&path, current); + return Err(SnapshotError::SizeMismatch { + expected: previous.dimensions(), + actual: current.dimensions(), + }); + } + let result = dify::diff::get_results(previous, current.clone(), 0.1, true, None, &None, &None); if let Some((diff, result_image)) = result { result_image.save(diff_path.clone()).unwrap(); - - if std::env::var("UPDATE_SNAPSHOTS").is_ok() { - current.save(&path).unwrap(); - println!("Updated snapshot: {path:?}"); - } else { - return Err(SnapshotError::Diff { diff, diff_path }); - } + maybe_update_snapshot(&path, current); + return Err(SnapshotError::Diff { diff, diff_path }); } else { // Delete old diff if it exists std::fs::remove_file(diff_path).ok(); @@ -68,6 +96,17 @@ pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> Result<(), Ok(()) } +fn should_update_snapshots() -> bool { + std::env::var("UPDATE_SNAPSHOTS").is_ok() +} + +fn maybe_update_snapshot(snapshot_path: &Path, current: &image::RgbaImage) { + if should_update_snapshots() { + current.save(snapshot_path).unwrap(); + println!("Updated snapshot: {snapshot_path:?}"); + } +} + /// Image snapshot test. /// /// # Panics diff --git a/crates/egui_kittest/tests/snapshots/readme_example.png b/crates/egui_kittest/tests/snapshots/readme_example.png new file mode 100644 index 00000000000..3c0fe1f2e3c --- /dev/null +++ b/crates/egui_kittest/tests/snapshots/readme_example.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:20600ce0f58d9593b644a69c749b1c2aebd6b6839a9a9a52b8f6d709cd660b25 +size 2624