Skip to content

Commit 0d87ddc

Browse files
committed
feat: Error when no setup is made for JS
1 parent 47f9c22 commit 0d87ddc

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

src/bunit/JSInterop/InvocationHandlers/JSRuntimeInvocationHandlerBase{TResult}.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,14 @@ protected void SetResultBase(TResult result)
8282
protected internal virtual Task<TResult> HandleAsync(JSRuntimeInvocation invocation)
8383
{
8484
Invocations.RegisterInvocation(invocation);
85-
return completionSource.Task;
85+
86+
var task = completionSource.Task;
87+
if (task is { IsCanceled: false, IsFaulted: false, IsCompletedSuccessfully: false })
88+
{
89+
throw new JSRuntimeInvocationNotSetException(invocation);
90+
}
91+
92+
return task;
8693
}
8794

8895
/// <summary>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Text;
2+
3+
namespace Bunit;
4+
5+
/// <summary>
6+
/// Exception used to indicate that an invocation was received by a JSRuntime invocation handler,
7+
/// but the handler was not configured with a result (via SetResult, SetVoidResult, SetCanceled, or SetException).
8+
/// This causes the invocation to hang indefinitely.
9+
/// </summary>
10+
public sealed class JSRuntimeInvocationNotSetException : Exception
11+
{
12+
/// <summary>
13+
/// Gets the invocation that was not handled with a result.
14+
/// </summary>
15+
public JSRuntimeInvocation Invocation { get; }
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="JSRuntimeInvocationNotSetException"/> class
19+
/// with the provided <see cref="Invocation"/> attached.
20+
/// </summary>
21+
/// <param name="invocation">The invocation that was not provided with a result.</param>
22+
public JSRuntimeInvocationNotSetException(JSRuntimeInvocation invocation)
23+
: base(CreateErrorMessage(invocation))
24+
{
25+
Invocation = invocation;
26+
}
27+
28+
[SuppressMessage("Minor Code Smell", "S6618:\"string.Create\" should be used instead of \"FormattableString\"", Justification = "string.Create not supported in all TFs")]
29+
private static string CreateErrorMessage(JSRuntimeInvocation invocation)
30+
{
31+
var sb = new StringBuilder();
32+
sb.AppendLine("bUnit's JSInterop invocation handler was setup to handle the call:");
33+
sb.AppendLine();
34+
sb.AppendLine($" {invocation.InvocationMethodName}");
35+
sb.AppendLine();
36+
sb.AppendLine("However, the invocation handler was not configured to return a result,");
37+
sb.AppendLine("causing the invocation to hang indefinitely.");
38+
sb.AppendLine();
39+
sb.AppendLine("To fix this, configure the handler to return a result using one of the following methods:");
40+
sb.AppendLine();
41+
sb.AppendLine(" handler.SetVoidResult();");
42+
sb.AppendLine(" handler.SetResult(result);");
43+
sb.AppendLine(" handler.SetCanceled();");
44+
sb.AppendLine(" handler.SetException(new Exception(\"error message\"));");
45+
46+
return sb.ToString();
47+
}
48+
}

tests/bunit.tests/JSInterop/BunitJSInteropTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,4 +686,17 @@ public void Test308(string identifier, string arg0, string arg1, string arg2)
686686
invocationMethodName: "InvokeUnmarshalled"));
687687
}
688688
#endif
689+
690+
[Fact(DisplayName = "JSRuntime invocation throws exception immediately when handler is not configured with result")]
691+
public void Test309()
692+
{
693+
var identifier = "testFunction";
694+
var sut = CreateSut(JSRuntimeMode.Strict);
695+
sut.Setup<int>(identifier);
696+
697+
var exception = Should.Throw<JSRuntimeInvocationNotSetException>(
698+
() => sut.JSRuntime.InvokeAsync<int>(identifier));
699+
700+
exception.Invocation.Identifier.ShouldBe(identifier);
701+
}
689702
}

0 commit comments

Comments
 (0)