Files
panopainter/tests/app_core/document_canvas_tests.cpp

628 lines
25 KiB
C++

#include "app_core/document_canvas.h"
#include "assets/ppi_header.h"
#include "test_harness.h"
#include <cstdint>
#include <limits>
#include <span>
#include <string>
namespace {
class FakeDocumentCanvasClearServices final : public pp::app::DocumentCanvasClearServices {
public:
void clear_current_canvas(float r, float g, float b, float a) override
{
clear_calls += 1;
last_r = r;
last_g = g;
last_b = b;
last_a = a;
call_order += "clear;";
}
int clear_calls = 0;
float last_r = 0.0F;
float last_g = 0.0F;
float last_b = 0.0F;
float last_a = 0.0F;
std::string call_order;
};
void snapshot_plan_projects_canvas_metadata(pp::tests::Harness& harness)
{
const std::uint32_t base_frames[] { 120U, 240U };
const std::uint32_t paint_frames[] { 180U };
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
{
.name = "Base",
.visible = false,
.alpha_locked = true,
.opacity = 0.5F,
.blend_mode = 2,
.frame_durations_ms = std::span<const std::uint32_t>(base_frames),
.pending_face_payloads = 6U,
},
{
.name = "Paint",
.visible = true,
.alpha_locked = false,
.opacity = 0.75F,
.blend_mode = 4,
.frame_durations_ms = std::span<const std::uint32_t>(paint_frames),
.pending_face_payloads = 3U,
},
};
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 64U,
.height = 32U,
.active_layer_index = 1U,
.active_frame_index = 1U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
PP_EXPECT(harness, result);
if (!result) {
return;
}
const auto& value = result.value();
PP_EXPECT(harness, value.layer_count == 2U);
PP_EXPECT(harness, value.frame_count == 2U);
PP_EXPECT(harness, value.pending_face_payloads == 9U);
PP_EXPECT(harness, value.metadata_only);
PP_EXPECT(harness, value.requires_renderer_payload_readback);
PP_EXPECT(harness, value.document.width() == 64U);
PP_EXPECT(harness, value.document.height() == 32U);
PP_EXPECT(harness, value.document.active_layer_index() == 1U);
PP_EXPECT(harness, value.document.active_frame_index() == 1U);
PP_EXPECT(harness, value.document.layers().size() == 2U);
PP_EXPECT(harness, value.document.frames().size() == 2U);
PP_EXPECT(harness, value.document.layers()[0].name == "Base");
PP_EXPECT(harness, !value.document.layers()[0].visible);
PP_EXPECT(harness, value.document.layers()[0].alpha_locked);
PP_EXPECT(harness, value.document.layers()[0].opacity == 0.5F);
PP_EXPECT(harness, value.document.layers()[0].blend_mode == pp::paint::BlendMode::screen);
PP_EXPECT(harness, value.document.layers()[0].frames[1].duration_ms == 240U);
PP_EXPECT(harness, value.document.layers()[1].name == "Paint");
PP_EXPECT(harness, value.document.layers()[1].blend_mode == pp::paint::BlendMode::overlay);
PP_EXPECT(harness, value.document.layers()[1].frames.size() == 1U);
PP_EXPECT(harness, value.document.face_pixel_payload_count() == 0U);
const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(value);
PP_EXPECT(harness, !export_result);
PP_EXPECT(harness, export_result.status().code == pp::foundation::StatusCode::invalid_argument);
}
void snapshot_plan_defaults_empty_names_and_frames(pp::tests::Harness& harness)
{
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
{
.name = "",
.frame_durations_ms = {},
},
};
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
PP_EXPECT(harness, result);
if (!result) {
return;
}
PP_EXPECT(harness, result.value().frame_count == 1U);
PP_EXPECT(harness, result.value().pending_face_payloads == 0U);
PP_EXPECT(harness, !result.value().requires_renderer_payload_readback);
PP_EXPECT(harness, result.value().document.layers()[0].name == "Layer 1");
PP_EXPECT(harness, result.value().document.frames()[0].duration_ms == 100U);
const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(result.value());
PP_EXPECT(harness, export_result);
if (export_result) {
const auto decoded = pp::assets::decode_ppi_project_images(export_result.value().bytes);
PP_EXPECT(harness, decoded);
if (decoded) {
PP_EXPECT(harness, decoded.value().project.body.summary.dirty_face_count == 0U);
}
}
}
void snapshot_plan_attaches_captured_face_payloads(pp::tests::Harness& harness)
{
const std::uint32_t frames[] { 100U };
const std::uint8_t rgba[] {
255U, 0U, 0U, 255U,
0U, 255U, 0U, 255U,
};
const pp::app::DocumentCanvasFacePayloadInput payloads[] {
{
.frame_index = 0U,
.face_index = 2U,
.x = 3U,
.y = 4U,
.width = 2U,
.height = 1U,
.rgba8 = std::span<const std::uint8_t>(rgba),
},
};
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
{
.name = "Paint",
.frame_durations_ms = std::span<const std::uint32_t>(frames),
.pending_face_payloads = 1U,
.captured_face_payloads = std::span<const pp::app::DocumentCanvasFacePayloadInput>(payloads),
},
};
const auto result = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
PP_EXPECT(harness, result);
if (!result) {
return;
}
PP_EXPECT(harness, result.value().pending_face_payloads == 1U);
PP_EXPECT(harness, result.value().captured_face_payloads == 1U);
PP_EXPECT(harness, !result.value().metadata_only);
PP_EXPECT(harness, !result.value().requires_renderer_payload_readback);
PP_EXPECT(harness, result.value().document.face_pixel_payload_count() == 1U);
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].face_index == 2U);
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].x == 3U);
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].rgba8[4] == 0U);
PP_EXPECT(harness, result.value().document.layers()[0].frames[0].face_pixels[0].rgba8[5] == 255U);
const auto report = pp::app::make_document_canvas_save_snapshot_report(result.value());
PP_EXPECT(harness, report.width == 16U);
PP_EXPECT(harness, report.height == 8U);
PP_EXPECT(harness, report.layer_count == 1U);
PP_EXPECT(harness, report.frame_count == 1U);
PP_EXPECT(harness, report.pending_face_payloads == 1U);
PP_EXPECT(harness, report.captured_face_payloads == 1U);
PP_EXPECT(harness, report.payload_complete);
PP_EXPECT(harness, report.can_export_ppi);
const auto export_result = pp::app::export_document_canvas_save_snapshot_to_ppi(result.value());
PP_EXPECT(harness, export_result);
if (export_result) {
PP_EXPECT(harness, export_result.value().report.can_export_ppi);
PP_EXPECT(harness, export_result.value().bytes.size() > 0U);
const auto decoded = pp::assets::decode_ppi_project_images(export_result.value().bytes);
PP_EXPECT(harness, decoded);
if (decoded) {
PP_EXPECT(harness, decoded.value().project.body.summary.dirty_face_count == 1U);
PP_EXPECT(harness, decoded.value().project.body.summary.rgba_face_payload_count == 1U);
}
}
}
void save_writer_route_uses_ppi_writer_for_complete_payloads(pp::tests::Harness& harness)
{
pp::app::DocumentCanvasSaveSnapshotReport report;
report.payload_complete = true;
report.can_export_ppi = true;
const auto plan = pp::app::plan_document_canvas_save_writer_route(report);
PP_EXPECT(harness, plan.action == pp::app::DocumentCanvasSaveWriterAction::use_document_ppi_writer);
PP_EXPECT(harness, plan.uses_document_ppi_writer);
PP_EXPECT(harness, plan.payload_complete);
PP_EXPECT(harness, plan.can_export_ppi);
PP_EXPECT(harness, plan.fallback_reason.empty());
}
void save_writer_route_falls_back_for_pending_payloads(pp::tests::Harness& harness)
{
pp::app::DocumentCanvasSaveSnapshotReport report;
report.payload_complete = false;
report.can_export_ppi = false;
const auto plan = pp::app::plan_document_canvas_save_writer_route(report);
PP_EXPECT(harness, plan.action == pp::app::DocumentCanvasSaveWriterAction::use_legacy_project_save);
PP_EXPECT(harness, !plan.uses_document_ppi_writer);
PP_EXPECT(harness, !plan.payload_complete);
PP_EXPECT(harness, !plan.can_export_ppi);
PP_EXPECT(
harness,
plan.fallback_reason == "canvas document snapshot still requires renderer payload readback");
}
void project_save_target_plan_preserves_legacy_paths(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_target(
"D:/Paint/data",
"D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, plan);
if (!plan) {
return;
}
PP_EXPECT(harness, plan.value().target_path == "D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, plan.value().file_name == "demo");
PP_EXPECT(harness, plan.value().temporary_path == "D:/Paint/data/demo.tmp.ppi");
PP_EXPECT(harness, plan.value().timelapse_path == "D:/Paint/data/demo.pptl");
}
void project_save_target_plan_accepts_windows_backslashes(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_target(
"D:/Paint/data",
"D:\\Paint\\projects\\demo.ppi");
PP_EXPECT(harness, plan);
if (!plan) {
return;
}
PP_EXPECT(harness, plan.value().file_name == "demo");
PP_EXPECT(harness, plan.value().temporary_path == "D:/Paint/data/demo.tmp.ppi");
PP_EXPECT(harness, plan.value().timelapse_path == "D:/Paint/data/demo.pptl");
}
void project_save_target_plan_rejects_empty_inputs(pp::tests::Harness& harness)
{
const auto no_data = pp::app::plan_document_canvas_project_save_target("", "D:/Paint/demo.ppi");
const auto no_target = pp::app::plan_document_canvas_project_save_target("D:/Paint/data", "");
const auto no_name = pp::app::plan_document_canvas_project_save_target("D:/Paint/data", "D:/Paint/");
PP_EXPECT(harness, !no_data);
PP_EXPECT(harness, !no_target);
PP_EXPECT(harness, !no_name);
PP_EXPECT(harness, no_data.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(harness, no_target.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(harness, no_name.status().code == pp::foundation::StatusCode::invalid_argument);
}
void project_save_write_plan_writes_direct_for_new_targets(pp::tests::Harness& harness)
{
const auto target = pp::app::plan_document_canvas_project_save_target(
"D:/Paint/data",
"D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, target);
if (!target) {
return;
}
const auto plan = pp::app::plan_document_canvas_project_save_write(target.value(), false);
PP_EXPECT(harness, plan);
if (!plan) {
return;
}
PP_EXPECT(harness, plan.value().action == pp::app::DocumentCanvasProjectSaveWriteAction::write_direct_to_target);
PP_EXPECT(harness, plan.value().write_path == "D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, plan.value().target_path == "D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, !plan.value().target_exists);
PP_EXPECT(harness, !plan.value().uses_temporary);
PP_EXPECT(harness, !plan.value().falls_back_to_direct_on_temporary_open_failure);
}
void project_save_write_plan_prefers_temporary_for_existing_targets(pp::tests::Harness& harness)
{
const auto target = pp::app::plan_document_canvas_project_save_target(
"D:/Paint/data",
"D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, target);
if (!target) {
return;
}
const auto plan = pp::app::plan_document_canvas_project_save_write(target.value(), true);
PP_EXPECT(harness, plan);
if (!plan) {
return;
}
PP_EXPECT(harness, plan.value().action == pp::app::DocumentCanvasProjectSaveWriteAction::write_temporary_then_swap);
PP_EXPECT(harness, plan.value().write_path == "D:/Paint/data/demo.tmp.ppi");
PP_EXPECT(harness, plan.value().target_path == "D:/Paint/projects/demo.ppi");
PP_EXPECT(harness, plan.value().temporary_path == "D:/Paint/data/demo.tmp.ppi");
PP_EXPECT(harness, plan.value().target_exists);
PP_EXPECT(harness, plan.value().uses_temporary);
PP_EXPECT(harness, plan.value().falls_back_to_direct_on_temporary_open_failure);
}
void project_save_write_plan_rejects_missing_paths(pp::tests::Harness& harness)
{
pp::app::DocumentCanvasProjectSaveTargetPlan empty_target;
const auto no_target = pp::app::plan_document_canvas_project_save_write(empty_target, false);
pp::app::DocumentCanvasProjectSaveTargetPlan no_temporary;
no_temporary.target_path = "D:/Paint/projects/demo.ppi";
const auto missing_temporary = pp::app::plan_document_canvas_project_save_write(no_temporary, true);
PP_EXPECT(harness, !no_target);
PP_EXPECT(harness, !missing_temporary);
PP_EXPECT(harness, no_target.status().code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(harness, missing_temporary.status().code == pp::foundation::StatusCode::invalid_argument);
}
void project_save_commit_plan_succeeds_for_direct_write(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_commit(
pp::app::DocumentCanvasProjectSaveCommitInput {
.used_temporary = false,
});
PP_EXPECT(harness, plan.saved);
PP_EXPECT(harness, !plan.used_temporary);
PP_EXPECT(harness, !plan.target_removed);
PP_EXPECT(harness, !plan.temporary_renamed);
PP_EXPECT(harness, !plan.target_may_be_missing);
PP_EXPECT(harness, plan.log_message == "project saved to target");
}
void project_save_commit_plan_succeeds_for_temporary_swap(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_commit(
pp::app::DocumentCanvasProjectSaveCommitInput {
.used_temporary = true,
.target_remove_attempted = true,
.target_remove_succeeded = true,
.temporary_rename_attempted = true,
.temporary_rename_succeeded = true,
});
PP_EXPECT(harness, plan.saved);
PP_EXPECT(harness, plan.used_temporary);
PP_EXPECT(harness, plan.target_removed);
PP_EXPECT(harness, plan.temporary_renamed);
PP_EXPECT(harness, !plan.target_may_be_missing);
PP_EXPECT(harness, plan.log_message == "temporary project swapped successfully");
}
void project_save_commit_plan_fails_when_target_remove_fails(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_commit(
pp::app::DocumentCanvasProjectSaveCommitInput {
.used_temporary = true,
.target_remove_attempted = true,
.target_remove_succeeded = false,
.temporary_rename_attempted = false,
.temporary_rename_succeeded = false,
});
PP_EXPECT(harness, !plan.saved);
PP_EXPECT(harness, plan.used_temporary);
PP_EXPECT(harness, !plan.target_removed);
PP_EXPECT(harness, !plan.temporary_renamed);
PP_EXPECT(harness, !plan.target_may_be_missing);
PP_EXPECT(harness, plan.log_message == "could not remove target project before temporary swap");
}
void project_save_commit_plan_flags_missing_target_after_rename_failure(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_project_save_commit(
pp::app::DocumentCanvasProjectSaveCommitInput {
.used_temporary = true,
.target_remove_attempted = true,
.target_remove_succeeded = true,
.temporary_rename_attempted = true,
.temporary_rename_succeeded = false,
});
PP_EXPECT(harness, !plan.saved);
PP_EXPECT(harness, plan.used_temporary);
PP_EXPECT(harness, plan.target_removed);
PP_EXPECT(harness, !plan.temporary_renamed);
PP_EXPECT(harness, plan.target_may_be_missing);
PP_EXPECT(harness, plan.log_message == "temporary project not swapped after original removal");
}
void snapshot_plan_rejects_invalid_canvas_state(pp::tests::Harness& harness)
{
const std::uint32_t frames[] { 100U };
const pp::app::DocumentCanvasLayerSnapshotInput layers[] {
{
.name = "Layer",
.frame_durations_ms = std::span<const std::uint32_t>(frames),
},
};
const auto no_canvas = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = false,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
const auto bad_layer = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.active_layer_index = 1U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(layers),
});
const pp::app::DocumentCanvasLayerSnapshotInput bad_blend_layers[] {
{
.name = "Layer",
.blend_mode = 64,
.frame_durations_ms = std::span<const std::uint32_t>(frames),
},
};
const auto bad_blend = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_blend_layers),
});
const std::uint32_t bad_frames[] { 0U };
const pp::app::DocumentCanvasLayerSnapshotInput bad_duration_layers[] {
{
.name = "Layer",
.frame_durations_ms = std::span<const std::uint32_t>(bad_frames),
},
};
const auto bad_duration = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_duration_layers),
});
const std::uint8_t bad_rgba[] { 1U, 2U, 3U };
const pp::app::DocumentCanvasFacePayloadInput bad_payloads[] {
{
.frame_index = 0U,
.face_index = 0U,
.x = 0U,
.y = 0U,
.width = 1U,
.height = 1U,
.rgba8 = std::span<const std::uint8_t>(bad_rgba),
},
};
const pp::app::DocumentCanvasLayerSnapshotInput bad_payload_layers[] {
{
.name = "Layer",
.frame_durations_ms = std::span<const std::uint32_t>(frames),
.pending_face_payloads = 1U,
.captured_face_payloads = std::span<const pp::app::DocumentCanvasFacePayloadInput>(bad_payloads),
},
};
const auto bad_payload = pp::app::plan_document_canvas_snapshot(pp::app::DocumentCanvasSnapshotInput {
.has_canvas = true,
.width = 16U,
.height = 8U,
.layers = std::span<const pp::app::DocumentCanvasLayerSnapshotInput>(bad_payload_layers),
});
PP_EXPECT(harness, !no_canvas);
PP_EXPECT(harness, !bad_layer);
PP_EXPECT(harness, !bad_blend);
PP_EXPECT(harness, !bad_duration);
PP_EXPECT(harness, !bad_payload);
}
void clear_plan_records_legacy_canvas_effects(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_clear(true, 0.0F, 0.1F, 0.2F, 0.3F);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, plan.value().clears_canvas);
PP_EXPECT(harness, plan.value().records_undo);
PP_EXPECT(harness, plan.value().marks_unsaved);
PP_EXPECT(harness, !plan.value().no_op);
PP_EXPECT(harness, plan.value().r == 0.0F);
PP_EXPECT(harness, plan.value().g == 0.1F);
PP_EXPECT(harness, plan.value().b == 0.2F);
PP_EXPECT(harness, plan.value().a == 0.3F);
}
}
void clear_plan_noops_without_canvas(pp::tests::Harness& harness)
{
const auto plan = pp::app::plan_document_canvas_clear(false);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, !plan.value().clears_canvas);
PP_EXPECT(harness, !plan.value().records_undo);
PP_EXPECT(harness, !plan.value().marks_unsaved);
PP_EXPECT(harness, plan.value().no_op);
}
}
void clear_plan_rejects_bad_color_channels(pp::tests::Harness& harness)
{
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(true, -0.01F, 0.0F, 0.0F, 0.0F));
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(true, 0.0F, 1.01F, 0.0F, 0.0F));
PP_EXPECT(harness, !pp::app::plan_document_canvas_clear(
true,
0.0F,
0.0F,
std::numeric_limits<float>::infinity(),
0.0F));
}
void clear_executor_dispatches_color_to_service(pp::tests::Harness& harness)
{
FakeDocumentCanvasClearServices services;
const auto plan = pp::app::plan_document_canvas_clear(true, 0.25F, 0.5F, 0.75F, 1.0F);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, pp::app::execute_document_canvas_clear_plan(plan.value(), services).ok());
}
PP_EXPECT(harness, services.clear_calls == 1);
PP_EXPECT(harness, services.last_r == 0.25F);
PP_EXPECT(harness, services.last_g == 0.5F);
PP_EXPECT(harness, services.last_b == 0.75F);
PP_EXPECT(harness, services.last_a == 1.0F);
PP_EXPECT(harness, services.call_order == "clear;");
}
void clear_executor_preserves_noop_without_canvas(pp::tests::Harness& harness)
{
FakeDocumentCanvasClearServices services;
const auto plan = pp::app::plan_document_canvas_clear(false);
PP_EXPECT(harness, plan);
if (plan) {
PP_EXPECT(harness, pp::app::execute_document_canvas_clear_plan(plan.value(), services).ok());
}
PP_EXPECT(harness, services.clear_calls == 0);
}
void clear_executor_rejects_invalid_color(pp::tests::Harness& harness)
{
FakeDocumentCanvasClearServices services;
pp::app::DocumentCanvasClearPlan plan;
plan.clears_canvas = true;
plan.records_undo = true;
plan.marks_unsaved = true;
plan.no_op = false;
plan.r = 0.0F;
plan.g = 0.0F;
plan.b = 1.5F;
plan.a = 0.0F;
const auto status = pp::app::execute_document_canvas_clear_plan(plan, services);
PP_EXPECT(harness, !status.ok());
PP_EXPECT(harness, status.code == pp::foundation::StatusCode::out_of_range);
PP_EXPECT(harness, services.clear_calls == 0);
}
} // namespace
int main()
{
pp::tests::Harness harness;
harness.run("snapshot plan projects canvas metadata", snapshot_plan_projects_canvas_metadata);
harness.run("snapshot plan defaults empty names and frames", snapshot_plan_defaults_empty_names_and_frames);
harness.run("snapshot plan attaches captured face payloads", snapshot_plan_attaches_captured_face_payloads);
harness.run("save writer route uses ppi writer for complete payloads", save_writer_route_uses_ppi_writer_for_complete_payloads);
harness.run("save writer route falls back for pending payloads", save_writer_route_falls_back_for_pending_payloads);
harness.run("project save target plan preserves legacy paths", project_save_target_plan_preserves_legacy_paths);
harness.run("project save target plan accepts windows backslashes", project_save_target_plan_accepts_windows_backslashes);
harness.run("project save target plan rejects empty inputs", project_save_target_plan_rejects_empty_inputs);
harness.run("project save write plan writes direct for new targets", project_save_write_plan_writes_direct_for_new_targets);
harness.run("project save write plan prefers temporary for existing targets", project_save_write_plan_prefers_temporary_for_existing_targets);
harness.run("project save write plan rejects missing paths", project_save_write_plan_rejects_missing_paths);
harness.run("project save commit plan succeeds for direct write", project_save_commit_plan_succeeds_for_direct_write);
harness.run("project save commit plan succeeds for temporary swap", project_save_commit_plan_succeeds_for_temporary_swap);
harness.run("project save commit plan fails when target remove fails", project_save_commit_plan_fails_when_target_remove_fails);
harness.run("project save commit plan flags missing target after rename failure", project_save_commit_plan_flags_missing_target_after_rename_failure);
harness.run("snapshot plan rejects invalid canvas state", snapshot_plan_rejects_invalid_canvas_state);
harness.run("clear plan records legacy canvas effects", clear_plan_records_legacy_canvas_effects);
harness.run("clear plan noops without canvas", clear_plan_noops_without_canvas);
harness.run("clear plan rejects bad color channels", clear_plan_rejects_bad_color_channels);
harness.run("clear executor dispatches color to service", clear_executor_dispatches_color_to_service);
harness.run("clear executor preserves noop without canvas", clear_executor_preserves_noop_without_canvas);
harness.run("clear executor rejects invalid color", clear_executor_rejects_invalid_color);
return harness.finish();
}