diff --git a/Cargo.lock b/Cargo.lock index b6a8071e40a..4d19eb73cc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,15 +66,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "accesskit_query" -version = "0.28.1" -dependencies = [ - "accesskit", - "accesskit_consumer", - "extension-traits", -] - [[package]] name = "accesskit_unix" version = "0.12.0" @@ -1350,7 +1341,7 @@ dependencies = [ "document-features", "egui", "egui_extras", - "etest", + "egui_kittest", "log", "serde", "unicode_names2", @@ -1396,6 +1387,21 @@ dependencies = [ "winit", ] +[[package]] +name = "egui_kittest" +version = "0.28.1" +dependencies = [ + "accesskit_consumer", + "dify", + "egui", + "egui-wgpu", + "image 0.24.9", + "image 0.25.0", + "kittest", + "pollster", + "wgpu", +] + [[package]] name = "ehttp" version = "0.5.0" @@ -1549,21 +1555,6 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" -[[package]] -name = "etest" -version = "0.28.1" -dependencies = [ - "accesskit_consumer", - "accesskit_query", - "dify", - "egui", - "egui-wgpu", - "image 0.24.9", - "image 0.25.0", - "pollster", - "wgpu", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1618,35 +1609,6 @@ dependencies = [ "zune-inflate", ] -[[package]] -name = "ext-trait" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703090345f7d5de48379b391c9dfe69967a3c1694730a3e53bf4c905f71069c0" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9f3f15f123dee4e8a6b14f033ba22904a48c5935505dc07225ce440e640d8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "extension-traits" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360cbc11ebc403c8ebb731dfb4d3950835d40d3d9a20f0e89a27b17e991d0863" -dependencies = [ - "ext-trait", -] - [[package]] name = "fancy-regex" version = "0.11.0" @@ -2453,6 +2415,16 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "kittest" +version = "0.1.0" +source = "git+https://github.com/rerun-io/kittest?branch=kittest#be12a16312993bd614f7291470c588e906908cc8" +dependencies = [ + "accesskit", + "accesskit_consumer", + "parking_lot", +] + [[package]] name = "kurbo" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index e8213df6cfd..1c83aa9ba31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/egui_demo_lib", "crates/egui_extras", "crates/egui_glow", + "crates/egui_kittest", "crates/egui-wgpu", "crates/egui-winit", "crates/egui", @@ -14,7 +15,7 @@ members = [ "crates/epaint_default_fonts", "examples/*", "tests/*", - "xtask", "crates/etest", "crates/accesskit_query", + "xtask", ] [workspace.package] diff --git a/crates/accesskit_query/Cargo.toml b/crates/accesskit_query/Cargo.toml deleted file mode 100644 index 9640027e1b9..00000000000 --- a/crates/accesskit_query/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "accesskit_query" -edition.workspace = true -license.workspace = true -rust-version.workspace = true -version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -accesskit_consumer = "0.24.0" -accesskit = "0.16.0" -extension-traits = "2" - -[lints] -workspace = true diff --git a/crates/accesskit_query/src/event.rs b/crates/accesskit_query/src/event.rs deleted file mode 100644 index f037e529bab..00000000000 --- a/crates/accesskit_query/src/event.rs +++ /dev/null @@ -1,11 +0,0 @@ -use accesskit::Vec2; - -pub enum AKEvent { - ActionRequest(accesskit::ActionRequest), - Simulated(SimulatedEvent), -} - -pub enum SimulatedEvent { - Click { position: Vec2 }, - Type { text: String }, -} diff --git a/crates/accesskit_query/src/lib.rs b/crates/accesskit_query/src/lib.rs deleted file mode 100644 index 66b78dda11a..00000000000 --- a/crates/accesskit_query/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod event; -mod node; -mod query; -mod tree; - -pub use event::*; -pub use node::*; -pub use query::*; -pub use tree::*; diff --git a/crates/accesskit_query/src/node.rs b/crates/accesskit_query/src/node.rs deleted file mode 100644 index 8352cd9153a..00000000000 --- a/crates/accesskit_query/src/node.rs +++ /dev/null @@ -1,105 +0,0 @@ -use crate::event::{AKEvent, SimulatedEvent}; -use crate::query::Queryable; -use accesskit::{ActionRequest, Vec2}; -use std::fmt::{Debug, Formatter}; -use std::ops::Deref; -use std::sync::Mutex; - -pub struct Node<'tree> { - node: accesskit_consumer::Node<'tree>, - pub(crate) queue: &'tree Mutex>, -} - -impl<'a> Debug for Node<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let mut s = f.debug_struct("Node"); - s.field("id", &self.node.id()); - s.field("role", &self.node.role()); - s.field("focused", &self.node.is_focused()); - s.field("hidden", &self.node.is_hidden()); - s.field("disabled", &self.node.is_disabled()); - if let Some(name) = self.node.name() { - s.field("name", &name); - } - if let Some(value) = self.node.value() { - s.field("value", &value); - } - if let Some(toggled) = self.node.toggled() { - s.field("toggled", &toggled); - } - if let Some(numeric) = self.node.numeric_value() { - s.field("numeric_value", &numeric); - } - s.finish() - } -} - -/// We should probably add our own methods to query the node state but for now this should work -impl<'tree> Deref for Node<'tree> { - type Target = accesskit_consumer::Node<'tree>; - - fn deref(&self) -> &Self::Target { - &self.node - } -} - -impl<'tree> Node<'tree> { - pub fn new(node: accesskit_consumer::Node<'tree>, queue: &'tree Mutex>) -> Self { - Self { node, queue } - } - - pub fn queue<'node>(&'node self) -> &'tree Mutex> { - self.queue - } - - pub fn focus(&self) { - self.queue - .lock() - .unwrap() - .push(AKEvent::ActionRequest(ActionRequest { - data: None, - action: accesskit::Action::Focus, - target: self.node.id(), - })); - } - - /// Click the node via accesskit - pub fn click(&self) { - self.queue - .lock() - .unwrap() - .push(AKEvent::ActionRequest(ActionRequest { - data: None, - action: accesskit::Action::Default, - target: self.node.id(), - })); - } - - /// Simulate a click event on the node center - pub fn simulate_click(&self) { - let rect = self.node.raw_bounds().expect("Node has no bounds"); - let center = Vec2::new(rect.x0 + rect.x1 / 2.0, rect.y0 + rect.y1 / 2.0); - self.queue - .lock() - .unwrap() - .push(AKEvent::Simulated(SimulatedEvent::Click { - position: center, - })); - } - - pub fn type_text(&self, text: &str) { - self.focus(); - self.queue - .lock() - .unwrap() - .push(AKEvent::Simulated(SimulatedEvent::Type { - text: text.to_owned(), - })); - } -} - -impl<'t, 'n> Queryable<'t, 'n> for Node<'t> { - fn node(&'n self) -> Node<'t> { - Node::new(self.node, self.queue) - } -} diff --git a/crates/accesskit_query/src/query.rs b/crates/accesskit_query/src/query.rs deleted file mode 100644 index 671a8445717..00000000000 --- a/crates/accesskit_query/src/query.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::Node; -use accesskit_consumer::{FilterResult, Node as AKNode}; -use std::iter::{FusedIterator, Peekable}; - -fn query_by_impl<'tree>(mut iter: impl Iterator>) -> Option> { - let result = iter.next(); - - if let Some(second) = iter.next() { - let first = result?; - panic!( - "Found two or more nodes matching the query: {:?} {:?}", - first.node().id(), - second.node().id(), - ); - } - result -} - -pub trait Queryable<'tree, 'node> { - fn node(&'node self) -> crate::Node<'tree>; - - fn query_all_by( - &'node self, - f: impl Fn(&Node<'_>) -> bool + 'tree, - ) -> impl Iterator> - + DoubleEndedIterator> - + FusedIterator> - + 'tree { - let root = self.node(); - let queue = root.queue(); - root.filtered_children(move |node: &AKNode<'_>| { - if f(&Node::new(*node, queue)) { - FilterResult::Include - } else { - FilterResult::ExcludeNode - } - }) - .map(|node| Node::new(node, queue)) - } - - fn query_by(&'node self, f: impl Fn(&Node<'_>) -> bool + 'tree) -> Option> { - query_by_impl(self.query_all_by(f)) - } - - fn get_by(&'node self, f: impl Fn(&Node<'_>) -> bool + 'tree) -> Node<'tree> { - self.query_by(f).expect("No node found matching the query") - } - - fn query_all_by_name(&'node self, name: &'tree str) -> impl IterType<'tree> + 'tree { - self.query_all_by(move |node| node.name().as_deref() == Some(name)) - } - - fn query_by_name(&'node self, name: &'tree str) -> Option> { - query_by_impl(self.query_all_by_name(name)) - } - - fn get_by_name(&'node self, name: &'tree str) -> Node<'tree> { - self.query_by_name(name) - .expect("No node found with the given name") - } - - fn query_all_by_role(&'node self, role: accesskit::Role) -> impl IterType<'tree> + 'tree { - self.query_all_by(move |node| node.role() == role) - } - - fn query_by_role(&'node self, role: accesskit::Role) -> Option> { - query_by_impl(self.query_all_by_role(role)) - } - - fn get_by_role(&'node self, role: accesskit::Role) -> Node<'tree> { - self.query_by_role(role) - .expect("No node found with the given role") - } -} - -trait IterType<'tree>: - Iterator> - + DoubleEndedIterator> - + FusedIterator> -{ -} - -impl<'tree, T> IterType<'tree> for T where - T: Iterator> - + DoubleEndedIterator> - + FusedIterator> -{ -} - -// pub trait Findable<'tree, 'node, 's>: Queryable<'tree, 'node> { -// fn run(&mut self); -// -// fn find_timeout(&self) -> std::time::Duration { -// std::time::Duration::from_secs(5) -// } -// -// fn find_all_by( -// &'node mut self, -// f: impl Fn(&Node<'_>) -> bool + Copy + 'tree, -// ) -> impl IterType<'tree> + 'tree { -// let timeout = self.find_timeout(); -// let step = timeout / 10; -// -// let mut start_time = std::time::Instant::now(); -// -// loop { -// { -// let node = self.node(); -// let iter = node.query_all_by(f); -// let mut peekable = iter.peekable(); -// if !peekable.peek().is_none() { -// return peekable; -// } -// -// if start_time.elapsed() > timeout { -// panic!("Timeout exceeded while waiting for node"); -// } -// } -// -// std::thread::sleep(step); -// } -// } -// } diff --git a/crates/accesskit_query/src/tree.rs b/crates/accesskit_query/src/tree.rs deleted file mode 100644 index 7f6d98f21bb..00000000000 --- a/crates/accesskit_query/src/tree.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::event::AKEvent; -use crate::query::Queryable; -use crate::Node; -use std::ops::Deref; -use std::sync::Mutex; - -pub struct Tree { - tree: accesskit_consumer::Tree, - queued_events: Mutex>, -} - -impl Tree { - pub fn new(update: accesskit::TreeUpdate) -> Tree { - Self { - tree: accesskit_consumer::Tree::new(update, true), - queued_events: Mutex::new(Vec::new()), - } - } - - pub fn update(&mut self, update: accesskit::TreeUpdate) { - self.tree.update(update); - } - - pub fn root(&self) -> Node<'_> { - self.node() - } - - pub fn take_events(&self) -> Vec { - self.queued_events.lock().unwrap().drain(..).collect() - } -} - -impl<'tree, 'node> Queryable<'tree, 'node> for Tree -where - 'node: 'tree, -{ - /// Return the root node - fn node(&'node self) -> Node<'tree> where { - Node::new(self.tree.state().root(), &self.queued_events) - } -} diff --git a/crates/egui_demo_lib/Cargo.toml b/crates/egui_demo_lib/Cargo.toml index 915525aa1e6..38ff19fe906 100644 --- a/crates/egui_demo_lib/Cargo.toml +++ b/crates/egui_demo_lib/Cargo.toml @@ -57,7 +57,7 @@ serde = { workspace = true, optional = true } [dev-dependencies] criterion.workspace = true -etest = { path = "../etest", features = ["wgpu", "snapshot"] } +egui_kittest = { path = "../egui_kittest", features = ["wgpu", "snapshot"] } wgpu = { workspace = true, features = ["metal"] } egui = { workspace = true, features = ["default_fonts"] } diff --git a/crates/egui_demo_lib/src/demo/widget_gallery.rs b/crates/egui_demo_lib/src/demo/widget_gallery.rs index a39c5d5da36..85b9e3a4fa6 100644 --- a/crates/egui_demo_lib/src/demo/widget_gallery.rs +++ b/crates/egui_demo_lib/src/demo/widget_gallery.rs @@ -291,9 +291,9 @@ mod tests { use super::*; use crate::View; use egui::{CentralPanel, Context, Vec2}; - use etest::snapshot::image_snapshot; - use etest::wgpu::TestRenderer; - use etest::Harness; + use egui_kittest::snapshot::image_snapshot; + use egui_kittest::wgpu::TestRenderer; + use egui_kittest::Harness; #[test] pub fn should_match_screenshot() { @@ -303,11 +303,11 @@ mod tests { demo.ui(ui); }); }; - let mut harness = Harness::new() + let mut harness = Harness::new(app) .with_size(Vec2::new(380.0, 550.0)) .with_dpi(2.0); - harness.run(app); + harness.run(); let image = TestRenderer::new().render(&harness); diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.new.webp b/crates/egui_demo_lib/tests/snapshots/widget_gallery.new.webp deleted file mode 100644 index c4f58f5e129..00000000000 Binary files a/crates/egui_demo_lib/tests/snapshots/widget_gallery.new.webp and /dev/null differ diff --git a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png b/crates/egui_demo_lib/tests/snapshots/widget_gallery.png deleted file mode 100644 index c9b77c37052..00000000000 Binary files a/crates/egui_demo_lib/tests/snapshots/widget_gallery.png and /dev/null differ diff --git a/crates/etest/Cargo.toml b/crates/egui_kittest/Cargo.toml similarity index 90% rename from crates/etest/Cargo.toml rename to crates/egui_kittest/Cargo.toml index a72fdfaf481..75185b4ae74 100644 --- a/crates/etest/Cargo.toml +++ b/crates/egui_kittest/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "etest" +name = "egui_kittest" edition.workspace = true license.workspace = true rust-version.workspace = true @@ -13,7 +13,7 @@ snapshot = ["dep:dify", "dep:dify_image"] [dependencies] accesskit_consumer = "0.24.0" -accesskit_query = { path = "../accesskit_query" } +kittest = { git = "https://github.com/rerun-io/kittest", branch = "kittest" } egui = { workspace = true, features = ["accesskit"] } # wgpu dependencies diff --git a/crates/etest/examples/etest.rs b/crates/egui_kittest/examples/kittest.rs similarity index 76% rename from crates/etest/examples/etest.rs rename to crates/egui_kittest/examples/kittest.rs index b102bbf2129..bd46d798473 100644 --- a/crates/etest/examples/etest.rs +++ b/crates/egui_kittest/examples/kittest.rs @@ -1,7 +1,7 @@ -use accesskit_query::Queryable; use egui::accesskit::{Role, Toggled}; use egui::{CentralPanel, Context, TextEdit, Vec2}; -use etest::Harness; +use egui_kittest::Harness; +use kittest::Queryable; use std::cell::RefCell; fn main() { @@ -16,13 +16,13 @@ fn main() { }); }; - let mut harness = Harness::new().with_size(Vec2::new(200.0, 100.0)); + let mut harness = Harness::new(app).with_size(Vec2::new(200.0, 100.0)); - harness.run(&mut app); + harness.run(); harness.get_by_name("Check me!").click(); - harness.run(&mut app); + harness.run(); assert!(*checked.borrow()); let checkbox = harness.get_by_name("Check me!"); @@ -32,7 +32,7 @@ fn main() { .get_by_role(Role::TextInput) .type_text("Hello, World!"); - harness.run(&mut app); + harness.run(); assert_eq!(&*text.borrow_mut(), "Hello, World!"); assert_eq!( @@ -42,9 +42,9 @@ fn main() { #[cfg(feature = "wgpu")] { - let mut renderer = etest::wgpu::TestRenderer::new(); + let mut renderer = egui_kittest::wgpu::TestRenderer::new(); let image = renderer.render(&harness); - image.save("crates/etest/etest.png").unwrap(); + image.save("../kittest.png").unwrap(); } } diff --git a/crates/etest/etest.png b/crates/egui_kittest/kittest.png similarity index 100% rename from crates/etest/etest.png rename to crates/egui_kittest/kittest.png diff --git a/crates/etest/src/lib.rs b/crates/egui_kittest/src/lib.rs similarity index 89% rename from crates/etest/src/lib.rs rename to crates/egui_kittest/src/lib.rs index 206bbf4b2dc..a29cb9924db 100644 --- a/crates/etest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -8,30 +8,28 @@ pub mod wgpu; use crate::utils::egui_vec2; pub use accesskit_consumer; -use accesskit_query::{AKEvent, Node, Queryable, SimulatedEvent, Tree}; use egui::accesskit::NodeId; use egui::{Pos2, Rect, TexturesDelta, Vec2}; +use kittest::{Node, Queryable, SimulatedEvent, Tree}; +use std::iter; +use std::time::Duration; -pub struct Harness { +pub struct Harness<'a> { pub ctx: egui::Context, input: egui::RawInput, tree: Option, output: Option, texture_deltas: Vec, + update_fn: Box, } -impl Default for Harness { - fn default() -> Self { - Self::new() - } -} - -impl Harness { - pub fn new() -> Self { +impl<'a> Harness<'a> { + pub fn new(app: impl FnMut(&egui::Context) + 'a) -> Self { let ctx = egui::Context::default(); ctx.enable_accesskit(); 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))), @@ -57,16 +55,16 @@ impl Harness { self } - pub fn run(&mut self, app: impl FnMut(&egui::Context)) { + pub fn run(&mut self) { if let Some(tree) = &mut self.tree { for event in tree.take_events() { match event { - AKEvent::ActionRequest(e) => { + kittest::Event::ActionRequest(e) => { self.input .events .push(egui::Event::AccessKitActionRequest(e)); } - AKEvent::Simulated(e) => match e { + kittest::Event::Simulated(e) => match e { SimulatedEvent::Click { position } => { let position = egui_vec2(position).to_pos2(); self.input.events.push(egui::Event::PointerButton { @@ -89,7 +87,7 @@ impl Harness { } } } - let mut output = self.ctx.run(self.input.take(), app); + let mut output = self.ctx.run(self.input.take(), self.update_fn.as_mut()); if let Some(tree) = &mut self.tree { tree.update( output @@ -168,7 +166,7 @@ impl Harness { } } -impl<'t, 'n> Queryable<'t, 'n> for Harness +impl<'t, 'n, 'h> Queryable<'t, 'n> for Harness<'h> where 'n: 't, { diff --git a/crates/etest/src/snapshot.rs b/crates/egui_kittest/src/snapshot.rs similarity index 82% rename from crates/etest/src/snapshot.rs rename to crates/egui_kittest/src/snapshot.rs index c7b9d8d53b0..ff76fe69c33 100644 --- a/crates/etest/src/snapshot.rs +++ b/crates/egui_kittest/src/snapshot.rs @@ -4,8 +4,8 @@ pub fn image_snapshot(current: image::RgbaImage, name: &str) { .unwrap(); let path = format!("tests/snapshots/{name}.png"); - let diff_path = format!("tests/snapshots/{name}.diff.webp"); - let current_path = format!("tests/snapshots/{name}.new.webp"); + let diff_path = format!("tests/snapshots/{name}.diff.png"); + let current_path = format!("tests/snapshots/{name}.new.png"); std::fs::create_dir_all("tests/snapshots").ok(); @@ -31,9 +31,7 @@ pub fn image_snapshot(current: image::RgbaImage, name: &str) { current.save(&path).unwrap(); println!("Updated snapshot: {path}"); } else { - panic!( - "Image did not match snapshot. Diff: {diff}, {diff_path}" - ); + panic!("Image did not match snapshot. Diff: {diff}, {diff_path}"); } } else { // Delete old diff if it exists diff --git a/crates/etest/src/texture_to_bytes.rs b/crates/egui_kittest/src/texture_to_bytes.rs similarity index 100% rename from crates/etest/src/texture_to_bytes.rs rename to crates/egui_kittest/src/texture_to_bytes.rs diff --git a/crates/etest/src/utils.rs b/crates/egui_kittest/src/utils.rs similarity index 100% rename from crates/etest/src/utils.rs rename to crates/egui_kittest/src/utils.rs diff --git a/crates/etest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs similarity index 99% rename from crates/etest/src/wgpu.rs rename to crates/egui_kittest/src/wgpu.rs index 68a31ffc9f6..8dcf51500c7 100644 --- a/crates/etest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -6,7 +6,7 @@ use image::RgbaImage; use std::iter::once; use wgpu::Maintain; -impl Harness { +impl<'a> Harness<'a> { pub fn image(&self, _renderer: TestRenderer) {} }