#include "app_core/canvas_hotkey.h" #include "test_harness.h" #include namespace { class FakeCanvasHotkeyServices final : public pp::app::CanvasHotkeyServices { public: pp::foundation::Status execute_tool(const pp::app::CanvasToolPlan& plan) override { tool_calls += 1; last_tool_mode = plan.mode; return pp::foundation::Status::success(); } pp::foundation::Status execute_history(const pp::app::HistoryUiPlan& plan) override { history_calls += 1; last_history_operation = plan.operation; return pp::foundation::Status::success(); } void save_document(pp::app::DocumentSaveIntent intent) override { save_calls += 1; last_save_intent = intent; } void toggle_ui() override { toggle_ui_calls += 1; } void adjust_brush_size(float delta) override { brush_adjust_calls += 1; last_brush_delta = delta; } void show_cursor() override { show_cursor_calls += 1; } int tool_calls = 0; int history_calls = 0; int save_calls = 0; int toggle_ui_calls = 0; int brush_adjust_calls = 0; int show_cursor_calls = 0; pp::app::CanvasToolMode last_tool_mode = pp::app::CanvasToolMode::draw; pp::app::HistoryUiOperation last_history_operation = pp::app::HistoryUiOperation::undo; pp::app::DocumentSaveIntent last_save_intent = pp::app::DocumentSaveIntent::save; float last_brush_delta = 0.0F; }; pp::app::CanvasHotkeyState default_state() noexcept { pp::app::CanvasHotkeyState state; state.undo_count = 2; state.redo_count = 1; return state; } void e_key_toggles_erase_and_draw(pp::tests::Harness& harness) { const auto down = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_down, pp::app::CanvasHotkeyKey::e, default_state()); const auto up = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::e, default_state()); PP_EXPECT(harness, down); PP_EXPECT(harness, up); if (down) { PP_EXPECT(harness, down.value().action == pp::app::CanvasHotkeyAction::select_tool); PP_EXPECT(harness, down.value().tool.mode == pp::app::CanvasToolMode::erase); PP_EXPECT(harness, !down.value().no_op); } if (up) { PP_EXPECT(harness, up.value().action == pp::app::CanvasHotkeyAction::select_tool); PP_EXPECT(harness, up.value().tool.mode == pp::app::CanvasToolMode::draw); PP_EXPECT(harness, !up.value().no_op); } } void ctrl_z_and_shift_z_plan_history(pp::tests::Harness& harness) { auto state = default_state(); state.ctrl_down = true; const auto undo = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::z, state); state.shift_down = true; const auto redo = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::z, state); PP_EXPECT(harness, undo); PP_EXPECT(harness, redo); if (undo) { PP_EXPECT(harness, undo.value().action == pp::app::CanvasHotkeyAction::history); PP_EXPECT(harness, undo.value().history.operation == pp::app::HistoryUiOperation::undo); PP_EXPECT(harness, undo.value().history.invokes_undo); } if (redo) { PP_EXPECT(harness, redo.value().action == pp::app::CanvasHotkeyAction::history); PP_EXPECT(harness, redo.value().history.operation == pp::app::HistoryUiOperation::redo); PP_EXPECT(harness, redo.value().history.invokes_redo); } } void save_hotkeys_plan_document_intents(pp::tests::Harness& harness) { auto state = default_state(); state.ctrl_down = true; const auto save = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::s, state); state.shift_down = true; const auto save_version = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::s, state); PP_EXPECT(harness, save); PP_EXPECT(harness, save_version); if (save) { PP_EXPECT(harness, save.value().action == pp::app::CanvasHotkeyAction::save_document); PP_EXPECT(harness, save.value().save_intent == pp::app::DocumentSaveIntent::save); } if (save_version) { PP_EXPECT(harness, save_version.value().action == pp::app::CanvasHotkeyAction::save_document); PP_EXPECT( harness, save_version.value().save_intent == pp::app::DocumentSaveIntent::save_dirty_version); } } void app_and_brush_hotkeys_are_planned(pp::tests::Harness& harness) { auto state = default_state(); state.mouse_focused = true; const auto alt = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_down, pp::app::CanvasHotkeyKey::alt, state); const auto tab = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::tab, state); const auto smaller = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::bracket_left, state); const auto larger = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::bracket_right, state); PP_EXPECT(harness, alt); PP_EXPECT(harness, tab); PP_EXPECT(harness, smaller); PP_EXPECT(harness, larger); if (alt) { PP_EXPECT(harness, alt.value().action == pp::app::CanvasHotkeyAction::show_cursor); } if (tab) { PP_EXPECT(harness, tab.value().action == pp::app::CanvasHotkeyAction::toggle_ui); } if (smaller) { PP_EXPECT(harness, smaller.value().brush_size_delta < 0.0F); } if (larger) { PP_EXPECT(harness, larger.value().brush_size_delta > 0.0F); } } void touch_and_android_back_plan_undo(pp::tests::Harness& harness) { auto state = default_state(); const auto android = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_down, pp::app::CanvasHotkeyKey::android_back, state); state.touch_finger_count = 2; const auto tap = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::touch_tap, pp::app::CanvasHotkeyKey::other, state); PP_EXPECT(harness, android); PP_EXPECT(harness, tap); if (android) { PP_EXPECT(harness, android.value().action == pp::app::CanvasHotkeyAction::history); PP_EXPECT(harness, android.value().history.operation == pp::app::HistoryUiOperation::undo); } if (tap) { PP_EXPECT(harness, tap.value().action == pp::app::CanvasHotkeyAction::history); PP_EXPECT(harness, tap.value().history.operation == pp::app::HistoryUiOperation::undo); } } void planner_preserves_no_ops_and_rejects_bad_counts(pp::tests::Harness& harness) { auto state = default_state(); const auto no_ctrl_z = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::z, state); state.undo_count = -1; const auto bad_undo = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_down, pp::app::CanvasHotkeyKey::android_back, state); PP_EXPECT(harness, no_ctrl_z); if (no_ctrl_z) { PP_EXPECT(harness, no_ctrl_z.value().action == pp::app::CanvasHotkeyAction::none); PP_EXPECT(harness, no_ctrl_z.value().no_op); } PP_EXPECT(harness, !bad_undo); if (!bad_undo) { PP_EXPECT(harness, bad_undo.status().code == pp::foundation::StatusCode::out_of_range); } } void executor_dispatches_actions(pp::tests::Harness& harness) { FakeCanvasHotkeyServices services; auto state = default_state(); state.ctrl_down = true; const auto save = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::s, state); const auto undo = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::z, state); const auto erase = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_down, pp::app::CanvasHotkeyKey::e, state); const auto brush = pp::app::plan_canvas_hotkey( pp::app::CanvasHotkeyEvent::key_up, pp::app::CanvasHotkeyKey::bracket_right, state); PP_EXPECT(harness, save && undo && erase && brush); if (save && undo && erase && brush) { PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(save.value(), services).ok()); PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(undo.value(), services).ok()); PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(erase.value(), services).ok()); PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(brush.value(), services).ok()); } PP_EXPECT(harness, services.save_calls == 1); PP_EXPECT(harness, services.history_calls == 1); PP_EXPECT(harness, services.tool_calls == 1); PP_EXPECT(harness, services.brush_adjust_calls == 1); PP_EXPECT(harness, services.last_save_intent == pp::app::DocumentSaveIntent::save); PP_EXPECT(harness, services.last_history_operation == pp::app::HistoryUiOperation::undo); PP_EXPECT(harness, services.last_tool_mode == pp::app::CanvasToolMode::erase); PP_EXPECT(harness, std::fabs(services.last_brush_delta - 0.05F) < 0.001F); } void executor_rejects_malformed_brush_adjust(pp::tests::Harness& harness) { FakeCanvasHotkeyServices services; pp::app::CanvasHotkeyPlan malformed; malformed.action = pp::app::CanvasHotkeyAction::adjust_brush_size; malformed.no_op = false; const auto status = pp::app::execute_canvas_hotkey_plan(malformed, services); PP_EXPECT(harness, !status.ok()); PP_EXPECT(harness, status.code == pp::foundation::StatusCode::invalid_argument); PP_EXPECT(harness, services.brush_adjust_calls == 0); } } // namespace int main() { pp::tests::Harness harness; harness.run("e key toggles erase and draw", e_key_toggles_erase_and_draw); harness.run("ctrl z and shift z plan history", ctrl_z_and_shift_z_plan_history); harness.run("save hotkeys plan document intents", save_hotkeys_plan_document_intents); harness.run("app and brush hotkeys are planned", app_and_brush_hotkeys_are_planned); harness.run("touch and android back plan undo", touch_and_android_back_plan_undo); harness.run("planner preserves no ops and rejects bad counts", planner_preserves_no_ops_and_rejects_bad_counts); harness.run("executor dispatches actions", executor_dispatches_actions); harness.run("executor rejects malformed brush adjust", executor_rejects_malformed_brush_adjust); return harness.finish(); }