From 6427f218e79a1dd1f527311f747c3c0dedc64090 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 13:22:16 +0200 Subject: [PATCH] Add history command service boundary --- docs/modernization/debt.md | 4 +- docs/modernization/roadmap.md | 12 +-- src/app_core/history_ui.h | 51 +++++++++++++ src/app_core/main_toolbar.h | 16 ++-- src/app_layout.cpp | 38 ++++++++-- tests/app_core/history_ui_tests.cpp | 101 ++++++++++++++++++++++++++ tests/app_core/main_toolbar_tests.cpp | 28 ++++++- 7 files changed, 228 insertions(+), 22 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 5b58cc1..bc6e50d 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -43,7 +43,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0023 | Open | Modernization | Brush/color/preset UI planning now consumes pure `pp_app_core` through `App::init_sidebar`, restored/docked floating-panel callbacks, and `pano_cli plan-brush-operation`, but live execution still mutates legacy `Brush`, calls legacy brush texture loading, and refreshes legacy quick/stroke/color widgets directly | Preserve existing brush UI behavior while brush commands move toward a brush/app command boundary and asset-managed texture selection | `pp_app_core_brush_ui_tests`; `pano_cli plan-brush-operation --kind color --r 0.25 --g 0.5 --b 0.75 --a 1`; `pano_cli plan-brush-operation --kind pattern --path data/patterns/noise.png --thumb data/patterns/thumbs/noise.png`; `ctest --preset desktop-fast --build-config Debug` | Brush color/texture/preset execution is owned by a brush/app command boundary with legacy `Brush`/UI nodes acting only as adapters or removed entirely | | DEBT-0024 | Open | Modernization | Grid/heightmap/lightmap UI planning now consumes pure `pp_app_core` through `NodePanelGrid` and `pano_cli plan-grid-operation`, but live execution still performs legacy image loading, OpenGL texture updates, nanort lightmap baking, progress UI, and `Canvas::draw_objects` commit directly | Preserve grid/lightmap behavior while moving renderable grid commands toward app/renderer/document boundaries | `pp_app_core_grid_ui_tests`; `pano_cli plan-grid-operation --kind render --float32 --texture-resolution 1024 --samples 32`; `ctest --preset desktop-fast --build-config Debug` | Grid heightmap/lightmap execution is owned by app/renderer/document services with `NodePanelGrid` acting only as UI adapter | | DEBT-0025 | Open | Modernization | Quick brush/color slot and mini-state planning now consumes pure `pp_app_core` through `NodePanelQuick` and `pano_cli plan-quick-operation`, but live execution still mutates legacy quick UI widgets, `Brush` previews, color picker popup state, and preset popup state directly | Preserve quick-panel behavior while quick brush/color commands move toward a brush/app command boundary with safer automation coverage | `pp_app_core_quick_ui_tests`; `pano_cli plan-quick-operation --kind brush --current-index 0 --slot-index 2`; `pano_cli plan-quick-operation --kind restore --brush-index 2 --color-index 1 --fire-event`; `ctest --preset desktop-fast --build-config Debug` | Quick-panel selection, popup, restore, reset, brush preview, and color execution are owned by app/brush/UI services with `NodePanelQuick` acting only as UI adapter | -| DEBT-0026 | Open | Modernization | Toolbar and canvas history command planning now consumes pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, and `pano_cli plan-history-operation`, but live execution still mutates legacy `ActionManager` stacks and `Canvas::I` unsaved state directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by document/app history services with toolbar and canvas input acting only as adapters | +| DEBT-0026 | Open | Modernization | Toolbar history command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `NodeCanvas`, `pano_cli plan-history-operation`, and the `HistoryUiServices` boundary, but the live adapter still mutates legacy `ActionManager` stacks directly | Preserve undo/redo/clear behavior while moving action history toward document/app command services | `pp_app_core_history_ui_tests`; `pano_cli plan-history-operation --kind undo --undo-count 2`; `pano_cli plan-history-operation --kind clear --undo-count 2 --redo-count 1 --memory-bytes 4096`; `ctest --preset desktop-fast --build-config Debug` | Undo/redo/clear execution is owned by injected document/app history services with no legacy `ActionManager` adapter | | DEBT-0027 | Open | Modernization | Canvas draw-tool toolbar command, canvas input mode switching, and active-state planning now consume pure `pp_app_core` through `App::init_toolbar_draw`, `App::update`, `NodeCanvas`, `pano_cli plan-canvas-tool`, and `pano_cli plan-canvas-tool-state`, but live execution/state storage still mutates or reads legacy `Canvas` mode state, pen picking state, touch-lock state, and transform copy/cut action objects directly | Preserve current toolbar, stylus eraser, and keyboard draw/erase behavior while canvas input/tools move toward an app/document command boundary | `pp_app_core_canvas_tool_ui_tests`; `pano_cli plan-canvas-tool --kind copy`; `pano_cli plan-canvas-tool-state --mode draw --picking --touch-lock`; `ctest --preset desktop-fast --build-config Debug` | Canvas tool selection, toolbar state refresh, picking, touch lock, stylus eraser/key mode switching, and transform action execution are owned by app/document/canvas services with toolbar/canvas callbacks acting only as adapters | | DEBT-0028 | Open | Modernization | Canvas clear command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-canvas-clear`, and the `DocumentCanvasClearServices` boundary, but the live adapter still calls legacy `Canvas::clear`, which records `ActionLayerClear`, clears the current layer/frame, and marks legacy `Canvas::I` unsaved | Preserve clear-current-layer behavior while canvas/document commands move toward document/app command services | `pp_app_core_document_canvas_tests`; `pano_cli plan-canvas-clear --r 0 --g 0.1 --b 0.2 --a 0.3`; `pano_cli plan-canvas-clear --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Canvas clear execution, undo recording, dirty-state updates, and clear color handling are owned by injected document/app services with no legacy canvas-clear adapter | | DEBT-0029 | Open | Modernization | Image import route planning and execution dispatch now consume pure `pp_app_core` through the File menu, `pano_cli plan-image-import`, and the `DocumentImageImportServices` boundary, but the live adapter still loads images with legacy `Image`, calls legacy `Canvas::import_equirectangular`, or configures legacy import transform mode directly | Preserve current File > Import behavior while image import moves toward document/app/asset command services | `pp_app_core_document_import_tests`; `pano_cli plan-image-import --width 4096 --height 2048`; `pano_cli plan-image-import --width 1024 --height 1024`; `ctest --preset desktop-fast --build-config Debug` | Image loading, equirectangular import, transform-placement import, and failure reporting are owned by injected document/app/asset services with File-menu callbacks acting only as adapters and no legacy image-import adapter | @@ -52,7 +52,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0032 | Open | Modernization | Layer menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_layer`, `pano_cli plan-layer-menu`, and the `DocumentLayerMenuServices` boundary, but the live adapter still calls legacy `Canvas::clear`, `App::dialog_layer_rename`, `NodePanelLayer::merge`, and reads `Canvas::I` animation/layer state directly | Preserve existing Layer menu behavior while layer commands move toward document/app services | `pp_app_core_document_layer_tests`; `pano_cli plan-layer-menu --command merge --current-index 2 --lower-name Paint`; `pano_cli plan-layer-menu --command rename --no-current-layer`; `ctest --preset desktop-fast --build-config Debug` | Layer clear, rename, merge-down execution, animation gating, and selected-layer state are owned by injected document/app services with Layer-menu callbacks acting only as UI adapters and no legacy Layer menu adapter | | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter | | DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, but the live adapter still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter | -| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, but the live adapter still opens legacy open/save/settings/message-box dialogs, mutates legacy `ActionManager` history, and clears the legacy `Canvas` directly | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | +| DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 6d01cc5..26fa0b3 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -538,15 +538,16 @@ pick/load/reload/clear, lightmap render capability/limit checks, and heightmap commit used by the live grid panel before legacy image loading, OpenGL texture updates, nanort lightmap baking, and `Canvas::draw_objects` execution continue. `pano_cli plan-history-operation` exposes app-core planning for undo, redo, and -clear-history availability used by toolbar buttons and canvas shortcuts before -legacy `ActionManager` stack execution continues. +clear-history availability used by toolbar buttons and canvas shortcuts; live +toolbar execution now dispatches through `HistoryUiServices` before the legacy +`ActionManager` stack adapter continues. `pano_cli plan-main-toolbar` exposes app-core planning for the live main toolbar/status-bar shell, including open/save dialogs, undo/redo availability, clear-history availability, clear-canvas no-canvas blocking, message-box creation, and settings dialog routing. `pp_app_core` now also owns a `MainToolbarServices` executor boundary, so `App::init_toolbar_main` dispatches -through a legacy adapter before legacy dialogs, `ActionManager`, and `Canvas` -execution continue. +through a legacy adapter before legacy dialogs, history/canvas adapters, and +settings UI execution continue. `pano_cli plan-quick-operation` exposes app-core planning for quick brush/color slot selection versus popup opening, plus quick mini-state restore/reset validation used by the live quick panel before legacy `Brush`, color picker, @@ -1272,7 +1273,8 @@ Results: routing as JSON automation. - `pp_app_core_history_ui_tests` passed, covering undo/redo availability, no-op history commands, clear-history stack/memory state, memory-only clear, - and negative metric rejection. + negative metric rejection, service dispatch order, empty-history no-op + execution, and invalid execution metric rejection. - `pano_cli_plan_history_operation_undo_smoke`, `pano_cli_plan_history_operation_redo_empty_smoke`, `pano_cli_plan_history_operation_clear_smoke`, and diff --git a/src/app_core/history_ui.h b/src/app_core/history_ui.h index de2a950..0730e25 100644 --- a/src/app_core/history_ui.h +++ b/src/app_core/history_ui.h @@ -23,6 +23,15 @@ struct HistoryUiPlan { bool no_op = false; }; +class HistoryUiServices { +public: + virtual ~HistoryUiServices() = default; + + virtual void invoke_undo() = 0; + virtual void invoke_redo() = 0; + virtual void clear_history() = 0; +}; + [[nodiscard]] inline pp::foundation::Status validate_history_metric(int value, const char* message) noexcept { if (value < 0) { @@ -107,4 +116,46 @@ struct HistoryUiPlan { return pp::foundation::Result::success(plan); } +[[nodiscard]] inline pp::foundation::Status execute_history_ui_plan( + const HistoryUiPlan& plan, + HistoryUiServices& services) +{ + const auto undo_status = validate_history_metric(plan.undo_count, "undo action count must not be negative"); + if (!undo_status.ok()) { + return undo_status; + } + const auto redo_status = validate_history_metric(plan.redo_count, "redo action count must not be negative"); + if (!redo_status.ok()) { + return redo_status; + } + const auto memory_status = validate_history_metric(plan.memory_bytes, "history memory bytes must not be negative"); + if (!memory_status.ok()) { + return memory_status; + } + + if (plan.no_op) { + return pp::foundation::Status::success(); + } + + switch (plan.operation) { + case HistoryUiOperation::undo: + if (plan.invokes_undo) { + services.invoke_undo(); + } + return pp::foundation::Status::success(); + case HistoryUiOperation::redo: + if (plan.invokes_redo) { + services.invoke_redo(); + } + return pp::foundation::Status::success(); + case HistoryUiOperation::clear: + if (plan.clears_history) { + services.clear_history(); + } + return pp::foundation::Status::success(); + } + + return pp::foundation::Status::invalid_argument("unknown history operation"); +} + } // namespace pp::app diff --git a/src/app_core/main_toolbar.h b/src/app_core/main_toolbar.h index e7ad3d5..15d0124 100644 --- a/src/app_core/main_toolbar.h +++ b/src/app_core/main_toolbar.h @@ -41,6 +41,7 @@ struct MainToolbarPlan { bool records_undo = false; bool marks_unsaved = false; bool no_op = false; + HistoryUiPlan history; DocumentCanvasClearPlan canvas_clear; }; @@ -50,9 +51,9 @@ public: virtual void show_open_dialog() = 0; virtual void show_save_dialog() = 0; - virtual void invoke_undo() = 0; - virtual void invoke_redo() = 0; - virtual void clear_history() = 0; + virtual void invoke_undo(const HistoryUiPlan& plan) = 0; + virtual void invoke_redo(const HistoryUiPlan& plan) = 0; + virtual void clear_history(const HistoryUiPlan& plan) = 0; virtual void clear_canvas(const DocumentCanvasClearPlan& plan) = 0; virtual void show_message_box() = 0; virtual void show_settings_dialog() = 0; @@ -92,6 +93,7 @@ public: plan.updates_memory_label = history.value().updates_memory_label; plan.updates_title = history.value().updates_title; plan.no_op = history.value().no_op; + plan.history = history.value(); return pp::foundation::Result::success(plan); } @@ -108,6 +110,7 @@ public: plan.updates_memory_label = history.value().updates_memory_label; plan.updates_title = history.value().updates_title; plan.no_op = history.value().no_op; + plan.history = history.value(); return pp::foundation::Result::success(plan); } @@ -123,6 +126,7 @@ public: plan.label = history.value().clears_history ? "Clear History" : "Clear History (Empty)"; plan.updates_memory_label = history.value().updates_memory_label; plan.no_op = history.value().no_op; + plan.history = history.value(); return pp::foundation::Result::success(plan); } @@ -171,13 +175,13 @@ public: services.show_save_dialog(); return pp::foundation::Status::success(); case MainToolbarAction::invoke_undo: - services.invoke_undo(); + services.invoke_undo(plan.history); return pp::foundation::Status::success(); case MainToolbarAction::invoke_redo: - services.invoke_redo(); + services.invoke_redo(plan.history); return pp::foundation::Status::success(); case MainToolbarAction::clear_history: - services.clear_history(); + services.clear_history(plan.history); return pp::foundation::Status::success(); case MainToolbarAction::clear_canvas: services.clear_canvas(plan.canvas_clear); diff --git a/src/app_layout.cpp b/src/app_layout.cpp index c6e95a8..c40e732 100644 --- a/src/app_layout.cpp +++ b/src/app_layout.cpp @@ -317,19 +317,19 @@ public: app_.dialog_save(); } - void invoke_undo() override + void invoke_undo(const pp::app::HistoryUiPlan& plan) override { - ActionManager::undo(); + execute_history_plan(plan); } - void invoke_redo() override + void invoke_redo(const pp::app::HistoryUiPlan& plan) override { - ActionManager::redo(); + execute_history_plan(plan); } - void clear_history() override + void clear_history(const pp::app::HistoryUiPlan& plan) override { - ActionManager::clear(); + execute_history_plan(plan); } void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override @@ -376,6 +376,32 @@ public: } private: + class LegacyHistoryUiServices final : public pp::app::HistoryUiServices { + public: + void invoke_undo() override + { + ActionManager::undo(); + } + + void invoke_redo() override + { + ActionManager::redo(); + } + + void clear_history() override + { + ActionManager::clear(); + } + }; + + void execute_history_plan(const pp::app::HistoryUiPlan& plan) + { + LegacyHistoryUiServices services; + const auto status = pp::app::execute_history_ui_plan(plan, services); + if (!status.ok()) + LOG("History action failed: %s", status.message); + } + App& app_; }; diff --git a/tests/app_core/history_ui_tests.cpp b/tests/app_core/history_ui_tests.cpp index df7e116..a4993f3 100644 --- a/tests/app_core/history_ui_tests.cpp +++ b/tests/app_core/history_ui_tests.cpp @@ -1,8 +1,36 @@ #include "app_core/history_ui.h" #include "test_harness.h" +#include + namespace { +class FakeHistoryUiServices final : public pp::app::HistoryUiServices { +public: + void invoke_undo() override + { + undo_calls += 1; + call_order += "undo;"; + } + + void invoke_redo() override + { + redo_calls += 1; + call_order += "redo;"; + } + + void clear_history() override + { + clear_calls += 1; + call_order += "clear;"; + } + + int undo_calls = 0; + int redo_calls = 0; + int clear_calls = 0; + std::string call_order; +}; + void undo_and_redo_plan_availability(pp::tests::Harness& harness) { const auto undo = pp::app::plan_history_undo(2); @@ -78,6 +106,76 @@ void rejects_negative_metrics(pp::tests::Harness& harness) PP_EXPECT(harness, !pp::app::plan_history_clear(0, 0, -1)); } +void executor_dispatches_undo_redo_and_clear(pp::tests::Harness& harness) +{ + FakeHistoryUiServices services; + + const auto undo = pp::app::plan_history_undo(2); + PP_EXPECT(harness, undo); + if (undo) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok()); + } + + const auto redo = pp::app::plan_history_redo(1); + PP_EXPECT(harness, redo); + if (redo) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok()); + } + + const auto clear = pp::app::plan_history_clear(2, 1, 4096); + PP_EXPECT(harness, clear); + if (clear) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok()); + } + + PP_EXPECT(harness, services.undo_calls == 1); + PP_EXPECT(harness, services.redo_calls == 1); + PP_EXPECT(harness, services.clear_calls == 1); + PP_EXPECT(harness, services.call_order == "undo;redo;clear;"); +} + +void executor_preserves_empty_history_noops(pp::tests::Harness& harness) +{ + FakeHistoryUiServices services; + + const auto undo = pp::app::plan_history_undo(0); + PP_EXPECT(harness, undo); + if (undo) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(undo.value(), services).ok()); + } + + const auto redo = pp::app::plan_history_redo(0); + PP_EXPECT(harness, redo); + if (redo) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(redo.value(), services).ok()); + } + + const auto clear = pp::app::plan_history_clear(0, 0, 0); + PP_EXPECT(harness, clear); + if (clear) { + PP_EXPECT(harness, pp::app::execute_history_ui_plan(clear.value(), services).ok()); + } + + PP_EXPECT(harness, services.undo_calls == 0); + PP_EXPECT(harness, services.redo_calls == 0); + PP_EXPECT(harness, services.clear_calls == 0); + PP_EXPECT(harness, services.call_order.empty()); +} + +void executor_rejects_invalid_metrics(pp::tests::Harness& harness) +{ + FakeHistoryUiServices services; + pp::app::HistoryUiPlan plan; + plan.operation = pp::app::HistoryUiOperation::undo; + plan.undo_count = -1; + plan.invokes_undo = true; + + const auto status = pp::app::execute_history_ui_plan(plan, services); + PP_EXPECT(harness, !status.ok()); + PP_EXPECT(harness, status.code == pp::foundation::StatusCode::out_of_range); + PP_EXPECT(harness, services.undo_calls == 0); +} + } // namespace int main() @@ -86,5 +184,8 @@ int main() harness.run("undo and redo plan availability", undo_and_redo_plan_availability); harness.run("clear plan tracks stacks and memory", clear_plan_tracks_stacks_and_memory); harness.run("rejects negative metrics", rejects_negative_metrics); + harness.run("executor dispatches undo redo and clear", executor_dispatches_undo_redo_and_clear); + harness.run("executor preserves empty history noops", executor_preserves_empty_history_noops); + harness.run("executor rejects invalid metrics", executor_rejects_invalid_metrics); return harness.finish(); } diff --git a/tests/app_core/main_toolbar_tests.cpp b/tests/app_core/main_toolbar_tests.cpp index 509b3fc..99589de 100644 --- a/tests/app_core/main_toolbar_tests.cpp +++ b/tests/app_core/main_toolbar_tests.cpp @@ -7,9 +7,24 @@ class FakeMainToolbarServices final : public pp::app::MainToolbarServices { public: void show_open_dialog() override { open_dialogs += 1; } void show_save_dialog() override { save_dialogs += 1; } - void invoke_undo() override { undo_calls += 1; } - void invoke_redo() override { redo_calls += 1; } - void clear_history() override { clear_history_calls += 1; } + void invoke_undo(const pp::app::HistoryUiPlan& plan) override + { + undo_calls += 1; + last_history = plan; + } + + void invoke_redo(const pp::app::HistoryUiPlan& plan) override + { + redo_calls += 1; + last_history = plan; + } + + void clear_history(const pp::app::HistoryUiPlan& plan) override + { + clear_history_calls += 1; + last_history = plan; + } + void clear_canvas(const pp::app::DocumentCanvasClearPlan& plan) override { clear_canvas_calls += 1; @@ -38,6 +53,7 @@ public: int clear_canvas_calls = 0; int message_boxes = 0; int settings_dialogs = 0; + pp::app::HistoryUiPlan last_history; pp::app::DocumentCanvasClearPlan last_clear; }; @@ -74,6 +90,8 @@ void history_commands_reuse_history_breakpoints(pp::tests::Harness& harness) PP_EXPECT(harness, undo.value().action == pp::app::MainToolbarAction::invoke_undo); PP_EXPECT(harness, undo.value().updates_memory_label); PP_EXPECT(harness, undo.value().updates_title); + PP_EXPECT(harness, undo.value().history.operation == pp::app::HistoryUiOperation::undo); + PP_EXPECT(harness, undo.value().history.invokes_undo); PP_EXPECT(harness, !undo.value().no_op); } @@ -95,6 +113,8 @@ void history_commands_reuse_history_breakpoints(pp::tests::Harness& harness) PP_EXPECT(harness, clear.value().action == pp::app::MainToolbarAction::clear_history); PP_EXPECT(harness, clear.value().updates_memory_label); PP_EXPECT(harness, !clear.value().updates_title); + PP_EXPECT(harness, clear.value().history.operation == pp::app::HistoryUiOperation::clear); + PP_EXPECT(harness, clear.value().history.clears_history); } } @@ -161,6 +181,8 @@ void executor_dispatches_to_service_boundary(pp::tests::Harness& harness) const auto status = pp::app::execute_main_toolbar_plan(undo.value(), services); PP_EXPECT(harness, status.ok()); PP_EXPECT(harness, services.undo_calls == 1); + PP_EXPECT(harness, services.last_history.operation == pp::app::HistoryUiOperation::undo); + PP_EXPECT(harness, services.last_history.undo_count == 1); } auto clear_canvas = pp::app::plan_main_toolbar_command(