Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions Assets/Tests/Editor/MouseUiDragTargetResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#nullable enable
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.EventSystems;

using io.github.hatayama.UnityCliLoop.FirstPartyTools;
using io.github.hatayama.UnityCliLoop.Runtime;

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Verifies drag target resolution shared by one-shot and incremental mouse UI drags.
/// </summary>
[TestFixture]
public sealed class MouseUiDragTargetResolverTests
{
private readonly List<GameObject> createdObjects = new();

[TearDown]
public void TearDown()
{
for (int index = createdObjects.Count - 1; index >= 0; index--)
{
GameObject gameObject = createdObjects[index];
if (gameObject != null)
{
Object.DestroyImmediate(gameObject);
}
}

createdObjects.Clear();
}

/// <summary>
/// Verifies bypass resolution preserves the raw raycast target and resolves its hierarchy drag handler.
/// </summary>
[Test]
public void Resolve_WithBypassPathBelowDragHandler_ReturnsHandlerAndRawRaycast()
{
EventSystem eventSystem = CreateEventSystem();
GameObject expectedTarget = CreateGameObject("MouseUiDragTargetResolverTests_Handler");
expectedTarget.AddComponent<MouseUiDragTargetResolverTestHandler>();
GameObject rawTarget = CreateGameObject("RawTarget", expectedTarget.transform);
MouseUiSimulationCommand command = CreateCommand(
UnityCliLoopMouseUiAction.Drag,
GameObjectPathUtility.GetFullPath(rawTarget));
Vector2 position = new(10f, 20f);

(RaycastResult startRaycast, GameObject? target, SimulateMouseUiResponse? failureResponse) =
MouseUiDragTargetResolver.Resolve(
command,
eventSystem,
MouseAction.Drag,
position,
position);

Assert.That(startRaycast.gameObject, Is.SameAs(rawTarget));
Assert.That(target, Is.SameAs(expectedTarget));
Assert.That(failureResponse, Is.Null);
}

/// <summary>
/// Verifies bypass resolution keeps the raw raycast while returning no target when no drag handler exists.
/// </summary>
[Test]
public void Resolve_WithBypassPathWithoutDragHandler_ReturnsRawRaycastWithoutFailure()
{
EventSystem eventSystem = CreateEventSystem();
GameObject rawTarget = CreateGameObject("MouseUiDragTargetResolverTests_NoHandler");
MouseUiSimulationCommand command = CreateCommand(
UnityCliLoopMouseUiAction.DragStart,
GameObjectPathUtility.GetFullPath(rawTarget));
Vector2 position = new(30f, 40f);

(RaycastResult startRaycast, GameObject? target, SimulateMouseUiResponse? failureResponse) =
MouseUiDragTargetResolver.Resolve(
command,
eventSystem,
MouseAction.DragStart,
position,
position);

Assert.That(startRaycast.gameObject, Is.SameAs(rawTarget));
Assert.That(target, Is.Null);
Assert.That(failureResponse, Is.Null);
}

/// <summary>
/// Verifies a missing bypass path preserves the requested action and input position in the failure response.
/// </summary>
[Test]
public void Resolve_WithMissingBypassPath_ReturnsPathFailure()
{
EventSystem eventSystem = CreateEventSystem();
string targetPath = "MouseUiDragTargetResolverTests_Missing/Target";
MouseUiSimulationCommand command = CreateCommand(
UnityCliLoopMouseUiAction.DragStart,
targetPath);
Vector2 position = new(50f, 60f);

(RaycastResult startRaycast, GameObject? target, SimulateMouseUiResponse? failureResponse) =
MouseUiDragTargetResolver.Resolve(
command,
eventSystem,
MouseAction.DragStart,
position,
position);

Assert.That(startRaycast.gameObject, Is.Null);
Assert.That(target, Is.Null);
Assert.That(failureResponse, Is.Not.Null);
Assert.That(failureResponse!.Message, Is.EqualTo($"TargetPath '{targetPath}' was not found."));
Assert.That(failureResponse.Action, Is.EqualTo(MouseAction.DragStart.ToString()));
Assert.That(failureResponse.PositionX, Is.EqualTo(position.x));
Assert.That(failureResponse.PositionY, Is.EqualTo(position.y));
}

private EventSystem CreateEventSystem()
{
return CreateGameObject("MouseUiDragTargetResolverTests_EventSystem").AddComponent<EventSystem>();
}

private GameObject CreateGameObject(string name, Transform? parent = null)
{
GameObject gameObject = new(name);
createdObjects.Add(gameObject);
if (parent != null)
{
gameObject.transform.SetParent(parent);
}

return gameObject;
}

private static MouseUiSimulationCommand CreateCommand(
UnityCliLoopMouseUiAction action,
string targetPath)
{
(MouseUiSimulationCommand? command, string? errorMessage) =
MouseUiSimulationCommand.TryFromSchema(new SimulateMouseUiSchema
{
Action = action,
BypassRaycast = true,
TargetPath = targetPath
});
Assert.That(errorMessage, Is.Null);
Assert.That(command, Is.Not.Null);
return command!;
}
}

internal sealed class MouseUiDragTargetResolverTestHandler : MonoBehaviour, IDragHandler
{
public void OnDrag(PointerEventData eventData)
{
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Editor/MouseUiDragTargetResolverTests.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#nullable enable
using UnityEngine;
using UnityEngine.EventSystems;

using io.github.hatayama.UnityCliLoop.Runtime;

namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
{
/// <summary>
/// Resolves the draggable target and starting raycast for mouse UI drag actions.
/// </summary>
internal static class MouseUiDragTargetResolver
{
internal static (
RaycastResult StartRaycast,
GameObject? Target,
SimulateMouseUiResponse? FailureResponse) Resolve(
MouseUiSimulationCommand parameters,
EventSystem eventSystem,
MouseAction action,
Vector2 inputPosition,
Vector2 screenPosition)
{
RaycastResult? hit = parameters.BypassRaycast
? null
: UiRaycastHelper.RaycastUI(screenPosition, eventSystem);
RaycastResult startRaycast = new();
GameObject? rawTarget = null;

if (parameters.BypassRaycast)
{
if (!MouseUiPointerTargetResolver.TryResolveGameObjectPath(
parameters.TargetPath,
"TargetPath",
action,
inputPosition,
out rawTarget,
out SimulateMouseUiResponse? failureResponse))
{
return (startRaycast, null, failureResponse);
}

startRaycast = MouseUiPointerTargetResolver.CreateDirectRaycastResult(rawTarget!);
}
else if (hit != null)
{
rawTarget = hit.Value.gameObject;
startRaycast = hit.Value;
}

// Execute dispatches only to the exact target; resolve the actual drag handler up the hierarchy.
GameObject? target = rawTarget != null
? ExecuteEvents.GetEventHandler<IDragHandler>(rawTarget)
: null;

return (startRaycast, target, null);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading