#include "app_core/document_animation.h" #include "test_harness.h" #include #include #define PP_REQUIRE(harness, expression) \ do { \ const bool pp_require_ok = static_cast(expression); \ (harness).expect(pp_require_ok, #expression, __FILE__, __LINE__); \ if (!pp_require_ok) { \ return; \ } \ } while (false) namespace { class FakeDocumentAnimationServices final : public pp::app::DocumentAnimationServices { public: void add_frame() override { adds += 1; call_order += "add;"; } void duplicate_frame(int selected_frame) override { duplicates += 1; last_selected_frame = selected_frame; call_order += "duplicate;"; } void remove_frame(int selected_frame, int target_frame) override { removes += 1; last_selected_frame = selected_frame; last_target_frame = target_frame; call_order += "remove;"; } void set_frame_duration(int selected_frame, int duration) override { duration_sets += 1; last_selected_frame = selected_frame; last_duration = duration; call_order += "duration;"; } int move_frame(int selected_frame, int move_offset) override { moves += 1; last_selected_frame = selected_frame; last_move_offset = move_offset; call_order += "move;"; return move_result; } void select_frame(std::uint32_t layer_id, int layer_index, int selected_frame) override { frame_selects += 1; last_layer_id = layer_id; last_layer_index = layer_index; last_selected_frame = selected_frame; call_order += "select-frame;"; } void select_layer(int layer_index) override { layer_selects += 1; last_layer_index = layer_index; call_order += "select-layer;"; } void goto_frame(int target_frame) override { gotos += 1; last_target_frame = target_frame; call_order += "goto;"; } void set_timeline_frame(int target_frame) override { timeline_sets += 1; last_timeline_frame = target_frame; call_order += "timeline;"; } void set_onion_size(int onion_size) override { onion_sets += 1; last_onion_size = onion_size; call_order += "onion;"; } void update_canvas_animation() override { canvas_updates += 1; call_order += "update;"; } void update_frame_status() override { frame_status_updates += 1; call_order += "frame-status;"; } void reload_animation_layers() override { reloads += 1; call_order += "reload;"; } void mark_unsaved() override { unsaved_marks += 1; call_order += "unsaved;"; } int adds = 0; int duplicates = 0; int removes = 0; int duration_sets = 0; int moves = 0; int frame_selects = 0; int layer_selects = 0; int gotos = 0; int timeline_sets = 0; int onion_sets = 0; int canvas_updates = 0; int frame_status_updates = 0; int reloads = 0; int unsaved_marks = 0; int last_selected_frame = -1; int last_target_frame = -1; int last_timeline_frame = -1; int last_duration = -1; int last_move_offset = 0; int last_onion_size = -1; int last_layer_index = -1; std::uint32_t last_layer_id = 0; int move_result = 0; std::string call_order; }; void add_duplicate_and_remove_validate_frame_bounds(pp::tests::Harness& harness) { const auto add = pp::app::plan_animation_add_frame(2, 0); PP_REQUIRE(harness, add); PP_EXPECT(harness, add.value().operation == pp::app::DocumentAnimationOperation::add_frame); PP_EXPECT(harness, add.value().selected_frame == 2); PP_EXPECT(harness, add.value().mutates_document); PP_EXPECT(harness, add.value().reloads_animation_layers); PP_EXPECT(harness, add.value().updates_canvas_animation); PP_EXPECT(harness, add.value().marks_unsaved); const auto duplicate = pp::app::plan_animation_duplicate_frame(3, 1); PP_REQUIRE(harness, duplicate); PP_EXPECT(harness, duplicate.value().operation == pp::app::DocumentAnimationOperation::duplicate_frame); PP_EXPECT(harness, duplicate.value().target_frame == 2); PP_EXPECT(harness, duplicate.value().requires_selected_frame); const auto remove = pp::app::plan_animation_remove_frame(3, 2); PP_REQUIRE(harness, remove); PP_EXPECT(harness, remove.value().operation == pp::app::DocumentAnimationOperation::remove_frame); PP_EXPECT(harness, remove.value().target_frame == 1); PP_EXPECT(harness, !pp::app::plan_animation_add_frame(0, 0)); PP_EXPECT(harness, !pp::app::plan_animation_add_frame(1, -1)); PP_EXPECT(harness, !pp::app::plan_animation_duplicate_frame(2, 2)); PP_EXPECT(harness, !pp::app::plan_animation_remove_frame(1, 0)); } void duration_plans_clamp_floor_and_reject_overflow(pp::tests::Harness& harness) { const auto up = pp::app::plan_animation_adjust_duration(2, 1, 4, 1); PP_REQUIRE(harness, up); PP_EXPECT(harness, up.value().operation == pp::app::DocumentAnimationOperation::adjust_duration); PP_EXPECT(harness, up.value().frame_duration == 5); PP_EXPECT(harness, up.value().mutates_document); const auto down = pp::app::plan_animation_adjust_duration(2, 1, 1, -1); PP_REQUIRE(harness, down); PP_EXPECT(harness, down.value().frame_duration == 1); PP_EXPECT(harness, !down.value().mutates_document); PP_EXPECT(harness, !down.value().marks_unsaved); PP_EXPECT(harness, !pp::app::plan_animation_adjust_duration(2, 1, 0, 1)); PP_EXPECT(harness, !pp::app::plan_animation_adjust_duration(2, 1, 2, 0)); PP_EXPECT( harness, !pp::app::plan_animation_adjust_duration(2, 1, std::numeric_limits::max(), 1)); } void move_and_timeline_plans_handle_edges(pp::tests::Harness& harness) { const auto left_edge = pp::app::plan_animation_move_frame(3, 0, -1); PP_REQUIRE(harness, left_edge); PP_EXPECT(harness, left_edge.value().operation == pp::app::DocumentAnimationOperation::move_frame); PP_EXPECT(harness, left_edge.value().target_frame == 0); PP_EXPECT(harness, !left_edge.value().mutates_document); PP_EXPECT(harness, left_edge.value().reloads_animation_layers); const auto right = pp::app::plan_animation_move_frame(3, 1, 1); PP_REQUIRE(harness, right); PP_EXPECT(harness, right.value().target_frame == 2); PP_EXPECT(harness, right.value().mutates_document); const auto huge_left = pp::app::plan_animation_move_frame(3, 1, std::numeric_limits::min()); PP_REQUIRE(harness, huge_left); PP_EXPECT(harness, huge_left.value().target_frame == 0); const auto huge_right = pp::app::plan_animation_move_frame(3, 1, std::numeric_limits::max()); PP_REQUIRE(harness, huge_right); PP_EXPECT(harness, huge_right.value().target_frame == 2); const auto go = pp::app::plan_animation_goto_frame(5, 3); PP_REQUIRE(harness, go); PP_EXPECT(harness, go.value().operation == pp::app::DocumentAnimationOperation::goto_frame); PP_EXPECT(harness, go.value().target_frame == 3); PP_EXPECT(harness, !go.value().mutates_document); PP_EXPECT(harness, go.value().updates_canvas_animation); const auto next = pp::app::plan_animation_step_frame(5, 4, 1); PP_REQUIRE(harness, next); PP_EXPECT(harness, next.value().operation == pp::app::DocumentAnimationOperation::goto_next); PP_EXPECT(harness, next.value().target_frame == 0); const auto prev = pp::app::plan_animation_step_frame(5, 0, -1); PP_REQUIRE(harness, prev); PP_EXPECT(harness, prev.value().operation == pp::app::DocumentAnimationOperation::goto_previous); PP_EXPECT(harness, prev.value().target_frame == 4); PP_EXPECT(harness, !pp::app::plan_animation_move_frame(3, 1, 0)); PP_EXPECT(harness, !pp::app::plan_animation_goto_frame(5, 5)); PP_EXPECT(harness, !pp::app::plan_animation_step_frame(0, 0, 1)); } void frame_selection_and_playback_plans_keep_ui_refresh_scoped(pp::tests::Harness& harness) { const auto select = pp::app::plan_animation_select_frame(3, 2, 42, 1); PP_REQUIRE(harness, select); PP_EXPECT(harness, select.value().operation == pp::app::DocumentAnimationOperation::select_frame); PP_EXPECT(harness, select.value().layer_index == 2); PP_EXPECT(harness, select.value().layer_id == 42); PP_EXPECT(harness, select.value().selected_frame == 1); PP_EXPECT(harness, select.value().target_frame == 1); PP_EXPECT(harness, select.value().updates_canvas_animation); PP_EXPECT(harness, !select.value().reloads_animation_layers); PP_EXPECT(harness, !select.value().mutates_document); const auto playback = pp::app::plan_animation_playback_step(4, 3, 1); PP_REQUIRE(harness, playback); PP_EXPECT(harness, playback.value().operation == pp::app::DocumentAnimationOperation::playback_step); PP_EXPECT(harness, playback.value().target_frame == 0); PP_EXPECT(harness, playback.value().move_offset == 1); PP_EXPECT(harness, playback.value().updates_canvas_animation); PP_EXPECT(harness, !playback.value().reloads_animation_layers); PP_EXPECT(harness, !playback.value().mutates_document); PP_EXPECT(harness, !pp::app::plan_animation_select_frame(2, -1, 42, 0)); PP_EXPECT(harness, !pp::app::plan_animation_select_frame(2, 0, 42, 2)); PP_EXPECT(harness, !pp::app::plan_animation_playback_step(0, 0, 1)); PP_EXPECT(harness, !pp::app::plan_animation_playback_step(3, 0, 0)); } void onion_size_updates_canvas_without_document_mutation(pp::tests::Harness& harness) { const auto plan = pp::app::plan_animation_onion_size(2); PP_REQUIRE(harness, plan); PP_EXPECT(harness, plan.value().operation == pp::app::DocumentAnimationOperation::set_onion_size); PP_EXPECT(harness, plan.value().onion_size == 2); PP_EXPECT(harness, plan.value().updates_canvas_animation); PP_EXPECT(harness, !plan.value().mutates_document); PP_EXPECT(harness, !plan.value().marks_unsaved); PP_EXPECT(harness, !pp::app::plan_animation_onion_size(-1)); } void executor_dispatches_mutating_frame_operations(pp::tests::Harness& harness) { FakeDocumentAnimationServices services; const auto add = pp::app::plan_animation_add_frame(2, 0); PP_REQUIRE(harness, add); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(add.value(), services).ok()); const auto duplicate = pp::app::plan_animation_duplicate_frame(3, 1); PP_REQUIRE(harness, duplicate); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duplicate.value(), services).ok()); const auto remove = pp::app::plan_animation_remove_frame(3, 2); PP_REQUIRE(harness, remove); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(remove.value(), services).ok()); PP_EXPECT(harness, services.adds == 1); PP_EXPECT(harness, services.duplicates == 1); PP_EXPECT(harness, services.removes == 1); PP_EXPECT(harness, services.unsaved_marks == 3); PP_EXPECT(harness, services.canvas_updates == 2); PP_EXPECT(harness, services.gotos == 1); PP_EXPECT(harness, services.reloads == 3); PP_EXPECT(harness, services.last_selected_frame == 2); PP_EXPECT(harness, services.last_target_frame == 1); PP_EXPECT( harness, services.call_order == "add;unsaved;update;reload;duplicate;unsaved;update;reload;remove;unsaved;goto;reload;"); } void executor_dispatches_timeline_and_parameter_operations(pp::tests::Harness& harness) { FakeDocumentAnimationServices services; services.move_result = 2; const auto duration = pp::app::plan_animation_adjust_duration(3, 1, 4, 1); PP_REQUIRE(harness, duration); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duration.value(), services).ok()); const auto duration_noop = pp::app::plan_animation_adjust_duration(3, 1, 1, -1); PP_REQUIRE(harness, duration_noop); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(duration_noop.value(), services).ok()); const auto move = pp::app::plan_animation_move_frame(3, 1, 1); PP_REQUIRE(harness, move); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(move.value(), services).ok()); const auto next = pp::app::plan_animation_step_frame(5, 4, 1); PP_REQUIRE(harness, next); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(next.value(), services).ok()); const auto onion = pp::app::plan_animation_onion_size(3); PP_REQUIRE(harness, onion); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(onion.value(), services).ok()); const auto select = pp::app::plan_animation_select_frame(3, 2, 42, 1); PP_REQUIRE(harness, select); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(select.value(), services).ok()); const auto playback = pp::app::plan_animation_playback_step(5, 4, 1); PP_REQUIRE(harness, playback); PP_EXPECT(harness, pp::app::execute_animation_operation_plan(playback.value(), services).ok()); PP_EXPECT(harness, services.duration_sets == 1); PP_EXPECT(harness, services.last_duration == 5); PP_EXPECT(harness, services.moves == 1); PP_EXPECT(harness, services.last_move_offset == 1); PP_EXPECT(harness, services.frame_selects == 1); PP_EXPECT(harness, services.layer_selects == 1); PP_EXPECT(harness, services.gotos == 4); PP_EXPECT(harness, services.timeline_sets == 1); PP_EXPECT(harness, services.last_target_frame == 0); PP_EXPECT(harness, services.last_timeline_frame == 0); PP_EXPECT(harness, services.last_layer_index == 2); PP_EXPECT(harness, services.last_layer_id == 42); PP_EXPECT(harness, services.onion_sets == 1); PP_EXPECT(harness, services.last_onion_size == 3); PP_EXPECT(harness, services.unsaved_marks == 2); PP_EXPECT(harness, services.canvas_updates == 2); PP_EXPECT(harness, services.frame_status_updates == 1); PP_EXPECT(harness, services.reloads == 3); PP_EXPECT( harness, services.call_order == "duration;unsaved;update;reload;move;unsaved;goto;reload;goto;reload;onion;update;" "select-frame;goto;select-layer;goto;timeline;frame-status;"); } void executor_rejects_malformed_animation_plans(pp::tests::Harness& harness) { FakeDocumentAnimationServices services; auto add = pp::app::plan_animation_add_frame(2, 0).value(); add.marks_unsaved = false; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(add, services).ok()); auto remove = pp::app::plan_animation_remove_frame(2, 1).value(); remove.frame_count = 1; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(remove, services).ok()); auto duration = pp::app::plan_animation_adjust_duration(2, 1, 4, 1).value(); duration.frame_duration = 0; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(duration, services).ok()); auto move = pp::app::plan_animation_move_frame(3, 1, 1).value(); move.move_offset = 0; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(move, services).ok()); auto go = pp::app::plan_animation_goto_frame(3, 1).value(); go.target_frame = 3; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(go, services).ok()); auto select = pp::app::plan_animation_select_frame(2, 0, 42, 1).value(); select.layer_index = -1; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(select, services).ok()); auto playback = pp::app::plan_animation_playback_step(3, 1, 1).value(); playback.move_offset = 0; PP_EXPECT(harness, !pp::app::execute_animation_operation_plan(playback, services).ok()); PP_EXPECT(harness, services.adds == 0); PP_EXPECT(harness, services.duration_sets == 0); PP_EXPECT(harness, services.gotos == 0); PP_EXPECT(harness, services.call_order.empty()); } } // namespace int main() { pp::tests::Harness harness; harness.run("add duplicate and remove validate frame bounds", add_duplicate_and_remove_validate_frame_bounds); harness.run("duration plans clamp floor and reject overflow", duration_plans_clamp_floor_and_reject_overflow); harness.run("move and timeline plans handle edges", move_and_timeline_plans_handle_edges); harness.run("frame selection and playback plans keep ui refresh scoped", frame_selection_and_playback_plans_keep_ui_refresh_scoped); harness.run("onion size updates canvas without document mutation", onion_size_updates_canvas_without_document_mutation); harness.run("executor dispatches mutating frame operations", executor_dispatches_mutating_frame_operations); harness.run("executor dispatches timeline and parameter operations", executor_dispatches_timeline_and_parameter_operations); harness.run("executor rejects malformed animation plans", executor_rejects_malformed_animation_plans); return harness.finish(); }