From 538c1203665a8f466daa016db0883007a9851151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 24 Oct 2025 17:28:58 +0200 Subject: [PATCH 01/23] Add hierarchical queries to Harness Fixes #11628 --- .../re_blueprint_tree/src/blueprint_tree.rs | 7 ++- .../re_component_ui/src/marker_shape.rs | 2 +- crates/viewer/re_data_ui/src/item_ui.rs | 5 ++- .../re_selection_panel/src/selection_panel.rs | 7 ++- crates/viewer/re_ui/src/list_item/scope.rs | 16 +++---- crates/viewer/re_ui/src/ui_ext.rs | 12 ++--- crates/viewer/re_viewer/src/app_state.rs | 7 ++- .../src/gpu_bridge/colormap.rs | 4 +- tests/rust/re_integration_test/src/lib.rs | 3 ++ .../re_integration_test/src/viewer_section.rs | 44 +++++++++++++++++++ .../tests/blueprint_context_menu_test.rs | 31 +++++++++++++ .../tests/snapshots/xtemp.png | 3 ++ 12 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 tests/rust/re_integration_test/src/viewer_section.rs create mode 100644 tests/rust/re_integration_test/tests/snapshots/xtemp.png diff --git a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs index 6a913f70d19f..7877dc0fb662 100644 --- a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs +++ b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs @@ -1,4 +1,4 @@ -use egui::{Response, Ui}; +use egui::{Response, Ui, WidgetInfo, WidgetType}; use smallvec::SmallVec; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item_with_context}; @@ -86,7 +86,7 @@ impl BlueprintTree { } ui.panel_content(|ui| { - ui.list_item_scope("blueprint_section_title", |ui| { + let response = ui.list_item_scope("blueprint_section_title", |ui| { ui.list_item().interactive(false).show_flat( ui, list_item::CustomContent::new(|ui, _| { @@ -111,6 +111,9 @@ impl BlueprintTree { ), ); }); + response.response.widget_info(|| { + WidgetInfo::labeled(WidgetType::Panel, true, "blueprint_section_title") + }); }); // This call is excluded from `panel_content` because it has a ScrollArea, which should not be diff --git a/crates/viewer/re_component_ui/src/marker_shape.rs b/crates/viewer/re_component_ui/src/marker_shape.rs index d8997131d83b..35d231e73d0a 100644 --- a/crates/viewer/re_component_ui/src/marker_shape.rs +++ b/crates/viewer/re_component_ui/src/marker_shape.rs @@ -50,7 +50,7 @@ pub(crate) fn edit_marker_shape_ui( combined_response.expect("At least one marker shape should be available") }; - re_ui::list_item::list_item_scope(ui, "marker_shape", list_ui) + re_ui::list_item::list_item_scope(ui, "marker_shape", list_ui).inner }), ) } else { diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs index 4b24d3b2a942..1acb27b2c10f 100644 --- a/crates/viewer/re_data_ui/src/item_ui.rs +++ b/crates/viewer/re_data_ui/src/item_ui.rs @@ -776,7 +776,8 @@ pub fn entity_db_button_ui( entity_db, ); }) - }); + }) + .inner; if response.hovered() { ctx.selection_state().set_hovered(item.clone()); @@ -866,7 +867,7 @@ pub fn table_id_button_ui( .on_hover_ui(|ui| { ui.label(format!("Table: {table_id}")); }) - }); + }).inner; if response.hovered() { ctx.selection_state().set_hovered(item.clone()); diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 073f125d1e40..b478b5140c43 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -72,7 +72,7 @@ impl SelectionPanel { ctx.send_time_commands([TimeControlCommand::ClearHighlightedRange]); } - panel.show_animated_inside(ui, expanded, |ui: &mut egui::Ui| { + let response = panel.show_animated_inside(ui, expanded, |ui: &mut egui::Ui| { ui.panel_content(|ui| { let hover = "The selection view contains information and options about \ the currently selected object(s)"; @@ -92,6 +92,11 @@ impl SelectionPanel { }); }); }); + if let Some(response) = response { + response.response.widget_info(|| { + egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "selection_panel") + }); + } // run modals (these are noop if the modals are not active) self.view_entity_modal.ui(ui.ctx(), ctx, viewport); diff --git a/crates/viewer/re_ui/src/list_item/scope.rs b/crates/viewer/re_ui/src/list_item/scope.rs index c238e8a7c784..2ce6bb0b5857 100644 --- a/crates/viewer/re_ui/src/list_item/scope.rs +++ b/crates/viewer/re_ui/src/list_item/scope.rs @@ -1,4 +1,4 @@ -use egui::NumExt as _; +use egui::{InnerResponse, NumExt as _}; use crate::UiExt as _; use crate::list_item::navigation::ListItemNavigation; @@ -263,7 +263,7 @@ pub fn list_item_scope( ui: &mut egui::Ui, id_salt: impl std::hash::Hash, content: impl FnOnce(&mut egui::Ui) -> R, -) -> R { +) -> InnerResponse { ui.sanity_check(); let id_salt = egui::Id::new(id_salt); // So we can use it twice @@ -294,12 +294,10 @@ pub fn list_item_scope( // push, run, pop LayoutInfoStack::push(ui.ctx(), state.clone()); - let result = ui - .push_id(id_salt, |ui| { - ui.spacing_mut().item_spacing.y = 0.0; - content(ui) - }) - .inner; + let response = ui.push_id(id_salt, |ui| { + ui.spacing_mut().item_spacing.y = 0.0; + content(ui) + }); LayoutInfoStack::pop(ui.ctx()); if is_root { @@ -308,5 +306,5 @@ pub fn list_item_scope( ui.sanity_check(); - result + response } diff --git a/crates/viewer/re_ui/src/ui_ext.rs b/crates/viewer/re_ui/src/ui_ext.rs index be4e1135d6e7..70475f9d3c91 100644 --- a/crates/viewer/re_ui/src/ui_ext.rs +++ b/crates/viewer/re_ui/src/ui_ext.rs @@ -345,12 +345,14 @@ pub trait UiExt { // TODO(ab): this used to be used for inner margin, after registering full span range in panels. // It's highly likely that all these use are now redundant. fn panel_content(&mut self, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> R { - egui::Frame { + let res = egui::Frame { inner_margin: self.tokens().panel_margin(), ..Default::default() } - .show(self.ui_mut(), |ui| add_contents(ui)) - .inner + .show(self.ui_mut(), |ui| add_contents(ui)); + res.response + .widget_info(|| WidgetInfo::labeled(egui::WidgetType::Label, true, "foooooo")); + res.inner } /// Static title bar used to separate panels into section. @@ -645,7 +647,7 @@ pub trait UiExt { &mut self, id_salt: impl std::hash::Hash, content: impl FnOnce(&mut egui::Ui) -> R, - ) -> R { + ) -> egui::InnerResponse { list_item::list_item_scope(self.ui_mut(), id_salt, content) } @@ -687,7 +689,7 @@ pub trait UiExt { id, default_open, list_item::LabelContent::new(label), - |ui| list_item::list_item_scope(ui, id, children_ui), + |ui| list_item::list_item_scope(ui, id, children_ui).inner, ) .body_response .map(|r| r.inner) diff --git a/crates/viewer/re_viewer/src/app_state.rs b/crates/viewer/re_viewer/src/app_state.rs index 99ca4ccdf86a..4397a97374da 100644 --- a/crates/viewer/re_viewer/src/app_state.rs +++ b/crates/viewer/re_viewer/src/app_state.rs @@ -527,7 +527,7 @@ impl AppState { ui.ctx().content_rect().width(), )); - left_panel.show_animated_inside( + let left_panel_response = left_panel.show_animated_inside( ui, app_blueprint.blueprint_panel_state().is_expanded(), |ui: &mut egui::Ui| { @@ -589,6 +589,11 @@ impl AppState { } }, ); + if let Some(left_panel_response) = left_panel_response { + left_panel_response.response.widget_info(|| { + egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "blueprint_panel") + }); + } // // Viewport diff --git a/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs b/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs index 3b78710f6081..787e70b2b4e7 100644 --- a/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs +++ b/crates/viewer/re_viewer_context/src/gpu_bridge/colormap.rs @@ -152,8 +152,8 @@ pub fn colormap_edit_or_view_ui( .show_ui(ui, |ui| { list_item::list_item_scope(ui, "inner_scope", content_ui) }); - if let Some(inner) = inner_response.inner - && inner.changed() + if let Some(response) = inner_response.inner + && response.inner.changed() { inner_response.response.mark_changed(); } diff --git a/tests/rust/re_integration_test/src/lib.rs b/tests/rust/re_integration_test/src/lib.rs index 2eb7de91032f..97ef23cf7942 100644 --- a/tests/rust/re_integration_test/src/lib.rs +++ b/tests/rust/re_integration_test/src/lib.rs @@ -2,12 +2,15 @@ mod kittest_harness_ext; mod test_data; +mod viewer_section; pub use kittest_harness_ext::HarnessExt; use re_redap_client::{ApiError, ConnectionClient, ConnectionRegistry}; use re_server::ServerHandle; use re_uri::external::url::Host; use std::net::TcpListener; +pub use viewer_section::GetSection; +pub use viewer_section::ViewerSection; pub struct TestServer { server_handle: Option, diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs new file mode 100644 index 000000000000..3519ac4a4ae3 --- /dev/null +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -0,0 +1,44 @@ +use egui::accesskit::Role; +use egui_kittest::kittest::NodeT as _; +use egui_kittest::kittest::Queryable as _; + +pub struct ViewerSection<'a, 'h> { + pub harness: &'a mut egui_kittest::Harness<'h, re_viewer::App>, + pub section_label: &'a str, +} + +impl<'a, 'h: 'a> ViewerSection<'a, 'h> { + pub fn root(&'a self) -> egui_kittest::Node<'a> { + let node = self + .harness + .get_by_role_and_label(Role::Pane, self.section_label); + node + } + + pub fn right_click_label(&mut self, label: &str) { + self.root().get_by_label(label).click_secondary(); + self.harness.run_ok(); + } +} + +pub trait GetSection<'h> { + fn get_section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a; + + fn blueprint_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.get_section("blueprint_panel") + } +} + +impl<'h> GetSection<'h> for egui_kittest::Harness<'h, re_viewer::App> { + fn get_section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a, + { + ViewerSection::<'a, 'h> { + harness: self, + section_label, + } + } +} diff --git a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs index 9413a9e5642b..672a143d026f 100644 --- a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs @@ -1,3 +1,5 @@ +use egui_kittest::kittest::NodeT as _; +use re_integration_test::GetSection; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::external::re_log_types::EntityPathFilter; @@ -79,3 +81,32 @@ pub async fn test_blueprint_tree_context_menu() { harness.right_click_nth_label("boxes3d", 1); harness.snapshot_app("blueprint_tree_context_menu_10"); } + +#[tokio::test(flavor = "multi_thread")] +pub async fn test_foo() { + let mut harness = make_test_harness(); + setup_single_view_blueprint(&mut harness); + + harness.set_selection_panel_opened(true); + + // println!( + // "1: {}", + // harness + // .blueprint_panel() + // .root() + // .get_all_by_label("Test view") + // .count() + // ); + + println!("## :{:#?}", harness.root().children().collect::>()); + + harness.blueprint_panel().right_click_label("Test view"); + + harness.snapshot_app("xtemp"); + + let section = harness.blueprint_panel(); + + let node = section.root(); + + println!("node: {:#?}", node); +} diff --git a/tests/rust/re_integration_test/tests/snapshots/xtemp.png b/tests/rust/re_integration_test/tests/snapshots/xtemp.png new file mode 100644 index 000000000000..f4794ca6182c --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/xtemp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a379a117f88c89afbc00106a1ff978e22eada910f8c5c069e804504935344ce8 +size 139602 From bd3f600244313a72f1fa50d694e8b621dc182c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 14:34:39 +0100 Subject: [PATCH 02/23] Work on panel selector --- .../re_blueprint_tree/src/blueprint_tree.rs | 9 +-- .../re_selection_panel/src/selection_panel.rs | 13 ++++- crates/viewer/re_time_panel/src/time_panel.rs | 3 + crates/viewer/re_ui/src/ui_ext.rs | 12 ++-- crates/viewer/re_viewport/src/viewport_ui.rs | 3 +- .../re_integration_test/src/viewer_section.rs | 18 +++++- .../tests/blueprint_context_menu_test.rs | 39 ++----------- .../tests/container_context_menu_test.rs | 56 ++++++++++++------- .../tests/snapshots/xtemp.png | 3 - 9 files changed, 82 insertions(+), 74 deletions(-) delete mode 100644 tests/rust/re_integration_test/tests/snapshots/xtemp.png diff --git a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs index 7877dc0fb662..a6ef40ade85d 100644 --- a/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs +++ b/crates/viewer/re_blueprint_tree/src/blueprint_tree.rs @@ -86,7 +86,7 @@ impl BlueprintTree { } ui.panel_content(|ui| { - let response = ui.list_item_scope("blueprint_section_title", |ui| { + ui.list_item_scope("blueprint_section_title", |ui| { ui.list_item().interactive(false).show_flat( ui, list_item::CustomContent::new(|ui, _| { @@ -111,9 +111,6 @@ impl BlueprintTree { ), ); }); - response.response.widget_info(|| { - WidgetInfo::labeled(WidgetType::Panel, true, "blueprint_section_title") - }); }); // This call is excluded from `panel_content` because it has a ScrollArea, which should not be @@ -160,6 +157,10 @@ impl BlueprintTree { root_container, ); } + }) + .response + .widget_info(|| { + WidgetInfo::labeled(WidgetType::Panel, true, "_blueprint_tree") }); let empty_space_response = diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index b478b5140c43..2ef699bd3939 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -83,7 +83,7 @@ impl SelectionPanel { // area ui.add_space(-ui.spacing().item_spacing.y); - egui::ScrollArea::both() + let r = egui::ScrollArea::both() .auto_shrink([false; 2]) .show(ui, |ui| { ui.add_space(ui.spacing().item_spacing.y); @@ -91,6 +91,7 @@ impl SelectionPanel { self.contents(ctx, viewport, view_states, ui); }); }); + r.state }); if let Some(response) = response { response.response.widget_info(|| { @@ -124,7 +125,7 @@ impl SelectionPanel { if selection.len() == 1 { for item in selection.iter_items() { - list_item::list_item_scope(ui, item, |ui| { + let res = list_item::list_item_scope(ui, item, |ui| { item_heading_with_breadcrumbs(ctx, viewport, ui, item); self.item_ui( @@ -136,9 +137,12 @@ impl SelectionPanel { UiLayout::SelectionPanel, ); }); + res.response.widget_info(|| { + egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "_recordings_tree") + }); } } else { - list_item::list_item_scope(ui, "selections_panel", |ui| { + let res = list_item::list_item_scope(ui, "selections_panel", |ui| { ui.list_item() .with_height(tokens.title_bar_height()) .interactive(false) @@ -156,6 +160,9 @@ impl SelectionPanel { item_title_list_item(ctx, viewport, ui, item); } }); + res.response.widget_info(|| { + WidgetInfo::labeled(egui::WidgetType::Label, true, "_recordings_tree") + }); } } diff --git a/crates/viewer/re_time_panel/src/time_panel.rs b/crates/viewer/re_time_panel/src/time_panel.rs index e6fcdc7b9a7c..712d8719761e 100644 --- a/crates/viewer/re_time_panel/src/time_panel.rs +++ b/crates/viewer/re_time_panel/src/time_panel.rs @@ -5,6 +5,7 @@ use egui::{ Color32, CursorIcon, Modifiers, NumExt as _, Painter, PointerButton, Rect, Response, RichText, Shape, Ui, Vec2, pos2, scroll_area::ScrollSource, }; +use egui::{WidgetInfo, WidgetType}; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item_with_context}; use re_data_ui::DataUi as _; use re_data_ui::item_ui::guess_instance_path_icon; @@ -476,6 +477,8 @@ impl TimePanel { let timeline_rect = { let top = ui.min_rect().bottom(); + ui.response() + .widget_info(|| WidgetInfo::labeled(WidgetType::Panel, true, "_streams_tree")); let size = egui::vec2(self.prev_col_width, DesignTokens::list_item_height()); ui.allocate_ui_with_layout(size, egui::Layout::top_down(egui::Align::LEFT), |ui| { diff --git a/crates/viewer/re_ui/src/ui_ext.rs b/crates/viewer/re_ui/src/ui_ext.rs index 70475f9d3c91..c5d7b75c0c3e 100644 --- a/crates/viewer/re_ui/src/ui_ext.rs +++ b/crates/viewer/re_ui/src/ui_ext.rs @@ -344,15 +344,15 @@ pub trait UiExt { // TODO(ab): this used to be used for inner margin, after registering full span range in panels. // It's highly likely that all these use are now redundant. - fn panel_content(&mut self, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> R { - let res = egui::Frame { + fn panel_content( + &mut self, + add_contents: impl FnOnce(&mut egui::Ui) -> R, + ) -> egui::InnerResponse { + egui::Frame { inner_margin: self.tokens().panel_margin(), ..Default::default() } - .show(self.ui_mut(), |ui| add_contents(ui)); - res.response - .widget_info(|| WidgetInfo::labeled(egui::WidgetType::Label, true, "foooooo")); - res.inner + .show(self.ui_mut(), |ui| add_contents(ui)) } /// Static title bar used to separate panels into section. diff --git a/crates/viewer/re_viewport/src/viewport_ui.rs b/crates/viewer/re_viewport/src/viewport_ui.rs index ff5ff6bdef0c..27ae3401bde0 100644 --- a/crates/viewer/re_viewport/src/viewport_ui.rs +++ b/crates/viewer/re_viewport/src/viewport_ui.rs @@ -3,7 +3,7 @@ //! Contains all views. use ahash::HashMap; -use egui::remap_clamp; +use egui::{WidgetInfo, remap_clamp}; use egui_tiles::{Behavior as _, EditAction}; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item}; @@ -231,7 +231,6 @@ impl ViewportUi { } } }); - self.blueprint.set_maximized(maximized, ctx); } diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 3519ac4a4ae3..7e42839ebf29 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -15,6 +15,16 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { node } + pub fn click_label(&mut self, label: &str) { + self.root().get_by_label(label).click(); + self.harness.run_ok(); + } + + pub fn click_label_modifiers(&mut self, label: &str, modifiers: egui::Modifiers) { + self.root().get_by_label(label).click_modifiers(modifiers); + self.harness.run_ok(); + } + pub fn right_click_label(&mut self, label: &str) { self.root().get_by_label(label).click_secondary(); self.harness.run_ok(); @@ -26,8 +36,12 @@ pub trait GetSection<'h> { where 'h: 'a; - fn blueprint_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.get_section("blueprint_panel") + fn blueprint_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.get_section("_blueprint_tree") + } + + fn streams_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.get_section("_streams_tree") } } diff --git a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs index 672a143d026f..2dec4de2c47d 100644 --- a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs @@ -60,53 +60,26 @@ pub async fn test_blueprint_tree_context_menu() { harness.click_label("Expand all"); harness.snapshot_app("blueprint_tree_context_menu_03"); - harness.right_click_label("Viewport (Grid container)"); + harness + .blueprint_tree() + .right_click_label("Viewport (Grid container)"); harness.snapshot_app("blueprint_tree_context_menu_04"); harness.key_press(egui::Key::Escape); harness.snapshot_app("blueprint_tree_context_menu_05"); - harness.right_click_nth_label("Test view", 0); + harness.blueprint_tree().right_click_label("Test view"); harness.snapshot_app("blueprint_tree_context_menu_06"); harness.key_press(egui::Key::Escape); harness.snapshot_app("blueprint_tree_context_menu_07"); - harness.right_click_label("group"); + harness.blueprint_tree().right_click_label("group"); harness.snapshot_app("blueprint_tree_context_menu_08"); harness.key_press(egui::Key::Escape); harness.snapshot_app("blueprint_tree_context_menu_09"); - harness.right_click_nth_label("boxes3d", 1); + harness.blueprint_tree().right_click_label("boxes3d"); harness.snapshot_app("blueprint_tree_context_menu_10"); } - -#[tokio::test(flavor = "multi_thread")] -pub async fn test_foo() { - let mut harness = make_test_harness(); - setup_single_view_blueprint(&mut harness); - - harness.set_selection_panel_opened(true); - - // println!( - // "1: {}", - // harness - // .blueprint_panel() - // .root() - // .get_all_by_label("Test view") - // .count() - // ); - - println!("## :{:#?}", harness.root().children().collect::>()); - - harness.blueprint_panel().right_click_label("Test view"); - - harness.snapshot_app("xtemp"); - - let section = harness.blueprint_panel(); - - let node = section.root(); - - println!("node: {:#?}", node); -} diff --git a/tests/rust/re_integration_test/tests/container_context_menu_test.rs b/tests/rust/re_integration_test/tests/container_context_menu_test.rs index 3525562b4a40..820d9de547a9 100644 --- a/tests/rust/re_integration_test/tests/container_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/container_context_menu_test.rs @@ -1,6 +1,6 @@ use egui::Modifiers; -use re_integration_test::HarnessExt as _; +use re_integration_test::{GetSection as _, HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::ViewClass as _; @@ -80,12 +80,12 @@ pub async fn test_context_menu_invalid_sub_container() { harness.key_press(egui::Key::Escape); // Test context menus of view items in the blueprint panel - harness.right_click_nth_label("3D view", 0); + harness.blueprint_tree().right_click_label("3D view"); harness.hover_label_contains("Move to new container"); harness.snapshot_app("context_menu_invalid_sub_container_04"); harness.key_press(egui::Key::Escape); - harness.right_click_nth_label("2D view", 0); + harness.blueprint_tree().right_click_label("2D view"); harness.hover_label_contains("Move to new container"); harness.snapshot_app("context_menu_invalid_sub_container_05"); harness.key_press(egui::Key::Escape); @@ -98,47 +98,61 @@ pub async fn test_context_menu_multi_selection() { harness.snapshot_app("context_menu_multi_selection_01"); // Expand both views and the boxes2d entity - harness.right_click_nth_label("3D view", 0); + harness.blueprint_tree().right_click_label("3D view"); harness.click_label("Expand all"); - harness.right_click_nth_label("2D view", 0); + harness.blueprint_tree().right_click_label("2D view"); harness.click_label("Expand all"); - harness.right_click_nth_label("boxes2d", 0); + harness.streams_tree().right_click_label("boxes2d"); harness.click_label("Expand all"); harness.snapshot("context_menu_multi_selection_02"); // Select 3D View and 2D View, check context menu - harness.click_nth_label("3D view", 0); - harness.click_nth_label_modifiers("2D view", 0, Modifiers::COMMAND); - harness.right_click_nth_label("2D view", 0); + harness.blueprint_tree().click_label("3D view"); + harness + .blueprint_tree() + .click_label_modifiers("2D view", Modifiers::COMMAND); + harness.blueprint_tree().right_click_label("2D view"); harness.snapshot_app("context_menu_multi_selection_03"); harness.key_press(egui::Key::Escape); // Add container to selection, check context menu - harness.click_nth_label_modifiers("Grid container", 0, Modifiers::COMMAND); - harness.right_click_nth_label("2D view", 0); + harness + .blueprint_tree() + .click_label_modifiers("Grid container", Modifiers::COMMAND); + harness.blueprint_tree().right_click_label("2D view"); harness.snapshot_app("context_menu_multi_selection_04"); harness.key_press(egui::Key::Escape); // Select viewport and check context menu - harness.click_nth_label_modifiers("Viewport (Grid container)", 0, Modifiers::COMMAND); - harness.right_click_nth_label("Viewport (Grid container)", 0); + harness + .blueprint_tree() + .click_label_modifiers("Viewport (Grid container)", Modifiers::COMMAND); + harness + .blueprint_tree() + .right_click_label("Viewport (Grid container)"); harness.snapshot_app("context_menu_multi_selection_05"); harness.key_press(egui::Key::Escape); // View + data result - harness.click_nth_label("2D view", 0); - harness.click_nth_label_modifiers("boxes2d", 1, Modifiers::COMMAND); - harness.right_click_nth_label("boxes2d", 1); + harness.blueprint_tree().click_label("2D view"); + harness + .blueprint_tree() + .click_label_modifiers("boxes2d", Modifiers::COMMAND); + harness.blueprint_tree().right_click_label("boxes2d"); harness.snapshot("context_menu_multi_selection_06"); harness.key_press(egui::Key::Escape); - harness.click_nth_label("boxes2d", 0); - harness.click_nth_label_modifiers("boxes3d", 0, Modifiers::COMMAND); - harness.right_click_nth_label("boxes3d", 0); + harness.streams_tree().click_label("boxes2d"); + harness + .blueprint_tree() + .click_label_modifiers("boxes3d", Modifiers::COMMAND); + harness.blueprint_tree().right_click_label("boxes3d"); harness.snapshot("context_menu_multi_selection_07"); harness.key_press(egui::Key::Escape); - harness.click_nth_label_modifiers("half_sizes", 0, Modifiers::COMMAND); - harness.right_click_nth_label("half_sizes", 0); + harness + .streams_tree() + .click_label_modifiers("half_sizes", Modifiers::COMMAND); + harness.streams_tree().right_click_label("half_sizes"); harness.snapshot("context_menu_multi_selection_08"); } diff --git a/tests/rust/re_integration_test/tests/snapshots/xtemp.png b/tests/rust/re_integration_test/tests/snapshots/xtemp.png deleted file mode 100644 index f4794ca6182c..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/xtemp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a379a117f88c89afbc00106a1ff978e22eada910f8c5c069e804504935344ce8 -size 139602 From 6bf2ba4d1ee6c6cee198c2366d3b4cbd1a8ecb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 15:06:23 +0100 Subject: [PATCH 03/23] Progess --- .../re_selection_panel/src/selection_panel.rs | 4 ++-- .../re_integration_test/src/viewer_section.rs | 4 ++++ .../tests/context_menu_test.rs | 20 ++++++++----------- .../container_selection_context_menu_1.png | 4 ++-- .../container_selection_context_menu_2.png | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 2ef699bd3939..779b18338ce5 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -138,7 +138,7 @@ impl SelectionPanel { ); }); res.response.widget_info(|| { - egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "_recordings_tree") + egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "_selection_panel") }); } } else { @@ -161,7 +161,7 @@ impl SelectionPanel { } }); res.response.widget_info(|| { - WidgetInfo::labeled(egui::WidgetType::Label, true, "_recordings_tree") + WidgetInfo::labeled(egui::WidgetType::Panel, true, "_selection_panel") }); } } diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 7e42839ebf29..53cfa1aadfc9 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -43,6 +43,10 @@ pub trait GetSection<'h> { fn streams_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { self.get_section("_streams_tree") } + + fn selection_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.get_section("_selection_panel") + } } impl<'h> GetSection<'h> for egui_kittest::Harness<'h, re_viewer::App> { diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs index 44811424a521..3d2d01b6a52e 100644 --- a/tests/rust/re_integration_test/tests/context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -1,4 +1,4 @@ -use re_integration_test::HarnessExt as _; +use re_integration_test::{GetSection as _, HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_view_text_document::TextDocumentView; @@ -85,15 +85,11 @@ pub async fn test_container_selection_context_menu() { let container_id = harness.add_blueprint_container(egui_tiles::ContainerKind::Vertical, None); harness.setup_viewport_blueprint(move |_viewer_context, blueprint| { - blueprint.add_views( - [ - ViewBlueprint::new_with_root_wildcard(TextDocumentView::identifier()), - ViewBlueprint::new_with_root_wildcard(TextDocumentView::identifier()), - ] - .into_iter(), - Some(container_id), - None, - ); + let mut view_1 = ViewBlueprint::new_with_root_wildcard(TextDocumentView::identifier()); + view_1.display_name = Some("View 1".into()); + let mut view_2 = ViewBlueprint::new_with_root_wildcard(TextDocumentView::identifier()); + view_2.display_name = Some("View 2".into()); + blueprint.add_views([view_1, view_2].into_iter(), Some(container_id), None); }); harness.click_label("Vertical container"); @@ -101,10 +97,10 @@ pub async fn test_container_selection_context_menu() { // There are multiple nodes with that label, second and third are // the ones on the selection panel. - harness.right_click_nth_label("/", 1); + harness.selection_panel().right_click_label("View 1"); harness.snapshot_app("container_selection_context_menu_1"); harness.key_press(egui::Key::Escape); - harness.right_click_nth_label("/", 2); + harness.selection_panel().right_click_label("View 2"); harness.snapshot_app("container_selection_context_menu_2"); } diff --git a/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_1.png b/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_1.png index 1c30ba8a9cf7..f529e9e2631c 100644 --- a/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d77593af56a36d8c9a80966fcac1a2edaa08d3445c129616891c3580fd93b81b -size 88950 +oid sha256:7203ee5381514e125f7770a206d9eb7246e7a30289e1c512f48e7e8733549c7e +size 92155 diff --git a/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_2.png b/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_2.png index 365cf9820294..e7353319c8cf 100644 --- a/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/container_selection_context_menu_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d79673a8f831b119165acb19823a8abb88041199d11c8f0bf76352f5c0b1cdd9 -size 89139 +oid sha256:82395330d1103d44e2205a7f2395f04bd362abefedc6b1c3bda28cb8eca19278 +size 92771 From 4a5c9953965df3b4b0ad63c0cdb2266d3835d420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 16:22:23 +0100 Subject: [PATCH 04/23] Updates --- .../re_integration_test/src/viewer_section.rs | 105 +++++++++++++++++- .../tests/multi_container_test.rs | 40 ++++--- 2 files changed, 131 insertions(+), 14 deletions(-) diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 53cfa1aadfc9..5ed8e739526f 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -1,3 +1,4 @@ +use egui::PointerButton; use egui::accesskit::Role; use egui_kittest::kittest::NodeT as _; use egui_kittest::kittest::Queryable as _; @@ -8,13 +9,51 @@ pub struct ViewerSection<'a, 'h> { } impl<'a, 'h: 'a> ViewerSection<'a, 'h> { - pub fn root(&'a self) -> egui_kittest::Node<'a> { + pub fn root<'n>(&'n self) -> egui_kittest::Node<'n> + where + 'a: 'n, + { let node = self .harness .get_by_role_and_label(Role::Pane, self.section_label); node } + pub fn get_label<'n>(&'n mut self, label: &'n str) -> egui_kittest::Node<'n> + where + 'a: 'n, + { + self.root().get_by_label(label) + } + + pub fn get_nth_label<'n>(&'n mut self, label: &'n str, index: usize) -> egui_kittest::Node<'n> + where + 'a: 'n, + { + let mut nodes = self.root().get_all_by_label(label).collect::>(); + assert!( + index < nodes.len(), + "Failed to find label '{label}' #{index}, there are only {} nodes:\n{nodes:#?}", + nodes.len() + ); + nodes.swap_remove(index) + } + + fn get_nth_label_inner<'n>( + &'n mut self, + label: &'n str, + index: Option, + ) -> egui_kittest::Node<'n> + where + 'a: 'n, + { + if let Some(index) = index { + self.get_nth_label(label, index) + } else { + self.get_label(label) + } + } + pub fn click_label(&mut self, label: &str) { self.root().get_by_label(label).click(); self.harness.run_ok(); @@ -29,6 +68,70 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.root().get_by_label(label).click_secondary(); self.harness.run_ok(); } + + fn drag_label_inner(&mut self, label: &str, index: Option) { + let node = self.get_nth_label_inner(label, index); + + let center = node.rect().center(); + self.harness.event(egui::Event::PointerButton { + pos: center, + button: PointerButton::Primary, + pressed: true, + modifiers: egui::Modifiers::NONE, + }); + + // Step until the time has passed `max_click_duration` so this gets + // registered as a drag. + // self.harness.step(); + let wait_time = self + .harness + .ctx + .options(|o| o.input_options.max_click_duration); + let end_time = self.harness.ctx.input(|i| i.time + wait_time); + while self.harness.ctx.input(|i| i.time) < end_time { + self.harness.step(); + } + // self.harness.step(); + } + + pub fn drag_nth_label(&mut self, label: &str, index: usize) { + self.drag_label_inner(label, Some(index)); + } + + pub fn drag_label(&mut self, label: &str) { + self.drag_label_inner(label, None); + } + + pub fn drop_label_inner(&mut self, label: &str, index: Option) { + let node = self.get_nth_label_inner(label, index); + let event = egui::Event::PointerButton { + pos: node.rect().center(), + button: PointerButton::Primary, + pressed: false, + modifiers: egui::Modifiers::NONE, + }; + self.harness.event(event); + self.harness.remove_cursor(); + self.harness.run_ok(); + } + + pub fn drop_nth_label(&mut self, label: &str, index: usize) { + self.drop_label_inner(label, Some(index)); + } + + pub fn drop_label(&mut self, label: &str) { + self.drop_label_inner(label, None); + } + + pub fn hover_label(&mut self, label: &str) { + self.get_label(label).hover(); + self.harness.run_ok(); + } + + pub fn hover_nth_label(&mut self, label: &str, index: usize) { + self.get_nth_label(label, index).hover(); + self.harness.run_ok(); + } } pub trait GetSection<'h> { diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index 2fe238b70c9e..5728e93168eb 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -1,6 +1,6 @@ use egui::vec2; use egui_kittest::kittest::Queryable as _; -use re_integration_test::HarnessExt as _; +use re_integration_test::{GetSection, HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::{ContainerId, ViewClass as _}; @@ -150,13 +150,15 @@ pub async fn test_multi_container_drag_single_view() { let mut harness = make_multi_view_test_harness(); add_containers_recursive(&mut harness, None, 2, 4, 0); - harness.drag_nth_label("3D view 0", 0); + harness.blueprint_tree().drag_label("3D view 0"); harness.snapshot_app("multi_container_drag_single_view_1"); - harness.hover_nth_label("Vertical container", 1); + harness + .blueprint_tree() + .hover_nth_label("Vertical container", 1); harness.snapshot_app("multi_container_drag_single_view_2"); - harness.drop_nth_label("2D view 9", 0); + harness.blueprint_tree().drop_nth_label("2D view 9", 0); harness.snapshot_app("multi_container_drag_single_view_3"); } @@ -166,30 +168,42 @@ pub async fn test_multi_container_drag_container() { let mut harness = make_multi_view_test_harness(); add_containers_recursive(&mut harness, None, 2, 4, 0); - harness.drag_nth_label("Vertical container", 0); + harness + .blueprint_tree() + .drag_nth_label("Vertical container", 0); harness.snapshot_app("multi_container_drag_container_1"); // Hovering the same kind of container should be disallowed - harness.hover_nth_label("Vertical container", 1); + harness + .blueprint_tree() + .hover_nth_label("Vertical container", 1); harness.snapshot_app("multi_container_drag_container_2"); // Hovering a different kind of container should be allowed - harness.hover_nth_label("Horizontal container", 1); + harness + .blueprint_tree() + .hover_nth_label("Horizontal container", 1); harness.snapshot_app("multi_container_drag_container_3"); // Hover a bit over root container to drop before it. // It should be disallowed to drop an item before the root container. - let root_container = harness.get_nth_label("Viewport (Grid container)", 0); - let upper_edge = root_container.rect().center_top(); - harness.event(egui::Event::PointerMoved(upper_edge)); - harness.run_ok(); + let upper_edge = harness + .blueprint_tree() + .get_label("Viewport (Grid container)") + .rect() + .center_top(); + harness.hover_at(upper_edge); harness.snapshot_app("multi_container_drag_container_4"); // Hovering the root container otherwise should be allowed - harness.hover_nth_label("Viewport (Grid container)", 0); + harness + .blueprint_tree() + .hover_label("Viewport (Grid container)"); harness.snapshot_app("multi_container_drag_container_5"); - harness.drop_nth_label("Viewport (Grid container)", 0); + harness + .blueprint_tree() + .drop_label("Viewport (Grid container)"); harness.snapshot_app("multi_container_drag_container_6"); } From 937753dbf71f5c83598d6820012c37bb2bb7823f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 16:35:24 +0100 Subject: [PATCH 05/23] Progress --- tests/rust/re_integration_test/src/viewer_section.rs | 5 +++++ .../tests/multi_container_test.rs | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 5ed8e739526f..2ccf27723d21 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -59,6 +59,11 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.harness.run_ok(); } + pub fn click_nth_label(&mut self, label: &str, index: usize) { + self.get_nth_label(label, index).click(); + self.harness.run_ok(); + } + pub fn click_label_modifiers(&mut self, label: &str, modifiers: egui::Modifiers) { self.root().get_by_label(label).click_modifiers(modifiers); self.harness.run_ok(); diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index 5728e93168eb..30221d9b1692 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -251,7 +251,9 @@ pub async fn test_multi_change_container_type() { add_containers_recursive(&mut harness, None, 1, 2, 0); harness.set_selection_panel_opened(true); - harness.click_nth_label("Vertical container", 0); + harness + .blueprint_tree() + .click_nth_label("Vertical container", 0); harness.snapshot_app("change_container_type_1"); harness.change_dropdown_value("Container kind", "Horizontal"); @@ -274,7 +276,7 @@ pub async fn test_simplify_container_hierarchy() { harness.snapshot_app("simplify_container_hierarchy_2"); harness.set_selection_panel_opened(true); - harness.click_nth_label("Horizontal container", 0); + harness.blueprint_tree().click_label("Horizontal container"); harness.click_label("Simplify hierarchy"); harness.snapshot_app("simplify_container_hierarchy_3"); } @@ -293,11 +295,13 @@ pub async fn test_simplify_root_hierarchy() { // Only add content to the first child, leave the second child empty add_views_to_container(&mut harness, Some(child_cid_1), 2, 0); - harness.click_nth_label("Viewport (Grid container)", 0); + harness + .blueprint_tree() + .click_label("Viewport (Grid container)"); harness.snapshot_app("simplify_root_hierarchy_2"); harness.set_selection_panel_opened(true); - harness.click_label("Simplify hierarchy"); + harness.selection_panel().click_label("Simplify hierarchy"); harness.snapshot_app("simplify_root_hierarchy_3"); } From 5e8f9595d97c5872bb6dfc9693e2b74c199a61c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 16:38:36 +0100 Subject: [PATCH 06/23] Work --- .../re_integration_test/tests/blueprint_context_menu_test.rs | 1 + tests/rust/re_integration_test/tests/multi_container_test.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs index 2dec4de2c47d..024573a1f3d0 100644 --- a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs @@ -54,6 +54,7 @@ pub async fn test_blueprint_tree_context_menu() { harness.snapshot_app("blueprint_tree_context_menu_01"); + // Click on the view panel widget harness.right_click_nth_label("Test view", 1); harness.snapshot_app("blueprint_tree_context_menu_02"); diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index 30221d9b1692..fc6140d19771 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -312,6 +312,7 @@ pub async fn test_drag_view_to_other_view_right() { let target_pos = harness.get_panel_position("2D view 1").right_center() + vec2(-50.0, 0.0); + // Drag the view panel widget harness.drag_nth_label("3D view 4", 1); harness.hover_at(target_pos); harness.snapshot_app("drag_view_to_other_view_right_1"); @@ -327,6 +328,7 @@ pub async fn test_drag_view_to_other_view_left() { let target_pos = harness.get_panel_position("2D view 1").left_center() + vec2(50.0, 0.0); + // Drag the view panel widget harness.drag_nth_label("3D view 4", 1); harness.hover_at(target_pos); harness.snapshot_app("drag_view_to_other_view_left_1"); @@ -342,6 +344,7 @@ pub async fn test_drag_view_to_other_view_center() { let target_pos = harness.get_panel_position("2D view 1").center(); + // Drag the view panel widget harness.drag_nth_label("3D view 4", 1); harness.hover_at(target_pos); harness.snapshot_app("drag_view_to_other_view_center_1"); @@ -357,6 +360,7 @@ pub async fn test_drag_view_to_other_view_top() { let target_pos = harness.get_panel_position("2D view 1").center_top() + vec2(0.0, 50.0); + // Drag the view panel widget harness.drag_nth_label("3D view 4", 1); harness.hover_at(target_pos); harness.snapshot_app("drag_view_to_other_view_top_1"); @@ -372,6 +376,7 @@ pub async fn test_drag_view_to_other_view_bottom() { let target_pos = harness.get_panel_position("2D view 1").center_bottom() + vec2(0.0, -50.0); + // Drag the view panel widget harness.drag_nth_label("3D view 4", 1); harness.hover_at(target_pos); harness.snapshot_app("drag_view_to_other_view_bottom_1"); From 8d818b715ac6d883e7804afc3d88027c72cf5343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Mon, 27 Oct 2025 16:59:23 +0100 Subject: [PATCH 07/23] Work --- .../src/kittest_harness_ext.rs | 63 +++---------------- .../re_integration_test/src/viewer_section.rs | 42 +++++++++---- 2 files changed, 40 insertions(+), 65 deletions(-) diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index 6de0b199b448..4b4169e5eccc 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -29,6 +29,8 @@ use re_viewer::{ use re_viewer_context::ContainerId; use re_viewport_blueprint::ViewportBlueprint; +use crate::GetSection as _; + // Kittest harness utilities specific to the Rerun app. pub trait HarnessExt { // Initializes the chuck store with a new, empty recording and blueprint. @@ -64,9 +66,6 @@ pub trait HarnessExt { build_chunk: impl FnOnce(ChunkBuilder) -> ChunkBuilder, ); - // Finds the nth node with a given label - fn get_nth_label<'a>(&'a mut self, label: &'a str, index: usize) -> egui_kittest::Node<'a>; - // Get the position of a node in the UI by its label. fn get_panel_position(&mut self, label: &str) -> egui::Rect; @@ -76,7 +75,6 @@ pub trait HarnessExt { fn click_label_contains(&mut self, label: &str); fn click_nth_label(&mut self, label: &str, index: usize); fn right_click_nth_label(&mut self, label: &str, index: usize); - fn click_nth_label_modifiers(&mut self, label: &str, index: usize, modifiers: Modifiers); fn hover_label_contains(&mut self, label: &str); fn hover_nth_label(&mut self, label: &str, index: usize); @@ -248,13 +246,11 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { } fn click_label(&mut self, label: &str) { - self.get_by_label(label).click(); - self.run_ok(); + self.root_section().click_label(label); } fn right_click_label(&mut self, label: &str) { - self.get_by_label(label).click_secondary(); - self.run_ok(); + self.root_section().right_click_label(label); } fn click_label_contains(&mut self, label: &str) { @@ -262,27 +258,16 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { self.run_ok(); } - fn get_nth_label<'a>(&'a mut self, label: &'a str, index: usize) -> egui_kittest::Node<'a> { - let mut nodes = self.get_all_by_label(label).collect::>(); - assert!( - index < nodes.len(), - "Failed to find label '{label}' #{index}, there are only {} nodes:\n{nodes:#?}", - nodes.len() - ); - nodes.swap_remove(index) - } - fn get_panel_position(&mut self, label: &str) -> egui::Rect { self.get_by_role_and_label(Role::Pane, label).rect() } fn click_nth_label(&mut self, label: &str, index: usize) { - self.click_nth_label_modifiers(label, index, Modifiers::NONE); + self.root_section().click_nth_label(label, index); } fn right_click_nth_label(&mut self, label: &str, index: usize) { - self.get_nth_label(label, index).click_secondary(); - self.run_ok(); + self.root_section().right_click_nth_label(label, index); } fn hover_label_contains(&mut self, label: &str) { @@ -291,45 +276,15 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { } fn hover_nth_label(&mut self, label: &str, index: usize) { - self.get_nth_label(label, index).hover(); - self.run_ok(); - } - - fn click_nth_label_modifiers(&mut self, label: &str, index: usize, modifiers: Modifiers) { - self.get_nth_label(label, index).click_modifiers(modifiers); - self.run_ok(); + self.root_section().hover_nth_label(label, index); } fn drag_nth_label(&mut self, label: &str, index: usize) { - let node = self.get_nth_label(label, index); - - let center = node.rect().center(); - self.event(egui::Event::PointerButton { - pos: center, - button: PointerButton::Primary, - pressed: true, - modifiers: Modifiers::NONE, - }); - - // Step until the time has passed `max_click_duration` so this gets - // registered as a drag. - let wait_time = self.ctx.options(|o| o.input_options.max_click_duration); - let end_time = self.ctx.input(|i| i.time + wait_time); - while self.ctx.input(|i| i.time) < end_time { - self.step(); - } + self.root_section().drag_nth_label(label, index); } fn drop_nth_label(&mut self, label: &str, index: usize) { - let node = self.get_nth_label(label, index); - let event = egui::Event::PointerButton { - pos: node.rect().center(), - button: PointerButton::Primary, - pressed: false, - modifiers: Modifiers::NONE, - }; - self.event(event); - self.remove_cursor(); + self.root_section().drop_nth_label(label, index); } fn drag_at(&mut self, pos: egui::Pos2) { diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 2ccf27723d21..75147ae21615 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -5,7 +5,7 @@ use egui_kittest::kittest::Queryable as _; pub struct ViewerSection<'a, 'h> { pub harness: &'a mut egui_kittest::Harness<'h, re_viewer::App>, - pub section_label: &'a str, + pub section_label: Option<&'a str>, } impl<'a, 'h: 'a> ViewerSection<'a, 'h> { @@ -13,10 +13,11 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { where 'a: 'n, { - let node = self - .harness - .get_by_role_and_label(Role::Pane, self.section_label); - node + let Some(section_label) = self.section_label else { + return self.harness.root(); + }; + self.harness + .get_by_role_and_label(Role::Pane, section_label) } pub fn get_label<'n>(&'n mut self, label: &'n str) -> egui_kittest::Node<'n> @@ -74,6 +75,11 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.harness.run_ok(); } + pub fn right_click_nth_label(&mut self, label: &str, index: usize) { + self.get_nth_label(label, index).click_secondary(); + self.harness.run_ok(); + } + fn drag_label_inner(&mut self, label: &str, index: Option) { let node = self.get_nth_label_inner(label, index); @@ -140,31 +146,45 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { } pub trait GetSection<'h> { - fn get_section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a; + + fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> where 'h: 'a; fn blueprint_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.get_section("_blueprint_tree") + self.section("_blueprint_tree") } fn streams_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.get_section("_streams_tree") + self.section("_streams_tree") } fn selection_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.get_section("_selection_panel") + self.section("_selection_panel") } } impl<'h> GetSection<'h> for egui_kittest::Harness<'h, re_viewer::App> { - fn get_section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a, + { + ViewerSection::<'a, 'h> { + harness: self, + section_label: Some(section_label), + } + } + + fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> where 'h: 'a, { ViewerSection::<'a, 'h> { harness: self, - section_label, + section_label: None, } } } From 676d2466649324975ad4f1f2e4d7df3c5d1f8081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 09:51:27 +0100 Subject: [PATCH 08/23] work --- .../src/kittest_harness_ext.rs | 95 +++++++--------- .../re_integration_test/src/viewer_section.rs | 106 ++++++++++-------- 2 files changed, 101 insertions(+), 100 deletions(-) diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index 4b4169e5eccc..d84d65673928 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -29,10 +29,11 @@ use re_viewer::{ use re_viewer_context::ContainerId; use re_viewport_blueprint::ViewportBlueprint; +use crate::GetSection; use crate::GetSection as _; // Kittest harness utilities specific to the Rerun app. -pub trait HarnessExt { +pub trait HarnessExt<'h>: GetSection<'h> { // Initializes the chuck store with a new, empty recording and blueprint. fn init_recording(&mut self); @@ -69,19 +70,7 @@ pub trait HarnessExt { // Get the position of a node in the UI by its label. fn get_panel_position(&mut self, label: &str) -> egui::Rect; - // Clicks a node in the UI by its label. - fn click_label(&mut self, label: &str); - fn right_click_label(&mut self, label: &str); - fn click_label_contains(&mut self, label: &str); - fn click_nth_label(&mut self, label: &str, index: usize); - fn right_click_nth_label(&mut self, label: &str, index: usize); - fn hover_label_contains(&mut self, label: &str); - fn hover_nth_label(&mut self, label: &str, index: usize); - - // Drag-and-drop functions. You can use `hover` between `drag` and `drop`. - fn drag_nth_label(&mut self, label: &str, index: usize); - fn drop_nth_label(&mut self, label: &str, index: usize); - + // Drag-and-drop functions based on position fn drag_at(&mut self, pos: egui::Pos2); fn hover_at(&mut self, pos: egui::Pos2); fn drop_at(&mut self, pos: egui::Pos2); @@ -109,9 +98,47 @@ pub trait HarnessExt { fn set_time_panel_opened(&mut self, opened: bool) { self.set_panel_opened("Time panel toggle", opened); } + + // Convenience proxy functions to the root ViewerSection + + fn click_label(&mut self, label: &str) { + self.root_section().click_label(label); + } + + fn right_click_label(&mut self, label: &str) { + self.root_section().right_click_label(label); + } + + fn click_label_contains(&mut self, label: &str) { + self.root_section().click_label_contains(label); + } + + fn click_nth_label(&mut self, label: &str, index: usize) { + self.root_section().click_nth_label(label, index); + } + + fn right_click_nth_label(&mut self, label: &str, index: usize) { + self.root_section().right_click_nth_label(label, index); + } + + fn hover_label_contains(&mut self, label: &str) { + self.root_section().hover_label_contains(label); + } + + fn hover_nth_label(&mut self, label: &str, index: usize) { + self.root_section().hover_nth_label(label, index); + } + + fn drag_nth_label(&mut self, label: &str, index: usize) { + self.root_section().drag_nth_label(label, index); + } + + fn drop_nth_label(&mut self, label: &str, index: usize) { + self.root_section().drop_nth_label(label, index); + } } -impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { +impl<'h> HarnessExt<'h> for egui_kittest::Harness<'h, re_viewer::App> { fn clear_current_blueprint(&mut self) { self.setup_viewport_blueprint(|_viewer_context, blueprint| { for item in blueprint.contents_iter() { @@ -245,48 +272,10 @@ impl HarnessExt for egui_kittest::Harness<'_, re_viewer::App> { self.run_ok(); } - fn click_label(&mut self, label: &str) { - self.root_section().click_label(label); - } - - fn right_click_label(&mut self, label: &str) { - self.root_section().right_click_label(label); - } - - fn click_label_contains(&mut self, label: &str) { - self.get_by_label_contains(label).click(); - self.run_ok(); - } - fn get_panel_position(&mut self, label: &str) -> egui::Rect { self.get_by_role_and_label(Role::Pane, label).rect() } - fn click_nth_label(&mut self, label: &str, index: usize) { - self.root_section().click_nth_label(label, index); - } - - fn right_click_nth_label(&mut self, label: &str, index: usize) { - self.root_section().right_click_nth_label(label, index); - } - - fn hover_label_contains(&mut self, label: &str) { - self.get_by_label_contains(label).hover(); - self.run_ok(); - } - - fn hover_nth_label(&mut self, label: &str, index: usize) { - self.root_section().hover_nth_label(label, index); - } - - fn drag_nth_label(&mut self, label: &str, index: usize) { - self.root_section().drag_nth_label(label, index); - } - - fn drop_nth_label(&mut self, label: &str, index: usize) { - self.root_section().drop_nth_label(label, index); - } - fn drag_at(&mut self, pos: egui::Pos2) { self.event(egui::Event::PointerButton { pos, diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 75147ae21615..084fbb4f4e67 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -9,6 +9,7 @@ pub struct ViewerSection<'a, 'h> { } impl<'a, 'h: 'a> ViewerSection<'a, 'h> { + // Returns the root node of the section. pub fn root<'n>(&'n self) -> egui_kittest::Node<'n> where 'a: 'n, @@ -20,6 +21,7 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { .get_by_role_and_label(Role::Pane, section_label) } + // Returns the only node with the given label. pub fn get_label<'n>(&'n mut self, label: &'n str) -> egui_kittest::Node<'n> where 'a: 'n, @@ -27,6 +29,7 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.root().get_by_label(label) } + // Returns the nth node with the given label. pub fn get_nth_label<'n>(&'n mut self, label: &'n str, index: usize) -> egui_kittest::Node<'n> where 'a: 'n, @@ -40,46 +43,83 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { nodes.swap_remove(index) } - fn get_nth_label_inner<'n>( - &'n mut self, - label: &'n str, - index: Option, - ) -> egui_kittest::Node<'n> - where - 'a: 'n, - { - if let Some(index) = index { - self.get_nth_label(label, index) - } else { - self.get_label(label) - } - } - pub fn click_label(&mut self, label: &str) { self.root().get_by_label(label).click(); self.harness.run_ok(); } + pub fn right_click_label(&mut self, label: &str) { + self.root().get_by_label(label).click_secondary(); + self.harness.run_ok(); + } + pub fn click_nth_label(&mut self, label: &str, index: usize) { self.get_nth_label(label, index).click(); self.harness.run_ok(); } + pub fn right_click_nth_label(&mut self, label: &str, index: usize) { + self.get_nth_label(label, index).click_secondary(); + self.harness.run_ok(); + } + pub fn click_label_modifiers(&mut self, label: &str, modifiers: egui::Modifiers) { self.root().get_by_label(label).click_modifiers(modifiers); self.harness.run_ok(); } - pub fn right_click_label(&mut self, label: &str) { - self.root().get_by_label(label).click_secondary(); + pub fn click_label_contains(&mut self, label: &str) { + self.root().get_by_label_contains(label).click(); self.harness.run_ok(); } - pub fn right_click_nth_label(&mut self, label: &str, index: usize) { - self.get_nth_label(label, index).click_secondary(); + pub fn drag_nth_label(&mut self, label: &str, index: usize) { + self.drag_label_inner(label, Some(index)); + } + + pub fn drag_label(&mut self, label: &str) { + self.drag_label_inner(label, None); + } + + pub fn drop_nth_label(&mut self, label: &str, index: usize) { + self.drop_label_inner(label, Some(index)); + } + + pub fn drop_label(&mut self, label: &str) { + self.drop_label_inner(label, None); + } + + pub fn hover_label(&mut self, label: &str) { + self.get_label(label).hover(); + self.harness.run_ok(); + } + + pub fn hover_nth_label(&mut self, label: &str, index: usize) { + self.get_nth_label(label, index).hover(); + self.harness.run_ok(); + } + + pub fn hover_label_contains(&mut self, label: &str) { + self.root().get_by_label_contains(label).hover(); self.harness.run_ok(); } + // Helper function to get the node with the given label + fn get_nth_label_inner<'n>( + &'n mut self, + label: &'n str, + index: Option, + ) -> egui_kittest::Node<'n> + where + 'a: 'n, + { + if let Some(index) = index { + self.get_nth_label(label, index) + } else { + self.get_label(label) + } + } + fn drag_label_inner(&mut self, label: &str, index: Option) { let node = self.get_nth_label_inner(label, index); @@ -93,7 +133,6 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { // Step until the time has passed `max_click_duration` so this gets // registered as a drag. - // self.harness.step(); let wait_time = self .harness .ctx @@ -102,15 +141,6 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { while self.harness.ctx.input(|i| i.time) < end_time { self.harness.step(); } - // self.harness.step(); - } - - pub fn drag_nth_label(&mut self, label: &str, index: usize) { - self.drag_label_inner(label, Some(index)); - } - - pub fn drag_label(&mut self, label: &str) { - self.drag_label_inner(label, None); } pub fn drop_label_inner(&mut self, label: &str, index: Option) { @@ -125,24 +155,6 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.harness.remove_cursor(); self.harness.run_ok(); } - - pub fn drop_nth_label(&mut self, label: &str, index: usize) { - self.drop_label_inner(label, Some(index)); - } - - pub fn drop_label(&mut self, label: &str) { - self.drop_label_inner(label, None); - } - - pub fn hover_label(&mut self, label: &str) { - self.get_label(label).hover(); - self.harness.run_ok(); - } - - pub fn hover_nth_label(&mut self, label: &str, index: usize) { - self.get_nth_label(label, index).hover(); - self.harness.run_ok(); - } } pub trait GetSection<'h> { From 1d765c4ef1a3681da893ca1b9d4462e38ecd9c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 10:17:06 +0100 Subject: [PATCH 09/23] Lint --- crates/viewer/re_viewport/src/viewport_ui.rs | 1 - .../src/kittest_harness_ext.rs | 54 +++++++++++++++++-- tests/rust/re_integration_test/src/lib.rs | 2 +- .../re_integration_test/src/viewer_section.rs | 45 ---------------- .../tests/blueprint_context_menu_test.rs | 3 -- .../tests/container_context_menu_test.rs | 2 - .../tests/context_menu_test.rs | 1 - .../tests/multi_container_test.rs | 2 +- 8 files changed, 52 insertions(+), 58 deletions(-) diff --git a/crates/viewer/re_viewport/src/viewport_ui.rs b/crates/viewer/re_viewport/src/viewport_ui.rs index 27ae3401bde0..5b001e40f571 100644 --- a/crates/viewer/re_viewport/src/viewport_ui.rs +++ b/crates/viewer/re_viewport/src/viewport_ui.rs @@ -3,7 +3,6 @@ //! Contains all views. use ahash::HashMap; -use egui::{WidgetInfo, remap_clamp}; use egui_tiles::{Behavior as _, EditAction}; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item}; diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index d84d65673928..db4334337aff 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -29,11 +29,12 @@ use re_viewer::{ use re_viewer_context::ContainerId; use re_viewport_blueprint::ViewportBlueprint; -use crate::GetSection; -use crate::GetSection as _; +// use crate::GetSection; +// use crate::GetSection as _; +use crate::ViewerSection; // Kittest harness utilities specific to the Rerun app. -pub trait HarnessExt<'h>: GetSection<'h> { +pub trait HarnessExt<'h> { // Initializes the chuck store with a new, empty recording and blueprint. fn init_recording(&mut self); @@ -99,7 +100,32 @@ pub trait HarnessExt<'h>: GetSection<'h> { self.set_panel_opened("Time panel toggle", opened); } - // Convenience proxy functions to the root ViewerSection + // Get a viewer section, eg. blueprint tree or selection panel. + fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a; + + // The viewer section whose root node is the root of the app. + fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> + where + 'h: 'a; + + // The viewer section whose root node is the blueprint tree. + fn blueprint_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.section("_blueprint_tree") + } + + // The viewer section whose root node is the streams tree. + fn streams_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.section("_streams_tree") + } + + // The viewer section whose root node is the selection panel. + fn selection_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { + self.section("_selection_panel") + } + + // Convenience proxy functions to the root section: fn click_label(&mut self, label: &str) { self.root_section().click_label(label); @@ -378,4 +404,24 @@ impl<'h> HarnessExt<'h> for egui_kittest::Harness<'h, re_viewer::App> { self.remove_cursor(); self.run_ok(); } + + fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> + where + 'h: 'a, + { + ViewerSection::<'a, 'h> { + harness: self, + section_label: Some(section_label), + } + } + + fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> + where + 'h: 'a, + { + ViewerSection::<'a, 'h> { + harness: self, + section_label: None, + } + } } diff --git a/tests/rust/re_integration_test/src/lib.rs b/tests/rust/re_integration_test/src/lib.rs index 97ef23cf7942..c43d062629a6 100644 --- a/tests/rust/re_integration_test/src/lib.rs +++ b/tests/rust/re_integration_test/src/lib.rs @@ -9,7 +9,7 @@ use re_redap_client::{ApiError, ConnectionClient, ConnectionRegistry}; use re_server::ServerHandle; use re_uri::external::url::Host; use std::net::TcpListener; -pub use viewer_section::GetSection; +// pub use viewer_section::GetSection; pub use viewer_section::ViewerSection; pub struct TestServer { diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 084fbb4f4e67..3439d37fecf7 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -1,6 +1,5 @@ use egui::PointerButton; use egui::accesskit::Role; -use egui_kittest::kittest::NodeT as _; use egui_kittest::kittest::Queryable as _; pub struct ViewerSection<'a, 'h> { @@ -156,47 +155,3 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { self.harness.run_ok(); } } - -pub trait GetSection<'h> { - fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> - where - 'h: 'a; - - fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> - where - 'h: 'a; - - fn blueprint_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.section("_blueprint_tree") - } - - fn streams_tree<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.section("_streams_tree") - } - - fn selection_panel<'a>(&'a mut self) -> ViewerSection<'a, 'h> { - self.section("_selection_panel") - } -} - -impl<'h> GetSection<'h> for egui_kittest::Harness<'h, re_viewer::App> { - fn section<'a>(&'a mut self, section_label: &'a str) -> ViewerSection<'a, 'h> - where - 'h: 'a, - { - ViewerSection::<'a, 'h> { - harness: self, - section_label: Some(section_label), - } - } - - fn root_section<'a>(&'a mut self) -> ViewerSection<'a, 'h> - where - 'h: 'a, - { - ViewerSection::<'a, 'h> { - harness: self, - section_label: None, - } - } -} diff --git a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs index 024573a1f3d0..09f604b3d922 100644 --- a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs @@ -1,6 +1,3 @@ -use egui_kittest::kittest::NodeT as _; -use re_integration_test::GetSection; -use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::external::re_log_types::EntityPathFilter; use re_sdk::log::RowId; diff --git a/tests/rust/re_integration_test/tests/container_context_menu_test.rs b/tests/rust/re_integration_test/tests/container_context_menu_test.rs index 820d9de547a9..54abe76773f6 100644 --- a/tests/rust/re_integration_test/tests/container_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/container_context_menu_test.rs @@ -1,6 +1,4 @@ use egui::Modifiers; - -use re_integration_test::{GetSection as _, HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::ViewClass as _; diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs index 3d2d01b6a52e..b293977decfb 100644 --- a/tests/rust/re_integration_test/tests/context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -1,4 +1,3 @@ -use re_integration_test::{GetSection as _, HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_view_text_document::TextDocumentView; diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index fc6140d19771..eb08f17d1733 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -1,6 +1,6 @@ use egui::vec2; use egui_kittest::kittest::Queryable as _; -use re_integration_test::{GetSection, HarnessExt as _}; +use re_integration_test::{HarnessExt as _}; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::{ContainerId, ViewClass as _}; From c7153ecfef503f46434629d811f2177b45eccc5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 10:31:39 +0100 Subject: [PATCH 10/23] lint --- .../viewer/re_selection_panel/src/selection_panel.rs | 11 +++-------- crates/viewer/re_ui/src/ui_ext.rs | 6 ++---- crates/viewer/re_viewport/src/viewport_ui.rs | 1 + .../tests/blueprint_context_menu_test.rs | 1 + .../tests/container_context_menu_test.rs | 1 + .../re_integration_test/tests/context_menu_test.rs | 1 + 6 files changed, 9 insertions(+), 12 deletions(-) diff --git a/crates/viewer/re_selection_panel/src/selection_panel.rs b/crates/viewer/re_selection_panel/src/selection_panel.rs index 779b18338ce5..d2b92886adf9 100644 --- a/crates/viewer/re_selection_panel/src/selection_panel.rs +++ b/crates/viewer/re_selection_panel/src/selection_panel.rs @@ -72,7 +72,7 @@ impl SelectionPanel { ctx.send_time_commands([TimeControlCommand::ClearHighlightedRange]); } - let response = panel.show_animated_inside(ui, expanded, |ui: &mut egui::Ui| { + panel.show_animated_inside(ui, expanded, |ui: &mut egui::Ui| { ui.panel_content(|ui| { let hover = "The selection view contains information and options about \ the currently selected object(s)"; @@ -93,11 +93,6 @@ impl SelectionPanel { }); r.state }); - if let Some(response) = response { - response.response.widget_info(|| { - egui::WidgetInfo::labeled(egui::WidgetType::Panel, true, "selection_panel") - }); - } // run modals (these are noop if the modals are not active) self.view_entity_modal.ui(ui.ctx(), ctx, viewport); @@ -142,7 +137,7 @@ impl SelectionPanel { }); } } else { - let res = list_item::list_item_scope(ui, "selections_panel", |ui| { + let response = list_item::list_item_scope(ui, "selections_panel", |ui| { ui.list_item() .with_height(tokens.title_bar_height()) .interactive(false) @@ -160,7 +155,7 @@ impl SelectionPanel { item_title_list_item(ctx, viewport, ui, item); } }); - res.response.widget_info(|| { + response.response.widget_info(|| { WidgetInfo::labeled(egui::WidgetType::Panel, true, "_selection_panel") }); } diff --git a/crates/viewer/re_ui/src/ui_ext.rs b/crates/viewer/re_ui/src/ui_ext.rs index c5d7b75c0c3e..1bd459683502 100644 --- a/crates/viewer/re_ui/src/ui_ext.rs +++ b/crates/viewer/re_ui/src/ui_ext.rs @@ -344,15 +344,13 @@ pub trait UiExt { // TODO(ab): this used to be used for inner margin, after registering full span range in panels. // It's highly likely that all these use are now redundant. - fn panel_content( - &mut self, - add_contents: impl FnOnce(&mut egui::Ui) -> R, - ) -> egui::InnerResponse { + fn panel_content(&mut self, add_contents: impl FnOnce(&mut egui::Ui) -> R) -> R { egui::Frame { inner_margin: self.tokens().panel_margin(), ..Default::default() } .show(self.ui_mut(), |ui| add_contents(ui)) + .inner } /// Static title bar used to separate panels into section. diff --git a/crates/viewer/re_viewport/src/viewport_ui.rs b/crates/viewer/re_viewport/src/viewport_ui.rs index 5b001e40f571..b6b30194e93d 100644 --- a/crates/viewer/re_viewport/src/viewport_ui.rs +++ b/crates/viewer/re_viewport/src/viewport_ui.rs @@ -3,6 +3,7 @@ //! Contains all views. use ahash::HashMap; +use egui::remap_clamp; use egui_tiles::{Behavior as _, EditAction}; use re_context_menu::{SelectionUpdateBehavior, context_menu_ui_for_item}; diff --git a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs index 09f604b3d922..424f64f0a215 100644 --- a/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/blueprint_context_menu_test.rs @@ -1,3 +1,4 @@ +use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::external::re_log_types::EntityPathFilter; use re_sdk::log::RowId; diff --git a/tests/rust/re_integration_test/tests/container_context_menu_test.rs b/tests/rust/re_integration_test/tests/container_context_menu_test.rs index 54abe76773f6..cd3b934b2dbf 100644 --- a/tests/rust/re_integration_test/tests/container_context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/container_context_menu_test.rs @@ -1,4 +1,5 @@ use egui::Modifiers; +use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::ViewClass as _; diff --git a/tests/rust/re_integration_test/tests/context_menu_test.rs b/tests/rust/re_integration_test/tests/context_menu_test.rs index b293977decfb..92008cdf25fc 100644 --- a/tests/rust/re_integration_test/tests/context_menu_test.rs +++ b/tests/rust/re_integration_test/tests/context_menu_test.rs @@ -1,3 +1,4 @@ +use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_view_text_document::TextDocumentView; From 33b638bc086d3a450b376e946a0a8590d7b7922f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 11:10:06 +0100 Subject: [PATCH 11/23] Clippy --- crates/viewer/re_data_ui/src/item_ui.rs | 3 ++- tests/rust/re_integration_test/src/viewer_section.rs | 6 +++--- .../rust/re_integration_test/tests/multi_container_test.rs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/viewer/re_data_ui/src/item_ui.rs b/crates/viewer/re_data_ui/src/item_ui.rs index 1acb27b2c10f..32f92800951c 100644 --- a/crates/viewer/re_data_ui/src/item_ui.rs +++ b/crates/viewer/re_data_ui/src/item_ui.rs @@ -867,7 +867,8 @@ pub fn table_id_button_ui( .on_hover_ui(|ui| { ui.label(format!("Table: {table_id}")); }) - }).inner; + }) + .inner; if response.hovered() { ctx.selection_state().set_hovered(item.clone()); diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index 3439d37fecf7..ac1615afa690 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -21,7 +21,7 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { } // Returns the only node with the given label. - pub fn get_label<'n>(&'n mut self, label: &'n str) -> egui_kittest::Node<'n> + pub fn get_label<'n>(&'n self, label: &'n str) -> egui_kittest::Node<'n> where 'a: 'n, { @@ -29,7 +29,7 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { } // Returns the nth node with the given label. - pub fn get_nth_label<'n>(&'n mut self, label: &'n str, index: usize) -> egui_kittest::Node<'n> + pub fn get_nth_label<'n>(&'n self, label: &'n str, index: usize) -> egui_kittest::Node<'n> where 'a: 'n, { @@ -105,7 +105,7 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { // Helper function to get the node with the given label fn get_nth_label_inner<'n>( - &'n mut self, + &'n self, label: &'n str, index: Option, ) -> egui_kittest::Node<'n> diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index eb08f17d1733..e0cf1b68d9c9 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -1,6 +1,6 @@ use egui::vec2; use egui_kittest::kittest::Queryable as _; -use re_integration_test::{HarnessExt as _}; +use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; use re_viewer::external::re_viewer_context::{ContainerId, ViewClass as _}; From 1ce498f71d35d678e723e1d93544e3d4acacb0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 11:39:48 +0100 Subject: [PATCH 12/23] Removes run_ok() --- .../re_integration_test/src/viewer_section.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index ac1615afa690..b47ad507f872 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -44,32 +44,32 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { pub fn click_label(&mut self, label: &str) { self.root().get_by_label(label).click(); - self.harness.run_ok(); + self.harness.run(); } pub fn right_click_label(&mut self, label: &str) { self.root().get_by_label(label).click_secondary(); - self.harness.run_ok(); + self.harness.run(); } pub fn click_nth_label(&mut self, label: &str, index: usize) { self.get_nth_label(label, index).click(); - self.harness.run_ok(); + self.harness.run(); } pub fn right_click_nth_label(&mut self, label: &str, index: usize) { self.get_nth_label(label, index).click_secondary(); - self.harness.run_ok(); + self.harness.run(); } pub fn click_label_modifiers(&mut self, label: &str, modifiers: egui::Modifiers) { self.root().get_by_label(label).click_modifiers(modifiers); - self.harness.run_ok(); + self.harness.run(); } pub fn click_label_contains(&mut self, label: &str) { self.root().get_by_label_contains(label).click(); - self.harness.run_ok(); + self.harness.run(); } pub fn drag_nth_label(&mut self, label: &str, index: usize) { @@ -90,17 +90,17 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { pub fn hover_label(&mut self, label: &str) { self.get_label(label).hover(); - self.harness.run_ok(); + self.harness.run(); } pub fn hover_nth_label(&mut self, label: &str, index: usize) { self.get_nth_label(label, index).hover(); - self.harness.run_ok(); + self.harness.run(); } pub fn hover_label_contains(&mut self, label: &str) { self.root().get_by_label_contains(label).hover(); - self.harness.run_ok(); + self.harness.run(); } // Helper function to get the node with the given label @@ -152,6 +152,6 @@ impl<'a, 'h: 'a> ViewerSection<'a, 'h> { }; self.harness.event(event); self.harness.remove_cursor(); - self.harness.run_ok(); + self.harness.run(); } } From 2ed981a4e40b7ba0db748e476fc5dcddb68a4e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Tue, 28 Oct 2025 11:41:48 +0100 Subject: [PATCH 13/23] Adds a comment --- tests/rust/re_integration_test/src/viewer_section.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/rust/re_integration_test/src/viewer_section.rs b/tests/rust/re_integration_test/src/viewer_section.rs index b47ad507f872..13ebb911f8a1 100644 --- a/tests/rust/re_integration_test/src/viewer_section.rs +++ b/tests/rust/re_integration_test/src/viewer_section.rs @@ -2,6 +2,8 @@ use egui::PointerButton; use egui::accesskit::Role; use egui_kittest::kittest::Queryable as _; +// A section of the viewer, e.g. the "Blueprint" or "Recording" panel. Every query and action in a section +// only affects the children of the section. pub struct ViewerSection<'a, 'h> { pub harness: &'a mut egui_kittest::Harness<'h, re_viewer::App>, pub section_label: Option<&'a str>, From 1f40dd8476a606149fa51f96313e27cae29ff826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Wed, 29 Oct 2025 13:44:52 +0100 Subject: [PATCH 14/23] Test: check focus --- .../src/kittest_harness_ext.rs | 15 +++ .../re_integration_test/tests/check_focus.rs | 124 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tests/rust/re_integration_test/tests/check_focus.rs diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index db4334337aff..1391d157233d 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -71,6 +71,9 @@ pub trait HarnessExt<'h> { // Get the position of a node in the UI by its label. fn get_panel_position(&mut self, label: &str) -> egui::Rect; + // Click at a position in the UI. + fn click_at(&mut self, pos: egui::Pos2); + // Drag-and-drop functions based on position fn drag_at(&mut self, pos: egui::Pos2); fn hover_at(&mut self, pos: egui::Pos2); @@ -302,6 +305,18 @@ impl<'h> HarnessExt<'h> for egui_kittest::Harness<'h, re_viewer::App> { self.get_by_role_and_label(Role::Pane, label).rect() } + fn click_at(&mut self, pos: egui::Pos2) { + for pressed in [true, false] { + self.event(egui::Event::PointerButton { + pos, + button: PointerButton::Primary, + pressed, + modifiers: Modifiers::NONE, + }); + } + self.run(); + } + fn drag_at(&mut self, pos: egui::Pos2) { self.event(egui::Event::PointerButton { pos, diff --git a/tests/rust/re_integration_test/tests/check_focus.rs b/tests/rust/re_integration_test/tests/check_focus.rs new file mode 100644 index 000000000000..c588b8929782 --- /dev/null +++ b/tests/rust/re_integration_test/tests/check_focus.rs @@ -0,0 +1,124 @@ +use egui::accesskit::Role; +use egui_kittest::SnapshotOptions; +use egui_kittest::kittest::Queryable; +use re_integration_test::HarnessExt as _; +use re_sdk::TimePoint; +use re_sdk::external::re_log_types::EntityPathFilter; +use re_sdk::log::RowId; +use re_viewer::external::re_viewer_context::{RecommendedView, ViewClass as _}; +use re_viewer::external::{re_types, re_view_spatial}; +use re_viewer::viewer_test_utils::{self, HarnessOptions}; +use re_viewport_blueprint::ViewBlueprint; + +fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); + harness.init_recording(); + harness.set_selection_panel_opened(true); + + // Log some data + harness.log_entity("group/boxes3d", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::default(), + &re_types::archetypes::Boxes3D::from_centers_and_half_sizes( + [(-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0)], + [(0.2, 0.4, 0.2), (0.2, 0.2, 0.4), (0.4, 0.2, 0.2)], + ) + .with_colors([0xFF0000FF, 0x00FF00FF, 0x0000FFFF]) + .with_fill_mode(re_types::components::FillMode::Solid), + ) + }); + harness +} + +fn setup_single_view_blueprint(harness: &mut egui_kittest::Harness<'_, re_viewer::App>) { + harness.clear_current_blueprint(); + + let mut view_1 = + ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier()); + view_1.display_name = Some("3D view 1".into()); + + harness.setup_viewport_blueprint(|_viewer_context, blueprint| { + blueprint.add_view_at_root(view_1); + }); +} + +#[tokio::test(flavor = "multi_thread")] +pub async fn test_foo() { + let mut harness = make_test_harness(); + harness + .state_mut() + .app_options_mut() + .show_picking_debug_overlay = true; + + setup_single_view_blueprint(&mut harness); + + // Zoom out a bit to see every box + harness.event(egui::Event::Zoom(0.5)); + + // One of the boxes is a bit left to the center + let pixel_of_a_box = harness.get_panel_position("3D view 1").center() + egui::vec2(-30.0, 0.0); + + // Click on the view panel widget + + // harness.blueprint_tree().drag_label("3D view 2"); + harness.run_ok(); + harness.run_ok(); + harness.run_ok(); + harness.hover_at(pixel_of_a_box); + + harness.run_ok(); + harness.hover_at(pixel_of_a_box); + // 3D picking only works if we actually render the app + harness.render().expect("Cannot render app"); + // harness.run_ok(); + + // harness.run_ok(); + // harness.run_ok(); + // harness.run_ok(); + // // harness.streams_tree().hover_label("group/"); + // std::thread::sleep(std::time::Duration::from_millis(1000)); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + // harness.click_at(pixel_of_a_box); + + harness.snapshot_app("xtemp"); + + // harness.click_label("Expand all"); + // harness.snapshot_app("blueprint_tree_context_menu_03"); + + // harness + // .blueprint_tree() + // .right_click_label("Viewport (Grid container)"); + // harness.snapshot_app("blueprint_tree_context_menu_04"); + + // harness.key_press(egui::Key::Escape); + // harness.snapshot_app("blueprint_tree_context_menu_05"); + + // harness.blueprint_tree().right_click_label("Test view"); + // harness.snapshot_app("blueprint_tree_context_menu_06"); + + // harness.key_press(egui::Key::Escape); + // harness.snapshot_app("blueprint_tree_context_menu_07"); + + // harness.blueprint_tree().right_click_label("group"); + // harness.snapshot_app("blueprint_tree_context_menu_08"); + + // harness.key_press(egui::Key::Escape); + // harness.snapshot_app("blueprint_tree_context_menu_09"); + + // harness.blueprint_tree().right_click_label("boxes3d"); + // harness.snapshot_app("blueprint_tree_context_menu_10"); +} From 3fa80ab00c70621c847f2df810320cb8826a9cb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Thu, 30 Oct 2025 17:42:36 +0100 Subject: [PATCH 15/23] Works --- crates/viewer/re_ui/src/testing.rs | 2 +- .../re_viewer/src/viewer_test_utils/mod.rs | 2 +- .../src/kittest_harness_ext.rs | 2 +- .../re_integration_test/tests/check_focus.rs | 124 +++++++----------- 4 files changed, 54 insertions(+), 76 deletions(-) diff --git a/crates/viewer/re_ui/src/testing.rs b/crates/viewer/re_ui/src/testing.rs index b182725160d1..0d7662d3a561 100644 --- a/crates/viewer/re_ui/src/testing.rs +++ b/crates/viewer/re_ui/src/testing.rs @@ -19,7 +19,7 @@ pub fn new_harness(option: TestOptions, size: impl Into) -> HarnessBuil TestOptions::Rendering3D => default_snapshot_options_for_3d(size), }; - let mut builder = egui_kittest::Harness::builder().wgpu().with_size(size); + let mut builder = egui_kittest::Harness::builder().wgpu().with_size(size).with_step_dt(0.01); // emilk did a mistake and made `with_options` a setter instead of a builder… // …we will fix that in the future, but for now, we have to live with it: diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs index 35ab51e0614a..dacbc4bd8969 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs @@ -19,7 +19,7 @@ pub struct HarnessOptions { pub fn viewer_harness(options: &HarnessOptions) -> Harness<'static, App> { let window_size = options.window_size.unwrap_or(egui::vec2(1024.0, 768.0)); - re_ui::testing::new_harness(re_ui::testing::TestOptions::Rendering3D, window_size).build_eframe( + re_ui::testing::new_harness(re_ui::testing::TestOptions::Rendering3D, window_size).with_max_steps(200).build_eframe( |cc| { cc.egui_ctx.set_os(egui::os::OperatingSystem::Nix); customize_eframe_and_setup_renderer(cc).expect("Failed to customize eframe"); diff --git a/tests/rust/re_integration_test/src/kittest_harness_ext.rs b/tests/rust/re_integration_test/src/kittest_harness_ext.rs index 1391d157233d..94840b9e150d 100644 --- a/tests/rust/re_integration_test/src/kittest_harness_ext.rs +++ b/tests/rust/re_integration_test/src/kittest_harness_ext.rs @@ -313,8 +313,8 @@ impl<'h> HarnessExt<'h> for egui_kittest::Harness<'h, re_viewer::App> { pressed, modifiers: Modifiers::NONE, }); + self.run(); } - self.run(); } fn drag_at(&mut self, pos: egui::Pos2) { diff --git a/tests/rust/re_integration_test/tests/check_focus.rs b/tests/rust/re_integration_test/tests/check_focus.rs index c588b8929782..e8493c905ab4 100644 --- a/tests/rust/re_integration_test/tests/check_focus.rs +++ b/tests/rust/re_integration_test/tests/check_focus.rs @@ -1,11 +1,7 @@ -use egui::accesskit::Role; -use egui_kittest::SnapshotOptions; -use egui_kittest::kittest::Queryable; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; -use re_sdk::external::re_log_types::EntityPathFilter; use re_sdk::log::RowId; -use re_viewer::external::re_viewer_context::{RecommendedView, ViewClass as _}; +use re_viewer::external::re_viewer_context::ViewClass as _; use re_viewer::external::{re_types, re_view_spatial}; use re_viewer::viewer_test_utils::{self, HarnessOptions}; use re_viewport_blueprint::ViewBlueprint; @@ -13,7 +9,6 @@ use re_viewport_blueprint::ViewBlueprint; fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); harness.init_recording(); - harness.set_selection_panel_opened(true); // Log some data harness.log_entity("group/boxes3d", |builder| { @@ -21,104 +16,87 @@ fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { RowId::new(), TimePoint::default(), &re_types::archetypes::Boxes3D::from_centers_and_half_sizes( - [(-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0)], + [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0)], [(0.2, 0.4, 0.2), (0.2, 0.2, 0.4), (0.4, 0.2, 0.2)], ) .with_colors([0xFF0000FF, 0x00FF00FF, 0x0000FFFF]) .with_fill_mode(re_types::components::FillMode::Solid), ) }); + harness.log_entity("txt/hello", |builder| { + builder.with_archetype( + RowId::new(), + TimePoint::STATIC, + &re_types::archetypes::TextDocument::new("Hello World!"), + ) + }); + harness } fn setup_single_view_blueprint(harness: &mut egui_kittest::Harness<'_, re_viewer::App>) { harness.clear_current_blueprint(); + let root_cid = harness.add_blueprint_container(egui_tiles::ContainerKind::Horizontal, None); + let tab_cid = harness.add_blueprint_container(egui_tiles::ContainerKind::Tabs, Some(root_cid)); + let vertical_cid = + harness.add_blueprint_container(egui_tiles::ContainerKind::Vertical, Some(root_cid)); + let mut view_1 = ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier()); view_1.display_name = Some("3D view 1".into()); - - harness.setup_viewport_blueprint(|_viewer_context, blueprint| { - blueprint.add_view_at_root(view_1); + let mut view_2 = + ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier()); + view_2.display_name = Some("3D view 2".into()); + + harness.setup_viewport_blueprint(move |_viewer_context, blueprint| { + let text_views = (0..20).map(|i| { + let mut view = ViewBlueprint::new_with_root_wildcard( + re_view_text_document::TextDocumentView::identifier(), + ); + view.display_name = Some(format!("Text view {i}")); + view + }); + blueprint.add_views(text_views, Some(tab_cid), None); + blueprint.add_views([view_1, view_2].into_iter(), Some(vertical_cid), None); }); } #[tokio::test(flavor = "multi_thread")] pub async fn test_foo() { let mut harness = make_test_harness(); - harness - .state_mut() - .app_options_mut() - .show_picking_debug_overlay = true; - setup_single_view_blueprint(&mut harness); - // Zoom out a bit to see every box - harness.event(egui::Event::Zoom(0.5)); + let centerline = harness.get_panel_position("Text view 0").left_center(); + let target_pos = centerline + egui::vec2(100.0, 0.0); + harness.drag_at(centerline); + harness.hover_at(target_pos); + harness.drop_at(target_pos); // One of the boxes is a bit left to the center - let pixel_of_a_box = harness.get_panel_position("3D view 1").center() + egui::vec2(-30.0, 0.0); + let pixel_of_a_box = harness.get_panel_position("3D view 1").center() + egui::vec2(-0.0, 10.0); - // Click on the view panel widget - - // harness.blueprint_tree().drag_label("3D view 2"); - harness.run_ok(); - harness.run_ok(); - harness.run_ok(); + // Hover over the box harness.hover_at(pixel_of_a_box); - harness.run_ok(); - harness.hover_at(pixel_of_a_box); - // 3D picking only works if we actually render the app + // Let the app render. This will run the picking logic which needs the GPU + // and lets the app find the hovered box. harness.render().expect("Cannot render app"); - // harness.run_ok(); - - // harness.run_ok(); - // harness.run_ok(); - // harness.run_ok(); - // // harness.streams_tree().hover_label("group/"); - // std::thread::sleep(std::time::Duration::from_millis(1000)); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - // harness.click_at(pixel_of_a_box); - - harness.snapshot_app("xtemp"); - - // harness.click_label("Expand all"); - // harness.snapshot_app("blueprint_tree_context_menu_03"); + harness.run_steps(50); - // harness - // .blueprint_tree() - // .right_click_label("Viewport (Grid container)"); - // harness.snapshot_app("blueprint_tree_context_menu_04"); + // Double click on the box + harness.click_at(pixel_of_a_box); + harness.click_at(pixel_of_a_box); - // harness.key_press(egui::Key::Escape); - // harness.snapshot_app("blueprint_tree_context_menu_05"); - - // harness.blueprint_tree().right_click_label("Test view"); - // harness.snapshot_app("blueprint_tree_context_menu_06"); - - // harness.key_press(egui::Key::Escape); - // harness.snapshot_app("blueprint_tree_context_menu_07"); - - // harness.blueprint_tree().right_click_label("group"); - // harness.snapshot_app("blueprint_tree_context_menu_08"); + harness.blueprint_tree().hover_label("3D view 1"); + harness.event(egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Page, + delta: egui::vec2(0.0, -1.0), + modifiers: egui::Modifiers::NONE, + }); - // harness.key_press(egui::Key::Escape); - // harness.snapshot_app("blueprint_tree_context_menu_09"); + harness.blueprint_tree().click_label("boxes3d"); + harness.blueprint_tree().click_label("boxes3d"); - // harness.blueprint_tree().right_click_label("boxes3d"); - // harness.snapshot_app("blueprint_tree_context_menu_10"); + harness.snapshot_app("xtemp"); } From 93dce7eff125246c9be60ab290603f7eefdb4a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 31 Oct 2025 10:39:51 +0100 Subject: [PATCH 16/23] Finishes test --- .../re_viewer/src/viewer_test_utils/mod.rs | 63 +++++++++++-------- .../re_integration_test/tests/check_focus.rs | 41 +++++++++--- .../tests/multi_container_test.rs | 1 + .../tests/snapshots/check_focus_1.png | 3 + .../tests/snapshots/check_focus_2.png | 3 + .../tests/snapshots/check_focus_3.png | 3 + .../tests/snapshots/check_focus_4.png | 3 + .../tests/snapshots/check_focus_5.png | 3 + .../tests/snapshots/check_focus_6.png | 3 + .../tests/snapshots/check_focus_7.png | 3 + .../tests/snapshots/check_focus_8.png | 3 + .../tests/snapshots/xtemp.png | 3 + 12 files changed, 96 insertions(+), 36 deletions(-) create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_1.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_2.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_3.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_4.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_5.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_6.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_7.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/check_focus_8.png create mode 100644 tests/rust/re_integration_test/tests/snapshots/xtemp.png diff --git a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs index dacbc4bd8969..54c2d69f23e8 100644 --- a/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs +++ b/crates/viewer/re_viewer/src/viewer_test_utils/mod.rs @@ -13,39 +13,48 @@ use crate::{ #[derive(Default)] pub struct HarnessOptions { pub window_size: Option, + pub max_steps: Option, + pub step_dt: Option, } /// Convenience function for creating a kittest harness of the viewer App. pub fn viewer_harness(options: &HarnessOptions) -> Harness<'static, App> { let window_size = options.window_size.unwrap_or(egui::vec2(1024.0, 768.0)); - re_ui::testing::new_harness(re_ui::testing::TestOptions::Rendering3D, window_size).with_max_steps(200).build_eframe( - |cc| { - cc.egui_ctx.set_os(egui::os::OperatingSystem::Nix); - customize_eframe_and_setup_renderer(cc).expect("Failed to customize eframe"); - let mut app = App::new( - MainThreadToken::i_promise_i_am_only_using_this_for_a_test(), - build_info!(), - AppEnvironment::Test, - StartupOptions { - // Don't show the welcome / example screen in tests. - // See also: https://github.com/rerun-io/rerun/issues/10989 - hide_welcome_screen: true, - // Don't calculate memory limit in tests. - memory_limit: re_memory::MemoryLimit::UNLIMITED, - ..Default::default() - }, - cc, - None, - AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen() - .expect("Failed to create AsyncRuntimeHandle"), - ); - // Force the FFmpeg path to be wrong so we have a reproducible behavior. - app.app_options_mut().video_decoder_ffmpeg_path = "/fake/ffmpeg/path".to_owned(); - app.app_options_mut().video_decoder_override_ffmpeg_path = true; - app - }, - ) + let mut harness_builder = + re_ui::testing::new_harness(re_ui::testing::TestOptions::Rendering3D, window_size); + if let Some(max_steps) = options.max_steps { + harness_builder = harness_builder.with_max_steps(max_steps); + } + if let Some(step_dt) = options.step_dt { + harness_builder = harness_builder.with_step_dt(step_dt); + } + + harness_builder.build_eframe(|cc| { + cc.egui_ctx.set_os(egui::os::OperatingSystem::Nix); + customize_eframe_and_setup_renderer(cc).expect("Failed to customize eframe"); + let mut app = App::new( + MainThreadToken::i_promise_i_am_only_using_this_for_a_test(), + build_info!(), + AppEnvironment::Test, + StartupOptions { + // Don't show the welcome / example screen in tests. + // See also: https://github.com/rerun-io/rerun/issues/10989 + hide_welcome_screen: true, + // Don't calculate memory limit in tests. + memory_limit: re_memory::MemoryLimit::UNLIMITED, + ..Default::default() + }, + cc, + None, + AsyncRuntimeHandle::from_current_tokio_runtime_or_wasmbindgen() + .expect("Failed to create AsyncRuntimeHandle"), + ); + // Force the FFmpeg path to be wrong so we have a reproducible behavior. + app.app_options_mut().video_decoder_ffmpeg_path = "/fake/ffmpeg/path".to_owned(); + app.app_options_mut().video_decoder_override_ffmpeg_path = true; + app + }) } /// Steps through the harness until the `predicate` closure returns `true`. diff --git a/tests/rust/re_integration_test/tests/check_focus.rs b/tests/rust/re_integration_test/tests/check_focus.rs index e8493c905ab4..b2bf847dc023 100644 --- a/tests/rust/re_integration_test/tests/check_focus.rs +++ b/tests/rust/re_integration_test/tests/check_focus.rs @@ -1,3 +1,4 @@ +use egui_kittest::kittest::Queryable as _; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; @@ -7,7 +8,11 @@ use re_viewer::viewer_test_utils::{self, HarnessOptions}; use re_viewport_blueprint::ViewBlueprint; fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { - let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions::default()); + let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { + window_size: Some(egui::vec2(1024.0, 768.0)), + max_steps: Some(200), + step_dt: Some(1.0 / 60.0), + }); harness.init_recording(); // Log some data @@ -63,18 +68,22 @@ fn setup_single_view_blueprint(harness: &mut egui_kittest::Harness<'_, re_viewer } #[tokio::test(flavor = "multi_thread")] -pub async fn test_foo() { +pub async fn test_check_focus() { let mut harness = make_test_harness(); setup_single_view_blueprint(&mut harness); + // Make the left panel wider let centerline = harness.get_panel_position("Text view 0").left_center(); let target_pos = centerline + egui::vec2(100.0, 0.0); harness.drag_at(centerline); + harness.snapshot_app("check_focus_1"); harness.hover_at(target_pos); + harness.snapshot_app("check_focus_2"); harness.drop_at(target_pos); + harness.snapshot_app("check_focus_3"); - // One of the boxes is a bit left to the center - let pixel_of_a_box = harness.get_panel_position("3D view 1").center() + egui::vec2(-0.0, 10.0); + // One of the boxes is at the center of the view + let pixel_of_a_box = harness.get_panel_position("3D view 1").center(); // Hover over the box harness.hover_at(pixel_of_a_box); @@ -82,21 +91,35 @@ pub async fn test_foo() { // Let the app render. This will run the picking logic which needs the GPU // and lets the app find the hovered box. harness.render().expect("Cannot render app"); - harness.run_steps(50); + harness.run(); + harness.snapshot_app("check_focus_4"); - // Double click on the box + // Double click on the box, see how it expands the view harness.click_at(pixel_of_a_box); harness.click_at(pixel_of_a_box); + harness.snapshot_app("check_focus_5"); + // Scroll down to see the second view stays collapsed harness.blueprint_tree().hover_label("3D view 1"); harness.event(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Page, delta: egui::vec2(0.0, -1.0), modifiers: egui::Modifiers::NONE, }); + harness.snapshot_app("check_focus_6"); - harness.blueprint_tree().click_label("boxes3d"); - harness.blueprint_tree().click_label("boxes3d"); + // Double click the entity on the streams tree and see all views expand + harness.streams_tree().hover_label("boxes3d"); + harness.streams_tree().click_label("boxes3d"); + harness.streams_tree().click_label("boxes3d"); + harness.snapshot_app("check_focus_7"); - harness.snapshot_app("xtemp"); + // Scroll down to see the second view is entirely expanded + harness.blueprint_tree().hover_label("3D view 1"); + harness.event(egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Page, + delta: egui::vec2(0.0, -1.0), + modifiers: egui::Modifiers::NONE, + }); + harness.snapshot_app("check_focus_8"); } diff --git a/tests/rust/re_integration_test/tests/multi_container_test.rs b/tests/rust/re_integration_test/tests/multi_container_test.rs index e0cf1b68d9c9..12f96bd65063 100644 --- a/tests/rust/re_integration_test/tests/multi_container_test.rs +++ b/tests/rust/re_integration_test/tests/multi_container_test.rs @@ -11,6 +11,7 @@ use re_viewport_blueprint::ViewBlueprint; fn make_multi_view_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { window_size: Some(egui::Vec2::new(1024.0, 1024.0)), + ..Default::default() }); harness.init_recording(); diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png new file mode 100644 index 000000000000..52548d157ae6 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d9fe5c0fac60ecb8d25d526d929a398e70d37af45aa659a20e260c80ecc2d9f +size 162038 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png new file mode 100644 index 000000000000..723dff6b35ef --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a6bd5a12ae9172637ad3f42da66bde9855bd55a7096bdcbba15260ca329c9ef +size 156587 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png new file mode 100644 index 000000000000..59e74cb54114 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c28437e37d1c31b35ec9a305e19dd6a08f6b125f6744b83471becfab40dfc216 +size 156117 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png new file mode 100644 index 000000000000..bb2e98174c85 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61b6d1e57495da0f66601948ae5ca60f4736e0e6ce3d0275d5fbd4ebc8b6e8e9 +size 157712 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png new file mode 100644 index 000000000000..a33338b0ea36 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1a8b4485f083db90a1872130f0629d41fbe026e858f7da5dc1a6fd728b64d04 +size 162089 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png new file mode 100644 index 000000000000..cd2caad9fc11 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4b277446de6da1878160e151d2b1901e805594ec3708f6223016d418a1c03cd5 +size 162794 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png new file mode 100644 index 000000000000..a554232e396e --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d4e24727f3aa483cecf43cdd899527e01120ec1c9ab3a750c957ba15a1c1df4 +size 133540 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png new file mode 100644 index 000000000000..a8dd725daeda --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:920658fb1dd76139dd74215325d55cefee2cbc37fe17f8b5b4d1cbdedb8b9c9c +size 132579 diff --git a/tests/rust/re_integration_test/tests/snapshots/xtemp.png b/tests/rust/re_integration_test/tests/snapshots/xtemp.png new file mode 100644 index 000000000000..d553aacda2e0 --- /dev/null +++ b/tests/rust/re_integration_test/tests/snapshots/xtemp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:075c63cd162193b17fd70cac276297bf474d55d0658cdc47e3c432aa5dea0049 +size 162488 From fe5a0e6ce3946fe5d782e94e18f0e28e2375b33b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 31 Oct 2025 10:40:31 +0100 Subject: [PATCH 17/23] Removes manual checklist item --- .../re_integration_test/tests/check_focus.rs | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 tests/rust/re_integration_test/tests/check_focus.rs diff --git a/tests/rust/re_integration_test/tests/check_focus.rs b/tests/rust/re_integration_test/tests/check_focus.rs deleted file mode 100644 index b2bf847dc023..000000000000 --- a/tests/rust/re_integration_test/tests/check_focus.rs +++ /dev/null @@ -1,125 +0,0 @@ -use egui_kittest::kittest::Queryable as _; -use re_integration_test::HarnessExt as _; -use re_sdk::TimePoint; -use re_sdk::log::RowId; -use re_viewer::external::re_viewer_context::ViewClass as _; -use re_viewer::external::{re_types, re_view_spatial}; -use re_viewer::viewer_test_utils::{self, HarnessOptions}; -use re_viewport_blueprint::ViewBlueprint; - -fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { - let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { - window_size: Some(egui::vec2(1024.0, 768.0)), - max_steps: Some(200), - step_dt: Some(1.0 / 60.0), - }); - harness.init_recording(); - - // Log some data - harness.log_entity("group/boxes3d", |builder| { - builder.with_archetype( - RowId::new(), - TimePoint::default(), - &re_types::archetypes::Boxes3D::from_centers_and_half_sizes( - [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (1.0, 1.0, 0.0)], - [(0.2, 0.4, 0.2), (0.2, 0.2, 0.4), (0.4, 0.2, 0.2)], - ) - .with_colors([0xFF0000FF, 0x00FF00FF, 0x0000FFFF]) - .with_fill_mode(re_types::components::FillMode::Solid), - ) - }); - harness.log_entity("txt/hello", |builder| { - builder.with_archetype( - RowId::new(), - TimePoint::STATIC, - &re_types::archetypes::TextDocument::new("Hello World!"), - ) - }); - - harness -} - -fn setup_single_view_blueprint(harness: &mut egui_kittest::Harness<'_, re_viewer::App>) { - harness.clear_current_blueprint(); - - let root_cid = harness.add_blueprint_container(egui_tiles::ContainerKind::Horizontal, None); - let tab_cid = harness.add_blueprint_container(egui_tiles::ContainerKind::Tabs, Some(root_cid)); - let vertical_cid = - harness.add_blueprint_container(egui_tiles::ContainerKind::Vertical, Some(root_cid)); - - let mut view_1 = - ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier()); - view_1.display_name = Some("3D view 1".into()); - let mut view_2 = - ViewBlueprint::new_with_root_wildcard(re_view_spatial::SpatialView3D::identifier()); - view_2.display_name = Some("3D view 2".into()); - - harness.setup_viewport_blueprint(move |_viewer_context, blueprint| { - let text_views = (0..20).map(|i| { - let mut view = ViewBlueprint::new_with_root_wildcard( - re_view_text_document::TextDocumentView::identifier(), - ); - view.display_name = Some(format!("Text view {i}")); - view - }); - blueprint.add_views(text_views, Some(tab_cid), None); - blueprint.add_views([view_1, view_2].into_iter(), Some(vertical_cid), None); - }); -} - -#[tokio::test(flavor = "multi_thread")] -pub async fn test_check_focus() { - let mut harness = make_test_harness(); - setup_single_view_blueprint(&mut harness); - - // Make the left panel wider - let centerline = harness.get_panel_position("Text view 0").left_center(); - let target_pos = centerline + egui::vec2(100.0, 0.0); - harness.drag_at(centerline); - harness.snapshot_app("check_focus_1"); - harness.hover_at(target_pos); - harness.snapshot_app("check_focus_2"); - harness.drop_at(target_pos); - harness.snapshot_app("check_focus_3"); - - // One of the boxes is at the center of the view - let pixel_of_a_box = harness.get_panel_position("3D view 1").center(); - - // Hover over the box - harness.hover_at(pixel_of_a_box); - - // Let the app render. This will run the picking logic which needs the GPU - // and lets the app find the hovered box. - harness.render().expect("Cannot render app"); - harness.run(); - harness.snapshot_app("check_focus_4"); - - // Double click on the box, see how it expands the view - harness.click_at(pixel_of_a_box); - harness.click_at(pixel_of_a_box); - harness.snapshot_app("check_focus_5"); - - // Scroll down to see the second view stays collapsed - harness.blueprint_tree().hover_label("3D view 1"); - harness.event(egui::Event::MouseWheel { - unit: egui::MouseWheelUnit::Page, - delta: egui::vec2(0.0, -1.0), - modifiers: egui::Modifiers::NONE, - }); - harness.snapshot_app("check_focus_6"); - - // Double click the entity on the streams tree and see all views expand - harness.streams_tree().hover_label("boxes3d"); - harness.streams_tree().click_label("boxes3d"); - harness.streams_tree().click_label("boxes3d"); - harness.snapshot_app("check_focus_7"); - - // Scroll down to see the second view is entirely expanded - harness.blueprint_tree().hover_label("3D view 1"); - harness.event(egui::Event::MouseWheel { - unit: egui::MouseWheelUnit::Page, - delta: egui::vec2(0.0, -1.0), - modifiers: egui::Modifiers::NONE, - }); - harness.snapshot_app("check_focus_8"); -} From 303353a25f31b22284ab5601fec55b42f2b10b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 31 Oct 2025 11:02:28 +0100 Subject: [PATCH 18/23] Remove checklist item --- tests/python/release_checklist/check_focus.py | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 tests/python/release_checklist/check_focus.py diff --git a/tests/python/release_checklist/check_focus.py b/tests/python/release_checklist/check_focus.py deleted file mode 100644 index b63f7b6bb50f..000000000000 --- a/tests/python/release_checklist/check_focus.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -import os -from argparse import Namespace -from uuid import uuid4 - -import rerun as rr -import rerun.blueprint as rrb - -README = """\ -# Focus checks - -- Double-click on a box in the first view - - check ONLY the corresponding view expands and scrolls - - check the streams view expands and scrolls -- Double-click on the leaf "boxes3d" entity in the streams view, check both views expand (manual scrolling might be needed). -""" - - -def log_readme() -> None: - rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True) - - -def blueprint() -> rrb.BlueprintLike: - return rrb.Horizontal( - rrb.Tabs(*[rrb.TextDocumentView(origin="readme") for _ in range(100)]), - rrb.Vertical(rrb.Spatial3DView(origin="/", name="SV1"), rrb.Spatial3DView(origin="/", name="SV2")), - column_shares=[1, 2], - ) - - -def log_some_views() -> None: - rr.set_time("frame_nr", sequence=0) - - for i in range(500): - rr.log(f"a_entity_{i}", rr.AnyValues(empty=0)) - - rr.log( - "/objects/boxes/boxes3d", - rr.Boxes3D(centers=[[0.0, 0.0, 0.0], [1.0, 1.5, 1.15], [3.0, 2.0, 1.0]], half_sizes=[0.5, 1.0, 0.5] * 3), - ) - - -def run(args: Namespace) -> None: - rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4()) - rr.send_blueprint(blueprint(), make_active=True, make_default=True) - - log_readme() - log_some_views() - - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Interactive release checklist") - rr.script_add_args(parser) - args = parser.parse_args() - run(args) From 8d59c58e8a0fe78bcd0474486ffb2b94fc616429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 31 Oct 2025 15:01:50 +0100 Subject: [PATCH 19/23] Updates --- crates/viewer/re_viewer/tests/app_kittest.rs | 1 + tests/rust/re_integration_test/tests/check_focus_test.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/re_viewer/tests/app_kittest.rs b/crates/viewer/re_viewer/tests/app_kittest.rs index 7c1c86b13954..ccf597460c1a 100644 --- a/crates/viewer/re_viewer/tests/app_kittest.rs +++ b/crates/viewer/re_viewer/tests/app_kittest.rs @@ -22,6 +22,7 @@ async fn settings_screen() { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { window_size: Some(egui::vec2(1024.0, 1080.0)), // Settings screen can be a bit tall + ..Default::default() }); harness.get_by_label("Menu").click(); harness.run_ok(); diff --git a/tests/rust/re_integration_test/tests/check_focus_test.rs b/tests/rust/re_integration_test/tests/check_focus_test.rs index 1faad0759362..a62e394f4768 100644 --- a/tests/rust/re_integration_test/tests/check_focus_test.rs +++ b/tests/rust/re_integration_test/tests/check_focus_test.rs @@ -1,4 +1,3 @@ -use egui_kittest::kittest::Queryable as _; use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; From 4118e94a44c9dd01a567347d28e0a7acdf3ae143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Fri, 31 Oct 2025 15:12:39 +0100 Subject: [PATCH 20/23] Updates --- crates/viewer/re_ui/src/testing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/viewer/re_ui/src/testing.rs b/crates/viewer/re_ui/src/testing.rs index 0d7662d3a561..b182725160d1 100644 --- a/crates/viewer/re_ui/src/testing.rs +++ b/crates/viewer/re_ui/src/testing.rs @@ -19,7 +19,7 @@ pub fn new_harness(option: TestOptions, size: impl Into) -> HarnessBuil TestOptions::Rendering3D => default_snapshot_options_for_3d(size), }; - let mut builder = egui_kittest::Harness::builder().wgpu().with_size(size).with_step_dt(0.01); + let mut builder = egui_kittest::Harness::builder().wgpu().with_size(size); // emilk did a mistake and made `with_options` a setter instead of a builder… // …we will fix that in the future, but for now, we have to live with it: From ec1626439bfee3d7fa1d4771088f97ecb89aaf7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Wed, 12 Nov 2025 16:01:48 +0100 Subject: [PATCH 21/23] Updates snapshots --- .../re_integration_test/tests/snapshots/check_focus_1.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_2.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_3.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_4.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_5.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_6.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_7.png | 4 ++-- .../re_integration_test/tests/snapshots/check_focus_8.png | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png index 8b6c8c6189a7..e105258c7385 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c7ebf478fbb3d3bbad4b8ae3bef380a83e8f66509936c6b47df5e3b6c0f89921 -size 138596 +oid sha256:e445984ac13e9a5b654fca48484045d4ae9ea66479af0cfb3ff01b38d88d1a94 +size 139591 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png index 7f1a325df9d5..ef14386f6fbb 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:827f91a2c19ac689202906cc0c8a28784d42c9e666a9042ef551c3feb37d3c53 -size 131890 +oid sha256:da729f699cf9b8a353f4b79abd0a2b5e2f8ed5a573958312322da441b31534b5 +size 133152 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png index 37d6e58998ee..0b2f569e4f0e 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:649b463176791af2e5b9b42d5a1345084fd992ce6e0edc1377e5db806d1b5ea4 -size 131867 +oid sha256:801a6b2a6b8b068d430e5bb28b2b7c7988186c986fa1ec39cb75a4a7aca9b4b5 +size 133131 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png index 94885178687f..1496a0acb1c8 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:65ad3d7c10dd4429460a618ea2792ffc2fda3427ef9397d40b5eb4cfafd7662a -size 133451 +oid sha256:aa8516ca6e75d56c5142a4e340ce04eb9ca1cc9cfc3b0fbe4874bbf65fd2902a +size 134713 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png index ad8af2a63bf9..ffd5a9bf3e1d 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c4c33dba7485c988c5ef2f67a821286372c65fdad9d84ca8d2401b10116e1cc4 -size 133665 +oid sha256:796bc1101de6e26640af9260cbe183c84bbeb5797b4fa352a269cf919d4f6b8b +size 134727 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png index 9f8d0a29eb1d..ab78eea3cbf2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0de7e99832c0804ae03924996bb38aba55866ccbc643a89852ec41d251c902c -size 133825 +oid sha256:14f1114cfe4a7c1096eecf6357275cd2d637813931168c4d318202e12222266d +size 134882 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png index d1b13462428d..54a461aa4e23 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_7.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:540fe483086c23092f03298dbeddd370f93de56c3acb9711f797270ba3df11a0 -size 131804 +oid sha256:304dbdde9f336edfe89c33c3eaaa6c9177339344b37ab8a49cdadbfe06548267 +size 132858 diff --git a/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png b/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png index 588a708e231b..f90509ac81d2 100644 --- a/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png +++ b/tests/rust/re_integration_test/tests/snapshots/check_focus_8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acf12cdad80ce296b0b4391ac775bb5b26d08c9d8009a09fc8639521c97d86ee -size 130664 +oid sha256:211778a0f21a3a74bb34619444d08f3d4136919d389683c481d404b1cc8f463b +size 131718 From 767da0911c01c7ffc056305144e9aa9e7886794b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Wed, 12 Nov 2025 16:02:19 +0100 Subject: [PATCH 22/23] Removed accidental commit --- tests/rust/re_integration_test/tests/snapshots/xtemp.png | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tests/rust/re_integration_test/tests/snapshots/xtemp.png diff --git a/tests/rust/re_integration_test/tests/snapshots/xtemp.png b/tests/rust/re_integration_test/tests/snapshots/xtemp.png deleted file mode 100644 index d553aacda2e0..000000000000 --- a/tests/rust/re_integration_test/tests/snapshots/xtemp.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:075c63cd162193b17fd70cac276297bf474d55d0658cdc47e3c432aa5dea0049 -size 162488 From eca6a4f5aa6c1379a1516b1a7e420c9c2bbbd9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Gyebn=C3=A1r?= Date: Wed, 12 Nov 2025 16:07:09 +0100 Subject: [PATCH 23/23] Updates comments --- .../tests/check_focus_test.rs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/rust/re_integration_test/tests/check_focus_test.rs b/tests/rust/re_integration_test/tests/check_focus_test.rs index a62e394f4768..9bcdd108fe0f 100644 --- a/tests/rust/re_integration_test/tests/check_focus_test.rs +++ b/tests/rust/re_integration_test/tests/check_focus_test.rs @@ -1,3 +1,10 @@ +//! This test logs a few boxes and performs the following focus checks: +//! +//! - Double-click on a box in the first view +//! - check ONLY the corresponding view expands and scrolls +//! - check the streams view expands and scrolls +//! - Double-click on the leaf "boxes3d" entity in the streams view, check both views expand (manual scrolling might be needed). + use re_integration_test::HarnessExt as _; use re_sdk::TimePoint; use re_sdk::log::RowId; @@ -9,13 +16,13 @@ use re_viewport_blueprint::ViewBlueprint; fn make_test_harness<'a>() -> egui_kittest::Harness<'a, re_viewer::App> { let mut harness = viewer_test_utils::viewer_harness(&HarnessOptions { window_size: Some(egui::vec2(1024.0, 768.0)), - max_steps: Some(200), // Allow animations to finish - step_dt: Some(1.0 / 60.0), // Allow double clicks to go through + max_steps: Some(200), // Allow animations to finish. + step_dt: Some(1.0 / 60.0), // Allow double clicks to go through. }); harness.init_recording(); harness.set_selection_panel_opened(false); - // Log some data + // Log some data. harness.log_entity("group/boxes3d", |builder| { builder.with_archetype( RowId::new(), @@ -72,7 +79,7 @@ pub async fn test_check_focus() { let mut harness = make_test_harness(); setup_single_view_blueprint(&mut harness); - // Make the left panel wider + // Make the left panel wider. let centerline = harness.get_panel_position("Text view 0").left_center(); let target_pos = centerline + egui::vec2(100.0, 0.0); harness.drag_at(centerline); @@ -82,10 +89,10 @@ pub async fn test_check_focus() { harness.drop_at(target_pos); harness.snapshot_app("check_focus_3"); - // One of the boxes is at the center of the view + // One of the boxes is at the center of the view. let pixel_of_a_box = harness.get_panel_position("3D view 1").center(); - // Hover over the box + // Hover over the box. harness.hover_at(pixel_of_a_box); // Let the app render. This will run the picking logic which needs the GPU @@ -94,12 +101,12 @@ pub async fn test_check_focus() { harness.run(); harness.snapshot_app("check_focus_4"); - // Double click on the box, see how it expands the view + // Double click on the box, see how it expands the view. harness.click_at(pixel_of_a_box); harness.click_at(pixel_of_a_box); harness.snapshot_app("check_focus_5"); - // Scroll down to see the second view stays collapsed + // Scroll down to see the second view stays collapsed. harness.blueprint_tree().hover_label("3D view 1"); harness.event(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Page, @@ -108,13 +115,13 @@ pub async fn test_check_focus() { }); harness.snapshot_app("check_focus_6"); - // Double click the entity on the streams tree and see all views expand + // Double click the entity on the streams tree and see all views expand. harness.streams_tree().hover_label("boxes3d"); harness.streams_tree().click_label("boxes3d"); harness.streams_tree().click_label("boxes3d"); harness.snapshot_app("check_focus_7"); - // Scroll down to see the second view is entirely expanded + // Scroll down to see the second view is entirely expanded. harness.blueprint_tree().hover_label("3D view 1"); harness.event(egui::Event::MouseWheel { unit: egui::MouseWheelUnit::Page,