Skip to content
22 changes: 21 additions & 1 deletion src/bunit.web/JSInterop/BunitJSInterop.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -179,7 +180,11 @@ private void RegisterInvocation(JSRuntimeInvocation invocation)
return result;
}

private class BUnitJSRuntime : IJSRuntime
#if NET5_0
private class BUnitJSRuntime : IJSRuntime, IJSInProcessRuntime, IJSUnmarshalledRuntime
#else
private class BUnitJSRuntime : IJSRuntime, IJSInProcessRuntime
#endif
{
private readonly BunitJSInterop _jsInterop;

Expand All @@ -188,6 +193,9 @@ public BUnitJSRuntime(BunitJSInterop bunitJsInterop)
_jsInterop = bunitJsInterop;
}

public TResult Invoke<TResult>(string identifier, params object?[]? args) =>
InvokeAsync<TResult>(identifier, args).GetAwaiter().GetResult();

public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
=> InvokeAsync<TValue>(identifier, default, args);

Expand All @@ -199,6 +207,18 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
return TryHandlePlannedInvocation<TValue>(invocation) ?? new ValueTask<TValue>(default(TValue)!);
}

public TResult InvokeUnmarshalled<TResult>(string identifier) =>
InvokeAsync<TResult>(identifier, null).GetAwaiter().GetResult();

public TResult InvokeUnmarshalled<T0, TResult>(string identifier, [DisallowNull] T0 arg0) =>
InvokeAsync<TResult>(identifier, new object[1] {arg0}).GetAwaiter().GetResult();

public TResult InvokeUnmarshalled<T0, T1, TResult>(string identifier, [DisallowNull] T0 arg0, [DisallowNull] T1 arg1) =>
InvokeAsync<TResult>(identifier, new object[2] { arg0, arg1 }).GetAwaiter().GetResult();

public TResult InvokeUnmarshalled<T0, T1, T2, TResult>(string identifier, [DisallowNull] T0 arg0, [DisallowNull] T1 arg1, [DisallowNull] T2 arg2) =>
InvokeAsync<TResult>(identifier, new object[3] { arg0, arg1, arg2 }).GetAwaiter().GetResult();

private ValueTask<TValue>? TryHandlePlannedInvocation<TValue>(JSRuntimeInvocation invocation)
{
ValueTask<TValue>? result = default;
Expand Down
102 changes: 102 additions & 0 deletions tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,5 +435,107 @@ 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<object>("ident", Array.Empty<object>());

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" };
using var cts = new CancellationTokenSource();
var sut = CreateSut(JSRuntimeMode.Loose);

var _ = ((IJSInProcessRuntime)sut.JSRuntime).Invoke<object>(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<Guid>(identifier, args);
planned.SetResult(expectedResult);

var i = ((IJSInProcessRuntime)sut.JSRuntime).Invoke<Guid>(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<JSRuntimeUnhandledInvocationException>(() => { var _ = ((IJSInProcessRuntime)sut.JSRuntime).Invoke<object>(identifier, args); });
exception.Invocation.Identifier.ShouldBe(identifier);
exception.Invocation.Arguments.ShouldBe(args);
}

#if NET5_0
[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<JSRuntimeUnhandledInvocationException>(() => { var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, object>(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<JSRuntimeUnhandledInvocationException>(() => { var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, object>(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<JSRuntimeUnhandledInvocationException>(() => { var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, string, object>(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<JSRuntimeUnhandledInvocationException>(() => { var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<object>(identifier); });
exception.Invocation.Identifier.ShouldBe(identifier);
exception.Invocation.Arguments.ShouldBeEmpty();
}
#endif
}
}