Extract animation operation planning

This commit is contained in:
2026-06-03 10:32:06 +02:00
parent fdc1defaba
commit 4f0909f30c
8 changed files with 812 additions and 27 deletions

View File

@@ -318,6 +318,16 @@ add_test(NAME pp_app_core_document_recording_tests COMMAND pp_app_core_document_
set_tests_properties(pp_app_core_document_recording_tests PROPERTIES
LABELS "app;desktop-fast;fuzz")
add_executable(pp_app_core_document_animation_tests
app_core/document_animation_tests.cpp)
target_link_libraries(pp_app_core_document_animation_tests PRIVATE
pp_app_core
pp_test_harness)
add_test(NAME pp_app_core_document_animation_tests COMMAND pp_app_core_document_animation_tests)
set_tests_properties(pp_app_core_document_animation_tests PROPERTIES
LABELS "app;desktop-fast;fuzz")
add_executable(pp_app_core_document_layer_tests
app_core/document_layer_tests.cpp)
target_link_libraries(pp_app_core_document_layer_tests PRIVATE
@@ -742,6 +752,30 @@ if(TARGET pano_cli)
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_animation_operation_add_smoke
COMMAND pano_cli plan-animation-operation --kind add --frame-count 2 --current-frame 0)
set_tests_properties(pano_cli_plan_animation_operation_add_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"add-frame\".*\"selectedFrame\":2.*\"mutatesDocument\":true.*\"updatesCanvasAnimation\":true.*\"marksUnsaved\":true")
add_test(NAME pano_cli_plan_animation_operation_duration_floor_smoke
COMMAND pano_cli plan-animation-operation --kind duration --frame-count 2 --selected-frame 1 --current-duration 1 --delta -1)
set_tests_properties(pano_cli_plan_animation_operation_duration_floor_smoke PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"adjust-duration\".*\"selectedFrame\":1.*\"frameDuration\":1.*\"mutatesDocument\":false.*\"marksUnsaved\":false")
add_test(NAME pano_cli_plan_animation_operation_next_wrap_smoke
COMMAND pano_cli plan-animation-operation --kind next --total-duration 5 --current-frame 4)
set_tests_properties(pano_cli_plan_animation_operation_next_wrap_smoke PROPERTIES
LABELS "app;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"command\":\"plan-animation-operation\".*\"operation\":\"goto-next\".*\"currentFrame\":4.*\"targetFrame\":0.*\"updatesCanvasAnimation\":true")
add_test(NAME pano_cli_plan_animation_operation_rejects_remove_last_frame
COMMAND pano_cli plan-animation-operation --kind remove --frame-count 1 --selected-frame 0)
set_tests_properties(pano_cli_plan_animation_operation_rejects_remove_last_frame PROPERTIES
LABELS "app;integration;desktop-fast;fuzz"
WILL_FAIL TRUE)
add_test(NAME pano_cli_plan_share_file_unsaved_smoke
COMMAND pano_cli plan-share-file)
set_tests_properties(pano_cli_plan_share_file_unsaved_smoke PROPERTIES

View File

@@ -0,0 +1,133 @@
#include "app_core/document_animation.h"
#include "test_harness.h"
#include <limits>
#define PP_REQUIRE(harness, expression) \
do { \
const bool pp_require_ok = static_cast<bool>(expression); \
(harness).expect(pp_require_ok, #expression, __FILE__, __LINE__); \
if (!pp_require_ok) { \
return; \
} \
} while (false)
namespace {
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<int>::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<int>::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<int>::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 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));
}
} // 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("onion size updates canvas without document mutation", onion_size_updates_canvas_without_document_mutation);
return harness.finish();
}