diff --git a/CHANGELOG.md b/CHANGELOG.md index d2325fcd3..a473f3b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ The following section list all changes in 1.0.0 preview 01. ### Added List of new features. +- Added support for casting `BUnitJSRuntime` to `IJSInProcessRuntime` and `IJSUnmarshalledRuntime`. By [@KristofferStrube](https://github.com/KristofferStrube) in [#279](https://github.com/egil/bUnit/pull/279) + - Added support for triggering `@ontoggle` event handlers through a dedicated `Toggle()` method. By [@egil](https://github.com/egil) in [#256](https://github.com/egil/bUnit/pull/256). - Added out of the box support for `` component. When a `` component is used in a component under test, it's JavaScript interop-calls are faked by bUnits JSInterop, and it should result in all items being rendered immediately. By [@egil](https://github.com/egil) in [#240](https://github.com/egil/bUnit/issues/240). diff --git a/src/bunit.web/JSInterop/BunitJSInterop.cs b/src/bunit.web/JSInterop/BunitJSInterop.cs index 150fe58de..8bc4e13de 100644 --- a/src/bunit.web/JSInterop/BunitJSInterop.cs +++ b/src/bunit.web/JSInterop/BunitJSInterop.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -179,7 +180,13 @@ private void RegisterInvocation(JSRuntimeInvocation invocation) return result; } - private class BUnitJSRuntime : IJSRuntime + + [SuppressMessage("Design", "CA2012:ValueTask instances should not have their result directly accessed unless the instance has already completed.", Justification = "The ValueTask always wraps a Task object.")] +#if NET5_0 + private class BUnitJSRuntime : IJSRuntime, IJSInProcessRuntime, IJSUnmarshalledRuntime +#else + private class BUnitJSRuntime : IJSRuntime, IJSInProcessRuntime + #endif { private readonly BunitJSInterop _jsInterop; @@ -188,6 +195,9 @@ public BUnitJSRuntime(BunitJSInterop bunitJsInterop) _jsInterop = bunitJsInterop; } + public TResult Invoke(string identifier, params object?[]? args) => + InvokeAsync(identifier, args).GetAwaiter().GetResult(); + public ValueTask InvokeAsync(string identifier, object?[]? args) => InvokeAsync(identifier, default, args); @@ -199,6 +209,18 @@ public ValueTask InvokeAsync(string identifier, CancellationToke return TryHandlePlannedInvocation(invocation) ?? new ValueTask(default(TValue)!); } + public TResult InvokeUnmarshalled(string identifier) => + InvokeAsync(identifier, Array.Empty()).GetAwaiter().GetResult(); + + public TResult InvokeUnmarshalled(string identifier, T0 arg0) => + InvokeAsync(identifier, new object?[] {arg0}).GetAwaiter().GetResult(); + + public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1) => + InvokeAsync(identifier, new object?[] { arg0, arg1 }).GetAwaiter().GetResult(); + + public TResult InvokeUnmarshalled(string identifier, T0 arg0, T1 arg1, T2 arg2) => + InvokeAsync(identifier, new object?[] { arg0, arg1, arg2 }).GetAwaiter().GetResult(); + private ValueTask? TryHandlePlannedInvocation(JSRuntimeInvocation invocation) { ValueTask? result = default; diff --git a/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs b/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs index 2004cfe77..1864678fb 100644 --- a/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs +++ b/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs @@ -10,7 +10,7 @@ namespace Bunit.JSInterop { - public class BunitJSInteropTest + public partial class BunitJSInteropTest { private static BunitJSInterop CreateSut(JSRuntimeMode mode) => new BunitJSInterop { Mode = mode }; @@ -435,5 +435,57 @@ public void Test042() actual.ShouldBe(expected); } + + [Fact(DisplayName = "Mock returns default value from IJSInProcessRuntime's invoke method in loose mode without invocation setup")] + public void Test043() + { + var sut = CreateSut(JSRuntimeMode.Loose); + + var result = ((IJSInProcessRuntime)sut.JSRuntime).Invoke("ident", Array.Empty()); + + result.ShouldBe(default); + } + + [Fact(DisplayName = "After IJSInProcessRuntime invocation a invocation should be visible from the Invocations list")] + public void Test044() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz" }; + var sut = CreateSut(JSRuntimeMode.Loose); + + var _ = ((IJSInProcessRuntime)sut.JSRuntime).Invoke(identifier, args); + + var invocation = sut.Invocations[identifier].Single(); + invocation.Identifier.ShouldBe(identifier); + invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "IJSInProcessRuntime invocations receive the result set in a planned invocation")] + public void Test045() + { + var identifier = "func"; + var args = new[] { "bar", "baz" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var expectedResult = Guid.NewGuid(); + var planned = sut.Setup(identifier, args); + planned.SetResult(expectedResult); + + var i = ((IJSInProcessRuntime)sut.JSRuntime).Invoke(identifier, args); + + i.ShouldBe(expectedResult); + } + + [Fact(DisplayName = "Mock throws exception when in strict mode and IJSInProcessRuntime invocation has not been setup")] + public void Test046() + { + var sut = CreateSut(JSRuntimeMode.Strict); + var identifier = "func"; + var args = new[] { "bar", "baz" }; + + var exception = Should.Throw(() => ((IJSInProcessRuntime)sut.JSRuntime).Invoke(identifier, args)); + exception.Invocation.Identifier.ShouldBe(identifier); + exception.Invocation.Arguments.ShouldBe(args); + } } } diff --git a/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.net5.cs b/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.net5.cs new file mode 100644 index 000000000..b456ab867 --- /dev/null +++ b/tests/bunit.web.tests/JSInterop/BunitJSInteropTest.net5.cs @@ -0,0 +1,191 @@ +#if NET5_0 +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Bunit; +using Microsoft.JSInterop; +using Shouldly; +using Xunit; + +namespace Bunit.JSInterop +{ + public partial class BunitJSInteropTest + { + [Fact(DisplayName = "Mock throws exception when in strict mode and IJSUnmarshalledRuntime invocation has not been setup with one argument")] + public void Test047() + { + var sut = CreateSut(JSRuntimeMode.Strict); + var identifier = "func"; + var args = new[] { "bar" }; + + var exception = Should.Throw(() => ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar")); + exception.Invocation.Identifier.ShouldBe(identifier); + exception.Invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "Mock throws exception when in strict mode and IJSUnmarshalledRuntime invocation has not been setup with two arguments")] + public void Test048() + { + var sut = CreateSut(JSRuntimeMode.Strict); + var identifier = "func"; + var args = new[] { "bar", "baz" }; + + var exception = Should.Throw(() => ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz")); + exception.Invocation.Identifier.ShouldBe(identifier); + exception.Invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "Mock throws exception when in strict mode and IJSUnmarshalledRuntime invocation has not been setup with three arguments")] + public void Test049() + { + var sut = CreateSut(JSRuntimeMode.Strict); + var identifier = "func"; + var args = new[] { "bar", "baz", "bau" }; + + var exception = Should.Throw(() => ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz", "bau")); + exception.Invocation.Identifier.ShouldBe(identifier); + exception.Invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "Mock throws exception when in strict mode and IJSUnmarshalledRuntime invocation has not been setup with zero arguments")] + public void Test050() + { + var sut = CreateSut(JSRuntimeMode.Strict); + var identifier = "func"; + + var exception = Should.Throw(() => ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier)); + exception.Invocation.Identifier.ShouldBe(identifier); + exception.Invocation.Arguments.ShouldBeEmpty(); + } + + [Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when parsed one arguments.")] + public void Test051() + { + var identifier = "fooFunc"; + var args = new[] { "bar"}; + var sut = CreateSut(JSRuntimeMode.Strict); + + var planned = sut.Setup("fooFunc", args); + planned.SetResult(Guid.NewGuid()); + + var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar"); + + var invocation = sut.Invocations[identifier].Single(); + invocation.Identifier.ShouldBe(identifier); + invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when parsed two arguments.")] + public void Test052() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var planned = sut.Setup("fooFunc", args); + planned.SetResult(Guid.NewGuid()); + + var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz"); + + var invocation = sut.Invocations[identifier].Single(); + invocation.Identifier.ShouldBe(identifier); + invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when parsed three arguments.")] + public void Test053() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz", "boa" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var planned = sut.Setup("fooFunc", args); + planned.SetResult(Guid.NewGuid()); + + var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz", "boa"); + + var invocation = sut.Invocations[identifier].Single(); + invocation.Identifier.ShouldBe(identifier); + invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when parsed zero arguments.")] + public void Test054() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz", "boa" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var planned = sut.Setup("fooFunc", args); + planned.SetResult(Guid.NewGuid()); + + var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz", "boa"); + + var invocation = sut.Invocations[identifier].Single(); + invocation.Identifier.ShouldBe(identifier); + invocation.Arguments.ShouldBe(args); + } + + [Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed one arguments.")] + public void Test055() + { + var identifier = "fooFunc"; + var args = new[] { "bar" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var expectedResult = Guid.NewGuid(); + var planned = sut.Setup("fooFunc", args); + planned.SetResult(expectedResult); + + var actual = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar"); + actual.ShouldBe(expectedResult); + } + + [Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed two arguments.")] + public void Test056() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var expectedResult = Guid.NewGuid(); + var planned = sut.Setup("fooFunc", args); + planned.SetResult(expectedResult); + + var actual = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz"); + actual.ShouldBe(expectedResult); + } + + [Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed three arguments.")] + public void Test057() + { + var identifier = "fooFunc"; + var args = new[] { "bar", "baz", "bao" }; + var sut = CreateSut(JSRuntimeMode.Strict); + + var expectedResult = Guid.NewGuid(); + var planned = sut.Setup("fooFunc", args); + planned.SetResult(expectedResult); + + var actual = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier, "bar", "baz", "bao"); + actual.ShouldBe(expectedResult); + } + + [Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed zero arguments.")] + public void Test058() + { + var identifier = "fooFunc"; + var sut = CreateSut(JSRuntimeMode.Strict); + + var expectedResult = Guid.NewGuid(); + var planned = sut.Setup("fooFunc"); + planned.SetResult(expectedResult); + + var actual = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled(identifier); + actual.ShouldBe(expectedResult); + } + } +} +#endif