Skip to content

Commit

Permalink
Add Harness::fit_contents and add rendering_test snapshot test
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Oct 16, 2024
1 parent e824e01 commit 7cad2da
Show file tree
Hide file tree
Showing 16 changed files with 192 additions and 24 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,7 @@ dependencies = [
"document-features",
"egui",
"egui-wgpu",
"egui_kittest",
"image",
"kittest",
"pollster",
Expand Down
13 changes: 5 additions & 8 deletions crates/egui_demo_lib/src/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,12 @@ mod tests {
date: Some(chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()),
..Default::default()
};
let app = |ctx: &Context| {
CentralPanel::default().show(ctx, |ui| {
demo.ui(ui);
});
};
let harness = Harness::builder()
.with_size(Vec2::new(380.0, 550.0))
let mut harness = Harness::builder()
.with_dpi(2.0)
.build(app);
.with_size(Vec2::new(380.0, 550.0))
.build_ui(|ui| demo.ui(ui));

harness.fit_contents();

harness.wgpu_snapshot("widget_gallery");
}
Expand Down
38 changes: 34 additions & 4 deletions crates/egui_demo_lib/src/rendering_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -435,10 +435,7 @@ fn pixel_test_strokes(ui: &mut Ui) {
let thickness_pixels = thickness_pixels as f32;
let thickness_points = thickness_pixels / pixels_per_point;
let num_squares = (pixels_per_point * 10.0).round().max(10.0) as u32;
let size_pixels = vec2(
ui.available_width(),
num_squares as f32 + thickness_pixels * 2.0,
);
let size_pixels = vec2(ui.min_size().x, num_squares as f32 + thickness_pixels * 2.0);
let size_points = size_pixels / pixels_per_point + Vec2::splat(2.0);
let (response, painter) = ui.allocate_painter(size_points, Sense::hover());

Expand Down Expand Up @@ -680,3 +677,36 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 {
(left.a() as f32 * right.a() as f32 / 255.0).round() as u8,
)
}

#[cfg(test)]
mod tests {
use crate::ColorTest;
use egui::vec2;

#[test]
pub fn rendering_test() {
let mut errors = vec![];
for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] {
let mut color_test = ColorTest::default();
let mut harness = egui_kittest::Harness::builder()
.with_size(vec2(2000.0, 2000.0))
.with_dpi(dpi)
.build_ui(|ui| {
color_test.ui(ui);
});

//harness.set_size(harness.ctx.used_size());

harness.fit_contents();

let result = harness.try_wgpu_snapshot(&format!("rendering_test/dpi_{:.2}", dpi));
if let Err(err) = result {
errors.push(err);
}
}

if !errors.is_empty() {
panic!("Errors: {:#?}", errors);
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions crates/egui_demo_lib/tests/snapshots/widget_gallery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions crates/egui_kittest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dify = { workspace = true, optional = true }
document-features = { workspace = true, optional = true }

[dev-dependencies]
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
wgpu = { workspace = true, features = ["metal"] }
image = { workspace = true, features = ["png"] }
egui = { workspace = true, features = ["default_fonts"] }
Expand Down
7 changes: 6 additions & 1 deletion crates/egui_kittest/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::harness_kind::AppKind;
use crate::Harness;
use egui::{Pos2, Rect, Vec2};

Expand Down Expand Up @@ -50,6 +51,10 @@ impl HarnessBuilder {
/// });
/// ```
pub fn build<'a>(self, app: impl FnMut(&egui::Context) + 'a) -> Harness<'a> {
Harness::from_builder(&self, app)
Harness::from_builder(&self, AppKind::Context(Box::new(app)))
}

pub fn build_ui<'a>(self, app: impl FnMut(&mut egui::Ui) + 'a) -> Harness<'a> {
Harness::from_builder(&self, AppKind::Ui(Box::new(app)))
}
}
72 changes: 72 additions & 0 deletions crates/egui_kittest/src/harness_kind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use egui::Frame;

type AppKindContext<'a> = Box<dyn FnMut(&egui::Context) + 'a>;
type AppKindUi<'a> = Box<dyn FnMut(&mut egui::Ui) + 'a>;

