#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; }; 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_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_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 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 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(); }