Skip to content

Commit 82be366

Browse files
committed
Add SnapshotResults struct to egui_kittest
1 parent 9936966 commit 82be366

File tree

8 files changed

+135
-46
lines changed

8 files changed

+135
-46
lines changed

crates/egui_demo_app/tests/test_demo_app.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use egui::accesskit::Role;
22
use egui::Vec2;
33
use egui_demo_app::{Anchor, WrapApp};
44
use egui_kittest::kittest::Queryable;
5+
use egui_kittest::SnapshotResults;
56

67
#[test]
78
fn test_demo_app() {
@@ -27,7 +28,7 @@ fn test_demo_app() {
2728
"Expected to find the Custom3d app.",
2829
);
2930

30-
let mut results = vec![];
31+
let mut results = SnapshotResults::new();
3132

3233
for (name, anchor) in apps {
3334
harness.get_by_role_and_label(Role::Button, name).click();
@@ -68,12 +69,8 @@ fn test_demo_app() {
6869
// Can't use Harness::run because fractal clock keeps requesting repaints
6970
harness.run_steps(2);
7071

71-
if let Err(e) = harness.try_snapshot(&anchor.to_string()) {
72-
results.push(e);
73-
}
72+
results.add(harness.try_snapshot(&anchor.to_string()));
7473
}
7574

76-
if let Some(error) = results.first() {
77-
panic!("{error}");
78-
}
75+
results.unwrap();
7976
}

crates/egui_demo_lib/src/demo/demo_app_windows.rs

+4-7
Original file line numberDiff line numberDiff line change
@@ -365,13 +365,13 @@ mod tests {
365365
use crate::{demo::demo_app_windows::DemoGroups, Demo};
366366
use egui::Vec2;
367367
use egui_kittest::kittest::Queryable;
368-
use egui_kittest::{Harness, SnapshotOptions};
368+
use egui_kittest::{Harness, SnapshotOptions, SnapshotResults};
369369

370370
#[test]
371371
fn demos_should_match_snapshot() {
372372
let demos = DemoGroups::default().demos;
373373

374-
let mut errors = Vec::new();
374+
let mut results = SnapshotResults::new();
375375

376376
for mut demo in demos.demos {
377377
// Widget Gallery needs to be customized (to set a specific date) and has its own test
@@ -405,12 +405,9 @@ mod tests {
405405
options.threshold = 2.1;
406406
}
407407

408-
let result = harness.try_snapshot_options(&format!("demos/{name}"), &options);
409-
if let Err(err) = result {
410-
errors.push(err.to_string());
411-
}
408+
results.add(harness.try_snapshot_options(&format!("demos/{name}"), &options));
412409
}
413410

414-
assert!(errors.is_empty(), "Errors: {errors:#?}");
411+
results.unwrap();
415412
}
416413
}

crates/egui_demo_lib/src/demo/modals.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ mod tests {
165165
use egui::accesskit::Role;
166166
use egui::Key;
167167
use egui_kittest::kittest::Queryable;
168-
use egui_kittest::Harness;
168+
use egui_kittest::{Harness, SnapshotResults};
169169

170170
#[test]
171171
fn clicking_escape_when_popup_open_should_not_close_modal() {
@@ -233,22 +233,20 @@ mod tests {
233233
initial_state,
234234
);
235235

236-
let mut results = Vec::new();
236+
let mut results = SnapshotResults::new();
237237

238238
harness.run();
239-
results.push(harness.try_snapshot("modals_1"));
239+
results.add(harness.try_snapshot("modals_1"));
240240

241241
harness.get_by_label("Save").click();
242242
harness.run_ok();
243-
results.push(harness.try_snapshot("modals_2"));
243+
results.add(harness.try_snapshot("modals_2"));
244244

245245
harness.get_by_label("Yes Please").click();
246246
harness.run_ok();
247-
results.push(harness.try_snapshot("modals_3"));
247+
results.add(harness.try_snapshot("modals_3"));
248248

249-
for result in results {
250-
result.unwrap();
251-
}
249+
results.unwrap();
252250
}
253251

254252
// This tests whether the backdrop actually prevents interaction with lower layers.

crates/egui_demo_lib/src/demo/widget_gallery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct WidgetGallery {
2323
#[cfg_attr(feature = "serde", serde(skip))]
2424
date: Option<chrono::NaiveDate>,
2525

26+
#[cfg(feature = "chrono")]
2627
with_date_button: bool,
2728
}
2829

crates/egui_demo_lib/src/rendering_test.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -688,10 +688,11 @@ fn mul_color_gamma(left: Color32, right: Color32) -> Color32 {
688688
mod tests {
689689
use crate::ColorTest;
690690
use egui_kittest::kittest::Queryable as _;
691+
use egui_kittest::SnapshotResults;
691692

692693
#[test]
693694
pub fn rendering_test() {
694-
let mut errors = vec![];
695+
let mut results = SnapshotResults::new();
695696
for dpi in [1.0, 1.25, 1.5, 1.75, 1.6666667, 2.0] {
696697
let mut color_test = ColorTest::default();
697698
let mut harness = egui_kittest::Harness::builder()
@@ -708,12 +709,9 @@ mod tests {
708709

709710
harness.fit_contents();
710711

711-
let result = harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}"));
712-
if let Err(err) = result {
713-
errors.push(err);
714-
}
712+
results.add(harness.try_snapshot(&format!("rendering_test/dpi_{dpi:.2}")));
715713
}
716714

717-
assert!(errors.is_empty(), "Errors: {errors:#?}");
715+
results.unwrap();
718716
}
719717
}

crates/egui_kittest/src/snapshot.rs

+104-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::fmt::Display;
44
use std::io::ErrorKind;
55
use std::path::PathBuf;
66

7+
pub type SnapshotResult = Result<(), SnapshotError>;
8+
79
#[non_exhaustive]
810
pub struct SnapshotOptions {
911
/// The threshold for the image comparison.
@@ -189,7 +191,7 @@ pub fn try_image_snapshot_options(
189191
new: &image::RgbaImage,
190192
name: &str,
191193
options: &SnapshotOptions,
192-
) -> Result<(), SnapshotError> {
194+
) -> SnapshotResult {
193195
let SnapshotOptions {
194196
threshold,
195197
output_path,
@@ -301,7 +303,7 @@ pub fn try_image_snapshot_options(
301303
/// # Errors
302304
/// Returns a [`SnapshotError`] if the image does not match the snapshot or if there was an error
303305
/// reading or writing the snapshot.
304-
pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> Result<(), SnapshotError> {
306+
pub fn try_image_snapshot(current: &image::RgbaImage, name: &str) -> SnapshotResult {
305307
try_image_snapshot_options(current, name, &SnapshotOptions::default())
306308
}
307309

@@ -373,7 +375,7 @@ impl<State> Harness<'_, State> {
373375
&mut self,
374376
name: &str,
375377
options: &SnapshotOptions,
376-
) -> Result<(), SnapshotError> {
378+
) -> SnapshotResult {
377379
let image = self
378380
.render()
379381
.map_err(|err| SnapshotError::RenderError { err })?;
@@ -388,7 +390,7 @@ impl<State> Harness<'_, State> {
388390
/// # Errors
389391
/// Returns a [`SnapshotError`] if the image does not match the snapshot, if there was an
390392
/// error reading or writing the snapshot, if the rendering fails or if no default renderer is available.
391-
pub fn try_snapshot(&mut self, name: &str) -> Result<(), SnapshotError> {
393+
pub fn try_snapshot(&mut self, name: &str) -> SnapshotResult {
392394
let image = self
393395
.render()
394396
.map_err(|err| SnapshotError::RenderError { err })?;
@@ -455,15 +457,15 @@ impl<State> Harness<'_, State> {
455457
&mut self,
456458
name: &str,
457459
options: &SnapshotOptions,
458-
) -> Result<(), SnapshotError> {
460+
) -> SnapshotResult {
459461
self.try_snapshot_options(name, options)
460462
}
461463

462464
#[deprecated(
463465
since = "0.31.0",
464466
note = "Use `try_snapshot` instead. This function will be removed in 0.32"
465467
)]
466-
pub fn try_wgpu_snapshot(&mut self, name: &str) -> Result<(), SnapshotError> {
468+
pub fn try_wgpu_snapshot(&mut self, name: &str) -> SnapshotResult {
467469
self.try_snapshot(name)
468470
}
469471

@@ -483,3 +485,99 @@ impl<State> Harness<'_, State> {
483485
self.snapshot(name);
484486
}
485487
}
488+
489+
/// Utility to collect snapshot errors and display them at the end of the test.
490+
///
491+
/// # Example
492+
/// ```
493+
/// # let harness = MockHarness;
494+
/// # struct MockHarness;
495+
/// # impl MockHarness {
496+
/// # fn try_snapshot(&self, _: &str) -> Result<(), egui_kittest::SnapshotError> { Ok(()) }
497+
/// # }
498+
///
499+
/// // [...] Construct a Harness
500+
///
501+
/// let mut results = egui_kittest::SnapshotResults::new();
502+
///
503+
/// // Call add for each snapshot in your test
504+
/// results.add(harness.try_snapshot("my_test"));
505+
///
506+
/// // At the end of the test, fail if there are any errors
507+
/// results.unwrap();
508+
/// ```
509+
///
510+
/// # Panics
511+
/// Panics if there are any errors when dropped (this way it is impossible to forget to call `unwrap`).
512+
#[derive(Debug, Default)]
513+
pub struct SnapshotResults {
514+
errors: Vec<SnapshotError>,
515+
}
516+
517+
impl Display for SnapshotResults {
518+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519+
if self.errors.is_empty() {
520+
write!(f, "All snapshots passed")
521+
} else {
522+
writeln!(f, "Snapshot errors:")?;
523+
for error in &self.errors {
524+
writeln!(f, " {error}")?;
525+
}
526+
Ok(())
527+
}
528+
}
529+
}
530+
531+
impl SnapshotResults {
532+
pub fn new() -> Self {
533+
Default::default()
534+
}
535+
536+
/// Check if the result is an error and add it to the list of errors.
537+
pub fn add(&mut self, result: SnapshotResult) {
538+
if let Err(err) = result {
539+
self.errors.push(err);
540+
}
541+
}
542+
543+
/// Check if there are any errors.
544+
pub fn has_errors(&self) -> bool {
545+
!self.errors.is_empty()
546+
}
547+
548+
/// Convert this into a `Result<(), Self>`.
549+
#[allow(clippy::missing_errors_doc)]
550+
pub fn into_result(self) -> Result<(), Self> {
551+
if self.has_errors() {
552+
Err(self)
553+
} else {
554+
Ok(())
555+
}
556+
}
557+
558+
pub fn into_inner(mut self) -> Vec<SnapshotError> {
559+
std::mem::take(&mut self.errors)
560+
}
561+
562+
/// Panics if there are any errors, displaying each.
563+
#[allow(clippy::unused_self)]
564+
pub fn unwrap(self) {
565+
// Panic is handled in drop
566+
}
567+
}
568+
569+
impl From<SnapshotResults> for Vec<SnapshotError> {
570+
fn from(results: SnapshotResults) -> Self {
571+
results.into_inner()
572+
}
573+
}
574+
575+
impl Drop for SnapshotResults {
576+
fn drop(&mut self) {
577+
// Don't panic if we are already panicking (the test probably failed for another reason)
578+
if std::thread::panicking() {
579+
return;
580+
}
581+
assert!(!self.has_errors(), "{}", self);
582+
}
583+
}

crates/egui_kittest/tests/regression_tests.rs

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use egui::accesskit::Role;
22
use egui::{Button, ComboBox, Image, Vec2, Widget};
3-
use egui_kittest::{kittest::Queryable, Harness};
3+
use egui_kittest::{kittest::Queryable, Harness, SnapshotResults};
44

55
#[test]
66
pub fn focus_should_skip_over_disabled_buttons() {
@@ -64,18 +64,18 @@ fn test_combobox() {
6464

6565
harness.run();
6666

67-
let mut results = vec![];
67+
let mut results = SnapshotResults::new();
6868

6969
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
70-
results.push(harness.try_snapshot("combobox_closed"));
70+
results.add(harness.try_snapshot("combobox_closed"));
7171

7272
let combobox = harness.get_by_role_and_label(Role::ComboBox, "Select Something");
7373
combobox.click();
7474

7575
harness.run();
7676

7777
#[cfg(all(feature = "wgpu", feature = "snapshot"))]
78-
results.push(harness.try_snapshot("combobox_opened"));
78+
results.add(harness.try_snapshot("combobox_opened"));
7979

8080
let item_2 = harness.get_by_role_and_label(Role::Button, "Item 2");
8181
// Node::click doesn't close the popup, so we use simulate_click
@@ -88,9 +88,5 @@ fn test_combobox() {
8888
// Popup should be closed now
8989
assert!(harness.query_by_label("Item 2").is_none());
9090

91-
for result in results {
92-
if let Err(err) = result {
93-
panic!("{}", err);
94-
}
95-
}
91+
results.unwrap();
9692
}

crates/egui_kittest/tests/tests.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
use egui_kittest::Harness;
1+
use egui_kittest::{Harness, SnapshotResults};
22

33
#[test]
44
fn test_shrink() {
55
let mut harness = Harness::new_ui(|ui| {
66
ui.label("Hello, world!");
77
ui.separator();
8-
ui.label("This is a test");
8+
ui.label("This is a tesst");
99
});
1010

1111
harness.fit_contents();
1212

13+
let mut results = SnapshotResults::new();
14+
1315
#[cfg(all(feature = "snapshot", feature = "wgpu"))]
14-
harness.snapshot("test_shrink");
16+
results.add(harness.try_snapshot("test_shrink"));
17+
18+
panic!("test")
1519
}

0 commit comments

Comments
 (0)