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
222 changes: 222 additions & 0 deletions Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#nullable enable
using NUnit.Framework;
using UnityEngine;

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

namespace io.github.hatayama.UnityCliLoop.Tests.Editor
{
/// <summary>
/// Characterizes wire-visible mouse UI response construction before factory extraction.
/// </summary>
[TestFixture]
public sealed class MouseUiSimulationResponseFactoryTests
{
/// <summary>
/// Verifies generic validation failures preserve the command action and supplied message.
/// </summary>
[Test]
public void CreateFailure_WithCommandAndMessage_MapsFailureResponse()
{
MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema
{
Action = UnityCliLoopMouseUiAction.LongPress
});

SimulateMouseUiResponse response =
MouseUiSimulationResponseFactory.CreateFailure(command, "Validation failed.");

Assert.That(response.Success, Is.False);
Assert.That(response.Message, Is.EqualTo("Validation failed."));
Assert.That(response.Action, Is.EqualTo(MouseAction.LongPress.ToString()));
}

/// <summary>
/// Verifies frame timeout responses preserve the hit and complete drag coordinates.
/// </summary>
[Test]
public void CreateFrameTimeoutResult_WithEndPosition_MapsAllCoordinates()
{
Vector2 position = new(10.25f, 20.75f);
Vector2 endPosition = new(30.5f, 40.25f);

SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(
MouseAction.Drag,
position,
endPosition,
"DragTarget");

Assert.That(response.Success, Is.False);
Assert.That(
response.Message,
Is.EqualTo(
$"Timed out after {UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS}ms while waiting for an editor frame."));
Assert.That(response.Action, Is.EqualTo(MouseAction.Drag.ToString()));
Assert.That(response.HitGameObjectName, Is.EqualTo("DragTarget"));
Assert.That(response.PositionX, Is.EqualTo(position.x));
Assert.That(response.PositionY, Is.EqualTo(position.y));
Assert.That(response.EndPositionX, Is.EqualTo(endPosition.x));
Assert.That(response.EndPositionY, Is.EqualTo(endPosition.y));
}

/// <summary>
/// Verifies frame timeout responses keep optional hit and end coordinates absent.
/// </summary>
[Test]
public void CreateFrameTimeoutResult_WithoutEndPosition_LeavesOptionalFieldsNull()
{
Vector2 position = new(10.25f, 20.75f);

SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(
MouseAction.Click,
position,
null,
null);

Assert.That(response.Action, Is.EqualTo(MouseAction.Click.ToString()));
Assert.That(response.HitGameObjectName, Is.Null);
Assert.That(response.EndPositionX, Is.Null);
Assert.That(response.EndPositionY, Is.Null);
}

/// <summary>
/// Verifies click results retain normal, bypass, and no-hit message contracts.
/// </summary>
[TestCase(false, true, "ClickTarget", "", "Clicked 'ClickTarget' at (10.3, 20.8)")]
[TestCase(true, true, "ClickTarget", "Canvas/ClickTarget", "Bypass-clicked 'ClickTarget' at (10.3, 20.8) via 'Canvas/ClickTarget'")]
[TestCase(false, false, null, "", "Clicked at (10.3, 20.8) - no UI element hit")]
public void CreateClickResult_WithHitMode_MapsMessageAndFields(
bool bypassRaycast,
bool hitTarget,
string? targetName,
string targetPath,
string expectedMessage)
{
MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema
{
Action = UnityCliLoopMouseUiAction.Click,
BypassRaycast = bypassRaycast,
TargetPath = targetPath
});
Vector2 position = new(10.25f, 20.75f);

SimulateMouseUiResponse response =
MouseUiSimulationResponseFactory.CreateClickResult(command, position, targetName, hitTarget);

Assert.That(response.Success, Is.True);
Assert.That(response.Message, Is.EqualTo(expectedMessage));
Assert.That(response.Action, Is.EqualTo(MouseAction.Click.ToString()));
Assert.That(response.HitGameObjectName, Is.EqualTo(targetName));
Assert.That(response.PositionX, Is.EqualTo(position.x));
Assert.That(response.PositionY, Is.EqualTo(position.y));
}

