use egui::{Align, Layout, Popup}; use egui_kittest::Harness; use kittest::Queryable as _; #[test] fn reopened_popup_resizes_for_wider_items() { const POPUP_BUTTON: &str = "Dynamic popup"; const SHORT_ITEM: &str = "Short item"; const WIDE_ITEM: &str = "Newly added item with a much wider label"; #[derive(Default)] struct State { open: bool, show_wide_item: bool, } let mut harness = Harness::builder() .with_size(egui::Vec2::new(500.0, 300.0)) .build_ui_state( |ui, state| { let response = ui.button(POPUP_BUTTON); if response.clicked() { state.open = !state.open; } Popup::from_response(&response) .open(state.open) .layout(Layout::top_down_justified(Align::Min)) .show(|ui| { _ = ui.selectable_label(false, SHORT_ITEM); _ = ui.selectable_label(false, "Another short item"); if state.show_wide_item { _ = ui.selectable_label(false, WIDE_ITEM); } }); }, State::default(), ); harness.get_by_label(POPUP_BUTTON).click(); harness.run(); let initial_row_size = harness.get_by_label(SHORT_ITEM).rect().size(); harness.get_by_label(POPUP_BUTTON).click(); harness.run(); assert!(harness.query_by_label(SHORT_ITEM).is_none()); harness.state_mut().show_wide_item = true; harness.run(); harness.get_by_label(POPUP_BUTTON).click(); harness.run(); let reopened_row_size = harness.get_by_label(SHORT_ITEM).rect().size(); let wide_row_size = harness.get_by_label(WIDE_ITEM).rect().size(); assert!( reopened_row_size.x > initial_row_size.x, "reopened row width ({}) did not grow beyond its initial width ({})", reopened_row_size.x, initial_row_size.x ); assert!( wide_row_size.y <= initial_row_size.y + 0.5, "new row height ({}) exceeds the single-line row height ({})", wide_row_size.y, initial_row_size.y ); } #[test] fn open_popup_resizes_after_explicit_sizing_pass() { const POPUP_BUTTON: &str = "Growing popup"; const MAX_HEIGHT: f32 = 100.0; struct State { item_count: usize, needs_sizing_pass: bool, popup_height: f32, viewport_height: f32, content_height: f32, } let mut harness = Harness::builder() .with_size(egui::Vec2::new(500.0, 300.0)) .build_ui_state( |ui, state| { let response = ui.button(POPUP_BUTTON); let needs_sizing_pass = core::mem::take(&mut state.needs_sizing_pass); let item_count = state.item_count; if let Some(popup) = Popup::from_response(&response) .sizing_pass(needs_sizing_pass) .show(|ui| { egui::ScrollArea::vertical() .max_height(MAX_HEIGHT) .show(ui, |ui| { for index in 0..item_count { ui.label(format!("Item {index}")); } }) }) { state.popup_height = popup.response.rect.height(); state.viewport_height = popup.inner.inner_rect.height(); state.content_height = popup.inner.content_size.y; } }, State { item_count: 2, needs_sizing_pass: false, popup_height: 0.0, viewport_height: 0.0, content_height: 0.0, }, ); harness.run(); let initial_popup_height = harness.state().popup_height; harness.state_mut().item_count = 20; harness.run(); let stale_viewport_height = harness.state().viewport_height; assert!( stale_viewport_height < MAX_HEIGHT, "viewport unexpectedly reached its maximum without a sizing pass" ); harness.state_mut().needs_sizing_pass = true; harness.run(); assert!( harness.state().popup_height > initial_popup_height, "popup did not grow after an explicit sizing pass" ); assert!( harness.state().viewport_height > stale_viewport_height, "scroll viewport did not grow after an explicit sizing pass" ); assert!( (harness.state().viewport_height - MAX_HEIGHT).abs() <= 0.5, "scroll viewport did not stop at its maximum height" ); assert!( harness.state().content_height > harness.state().viewport_height, "popup contents did not remain scrollable at the maximum height" ); } #[test] fn test_interactive_tooltip() { struct State { link_clicked: bool, } let mut harness = egui_kittest::Harness::new_ui_state( |ui, state| { ui.label("I have a tooltip").on_hover_ui(|ui| { if ui.link("link").clicked() { state.link_clicked = true; } }); }, State { link_clicked: false, }, ); harness.get_by_label_contains("tooltip").hover(); harness.run(); harness.get_by_label("link").hover(); harness.run(); harness.get_by_label("link").click(); harness.run(); assert!(harness.state().link_clicked); } #[cfg(all(feature = "snapshot", feature = "wgpu"))] #[test] fn scroll_area_extends_into_popup_margin() { fn scroll_area(ui: &mut egui::Ui, extend: bool) { egui::ScrollArea::vertical() .max_height(110.0) .auto_shrink([false, true]) .extend_into_parent_margin(extend) .show(ui, |ui| { for i in 0..20 { _ = ui.selectable_label(i == 2, format!("Item {i}")); } }); } fn popup_with_scroll_area(ui: &mut egui::Ui, label: &str, extend: bool, nested: bool) { let response = ui.button(label); Popup::from_response(&response).open(true).show(|ui| { ui.set_width(120.0); if nested { // The scroll area is not a direct child of the popup frame here, // so we must find the frame by walking up the `UiStack`: ui.vertical(|ui| { ui.label("Header"); scroll_area(ui, extend); }); } else { ui.label("Header"); scroll_area(ui, extend); } }); } let mut harness = Harness::builder() .with_size(egui::Vec2::new(600.0, 220.0)) .with_pixels_per_point(2.0) .build_ui(|ui| { // Solid scroll bars make it easy to see where the scroll area ends: ui.ctx() .all_styles_mut(|style| style.spacing.scroll = egui::style::ScrollStyle::solid()); ui.horizontal(|ui| { popup_with_scroll_area(ui, "normal", false, false); ui.add_space(140.0); popup_with_scroll_area(ui, "full bleed", true, false); ui.add_space(140.0); popup_with_scroll_area(ui, "full bleed, nested", true, true); }); }); harness.run(); harness.snapshot("popup_full_bleed_scroll_area"); }