Skip to content

Commit

Permalink
Added support for casting BunitJSObjectReference to IJSInProcessObjec…
Browse files Browse the repository at this point in the history
…tReference, IJSUnmarshalledObjectReference
  • Loading branch information
egil committed Dec 19, 2020
1 parent e000f0d commit a69fed1
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 32 deletions.
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ dotnet_diagnostic.BL0006.severity = none

[*.cs]

dotnet_diagnostic.S125.severity = suggestion # S125: Sections of code should not be commented out
dotnet_diagnostic.S927.severity = suggestion # S927: Parameter names should match base declaration and other partial definitions
dotnet_diagnostic.S1075.severity = suggestion # S1075: URIs should not be hardcoded
dotnet_diagnostic.S1186.severity = suggestion # S1186: Methods should not be empty
dotnet_diagnostic.S1199.severity = suggestion # S1199: Nested code blocks should not be used
dotnet_diagnostic.S3925.severity = suggestion # S3925: "ISerializable" should be implemented correctly

[tests/**.cs]

dotnet_diagnostic.S3459.severity = suggestion # S3459: Unassigned members should be removed
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,14 @@ List of new features.

```c#
using var ctx = new TestContext();
var module = ctx.JSInterop.SetupModule("foo.js");
var moduleJsInterop = ctx.JSInterop.SetupModule("foo.js");
```

The returned `module` is a `BunitJSInterop` type, which means all the normal `Setup<TResult>` and `SetupVoid` methods can be used to configure it to handle calls to the module from a component.
The returned `moduleJsInterop` is a `BunitJSInterop` type, which means all the normal `Setup<TResult>` and `SetupVoid` methods can be used to configure it to handle calls to the module from a component. For example, to configure a handler for a call to `hello` in the `foo.js` module, do the following:

```c#
moduleJsInterop.SetupVoid("hello");
```

By [@egil](https://github.com/egil) in [#288](https://github.com/egil/bUnit/pull/288).

Expand Down
31 changes: 30 additions & 1 deletion src/bunit.web/JSInterop/Implementation/BunitJSObjectReference.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#if NET5_0
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace Bunit
{
internal sealed class BunitJSObjectReference : IJSObjectReference
[SuppressMessage("Minor Code Smell", "S1939:Inheritance list should not be redundant", Justification = "By design. To make it obvious that both is implemented.")]
[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.")]
internal sealed class BunitJSObjectReference : IJSObjectReference, IJSInProcessObjectReference, IJSUnmarshalledObjectReference
{
private readonly IJSRuntime _jsRuntime;

Expand All @@ -14,12 +18,37 @@ public BunitJSObjectReference(IJSRuntime jsRuntime)
_jsRuntime = jsRuntime;
}

/// <inheritdoc/>
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, object?[]? args)
=> _jsRuntime.InvokeAsync<TValue>(identifier, CancellationToken.None, args);

/// <inheritdoc/>
public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToken cancellationToken, object?[]? args)
=> _jsRuntime.InvokeAsync<TValue>(identifier, cancellationToken, args);

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

/// <inheritdoc/>
public TResult InvokeUnmarshalled<TResult>(string identifier) =>
InvokeAsync<TResult>(identifier, Array.Empty<object?>()).GetAwaiter().GetResult();

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

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

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

/// <inheritdoc/>
public void Dispose() { }

/// <inheritdoc/>
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
Expand Down
6 changes: 3 additions & 3 deletions src/bunit.web/JSInterop/Implementation/BunitJSRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Bunit.JSInterop
[SuppressMessage("Minor Code Smell", "S1939:Inheritance list should not be redundant", Justification = "By design. To make it obvious that both is implemented.")]
[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.")]
internal sealed partial class BunitJSRuntime : IJSRuntime, IJSInProcessRuntime
{
{
private BunitJSInterop _jsInterop { get; }

public BunitJSRuntime(BunitJSInterop jsInterop)
Expand All @@ -33,8 +33,8 @@ public ValueTask<TValue> InvokeAsync<TValue>(string identifier, CancellationToke
}

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

private ValueTask<TValue>? TryHandlePlannedInvocation<TValue>(JSRuntimeInvocation invocation)
{
Expand Down
4 changes: 2 additions & 2 deletions tests/bunit.web.tests/BlazorE2E/ComponentRenderingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class ComponentRenderingTest : TestContext
{
public ComponentRenderingTest()
{
JSInterop.Mode = JSRuntimeMode.Loose;
JSInterop.Mode = JSRuntimeMode.Loose;
}

[Fact]
Expand Down Expand Up @@ -371,7 +371,7 @@ public void CanUseJSInteropToReferenceElements()
// Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
// buttonElement.Click();
// Assert.Equal("Clicks: 2", inputElement.GetAttribute("value"));

var cut = RenderComponent<ElementRefComponent>();
var inputElement = cut.Find("#capturedElement");
var refId = inputElement.GetAttribute(Htmlizer.ELEMENT_REFERENCE_ATTR_NAME);
Expand Down
12 changes: 6 additions & 6 deletions tests/bunit.web.tests/JSInterop/BunitJSInteropTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ public void Test009()
var identifier = "func";
var sut = new BunitJSInterop();
var handler = sut.Setup<Guid>(identifier, x => true);
var i1 = sut.JSRuntime.InvokeAsync<Guid>(identifier, "first");
var i2 = sut.JSRuntime.InvokeAsync<Guid>(identifier, "second");
sut.JSRuntime.InvokeAsync<Guid>(identifier, "first");
sut.JSRuntime.InvokeAsync<Guid>(identifier, "second");

var invocations = handler.Invocations;

Expand All @@ -201,9 +201,9 @@ public void Test010()
var sut = CreateSut(JSRuntimeMode.Strict);
var planned = sut.Setup<object>("foo", "bar", 42);

var _ = sut.JSRuntime.InvokeAsync<object>("foo", "bar", 42);
sut.JSRuntime.InvokeAsync<object>("foo", "bar", 42);

Should.Throw<JSRuntimeUnhandledInvocationException>(() => { var _ = sut.JSRuntime.InvokeAsync<object>("foo", "bar", 41); });
Should.Throw<JSRuntimeUnhandledInvocationException>(() => sut.JSRuntime.InvokeAsync<object>("foo", "bar", 41));

planned.Invocations.Count.ShouldBe(1);
var invocation = planned.Invocations["foo"][0];
Expand All @@ -218,9 +218,9 @@ public void Test011()
var sut = CreateSut(JSRuntimeMode.Strict);
var planned = sut.Setup<object>("foo", x => x.Arguments.Count == 1);

var _ = sut.JSRuntime.InvokeAsync<object>("foo", 42);
sut.JSRuntime.InvokeAsync<object>("foo", 42);

Should.Throw<JSRuntimeUnhandledInvocationException>(() => { var _ = sut.JSRuntime.InvokeAsync<object>("foo", "bar", 42); });
Should.Throw<JSRuntimeUnhandledInvocationException>(() => sut.JSRuntime.InvokeAsync<object>("foo", "bar", 42));

planned.Invocations.Count.ShouldBe(1);
var invocation = planned.Invocations["foo"][0];
Expand Down
30 changes: 14 additions & 16 deletions tests/bunit.web.tests/JSInterop/BunitJSInteropTest.net5.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#if NET5_0
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.JSInterop;
using Shouldly;
using Xunit;
Expand Down Expand Up @@ -57,7 +56,7 @@ public void Test050()
exception.Invocation.Arguments.ShouldBeEmpty();
}

[Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when parsed one arguments.")]
[Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when passed one arguments.")]
public void Test051()
{
var identifier = "fooFunc";
Expand All @@ -67,14 +66,14 @@ public void Test051()
var planned = sut.Setup<Guid>("fooFunc", args);
planned.SetResult(Guid.NewGuid());

var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, Guid>(identifier, "bar");
((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, Guid>(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.")]
[Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when passed two arguments.")]
public void Test052()
{
var identifier = "fooFunc";
Expand All @@ -84,14 +83,14 @@ public void Test052()
var planned = sut.Setup<Guid>("fooFunc", args);
planned.SetResult(Guid.NewGuid());

var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, Guid>(identifier, "bar", "baz");
((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, Guid>(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.")]
[Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when passed three arguments.")]
public void Test053()
{
var identifier = "fooFunc";
Expand All @@ -101,31 +100,30 @@ public void Test053()
var planned = sut.Setup<Guid>("fooFunc", args);
planned.SetResult(Guid.NewGuid());

var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, string, Guid>(identifier, "bar", "baz", "boa");
((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, string, Guid>(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.")]
[Fact(DisplayName = "After IJSUnmarshalledRuntime invocation a invocation should be visible from the Invocations list when passed zero arguments.")]
public void Test054()
{
var identifier = "fooFunc";
var args = new[] { "bar", "baz", "boa" };
var sut = CreateSut(JSRuntimeMode.Strict);

var planned = sut.Setup<Guid>("fooFunc", args);
var planned = sut.Setup<Guid>("fooFunc");
planned.SetResult(Guid.NewGuid());

var _ = ((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<string, string, string, Guid>(identifier, "bar", "baz", "boa");
((IJSUnmarshalledRuntime)sut.JSRuntime).InvokeUnmarshalled<Guid>(identifier);

var invocation = sut.Invocations[identifier].Single();
invocation.Identifier.ShouldBe(identifier);
invocation.Arguments.ShouldBe(args);
invocation.Arguments.ShouldBeEmpty();
}

[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed one arguments.")]
[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when passed one arguments.")]
public void Test055()
{
var identifier = "fooFunc";
Expand All @@ -140,7 +138,7 @@ public void Test055()
actual.ShouldBe(expectedResult);
}

[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed two arguments.")]
[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when passed two arguments.")]
public void Test056()
{
var identifier = "fooFunc";
Expand All @@ -155,7 +153,7 @@ public void Test056()
actual.ShouldBe(expectedResult);
}

[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed three arguments.")]
[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when passed three arguments.")]
public void Test057()
{
var identifier = "fooFunc";
Expand All @@ -170,7 +168,7 @@ public void Test057()
actual.ShouldBe(expectedResult);
}

[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when parsed zero arguments.")]
[Fact(DisplayName = "An IJSUnmarshalledRuntime invocation should return the correct result when passed zero arguments.")]
public void Test058()
{
var identifier = "fooFunc";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bunit.JSInterop.InvocationHandlers;
using Bunit.JSInterop.InvocationHandlers.Implementation;
using Microsoft.JSInterop;
using Microsoft.JSInterop.Implementation;
Expand All @@ -11,7 +10,7 @@

namespace Bunit.JSInterop
{
public class BunitJSModuleInteropTest : TestContext
public class BunitJSObjectReferenceTest : TestContext
{
[Theory(DisplayName = "Calling Setup<JSObjectReference> or Setup<IJSObjectReference> throws")]
[InlineData("import", null)]
Expand Down Expand Up @@ -261,6 +260,76 @@ public void Test061()

actual.ShouldBeNull();
}

[Fact(DisplayName = "IJSObjectReference can be cast to IJSInProcessObjectReference")]
public async Task Test070()
{
JSInterop.Mode = JSRuntimeMode.Loose;

var jsRuntime = await JSInterop.JSRuntime.InvokeAsync<IJSObjectReference>("FOO.js");

jsRuntime.ShouldBeAssignableTo<IJSInProcessObjectReference>();
}

[Fact(DisplayName = "IJSObjectReference can be cast to IJSUnmarshalledObjectReference")]
public async Task Test071()
{
JSInterop.Mode = JSRuntimeMode.Loose;

var jsRuntime = await JSInterop.JSRuntime.InvokeAsync<IJSObjectReference>("FOO.js");

jsRuntime.ShouldBeAssignableTo<IJSUnmarshalledObjectReference>();
}

[Fact(DisplayName = "IJSInProcessObjectReference-invocations is handled by handlers from BunitJSInterop")]
public async Task Test080()
{
JSInterop.Mode = JSRuntimeMode.Loose;
var jsInProcess = (IJSInProcessObjectReference)(await JSInterop.JSRuntime.InvokeAsync<IJSObjectReference>("FOO.js"));

await jsInProcess.InvokeAsync<string>("bar1");
await jsInProcess.InvokeAsync<string>("bar2", "baz");
await jsInProcess.InvokeVoidAsync("bar3");
await jsInProcess.InvokeVoidAsync("bar4", "baz");
jsInProcess.Invoke<string>("bar5");
jsInProcess.Invoke<string>("bar6", "baz");

JSInterop.VerifyInvoke("bar1");
JSInterop.VerifyInvoke("bar2").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar3");
JSInterop.VerifyInvoke("bar4").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar5");
JSInterop.VerifyInvoke("bar6").Arguments.ShouldBe(new[] { "baz" });
}

[Fact(DisplayName = "IJSUnmarshalledObjectReference-invocations is handled by handlers from BunitJSInterop")]
public async Task Test081()
{
JSInterop.Mode = JSRuntimeMode.Loose;
var jsUnmarshalled = (IJSUnmarshalledObjectReference)(await JSInterop.JSRuntime.InvokeAsync<IJSObjectReference>("FOO.js"));

await jsUnmarshalled.InvokeAsync<string>("bar1");
await jsUnmarshalled.InvokeAsync<string>("bar2", "baz");
await jsUnmarshalled.InvokeVoidAsync("bar3");
await jsUnmarshalled.InvokeVoidAsync("bar4", "baz");
jsUnmarshalled.Invoke<string>("bar5");
jsUnmarshalled.Invoke<string>("bar6", "baz");
jsUnmarshalled.InvokeUnmarshalled<string>("bar7");
jsUnmarshalled.InvokeUnmarshalled<string, string>("bar8", "baz");
jsUnmarshalled.InvokeUnmarshalled<string, string, string>("bar9", "baz", "boo");
jsUnmarshalled.InvokeUnmarshalled<string, string, string, string>("bar10", "baz", "boo", "bah");

JSInterop.VerifyInvoke("bar1");
JSInterop.VerifyInvoke("bar2").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar3");
JSInterop.VerifyInvoke("bar4").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar5");
JSInterop.VerifyInvoke("bar6").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar7");
JSInterop.VerifyInvoke("bar8").Arguments.ShouldBe(new[] { "baz" });
JSInterop.VerifyInvoke("bar9").Arguments.ShouldBe(new[] { "baz", "boo" });
JSInterop.VerifyInvoke("bar10").Arguments.ShouldBe(new[] { "baz", "boo", "bah" });
}
}
}
#endif

0 comments on commit a69fed1

Please sign in to comment.