/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include namespace UnitTest { PrefabTestToolsApplication::PrefabTestToolsApplication(AZStd::string appName) : ToolsTestApplication(AZStd::move(appName)) { } void PrefabTestFixture::SetUpEditorFixtureImpl() { // Acquire the system entity AZ::Entity* systemEntity = GetApplication()->FindEntity(AZ::SystemEntityId); EXPECT_TRUE(systemEntity); m_prefabSystemComponent = systemEntity->FindComponent(); EXPECT_TRUE(m_prefabSystemComponent); m_prefabLoaderInterface = AZ::Interface::Get(); EXPECT_TRUE(m_prefabLoaderInterface); m_prefabPublicInterface = AZ::Interface::Get(); EXPECT_TRUE(m_prefabPublicInterface); m_instanceEntityMapperInterface = AZ::Interface::Get(); EXPECT_TRUE(m_instanceEntityMapperInterface); m_instanceUpdateExecutorInterface = AZ::Interface::Get(); EXPECT_TRUE(m_instanceUpdateExecutorInterface); m_instanceToTemplateInterface = AZ::Interface::Get(); EXPECT_TRUE(m_instanceToTemplateInterface); m_prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); EXPECT_TRUE(m_prefabEditorEntityOwnershipInterface); m_settingsRegistryInterface = AZ::SettingsRegistry::Get(); EXPECT_TRUE(m_settingsRegistryInterface); // Have all instances cache their DOM for testing purposes. Instance::s_DomCachingEnabledDefault = true; // This is for calling CreateEditorRepresentation that adds required editor components. AzToolsFramework::EditorRequestBus::Handler::BusConnect(); GetApplication()->RegisterComponentDescriptor(PrefabTestComponent::CreateDescriptor()); GetApplication()->RegisterComponentDescriptor(PrefabNonEditorComponent::CreateDescriptor()); GetApplication()->RegisterComponentDescriptor(PrefabTestComponentWithUnReflectedTypeMember::CreateDescriptor()); // Gets undo stack. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( m_undoStack, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetUndoStack); AZ_Assert(m_undoStack, "Failed to look up undo stack from tools application"); // This ensures that the flag (if root prefab is assigned) in prefab editor entity ownership service is set to true. // Public prefab operations like "create prefab" will fail if the flag is off. CreateRootPrefab(); } void PrefabTestFixture::TearDownEditorFixtureImpl() { m_undoStack = nullptr; AzToolsFramework::EditorRequestBus::Handler::BusDisconnect(); } AZStd::unique_ptr PrefabTestFixture::CreateTestApplication() { return AZStd::make_unique("PrefabTestApplication"); } void PrefabTestFixture::CreateRootPrefab() { m_prefabEditorEntityOwnershipInterface->CreateNewLevelPrefab("UnitTestRoot.prefab", ""); InstanceOptionalReference rootInstance = m_prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); ASSERT_TRUE(rootInstance.has_value()); EntityOptionalReference rootContainerEntity = rootInstance->get().GetContainerEntity(); ASSERT_TRUE(rootContainerEntity.has_value()); if (rootContainerEntity->get().GetState() == AZ::Entity::State::Constructed) { rootContainerEntity->get().Init(); } // Focus on root prefab instance. auto* prefabFocusPublicInterface = AZ::Interface::Get(); EXPECT_TRUE(prefabFocusPublicInterface != nullptr); PrefabFocusOperationResult focusResult = prefabFocusPublicInterface->FocusOnOwningPrefab( rootContainerEntity->get().GetId()); EXPECT_TRUE(focusResult.IsSuccess()); } void PrefabTestFixture::PropagateAllTemplateChanges() { m_prefabSystemComponent->OnSystemTick(); } AZ::EntityId PrefabTestFixture::CreateEditorEntityUnderRoot(const AZStd::string& entityName) { return CreateEditorEntity(entityName, GetRootContainerEntityId()); } AZ::EntityId PrefabTestFixture::CreateEditorEntity(const AZStd::string& entityName, AZ::EntityId parentId) { PrefabEntityResult createResult = m_prefabPublicInterface->CreateEntity(parentId, AZ::Vector3()); AZ_Assert(createResult.IsSuccess(), "CreateEditorEntity - Failed to create entity %s. Error: %s", entityName.c_str(), createResult.GetError().c_str()); // Verify new entity. AZ::EntityId newEntityId = createResult.GetValue(); EXPECT_TRUE(newEntityId.IsValid()); AZ::Entity* newEntity = AzToolsFramework::GetEntityById(newEntityId); EXPECT_TRUE(newEntity != nullptr); newEntity->SetName(entityName); m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(newEntityId, m_undoStack->GetTop()); PropagateAllTemplateChanges(); return newEntityId; } AZ::EntityId PrefabTestFixture::CreateEditorPrefab(AZ::IO::PathView filePath, const AzToolsFramework::EntityIdList& entityIds) { // New prefab instance is reparent under the common root entity of the input entities. CreatePrefabResult createResult = m_prefabPublicInterface->CreatePrefabInMemory(entityIds, filePath); AZ_Assert(createResult.IsSuccess(), "CreateEditorPrefab - Failed to create prefab %s. Error: %s", AZStd::string(filePath.Native()).c_str(), createResult.GetError().c_str()); // Verify new container entity. AZ::EntityId prefabContainerId = createResult.GetValue(); EXPECT_TRUE(prefabContainerId.IsValid()); AZ::Entity* prefabContainerEntity = AzToolsFramework::GetEntityById(prefabContainerId); EXPECT_TRUE(prefabContainerEntity != nullptr); PropagateAllTemplateChanges(); return prefabContainerId; } AZ::EntityId PrefabTestFixture::InstantiateEditorPrefab(AZ::IO::PathView filePath, AZ::EntityId parentId) { InstantiatePrefabResult instantiateResult = m_prefabPublicInterface->InstantiatePrefab( filePath.Native(), parentId, AZ::Vector3()); AZ_Assert(instantiateResult.IsSuccess(), "InstantiateEditorPrefab - Failed to instantiate prefab %s. Error: %s", AZStd::string(filePath.Native()).c_str(), instantiateResult.GetError().c_str()); // Verify new container entity. AZ::EntityId prefabContainerId = instantiateResult.GetValue(); EXPECT_TRUE(prefabContainerId.IsValid()); AZ::Entity* prefabContainerEntity = AzToolsFramework::GetEntityById(prefabContainerId); EXPECT_TRUE(prefabContainerEntity != nullptr); PropagateAllTemplateChanges(); return prefabContainerId; } AZ::Entity* PrefabTestFixture::CreateEntity(const AZStd::string& entityName, bool shouldActivate) { AZ::Entity* newEntity = aznew AZ::Entity(entityName); if (shouldActivate) { newEntity->Init(); newEntity->Activate(); } return newEntity; } AZ::EntityId PrefabTestFixture::GetRootContainerEntityId() { auto rootInstance = m_prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); EXPECT_TRUE(rootInstance.has_value()); AZ::EntityId rootContainerId = rootInstance->get().GetContainerEntityId(); EXPECT_TRUE(rootContainerId.IsValid()); return rootContainerId; } void PrefabTestFixture::CompareInstances(const AzToolsFramework::Prefab::Instance& instanceA, const AzToolsFramework::Prefab::Instance& instanceB, bool shouldCompareLinkIds, bool shouldCompareContainerEntities) { AzToolsFramework::Prefab::TemplateId templateAId = instanceA.GetTemplateId(); AzToolsFramework::Prefab::TemplateId templateBId = instanceB.GetTemplateId(); ASSERT_TRUE(templateAId != AzToolsFramework::Prefab::InvalidTemplateId); ASSERT_TRUE(templateBId != AzToolsFramework::Prefab::InvalidTemplateId); EXPECT_EQ(templateAId, templateBId); AzToolsFramework::Prefab::TemplateReference templateA = m_prefabSystemComponent->FindTemplate(templateAId); ASSERT_TRUE(templateA.has_value()); AzToolsFramework::Prefab::PrefabDom prefabDomA; ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceA, prefabDomA)); AzToolsFramework::Prefab::PrefabDom prefabDomB; ASSERT_TRUE(AzToolsFramework::Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceB, prefabDomB)); // Validate that both instances match when serialized. PrefabTestDomUtils::ComparePrefabDoms(prefabDomA, prefabDomB, shouldCompareLinkIds, shouldCompareContainerEntities); // Validate that the serialized instances match the shared template when serialized // Note: We do not compare the link ids. The template DOM is not supposed to have this member. PrefabTestDomUtils::ComparePrefabDoms(templateA->get().GetPrefabDom(), prefabDomB, false, shouldCompareContainerEntities); } void PrefabTestFixture::DeleteInstances(const InstanceList& instancesToDelete) { for (Instance* instanceToDelete : instancesToDelete) { ASSERT_TRUE(instanceToDelete); delete instanceToDelete; instanceToDelete = nullptr; } } EntityAlias PrefabTestFixture::FindEntityAliasInInstance( AZ::EntityId containerEntityId, const AZStd::string& entityName) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); EntityAlias foundEntityAlias = ""; owningInstance->get().GetEntities( [&foundEntityAlias, &owningInstance, &entityName](AZStd::unique_ptr& entity) { if (entity->GetName() == entityName) { auto entityAlias = owningInstance->get().GetEntityAlias(entity->GetId()); EXPECT_TRUE(entityAlias.has_value()) << "FindEntityAliasInInstance - Retrieved entity alias is null."; foundEntityAlias = entityAlias->get(); return false; } return true; }); return foundEntityAlias; } InstanceAlias PrefabTestFixture::FindNestedInstanceAliasInInstance( AZ::EntityId containerEntityId, const AZStd::string& nestedContainerEntityName) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); InstanceAlias foundInstanceAlias = ""; owningInstance->get().GetNestedInstances( [&foundInstanceAlias, &nestedContainerEntityName](AZStd::unique_ptr& nestedInstance) { auto nestedContainerEntity = nestedInstance->GetContainerEntity(); EXPECT_TRUE(nestedContainerEntity.has_value()); if (nestedContainerEntity->get().GetName() == nestedContainerEntityName) { foundInstanceAlias = nestedInstance->GetInstanceAlias(); return; } }); return foundInstanceAlias; } void PrefabTestFixture::RenameEntity(AZ::EntityId entityId, const AZStd::string& newName) { ASSERT_FALSE(newName.empty()) << "Cannot rename an entity to empty string."; AZ::Entity* entityToRename = AzToolsFramework::GetEntityById(entityId); ASSERT_TRUE(entityToRename != nullptr) << "Cannot rename a null entity."; entityToRename->SetName(newName); m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_undoStack->GetTop()); PropagateAllTemplateChanges(); } void PrefabTestFixture::ValidateEntityUnderInstance( AZ::EntityId containerEntityId, const EntityAlias& entityAlias, const AZStd::string& entityName) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); auto entity = owningInstance->get().GetEntity(entityAlias); ASSERT_TRUE(entity.has_value()); ASSERT_EQ(entity->get().GetName(), entityName); } void PrefabTestFixture::ValidateEntityNotUnderInstance( AZ::EntityId containerEntityId, const EntityAlias& entityAlias) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); auto entity = owningInstance->get().GetEntity(entityAlias); ASSERT_FALSE(entity.has_value()); } void PrefabTestFixture::ValidateNestedInstanceUnderInstance( AZ::EntityId containerEntityId, const InstanceAlias& nestedInstanceAlias) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); auto nestedInstance = owningInstance->get().FindNestedInstance(nestedInstanceAlias); ASSERT_TRUE(nestedInstance.has_value()); } void PrefabTestFixture::ValidateNestedInstanceNotUnderInstance( AZ::EntityId containerEntityId, const InstanceAlias& nestedInstanceAlias) { auto owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId); EXPECT_TRUE(owningInstance.has_value()); auto nestedInstance = owningInstance->get().FindNestedInstance(nestedInstanceAlias); ASSERT_FALSE(nestedInstance.has_value()); } void PrefabTestFixture::ValidateInstanceEntitiesActive(Instance& instance) { AZStd::vector entityIdVector; instance.GetAllEntityIdsInHierarchy([&entityIdVector](AZ::EntityId entityId) { entityIdVector.push_back(entityId); return true; }); for (AZ::EntityId entityId : entityIdVector) { AZ::Entity* entityInInstance = nullptr; AZ::ComponentApplicationBus::BroadcastResult(entityInInstance, &AZ::ComponentApplicationBus::Events::FindEntity, entityId); ASSERT_TRUE(entityInInstance); EXPECT_EQ(entityInInstance->GetState(), AZ::Entity::State::Active); } } void PrefabTestFixture::ProcessDeferredUpdates() { // Force a prefab propagation for updates that are deferred to the next tick. m_prefabSystemComponent->OnSystemTick(); } void PrefabTestFixture::Undo() { m_undoStack->Undo(); ProcessDeferredUpdates(); } void PrefabTestFixture::Redo() { m_undoStack->Redo(); ProcessDeferredUpdates(); } void PrefabTestFixture::AddRequiredEditorComponents(const AzToolsFramework::EntityIdList& entityIds) { for (AZ::EntityId entityId : entityIds) { AZ::Entity* entity = nullptr; AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId); EXPECT_TRUE(entity != nullptr) << "The entity to be added required editor components is nullptr."; entity->Deactivate(); AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequests::AddRequiredComponents, *entity); entity->Activate(); } } // EditorRequestBus void PrefabTestFixture::CreateEditorRepresentation(AZ::Entity* entity) { if (!entity) { EXPECT_TRUE(false) << "Cannot call CreateEditorRepresentation for a null entity."; return; } AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *entity); } } // namespace UnitTest