628 lines
25 KiB
C++
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();
|
|
}
|