using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using Newtonsoft.Json.Linq; using MCPForUnity.Editor.Tools; namespace MCPForUnityTests.Editor.Tools { /// /// Comprehensive baseline tests for ManageGameObject "delete" action. /// These tests capture existing behavior before API redesign. /// public class ManageGameObjectDeleteTests { private List testObjects = new List(); [TearDown] public void TearDown() { foreach (var go in testObjects) { if (go != null) { Object.DestroyImmediate(go); } } testObjects.Clear(); } private GameObject CreateTestObject(string name) { var go = new GameObject(name); testObjects.Add(go); return go; } #region Basic Delete Tests [Test] public void Delete_ByName_DeletesObject() { var target = CreateTestObject("DeleteTargetByName"); var p = new JObject { ["action"] = "delete", ["target"] = "DeleteTargetByName", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify object is deleted var found = GameObject.Find("DeleteTargetByName"); Assert.IsNull(found, "Object should be deleted"); // Remove from our tracking list since it's deleted testObjects.Remove(target); } [Test] public void Delete_ByInstanceID_DeletesObject() { var target = CreateTestObject("DeleteTargetByID"); int instanceID = target.GetInstanceID(); var p = new JObject { ["action"] = "delete", ["target"] = instanceID, ["searchMethod"] = "by_id" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify object is deleted var found = GameObject.Find("DeleteTargetByID"); Assert.IsNull(found, "Object should be deleted"); testObjects.Remove(target); } [Test] public void Delete_NonExistentObject_ReturnsError() { var p = new JObject { ["action"] = "delete", ["target"] = "NonExistentObject12345", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsFalse(resultObj.Value("success"), "Should fail for non-existent object"); } [Test] public void Delete_WithoutTarget_ReturnsError() { var p = new JObject { ["action"] = "delete" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsFalse(resultObj.Value("success"), "Should fail without target"); } #endregion #region Search Method Tests [Test] public void Delete_ByTag_DeletesMatchingObjects() { // Current behavior: delete action finds first matching object and deletes it. // This test verifies at least one tagged object is deleted. var target1 = CreateTestObject("DeleteByTag1"); var target2 = CreateTestObject("DeleteByTag2"); // Use built-in tag target1.tag = "MainCamera"; target2.tag = "MainCamera"; var p = new JObject { ["action"] = "delete", ["target"] = "MainCamera", ["searchMethod"] = "by_tag" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify at least one object was deleted (current behavior deletes first match) bool target1Deleted = target1 == null; // Unity Object == null check bool target2Deleted = target2 == null; Assert.IsTrue(target1Deleted || target2Deleted, "At least one tagged object should be deleted"); // Check response data for deletion info var data = resultObj["data"]; Assert.IsNotNull(data, "Response should include data"); // Clean up only surviving objects from tracking if (!target1Deleted) testObjects.Remove(target1); if (!target2Deleted) testObjects.Remove(target2); } [Test] public void Delete_ByLayer_DeletesMatchingObjects() { var target = CreateTestObject("DeleteByLayer"); target.layer = LayerMask.NameToLayer("UI"); var p = new JObject { ["action"] = "delete", ["target"] = "UI", ["searchMethod"] = "by_layer" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify the object was actually deleted bool targetDeleted = target == null; // Unity Object == null check Assert.IsTrue(targetDeleted, "Object on UI layer should be deleted"); Assert.IsFalse(testObjects.Contains(target) && target != null, "Deleted object should not be findable"); // Only remove from tracking if not already destroyed if (!targetDeleted) testObjects.Remove(target); } [Test] public void Delete_ByPath_DeletesObject() { var parent = CreateTestObject("DeleteParent"); var child = CreateTestObject("DeleteChild"); child.transform.SetParent(parent.transform); var p = new JObject { ["action"] = "delete", ["target"] = "DeleteParent/DeleteChild", ["searchMethod"] = "by_path" }; var result = ManageGameObject.HandleCommand(p); // Capture current behavior Assert.IsNotNull(result, "Should return a result"); testObjects.Remove(child); } #endregion #region Hierarchy Tests [Test] public void Delete_Parent_DeletesChildren() { var parent = CreateTestObject("DeleteParentWithChildren"); var child1 = CreateTestObject("Child1"); var child2 = CreateTestObject("Child2"); var grandchild = CreateTestObject("Grandchild"); child1.transform.SetParent(parent.transform); child2.transform.SetParent(parent.transform); grandchild.transform.SetParent(child1.transform); var p = new JObject { ["action"] = "delete", ["target"] = "DeleteParentWithChildren", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // All should be deleted Assert.IsNull(GameObject.Find("DeleteParentWithChildren"), "Parent should be deleted"); Assert.IsNull(GameObject.Find("Child1"), "Child1 should be deleted"); Assert.IsNull(GameObject.Find("Child2"), "Child2 should be deleted"); Assert.IsNull(GameObject.Find("Grandchild"), "Grandchild should be deleted"); testObjects.Remove(parent); testObjects.Remove(child1); testObjects.Remove(child2); testObjects.Remove(grandchild); } [Test] public void Delete_Child_DoesNotDeleteParent() { var parent = CreateTestObject("ParentShouldSurvive"); var child = CreateTestObject("ChildToDelete"); child.transform.SetParent(parent.transform); var p = new JObject { ["action"] = "delete", ["target"] = "ChildToDelete", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Child deleted, parent survives Assert.IsNull(GameObject.Find("ChildToDelete"), "Child should be deleted"); Assert.IsNotNull(GameObject.Find("ParentShouldSurvive"), "Parent should survive"); testObjects.Remove(child); } #endregion #region Response Structure Tests [Test] public void Delete_Success_ReturnsDeletedCount() { var target = CreateTestObject("DeleteCountTest"); var p = new JObject { ["action"] = "delete", ["target"] = "DeleteCountTest", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify object was actually deleted bool targetDeleted = target == null; Assert.IsTrue(targetDeleted, "Object should be deleted"); // Check for deleted count in response var data = resultObj["data"]; Assert.IsNotNull(data, "Response should include data"); // Verify the actual count if present if (data is JObject dataObj && dataObj.ContainsKey("deletedCount")) { Assert.AreEqual(1, dataObj.Value("deletedCount"), "Should report 1 deleted object"); } // Only remove from tracking if not already destroyed if (!targetDeleted) testObjects.Remove(target); } #endregion #region Edge Cases [Test] public void Delete_InactiveObject_StillDeletes() { var target = CreateTestObject("InactiveDeleteTarget"); target.SetActive(false); var p = new JObject { ["action"] = "delete", ["target"] = "InactiveDeleteTarget", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); // Capture current behavior for inactive objects Assert.IsNotNull(result, "Should return a result"); testObjects.Remove(target); } [Test] public void Delete_MultipleObjectsSameName_DeletesCorrectly() { // Expected behavior: delete action with by_name finds the FIRST matching object // and deletes only that one. This is consistent with Unity's GameObject.Find behavior. var target1 = CreateTestObject("DuplicateName"); var target2 = CreateTestObject("DuplicateName"); var p = new JObject { ["action"] = "delete", ["target"] = "DuplicateName", ["searchMethod"] = "by_name" }; var result = ManageGameObject.HandleCommand(p); var resultObj = result as JObject ?? JObject.FromObject(result); Assert.IsTrue(resultObj.Value("success"), resultObj.ToString()); // Verify deletion occurred - at least one should be deleted bool target1Deleted = target1 == null; bool target2Deleted = target2 == null; Assert.IsTrue(target1Deleted || target2Deleted, "At least one object should be deleted"); // Count remaining objects with the name to verify behavior int remainingCount = 0; if (!target1Deleted) remainingCount++; if (!target2Deleted) remainingCount++; // Document the actual behavior: first match is deleted, second survives // If both are deleted, that's also acceptable (bulk delete mode) Assert.IsTrue(remainingCount <= 1, $"Expected at most 1 remaining, got {remainingCount}"); // Clean up only survivors from tracking if (!target1Deleted) testObjects.Remove(target1); if (!target2Deleted) testObjects.Remove(target2); } #endregion } }