From b7850dafe09fa904cde5d23805b213e0a0b4fb9e Mon Sep 17 00:00:00 2001 From: hatayama Date: Thu, 9 Jul 2026 04:18:49 +0900 Subject: [PATCH 1/2] Characterize mouse UI response construction Expose the existing response helpers at their final internal visibility and pin all current success, failure, timeout, bypass, hit, and no-hit response branches with synchronous EditMode tests before moving the implementation. --- .../MouseUiSimulationResponseFactoryTests.cs | 222 ++++++++++++++++++ ...seUiSimulationResponseFactoryTests.cs.meta | 11 + .../SimulateMouseUi/SimulateMouseUiUseCase.cs | 10 +- 3 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs create mode 100644 Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs.meta diff --git a/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs new file mode 100644 index 0000000000..cc9982fc66 --- /dev/null +++ b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs @@ -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 +{ + /// + /// Characterizes wire-visible mouse UI response construction before factory extraction. + /// + [TestFixture] + public sealed class MouseUiSimulationResponseFactoryTests + { + /// + /// Verifies generic validation failures preserve the command action and supplied message. + /// + [Test] + public void CreateFailure_WithCommandAndMessage_MapsFailureResponse() + { + MouseUiSimulationCommand command = CreateCommand(new SimulateMouseUiSchema + { + Action = UnityCliLoopMouseUiAction.LongPress + }); + + SimulateMouseUiResponse response = + MouseUiSimulationValidator.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())); + } + + /// + /// Verifies frame timeout responses preserve the hit and complete drag coordinates. + /// + [Test] + public void CreateFrameTimeoutResult_WithEndPosition_MapsAllCoordinates() + { + Vector2 position = new(10.25f, 20.75f); + Vector2 endPosition = new(30.5f, 40.25f); + + SimulateMouseUiResponse response = SimulateMouseUiUseCase.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)); + } + + /// + /// Verifies frame timeout responses keep optional hit and end coordinates absent. + /// + [Test] + public void CreateFrameTimeoutResult_WithoutEndPosition_LeavesOptionalFieldsNull() + { + Vector2 position = new(10.25f, 20.75f); + + SimulateMouseUiResponse response = SimulateMouseUiUseCase.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); + } + + /// + /// Verifies click results retain normal, bypass, and no-hit message contracts. + /// + [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 = + SimulateMouseUiUseCase.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)); + } + + /// + /// Verifies long-press results retain normal, bypass, and no-hit message contracts. + /// + [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 = + SimulateMouseUiUseCase.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)); + } + + /// + /// Verifies one-shot drag results retain normal and bypass message contracts. + /// + [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 = SimulateMouseUiUseCase.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)); + } + + /// + /// Verifies drag-end results preserve the final position and speed message. + /// + [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 = SimulateMouseUiUseCase.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!; + } + } +} diff --git a/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs.meta b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs.meta new file mode 100644 index 0000000000..34e14adcc8 --- /dev/null +++ b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8a4d821c67342d4ab063457c70d4072 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs index d1d1ac1721..c31e429c7b 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs @@ -1128,7 +1128,7 @@ private void ExecuteCleanupOnMainThread(Action cleanup) context.Post(_ => cleanup(), null); } - private static SimulateMouseUiResponse CreateFrameTimeoutResult( + internal static SimulateMouseUiResponse CreateFrameTimeoutResult( MouseAction action, Vector2 position, Vector2? endPosition, @@ -1147,7 +1147,7 @@ private static SimulateMouseUiResponse CreateFrameTimeoutResult( }; } - private static SimulateMouseUiResponse CreateClickResult( + internal static SimulateMouseUiResponse CreateClickResult( MouseUiSimulationCommand parameters, Vector2 inputPos, string? targetName, @@ -1168,7 +1168,7 @@ private static SimulateMouseUiResponse CreateClickResult( }; } - private static SimulateMouseUiResponse CreateLongPressResult( + internal static SimulateMouseUiResponse CreateLongPressResult( MouseUiSimulationCommand parameters, Vector2 inputPos, string? targetName, @@ -1189,7 +1189,7 @@ private static SimulateMouseUiResponse CreateLongPressResult( }; } - private static SimulateMouseUiResponse CreateDragResult( + internal static SimulateMouseUiResponse CreateDragResult( MouseUiSimulationCommand parameters, Vector2 inputStart, Vector2 inputEnd, @@ -1210,7 +1210,7 @@ private static SimulateMouseUiResponse CreateDragResult( }; } - private static SimulateMouseUiResponse CreateDragEndResult( + internal static SimulateMouseUiResponse CreateDragEndResult( MouseUiSimulationCommand parameters, Vector2 inputEnd, string targetName) From adfb3d30ac5dddbb922d6356e2f2af4d66701f70 Mon Sep 17 00:00:00 2001 From: hatayama Date: Thu, 9 Jul 2026 04:23:03 +0900 Subject: [PATCH 2/2] Extract mouse UI simulation response factory Move the six existing response-construction helpers into a stateless factory and switch the use case, validator, and characterization tests to direct calls. Preserve all response fields, messages, nullability, and timeout constants without retaining compatibility wrappers. --- .../MouseUiSimulationResponseFactoryTests.cs | 14 +- .../MouseUiSimulationResponseFactory.cs | 124 +++++++++++++++ .../MouseUiSimulationResponseFactory.cs.meta | 11 ++ .../MouseUiSimulationValidator.cs | 28 +--- .../SimulateMouseUi/SimulateMouseUiUseCase.cs | 144 +++--------------- 5 files changed, 173 insertions(+), 148 deletions(-) create mode 100644 Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs create mode 100644 Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs.meta diff --git a/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs index cc9982fc66..f7f9ab351b 100644 --- a/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs +++ b/Assets/Tests/Editor/MouseUiSimulationResponseFactoryTests.cs @@ -26,7 +26,7 @@ public void CreateFailure_WithCommandAndMessage_MapsFailureResponse() }); SimulateMouseUiResponse response = - MouseUiSimulationValidator.CreateFailure(command, "Validation failed."); + MouseUiSimulationResponseFactory.CreateFailure(command, "Validation failed."); Assert.That(response.Success, Is.False); Assert.That(response.Message, Is.EqualTo("Validation failed.")); @@ -42,7 +42,7 @@ public void CreateFrameTimeoutResult_WithEndPosition_MapsAllCoordinates() Vector2 position = new(10.25f, 20.75f); Vector2 endPosition = new(30.5f, 40.25f); - SimulateMouseUiResponse response = SimulateMouseUiUseCase.CreateFrameTimeoutResult( + SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateFrameTimeoutResult( MouseAction.Drag, position, endPosition, @@ -69,7 +69,7 @@ public void CreateFrameTimeoutResult_WithoutEndPosition_LeavesOptionalFieldsNull { Vector2 position = new(10.25f, 20.75f); - SimulateMouseUiResponse response = SimulateMouseUiUseCase.CreateFrameTimeoutResult( + SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateFrameTimeoutResult( MouseAction.Click, position, null, @@ -103,7 +103,7 @@ public void CreateClickResult_WithHitMode_MapsMessageAndFields( Vector2 position = new(10.25f, 20.75f); SimulateMouseUiResponse response = - SimulateMouseUiUseCase.CreateClickResult(command, position, targetName, hitTarget); + MouseUiSimulationResponseFactory.CreateClickResult(command, position, targetName, hitTarget); Assert.That(response.Success, Is.True); Assert.That(response.Message, Is.EqualTo(expectedMessage)); @@ -136,7 +136,7 @@ public void CreateLongPressResult_WithHitMode_MapsMessageAndFields( Vector2 position = new(10.25f, 20.75f); SimulateMouseUiResponse response = - SimulateMouseUiUseCase.CreateLongPressResult(command, position, targetName, hitTarget); + MouseUiSimulationResponseFactory.CreateLongPressResult(command, position, targetName, hitTarget); Assert.That(response.Success, Is.True); Assert.That(response.Message, Is.EqualTo(expectedMessage)); @@ -166,7 +166,7 @@ public void CreateDragResult_WithBypassMode_MapsMessageAndFields( Vector2 startPosition = new(10.25f, 20.75f); Vector2 endPosition = new(30.5f, 40.25f); - SimulateMouseUiResponse response = SimulateMouseUiUseCase.CreateDragResult( + SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateDragResult( command, startPosition, endPosition, @@ -195,7 +195,7 @@ public void CreateDragEndResult_WithFinalPosition_MapsMessageAndFields() }); Vector2 endPosition = new(30.5f, 40.25f); - SimulateMouseUiResponse response = SimulateMouseUiUseCase.CreateDragEndResult( + SimulateMouseUiResponse response = MouseUiSimulationResponseFactory.CreateDragEndResult( command, endPosition, "DragTarget"); diff --git a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs new file mode 100644 index 0000000000..739f75311b --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs @@ -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 +{ + /// + /// Creates wire-visible responses for mouse UI simulation outcomes. + /// + 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 + }; + } + } +} diff --git a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs.meta b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs.meta new file mode 100644 index 0000000000..1014954af7 --- /dev/null +++ b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationResponseFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b56a2e6129684475a65e9cfdfa18ccfd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationValidator.cs b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationValidator.cs index 0c1d16be2d..0a0b3db02d 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationValidator.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/MouseUiSimulationValidator.cs @@ -20,12 +20,12 @@ internal static class MouseUiSimulationValidator PlayModeToolPreflightService.RequireActiveAndNotPaused(pausedActionDescription); if (!playModeResult.IsValid) { - return CreateFailure(parameters, playModeResult.ErrorMessage); + return MouseUiSimulationResponseFactory.CreateFailure(parameters, playModeResult.ErrorMessage); } if (eventSystem == null) { - return CreateFailure( + return MouseUiSimulationResponseFactory.CreateFailure( parameters, "No EventSystem found in the scene. Ensure an EventSystem GameObject exists."); } @@ -40,48 +40,36 @@ internal static class MouseUiSimulationValidator return null; } - return CreateFailure( + return MouseUiSimulationResponseFactory.CreateFailure( parameters, $"Cannot {parameters.Action.ToString()} while a split drag is active. Call DragEnd first."); } - internal static SimulateMouseUiResponse CreateFailure( - MouseUiSimulationCommand parameters, - string message) - { - return new SimulateMouseUiResponse - { - Success = false, - Message = message, - Action = parameters.Action.ToString() - }; - } - private static SimulateMouseUiResponse? ValidateSimulationRequestOptions( MouseUiSimulationCommand parameters) { if (parameters.Action != MouseAction.Click && parameters.Action != MouseAction.LongPress && parameters.DragSpeed < 0f) { - return CreateFailure(parameters, $"DragSpeed must be non-negative, got: {parameters.DragSpeed}"); + return MouseUiSimulationResponseFactory.CreateFailure(parameters, $"DragSpeed must be non-negative, got: {parameters.DragSpeed}"); } if (IsDragAction(parameters.Action) && parameters.Button != MouseButton.Left) { - return CreateFailure( + return MouseUiSimulationResponseFactory.CreateFailure( parameters, $"Drag actions only support Left button (uGUI ignores non-left drags), got: {parameters.Button}"); } if (parameters.BypassRaycast && !SupportsBypassRaycast(parameters.Action)) { - return CreateFailure(parameters, "BypassRaycast is not supported for this action."); + return MouseUiSimulationResponseFactory.CreateFailure(parameters, "BypassRaycast is not supported for this action."); } if (parameters.BypassRaycast && RequiresBypassTargetPath(parameters.Action) && string.IsNullOrWhiteSpace(parameters.TargetPath)) { - return CreateFailure( + return MouseUiSimulationResponseFactory.CreateFailure( parameters, "TargetPath is required when BypassRaycast is true for Click, LongPress, Drag, or DragStart."); } @@ -90,7 +78,7 @@ internal static SimulateMouseUiResponse CreateFailure( parameters.Action != MouseAction.Drag && parameters.Action != MouseAction.DragEnd) { - return CreateFailure(parameters, "DropTargetPath supports Drag and DragEnd only."); + return MouseUiSimulationResponseFactory.CreateFailure(parameters, "DropTargetPath supports Drag and DragEnd only."); } return null; diff --git a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs index c31e429c7b..0e2afe7b94 100644 --- a/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs +++ b/Packages/src/Editor/FirstPartyTools/SimulateMouseUi/SimulateMouseUiUseCase.cs @@ -140,7 +140,7 @@ private async Task ExecuteMouseAction( default: // Unreachable when TryFromSchema succeeds; kept as a defensive Success=false response // instead of a throw so any future MouseAction addition surfaces as a validation failure. - return MouseUiSimulationValidator.CreateFailure( + return MouseUiSimulationResponseFactory.CreateFailure( parameters, $"Unknown mouse action: {parameters.Action}"); } @@ -214,7 +214,7 @@ private async Task ExecuteClick( if (!expandCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Click, inputPos, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Click, inputPos, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -225,11 +225,11 @@ private async Task ExecuteClick( if (!dissipateCompleted) { QueueOverlayClear(); - return CreateClickResult(parameters, inputPos, targetName, hitTarget); + return MouseUiSimulationResponseFactory.CreateClickResult(parameters, inputPos, targetName, hitTarget); } await MainThreadSwitcher.SwitchToMainThread(ct); - return CreateClickResult(parameters, inputPos, targetName, hitTarget); + return MouseUiSimulationResponseFactory.CreateClickResult(parameters, inputPos, targetName, hitTarget); } private async Task ExecuteLongPress( @@ -278,7 +278,7 @@ private async Task ExecuteLongPress( if (!expandCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.LongPress, inputPos, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.LongPress, inputPos, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -296,7 +296,7 @@ private async Task ExecuteLongPress( if (!frameReady) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.LongPress, inputPos, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.LongPress, inputPos, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); elapsed = Time.realtimeSinceStartup - startTime; @@ -317,11 +317,11 @@ private async Task ExecuteLongPress( if (!dissipateCompleted) { QueueOverlayClear(); - return CreateLongPressResult(parameters, inputPos, targetName, hitTarget); + return MouseUiSimulationResponseFactory.CreateLongPressResult(parameters, inputPos, targetName, hitTarget); } await MainThreadSwitcher.SwitchToMainThread(ct); - return CreateLongPressResult(parameters, inputPos, targetName, hitTarget); + return MouseUiSimulationResponseFactory.CreateLongPressResult(parameters, inputPos, targetName, hitTarget); } private static PointerEventData CreatePointerPressData( @@ -545,7 +545,7 @@ private async Task ExecuteDragOneShot( if (!expandCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, null); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, null); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -553,7 +553,7 @@ private async Task ExecuteDragOneShot( if (!dissipateCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, null); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, null); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -586,7 +586,7 @@ private async Task ExecuteDragOneShot( if (!expandCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -595,7 +595,7 @@ private async Task ExecuteDragOneShot( if (!dragCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -603,7 +603,7 @@ private async Task ExecuteDragOneShot( if (!frameReady) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.Drag, inputStart, inputEnd, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); } @@ -619,11 +619,11 @@ private async Task ExecuteDragOneShot( if (!completedDissipate) { QueueOverlayClear(); - return CreateDragResult(parameters, inputStart, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateDragResult(parameters, inputStart, inputEnd, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); - return CreateDragResult(parameters, inputStart, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateDragResult(parameters, inputStart, inputEnd, targetName); } // Lifecycle must match StandaloneInputModule: raycast → pointerUp → drop → endDrag @@ -774,7 +774,7 @@ private async Task ExecuteDragStart( if (!expandCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, null); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, null); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -782,7 +782,7 @@ private async Task ExecuteDragStart( if (!dissipateCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, null); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, null); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -816,7 +816,7 @@ private async Task ExecuteDragStart( if (!animationCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragStart, inputPos, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); animationCompleted = true; @@ -888,7 +888,7 @@ private async Task ExecuteDragMove( if (!dragCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragMove, inputEnd, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragMove, inputEnd, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -960,7 +960,7 @@ private async Task ExecuteDragEnd( if (!dragCompleted) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragEnd, inputEnd, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragEnd, inputEnd, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); @@ -968,7 +968,7 @@ private async Task ExecuteDragEnd( if (!frameReady) { QueueOverlayClear(); - return CreateFrameTimeoutResult(MouseAction.DragEnd, inputEnd, null, targetName); + return MouseUiSimulationResponseFactory.CreateFrameTimeoutResult(MouseAction.DragEnd, inputEnd, null, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); } @@ -988,11 +988,11 @@ private async Task ExecuteDragEnd( if (!dissipateCompleted) { QueueOverlayClear(); - return CreateDragEndResult(parameters, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateDragEndResult(parameters, inputEnd, targetName); } await MainThreadSwitcher.SwitchToMainThread(ct); - return CreateDragEndResult(parameters, inputEnd, targetName); + return MouseUiSimulationResponseFactory.CreateDragEndResult(parameters, inputEnd, targetName); } // User input during a CLI drag can cause Unity's StandaloneInputModule to @@ -1128,104 +1128,6 @@ private void ExecuteCleanupOnMainThread(Action cleanup) context.Post(_ => cleanup(), null); } - 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 - }; - } - private static RaycastResult? RaycastUI(Vector2 screenPosition, EventSystem eventSystem) { return UiRaycastHelper.RaycastUI(screenPosition, eventSystem);