/// <summary>
/// Verifies long-press results retain normal, bypass, and no-hit message contracts.
/// </summary>
[TestCase(false, true, "PressTarget", "", "Long-pressed 'PressTarget' at (10.3, 20.8) for 1.3s")]
[TestCase(true, true, "PressTarget", "Canvas/PressTarget", "Bypass-long-pressed 'PressTarget' at (10.3, 20.8) via 'Canvas/PressTarget' for 1.3s")]
[TestCase(false, false, null, "", "Long-pressed at (10.3, 20.8) for 1.3s - no UI element hit")]
public void CreateLongPressResult_WithHitMode_MapsMessageAndFields(
bool bypassRaycast,
bool hitTarget,
string? targetName,
string targetPath,
string expectedMessage)
{
MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema
{
Action = UnityCliLoopMouseUiAction.LongPress,
BypassRaycast = bypassRaycast,
TargetPath = targetPath,
Duration = 1.25f
});
Vector2 position = new(10.25f, 20.75f);

SimulateMouseUiResponse response =
MouseUiSimulationResponseFactory.CreateLongPressResult(command, position, targetName, hitTarget);

Assert.That(response.Success, Is.True);
Assert.That(response.Message, Is.EqualTo(expectedMessage));
Assert.That(response.Action, Is.EqualTo(MouseAction.LongPress.ToString()));
Assert.That(response.HitGameObjectName, Is.EqualTo(targetName));
Assert.That(response.PositionX, Is.EqualTo(position.x));
Assert.That(response.PositionY, Is.EqualTo(position.y));
}

/// <summary>
/// Verifies one-shot drag results retain normal and bypass message contracts.
/// </summary>
[TestCase(false, "", "Dragged 'DragTarget' from (10.3, 20.8) to (30.5, 40.3) at 125 px/s")]
[TestCase(true, "Canvas/DragTarget", "Bypass-dragged 'DragTarget' from (10.3, 20.8) to (30.5, 40.3) via 'Canvas/DragTarget' at 125 px/s")]
public void CreateDragResult_WithBypassMode_MapsMessageAndFields(
bool bypassRaycast,
string targetPath,
string expectedMessage)
{
MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema
{
Action = UnityCliLoopMouseUiAction.Drag,
BypassRaycast = bypassRaycast,
TargetPath = targetPath,
DragSpeed = 125f
});
Vector2 startPosition = new(10.25f, 20.75f);
Vector2 endPosition = new(30.5f, 40.25f);

SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateDragResult(
command,
startPosition,
endPosition,
"DragTarget");

Assert.That(response.Success, Is.True);
Assert.That(response.Message, Is.EqualTo(expectedMessage));
Assert.That(response.Action, Is.EqualTo(MouseAction.Drag.ToString()));
Assert.That(response.HitGameObjectName, Is.EqualTo("DragTarget"));
Assert.That(response.PositionX, Is.EqualTo(startPosition.x));
Assert.That(response.PositionY, Is.EqualTo(startPosition.y));
Assert.That(response.EndPositionX, Is.EqualTo(endPosition.x));
Assert.That(response.EndPositionY, Is.EqualTo(endPosition.y));
}

/// <summary>
/// Verifies drag-end results preserve the final position and speed message.
/// </summary>
[Test]
public void CreateDragEndResult_WithFinalPosition_MapsMessageAndFields()
{
MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema
{
Action = UnityCliLoopMouseUiAction.DragEnd,
DragSpeed = 125f
});
Vector2 endPosition = new(30.5f, 40.25f);

SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateDragEndResult(
command,
endPosition,
"DragTarget");

Assert.That(response.Success, Is.True);
Assert.That(
response.Message,
Is.EqualTo("Drag ended on 'DragTarget' at (30.5, 40.3) at 125 px/s"));
Assert.That(response.Action, Is.EqualTo(MouseAction.DragEnd.ToString()));
Assert.That(response.HitGameObjectName, Is.EqualTo("DragTarget"));
Assert.That(response.PositionX, Is.EqualTo(endPosition.x));
Assert.That(response.PositionY, Is.EqualTo(endPosition.y));
}

private static MouseUiSimulationCommand CreateCommand(SimulateMouseUiSchema schema)
{
(MouseUiSimulationCommand? command, string? errorMessage) =
MouseUiSimulationCommand.TryFromSchema(schema);
Assert.That(errorMessage, Is.Null);
Assert.That(command, Is.Not.Null);
return command!;
}
}
}
11 changes: 11 additions & 0 deletions Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.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,124 @@
#nullable enable
using UnityEngine;

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

