using NUnit.Framework; using System; using System.Diagnostics.CodeAnalysis; using System.Linq; using UnityEditor; using UnityEditor.Rendering; namespace UnityEngine.Rendering.Tests { class RemoveComponent { #region Components interface ITest { } protected class Apple : MonoBehaviour, ITest { } protected class Banana : MonoBehaviour, ITest { } [RequireComponent(typeof(Apple))] protected class AdditionalApple : MonoBehaviour, IAdditionalData, ITest { } [RequireComponent(typeof(Banana))] protected class AdditionalBanana : MonoBehaviour, IAdditionalData, ITest { } [RequireComponent(typeof(Banana))] protected class AdditionalBananaColor : MonoBehaviour, IAdditionalData, ITest { } protected class WaterMelon : MonoBehaviour, IAdditionalData, ITest{ } [RequireComponent(typeof(Apple), typeof(Banana))] protected class FruitBasket : MonoBehaviour, IAdditionalData, ITest { } #endregion #region SetUp & TearDown protected GameObject m_GameObject; [SetUp] public void SetUp() { m_GameObject = new("RemoveComponentTestsRoot"); } [TearDown] public void TearDown() { GameObject.DestroyImmediate(m_GameObject); } #endregion protected Type[] GenericRemoveComponent( [DisallowNull] int selectionAmount, [DisallowNull] Type componentToRemove, [DisallowNull] Type[] componentsToAdd, [DisallowNull] Action removeMethod) { Assert.IsTrue(selectionAmount > 0, "GenericRemoveComponent should only be called with a non null selection"); //Create object to select GameObject[] selectedGameObjects = new GameObject[selectionAmount]; for (int i = 0; i < selectionAmount; ++i) { (selectedGameObjects[i] = new GameObject($"Selected_{i}", componentsToAdd)).transform.parent = m_GameObject.transform; } //update selected objects Selection.objects = selectedGameObjects; //Call method to test removeMethod(Selection.activeGameObject.GetComponent(componentToRemove)); //Assert all selection have same component var typesOfFirstSelected = selectedGameObjects[0].GetComponents().Select(c => c.GetType()); Assert.IsTrue(DoesAllGameObjectHaveSameComponents(selectedGameObjects.Skip(1), typesOfFirstSelected), "Not all the GameObject of the selection have same Components"); //Will assert that remaining components are the desired ones return typesOfFirstSelected.ToArray(); } bool DoesAllGameObjectHaveSameComponents([NotNull] System.Collections.Generic.IEnumerable objectsToCheck, System.Collections.Generic.IEnumerable typesToCheck) { var typeAmount = typesToCheck.Count(); foreach(var objectToCheck in objectsToCheck) { if (objectToCheck.GetComponents().Length != typeAmount) return false; foreach (var typeToCheck in typesToCheck) if (!objectToCheck.TryGetComponent(typeToCheck, out _)) return false; } return true; } } [TestOf(typeof(RemoveComponentUtils))] class RemoveComponentUtilsTests : RemoveComponent { //No multiedition test needed as this is not Selection dependant static TestCaseData[] s_RemoveComponentTestCaseDatas = { new TestCaseData(typeof(Banana), new Type[] {typeof(Banana), typeof(AdditionalBanana)}) .SetName("Removal of target component removes it's additional data") .Returns(Array.Empty()), new TestCaseData(typeof(Banana), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(AdditionalBananaColor)}) .SetName("Removal of target component removes all it's additional datas") .Returns(Array.Empty()), new TestCaseData(typeof(Apple), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(Apple), typeof(AdditionalApple)}) .SetName("Given multiple components, each with additional datas, the removal of the component only removes its additional datas") .Returns(new Type[] {typeof(Banana), typeof(AdditionalBanana) }), }; [Test, TestCaseSource(nameof(s_RemoveComponentTestCaseDatas))] public Type[] RemoveComponentAndPropagateTheDeleteToAdditionalDatas([DisallowNull] Type componentToRemove, [DisallowNull] Type[] componentsToAdd) { return GenericRemoveComponent(1, componentToRemove, componentsToAdd, RemoveComponentUtils.RemoveComponent); } } [TestOf(typeof(RemoveAdditionalDataUtils))] class RemoveAdditionalDataUtilsTests : RemoveComponent { //No multiedition test needed as this is not Selection dependant static TestCaseData[] s_TryGetComponentsToRemoveTestCaseDatas = { new TestCaseData(typeof(AdditionalBanana)) .Returns(new string[] {"Banana"}) .SetName("For additional data targeting one component, return the targeted component (most common case)"), new TestCaseData(typeof(FruitBasket)) .Returns(new string[] { "Apple", "Banana" }) .SetName("For additional data targeting multiple components, return all the targeted components"), new TestCaseData(typeof(WaterMelon)) .Returns(Array.Empty()) .SetName("For additional data targetting no component, return empty collection."), }; [Test, TestCaseSource(nameof(s_TryGetComponentsToRemoveTestCaseDatas))] public string[] TryGetComponentsToRemove([DisallowNull] Type type) { string[] result = Array.Empty(); var additionalData = m_GameObject.AddComponent(type) as IAdditionalData; using (ListPool.Get(out var componentsToRemove)) { if (RemoveAdditionalDataUtils.TryGetComponentsToRemove(additionalData, componentsToRemove, out var error)) result = componentsToRemove.Select(t => t.Name).ToArray(); } return result; } //No multiedition RECQUIRED: This is Selection dependent to handle correctly the popup static TestCaseData[] s_RemoveAdditionalDataComponentTestCaseDatas = { new TestCaseData(1, typeof(AdditionalBanana), new Type[] {typeof(Banana), typeof(AdditionalBanana) }) .SetName("For single additional data, when removing it, the target component is deleted") .Returns(Array.Empty()), new TestCaseData(1, typeof(AdditionalBananaColor), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(AdditionalBananaColor)}) .SetName("For multiple additional datas, when removing one of them, target component is deleted, and the other additional data") .Returns(Array.Empty()), new TestCaseData(1, typeof(AdditionalBananaColor), new Type[] {typeof(Banana), typeof(Banana), typeof(AdditionalBanana), typeof(AdditionalBananaColor)}) .SetName("For multiple additional component and datas, when removing one of them everything is removed") .Returns(Array.Empty()), new TestCaseData(1, typeof(AdditionalApple), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(Apple), typeof(AdditionalApple)}) .SetName("For multiple types of target component, when deleting an additional data, only the target component is being removed") .Returns(new Type[] {typeof(Banana), typeof(AdditionalBanana)}), new TestCaseData(3, typeof(AdditionalBanana), new Type[] {typeof(Banana), typeof(AdditionalBanana) }) .SetName("For single additional data, when removing it, the target component is deleted (multiedition case)") .Returns(Array.Empty()), new TestCaseData(3, typeof(AdditionalBananaColor), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(AdditionalBananaColor)}) .SetName("For multiple additional datas, when removing one of them, target component is deleted, and the other additional data (multiedition case)") .Returns(Array.Empty()), new TestCaseData(3, typeof(AdditionalBananaColor), new Type[] {typeof(Banana), typeof(Banana), typeof(AdditionalBanana), typeof(AdditionalBananaColor)}) .SetName("For multiple additional component and datas, when removing one of them everything is removed (multiedition case)") .Returns(Array.Empty()), new TestCaseData(3, typeof(AdditionalApple), new Type[] {typeof(Banana), typeof(AdditionalBanana), typeof(Apple), typeof(AdditionalApple)}) .SetName("For multiple types of target component, when deleting an additional data, only the target component is being removed (multiedition case)") .Returns(new Type[] {typeof(Banana), typeof(AdditionalBanana)}) }; [Test, TestCaseSource(nameof(s_RemoveAdditionalDataComponentTestCaseDatas))] [NUnit.Framework.Property("FogBugz", "1396805")] [NUnit.Framework.Property("Jira", "UUM-5452")] public Type[] RemoveAdditionalDataComponentAndPropagateToComponent(int selectionAmount, [DisallowNull] Type componentToRemove, [DisallowNull] Type[] componentsToAdd) { return GenericRemoveComponent(selectionAmount, componentToRemove, componentsToAdd, c => RemoveAdditionalDataUtils.RemoveAdditionalData(new UnityEditor.MenuCommand(c), false)); } } }