#include "app_core/document_layer.h" #include "test_harness.h" #include #include namespace { class FakeDocumentLayerMenuServices final : public pp::app::DocumentLayerMenuServices { public: void clear_current_layer() override { clear_calls += 1; } void show_rename_dialog() override { rename_dialogs += 1; } void merge_with_lower_layer(int from_index, int to_index) override { merge_calls += 1; last_from_index = from_index; last_to_index = to_index; } void show_merge_animated_not_supported() override { merge_blocked_messages += 1; } [[nodiscard]] int total_calls() const noexcept { return clear_calls + rename_dialogs + merge_calls + merge_blocked_messages; } int clear_calls = 0; int rename_dialogs = 0; int merge_calls = 0; int merge_blocked_messages = 0; int last_from_index = -1; int last_to_index = -1; }; class FakeDocumentLayerRenameServices final : public pp::app::DocumentLayerRenameServices { public: void rename_layer(std::string_view old_name, std::string_view new_name) override { rename_calls += 1; last_old_name = std::string(old_name); last_new_name = std::string(new_name); } void finish_layer_rename() override { finish_calls += 1; } int rename_calls = 0; int finish_calls = 0; std::string last_old_name; std::string last_new_name; }; class FakeDocumentLayerOperationServices final : public pp::app::DocumentLayerOperationServices { public: void add_layer(std::string_view name, int insert_index) override { add_calls += 1; last_name = std::string(name); last_insert_index = insert_index; } void duplicate_layer(int source_index, int insert_index) override { duplicate_calls += 1; last_source_index = source_index; last_insert_index = insert_index; } void select_layer(int index) override { select_calls += 1; last_index = index; } void reorder_layer(int from_index, int to_index) override { reorder_calls += 1; last_from_index = from_index; last_to_index = to_index; } void remove_layer(int index) override { remove_calls += 1; last_index = index; } void set_layer_opacity(int index, float opacity) override { opacity_calls += 1; last_index = index; last_opacity = opacity; } void set_layer_visibility(int index, bool visible) override { visibility_calls += 1; last_index = index; last_flag = visible; } void set_layer_alpha_lock(int index, bool locked) override { alpha_lock_calls += 1; last_index = index; last_flag = locked; } void set_layer_blend_mode(int index, int blend_mode) override { blend_mode_calls += 1; last_index = index; last_blend_mode = blend_mode; } void set_layer_highlight(int index, bool highlighted) override { highlight_calls += 1; last_index = index; last_flag = highlighted; } void mark_unsaved() override { unsaved_marks += 1; } void reload_animation_layers() override { animation_reloads += 1; } void update_title() override { title_updates += 1; } [[nodiscard]] int mutation_calls() const noexcept { return add_calls + duplicate_calls + reorder_calls + remove_calls + opacity_calls + visibility_calls + alpha_lock_calls + blend_mode_calls; } int add_calls = 0; int duplicate_calls = 0; int select_calls = 0; int reorder_calls = 0; int remove_calls = 0; int opacity_calls = 0; int visibility_calls = 0; int alpha_lock_calls = 0; int blend_mode_calls = 0; int highlight_calls = 0; int unsaved_marks = 0; int animation_reloads = 0; int title_updates = 0; int last_index = -1; int last_from_index = -1; int last_to_index = -1; int last_insert_index = -1; int last_source_index = -1; int last_blend_mode = -1; float last_opacity = -1.0F; bool last_flag = false; std::string last_name; }; void layer_rename_records_changed_name(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_rename("Base", "Paint"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().old_name == "Base"); PP_EXPECT(harness, plan.value().new_name == "Paint"); PP_EXPECT( harness, plan.value().action == pp::app::DocumentLayerRenameAction::rename_and_record_undo); } } void layer_rename_ignores_unchanged_name(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_rename("Ink", "Ink"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().old_name == "Ink"); PP_EXPECT(harness, plan.value().new_name == "Ink"); PP_EXPECT( harness, plan.value().action == pp::app::DocumentLayerRenameAction::no_op_same_name); } } void layer_rename_rejects_empty_name(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_rename("Ink", ""); PP_EXPECT(harness, !plan); if (!plan) { PP_EXPECT(harness, plan.status().code == pp::foundation::StatusCode::invalid_argument); } } void layer_rename_rejects_overlong_name(pp::tests::Harness& harness) { const std::string too_long(pp::app::document_layer_name_max_length + 1U, 'x'); const auto plan = pp::app::plan_document_layer_rename("Ink", too_long); PP_EXPECT(harness, !plan); if (!plan) { PP_EXPECT(harness, plan.status().code == pp::foundation::StatusCode::out_of_range); } } void layer_rename_executor_dispatches_changed_name(pp::tests::Harness& harness) { FakeDocumentLayerRenameServices services; const auto plan = pp::app::plan_document_layer_rename("Base", "Paint"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, pp::app::execute_document_layer_rename_plan(plan.value(), services).ok()); PP_EXPECT(harness, services.rename_calls == 1); PP_EXPECT(harness, services.finish_calls == 1); PP_EXPECT(harness, services.last_old_name == "Base"); PP_EXPECT(harness, services.last_new_name == "Paint"); } } void layer_rename_executor_finishes_no_op(pp::tests::Harness& harness) { FakeDocumentLayerRenameServices services; const auto plan = pp::app::plan_document_layer_rename("Ink", "Ink"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, pp::app::execute_document_layer_rename_plan(plan.value(), services).ok()); PP_EXPECT(harness, services.rename_calls == 0); PP_EXPECT(harness, services.finish_calls == 1); } } void layer_rename_executor_rejects_malformed_changed_plan(pp::tests::Harness& harness) { FakeDocumentLayerRenameServices services; pp::app::DocumentLayerRenamePlan malformed; malformed.old_name = "Ink"; malformed.action = pp::app::DocumentLayerRenameAction::rename_and_record_undo; const auto status = pp::app::execute_document_layer_rename_plan(malformed, services); PP_EXPECT(harness, !status.ok()); PP_EXPECT(harness, status.code == pp::foundation::StatusCode::invalid_argument); PP_EXPECT(harness, services.rename_calls == 0); PP_EXPECT(harness, services.finish_calls == 0); } void layer_add_validates_insert_index_and_name(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_add(2, 1, "Paint"); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::DocumentLayerOperation::add); PP_EXPECT(harness, plan.value().insert_index == 1); PP_EXPECT(harness, plan.value().name == "Paint"); PP_EXPECT(harness, plan.value().marks_unsaved); PP_EXPECT(harness, plan.value().reloads_animation_layers); } PP_EXPECT(harness, !pp::app::plan_document_layer_add(2, -1, "Paint")); PP_EXPECT(harness, !pp::app::plan_document_layer_add(2, 3, "Paint")); PP_EXPECT(harness, !pp::app::plan_document_layer_add(2, 1, "")); } void layer_duplicate_select_and_reorder_validate_indices(pp::tests::Harness& harness) { const auto duplicate = pp::app::plan_document_layer_duplicate(3, 1); PP_EXPECT(harness, duplicate); if (duplicate) { PP_EXPECT(harness, duplicate.value().source_index == 1); PP_EXPECT(harness, duplicate.value().insert_index == 2); PP_EXPECT(harness, duplicate.value().marks_unsaved); } const auto select = pp::app::plan_document_layer_select(3, 2); PP_EXPECT(harness, select); if (select) { PP_EXPECT(harness, select.value().index == 2); PP_EXPECT(harness, !select.value().marks_unsaved); PP_EXPECT(harness, select.value().reloads_animation_layers); } const auto reorder = pp::app::plan_document_layer_reorder(3, 2, 0); PP_EXPECT(harness, reorder); if (reorder) { PP_EXPECT(harness, reorder.value().from_index == 2); PP_EXPECT(harness, reorder.value().to_index == 0); PP_EXPECT(harness, reorder.value().marks_unsaved); } const auto no_op_reorder = pp::app::plan_document_layer_reorder(3, 1, 1); PP_EXPECT(harness, no_op_reorder); if (no_op_reorder) { PP_EXPECT(harness, !no_op_reorder.value().mutates_document); PP_EXPECT(harness, !no_op_reorder.value().marks_unsaved); } PP_EXPECT(harness, !pp::app::plan_document_layer_duplicate(3, 3)); PP_EXPECT(harness, !pp::app::plan_document_layer_select(3, -1)); PP_EXPECT(harness, !pp::app::plan_document_layer_reorder(3, 0, 3)); } void layer_remove_keeps_at_least_one_layer(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_remove(2, 0); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::DocumentLayerOperation::remove); PP_EXPECT(harness, plan.value().index == 0); PP_EXPECT(harness, plan.value().marks_unsaved); PP_EXPECT(harness, plan.value().reloads_animation_layers); } PP_EXPECT(harness, !pp::app::plan_document_layer_remove(1, 0)); PP_EXPECT(harness, !pp::app::plan_document_layer_remove(2, 2)); } void layer_metadata_plans_validate_values(pp::tests::Harness& harness) { const auto opacity = pp::app::plan_document_layer_opacity(2, 1, 0.25F); PP_EXPECT(harness, opacity); if (opacity) { PP_EXPECT(harness, opacity.value().operation == pp::app::DocumentLayerOperation::set_opacity); PP_EXPECT(harness, opacity.value().opacity == 0.25F); PP_EXPECT(harness, opacity.value().marks_unsaved); PP_EXPECT(harness, !opacity.value().reloads_animation_layers); } const auto visibility = pp::app::plan_document_layer_visibility(2, 1, false); PP_EXPECT(harness, visibility); if (visibility) { PP_EXPECT(harness, visibility.value().operation == pp::app::DocumentLayerOperation::set_visibility); PP_EXPECT(harness, !visibility.value().flag); PP_EXPECT(harness, visibility.value().reloads_animation_layers); } const auto alpha_lock = pp::app::plan_document_layer_alpha_lock(2, 1, true); PP_EXPECT(harness, alpha_lock); if (alpha_lock) { PP_EXPECT(harness, alpha_lock.value().operation == pp::app::DocumentLayerOperation::set_alpha_lock); PP_EXPECT(harness, alpha_lock.value().flag); } const auto blend = pp::app::plan_document_layer_blend_mode(2, 1, 4); PP_EXPECT(harness, blend); if (blend) { PP_EXPECT(harness, blend.value().operation == pp::app::DocumentLayerOperation::set_blend_mode); PP_EXPECT(harness, blend.value().blend_mode == 4); } PP_EXPECT(harness, !pp::app::plan_document_layer_opacity(2, 1, -0.1F)); PP_EXPECT(harness, !pp::app::plan_document_layer_opacity(2, 1, 1.1F)); PP_EXPECT(harness, !pp::app::plan_document_layer_opacity(2, 1, std::nanf(""))); PP_EXPECT(harness, !pp::app::plan_document_layer_blend_mode(2, 1, -1)); PP_EXPECT(harness, !pp::app::plan_document_layer_blend_mode(2, 1, 5)); PP_EXPECT(harness, !pp::app::plan_document_layer_visibility(2, 2, true)); PP_EXPECT(harness, !pp::app::plan_document_layer_alpha_lock(2, 2, true)); } void layer_highlight_is_transient(pp::tests::Harness& harness) { const auto plan = pp::app::plan_document_layer_highlight(2, 1, true); PP_EXPECT(harness, plan); if (plan) { PP_EXPECT(harness, plan.value().operation == pp::app::DocumentLayerOperation::set_highlight); PP_EXPECT(harness, plan.value().flag); PP_EXPECT(harness, !plan.value().mutates_document); PP_EXPECT(harness, !plan.value().marks_unsaved); PP_EXPECT(harness, !plan.value().updates_title); } PP_EXPECT(harness, !pp::app::plan_document_layer_highlight(2, 2, true)); } void layer_operation_executor_dispatches_document_mutations(pp::tests::Harness& harness) { FakeDocumentLayerOperationServices services; const auto add = pp::app::plan_document_layer_add(2, 1, "Paint"); PP_EXPECT(harness, add); if (add) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(add.value(), services).ok()); PP_EXPECT(harness, services.add_calls == 1); PP_EXPECT(harness, services.last_name == "Paint"); PP_EXPECT(harness, services.last_insert_index == 1); PP_EXPECT(harness, services.unsaved_marks == 1); PP_EXPECT(harness, services.animation_reloads == 1); PP_EXPECT(harness, services.title_updates == 1); } const auto duplicate = pp::app::plan_document_layer_duplicate(3, 1); PP_EXPECT(harness, duplicate); if (duplicate) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(duplicate.value(), services).ok()); PP_EXPECT(harness, services.duplicate_calls == 1); PP_EXPECT(harness, services.last_source_index == 1); PP_EXPECT(harness, services.last_insert_index == 2); PP_EXPECT(harness, services.unsaved_marks == 2); PP_EXPECT(harness, services.animation_reloads == 2); PP_EXPECT(harness, services.title_updates == 2); } const auto reorder = pp::app::plan_document_layer_reorder(3, 2, 0); PP_EXPECT(harness, reorder); if (reorder) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(reorder.value(), services).ok()); PP_EXPECT(harness, services.reorder_calls == 1); PP_EXPECT(harness, services.last_from_index == 2); PP_EXPECT(harness, services.last_to_index == 0); PP_EXPECT(harness, services.unsaved_marks == 3); PP_EXPECT(harness, services.animation_reloads == 3); PP_EXPECT(harness, services.title_updates == 3); } const auto remove = pp::app::plan_document_layer_remove(3, 1); PP_EXPECT(harness, remove); if (remove) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(remove.value(), services).ok()); PP_EXPECT(harness, services.remove_calls == 1); PP_EXPECT(harness, services.last_index == 1); PP_EXPECT(harness, services.unsaved_marks == 4); PP_EXPECT(harness, services.animation_reloads == 4); PP_EXPECT(harness, services.title_updates == 4); } } void layer_operation_executor_dispatches_selection_and_metadata(pp::tests::Harness& harness) { FakeDocumentLayerOperationServices services; const auto select = pp::app::plan_document_layer_select(3, 2); PP_EXPECT(harness, select); if (select) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(select.value(), services).ok()); PP_EXPECT(harness, services.select_calls == 1); PP_EXPECT(harness, services.last_index == 2); PP_EXPECT(harness, services.unsaved_marks == 0); PP_EXPECT(harness, services.animation_reloads == 1); PP_EXPECT(harness, services.title_updates == 0); } const auto opacity = pp::app::plan_document_layer_opacity(3, 1, 0.25F); PP_EXPECT(harness, opacity); if (opacity) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(opacity.value(), services).ok()); PP_EXPECT(harness, services.opacity_calls == 1); PP_EXPECT(harness, services.last_index == 1); PP_EXPECT(harness, services.last_opacity == 0.25F); PP_EXPECT(harness, services.unsaved_marks == 1); PP_EXPECT(harness, services.animation_reloads == 1); PP_EXPECT(harness, services.title_updates == 1); } const auto visibility = pp::app::plan_document_layer_visibility(3, 1, false); PP_EXPECT(harness, visibility); if (visibility) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(visibility.value(), services).ok()); PP_EXPECT(harness, services.visibility_calls == 1); PP_EXPECT(harness, !services.last_flag); PP_EXPECT(harness, services.unsaved_marks == 2); PP_EXPECT(harness, services.animation_reloads == 2); PP_EXPECT(harness, services.title_updates == 2); } const auto alpha_lock = pp::app::plan_document_layer_alpha_lock(3, 1, true); PP_EXPECT(harness, alpha_lock); if (alpha_lock) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(alpha_lock.value(), services).ok()); PP_EXPECT(harness, services.alpha_lock_calls == 1); PP_EXPECT(harness, services.last_flag); PP_EXPECT(harness, services.unsaved_marks == 3); PP_EXPECT(harness, services.animation_reloads == 2); PP_EXPECT(harness, services.title_updates == 3); } const auto blend = pp::app::plan_document_layer_blend_mode(3, 1, 4); PP_EXPECT(harness, blend); if (blend) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(blend.value(), services).ok()); PP_EXPECT(harness, services.blend_mode_calls == 1); PP_EXPECT(harness, services.last_blend_mode == 4); PP_EXPECT(harness, services.unsaved_marks == 4); PP_EXPECT(harness, services.title_updates == 4); } } void layer_operation_executor_preserves_no_op_and_transient_actions(pp::tests::Harness& harness) { FakeDocumentLayerOperationServices services; const auto no_op_reorder = pp::app::plan_document_layer_reorder(3, 1, 1); PP_EXPECT(harness, no_op_reorder); if (no_op_reorder) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(no_op_reorder.value(), services).ok()); PP_EXPECT(harness, services.reorder_calls == 0); PP_EXPECT(harness, services.mutation_calls() == 0); PP_EXPECT(harness, services.unsaved_marks == 0); PP_EXPECT(harness, services.animation_reloads == 0); PP_EXPECT(harness, services.title_updates == 0); } const auto highlight = pp::app::plan_document_layer_highlight(3, 1, true); PP_EXPECT(harness, highlight); if (highlight) { PP_EXPECT(harness, pp::app::execute_document_layer_operation_plan(highlight.value(), services).ok()); PP_EXPECT(harness, services.highlight_calls == 1); PP_EXPECT(harness, services.last_index == 1); PP_EXPECT(harness, services.last_flag); PP_EXPECT(harness, services.unsaved_marks == 0); PP_EXPECT(harness, services.animation_reloads == 0); PP_EXPECT(harness, services.title_updates == 0); } } void layer_operation_executor_rejects_malformed_mutation_plans(pp::tests::Harness& harness) { FakeDocumentLayerOperationServices services; pp::app::DocumentLayerOperationPlan malformed; malformed.operation = pp::app::DocumentLayerOperation::add; malformed.insert_index = 1; malformed.mutates_document = true; const auto status = pp::app::execute_document_layer_operation_plan(malformed, services); PP_EXPECT(harness, !status.ok()); PP_EXPECT(harness, status.code == pp::foundation::StatusCode::invalid_argument); PP_EXPECT(harness, services.add_calls == 0); PP_EXPECT(harness, services.unsaved_marks == 0); } void layer_menu_labels_selected_layer_commands(pp::tests::Harness& harness) { const auto clear = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::clear, true, 1, 1, "Paint", "Base"); const auto rename = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::rename, true, 1, 1, "Paint", "Base"); PP_EXPECT(harness, clear); PP_EXPECT(harness, rename); if (clear) { PP_EXPECT(harness, clear.value().label == "Clear Layer Paint"); PP_EXPECT(harness, clear.value().action == pp::app::DocumentLayerMenuAction::clear_current_layer); } if (rename) { PP_EXPECT(harness, rename.value().label == "Rename Layer Paint"); PP_EXPECT(harness, rename.value().action == pp::app::DocumentLayerMenuAction::show_rename_dialog); } } void layer_menu_plans_merge_down_or_blocks_it(pp::tests::Harness& harness) { const auto merge = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 2, 1, "Ink", "Paint"); const auto base_layer = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 0, 1, "Base", ""); const auto animated = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 2, 3, "Ink", "Paint"); PP_EXPECT(harness, merge); PP_EXPECT(harness, base_layer); PP_EXPECT(harness, animated); if (merge) { PP_EXPECT(harness, merge.value().label == "Merge with Paint"); PP_EXPECT(harness, merge.value().from_index == 2); PP_EXPECT(harness, merge.value().to_index == 1); PP_EXPECT(harness, merge.value().action == pp::app::DocumentLayerMenuAction::merge_with_lower_layer); } if (base_layer) { PP_EXPECT(harness, base_layer.value().action == pp::app::DocumentLayerMenuAction::no_op_select_upper_layer); PP_EXPECT(harness, base_layer.value().label == "Merge Layer (Select upper layers)"); } if (animated) { PP_EXPECT( harness, animated.value().action == pp::app::DocumentLayerMenuAction::show_merge_animated_not_supported); } } void layer_menu_handles_missing_selection_and_bad_state(pp::tests::Harness& harness) { const auto missing = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::rename, false, 0, 1, "", ""); PP_EXPECT(harness, missing); if (missing) { PP_EXPECT(harness, missing.value().action == pp::app::DocumentLayerMenuAction::no_op_select_layer); PP_EXPECT(harness, missing.value().label == "Rename Layer (Select a layer)"); } PP_EXPECT( harness, !pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, -1, 1, "Ink", "Paint")); PP_EXPECT( harness, !pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 1, -1, "Ink", "Paint")); } void layer_menu_executor_dispatches_menu_actions(pp::tests::Harness& harness) { FakeDocumentLayerMenuServices services; const auto clear = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::clear, true, 1, 1, "Paint", "Base"); PP_EXPECT(harness, clear); if (clear) { PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(clear.value(), services).ok()); PP_EXPECT(harness, services.clear_calls == 1); } const auto rename = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::rename, true, 1, 1, "Paint", "Base"); PP_EXPECT(harness, rename); if (rename) { PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(rename.value(), services).ok()); PP_EXPECT(harness, services.rename_dialogs == 1); } const auto merge = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 2, 1, "Ink", "Paint"); PP_EXPECT(harness, merge); if (merge) { PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(merge.value(), services).ok()); PP_EXPECT(harness, services.merge_calls == 1); PP_EXPECT(harness, services.last_from_index == 2); PP_EXPECT(harness, services.last_to_index == 1); } const auto animated = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 2, 3, "Ink", "Paint"); PP_EXPECT(harness, animated); if (animated) { PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(animated.value(), services).ok()); PP_EXPECT(harness, services.merge_blocked_messages == 1); } } void layer_menu_executor_preserves_no_op_actions(pp::tests::Harness& harness) { FakeDocumentLayerMenuServices services; const auto missing = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::clear, false, 0, 1, "", ""); PP_EXPECT(harness, missing); if (missing) { PP_EXPECT(harness, missing.value().action == pp::app::DocumentLayerMenuAction::no_op_select_layer); PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(missing.value(), services).ok()); } const auto base_layer = pp::app::plan_document_layer_menu( pp::app::DocumentLayerMenuCommand::merge_down, true, 0, 1, "Base", ""); PP_EXPECT(harness, base_layer); if (base_layer) { PP_EXPECT(harness, base_layer.value().action == pp::app::DocumentLayerMenuAction::no_op_select_upper_layer); PP_EXPECT(harness, pp::app::execute_document_layer_menu_plan(base_layer.value(), services).ok()); } PP_EXPECT(harness, services.total_calls() == 0); } } int main() { pp::tests::Harness harness; harness.run("layer rename records changed name", layer_rename_records_changed_name); harness.run("layer rename ignores unchanged name", layer_rename_ignores_unchanged_name); harness.run("layer rename rejects empty name", layer_rename_rejects_empty_name); harness.run("layer rename rejects overlong name", layer_rename_rejects_overlong_name); harness.run("layer rename executor dispatches changed name", layer_rename_executor_dispatches_changed_name); harness.run("layer rename executor finishes no op", layer_rename_executor_finishes_no_op); harness.run("layer rename executor rejects malformed changed plan", layer_rename_executor_rejects_malformed_changed_plan); harness.run("layer add validates insert index and name", layer_add_validates_insert_index_and_name); harness.run("layer duplicate select and reorder validate indices", layer_duplicate_select_and_reorder_validate_indices); harness.run("layer remove keeps at least one layer", layer_remove_keeps_at_least_one_layer); harness.run("layer metadata plans validate values", layer_metadata_plans_validate_values); harness.run("layer highlight is transient", layer_highlight_is_transient); harness.run("layer operation executor dispatches document mutations", layer_operation_executor_dispatches_document_mutations); harness.run("layer operation executor dispatches selection and metadata", layer_operation_executor_dispatches_selection_and_metadata); harness.run("layer operation executor preserves no op and transient actions", layer_operation_executor_preserves_no_op_and_transient_actions); harness.run("layer operation executor rejects malformed mutation plans", layer_operation_executor_rejects_malformed_mutation_plans); harness.run("layer menu labels selected layer commands", layer_menu_labels_selected_layer_commands); harness.run("layer menu plans merge down or blocks it", layer_menu_plans_merge_down_or_blocks_it); harness.run("layer menu handles missing selection and bad state", layer_menu_handles_missing_selection_and_bad_state); harness.run("layer menu executor dispatches menu actions", layer_menu_executor_dispatches_menu_actions); harness.run("layer menu executor preserves no op actions", layer_menu_executor_preserves_no_op_actions); return harness.finish(); }