| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/undo/undo_manager.h" |
| |
| #include <memory> |
| |
| #include "base/auto_reset.h" |
| #include "base/memory/raw_ptr.h" |
| #include "components/undo/undo_manager_observer.h" |
| #include "components/undo/undo_operation.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| std::vector<UndoOperation*> ConvertToRawPtrVector( |
| const std::vector<std::unique_ptr<UndoOperation>>& args) { |
| std::vector<UndoOperation*> args_rawptrs; |
| for (auto i = args.begin(); i != args.end(); ++i) |
| args_rawptrs.push_back(i->get()); |
| return args_rawptrs; |
| } |
| |
| } // namespace |
| |
| // UndoManagerTestApi ---------------------------------------------------------- |
| |
| class UndoManagerTestApi { |
| public: |
| UndoManagerTestApi() = delete; |
| UndoManagerTestApi(const UndoManagerTestApi&) = delete; |
| UndoManagerTestApi& operator=(const UndoManagerTestApi&) = delete; |
| |
| // Returns all UndoOperations that are awaiting Undo or Redo. |
| static std::vector<UndoOperation*> GetAllUndoOperations( |
| const UndoManager& undo_manager); |
| }; |
| |
| std::vector<UndoOperation*> UndoManagerTestApi::GetAllUndoOperations( |
| const UndoManager& undo_manager) { |
| std::vector<UndoOperation*> result; |
| for (size_t i = 0; i < undo_manager.undo_actions_.size(); ++i) { |
| const std::vector<UndoOperation*> operations = |
| ConvertToRawPtrVector(undo_manager.undo_actions_[i]->undo_operations()); |
| result.insert(result.end(), operations.begin(), operations.end()); |
| } |
| for (size_t i = 0; i < undo_manager.redo_actions_.size(); ++i) { |
| const std::vector<UndoOperation*> operations = |
| ConvertToRawPtrVector(undo_manager.redo_actions_[i]->undo_operations()); |
| result.insert(result.end(), operations.begin(), operations.end()); |
| } |
| // Ensure that if an Undo is in progress the UndoOperations part of that |
| // UndoGroup are included in the returned set. This will ensure that any |
| // changes (such as renumbering) will be applied to any potentially |
| // unprocessed UndoOperations. |
| if (undo_manager.undo_in_progress_action_) { |
| const std::vector<UndoOperation*> operations = ConvertToRawPtrVector( |
| undo_manager.undo_in_progress_action_->undo_operations()); |
| result.insert(result.end(), operations.begin(), operations.end()); |
| } |
| |
| return result; |
| } |
| |
| namespace { |
| |
| class TestUndoOperation; |
| |
| // TestUndoService ------------------------------------------------------------- |
| |
| class TestUndoService { |
| public: |
| TestUndoService(); |
| ~TestUndoService(); |
| |
| void Redo(); |
| void TriggerOperation(); |
| void RecordUndoCall(); |
| |
| UndoManager undo_manager_; |
| |
| bool performing_redo_; |
| |
| int undo_operation_count_; |
| int redo_operation_count_; |
| }; |
| |
| // TestUndoOperation ----------------------------------------------------------- |
| |
| class TestUndoOperation : public UndoOperation { |
| public: |
| explicit TestUndoOperation(TestUndoService* undo_service); |
| |
| TestUndoOperation(const TestUndoOperation&) = delete; |
| TestUndoOperation& operator=(const TestUndoOperation&) = delete; |
| |
| ~TestUndoOperation() override; |
| |
| // UndoOperation: |
| void Undo() override; |
| int GetUndoLabelId() const override; |
| int GetRedoLabelId() const override; |
| |
| private: |
| raw_ptr<TestUndoService> undo_service_; |
| }; |
| |
| TestUndoOperation::TestUndoOperation(TestUndoService* undo_service) |
| : undo_service_(undo_service) { |
| } |
| |
| TestUndoOperation::~TestUndoOperation() { |
| } |
| |
| void TestUndoOperation::Undo() { |
| undo_service_->TriggerOperation(); |
| undo_service_->RecordUndoCall(); |
| } |
| |
| int TestUndoOperation::GetUndoLabelId() const { |
| return 0; |
| } |
| |
| int TestUndoOperation::GetRedoLabelId() const { |
| return 0; |
| } |
| |
| // TestUndoService ------------------------------------------------------------- |
| |
| TestUndoService::TestUndoService() : performing_redo_(false), |
| undo_operation_count_(0), |
| redo_operation_count_(0) { |
| } |
| |
| TestUndoService::~TestUndoService() { |
| } |
| |
| void TestUndoService::Redo() { |
| base::AutoReset<bool> incoming_changes(&performing_redo_, true); |
| undo_manager_.Redo(); |
| } |
| |
| void TestUndoService::TriggerOperation() { |
| undo_manager_.AddUndoOperation(std::make_unique<TestUndoOperation>(this)); |
| } |
| |
| void TestUndoService::RecordUndoCall() { |
| if (performing_redo_) |
| ++redo_operation_count_; |
| else |
| ++undo_operation_count_; |
| } |
| |
| // TestObserver ---------------------------------------------------------------- |
| |
| class TestObserver : public UndoManagerObserver { |
| public: |
| TestObserver() : state_change_count_(0) {} |
| // Returns the number of state change callbacks |
| |
| TestObserver(const TestObserver&) = delete; |
| TestObserver& operator=(const TestObserver&) = delete; |
| |
| int state_change_count() { return state_change_count_; } |
| |
| void OnUndoManagerStateChange() override { ++state_change_count_; } |
| |
| private: |
| int state_change_count_; |
| }; |
| |
| // Tests ----------------------------------------------------------------------- |
| |
| TEST(UndoServiceTest, AddUndoActions) { |
| TestUndoService undo_service; |
| |
| undo_service.TriggerOperation(); |
| undo_service.TriggerOperation(); |
| EXPECT_EQ(2U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| } |
| |
| TEST(UndoServiceTest, UndoMultipleActions) { |
| TestUndoService undo_service; |
| |
| undo_service.TriggerOperation(); |
| undo_service.TriggerOperation(); |
| |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); |
| |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(2U, undo_service.undo_manager_.redo_count()); |
| |
| EXPECT_EQ(2, undo_service.undo_operation_count_); |
| EXPECT_EQ(0, undo_service.redo_operation_count_); |
| } |
| |
| TEST(UndoServiceTest, RedoAction) { |
| TestUndoService undo_service; |
| |
| undo_service.TriggerOperation(); |
| |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); |
| |
| undo_service.Redo(); |
| EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| |
| EXPECT_EQ(1, undo_service.undo_operation_count_); |
| EXPECT_EQ(1, undo_service.redo_operation_count_); |
| } |
| |
| TEST(UndoServiceTest, GroupActions) { |
| TestUndoService undo_service; |
| |
| // Add two operations in a single action. |
| undo_service.undo_manager_.StartGroupingActions(); |
| undo_service.TriggerOperation(); |
| undo_service.TriggerOperation(); |
| undo_service.undo_manager_.EndGroupingActions(); |
| |
| // Check that only one action is created. |
| EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); |
| |
| undo_service.Redo(); |
| EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| |
| // Check that both operations were called in Undo and Redo. |
| EXPECT_EQ(2, undo_service.undo_operation_count_); |
| EXPECT_EQ(2, undo_service.redo_operation_count_); |
| } |
| |
| TEST(UndoServiceTest, SuspendUndoTracking) { |
| TestUndoService undo_service; |
| |
| undo_service.undo_manager_.SuspendUndoTracking(); |
| EXPECT_TRUE(undo_service.undo_manager_.IsUndoTrakingSuspended()); |
| |
| undo_service.TriggerOperation(); |
| |
| undo_service.undo_manager_.ResumeUndoTracking(); |
| EXPECT_FALSE(undo_service.undo_manager_.IsUndoTrakingSuspended()); |
| |
| EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| } |
| |
| TEST(UndoServiceTest, RedoEmptyAfterNewAction) { |
| TestUndoService undo_service; |
| |
| undo_service.TriggerOperation(); |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(0U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(1U, undo_service.undo_manager_.redo_count()); |
| |
| undo_service.TriggerOperation(); |
| EXPECT_EQ(1U, undo_service.undo_manager_.undo_count()); |
| EXPECT_EQ(0U, undo_service.undo_manager_.redo_count()); |
| } |
| |
| TEST(UndoServiceTest, GetAllUndoOperations) { |
| TestUndoService undo_service; |
| |
| undo_service.TriggerOperation(); |
| |
| undo_service.undo_manager_.StartGroupingActions(); |
| undo_service.TriggerOperation(); |
| undo_service.TriggerOperation(); |
| undo_service.undo_manager_.EndGroupingActions(); |
| |
| undo_service.TriggerOperation(); |
| |
| undo_service.undo_manager_.Undo(); |
| ASSERT_EQ(2U, undo_service.undo_manager_.undo_count()); |
| ASSERT_EQ(1U, undo_service.undo_manager_.redo_count()); |
| |
| std::vector<UndoOperation*> all_operations = |
| UndoManagerTestApi::GetAllUndoOperations(undo_service.undo_manager_); |
| EXPECT_EQ(4U, all_operations.size()); |
| } |
| |
| TEST(UndoServiceTest, ObserverCallbacks) { |
| TestObserver observer; |
| TestUndoService undo_service; |
| undo_service.undo_manager_.AddObserver(&observer); |
| EXPECT_EQ(0, observer.state_change_count()); |
| |
| undo_service.TriggerOperation(); |
| EXPECT_EQ(1, observer.state_change_count()); |
| |
| undo_service.undo_manager_.StartGroupingActions(); |
| undo_service.TriggerOperation(); |
| undo_service.TriggerOperation(); |
| undo_service.undo_manager_.EndGroupingActions(); |
| EXPECT_EQ(2, observer.state_change_count()); |
| |
| // There should be at least 1 observer callback for undo. |
| undo_service.undo_manager_.Undo(); |
| int callback_count_after_undo = observer.state_change_count(); |
| EXPECT_GT(callback_count_after_undo, 2); |
| |
| // There should be at least 1 observer callback for redo. |
| undo_service.undo_manager_.Redo(); |
| int callback_count_after_redo = observer.state_change_count(); |
| EXPECT_GT(callback_count_after_redo, callback_count_after_undo); |
| |
| undo_service.undo_manager_.RemoveObserver(&observer); |
| undo_service.undo_manager_.Undo(); |
| EXPECT_EQ(callback_count_after_redo, observer.state_change_count()); |
| } |
| |
| } // namespace |