Skip to content

Commit d4f8950

Browse files
authored
Kittest utility toolbelt (#11435)
### Related * Part of #8948 ### What - Allows interacting with app state from an integration test using a test hook function - Adds a bunch of Rerun-specific utility functions to Kittest - Replaces a context menu test from the release checklist as a proof of concept
1 parent 0a2b665 commit d4f8950

21 files changed

+414
-16
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8896,7 +8896,9 @@ dependencies = [
88968896
"re_sdk",
88978897
"re_server",
88988898
"re_uri",
8899+
"re_view_text_document",
88998900
"re_viewer",
8901+
"re_viewport_blueprint",
89008902
"tempfile",
89018903
"tokio",
89028904
]

crates/viewer/re_viewer/src/app_state.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ use crate::{
3232

3333
const WATERMARK: bool = false; // Nice for recording media material
3434

35+
#[cfg(feature = "testing")]
36+
pub type TestHookFn = Box<dyn FnOnce(&ViewerContext<'_>)>;
37+
3538
#[derive(serde::Deserialize, serde::Serialize)]
3639
#[serde(default)]
3740
pub struct AppState {
@@ -66,6 +69,12 @@ pub struct AppState {
6669
#[serde(skip)]
6770
pub(crate) share_modal: crate::ui::ShareModal,
6871

72+
/// Test-only: single-shot callback to run at the end of the frame. Used in integration tests
73+
/// to interact with the `ViewerContext`.
74+
#[cfg(feature = "testing")]
75+
#[serde(skip)]
76+
pub(crate) test_hook: Option<TestHookFn>,
77+
6978
/// A stack of display modes that represents tab-like navigation of the user.
7079
#[serde(skip)]
7180
pub(crate) navigation: Navigation,
@@ -115,6 +124,9 @@ impl Default for AppState {
115124
view_states: Default::default(),
116125
selection_state: Default::default(),
117126
focused_item: Default::default(),
127+
128+
#[cfg(feature = "testing")]
129+
test_hook: None,
118130
}
119131
}
120132
}
@@ -658,6 +670,12 @@ impl AppState {
658670
self.open_url_modal.ui(ui);
659671
self.share_modal
660672
.ui(&ctx, ui, startup_options.web_viewer_base_url().as_ref());
673+
674+
// Only in integration tests: call the test hook if any.
675+
#[cfg(feature = "testing")]
676+
if let Some(test_hook) = self.test_hook.take() {
677+
test_hook(&ctx);
678+
}
661679
}
662680
}
663681

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![cfg(feature = "testing")]
2+
use re_viewer_context::StoreHub;
3+
4+
use crate::App;
5+
6+
pub trait AppTestingExt {
7+
fn testonly_get_store_hub(&mut self) -> &mut StoreHub;
8+
fn testonly_set_test_hook(&mut self, func: crate::app_state::TestHookFn);
9+
}
10+
11+
impl AppTestingExt for App {
12+
fn testonly_get_store_hub(&mut self) -> &mut StoreHub {
13+
self.store_hub
14+
.as_mut()
15+
.expect("store_hub should be initialized")
16+
}
17+
18+
fn testonly_set_test_hook(&mut self, func: crate::app_state::TestHookFn) {
19+
self.state.test_hook = Some(func);
20+
}
21+
}

crates/viewer/re_viewer/src/viewer_test_utils/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod app_testing_ext;
2+
3+
#[cfg(feature = "testing")]
4+
pub use app_testing_ext::AppTestingExt;
15
use egui_kittest::Harness;
26
use re_build_info::build_info;
37

@@ -6,11 +10,17 @@ use crate::{
610
customize_eframe_and_setup_renderer,
711
};
812

13+
#[derive(Default)]
14+
pub struct HarnessOptions {
15+
pub window_size: Option<egui::Vec2>,
16+
}
17+
918
/// Convenience function for creating a kittest harness of the viewer App.
10-
pub fn viewer_harness() -> Harness<'static, App> {
19+
pub fn viewer_harness(options: &HarnessOptions) -> Harness<'static, App> {
20+
let window_size = options.window_size.unwrap_or(egui::vec2(1024., 768.));
1121
Harness::builder()
1222
.wgpu()
13-
.with_size(egui::vec2(1500., 1000.))
23+
.with_size(window_size)
1424
.build_eframe(|cc| {
1525
cc.egui_ctx.set_os(egui::os::OperatingSystem::Nix);
1626
customize_eframe_and_setup_renderer(cc).expect("Failed to customize eframe");

crates/viewer/re_viewer/tests/app_kittest.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use egui_kittest::{SnapshotOptions, kittest::Queryable as _};
77

88
use re_test_context::TestContext;
99
use re_types::components::Colormap;
10-
use re_viewer::viewer_test_utils;
10+
use re_viewer::viewer_test_utils::{self, HarnessOptions};
1111
use re_viewer_context::{MaybeMutRef, ViewerContext};
1212

1313
/// Navigates from welcome to settings screen and snapshots it.
@@ -20,7 +20,7 @@ async fn settings_screen() {
2020
std::env::set_var("TZ", "Europe/Stockholm");
2121
}
2222

23-
let mut harness = viewer_test_utils::viewer_harness();
23+
let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default());
2424
harness.get_by_label("Menu").click();
2525
harness.run_ok();
2626
harness.get_by_label_contains("Settings…").click();
@@ -45,7 +45,7 @@ async fn settings_screen() {
4545
/// Tests that certain recording-related entries are disabled (e.g. save or close recording).
4646
#[tokio::test]
4747
async fn menu_without_recording() {
48-
let mut harness = viewer_test_utils::viewer_harness();
48+
let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default());
4949
harness.get_by_label("Menu").click();
5050
harness.run_ok();
5151
// Mask the shortcut for quitting, it's platform-dependent.
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

tests/rust/re_integration_test/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,20 @@ version.workspace = true
1111
publish = false
1212

1313
[dependencies]
14+
egui_kittest.workspace = true
15+
egui.workspace = true
1416
re_redap_client.workspace = true
1517
re_protos.workspace = true
1618
re_sdk.workspace = true
1719
re_server.workspace = true
1820
re_uri.workspace = true
21+
re_viewer = { workspace = true, features = ["testing"] }
22+
re_viewport_blueprint.workspace = true
1923
tempfile.workspace = true
2024
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
2125

2226
[dev-dependencies]
23-
re_viewer = { workspace = true, features = ["testing"] }
24-
egui_kittest.workspace = true
25-
egui.workspace = true
27+
re_view_text_document.workspace = true
2628

2729
[lints]
2830
workspace = true

0 commit comments

Comments
 (0)