Model UI overlay lifetime in ui core

This commit is contained in:
2026-06-06 09:48:00 +02:00
parent 4071919124
commit 3101e65dd3
9 changed files with 483 additions and 7 deletions

View File

@@ -310,6 +310,16 @@ add_test(NAME pp_ui_core_node_lifetime_tests COMMAND pp_ui_core_node_lifetime_te
set_tests_properties(pp_ui_core_node_lifetime_tests PROPERTIES
LABELS "ui;desktop-fast")
add_executable(pp_ui_core_overlay_lifetime_tests
ui_core/overlay_lifetime_tests.cpp)
target_link_libraries(pp_ui_core_overlay_lifetime_tests PRIVATE
pp_ui_core
pp_test_harness)
add_test(NAME pp_ui_core_overlay_lifetime_tests COMMAND pp_ui_core_overlay_lifetime_tests)
set_tests_properties(pp_ui_core_overlay_lifetime_tests PROPERTIES
LABELS "ui;desktop-fast")
add_executable(pp_app_core_about_menu_tests
app_core/about_menu_tests.cpp)
target_link_libraries(pp_app_core_about_menu_tests PRIVATE

View File

@@ -0,0 +1,227 @@
#include "ui_core/overlay_lifetime.h"
#include "test_harness.h"
using pp::foundation::StatusCode;
using pp::ui::NodeLifetimeTree;
using pp::ui::UiCaptureKind;
using pp::ui::UiOverlayLifetime;
namespace {
void opens_root_and_child_popups_with_capture_restore(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto popup = overlays.open_popup();
PP_EXPECT(h, popup);
if (!popup) {
return;
}
const auto child = overlays.open_child_popup(popup.value());
PP_EXPECT(h, child);
if (!child) {
return;
}
PP_EXPECT(h, overlays.overlay_count() == 2U);
const auto captured_child = tree.captured_node(UiCaptureKind::pointer);
PP_EXPECT(h, captured_child);
if (captured_child) {
PP_EXPECT(h, captured_child.value() == child.value());
}
PP_EXPECT(h, overlays.close(child.value()).ok());
PP_EXPECT(h, overlays.overlay_count() == 1U);
PP_EXPECT(h, !tree.contains(child.value()));
PP_EXPECT(h, tree.contains(popup.value()));
const auto captured_parent = tree.captured_node(UiCaptureKind::pointer);
PP_EXPECT(h, captured_parent);
if (captured_parent) {
PP_EXPECT(h, captured_parent.value() == popup.value());
}
}
void closing_parent_popup_closes_child_branch(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto popup = overlays.open_popup();
PP_EXPECT(h, popup);
if (!popup) {
return;
}
const auto child = overlays.open_child_popup(popup.value());
PP_EXPECT(h, child);
if (!child) {
return;
}
PP_EXPECT(h, overlays.close(popup.value()).ok());
PP_EXPECT(h, overlays.overlay_count() == 0U);
PP_EXPECT(h, !tree.contains(popup.value()));
PP_EXPECT(h, !tree.contains(child.value()));
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
}
void modal_dialog_captures_pointer_and_keyboard(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto popup = overlays.open_popup();
PP_EXPECT(h, popup);
if (!popup) {
return;
}
const auto dialog = overlays.open_dialog(true);
PP_EXPECT(h, dialog);
if (!dialog) {
return;
}
const auto pointer = tree.captured_node(UiCaptureKind::pointer);
const auto keyboard = tree.captured_node(UiCaptureKind::keyboard);
PP_EXPECT(h, pointer);
PP_EXPECT(h, keyboard);
if (pointer) {
PP_EXPECT(h, pointer.value() == dialog.value());
}
if (keyboard) {
PP_EXPECT(h, keyboard.value() == dialog.value());
}
PP_EXPECT(h, overlays.close(dialog.value()).ok());
const auto restored_pointer = tree.captured_node(UiCaptureKind::pointer);
PP_EXPECT(h, restored_pointer);
if (restored_pointer) {
PP_EXPECT(h, restored_pointer.value() == popup.value());
}
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
}
void modeless_dialog_does_not_steal_capture(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto popup = overlays.open_popup();
PP_EXPECT(h, popup);
if (!popup) {
return;
}
const auto dialog = overlays.open_dialog(false);
PP_EXPECT(h, dialog);
if (!dialog) {
return;
}
const auto pointer = tree.captured_node(UiCaptureKind::pointer);
PP_EXPECT(h, pointer);
if (pointer) {
PP_EXPECT(h, pointer.value() == popup.value());
}
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
PP_EXPECT(h, overlays.top_overlay());
if (const auto top = overlays.top_overlay()) {
PP_EXPECT(h, top.value() == dialog.value());
}
}
void rejects_untracked_or_dead_overlay_closes(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
const auto raw_child = tree.create_child(root.value());
PP_EXPECT(h, raw_child);
if (!raw_child) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto close_raw = overlays.close(raw_child.value());
const auto child_popup = overlays.open_child_popup(raw_child.value());
PP_EXPECT(h, !close_raw.ok());
PP_EXPECT(h, close_raw.code == StatusCode::invalid_argument);
PP_EXPECT(h, !child_popup.ok());
PP_EXPECT(h, child_popup.status().code == StatusCode::invalid_argument);
}
void clear_for_layout_reload_invalidates_overlays(pp::tests::Harness& h)
{
NodeLifetimeTree tree;
const auto root = tree.create_root();
PP_EXPECT(h, root);
if (!root) {
return;
}
UiOverlayLifetime overlays(tree, root.value());
const auto popup = overlays.open_popup();
PP_EXPECT(h, popup);
if (!popup) {
return;
}
const auto dialog = overlays.open_dialog(true);
PP_EXPECT(h, dialog);
if (!dialog) {
return;
}
overlays.clear_for_layout_reload();
PP_EXPECT(h, overlays.overlay_count() == 0U);
PP_EXPECT(h, !overlays.top_overlay().ok());
PP_EXPECT(h, !tree.contains(root.value()));
PP_EXPECT(h, !tree.contains(popup.value()));
PP_EXPECT(h, !tree.contains(dialog.value()));
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::pointer).ok());
PP_EXPECT(h, !tree.captured_node(UiCaptureKind::keyboard).ok());
}
}
int main()
{
pp::tests::Harness harness;
harness.run(
"opens_root_and_child_popups_with_capture_restore",
opens_root_and_child_popups_with_capture_restore);
harness.run("closing_parent_popup_closes_child_branch", closing_parent_popup_closes_child_branch);
harness.run("modal_dialog_captures_pointer_and_keyboard", modal_dialog_captures_pointer_and_keyboard);
harness.run("modeless_dialog_does_not_steal_capture", modeless_dialog_does_not_steal_capture);
harness.run("rejects_untracked_or_dead_overlay_closes", rejects_untracked_or_dead_overlay_closes);
harness.run("clear_for_layout_reload_invalidates_overlays", clear_for_layout_reload_invalidates_overlays);
return harness.finish();
}