blob: 18e97c513110d5659e8dff796898650970aa17e7 [file]
// 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 <numeric>
#include <optional>
#include <set>
#include <variant>
#include "base/cfi_buildflags.h"
#include "base/types/expected.h"
#include "pdf/pdf_ink_ids.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
using testing::ElementsAreArray;
using testing::Optional;
namespace chrome_pdf {
namespace {
// InkStrokeId modification operators needed only for tests.
InkStrokeId& operator--(InkStrokeId& id) {
--id.value();
return id;
}
InkStrokeId operator+(const InkStrokeId& id, int amount) {
return InkStrokeId(id.value() + amount);
}
InkStrokeId& operator+=(InkStrokeId& id, int amount) {
id.value() += amount;
return id;
}
InkStrokeId& operator-=(InkStrokeId& id, int amount) {
id.value() -= amount;
return id;
}
// Shorthand for test setup that is expected to succeed.
void DoAddCommandsCycle(PdfInkUndoRedoModel& undo_redo,
const std::set<IdType>& ids) {
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
for (IdType id : ids) {
ASSERT_TRUE(undo_redo.Add(id));
}
ASSERT_TRUE(undo_redo.Finish());
}
TEST(PdfInkUndoRedoModelTest, BadActionDoubleStart) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_FALSE(undo_redo.Start().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousAdd) {
PdfInkUndoRedoModel undo_redo;
ASSERT_FALSE(undo_redo.Add(InkStrokeId(1)));
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinish) {
PdfInkUndoRedoModel undo_redo;
ASSERT_FALSE(undo_redo.Finish());
}
TEST(PdfInkUndoRedoModelTest, BadActionAddModeledShape) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_FALSE(undo_redo.Add(InkModeledShapeId(1)));
}
TEST(PdfInkUndoRedoModelTest, BadActionAddLoadedTextId) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_FALSE(undo_redo.Add(InkLoadedTextId(1)));
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousRemove) {
PdfInkUndoRedoModel undo_redo;
ASSERT_FALSE(undo_redo.Remove(InkStrokeId(1)));
ASSERT_FALSE(undo_redo.Remove(InkModeledShapeId(2)));
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousAddAfterUndo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(4)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({InkStrokeId(4)}));
ASSERT_FALSE(undo_redo.Add(InkStrokeId(1)));
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousFinishAfterUndo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(4)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({InkStrokeId(4)}));
ASSERT_FALSE(undo_redo.Finish());
}
TEST(PdfInkUndoRedoModelTest, BadActionSpuriousRemoveAfterUndo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(4)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({InkStrokeId(4)}));
ASSERT_FALSE(undo_redo.Remove(InkStrokeId(4)));
ASSERT_FALSE(undo_redo.Remove(InkModeledShapeId(9)));
}
TEST(PdfInkUndoRedoModelTest, BadActionRemoveUnknownStrokeId) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(1)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_FALSE(undo_redo.Remove(InkStrokeId(3)));
}
TEST(PdfInkUndoRedoModelTest, BadActionRemoveUnknownTextId) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_FALSE(undo_redo.Remove(InkTextId(3)));
}
TEST(PdfInkUndoRedoModelTest, BadActionRemoveTwice) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(0)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(0)));
ASSERT_FALSE(undo_redo.Remove(InkStrokeId(0)));
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(0)));
ASSERT_FALSE(undo_redo.Remove(InkModeledShapeId(0)));
}
TEST(PdfInkUndoRedoModelTest, BadActionStartAddRemove) {
PdfInkUndoRedoModel undo_redo;
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Add(InkStrokeId(0)));
ASSERT_FALSE(undo_redo.Remove(InkStrokeId(0)));
}
TEST(PdfInkUndoRedoModelTest, Empty) {
PdfInkUndoRedoModel undo_redo;
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
}
TEST(PdfInkUndoRedoModelTest, EmptyAdd) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
}
TEST(PdfInkUndoRedoModelTest, EmptyRemove) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
}
TEST(PdfInkUndoRedoModelTest, AddEnforcesIncreasingOrder) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo,
{InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
// Cannot add ID that is already on the stack.
ASSERT_FALSE(undo_redo.Add(InkStrokeId(1)));
ASSERT_FALSE(undo_redo.Add(InkStrokeId(3)));
// Can add larger IDs.
ASSERT_TRUE(undo_redo.Add(InkStrokeId(97)));
ASSERT_TRUE(undo_redo.Add(InkStrokeId(99)));
// Cannot add IDs that are not strictly increasing.
ASSERT_FALSE(undo_redo.Add(InkStrokeId(99)));
ASSERT_FALSE(undo_redo.Add(InkStrokeId(98)));
ASSERT_FALSE(undo_redo.Add(InkStrokeId(1)));
}
TEST(PdfInkUndoRedoModelTest, AddCanRepeatIdAfterUndo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo,
{InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)});
DoAddCommandsCycle(undo_redo,
{InkStrokeId(97), InkStrokeId(98), InkStrokeId(99)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(
commands.removes,
ElementsAreArray({InkStrokeId(97), InkStrokeId(98), InkStrokeId(99)}));
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(
commands.removes,
ElementsAreArray({InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)}));
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_THAT(lowest_discard.value(), Optional(InkStrokeId(1)));
ASSERT_TRUE(undo_redo.Add(InkStrokeId(2)));
ASSERT_TRUE(undo_redo.Add(InkStrokeId(98)));
}
TEST(PdfInkUndoRedoModelTest, AddUndoRedo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo,
{InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(
commands.removes,
ElementsAreArray({InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)}));
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
commands = undo_redo.Redo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({InkStrokeId(1), InkStrokeId(2),
InkStrokeId(3)}));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_TRUE(commands.removes.empty());
}
TEST(PdfInkUndoRedoModelTest, AddAddRemoveUndoRedo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo,
{InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)});
DoAddCommandsCycle(undo_redo, {InkStrokeId(4)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(1)));
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(4)));
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds,
ElementsAreArray({InkStrokeId(1), InkStrokeId(4)}));
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({InkStrokeId(4)}));
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(
commands.removes,
ElementsAreArray({InkStrokeId(1), InkStrokeId(2), InkStrokeId(3)}));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({InkStrokeId(1), InkStrokeId(2),
InkStrokeId(3)}));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({InkStrokeId(4)}));
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({InkStrokeId(4)}));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({InkStrokeId(4)}));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes,
ElementsAreArray({InkStrokeId(1), InkStrokeId(4)}));
}
TEST(PdfInkUndoRedoModelTest, AddAddUndoRemoveUndo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(5)});
DoAddCommandsCycle(undo_redo, {InkStrokeId(6), InkStrokeId(8)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes,
ElementsAreArray({InkStrokeId(6), InkStrokeId(8)}));
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_THAT(lowest_discard.value(), Optional(InkStrokeId(6)));
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(5)));
ASSERT_TRUE(undo_redo.Finish());
commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({InkStrokeId(5)}));
}
TEST(PdfInkUndoRedoModelTest, RemoveShapesUndoRedo) {
PdfInkUndoRedoModel undo_redo;
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(0)));
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(1)));
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds,
ElementsAre(InkModeledShapeId(0), InkModeledShapeId(1)));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes,
ElementsAre(InkModeledShapeId(0), InkModeledShapeId(1)));
}
TEST(PdfInkUndoRedoModelTest, AddAddRemoveStrokesAndShapesUndoRedo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(5)});
DoAddCommandsCycle(undo_redo, {InkStrokeId(6), InkStrokeId(8)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(0)));
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(6)));
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(1)));
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAre(InkModeledShapeId(0),
InkModeledShapeId(1), InkStrokeId(6)));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(
commands.removes,
ElementsAre(InkModeledShapeId(0), InkModeledShapeId(1), InkStrokeId(6)));
}
TEST(PdfInkUndoRedoModelTest, AddRemoveAddUndoUndoStart) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(1)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(1)));
ASSERT_TRUE(undo_redo.Finish());
DoAddCommandsCycle(undo_redo, {InkStrokeId(2)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_FALSE(commands.removes.empty());
commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_FALSE(commands.adds.empty());
// Discarded commands should be Remove(1), Add(2). The lowest discarded stroke
// ID is 2.
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
EXPECT_THAT(lowest_discard.value(), Optional(InkStrokeId(2)));
}
TEST(PdfInkUndoRedoModelTest, AddUndoStrokeAndText) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(0)});
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_FALSE(commands.removes.empty());
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_TRUE(lowest_discard.value().has_value());
IdType lowest_discard_id = lowest_discard.value().value();
ASSERT_TRUE(std::holds_alternative<InkTextId>(lowest_discard_id));
ASSERT_EQ(InkTextId(1), std::get<InkTextId>(lowest_discard_id));
ASSERT_TRUE(undo_redo.Finish());
commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_FALSE(commands.removes.empty());
lowest_discard = undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_TRUE(lowest_discard.value().has_value());
lowest_discard_id = lowest_discard.value().value();
ASSERT_TRUE(std::holds_alternative<InkStrokeId>(lowest_discard_id));
ASSERT_EQ(InkStrokeId(0), std::get<InkStrokeId>(lowest_discard_id));
}
TEST(PdfInkUndoRedoModelTest, AddRemoveAllTypesUndoRedo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(5)});
DoAddCommandsCycle(undo_redo, {InkTextId(6)});
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkModeledShapeId(5)));
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(5)));
ASSERT_TRUE(undo_redo.Remove(InkTextId(6)));
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds,
ElementsAre(InkStrokeId(5), InkModeledShapeId(5), InkTextId(6)));
commands = undo_redo.Redo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes,
ElementsAre(InkStrokeId(5), InkModeledShapeId(5), InkTextId(6)));
}
TEST(PdfInkUndoRedoModelTest, EditTextUndoRedo) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(0)});
// "Edit" the text by removing the initial text ID and adding a new text ID.
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkTextId(0)));
ASSERT_TRUE(undo_redo.Add(InkTextId(1)));
ASSERT_TRUE(undo_redo.Finish());
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_THAT(commands.adds, ElementsAre(InkTextId(0)));
EXPECT_THAT(commands.removes, ElementsAre(InkTextId(1)));
commands = undo_redo.Redo();
EXPECT_THAT(commands.adds, ElementsAre(InkTextId(1)));
EXPECT_THAT(commands.removes, ElementsAre(InkTextId(0)));
}
TEST(PdfInkUndoRedoModelTest, GetUndoTextIdAndRedoInkTextId) {
PdfInkUndoRedoModel undo_redo;
// Add T1.
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
EXPECT_THAT(undo_redo.GetRedoInkTextId(), Optional(InkTextId(1)));
undo_redo.Redo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
// "Edit" T1 to T2.
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkTextId(1)));
ASSERT_TRUE(undo_redo.Add(InkTextId(2)));
ASSERT_TRUE(undo_redo.Finish());
EXPECT_THAT(undo_redo.GetUndoTextId(), Optional(InkTextId(1)));
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
EXPECT_THAT(undo_redo.GetRedoInkTextId(), Optional(InkTextId(2)));
}
TEST(PdfInkUndoRedoModelTest, GetUndoRedoTextIdWithLoadedText) {
PdfInkUndoRedoModel undo_redo;
// Remove loaded text ID 1.
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(InkLoadedTextId(1)));
ASSERT_TRUE(undo_redo.Finish());
EXPECT_THAT(undo_redo.GetUndoTextId(), Optional(InkLoadedTextId(1)));
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
undo_redo.Redo();
EXPECT_THAT(undo_redo.GetUndoTextId(), Optional(InkLoadedTextId(1)));
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdEmptyStack) {
PdfInkUndoRedoModel undo_redo;
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdAtBottomOfStack) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdNothingRemoved) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
undo_redo.Undo();
undo_redo.Redo();
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdTooManyItemsRemoved) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
DoAddCommandsCycle(undo_redo, {InkTextId(2)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkTextId(1)));
ASSERT_TRUE(undo_redo.Remove(InkTextId(2)));
ASSERT_TRUE(undo_redo.Finish());
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdTooManyItemsAdded) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(10)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkTextId(10)));
ASSERT_TRUE(undo_redo.Add(InkTextId(11)));
ASSERT_TRUE(undo_redo.Add(InkTextId(12)));
ASSERT_TRUE(undo_redo.Finish());
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetUndoTextIdNonText) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(1)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(1)));
ASSERT_TRUE(undo_redo.Finish());
EXPECT_FALSE(undo_redo.GetUndoTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdEmptyStack) {
PdfInkUndoRedoModel undo_redo;
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdAtTopOfStack) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1)});
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdNothingAdded) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(1)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkStrokeId(1)));
ASSERT_TRUE(undo_redo.Finish());
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdTooManyItemsAdded) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1), InkTextId(2)});
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdTooManyItemsRemoved) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkTextId(1), InkTextId(2)});
ASSERT_TRUE(undo_redo.Start().has_value());
ASSERT_TRUE(undo_redo.Remove(InkTextId(1)));
ASSERT_TRUE(undo_redo.Remove(InkTextId(2)));
ASSERT_TRUE(undo_redo.Add(InkTextId(3)));
ASSERT_TRUE(undo_redo.Finish());
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, BadGetRedoInkTextIdNonText) {
PdfInkUndoRedoModel undo_redo;
DoAddCommandsCycle(undo_redo, {InkStrokeId(1)});
undo_redo.Undo();
EXPECT_FALSE(undo_redo.GetRedoInkTextId().has_value());
}
TEST(PdfInkUndoRedoModelTest, Stress) {
#if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \
defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \
BUILDFLAG(CFI_ICALL_CHECK)
// The larger non-debug value is too slow for "dbg" bots and bots with
// sanitizers enabled.
constexpr size_t kCycles = 1000;
#else
constexpr size_t kCycles = 10000;
#endif
PdfInkUndoRedoModel undo_redo;
InkStrokeId id(0);
for (size_t i = 0; i < kCycles; ++i) {
DoAddCommandsCycle(undo_redo, {id, id + 1});
id += 2;
}
ASSERT_EQ(InkStrokeId(2 * kCycles), id);
for (size_t i = 0; i < kCycles; ++i) {
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_FALSE(lowest_discard.value().has_value());
ASSERT_TRUE(undo_redo.Remove(--id));
ASSERT_TRUE(undo_redo.Remove(--id));
ASSERT_TRUE(undo_redo.Finish());
}
ASSERT_EQ(InkStrokeId(0), id);
for (size_t i = 0; i < kCycles; ++i) {
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.removes.empty());
EXPECT_THAT(commands.adds, ElementsAreArray({id, id + 1}));
id += 2;
}
ASSERT_EQ(InkStrokeId(2 * kCycles), id);
for (size_t i = 0; i < kCycles; ++i) {
id -= 2;
PdfInkUndoRedoModel::Commands commands = undo_redo.Undo();
EXPECT_TRUE(commands.adds.empty());
EXPECT_THAT(commands.removes, ElementsAreArray({id, id + 1}));
}
base::expected<std::optional<IdType>, std::monostate> lowest_discard =
undo_redo.Start();
ASSERT_TRUE(lowest_discard.has_value());
ASSERT_THAT(lowest_discard.value(), Optional(InkStrokeId(0)));
ASSERT_TRUE(undo_redo.Add(InkStrokeId(0)));
ASSERT_TRUE(undo_redo.Finish());
}
} // namespace
} // namespace chrome_pdf