pub enum AppKind<'a> {
Context(AppKindContext<'a>),
Ui(AppKindUi<'a>),
}

// TODO: These aren't working unfortunately :(
// I think they should work though: https://geo-ant.github.io/blog/2021/rust-traits-and-variadic-functions/
// pub trait IntoAppKind<'a, UiKind> {
// fn into_harness_kind(self) -> AppKind<'a>;
// }
//
// impl<'a, F> IntoAppKind<'a, &egui::Context> for F
// where
// F: FnMut(&egui::Context) + 'a,
// {
// fn into_harness_kind(self) -> AppKind<'a> {
// AppKind::Context(Box::new(self))
// }
// }
//
// impl<'a, F> IntoAppKind<'a, &mut egui::Ui> for F
// where
// F: FnMut(&mut egui::Ui) + 'a,
// {
// fn into_harness_kind(self) -> AppKind<'a> {
// AppKind::Ui(Box::new(self))
// }
// }

impl<'a> AppKind<'a> {
pub fn run(&mut self, ctx: &egui::Context) -> Option<egui::Response> {
match self {
AppKind::Context(f) => {
f(ctx);
None
}
AppKind::Ui(f) => Some(Self::run_ui(f, ctx, false)),
}
}

pub fn run_sizing_pass(&mut self, ctx: &egui::Context) -> Option<egui::Response> {
match self {
AppKind::Context(f) => {
f(ctx);
None
}
AppKind::Ui(f) => Some(Self::run_ui(f, ctx, true)),
}
}

fn run_ui(f: &mut AppKindUi<'a>, ctx: &egui::Context, sizing_pass: bool) -> egui::Response {
egui::CentralPanel::default()
.frame(
Frame::central_panel(&ctx.style())
.inner_margin(0.0)
.outer_margin(0.0),
)
.show(ctx, |ui| {
let mut builder = egui::UiBuilder::new();
if sizing_pass {
builder.sizing_pass = true;
}
ui.scope_builder(builder, f).response
})
.inner
}
}
45 changes: 36 additions & 9 deletions crates/egui_kittest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod snapshot;
#[cfg(feature = "snapshot")]
pub use snapshot::*;
use std::fmt::{Debug, Formatter};
pub mod harness_kind;
#[cfg(feature = "wgpu")]
mod texture_to_image;
#[cfg(feature = "wgpu")]
Expand All @@ -20,8 +21,9 @@ pub use kittest;
use std::mem;

use crate::event::EventState;
use crate::harness_kind::AppKind;
pub use builder::*;
use egui::{Pos2, Rect, TexturesDelta, Vec2, ViewportId};
use egui::{CentralPanel, Pos2, Rect, TexturesDelta, UiBuilder, Vec2, ViewportId};
use kittest::{Node, Queryable};

/// The test Harness. This contains everything needed to run the test.
Expand All @@ -32,8 +34,9 @@ pub struct Harness<'a> {
kittest: kittest::State,
output: egui::FullOutput,
texture_deltas: Vec<TexturesDelta>,
update_fn: Box<dyn FnMut(&egui::Context) + 'a>,
app: AppKind<'a>,
event_state: EventState,
response: Option<egui::Response>,
}

impl<'a> Debug for Harness<'a> {
Expand All @@ -43,10 +46,7 @@ impl<'a> Debug for Harness<'a> {
}

impl<'a> Harness<'a> {
pub(crate) fn from_builder(
builder: &HarnessBuilder,
mut app: impl FnMut(&egui::Context) + 'a,
) -> Self {
pub(crate) fn from_builder(builder: &HarnessBuilder, mut app: AppKind<'a>) -> Self {
let ctx = egui::Context::default();
ctx.enable_accesskit();
let mut input = egui::RawInput {
Expand All @@ -56,12 +56,16 @@ impl<'a> Harness<'a> {
let viewport = input.viewports.get_mut(&ViewportId::ROOT).unwrap();
viewport.native_pixels_per_point = Some(builder.dpi);

let mut response = None;

// 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(), &mut app);
let mut output = ctx.run(input.clone(), |ctx| {
response = app.run(ctx);
});

let mut harness = Self {
update_fn: Box::new(app),
app,
ctx,
input,
kittest: kittest::State::new(
Expand All @@ -73,6 +77,7 @@ impl<'a> Harness<'a> {
),
texture_deltas: vec![mem::take(&mut output.textures_delta)],
output,
response,
event_state: EventState::default(),
};
// Run the harness until it is stable, ensuring that all Areas are shown and animations are done
Expand Down Expand Up @@ -104,6 +109,10 @@ impl<'a> Harness<'a> {
Self::builder().build(app)
}

pub fn new_ui(app: impl FnMut(&mut egui::Ui) + 'a) -> Self {
Self::builder().build_ui(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`].
Expand All @@ -125,13 +134,23 @@ impl<'a> Harness<'a> {
/// Run a frame.
/// This will call the app closure with the current context and update the Harness.
pub fn step(&mut self) {
self._step(false);
}

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(), self.update_fn.as_mut());
let mut output = self.ctx.run(self.input.take(), |ctx| {
if sizing_pass {
self.response = self.app.run_sizing_pass(ctx);
} else {
self.response = self.app.run(ctx);
}
});
self.kittest.update(
output
.platform_output
Expand All @@ -144,6 +163,14 @@ impl<'a> Harness<'a> {
self.output = output;
}

pub fn fit_contents(&mut self) {
self._step(true);
if let Some(response) = &self.response {
self.set_size(response.rect.size());
}
self.run();
}

/// Run a few frames.
/// This will soon be changed to run the app until it is "stable", meaning
/// - all animations are done
Expand Down
3 changes: 3 additions & 0 deletions crates/egui_kittest/tests/snapshots/test_shrink.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions crates/egui_kittest/tests/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use egui_kittest::Harness;

#[test]
fn test_shrink() {
let mut harness = Harness::new_ui(|ui| {
ui.label("Hello, world!");
ui.separator();
ui.label("This is a test");
});

harness.fit_contents();

harness.wgpu_snapshot("test_shrink");
}

0 comments on commit 7cad2da

Please sign in to comment.