using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using Newtonsoft.Json.Linq;
using MCPForUnity.Editor.Tools.GameObjects;
namespace MCPForUnityTests.Editor.Tools
{
/// <summary>
/// Comprehensive baseline tests for ManageGameObject "delete" action.
/// These tests capture existing behavior before API redesign.
/// </summary>
public class ManageGameObjectDeleteTests
{
private List<GameObject> testObjects = new List<GameObject>();
[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<bool>("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<bool>("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<bool>("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<bool>("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<bool>("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<bool>("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<bool>("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<bool>("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<bool>("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<int>("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<bool>("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
}
}