You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Framework/AzToolsFramework/Tests/UndoStack.cpp

542 lines
18 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
using namespace AZ;
using namespace AzToolsFramework;
using namespace AzToolsFramework::UndoSystem;
namespace UnitTest
{
class SequencePointTest
: public URSequencePoint
{
public:
SequencePointTest(AZStd::string friendlyName, URCommandID id)
: URSequencePoint(friendlyName, id)
{}
bool Changed() const override { return true; }
void Redo() override { m_redoCalled = true; }
void Undo() override { m_undoCalled = true; }
using URSequencePoint::RemoveChild;
bool m_redoCalled = false;
bool m_undoCalled = false;
};
class DifferentTypeSequencePointTest
: public URSequencePoint
{
public:
AZ_RTTI(DifferentTypeSequencePointTest, "{D7A42B6F-DCF8-443F-B4F1-57731B1D3CB8}")
DifferentTypeSequencePointTest(AZStd::string friendlyName, URCommandID id)
: URSequencePoint(friendlyName, id)
{}
bool Changed() const override { return true; }
};
///////////////////////////////////////////////////////////////////////////
// URSequencePoint
///////////////////////////////////////////////////////////////////////////
TEST(URSequencePoint, Find_IdAndTypeNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(6, AZ::Uuid::Create());
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_TypeNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(5, AZ::Uuid::Create());
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_IdNotPresent_ExpectNullptr)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(6, AZ::Uuid("{D7A42B6F-DCF8-443F-B4F1-57731B1D3CB8}"));
EXPECT_FALSE(result);
}
TEST(URSequencePoint, Find_MatchIsDirectChild_IdFound)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", static_cast<AZ::u64>(2));
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(2, AZ::AzTypeInfo<DifferentTypeSequencePointTest>::Uuid());
EXPECT_EQ(result, child_2);
}
TEST(URSequencePoint, Find_IdIsIndirectChild_IdFound)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
DifferentTypeSequencePointTest* child_2 = aznew DifferentTypeSequencePointTest("Child", static_cast<AZ::u64>(2));
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
DifferentTypeSequencePointTest* child_1_2 = aznew DifferentTypeSequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
auto result = parent->Find(5, AZ::AzTypeInfo<DifferentTypeSequencePointTest>::Uuid());
EXPECT_EQ(result, child_1_2);
}
TEST(URSequencePoint, RemoveChild)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_4 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_5 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_4->SetParent(parent);
child_5->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "children were not added properly";
parent->RemoveChild(child_5);
children = parent->GetChildren();
EXPECT_EQ(children.size(), 4) << "child was not removed properly";
}
TEST(URSequencePoint, SetParent_NotChildOfParent)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child = aznew SequencePointTest("Child", 1);
child->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 1) << "child was not added properly";
EXPECT_EQ(children[0]->GetName(), child->GetName()) << "child was not added properly";
}
TEST(URSequencePoint, SetParent_AlreadyChildOfParent)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_4 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_5 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_4->SetParent(parent);
child_5->SetParent(parent);
auto children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "children were not added properly";
child_5->SetParent(parent);
children = parent->GetChildren();
EXPECT_EQ(children.size(), 5) << "the parent did not de-dupe its children";
bool childFound = false;
for (auto tempChild : children)
{
if (tempChild->GetName() == child_5->GetName())
{
childFound = true;
break;
}
}
EXPECT_TRUE(childFound);
}
TEST(URSequencePoint, SetParent_AlreadyChildOfDifferentParent)
{
SequencePointTest* parent_1 = aznew SequencePointTest("Parent", 0);
SequencePointTest* parent_2 = aznew SequencePointTest("Parent", 1);
SequencePointTest* child = aznew SequencePointTest("Child", 5);
child->SetParent(parent_1);
auto children = parent_1->GetChildren();
EXPECT_EQ(children.size(), 1) << "child was not added properly";
child->SetParent(parent_2);
children = parent_1->GetChildren();
EXPECT_EQ(children.size(), 0) << "the original parent did not remove the child";
children = parent_2->GetChildren();
EXPECT_EQ(children[0]->GetName(), child->GetName()) << "child was not added to new parent properly";
}
TEST(URSequencePoint, RunUndo_NoChildren_UndoIsCalled)
{
SequencePointTest* object = aznew SequencePointTest("Object", 0);
object->m_undoCalled = false;
object->RunUndo();
EXPECT_TRUE(object->m_undoCalled) << "Undo was not called on the object";
}
TEST(URSequencePoint, RunUndo_HasChildren_UndoIsCalled)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 4);
parent->m_undoCalled = false;
child_1->SetParent(parent);
child_1->m_undoCalled = false;
child_2->SetParent(parent);
child_2->m_undoCalled = false;
child_1_1->SetParent(child_1);
child_1_1->m_undoCalled = false;
child_1_2->SetParent(child_1);
child_1_2->m_undoCalled = false;
parent->RunUndo();
EXPECT_TRUE(parent->m_undoCalled) << "Undo was not called on the parent";
EXPECT_TRUE(child_1->m_undoCalled) << "Undo was not called on the child";
EXPECT_TRUE(child_2->m_undoCalled) << "Undo was not called on the child";
EXPECT_TRUE(child_1_1->m_undoCalled) << "Undo was not called on the grandchild";
EXPECT_TRUE(child_1_2->m_undoCalled) << "Undo was not called on the grandchild";
}
TEST(URSequencePoint, RunRedo_NoChildren_RedoIsCalled)
{
SequencePointTest* object = aznew SequencePointTest("Object", 0);
object->m_redoCalled = false;
object->RunRedo();
EXPECT_TRUE(object->m_redoCalled) << "Redo was not called on the object";
}
TEST(URSequencePoint, RunRedo_HasChildren_RedoIsCalled)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 4);
parent->m_redoCalled = false;
child_1->SetParent(parent);
child_1->m_redoCalled = false;
child_2->SetParent(parent);
child_2->m_redoCalled = false;
child_1_1->SetParent(child_1);
child_1_1->m_redoCalled = false;
child_1_2->SetParent(child_1);
child_1_2->m_redoCalled = false;
parent->RunRedo();
EXPECT_TRUE(parent->m_redoCalled) << "Redo was not called on the parent";
EXPECT_TRUE(child_1->m_redoCalled) << "Redo was not called on the child";
EXPECT_TRUE(child_2->m_redoCalled) << "Redo was not called on the child";
EXPECT_TRUE(child_1_1->m_redoCalled) << "Redo was not called on the grandchild";
EXPECT_TRUE(child_1_2->m_redoCalled) << "Redo was not called on the grandchild";
}
TEST(URSequencePoint, SetName)
{
AZStd::string test_1("Test Point");
AZStd::string test_2("A different Test Point");
SequencePointTest* testPoint = aznew SequencePointTest("Test Point", 0);
EXPECT_EQ(testPoint->GetName(), test_1);
testPoint->SetName("A different Test Point");
EXPECT_EQ(testPoint->GetName(), test_2);
}
TEST(URSequencePoint, HasRealChildren_NoChildren_ExpectFalse)
{
SequencePointTest* testPoint = aznew SequencePointTest("Test Point", 0);
bool result = testPoint->HasRealChildren();
EXPECT_FALSE(result);
}
TEST(URSequencePoint, HasRealChildren_AllChildrenAreFake_ExpectFalse)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_FALSE(result);
}
TEST(URSequencePoint, HasRealChildren_OneChildIsReal_ExpectTrue)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
DifferentTypeSequencePointTest* child_3 = aznew DifferentTypeSequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
SequencePointTest* child_1_2 = aznew SequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_TRUE(result);
}
TEST(URSequencePoint, HasRealChildren_OneGrandChildIsReal_ExpectTrue)
{
SequencePointTest* parent = aznew SequencePointTest("Parent", 0);
SequencePointTest* child_1 = aznew SequencePointTest("Child", 1);
SequencePointTest* child_2 = aznew SequencePointTest("Child", 2);
SequencePointTest* child_3 = aznew SequencePointTest("Child", 3);
SequencePointTest* child_1_1 = aznew SequencePointTest("Child", 4);
DifferentTypeSequencePointTest* child_1_2 = aznew DifferentTypeSequencePointTest("Child", 5);
child_1->SetParent(parent);
child_2->SetParent(parent);
child_3->SetParent(parent);
child_1_1->SetParent(child_1);
child_1_2->SetParent(child_1);
bool result = parent->HasRealChildren();
EXPECT_TRUE(result);
}
///////////////////////////////////////////////////////////////////////////
// UndoStack
///////////////////////////////////////////////////////////////////////////
class UndoDestructorTest : public URSequencePoint
{
public:
UndoDestructorTest(bool* completedFlag)
: URSequencePoint("UndoDestructorTest", 0)
, m_completedFlag(completedFlag)
{
*m_completedFlag = false;
}
~UndoDestructorTest()
{
*m_completedFlag = true;
}
bool Changed() const override { return true; }
private:
bool* m_completedFlag;
};
TEST(UndoStack, UndoRedoMemory)
{
UndoStack undoStack(nullptr);
bool flag = false;
undoStack.Post(aznew UndoDestructorTest(&flag));
undoStack.Undo();
undoStack.Slice();
EXPECT_EQ(flag, true);
}
class UndoIntSetter : public URSequencePoint
{
public:
UndoIntSetter(int* value, int newValue)
: URSequencePoint("UndoIntSetter", 0)
, m_value(value)
, m_newValue(newValue)
, m_oldValue(*value)
{
Redo();
}
void Undo() override
{
*m_value = m_oldValue;
}
void Redo() override
{
*m_value = m_newValue;
}
bool Changed() const override { return true; }
private:
int* m_value;
int m_newValue;
int m_oldValue;
};
TEST(UndoStack, UndoRedoSequence)
{
UndoStack undoStack(nullptr);
int tracker = 0;
undoStack.Post(aznew UndoIntSetter(&tracker, 1));
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
undoStack.Post(aznew UndoIntSetter(&tracker, 100));
EXPECT_EQ(tracker, 100);
undoStack.Undo();
EXPECT_EQ(tracker, 1);
undoStack.Undo();
EXPECT_EQ(tracker, 0);
undoStack.Redo();
EXPECT_EQ(tracker, 1);
}
TEST(UndoStack, UndoRedoLotsOfUndos)
{
UndoStack undoStack(nullptr);
int tracker = 0;
const int numUndos = 1000;
for (int i = 0; i < 1000; i++)
{
undoStack.Post(aznew UndoIntSetter(&tracker, i + 1));
EXPECT_EQ(tracker, i + 1);
}
int counter = 0;
while (undoStack.CanUndo())
{
undoStack.Undo();
counter++;
}
EXPECT_EQ(numUndos, counter);
EXPECT_EQ(tracker, 0);
counter = 0;
while (undoStack.CanRedo())
{
undoStack.Redo();
counter++;
}
EXPECT_EQ(numUndos, counter);
EXPECT_EQ(tracker, numUndos);
}
}