diff --git a/My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs b/My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs index 1a2e8f9..a9d630b 100644 --- a/My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs +++ b/My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs @@ -1,15 +1,17 @@ using System; +using System.Collections; using System.Threading; using System.Threading.Tasks; using FlowScope.Resources; using NUnit.Framework; +using UnityEngine.TestTools; namespace FlowScope.Tests.PlayMode.Resources { public sealed class AddressablesResourceServiceTests { - [Test] - public void LoadAsync_WhenSameKeyLoadsConcurrently_SharesOneBackendLoad() + [UnityTest] + public IEnumerator LoadAsync_WhenSameKeyLoadsConcurrently_SharesOneBackendLoad() { var backendLoads = 0; var releaseCount = 0; @@ -26,8 +28,10 @@ namespace FlowScope.Tests.PlayMode.Resources var second = service.LoadAsync("shared", CancellationToken.None); gate.SetResult(new TestAsset()); - var firstHandle = first.GetAwaiter().GetResult(); - var secondHandle = second.GetAwaiter().GetResult(); + IResourceHandle firstHandle = null; + IResourceHandle secondHandle = null; + yield return AwaitTask(first, handle => firstHandle = handle); + yield return AwaitTask(second, handle => secondHandle = handle); Assert.AreEqual(1, backendLoads); Assert.AreSame(firstHandle.Asset, secondHandle.Asset); @@ -38,34 +42,58 @@ namespace FlowScope.Tests.PlayMode.Resources Assert.AreEqual(1, releaseCount); } - [Test] - public void LoadAsync_WhenBackendFails_IncludesKeyAndBackend() + [UnityTest] + public IEnumerator LoadAsync_WhenBackendFails_IncludesKeyAndBackend() { var service = new AddressablesResourceService( (_, _, _) => throw new InvalidOperationException("missing"), (_, _) => { }); - var exception = ThrowsAsync( - () => service.LoadAsync("missing-key", CancellationToken.None)); + InvalidOperationException exception = null; + yield return ThrowsAsync( + () => service.LoadAsync("missing-key", CancellationToken.None), + caught => exception = caught); StringAssert.Contains("missing-key", exception.Message); StringAssert.Contains("Addressables", exception.Message); } - private static TException ThrowsAsync(Func action) + private static IEnumerator ThrowsAsync(Func action, Action onCaught) where TException : Exception { - try + var task = action(); + while (!task.IsCompleted) { - action().GetAwaiter().GetResult(); + yield return null; } - catch (TException exception) + + if (task.IsFaulted && task.Exception?.GetBaseException() is TException exception) { - return exception; + onCaught(exception); + yield break; } Assert.Fail($"Expected {typeof(TException).Name}."); - return null; + } + + private static IEnumerator AwaitTask(Task task, Action onCompleted) + { + while (!task.IsCompleted) + { + yield return null; + } + + if (task.IsFaulted) + { + throw task.Exception.GetBaseException(); + } + + if (task.IsCanceled) + { + throw new OperationCanceledException(); + } + + onCompleted(task.Result); } private sealed class TestAsset diff --git a/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs b/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs index 0707f8a..e5ef2aa 100644 --- a/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs +++ b/My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs @@ -1,9 +1,13 @@ +using System; +using System.Collections; using System.Threading; using System.Threading.Tasks; using FlowScope.Samples.MainMenuP0; using NUnit.Framework; using UnityEngine; +using UnityEngine.TestTools; using UnityEngine.UI; +using Object = UnityEngine.Object; namespace FlowScope.Tests.PlayMode.Samples { @@ -20,8 +24,8 @@ namespace FlowScope.Tests.PlayMode.Samples Assert.AreEqual(1, data.Gold.Value); } - [Test] - public void Bootstrap_CanConstructAndShutdownWithoutSceneAsset() + [UnityTest] + public IEnumerator Bootstrap_CanConstructAndShutdownWithoutSceneAsset() { var root = new GameObject("bootstrap-test"); var panelObject = new GameObject("panel-prefab"); @@ -36,10 +40,28 @@ namespace FlowScope.Tests.PlayMode.Samples panel.Configure(label, button); var bootstrap = root.AddComponent(); - bootstrap.ShutdownAsync(CancellationToken.None).GetAwaiter().GetResult(); + yield return AwaitTask(bootstrap.ShutdownAsync(CancellationToken.None)); Object.DestroyImmediate(panelObject); Object.DestroyImmediate(root); } + + private static IEnumerator AwaitTask(Task task) + { + while (!task.IsCompleted) + { + yield return null; + } + + if (task.IsFaulted) + { + throw task.Exception.GetBaseException(); + } + + if (task.IsCanceled) + { + throw new OperationCanceledException(); + } + } } } diff --git a/My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs b/My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs index ddd0178..ad7fae8 100644 --- a/My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs +++ b/My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -6,6 +7,7 @@ using FlowScope.Resources; using FlowScope.UI; using NUnit.Framework; using UnityEngine; +using UnityEngine.TestTools; namespace FlowScope.Tests.PlayMode.UI { @@ -27,8 +29,8 @@ namespace FlowScope.Tests.PlayMode.UI _objects.Clear(); } - [Test] - public void OpenAsync_UsesAttributePathAndMountsPanelToRegisteredLayer() + [UnityTest] + public IEnumerator OpenAsync_UsesAttributePathAndMountsPanelToRegisteredLayer() { var canvas = CreateCanvas("popup"); var prefab = CreatePrefab("ExplicitPathPrefab"); @@ -38,7 +40,10 @@ namespace FlowScope.Tests.PlayMode.UI manager.RegisterLayer("popup", canvas, 120); var viewModel = new TestViewModel("first"); - var panel = manager.OpenAsync(viewModel, CancellationToken.None).GetAwaiter().GetResult(); + ExplicitPathPanel panel = null; + yield return AwaitTask( + manager.OpenAsync(viewModel, CancellationToken.None), + result => panel = result); Assert.That(resources.LoadedKeys, Is.EqualTo(new[] { "Custom/Explicit" })); Assert.That(panel.transform.parent, Is.EqualTo(canvas.transform)); @@ -48,8 +53,8 @@ namespace FlowScope.Tests.PlayMode.UI Assert.That(manager.IsOpen(), Is.True); } - [Test] - public void OpenAsync_WhenPathIsMissing_UsesPanelNameConvention() + [UnityTest] + public IEnumerator OpenAsync_WhenPathIsMissing_UsesPanelNameConvention() { var canvas = CreateCanvas("hud"); var prefab = CreatePrefab("MainHudPrefab"); @@ -58,13 +63,13 @@ namespace FlowScope.Tests.PlayMode.UI var manager = new UIManager(resources); manager.RegisterLayer("hud", canvas, 10); - manager.OpenAsync(new TestViewModel("hud"), CancellationToken.None).GetAwaiter().GetResult(); + yield return AwaitTask(manager.OpenAsync(new TestViewModel("hud"), CancellationToken.None)); Assert.That(resources.LoadedKeys, Is.EqualTo(new[] { "UI/MainHud/Prefab" })); } - [Test] - public void Close_WhenPanelIsNotLayerTop_ThrowsInvalidOperationException() + [UnityTest] + public IEnumerator Close_WhenPanelIsNotLayerTop_ThrowsInvalidOperationException() { var canvas = CreateCanvas("popup"); var resources = new FakeResourceService(); @@ -72,16 +77,16 @@ namespace FlowScope.Tests.PlayMode.UI resources.Register("UI/Second/Prefab", CreatePrefab("SecondPrefab")); var manager = new UIManager(resources); manager.RegisterLayer("popup", canvas, 0); - manager.OpenAsync(new TestViewModel("first"), CancellationToken.None).GetAwaiter().GetResult(); - manager.OpenAsync(new TestViewModel("second"), CancellationToken.None).GetAwaiter().GetResult(); + yield return AwaitTask(manager.OpenAsync(new TestViewModel("first"), CancellationToken.None)); + yield return AwaitTask(manager.OpenAsync(new TestViewModel("second"), CancellationToken.None)); Assert.Throws(() => manager.Close()); Assert.That(manager.IsOpen(), Is.True); Assert.That(manager.IsOpen(), Is.True); } - [Test] - public void Close_WithDestroyStrategy_UnbindsDestroysInstanceAndDisposesHandle() + [UnityTest] + public IEnumerator Close_WithDestroyStrategy_UnbindsDestroysInstanceAndDisposesHandle() { var canvas = CreateCanvas("popup"); var prefab = CreatePrefab("ExplicitPathPrefab"); @@ -89,7 +94,10 @@ namespace FlowScope.Tests.PlayMode.UI resources.Register("Custom/Explicit", prefab); var manager = new UIManager(resources); manager.RegisterLayer("popup", canvas, 0, PanelStrategy.Destroy); - var panel = manager.OpenAsync(new TestViewModel("first"), CancellationToken.None).GetAwaiter().GetResult(); + ExplicitPathPanel panel = null; + yield return AwaitTask( + manager.OpenAsync(new TestViewModel("first"), CancellationToken.None), + result => panel = result); var handle = resources.Handles[0]; manager.Close(); @@ -100,8 +108,8 @@ namespace FlowScope.Tests.PlayMode.UI Assert.That(manager.IsOpen(), Is.False); } - [Test] - public void OpenAsync_WithCacheStrategy_ReusesHiddenInstanceAndBindsAgain() + [UnityTest] + public IEnumerator OpenAsync_WithCacheStrategy_ReusesHiddenInstanceAndBindsAgain() { var canvas = CreateCanvas("dialog"); var prefab = CreatePrefab("CachedPrefab"); @@ -112,9 +120,15 @@ namespace FlowScope.Tests.PlayMode.UI var firstViewModel = new TestViewModel("first"); var secondViewModel = new TestViewModel("second"); - var first = manager.OpenAsync(firstViewModel, CancellationToken.None).GetAwaiter().GetResult(); + CachedPanel first = null; + yield return AwaitTask( + manager.OpenAsync(firstViewModel, CancellationToken.None), + result => first = result); manager.Close(); - var second = manager.OpenAsync(secondViewModel, CancellationToken.None).GetAwaiter().GetResult(); + CachedPanel second = null; + yield return AwaitTask( + manager.OpenAsync(secondViewModel, CancellationToken.None), + result => second = result); Assert.That(second, Is.SameAs(first)); Assert.That(second.gameObject.activeSelf, Is.True); @@ -124,8 +138,8 @@ namespace FlowScope.Tests.PlayMode.UI Assert.That(resources.Handles[0].IsDisposed, Is.False); } - [Test] - public void CloseLayer_OnlyClosesRequestedLayer() + [UnityTest] + public IEnumerator CloseLayer_OnlyClosesRequestedLayer() { var popupCanvas = CreateCanvas("popup"); var hudCanvas = CreateCanvas("hud"); @@ -135,8 +149,8 @@ namespace FlowScope.Tests.PlayMode.UI var manager = new UIManager(resources); manager.RegisterLayer("popup", popupCanvas, 0); manager.RegisterLayer("hud", hudCanvas, 0); - manager.OpenAsync(new TestViewModel("popup"), CancellationToken.None).GetAwaiter().GetResult(); - manager.OpenAsync(new TestViewModel("hud"), CancellationToken.None).GetAwaiter().GetResult(); + yield return AwaitTask(manager.OpenAsync(new TestViewModel("popup"), CancellationToken.None)); + yield return AwaitTask(manager.OpenAsync(new TestViewModel("hud"), CancellationToken.None)); manager.CloseLayer("popup"); @@ -144,8 +158,8 @@ namespace FlowScope.Tests.PlayMode.UI Assert.That(manager.IsOpen(), Is.True); } - [Test] - public void OpenAsync_WhenBindThrows_DisposesHandleAndDestroysCreatedInstance() + [UnityTest] + public IEnumerator OpenAsync_WhenBindThrows_DisposesHandleAndDestroysCreatedInstance() { var canvas = CreateCanvas("popup"); var prefab = CreatePrefab("ThrowingPrefab"); @@ -154,7 +168,7 @@ namespace FlowScope.Tests.PlayMode.UI var manager = new UIManager(resources); manager.RegisterLayer("popup", canvas, 0); - ThrowsAsync( + yield return ThrowsAsync( () => manager.OpenAsync(new TestViewModel("boom"), CancellationToken.None)); Assert.That(resources.Handles[0].IsDisposed, Is.True); @@ -169,20 +183,21 @@ namespace FlowScope.Tests.PlayMode.UI return gameObject.AddComponent(); } - private static TException ThrowsAsync(Func action) + private static IEnumerator ThrowsAsync(Func action) where TException : Exception { - try + var task = action(); + while (!task.IsCompleted) { - action().GetAwaiter().GetResult(); + yield return null; } - catch (TException exception) + + if (task.IsFaulted && task.Exception?.GetBaseException() is TException) { - return exception; + yield break; } Assert.Fail($"Expected {typeof(TException).Name}."); - return null; } private GameObject CreatePrefab(string name) @@ -194,6 +209,44 @@ namespace FlowScope.Tests.PlayMode.UI return gameObject; } + private static IEnumerator AwaitTask(Task task) + { + while (!task.IsCompleted) + { + yield return null; + } + + if (task.IsFaulted) + { + throw task.Exception.GetBaseException(); + } + + if (task.IsCanceled) + { + throw new OperationCanceledException(); + } + } + + private static IEnumerator AwaitTask(Task task, Action onCompleted) + { + while (!task.IsCompleted) + { + yield return null; + } + + if (task.IsFaulted) + { + throw task.Exception.GetBaseException(); + } + + if (task.IsCanceled) + { + throw new OperationCanceledException(); + } + + onCompleted(task.Result); + } + private sealed class FakeResourceService : IResourceService { private readonly Dictionary _assets = new();