egui_kittest 0.36.1

Testing library for egui based on kittest and AccessKit
Documentation
use egui::containers::menu::{MenuBar, MenuConfig, SubMenuButton};
use egui::{PopupCloseBehavior, Ui, include_image};
use egui_kittest::Harness;
use kittest::Queryable as _;

struct TestMenu {
    config: MenuConfig,
    checked: bool,
}

impl TestMenu {
    fn new(config: MenuConfig) -> Self {
        Self {
            config,
            checked: false,
        }
    }

    fn ui(&mut self, ui: &mut Ui) {
        ui.vertical(|ui| {
            MenuBar::new().config(self.config.clone()).ui(ui, |ui| {
                egui::Sides::new().show(
                    ui,
                    |ui| {
                        ui.menu_button("Menu A", |ui| {
                            _ = ui.button("Button in Menu A");
                            ui.menu_button("Submenu A", |ui| {
                                for i in 0..4 {
                                    _ = ui.button(format!("Button {i} in Submenu A"));
                                }
                            });
                            ui.menu_image_text_button(
                                include_image!("../../eframe/data/icon.png"),
                                "Submenu B with icon",
                                |ui| {
                                    _ = ui.button("Button in Submenu B");
                                },
                            );
                            SubMenuButton::new("Submenu C (CloseOnClickOutside)")
                                .config(
                                    MenuConfig::new()
                                        .close_behavior(PopupCloseBehavior::CloseOnClickOutside),
                                )
                                .ui(ui, |ui| {
                                    _ = ui.button("Button in Submenu C");
                                    ui.checkbox(&mut self.checked, "Checkbox in Submenu C");
                                    ui.menu_button("Submenu D", |ui| {
                                        if ui
                                            .button("Button in Submenu D (close on click)")
                                            .clicked()
                                        {
                                            ui.close();
                                        }
                                    });
                                });
                        });
                        ui.menu_image_text_button(
                            include_image!("../../eframe/data/icon.png"),
                            "Menu B with icon",
                            |ui| {
                                _ = ui.button("Button in Menu B");
                            },
                        );
                        _ = ui.button("Menu Button");
                        ui.menu_button("Menu C", |ui| {
                            _ = ui.button("Button in Menu C");
                        });
                    },
                    |ui| {
                        ui.label("Some other label");
                    },
                );
            });
        });
    }

    fn into_harness(self) -> Harness<'static, Self> {
        Harness::builder()
            .with_size(egui::Vec2::new(500.0, 300.0))
            .build_ui_state(
                |ui, menu| {
                    egui_extras::install_image_loaders(ui.ctx());
                    menu.ui(ui);
                },
                self,
            )
    }
}

#[test]
fn menu_close_on_click_outside() {
    // We're intentionally setting CloseOnClick here so we can test if a submenu can override the
    // close behavior. (Note how Submenu C has CloseOnClickOutside set)
    let mut harness =
        TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
            .into_harness();

    harness.get_by_label("Menu A").click();
    harness.run();

    harness
        .get_by_label_contains("Submenu C (CloseOnClickOutside)")
        .hover();
    harness.run();

    // We should be able to check the checkbox without closing the menu
    // Click a couple of times, just in case
    for expect_checked in [true, false, true, false] {
        harness.get_by_label("Checkbox in Submenu C").click();
        harness.run();
        assert_eq!(expect_checked, harness.state().checked);
    }

    // Hovering outside should not close the menu
    harness.get_by_label("Some other label").hover();
    harness.run();
    assert!(harness.query_by_label("Checkbox in Submenu C").is_some());

    // Clicking outside should close the menu
    harness.get_by_label("Some other label").click();
    harness.run();
    assert!(harness.query_by_label("Checkbox in Submenu C").is_none());
}

#[test]
fn menu_close_on_click() {
    let mut harness =
        TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
            .into_harness();

    harness.get_by_label("Menu A").click();
    harness.run();

    harness.get_by_label_contains("Submenu B with icon").hover();
    harness.run();

    // Clicking the button should close the menu (even if ui.close() is not called by the button)
    harness.get_by_label("Button in Submenu B").click();
    harness.run();
    assert!(harness.query_by_label("Button in Submenu B").is_none());
}

#[test]
fn clicking_submenu_button_should_never_close_menu() {
    // We test for this since otherwise the menu wouldn't work on touch devices
    // The other tests use .hover to open submenus, but this test explicitly uses .click
    let mut harness =
        TestMenu::new(MenuConfig::new().close_behavior(PopupCloseBehavior::CloseOnClick))
            .into_harness();

    harness.get_by_label("Menu A").click();
    harness.run();

    // Clicking the submenu button should not close the menu
    harness.get_by_label_contains("Submenu B with icon").click();
    harness.run();

    harness.get_by_label("Button in Submenu B").click();
    harness.run();
    assert!(harness.query_by_label("Button in Submenu B").is_none());
}

#[cfg(feature = "snapshot")]
#[test]
fn menu_snapshots() {
    let mut harness = TestMenu::new(MenuConfig::new()).into_harness();

    let mut results = egui_kittest::SnapshotResults::new();

    harness.get_by_label("Menu A").hover();
    harness.run();
    results.add(harness.try_snapshot("menu/closed_hovered"));

    harness.get_by_label("Menu A").click();
    harness.run();
    results.add(harness.try_snapshot("menu/opened"));

    harness
        .get_by_label_contains("Submenu C (CloseOnClickOutside)")
        .hover();
    harness.run();
    results.add(harness.try_snapshot("menu/submenu"));

    harness.get_by_label_contains("Submenu D").hover();
    harness.run();
    results.add(harness.try_snapshot("menu/subsubmenu"));
}