Files
panopainter/tests/app_core/document_layer_tests.cpp

428 lines
15 KiB
C++

#include "app_core/document_layer.h"
#include "test_harness.h"
#include <cmath>
#include <string>
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();
}