[PDF Ink Signatures] Add PdfInkUndoRedoModel
Add a class that models draw and erase commands. Based on the commands it recorded, the class processes undo and redo requests and calculates the commands that need to be applied in response. Bug: 335521182 Change-Id: Ie3f84ee84e401212e7179780dca2b6587740afe6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5590097 Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Andy Phan <andyphan@chromium.org> Reviewed-by: Alan Screen <awscreen@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Cr-Commit-Position: refs/heads/main@{#1310119}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
954f94c9b5
commit
bc09bdd0e5
@ -204,6 +204,8 @@ if (enable_pdf) {
|
||||
"ink_module.h",
|
||||
"pdf_ink_brush.cc",
|
||||
"pdf_ink_brush.h",
|
||||
"pdf_ink_undo_redo_model.cc",
|
||||
"pdf_ink_undo_redo_model.h",
|
||||
]
|
||||
|
||||
public_deps += [ "//pdf/ink" ]
|
||||
@ -449,7 +451,10 @@ if (enable_pdf) {
|
||||
]
|
||||
|
||||
if (enable_pdf_ink2) {
|
||||
sources += [ "ink_module_unittest.cc" ]
|
||||
sources += [
|
||||
"ink_module_unittest.cc",
|
||||
"pdf_ink_undo_redo_model_unittest.cc",
|
||||
]
|
||||
}
|
||||
|
||||
if (v8_use_external_startup_data) {
|
||||
|
252
pdf/pdf_ink_undo_redo_model.cc
Normal file
252
pdf/pdf_ink_undo_redo_model.cc
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "pdf/pdf_ink_undo_redo_model.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/check_op.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/types/strong_alias.h"
|
||||
#include "third_party/abseil-cpp/absl/types/variant.h"
|
||||
|
||||
namespace chrome_pdf {
|
||||
|
||||
namespace {
|
||||
|
||||
PdfInkUndoRedoModel::DrawCommands& GetModifiableDrawCommands(
|
||||
PdfInkUndoRedoModel::Commands& commands) {
|
||||
return absl::get<PdfInkUndoRedoModel::DrawCommands>(commands);
|
||||
}
|
||||
|
||||
PdfInkUndoRedoModel::EraseCommands& GetModifiableEraseCommands(
|
||||
PdfInkUndoRedoModel::Commands& commands) {
|
||||
return absl::get<PdfInkUndoRedoModel::EraseCommands>(commands);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PdfInkUndoRedoModel::PdfInkUndoRedoModel() = default;
|
||||
|
||||
PdfInkUndoRedoModel::~PdfInkUndoRedoModel() = default;
|
||||
|
||||
bool PdfInkUndoRedoModel::StartDraw() {
|
||||
return StartImpl<DrawCommands>();
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::Draw(size_t id) {
|
||||
CHECK(!commands_stack_.empty());
|
||||
|
||||
if (!IsAtTopOfStackWithGivenCommandType(CommandsType::kDraw)) {
|
||||
// Can only draw at top of the stack, and the entry there must be for
|
||||
// drawing.
|
||||
return false;
|
||||
}
|
||||
if (HasIdInDrawCommands(id)) {
|
||||
return false; // Failed invariant 3.
|
||||
}
|
||||
|
||||
// Invariant 4 holds if invariant 6 holds.
|
||||
CHECK(!HasIdInEraseCommands(id));
|
||||
GetModifiableDrawCommands(commands_stack_.back())->insert(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::FinishDraw() {
|
||||
CHECK(!commands_stack_.empty());
|
||||
|
||||
if (!IsAtTopOfStackWithGivenCommandType(CommandsType::kDraw)) {
|
||||
// Can only draw at top of the stack, and the entry there must be for
|
||||
// drawing.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& commands = commands_stack_.back();
|
||||
if (GetDrawCommands(commands)->empty()) {
|
||||
commands = absl::monostate(); // Reuse top of stack if empty.
|
||||
} else {
|
||||
// Otherwise push new item onto the stack.
|
||||
++stack_position_;
|
||||
commands_stack_.push_back(absl::monostate());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::StartErase() {
|
||||
return StartImpl<EraseCommands>();
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::Erase(size_t id) {
|
||||
CHECK(!commands_stack_.empty());
|
||||
|
||||
if (!IsAtTopOfStackWithGivenCommandType(CommandsType::kErase)) {
|
||||
// Can only erase at top of the stack, and the entry there must be for
|
||||
// erasing.
|
||||
return false;
|
||||
}
|
||||
if (!HasIdInDrawCommands(id)) {
|
||||
return false; // Failed invariant 6.
|
||||
}
|
||||
if (HasIdInEraseCommands(id)) {
|
||||
return false; // Failed invariant 5.
|
||||
}
|
||||
|
||||
GetModifiableEraseCommands(commands_stack_.back())->insert(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::FinishErase() {
|
||||
CHECK(!commands_stack_.empty());
|
||||
|
||||
if (!IsAtTopOfStackWithGivenCommandType(CommandsType::kErase)) {
|
||||
// Can only erase at top of the stack, and the entry there must be for
|
||||
// erasing.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& commands = commands_stack_.back();
|
||||
if (GetEraseCommands(commands)->empty()) {
|
||||
commands = absl::monostate(); // Reuse top of stack if empty.
|
||||
} else {
|
||||
// Otherwise push new item onto the stack.
|
||||
++stack_position_;
|
||||
commands_stack_.push_back(absl::monostate());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
PdfInkUndoRedoModel::Commands PdfInkUndoRedoModel::Undo() {
|
||||
CHECK(!commands_stack_.empty());
|
||||
CHECK_LT(stack_position_, commands_stack_.size());
|
||||
|
||||
if (stack_position_ == 0) {
|
||||
return absl::monostate(); // Already at bottom of stack.
|
||||
}
|
||||
|
||||
// Result is reverse of the recorded commands.
|
||||
--stack_position_;
|
||||
const auto& commands = commands_stack_[stack_position_];
|
||||
switch (GetCommandsType(commands)) {
|
||||
case CommandsType::kNone: {
|
||||
NOTREACHED_NORETURN(); // Invariant 2.
|
||||
}
|
||||
case CommandsType::kDraw: {
|
||||
EraseCommands result;
|
||||
for (size_t id : GetDrawCommands(commands).value()) {
|
||||
result->insert(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case CommandsType::kErase: {
|
||||
DrawCommands result;
|
||||
for (size_t id : GetEraseCommands(commands).value()) {
|
||||
result->insert(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PdfInkUndoRedoModel::Commands PdfInkUndoRedoModel::Redo() {
|
||||
CHECK(!commands_stack_.empty());
|
||||
CHECK_LT(stack_position_, commands_stack_.size());
|
||||
|
||||
if (stack_position_ == commands_stack_.size() - 1) {
|
||||
return absl::monostate(); // Already at top of stack.
|
||||
}
|
||||
|
||||
// Result is the recorded commands as-is.
|
||||
const auto& commands = commands_stack_[stack_position_];
|
||||
++stack_position_;
|
||||
switch (GetCommandsType(commands)) {
|
||||
case CommandsType::kNone: {
|
||||
NOTREACHED_NORETURN(); // Invariant 2.
|
||||
}
|
||||
case CommandsType::kDraw: {
|
||||
return GetDrawCommands(commands);
|
||||
}
|
||||
case CommandsType::kErase: {
|
||||
return GetEraseCommands(commands);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
PdfInkUndoRedoModel::CommandsType PdfInkUndoRedoModel::GetCommandsType(
|
||||
const Commands& commands) {
|
||||
if (absl::holds_alternative<absl::monostate>(commands)) {
|
||||
return CommandsType::kNone;
|
||||
}
|
||||
if (absl::holds_alternative<DrawCommands>(commands)) {
|
||||
return CommandsType::kDraw;
|
||||
}
|
||||
CHECK(absl::holds_alternative<EraseCommands>(commands));
|
||||
return CommandsType::kErase;
|
||||
}
|
||||
|
||||
// static
|
||||
const PdfInkUndoRedoModel::DrawCommands& PdfInkUndoRedoModel::GetDrawCommands(
|
||||
const Commands& commands) {
|
||||
return absl::get<DrawCommands>(commands);
|
||||
}
|
||||
|
||||
// static
|
||||
const PdfInkUndoRedoModel::EraseCommands& PdfInkUndoRedoModel::GetEraseCommands(
|
||||
const Commands& commands) {
|
||||
return absl::get<EraseCommands>(commands);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool PdfInkUndoRedoModel::StartImpl() {
|
||||
CHECK(!commands_stack_.empty());
|
||||
CHECK_LT(stack_position_, commands_stack_.size());
|
||||
|
||||
auto& commands = commands_stack_[stack_position_];
|
||||
const bool has_commands = GetCommandsType(commands) != CommandsType::kNone;
|
||||
if (stack_position_ == commands_stack_.size() - 1) {
|
||||
if (has_commands) {
|
||||
return false; // Cannot start when drawing/erasing already started.
|
||||
}
|
||||
} else {
|
||||
CHECK(has_commands); // Invariant 2.
|
||||
commands_stack_.resize(stack_position_ + 1); // Discard rest of stack.
|
||||
}
|
||||
|
||||
commands = T(); // Set the top of the stack to the appropriate command type.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::IsAtTopOfStackWithGivenCommandType(
|
||||
CommandsType type) const {
|
||||
if (stack_position_ != commands_stack_.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
return GetCommandsType(commands_stack_.back()) == type;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::HasIdInDrawCommands(size_t id) const {
|
||||
for (const auto& commands : commands_stack_) {
|
||||
if (GetCommandsType(commands) == CommandsType::kDraw &&
|
||||
GetDrawCommands(commands)->contains(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PdfInkUndoRedoModel::HasIdInEraseCommands(size_t id) const {
|
||||
for (const auto& commands : commands_stack_) {
|
||||
if (GetCommandsType(commands) == CommandsType::kErase &&
|
||||
GetEraseCommands(commands)->contains(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace chrome_pdf
|
118
pdf/pdf_ink_undo_redo_model.h
Normal file
118
pdf/pdf_ink_undo_redo_model.h
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef PDF_PDF_INK_UNDO_REDO_MODEL_H_
|
||||
#define PDF_PDF_INK_UNDO_REDO_MODEL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "base/types/strong_alias.h"
|
||||
#include "pdf/buildflags.h"
|
||||
#include "third_party/abseil-cpp/absl/types/variant.h"
|
||||
|
||||
static_assert(BUILDFLAG(ENABLE_PDF_INK2), "ENABLE_PDF_INK2 not set to true");
|
||||
|
||||
namespace chrome_pdf {
|
||||
|
||||
// Models draw and erase commands. Based on the recorded commands,
|
||||
// processes undo / redo requests and calculates what commands need to be
|
||||
// applied.
|
||||
class PdfInkUndoRedoModel {
|
||||
public:
|
||||
enum class CommandsType {
|
||||
kNone,
|
||||
kDraw,
|
||||
kErase,
|
||||
};
|
||||
|
||||
// Set of IDs to draw/erase.
|
||||
using DrawCommands =
|
||||
base::StrongAlias<class DrawCommandsTag, std::set<size_t>>;
|
||||
using EraseCommands =
|
||||
base::StrongAlias<class EraseCommandsTag, std::set<size_t>>;
|
||||
|
||||
using Commands = absl::variant<absl::monostate, DrawCommands, EraseCommands>;
|
||||
|
||||
PdfInkUndoRedoModel();
|
||||
PdfInkUndoRedoModel(const PdfInkUndoRedoModel&) = delete;
|
||||
PdfInkUndoRedoModel& operator=(const PdfInkUndoRedoModel&) = delete;
|
||||
~PdfInkUndoRedoModel();
|
||||
|
||||
// For all Draw / Erase methods:
|
||||
// - The expected usage is: 1 StartOp call, any number of Op calls, 1 FinishOp
|
||||
// call.
|
||||
// - Returns true on success. Returns false if any requirements are not met.
|
||||
// - Must not return false in production code. Returning false is only allowed
|
||||
// in tests to check failure modes without resorting to death tests.
|
||||
|
||||
// Starts recording draw commands. If the current commands stack position is
|
||||
// not at the top of the stack, then this discards all entries from the
|
||||
// current position to the top of the stack.
|
||||
// Must be called before Draw().
|
||||
// Must not be called while another draw/erase has been started.
|
||||
[[nodiscard]] bool StartDraw();
|
||||
// Records drawing a stroke identified by `id`.
|
||||
// Must be called between StartDraw() and FinishDraw().
|
||||
// `id` must not be on the commands stack.
|
||||
[[nodiscard]] bool Draw(size_t id);
|
||||
// Finishes recording draw commands and pushes a new element onto the stack.
|
||||
// Must be called after StartDraw().
|
||||
[[nodiscard]] bool FinishDraw();
|
||||
|
||||
// Starts recording erase commands. If the current commands stack position is
|
||||
// not at the top of the stack, then this discards all entries from the
|
||||
// current position to the top of the stack.
|
||||
// Must be called before Erase().
|
||||
// Must not be called while another draw/erase has been started.
|
||||
[[nodiscard]] bool StartErase();
|
||||
// Records erasing a stroke identified by `id`.
|
||||
// Must be called between StartErase() and FinishErase().
|
||||
// `id` must be in a `DrawCommands` on the commands stack.
|
||||
// `id` must not be in any `EraseCommands` on the commands stack.
|
||||
[[nodiscard]] bool Erase(size_t id);
|
||||
// Finishes recording erase commands and pushes a new element onto the stack.
|
||||
// Must be called after StartErase().
|
||||
[[nodiscard]] bool FinishErase();
|
||||
|
||||
// Returns the commands that needs to be applied to satisfy the undo / redo
|
||||
// request and moves the position in the commands stack without modifying the
|
||||
// commands themselves.
|
||||
Commands Undo();
|
||||
Commands Redo();
|
||||
|
||||
static CommandsType GetCommandsType(const Commands& commands);
|
||||
static const DrawCommands& GetDrawCommands(const Commands& commands);
|
||||
static const EraseCommands& GetEraseCommands(const Commands& commands);
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
bool StartImpl();
|
||||
|
||||
bool IsAtTopOfStackWithGivenCommandType(CommandsType type) const;
|
||||
bool HasIdInDrawCommands(size_t id) const;
|
||||
bool HasIdInEraseCommands(size_t id) const;
|
||||
|
||||
// Invariants:
|
||||
// (1) Never empty.
|
||||
// (2) The last element and only the last element can be `absl::monostate`.
|
||||
// (3) IDs used in `DrawCommands` elements are unique among all `DrawCommands`
|
||||
// elements.
|
||||
// (4) IDs added to a `DrawCommands` must not exist in any `EraseCommands`.
|
||||
// (5) IDs used in `EraseCommands` elements are unique among all
|
||||
// `EraseCommands` elements.
|
||||
// (6) IDs added to a `EraseCommands` must exist in some `DrawCommands`
|
||||
// element.
|
||||
std::vector<Commands> commands_stack_ = {absl::monostate()};
|
||||
|
||||
// Invariants:
|
||||
// (7) Always less than the size of `commands_stack_`.
|
||||
size_t stack_position_ = 0;
|
||||
};
|
||||
|
||||
} // namespace chrome_pdf
|
||||
|
||||
#endif // PDF_PDF_INK_UNDO_REDO_MODEL_H_
|
355
pdf/pdf_ink_undo_redo_model_unittest.cc
Normal file
355
pdf/pdf_ink_undo_redo_model_unittest.cc
Normal file
@ -0,0 +1,355 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "pdf/pdf_ink_undo_redo_model.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
using testing::ElementsAreArray;
|
||||
|
||||
namespace chrome_pdf {
|
||||
|
||||
using enum PdfInkUndoRedoModel::CommandsType;
|
||||
|
||||
namespace {
|
||||
|
||||
// Shorthand for test setup that is expected to succeed.
|
||||
void DoDrawCommandsCycle(PdfInkUndoRedoModel& undo_redo,
|
||||
const std::set<size_t>& ids) {
|
||||
ASSERT_TRUE(undo_redo.StartDraw());
|
||||
for (size_t id : ids) {
|
||||
ASSERT_TRUE(undo_redo.Draw(id));
|
||||
}
|
||||
ASSERT_TRUE(undo_redo.FinishDraw());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionDoubleStartDraw) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_TRUE(undo_redo.StartDraw());
|
||||
ASSERT_FALSE(undo_redo.StartDraw());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousDraw) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_FALSE(undo_redo.Draw(1));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinishDraw) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_FALSE(undo_redo.FinishDraw());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionEraseWhileDrawing) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_TRUE(undo_redo.StartDraw());
|
||||
ASSERT_TRUE(undo_redo.Draw(1));
|
||||
|
||||
ASSERT_FALSE(undo_redo.StartErase());
|
||||
ASSERT_FALSE(undo_redo.Erase(1));
|
||||
ASSERT_FALSE(undo_redo.FinishErase());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionDoubleStartErase) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_FALSE(undo_redo.StartErase());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousErase) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_FALSE(undo_redo.Erase(1));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinishErase) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_FALSE(undo_redo.FinishErase());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionDrawWhileErasing) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1});
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
|
||||
ASSERT_FALSE(undo_redo.StartDraw());
|
||||
ASSERT_FALSE(undo_redo.Draw(2));
|
||||
ASSERT_FALSE(undo_redo.FinishDraw());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousDrawAfterUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {4});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
ASSERT_FALSE(undo_redo.Draw(1));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinishDrawAfterUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {4});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
ASSERT_FALSE(undo_redo.FinishDraw());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousEraseAfterUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {4});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
ASSERT_FALSE(undo_redo.Erase(4));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinishEraseAfterUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {4});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
ASSERT_FALSE(undo_redo.FinishErase());
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionEraseUnknownId) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1});
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_FALSE(undo_redo.Erase(3));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, BadActionEraseTwice) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {0});
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_TRUE(undo_redo.Erase(0));
|
||||
ASSERT_FALSE(undo_redo.Erase(0));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, Empty) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, EmptyDraw) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, EmptyErase) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_TRUE(undo_redo.FinishErase());
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, DrawCannotRepeatId) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1, 2, 3});
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartDraw());
|
||||
ASSERT_FALSE(undo_redo.Draw(1));
|
||||
ASSERT_FALSE(undo_redo.Draw(3));
|
||||
|
||||
ASSERT_TRUE(undo_redo.Draw(97));
|
||||
ASSERT_TRUE(undo_redo.Draw(99));
|
||||
ASSERT_TRUE(undo_redo.Draw(98));
|
||||
|
||||
ASSERT_FALSE(undo_redo.Draw(1));
|
||||
ASSERT_FALSE(undo_redo.Draw(98));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, DrawCanRepeatIdAfterUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1, 2, 3});
|
||||
DoDrawCommandsCycle(undo_redo, {97, 98, 99});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({97, 98, 99}));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({1, 2, 3}));
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartDraw());
|
||||
ASSERT_TRUE(undo_redo.Draw(2));
|
||||
ASSERT_TRUE(undo_redo.Draw(98));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, DrawUndoRedo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1, 2, 3});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({1, 2, 3}));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({1, 2, 3}));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
EXPECT_EQ(kNone, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, DrawDrawEraseUndoRedo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {1, 2, 3});
|
||||
DoDrawCommandsCycle(undo_redo, {4});
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_TRUE(undo_redo.Erase(1));
|
||||
ASSERT_TRUE(undo_redo.Erase(4));
|
||||
ASSERT_TRUE(undo_redo.FinishErase());
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({1, 4}));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({1, 2, 3}));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({1, 2, 3}));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({4}));
|
||||
|
||||
commands = undo_redo.Redo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({1, 4}));
|
||||
}
|
||||
|
||||
TEST(PdfInkUndoRedoModelTest, DrawDrawUndoEraseUndo) {
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
DoDrawCommandsCycle(undo_redo, {5});
|
||||
DoDrawCommandsCycle(undo_redo, {4, 8});
|
||||
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({4, 8}));
|
||||
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_TRUE(undo_redo.Erase(5));
|
||||
ASSERT_TRUE(undo_redo.FinishErase());
|
||||
|
||||
commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({5}));
|
||||
}
|
||||
|
||||
// TODO(crbug.com/335521182): Figure out why this times out on bots and enable.
|
||||
TEST(PdfInkUndoRedoModelTest, DISABLED_Stress) {
|
||||
constexpr size_t kCycles = 10000;
|
||||
PdfInkUndoRedoModel undo_redo;
|
||||
size_t id = 0;
|
||||
for (size_t i = 0; i < kCycles; ++i) {
|
||||
DoDrawCommandsCycle(undo_redo, {id, id + 1});
|
||||
id += 2;
|
||||
}
|
||||
|
||||
ASSERT_EQ(2 * kCycles, id);
|
||||
for (size_t i = 0; i < kCycles; ++i) {
|
||||
ASSERT_TRUE(undo_redo.StartErase());
|
||||
ASSERT_TRUE(undo_redo.Erase(--id));
|
||||
ASSERT_TRUE(undo_redo.Erase(--id));
|
||||
ASSERT_TRUE(undo_redo.FinishErase());
|
||||
}
|
||||
|
||||
ASSERT_EQ(0u, id);
|
||||
for (size_t i = 0; i < kCycles; ++i) {
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kDraw, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetDrawCommands(commands).value(),
|
||||
ElementsAreArray({id, id + 1}));
|
||||
id += 2;
|
||||
}
|
||||
|
||||
ASSERT_EQ(2 * kCycles, id);
|
||||
for (size_t i = 0; i < kCycles; ++i) {
|
||||
id -= 2;
|
||||
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
|
||||
ASSERT_EQ(kErase, PdfInkUndoRedoModel::GetCommandsType(commands));
|
||||
EXPECT_THAT(PdfInkUndoRedoModel::GetEraseCommands(commands).value(),
|
||||
ElementsAreArray({id, id + 1}));
|
||||
}
|
||||
|
||||
DoDrawCommandsCycle(undo_redo, {0});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace chrome_pdf
|
Reference in New Issue
Block a user