-
-
Notifications
You must be signed in to change notification settings - Fork 119
Expand file tree
/
Copy pathJSRuntimeInvocationNotSetException.cs
More file actions
132 lines (118 loc) · 4.77 KB
/
JSRuntimeInvocationNotSetException.cs
File metadata and controls
132 lines (118 loc) · 4.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
using System.Text;
namespace Bunit;
/// <summary>
/// Exception used to indicate that an invocation was received by a JSRuntime invocation handler,
/// but the handler was not configured with a result (via SetResult, SetVoidResult, SetCanceled, or SetException).
/// This causes the invocation to hang indefinitely.
/// </summary>
public sealed class JSRuntimeInvocationNotSetException : Exception
{
/// <summary>
/// Gets the invocation that was not handled with a result.
/// </summary>
public JSRuntimeInvocation Invocation { get; }
/// <summary>
/// Initializes a new instance of the <see cref="JSRuntimeInvocationNotSetException"/> class
/// with the provided <see cref="Invocation"/> attached.
/// </summary>
/// <param name="invocation">The invocation that was not provided with a result.</param>
public JSRuntimeInvocationNotSetException(JSRuntimeInvocation invocation)
: base(CreateErrorMessage(invocation))
{
Invocation = invocation;
}
[SuppressMessage("Minor Code Smell", "S6618:\"string.Create\" should be used instead of \"FormattableString\"", Justification = "string.Create not supported in all TFs")]
private static string CreateErrorMessage(JSRuntimeInvocation invocation)
{
var sb = new StringBuilder();
sb.AppendLine("bUnit's JSInterop invocation handler was setup to handle the call:");
sb.AppendLine();
if (invocation.IsVoidResultInvocation)
{
sb.AppendLine(FormattableString.Invariant($" {invocation.InvocationMethodName}({GetArguments(invocation)})"));
}
else
{
sb.AppendLine(FormattableString.Invariant($" {invocation.InvocationMethodName}<{GetGenericInvocationArguments(invocation)}>({GetArguments(invocation)})"));
}
sb.AppendLine();
sb.AppendLine("However, the invocation handler was not configured to return a result,");
sb.AppendLine("causing the invocation to hang indefinitely.");
sb.AppendLine();
sb.AppendLine("To fix this, configure the handler to return a result using one of the following methods:");
sb.AppendLine();
if (invocation.IsVoidResultInvocation)
{
sb.AppendLine(" handler.SetVoidResult();");
}
else
{
sb.AppendLine(FormattableString.Invariant($" handler.SetResult({GetExampleResult(invocation.ResultType)});"));
}
sb.AppendLine(" handler.SetCanceled();");
sb.AppendLine(" handler.SetException(new Exception(\"error message\"));");
return sb.ToString();
}
private static string GetArguments(JSRuntimeInvocation invocation)
{
if (!invocation.Arguments.Any())
return $"\"{invocation.Identifier}\"";
var argStrings = invocation.Arguments.Select(FormatArgument).Prepend($"\"{invocation.Identifier}\"");
return string.Join(", ", argStrings);
}
private static string GetGenericInvocationArguments(JSRuntimeInvocation invocation)
{
return GetReturnTypeName(invocation.ResultType);
}
private static string FormatArgument(object? arg)
{
return arg switch
{
null => "null",
string str => $"\"{str}\"",
char c => $"'{c}'",
bool b => b.ToString().ToUpperInvariant(),
_ => arg.ToString() ?? "null"
};
}
private static string GetReturnTypeName(Type resultType)
=> resultType switch
{
Type { FullName: "System.Boolean" } => "bool",
Type { FullName: "System.Byte" } => "byte",
Type { FullName: "System.Char" } => "char",
Type { FullName: "System.Double" } => "double",
Type { FullName: "System.Int16" } => "short",
Type { FullName: "System.Int32" } => "int",
Type { FullName: "System.Int64" } => "long",
Type { FullName: "System.Single" } => "float",
Type { FullName: "System.String" } => "string",
Type { FullName: "System.Decimal" } => "decimal",
Type { FullName: "System.Guid" } => "Guid",
Type { FullName: "System.DateTime" } => "DateTime",
Type { FullName: "System.DateTimeOffset" } => "DateTimeOffset",
Type { FullName: "System.TimeSpan" } => "TimeSpan",
Type { FullName: "System.Object" } => "object",
_ => resultType.Name
};
private static string GetExampleResult(Type resultType)
=> resultType switch
{
Type { FullName: "System.Boolean" } => "true",
Type { FullName: "System.Byte" } => "1",
Type { FullName: "System.Char" } => "'a'",
Type { FullName: "System.Double" } => "1.0",
Type { FullName: "System.Int16" } => "1",
Type { FullName: "System.Int32" } => "1",
Type { FullName: "System.Int64" } => "1L",
Type { FullName: "System.Single" } => "1.0f",
Type { FullName: "System.String" } => "\"result\"",
Type { FullName: "System.Decimal" } => "1.0m",
Type { FullName: "System.Guid" } => "Guid.NewGuid()",
Type { FullName: "System.DateTime" } => "DateTime.Now",
Type { FullName: "System.DateTimeOffset" } => "DateTimeOffset.Now",
Type { FullName: "System.TimeSpan" } => "TimeSpan.FromSeconds(1)",
Type { FullName: "System.Object" } => "new object()",
_ => $"new {resultType.Name}()"
};
}