Skip to content

Commit 23382f0

Browse files
committed
Added setup suggestions to JSRuntimeUnhandledInvocationException message
1 parent 09ee510 commit 23382f0

3 files changed

Lines changed: 394 additions & 10 deletions

File tree

src/bunit.web/JSInterop/JSRuntimeUnhandledInvocationException.cs

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
43
using System.Runtime.Serialization;
4+
using System.Text;
55

66
namespace Bunit
77
{
@@ -24,24 +24,102 @@ public sealed class JSRuntimeUnhandledInvocationException : Exception
2424
/// </summary>
2525
/// <param name="invocation">The unplanned invocation.</param>
2626
public JSRuntimeUnhandledInvocationException(JSRuntimeInvocation invocation)
27-
: base($"The invocation of '{invocation.Identifier}' {PrintArguments(invocation.Arguments)} was not expected.")
27+
: base(CreateErrorMessage(invocation))
2828
{
2929
Invocation = invocation;
3030
}
3131

32-
private static string PrintArguments(IReadOnlyList<object?> arguments)
32+
private JSRuntimeUnhandledInvocationException(SerializationInfo serializationInfo, StreamingContext streamingContext)
33+
: base(serializationInfo, streamingContext)
34+
{
35+
}
36+
37+
private static string CreateErrorMessage(JSRuntimeInvocation invocation)
3338
{
34-
if (arguments.Count == 0)
35-
return "without arguments";
36-
if (arguments.Count == 1)
37-
return $"with the argument [{arguments[0]}]";
39+
var sb = new StringBuilder();
40+
sb.AppendLine("bUnit's JSInterop has not been configured to handle the call:");
41+
sb.AppendLine();
42+
43+
if (invocation.IsVoidResultInvocation)
44+
{
45+
sb.AppendLine($" {invocation.InvocationMethodName}({GetArguments(invocation)})");
46+
}
47+
else
48+
{
49+
sb.AppendLine($" {invocation.InvocationMethodName}<{GetGenericInvocationArguments(invocation)}>({GetArguments(invocation)})");
50+
}
51+
52+
sb.AppendLine();
53+
sb.AppendLine("Configure bUnit's JSInterop to handle the call with following:");
54+
sb.AppendLine();
55+
56+
if (invocation.IsVoidResultInvocation)
57+
{
58+
sb.AppendLine($" SetupVoid({GetArguments(invocation)})");
59+
}
60+
else
61+
{
62+
sb.AppendLine($" Setup<{GetReturnTypeName(invocation.ResultType)}>({GetArguments(invocation)})");
63+
}
64+
65+
if (invocation.Arguments.Any())
66+
{
67+
sb.AppendLine("or the following, to match any arguments:");
68+
if (invocation.IsVoidResultInvocation)
69+
sb.AppendLine($" SetupVoid(\"{invocation.Identifier}\", _ => true)");
70+
else
71+
sb.AppendLine($" Setup<{GetReturnTypeName(invocation.ResultType)}>(\"{invocation.Identifier}\", _ => true)");
72+
}
3873

39-
return $"with arguments [{string.Join(", ", arguments.OfType<object>().Select(x => x.ToString()))}]";
74+
sb.AppendLine();
75+
sb.AppendLine("The setup methods are available on an instance of the BunitJSInterop or");
76+
sb.AppendLine("BunitJSModuleInterop type. The standard BunitJSInterop is available");
77+
sb.AppendLine("through the TestContext.JSInterop property, and a BunitJSModuleInterop");
78+
sb.AppendLine("instance is returned from calling SetupModule on a BunitJSInterop instance.");
79+
return sb.ToString();
4080
}
4181

42-
private JSRuntimeUnhandledInvocationException(SerializationInfo serializationInfo, StreamingContext streamingContext)
43-
: base(serializationInfo, streamingContext)
82+
private static string GetReturnTypeName(Type resultType)
83+
=> resultType switch
84+
{
85+
Type { FullName: "System.Boolean" } => "bool",
86+
Type { FullName: "System.Byte" } => "byte",
87+
Type { FullName: "System.Char" } => "char",
88+
Type { FullName: "System.Double" } => "double",
89+
Type { FullName: "System.Int16" } => "short",
90+
Type { FullName: "System.Int32" } => "int",
91+
Type { FullName: "System.Int64" } => "long",
92+
Type { FullName: "System.SByte" } => "sbyte",
93+
Type { FullName: "System.Single" } => "float",
94+
Type { FullName: "System.UInt16" } => "ushort",
95+
Type { FullName: "System.UInt32" } => "uint",
96+
Type { FullName: "System.UInt64" } => "ulong",
97+
Type x => x.Name
98+
};
99+
100+
private static string GetGenericInvocationArguments(JSRuntimeInvocation invocation)
101+
{
102+
if (invocation.InvocationMethodName.Equals("InvokeUnmarshalled", StringComparison.Ordinal))
103+
{
104+
var genericArgs = invocation.Arguments
105+
.Select(x => x is not null ? GetReturnTypeName(x.GetType()) : "?")
106+
.Append(GetReturnTypeName(invocation.ResultType));
107+
108+
return string.Join(", ", genericArgs);
109+
}
110+
else
111+
{
112+
return GetReturnTypeName(invocation.ResultType);
113+
}
114+
}
115+
116+
private static string GetArguments(JSRuntimeInvocation invocation)
44117
{
118+
var args = invocation.Arguments
119+
.Select(x => x is string s ? $"\"{s}\"" : x?.ToString() ?? "null")
120+
.Prepend($"\"{invocation.Identifier}\"");
121+
122+
return string.Join(", ", args);
45123
}
46124
}
47125
}

0 commit comments

Comments
 (0)