namespace io.github.hatayama.UnityCliLoop.FirstPartyTools
{
/// <summary>
/// Creates wire-visible responses for mouse UI simulation outcomes.
/// </summary>
internal static class MouseUiSimulationResponseFactory
{
internal static SimulateMouseUiResponse CreateFailure(
MouseUiSimulationCommand parameters,
string message)
{
return new SimulateMouseUiResponse
{
Success = false,
Message = message,
Action = parameters.Action.ToString()
};
}

internal static SimulateMouseUiResponse CreateFrameTimeoutResult(
MouseAction action,
Vector2 position,
Vector2? endPosition,
string? hitGameObjectName)
{
return new SimulateMouseUiResponse
{
Success = false,
Message = $"Timed out after {UnityCliLoopConstants.EDITOR_FRAME_WAIT_TIMEOUT_MS}ms while waiting for an editor frame.",
Action = action.ToString(),
HitGameObjectName = hitGameObjectName,
PositionX = position.x,
PositionY = position.y,
EndPositionX = endPosition.HasValue ? endPosition.Value.x : null,
EndPositionY = endPosition.HasValue ? endPosition.Value.y : null
};
}

internal static SimulateMouseUiResponse CreateClickResult(
MouseUiSimulationCommand parameters,
Vector2 inputPos,
string? targetName,
bool hitTarget)
{
return new SimulateMouseUiResponse
{
Success = true,
Message = hitTarget
? parameters.BypassRaycast
? $"Bypass-clicked '{targetName}' at ({inputPos.x:F1}, {inputPos.y:F1}) via '{parameters.TargetPath}'"
: $"Clicked '{targetName}' at ({inputPos.x:F1}, {inputPos.y:F1})"
: $"Clicked at ({inputPos.x:F1}, {inputPos.y:F1}) - no UI element hit",
Action = MouseAction.Click.ToString(),
HitGameObjectName = targetName,
PositionX = inputPos.x,
PositionY = inputPos.y
};
}

internal static SimulateMouseUiResponse CreateLongPressResult(
MouseUiSimulationCommand parameters,
Vector2 inputPos,
string? targetName,
bool hitTarget)
{
return new SimulateMouseUiResponse
{
Success = true,
Message = hitTarget
? parameters.BypassRaycast
? $"Bypass-long-pressed '{targetName}' at ({inputPos.x:F1}, {inputPos.y:F1}) via '{parameters.TargetPath}' for {parameters.Duration:F1}s"
: $"Long-pressed '{targetName}' at ({inputPos.x:F1}, {inputPos.y:F1}) for {parameters.Duration:F1}s"
: $"Long-pressed at ({inputPos.x:F1}, {inputPos.y:F1}) for {parameters.Duration:F1}s - no UI element hit",
Action = MouseAction.LongPress.ToString(),
HitGameObjectName = targetName,
PositionX = inputPos.x,
PositionY = inputPos.y
};
}

internal static SimulateMouseUiResponse CreateDragResult(
MouseUiSimulationCommand parameters,
Vector2 inputStart,
Vector2 inputEnd,
string targetName)
{
return new SimulateMouseUiResponse
{
Success = true,
Message = parameters.BypassRaycast
? $"Bypass-dragged '{targetName}' from ({inputStart.x:F1}, {inputStart.y:F1}) to ({inputEnd.x:F1}, {inputEnd.y:F1}) via '{parameters.TargetPath}' at {parameters.DragSpeed:F0} px/s"
: $"Dragged '{targetName}' from ({inputStart.x:F1}, {inputStart.y:F1}) to ({inputEnd.x:F1}, {inputEnd.y:F1}) at {parameters.DragSpeed:F0} px/s",
Action = MouseAction.Drag.ToString(),
HitGameObjectName = targetName,
PositionX = inputStart.x,
PositionY = inputStart.y,
EndPositionX = inputEnd.x,
EndPositionY = inputEnd.y
};
}

internal static SimulateMouseUiResponse CreateDragEndResult(
MouseUiSimulationCommand parameters,
Vector2 inputEnd,
string targetName)
{
return new SimulateMouseUiResponse
{
Success = true,
Message = $"Drag ended on '{targetName}' at ({inputEnd.x:F1}, {inputEnd.y:F1}) at {parameters.DragSpeed:F0} px/s",
Action = MouseAction.DragEnd.ToString(),
HitGameObjectName = targetName,
PositionX = inputEnd.x,
PositionY = inputEnd.y
};
}
}
}

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

Loading
Loading