340 lines
14 KiB
C++
340 lines
14 KiB
C++
#include "document/document.h"
|
|
#include "test_harness.h"
|
|
|
|
#include <cmath>
|
|
#include <string_view>
|
|
|
|
using pp::paint::BlendMode;
|
|
using pp::document::CanvasDocument;
|
|
using pp::document::DocumentHistory;
|
|
using pp::document::DocumentConfig;
|
|
using pp::document::max_document_history_entries;
|
|
using pp::document::max_canvas_dimension;
|
|
using pp::document::max_frame_count;
|
|
using pp::document::max_layer_count;
|
|
using pp::document::max_layer_name_length;
|
|
using pp::foundation::StatusCode;
|
|
|
|
namespace {
|
|
|
|
void creates_document_with_default_layers(pp::tests::Harness& h)
|
|
{
|
|
const auto document = CanvasDocument::create(
|
|
DocumentConfig { .width = 128, .height = 64, .layer_count = 2 });
|
|
|
|
PP_EXPECT(h, document.ok());
|
|
PP_EXPECT(h, document.value().width() == 128U);
|
|
PP_EXPECT(h, document.value().height() == 64U);
|
|
PP_EXPECT(h, document.value().layers().size() == 2U);
|
|
PP_EXPECT(h, document.value().layers()[0].name == std::string_view("Layer 1"));
|
|
PP_EXPECT(h, document.value().layers()[1].name == std::string_view("Layer 2"));
|
|
PP_EXPECT(h, document.value().active_layer_index() == 0U);
|
|
PP_EXPECT(h, document.value().frames().size() == 1U);
|
|
PP_EXPECT(h, document.value().frames()[0].duration_ms == 100U);
|
|
PP_EXPECT(h, document.value().active_frame_index() == 0U);
|
|
}
|
|
|
|
void rejects_invalid_document_configs(pp::tests::Harness& h)
|
|
{
|
|
const auto zero_width = CanvasDocument::create(
|
|
DocumentConfig { .width = 0, .height = 64, .layer_count = 1 });
|
|
const auto huge_width = CanvasDocument::create(
|
|
DocumentConfig { .width = max_canvas_dimension + 1U, .height = 64, .layer_count = 1 });
|
|
const auto no_layers = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 0 });
|
|
const auto too_many_layers = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = max_layer_count + 1U });
|
|
|
|
PP_EXPECT(h, !zero_width.ok());
|
|
PP_EXPECT(h, zero_width.status().code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !huge_width.ok());
|
|
PP_EXPECT(h, huge_width.status().code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !no_layers.ok());
|
|
PP_EXPECT(h, no_layers.status().code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !too_many_layers.ok());
|
|
PP_EXPECT(h, too_many_layers.status().code == StatusCode::out_of_range);
|
|
}
|
|
|
|
void manages_layer_add_remove_and_active_index(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
const auto added = document.add_layer("Paint");
|
|
PP_EXPECT(h, added.ok());
|
|
PP_EXPECT(h, added.value() == 1U);
|
|
PP_EXPECT(h, document.active_layer_index() == 1U);
|
|
PP_EXPECT(h, document.layers()[1].name == std::string_view("Paint"));
|
|
|
|
PP_EXPECT(h, document.remove_layer(0).ok());
|
|
PP_EXPECT(h, document.layers().size() == 1U);
|
|
PP_EXPECT(h, document.active_layer_index() == 0U);
|
|
|
|
const auto remove_last = document.remove_layer(0);
|
|
PP_EXPECT(h, !remove_last.ok());
|
|
PP_EXPECT(h, remove_last.code == StatusCode::invalid_argument);
|
|
}
|
|
|
|
void moves_layers_and_preserves_active_layer_identity(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 3 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
PP_EXPECT(h, document.set_active_layer(2).ok());
|
|
PP_EXPECT(h, document.move_layer(2, 0).ok());
|
|
PP_EXPECT(h, document.active_layer_index() == 0U);
|
|
PP_EXPECT(h, document.layers()[0].name == std::string_view("Layer 3"));
|
|
PP_EXPECT(h, document.layers()[1].name == std::string_view("Layer 1"));
|
|
PP_EXPECT(h, document.layers()[2].name == std::string_view("Layer 2"));
|
|
|
|
const auto bad_move = document.move_layer(4, 0);
|
|
PP_EXPECT(h, !bad_move.ok());
|
|
PP_EXPECT(h, bad_move.code == StatusCode::out_of_range);
|
|
}
|
|
|
|
void updates_layer_metadata(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 2 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
PP_EXPECT(h, document.rename_layer(1, "Ink").ok());
|
|
PP_EXPECT(h, document.set_layer_visible(1, false).ok());
|
|
PP_EXPECT(h, document.set_layer_opacity(1, 0.25F).ok());
|
|
PP_EXPECT(h, document.set_layer_blend_mode(1, BlendMode::multiply).ok());
|
|
|
|
PP_EXPECT(h, document.layers()[1].name == std::string_view("Ink"));
|
|
PP_EXPECT(h, !document.layers()[1].visible);
|
|
PP_EXPECT(h, std::fabs(document.layers()[1].opacity - 0.25F) < 0.0001F);
|
|
PP_EXPECT(h, document.layers()[1].blend_mode == BlendMode::multiply);
|
|
}
|
|
|
|
void rejects_invalid_layer_metadata(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
const auto empty_name = document.rename_layer(0, "");
|
|
const auto long_name = document.rename_layer(0, std::string(max_layer_name_length + 1U, 'x'));
|
|
const auto missing_name = document.rename_layer(4, "Missing");
|
|
const auto bad_opacity_low = document.set_layer_opacity(0, -0.1F);
|
|
const auto bad_opacity_high = document.set_layer_opacity(0, 1.1F);
|
|
const auto bad_opacity_nan = document.set_layer_opacity(0, std::nanf(""));
|
|
const auto missing_visible = document.set_layer_visible(2, true);
|
|
const auto missing_blend = document.set_layer_blend_mode(2, BlendMode::normal);
|
|
const auto bad_blend = document.set_layer_blend_mode(0, static_cast<BlendMode>(255));
|
|
const auto bad_add_layer = document.add_layer(std::string(max_layer_name_length + 1U, 'x'));
|
|
|
|
PP_EXPECT(h, !empty_name.ok());
|
|
PP_EXPECT(h, empty_name.code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !long_name.ok());
|
|
PP_EXPECT(h, long_name.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !missing_name.ok());
|
|
PP_EXPECT(h, missing_name.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !bad_opacity_low.ok());
|
|
PP_EXPECT(h, bad_opacity_low.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !bad_opacity_high.ok());
|
|
PP_EXPECT(h, bad_opacity_high.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !bad_opacity_nan.ok());
|
|
PP_EXPECT(h, bad_opacity_nan.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !missing_visible.ok());
|
|
PP_EXPECT(h, missing_visible.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !missing_blend.ok());
|
|
PP_EXPECT(h, missing_blend.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !bad_blend.ok());
|
|
PP_EXPECT(h, bad_blend.code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !bad_add_layer.ok());
|
|
PP_EXPECT(h, bad_add_layer.status().code == StatusCode::out_of_range);
|
|
}
|
|
|
|
void manages_animation_frames_and_duration(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
const auto added = document.add_frame(250);
|
|
PP_EXPECT(h, added.ok());
|
|
PP_EXPECT(h, added.value() == 1U);
|
|
PP_EXPECT(h, document.active_frame_index() == 1U);
|
|
PP_EXPECT(h, document.frames()[1].duration_ms == 250U);
|
|
|
|
const auto duplicated = document.duplicate_frame(1);
|
|
PP_EXPECT(h, duplicated.ok());
|
|
PP_EXPECT(h, duplicated.value() == 2U);
|
|
PP_EXPECT(h, document.frames()[2].duration_ms == 250U);
|
|
PP_EXPECT(h, document.set_frame_duration(2, 333).ok());
|
|
PP_EXPECT(h, document.frames()[2].duration_ms == 333U);
|
|
|
|
PP_EXPECT(h, document.remove_frame(1).ok());
|
|
PP_EXPECT(h, document.frames().size() == 2U);
|
|
PP_EXPECT(h, document.active_frame_index() == 1U);
|
|
}
|
|
|
|
void rejects_invalid_animation_frame_operations(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
auto document = document_result.value();
|
|
|
|
const auto zero_duration = document.add_frame(0);
|
|
const auto duplicate_missing = document.duplicate_frame(9);
|
|
const auto remove_missing = document.remove_frame(9);
|
|
const auto remove_only = document.remove_frame(0);
|
|
const auto set_bad_duration = document.set_frame_duration(0, 0);
|
|
const auto set_missing_active = document.set_active_frame(2);
|
|
|
|
PP_EXPECT(h, !zero_duration.ok());
|
|
PP_EXPECT(h, zero_duration.status().code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !duplicate_missing.ok());
|
|
PP_EXPECT(h, duplicate_missing.status().code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !remove_missing.ok());
|
|
PP_EXPECT(h, remove_missing.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, !remove_only.ok());
|
|
PP_EXPECT(h, remove_only.code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !set_bad_duration.ok());
|
|
PP_EXPECT(h, set_bad_duration.code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !set_missing_active.ok());
|
|
PP_EXPECT(h, set_missing_active.code == StatusCode::out_of_range);
|
|
PP_EXPECT(h, max_frame_count > document.frames().size());
|
|
}
|
|
|
|
void records_document_history_and_restores_snapshots(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
|
|
auto history_result = DocumentHistory::create(document_result.value(), 4);
|
|
PP_EXPECT(h, history_result.ok());
|
|
auto history = history_result.value();
|
|
|
|
auto with_layer = history.current();
|
|
const auto added_layer = with_layer.add_layer("Paint");
|
|
PP_EXPECT(h, added_layer.ok());
|
|
PP_EXPECT(h, history.apply(with_layer).ok());
|
|
|
|
auto with_frame = history.current();
|
|
const auto added_frame = with_frame.add_frame(250);
|
|
PP_EXPECT(h, added_frame.ok());
|
|
PP_EXPECT(h, history.apply(with_frame).ok());
|
|
|
|
PP_EXPECT(h, history.size() == 3U);
|
|
PP_EXPECT(h, history.current_index() == 2U);
|
|
PP_EXPECT(h, history.current().layers().size() == 2U);
|
|
PP_EXPECT(h, history.current().frames().size() == 2U);
|
|
PP_EXPECT(h, history.can_undo());
|
|
PP_EXPECT(h, !history.can_redo());
|
|
|
|
PP_EXPECT(h, history.undo().ok());
|
|
PP_EXPECT(h, history.current().layers().size() == 2U);
|
|
PP_EXPECT(h, history.current().frames().size() == 1U);
|
|
PP_EXPECT(h, history.can_redo());
|
|
|
|
PP_EXPECT(h, history.undo().ok());
|
|
PP_EXPECT(h, history.current().layers().size() == 1U);
|
|
PP_EXPECT(h, history.current().frames().size() == 1U);
|
|
const auto undo_past_start = history.undo();
|
|
PP_EXPECT(h, !undo_past_start.ok());
|
|
PP_EXPECT(h, undo_past_start.code == StatusCode::out_of_range);
|
|
|
|
PP_EXPECT(h, history.redo().ok());
|
|
PP_EXPECT(h, history.current().layers().size() == 2U);
|
|
}
|
|
|
|
void applying_after_undo_discards_redo_branch(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
|
|
auto history_result = DocumentHistory::create(document_result.value(), 5);
|
|
PP_EXPECT(h, history_result.ok());
|
|
auto history = history_result.value();
|
|
|
|
auto first_branch = history.current();
|
|
PP_EXPECT(h, first_branch.add_layer("Branch A").ok());
|
|
PP_EXPECT(h, history.apply(first_branch).ok());
|
|
|
|
auto second_branch = history.current();
|
|
PP_EXPECT(h, second_branch.add_layer("Branch B").ok());
|
|
PP_EXPECT(h, history.apply(second_branch).ok());
|
|
|
|
PP_EXPECT(h, history.undo().ok());
|
|
PP_EXPECT(h, history.can_redo());
|
|
|
|
auto replacement_branch = history.current();
|
|
PP_EXPECT(h, replacement_branch.add_layer("Replacement").ok());
|
|
PP_EXPECT(h, history.apply(replacement_branch).ok());
|
|
|
|
PP_EXPECT(h, !history.can_redo());
|
|
PP_EXPECT(h, history.current().layers().size() == 3U);
|
|
PP_EXPECT(h, history.current().layers()[2].name == std::string_view("Replacement"));
|
|
}
|
|
|
|
void bounds_document_history_capacity(pp::tests::Harness& h)
|
|
{
|
|
auto document_result = CanvasDocument::create(
|
|
DocumentConfig { .width = 64, .height = 64, .layer_count = 1 });
|
|
PP_EXPECT(h, document_result.ok());
|
|
|
|
auto too_small = DocumentHistory::create(document_result.value(), 1);
|
|
auto too_large = DocumentHistory::create(document_result.value(), max_document_history_entries + 1U);
|
|
PP_EXPECT(h, !too_small.ok());
|
|
PP_EXPECT(h, too_small.status().code == StatusCode::invalid_argument);
|
|
PP_EXPECT(h, !too_large.ok());
|
|
PP_EXPECT(h, too_large.status().code == StatusCode::out_of_range);
|
|
|
|
auto history_result = DocumentHistory::create(document_result.value(), 3);
|
|
PP_EXPECT(h, history_result.ok());
|
|
auto history = history_result.value();
|
|
|
|
for (std::uint32_t i = 0; i < 5U; ++i) {
|
|
auto next = history.current();
|
|
const auto added = next.add_frame(100U + i);
|
|
PP_EXPECT(h, added.ok());
|
|
PP_EXPECT(h, history.apply(next).ok());
|
|
PP_EXPECT(h, history.size() <= 3U);
|
|
}
|
|
|
|
PP_EXPECT(h, history.size() == 3U);
|
|
PP_EXPECT(h, history.current_index() == 2U);
|
|
PP_EXPECT(h, history.current().frames().size() == 6U);
|
|
|
|
PP_EXPECT(h, history.undo().ok());
|
|
PP_EXPECT(h, history.current().frames().size() == 5U);
|
|
PP_EXPECT(h, history.undo().ok());
|
|
PP_EXPECT(h, history.current().frames().size() == 4U);
|
|
const auto undo_evicted_entry = history.undo();
|
|
PP_EXPECT(h, !undo_evicted_entry.ok());
|
|
PP_EXPECT(h, undo_evicted_entry.code == StatusCode::out_of_range);
|
|
}
|
|
|
|
}
|
|
|
|
int main()
|
|
{
|
|
pp::tests::Harness harness;
|
|
harness.run("creates_document_with_default_layers", creates_document_with_default_layers);
|
|
harness.run("rejects_invalid_document_configs", rejects_invalid_document_configs);
|
|
harness.run("manages_layer_add_remove_and_active_index", manages_layer_add_remove_and_active_index);
|
|
harness.run("moves_layers_and_preserves_active_layer_identity", moves_layers_and_preserves_active_layer_identity);
|
|
harness.run("updates_layer_metadata", updates_layer_metadata);
|
|
harness.run("rejects_invalid_layer_metadata", rejects_invalid_layer_metadata);
|
|
harness.run("manages_animation_frames_and_duration", manages_animation_frames_and_duration);
|
|
harness.run("rejects_invalid_animation_frame_operations", rejects_invalid_animation_frame_operations);
|
|
harness.run("records_document_history_and_restores_snapshots", records_document_history_and_restores_snapshots);
|
|
harness.run("applying_after_undo_discards_redo_branch", applying_after_undo_discards_redo_branch);
|
|
harness.run("bounds_document_history_capacity", bounds_document_history_capacity);
|
|
return harness.finish();
|
|
}
|