Skip to content

Commit

Permalink
Add snapshot integration tests using egui_kittest (#1)
Browse files Browse the repository at this point in the history
Builds the app, creates a snapshot, compares with the expected one.

Snapshots are disabled on Win/Ubuntu runners on GH actions that don't
support wgpu.
  • Loading branch information
MichaelGrupp authored Feb 3, 2025
1 parent 96302d5 commit c7ed234
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tests/snapshots/*png filter=lfs diff=lfs merge=lfs -text
38 changes: 31 additions & 7 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,56 @@ on:
branches: [ "master" ]

env:
RUST_LOG: "maps=debug"
CARGO_TERM_COLOR: always

jobs:
ubuntu:
runs-on: [ubuntu-latest]
macos:
runs-on: [macos-latest]
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Format check
run: cargo fmt --all -- --check
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
run: cargo test --verbose -- --show-output
- name: Upload new kittest snapshots
uses: actions/upload-artifact@v4
if: always()
with:
name: kittest-snapshots
path: |
tests/snapshots/
macos:
runs-on: [macos-latest]
ubuntu:
runs-on: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Format check
run: cargo fmt --all -- --check
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
# Run tests without kittest_snapshots feature
# (no wgpu support in CI runner).
run: cargo test --verbose --no-default-features -- --show-output

windows:
runs-on: [windows-latest]
steps:
- uses: actions/checkout@v4
with:
lfs: true
- name: Format check
run: cargo fmt --all -- --check
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
# Run tests without kittest_snapshots feature
# (no wgpu support in CI runner).
run: cargo test --verbose --no-default-features -- --show-output
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/target
/assets

/tests/snapshots/*new.png
/tests/snapshots/*diff.png
80 changes: 77 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ edition = "2021"
readme = "README.md"
build = "build.rs"

[features]
# kittest snapshot diff tests are enabled by default (see tests/).
# Disable when testing on a runner without wgpu support.
kittest_snapshots = []
default = ["kittest_snapshots"]

[build-dependencies]
built = { version = "0.7.5", features = ["git2"] }

Expand All @@ -21,6 +27,7 @@ confy = "0.6.1"
eframe = {version = "0.30.0", features = ["wgpu"]}
egui_extras = { version = "0.30.0", features = ["all_loaders"] }
egui-file-dialog = "0.8.0"
egui_kittest = { version = "0.30.0", features = ["wgpu", "snapshot"] }
egui_tiles = "0.11.0"
env_logger = "0.11.5"
fast_image_resize = { version = "5.1.1", features = ["image"] }
Expand Down
11 changes: 6 additions & 5 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ use egui_file_dialog::FileDialog;
use serde::{Deserialize, Serialize};
use strum_macros::{Display, EnumString, VariantNames};

use crate::app_impl::canvas_settings::CanvasOptions;
use crate::app_impl::pose_edit::PoseEditOptions;
use crate::app_impl::tint_settings::TintOptions;
use crate::grid_options::GridOptions;
use crate::lens::LensOptions;
pub use crate::app_impl::canvas_settings::CanvasOptions;
pub use crate::app_impl::pose_edit::PoseEditOptions;
pub use crate::app_impl::tint_settings::TintOptions;
pub use crate::grid_options::GridOptions;
pub use crate::lens::LensOptions;

use crate::map_state::MapState;
use crate::meta::Meta;
use crate::persistence::{save_app_options, PersistenceOptions};
Expand Down
29 changes: 29 additions & 0 deletions tests/aligned_view_kittest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
mod kittest_common;

use eframe::egui;
use env_logger;

use kittest_common::*;
use maps::app::{AppOptions, AppState, TintOptions};

#[test]
fn main() {
env_logger::init();

let app_state = AppState::init(
vec![
_load_meta_with_fake_path(TEST_META_0),
_load_meta_with_fake_path(TEST_META_1),
],
AppOptions {
tint_settings: TintOptions {
tint_for_all: egui::Color32::from_rgba_unmultiplied(255, 255, 255, 127),
..Default::default()
},
..Default::default()
},
)
.expect("Failed to initialize AppState");

snapshot_full_app(app_state, "aligned_view", egui::Vec2::new(1000., 750.));
}
21 changes: 21 additions & 0 deletions tests/empty_kittest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
mod kittest_common;

use eframe::egui;

use kittest_common::*;
use maps::app::{AppOptions, AppState};

#[test]
fn empty_start() {
env_logger::init();

let app_state = AppState::init(
vec![],
AppOptions {
..Default::default()
},
)
.expect("Failed to initialize AppState");

snapshot_full_app(app_state, "empty_start", egui::Vec2::new(1000., 750.));
}
73 changes: 73 additions & 0 deletions tests/kittest_common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::path::PathBuf;

use eframe::egui;
use eframe::egui::Context;
use egui_kittest::Harness;

use maps::app::AppState;
use maps::meta::Meta;

// Expects that cargo test is run from the root of the repository.
#[allow(dead_code)]
pub const TEST_META_0: &str = "data/dummy_maps/dummy_map_lores.yaml";
#[allow(dead_code)]
pub const TEST_META_1: &str = "data/dummy_maps/dummy_map_rot.yaml";

const PIXELS_PER_POINT: f32 = 1.;

/// Spins up the full app state UI.
/// Does a snapshot diff test unless the "kittest_snapshots" feature is disabled.
/// To create/update baseline snapshots, run: UPDATE_SNAPSHOTS=1 cargo test
pub fn snapshot_full_app(mut app_state: AppState, test_name: &str, size: egui::Vec2) {
let app_closure = |ctx: &Context| {
// This is copypasta from AppState::update().
// TODO: refactor this once egui_kittest can run eframe::App.
// Waiting for this to be released:
// https://github.com/emilk/egui/commit/46b58e5bcca0bb34861b5671958be872419bee90
egui::CentralPanel::default().show(ctx, |ui| {
app_state.error_modal(ui);
app_state.quit_modal(ui);
app_state.handle_key_shortcuts(ui);

app_state.header_panel(ui);
app_state.menu_panel(ui);
app_state.footer_panel(ui);
app_state.settings_panel(ui);
app_state.central_panel(ui);

app_state.info_window(ui);
});

if ctx.input(|i| i.viewport().close_requested()) {
if app_state.status.unsaved_changes {
ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose);
app_state.status.quit_modal_active = true;
}
}
};

let mut harness = Harness::builder()
.with_size(size)
.with_pixels_per_point(PIXELS_PER_POINT)
.build(app_closure);
harness.run();

#[cfg(feature = "kittest_snapshots")]
harness.wgpu_snapshot(test_name);

#[cfg(not(feature = "kittest_snapshots"))]
println!(
"Snapshot diff test for {} skipped. \
Enable the 'kittest_snapshots' feature to run it.",
test_name
);
}

/// Load the metadata with faked absolute YAML path.
/// Allows to have runner-agnostic snapshots when paths are shown in the UI.
pub fn _load_meta_with_fake_path(meta_path: &str) -> Meta {
let mut meta = Meta::load_from_file(&PathBuf::from(meta_path)).expect("Failed to load map");
let fake_parent = PathBuf::from("/fake_path_for_testing/");
meta.yaml_path = fake_parent.join(meta.yaml_path.file_name().unwrap());
meta
}
3 changes: 3 additions & 0 deletions tests/snapshots/aligned_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c7ed234

Please sign in to comment.