[Blazor] Adds E2E reliability tests for JS interop (#11958)
* Adds tests using ignitor to cover a variety of JS interop scenarios. * Validates that these errors don't bring down the circuit.
This commit is contained in:
parent
840c226e28
commit
6e53dac454
|
|
@ -38,6 +38,7 @@
|
||||||
<ProjectReference Include="..\testassets\ComponentsApp.App\ComponentsApp.App.csproj" />
|
<ProjectReference Include="..\testassets\ComponentsApp.App\ComponentsApp.App.csproj" />
|
||||||
<ProjectReference Include="..\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj" />
|
<ProjectReference Include="..\testassets\ComponentsApp.Server\ComponentsApp.Server.csproj" />
|
||||||
<ProjectReference Include="..\testassets\BasicTestApp\BasicTestApp.csproj" />
|
<ProjectReference Include="..\testassets\BasicTestApp\BasicTestApp.csproj" />
|
||||||
|
<ProjectReference Include="..\testassets\Ignitor\Ignitor.csproj" />
|
||||||
<ProjectReference Include="..\testassets\TestServer\Components.TestServer.csproj" />
|
<ProjectReference Include="..\testassets\TestServer\Components.TestServer.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,337 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ignitor;
|
||||||
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
|
{
|
||||||
|
public class InteropReliabilityTests : IClassFixture<AspNetSiteServerFixture>
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan DefaultLatencyTimeout = TimeSpan.FromMilliseconds(500);
|
||||||
|
private readonly AspNetSiteServerFixture _serverFixture;
|
||||||
|
|
||||||
|
public InteropReliabilityTests(AspNetSiteServerFixture serverFixture)
|
||||||
|
{
|
||||||
|
serverFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||||
|
_serverFixture = serverFixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlazorClient Client { get; set; } = new BlazorClient() { DefaultLatencyTimeout = DefaultLatencyTimeout };
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeNonJSInvokableMethods()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027WriteAllText\\u0027 on assembly \\u0027System.IO.FileSystem\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"System.IO.FileSystem",
|
||||||
|
"WriteAllText",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeNonExistingMethods()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027MadeUpMethod\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"BasicTestApp",
|
||||||
|
"MadeUpMethod",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new[] { ".\\log.txt", "log" }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsWithWrongNumberOfArguments()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"Microsoft.AspNetCore.Components.Server",
|
||||||
|
"NotifyLocationChanged",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new[] { _serverFixture.RootUri }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsEmptyAssemblyName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"",
|
||||||
|
"NotifyLocationChanged",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsEmptyMethodName()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"Microsoft.AspNetCore.Components.Server",
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new object[] { _serverFixture.RootUri + "counter", false }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsWithWrongReferenceId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedDotNetObjectRef = "[\"1\",true,{\"__dotNetObject\":1}]";
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027Reverse\\u0027 on assembly \\u0027\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"BasicTestApp",
|
||||||
|
"CreateImportant",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedDotNetObjectRef));
|
||||||
|
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
null,
|
||||||
|
"Reverse",
|
||||||
|
1,
|
||||||
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", "[\"1\",true,\"tnatropmI\"]"));
|
||||||
|
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
null,
|
||||||
|
"Reverse",
|
||||||
|
3, // non existing ref
|
||||||
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsWrongReferenceIdType()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedImportantDotNetObjectRef = "[\"1\",true,{\"__dotNetObject\":1}]";
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"BasicTestApp",
|
||||||
|
"CreateImportant",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(Array.Empty<object>()));
|
||||||
|
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedImportantDotNetObjectRef));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"BasicTestApp",
|
||||||
|
"ReceiveTrivial",
|
||||||
|
null,
|
||||||
|
JsonSerializer.Serialize(new object[] { new { __dotNetObject = 1 } }));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ContinuesWorkingAfterInvalidAsyncReturnCallback()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "An exception occurred executing JS interop: The JSON value could not be converted to System.Int32. Path: $ | LineNumber: 0 | BytePositionInLine: 3.. See InnerException for more details.";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.ClickAsync("triggerjsinterop");
|
||||||
|
|
||||||
|
Assert.Single(interopCalls, (4, "sendMalformedCallbackReturn", (string)null));
|
||||||
|
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
0,
|
||||||
|
"Microsoft.JSInterop",
|
||||||
|
"DotNetDispatcher.EndInvoke",
|
||||||
|
null,
|
||||||
|
"[4, true, \"{\"]");
|
||||||
|
|
||||||
|
var text = Assert.Single(
|
||||||
|
Client.FindElementById("errormessage").Children.OfType<TextNode>(),
|
||||||
|
e => expectedError == e.TextContent);
|
||||||
|
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsWithInvalidArgumentsPayload()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027NotifyLocationChanged\\u0027 on assembly \\u0027Microsoft.AspNetCore.Components.Server\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"Microsoft.AspNetCore.Components.Server",
|
||||||
|
"NotifyLocationChanged",
|
||||||
|
null,
|
||||||
|
"[ \"invalidPayload\"}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CannotInvokeJSInvokableMethodsWithMalformedArgumentPayload()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expectedError = "[\"1\"," +
|
||||||
|
"false," +
|
||||||
|
"\"There was an exception invoking \\u0027ReceiveTrivial\\u0027 on assembly \\u0027BasicTestApp\\u0027. For more details turn on detailed exceptions in \\u0027CircuitOptions.JSInteropDetailedErrors\\u0027\"]";
|
||||||
|
|
||||||
|
var (interopCalls, batches) = ConfigureClient();
|
||||||
|
await GoToTestComponent(batches);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await Client.InvokeDotNetMethod(
|
||||||
|
"1",
|
||||||
|
"BasicTestApp",
|
||||||
|
"ReceiveTrivial",
|
||||||
|
null,
|
||||||
|
"[ { \"data\": {\"}} ]");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Single(interopCalls, (0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", expectedError));
|
||||||
|
await ValidateClientKeepsWorking(Client, batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Task ValidateClientKeepsWorking(BlazorClient Client, List<(int, int, byte[])> batches) =>
|
||||||
|
ValidateClientKeepsWorking(Client, () => batches.Count);
|
||||||
|
|
||||||
|
private async Task ValidateClientKeepsWorking(BlazorClient Client, Func<int> countAccessor)
|
||||||
|
{
|
||||||
|
var currentBatches = countAccessor();
|
||||||
|
await Client.ClickAsync("thecounter");
|
||||||
|
|
||||||
|
Assert.Equal(currentBatches + 1, countAccessor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GoToTestComponent(List<(int, int, byte[])> batches)
|
||||||
|
{
|
||||||
|
var rootUri = _serverFixture.RootUri;
|
||||||
|
Assert.True(await Client.ConnectAsync(new Uri(rootUri, "/subdir"), prerendered: false), "Couldn't connect to the app");
|
||||||
|
Assert.Single(batches);
|
||||||
|
|
||||||
|
await Client.SelectAsync("test-selector-select", "BasicTestApp.ReliabilityComponent");
|
||||||
|
Assert.Equal(2, batches.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (List<(int, string, string)>, List<(int, int, byte[])>) ConfigureClient()
|
||||||
|
{
|
||||||
|
var interopCalls = new List<(int, string, string)>();
|
||||||
|
Client.JSInterop += (int arg1, string arg2, string arg3) => interopCalls.Add((arg1, arg2, arg3));
|
||||||
|
var batches = new List<(int, int, byte[])>();
|
||||||
|
Client.RenderBatchReceived += (id, renderer, data) => batches.Add((id, renderer, data));
|
||||||
|
return (interopCalls, batches);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,68 +1,69 @@
|
||||||
@using Microsoft.AspNetCore.Components.RenderTree
|
@using Microsoft.AspNetCore.Components.RenderTree
|
||||||
<div id="test-selector">
|
<div id="test-selector">
|
||||||
Select test:
|
Select test:
|
||||||
<select @bind=SelectedComponentTypeName>
|
<select id="test-selector-select" @bind=SelectedComponentTypeName>
|
||||||
<option value="none">Choose...</option>
|
<option value="none">Choose...</option>
|
||||||
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
|
||||||
<option value="BasicTestApp.LongRunningInterop">Long running interop</option>
|
|
||||||
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
|
|
||||||
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
||||||
|
<option value="BasicTestApp.AfterRenderInteropComponent">After-render interop component</option>
|
||||||
|
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
|
||||||
|
<option value="BasicTestApp.AuthTest.AuthRouter">Auth cases</option>
|
||||||
|
<option value="BasicTestApp.AuthTest.CascadingAuthenticationStateParent">Cascading authentication state</option>
|
||||||
|
<option value="BasicTestApp.BindCasesComponent">bind cases</option>
|
||||||
|
<option value="BasicTestApp.CascadingValueTest.CascadingValueSupplier">Cascading values</option>
|
||||||
|
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
||||||
|
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
||||||
<option value="BasicTestApp.CounterComponent">Counter</option>
|
<option value="BasicTestApp.CounterComponent">Counter</option>
|
||||||
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
|
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
|
||||||
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
|
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
|
||||||
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
|
<option value="BasicTestApp.CulturePicker">Culture Picker</option>
|
||||||
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
|
|
||||||
<option value="BasicTestApp.MouseEventComponent">Mouse events</option>
|
|
||||||
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
|
|
||||||
<option value="BasicTestApp.InputEventComponent">Input events</option>
|
|
||||||
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
|
|
||||||
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
|
|
||||||
<option value="BasicTestApp.RedTextComponent">Red text</option>
|
|
||||||
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
|
|
||||||
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
|
||||||
<option value="BasicTestApp.MarkupBlockComponent">Markup blocks</option>
|
|
||||||
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
|
||||||
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
|
|
||||||
<option value="BasicTestApp.HttpClientTest.BinaryHttpRequestsComponent">Binary HttpClient tester</option>
|
|
||||||
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
|
|
||||||
<option value="BasicTestApp.BindCasesComponent">bind cases</option>
|
|
||||||
<option value="BasicTestApp.DataDashComponent">data-* attribute rendering</option>
|
<option value="BasicTestApp.DataDashComponent">data-* attribute rendering</option>
|
||||||
<option value="BasicTestApp.ExternalContentPackage">External content package</option>
|
|
||||||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
|
||||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
|
||||||
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
|
|
||||||
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
|
||||||
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
|
||||||
<option value="BasicTestApp.AfterRenderInteropComponent">After-render interop component</option>
|
|
||||||
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
|
|
||||||
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
|
||||||
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
|
||||||
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
|
||||||
<option value="BasicTestApp.RouterTest.TestRouterWithoutNotFoundContent">Router without NotFoundContent</option>
|
|
||||||
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>
|
|
||||||
<option value="BasicTestApp.HtmlMixedChildContent">ChildContent Mixed Block</option>
|
|
||||||
<option value="BasicTestApp.HtmlEncodedChildContent">ChildContent HTML Encoded Block</option>
|
|
||||||
<option value="BasicTestApp.RazorTemplates">Razor Templates</option>
|
|
||||||
<option value="BasicTestApp.MultipleChildContent">Multiple child content</option>
|
|
||||||
<option value="BasicTestApp.CascadingValueTest.CascadingValueSupplier">Cascading values</option>
|
|
||||||
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
|
||||||
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
|
||||||
|
<option value="BasicTestApp.DuplicateAttributesComponent">Duplicate attributes</option>
|
||||||
|
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
|
||||||
|
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
|
||||||
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
|
||||||
|
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
|
||||||
|
<option value="BasicTestApp.EventPreventDefaultComponent">Event preventDefault</option>
|
||||||
|
<option value="BasicTestApp.ExternalContentPackage">External content package</option>
|
||||||
|
<option value="BasicTestApp.FocusEventComponent">Focus events</option>
|
||||||
|
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
|
<option value="BasicTestApp.FormsTest.SimpleValidationComponent">Simple validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
<option value="BasicTestApp.FormsTest.TypicalValidationComponent">Typical validation</option>
|
||||||
<option value="BasicTestApp.FormsTest.NotifyPropertyChangedValidationComponent">INotifyPropertyChanged validation</option>
|
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
||||||
|
<option value="BasicTestApp.HierarchicalImportsTest.Subdir.ComponentUsingImports">Imports statement</option>
|
||||||
|
<option value="BasicTestApp.HtmlBlockChildContent">ChildContent HTML Block</option>
|
||||||
|
<option value="BasicTestApp.HtmlEncodedChildContent">ChildContent HTML Encoded Block</option>
|
||||||
|
<option value="BasicTestApp.HtmlMixedChildContent">ChildContent Mixed Block</option>
|
||||||
|
<option value="BasicTestApp.HttpClientTest.BinaryHttpRequestsComponent">Binary HttpClient tester</option>
|
||||||
|
<option value="BasicTestApp.HttpClientTest.CookieCounterComponent">HttpClient cookies</option>
|
||||||
|
<option value="BasicTestApp.HttpClientTest.HttpRequestsComponent">HttpClient tester</option>
|
||||||
|
<option value="BasicTestApp.InputEventComponent">Input events</option>
|
||||||
|
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
||||||
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
<option value="BasicTestApp.InteropOnInitializationComponent">Interop on initialization</option>
|
||||||
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
<option value="BasicTestApp.KeyCasesComponent">Key cases</option>
|
||||||
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
<option value="BasicTestApp.KeyPressEventComponent">Key press event</option>
|
||||||
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
|
||||||
<option value="BasicTestApp.AuthTest.CascadingAuthenticationStateParent">Cascading authentication state</option>
|
|
||||||
<option value="BasicTestApp.AuthTest.AuthRouter">Auth cases</option>
|
|
||||||
<option value="BasicTestApp.DuplicateAttributesComponent">Duplicate attributes</option>
|
|
||||||
<option value="BasicTestApp.MovingCheckboxesComponent">Moving checkboxes diff case</option>
|
|
||||||
<option value="BasicTestApp.LaggyTypingComponent">Laggy typing</option>
|
<option value="BasicTestApp.LaggyTypingComponent">Laggy typing</option>
|
||||||
<option value="BasicTestApp.CulturePicker">Culture Picker</option>
|
|
||||||
<option value="BasicTestApp.LocalizedText">Localized Text</option>
|
<option value="BasicTestApp.LocalizedText">Localized Text</option>
|
||||||
<option value="BasicTestApp.GlobalizationBindCases">Globalization Bind Cases</option>
|
<option value="BasicTestApp.LogicalElementInsertionCases">Logical element insertion cases</option>
|
||||||
|
<option value="BasicTestApp.LongRunningInterop">Long running interop</option>
|
||||||
|
<option value="BasicTestApp.MarkupBlockComponent">Markup blocks</option>
|
||||||
|
<option value="BasicTestApp.MouseEventComponent">Mouse events</option>
|
||||||
|
<option value="BasicTestApp.MovingCheckboxesComponent">Moving checkboxes diff case</option>
|
||||||
|
<option value="BasicTestApp.MultipleChildContent">Multiple child content</option>
|
||||||
|
<option value="BasicTestApp.ParentChildComponent">Parent component with child</option>
|
||||||
|
<option value="BasicTestApp.PropertiesChangedHandlerParent">Parent component that changes parameters on child</option>
|
||||||
|
<option value="BasicTestApp.RazorTemplates">Razor Templates</option>
|
||||||
|
<option value="BasicTestApp.RedTextComponent">Red text</option>
|
||||||
|
<option value="BasicTestApp.ReliabilityComponent">Server reliability component</option>
|
||||||
|
<option value="BasicTestApp.RenderFragmentToggler">Render fragment renderer</option>
|
||||||
|
<option value="BasicTestApp.ReorderingFocusComponent">Reordering focus retention</option>
|
||||||
|
<option value="BasicTestApp.RouterTest.TestRouter">Router</option>
|
||||||
|
<option value="BasicTestApp.RouterTest.TestRouterWithoutNotFoundContent">Router without NotFoundContent</option>
|
||||||
|
<option value="BasicTestApp.RouterTest.UriHelperComponent">UriHelper Test</option>
|
||||||
|
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||||
|
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||||
|
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
||||||
|
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<span id="runtime-info"><code><tt>@System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription</tt></code></span>
|
<span id="runtime-info"><code><tt>@System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription</tt></code></span>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace BasicTestApp.ServerReliability
|
||||||
|
{
|
||||||
|
public class JSInterop
|
||||||
|
{
|
||||||
|
[JSInvokable]
|
||||||
|
public static DotNetObjectRef<ImportantInformation> CreateImportant()
|
||||||
|
{
|
||||||
|
return DotNetObjectRef.Create(new ImportantInformation());
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public static string ReceiveTrivial(DotNetObjectRef<TrivialInformation> information)
|
||||||
|
{
|
||||||
|
return information.Value.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ImportantInformation
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = "Important";
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public string Reverse()
|
||||||
|
{
|
||||||
|
var messageChars = Message.ToCharArray();
|
||||||
|
Array.Reverse(messageChars);
|
||||||
|
return new string(messageChars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TrivialInformation
|
||||||
|
{
|
||||||
|
public string Message { get; set; } = "Trivial";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
@inject Microsoft.JSInterop.IJSRuntime JSRuntime
|
||||||
|
@namespace BasicTestApp
|
||||||
|
<h1>Server reliability</h1>
|
||||||
|
<p>This component is used on the server-side execution model to validate that the circuit is resilient to failures, intentional or not.
|
||||||
|
The tests that use this component trigger invalid .NET calls to which the server replies with proper JS interop messages indicating a
|
||||||
|
failure to perform the call.
|
||||||
|
|
||||||
|
We include a counter to ensure that the circuit is still alive and working fine after the error.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Current count: @currentCount</p>
|
||||||
|
<p id="errormessage">Error = @error</p>
|
||||||
|
<button id="triggerjsinterop" @onclick="@TriggerJSInterop"></button>
|
||||||
|
|
||||||
|
<button id="thecounter" @onclick="@IncrementCount">Click me</button>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
int currentCount = 0;
|
||||||
|
string error = "";
|
||||||
|
|
||||||
|
void IncrementCount()
|
||||||
|
{
|
||||||
|
currentCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task TriggerJSInterop()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await JSRuntime.InvokeAsync<int>(
|
||||||
|
"sendMalformedCallbackReturn",
|
||||||
|
Array.Empty<object>());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
error = e.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,6 @@ using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.AspNetCore.SignalR.Protocol;
|
using Microsoft.AspNetCore.SignalR.Protocol;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
@ -27,44 +26,133 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
TaskCompletionSource.TrySetCanceled();
|
TaskCompletionSource.TrySetCanceled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ImplicitWait = DefaultLatencyTimeout != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TimeSpan? DefaultLatencyTimeout { get; set; } = TimeSpan.FromMilliseconds(500);
|
||||||
|
|
||||||
private CancellationTokenSource CancellationTokenSource { get; }
|
private CancellationTokenSource CancellationTokenSource { get; }
|
||||||
|
|
||||||
private CancellationToken CancellationToken => CancellationTokenSource.Token;
|
private CancellationToken CancellationToken => CancellationTokenSource.Token;
|
||||||
|
|
||||||
private TaskCompletionSource<object> TaskCompletionSource { get; }
|
private TaskCompletionSource<object> TaskCompletionSource { get; }
|
||||||
|
|
||||||
|
private CancellableOperation NextBatchReceived { get; set; }
|
||||||
|
|
||||||
|
private CancellableOperation NextJSInteropReceived { get; set; }
|
||||||
|
|
||||||
public bool ConfirmRenderBatch { get; set; } = true;
|
public bool ConfirmRenderBatch { get; set; } = true;
|
||||||
|
|
||||||
public event Action<int, string, string> JSInterop;
|
public event Action<int, string, string> JSInterop;
|
||||||
|
|
||||||
public event Action<int, int, byte[]> RenderBatchReceived;
|
public event Action<int, int, byte[]> RenderBatchReceived;
|
||||||
|
|
||||||
|
public event Action<Error> OnCircuitError;
|
||||||
|
|
||||||
public string CircuitId { get; set; }
|
public string CircuitId { get; set; }
|
||||||
|
|
||||||
|
public ElementHive Hive { get; set; } = new ElementHive();
|
||||||
|
|
||||||
|
public bool ImplicitWait { get; set; }
|
||||||
|
|
||||||
public HubConnection HubConnection { get; set; }
|
public HubConnection HubConnection { get; set; }
|
||||||
|
|
||||||
public Task ClickAsync(string elementId)
|
public Task PrepareForNextBatch(TimeSpan? timeout)
|
||||||
|
{
|
||||||
|
if (NextBatchReceived?.Completion != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
NextBatchReceived = new CancellableOperation(timeout);
|
||||||
|
|
||||||
|
return NextBatchReceived.Completion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task PrepareForNextJSInterop()
|
||||||
|
{
|
||||||
|
if (NextJSInteropReceived?.Completion != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid state previous task not completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
NextJSInteropReceived = new CancellableOperation(DefaultLatencyTimeout);
|
||||||
|
|
||||||
|
return NextJSInteropReceived.Completion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ClickAsync(string elementId)
|
||||||
{
|
{
|
||||||
if (!Hive.TryFindElementById(elementId, out var elementNode))
|
if (!Hive.TryFindElementById(elementId, out var elementNode))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Could not find element with id {elementId}.");
|
throw new InvalidOperationException($"Could not find element with id {elementId}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return elementNode.ClickAsync(HubConnection);
|
await ExpectRenderBatch(() => elementNode.ClickAsync(HubConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ElementHive Hive { get; set; }
|
public async Task SelectAsync(string elementId, string value)
|
||||||
|
{
|
||||||
|
if (!Hive.TryFindElementById(elementId, out var elementNode))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Could not find element with id {elementId}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExpectRenderBatch(() => elementNode.SelectAsync(HubConnection, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExpectRenderBatch(Func<Task> action, TimeSpan? timeout = null)
|
||||||
|
{
|
||||||
|
var task = WaitForRenderBatch(timeout);
|
||||||
|
await action();
|
||||||
|
await task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExpectJSInterop(Func<Task> action)
|
||||||
|
{
|
||||||
|
var task = WaitForJSInterop();
|
||||||
|
await action();
|
||||||
|
await task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task WaitForRenderBatch(TimeSpan? timeout = null)
|
||||||
|
{
|
||||||
|
if (ImplicitWait)
|
||||||
|
{
|
||||||
|
if (DefaultLatencyTimeout == null && timeout == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return PrepareForNextBatch(timeout ?? DefaultLatencyTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitForJSInterop()
|
||||||
|
{
|
||||||
|
if (ImplicitWait)
|
||||||
|
{
|
||||||
|
if (DefaultLatencyTimeout == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Implicit wait without DefaultLatencyTimeout is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await PrepareForNextJSInterop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> ConnectAsync(Uri uri, bool prerendered)
|
public async Task<bool> ConnectAsync(Uri uri, bool prerendered)
|
||||||
{
|
{
|
||||||
var builder = new HubConnectionBuilder();
|
var builder = new HubConnectionBuilder();
|
||||||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IHubProtocol, IgnitorMessagePackHubProtocol>());
|
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IHubProtocol, IgnitorMessagePackHubProtocol>());
|
||||||
builder.WithUrl(new Uri(uri, "_blazor/"));
|
builder.WithUrl(GetHubUrl(uri));
|
||||||
builder.ConfigureLogging(l => l.AddConsole().SetMinimumLevel(LogLevel.Trace));
|
builder.ConfigureLogging(l => l.AddConsole().SetMinimumLevel(LogLevel.Trace));
|
||||||
var hive = new ElementHive();
|
|
||||||
|
|
||||||
HubConnection = builder.Build();
|
HubConnection = builder.Build();
|
||||||
await HubConnection.StartAsync(CancellationToken);
|
await HubConnection.StartAsync(CancellationToken);
|
||||||
Console.WriteLine("Connected");
|
|
||||||
|
|
||||||
HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS);
|
HubConnection.On<int, string, string>("JS.BeginInvokeJS", OnBeginInvokeJS);
|
||||||
HubConnection.On<int, int, byte[]>("JS.RenderBatch", OnRenderBatch);
|
HubConnection.On<int, int, byte[]>("JS.RenderBatch", OnRenderBatch);
|
||||||
|
|
@ -75,55 +163,92 @@ namespace Ignitor
|
||||||
if (prerendered)
|
if (prerendered)
|
||||||
{
|
{
|
||||||
CircuitId = await GetPrerenderedCircuitIdAsync(uri);
|
CircuitId = await GetPrerenderedCircuitIdAsync(uri);
|
||||||
return await HubConnection.InvokeAsync<bool>("ConnectCircuit", CircuitId);
|
var result = false;
|
||||||
|
await ExpectRenderBatch(async () => result = await HubConnection.InvokeAsync<bool>("ConnectCircuit", CircuitId));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", uri, uri);
|
await ExpectRenderBatch(
|
||||||
|
async () => CircuitId = await HubConnection.InvokeAsync<string>("StartCircuit", new Uri(uri.GetLeftPart(UriPartial.Authority)), uri),
|
||||||
|
TimeSpan.FromSeconds(10));
|
||||||
return CircuitId != null;
|
return CircuitId != null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OnBeginInvokeJS(int asyncHandle, string identifier, string argsJson)
|
private void OnBeginInvokeJS(int asyncHandle, string identifier, string argsJson)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
JSInterop?.Invoke(asyncHandle, identifier, argsJson);
|
JSInterop?.Invoke(asyncHandle, identifier, argsJson);
|
||||||
}
|
|
||||||
|
|
||||||
void OnRenderBatch(int browserRendererId, int batchId, byte[] batchData)
|
NextJSInteropReceived?.Completion?.TrySetResult(null);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
NextJSInteropReceived?.Completion?.TrySetException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRenderBatch(int browserRendererId, int batchId, byte[] batchData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RenderBatchReceived?.Invoke(browserRendererId, batchId, batchData);
|
||||||
|
|
||||||
var batch = RenderBatchReader.Read(batchData);
|
var batch = RenderBatchReader.Read(batchData);
|
||||||
hive.Update(batch);
|
|
||||||
|
Hive.Update(batch);
|
||||||
|
|
||||||
if (ConfirmRenderBatch)
|
if (ConfirmRenderBatch)
|
||||||
{
|
{
|
||||||
HubConnection.InvokeAsync("OnRenderCompleted", batchId, /* error */ null);
|
HubConnection.InvokeAsync("OnRenderCompleted", batchId, /* error */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderBatchReceived?.Invoke(browserRendererId, batchId, batchData);
|
NextBatchReceived?.Completion?.TrySetResult(null);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
void OnError(Error error)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("ERROR: " + error.Stack);
|
NextBatchReceived?.Completion?.TrySetResult(e);
|
||||||
}
|
|
||||||
|
|
||||||
Task OnClosedAsync(Exception ex)
|
|
||||||
{
|
|
||||||
if (ex == null)
|
|
||||||
{
|
|
||||||
TaskCompletionSource.TrySetResult(null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TaskCompletionSource.TrySetException(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson)
|
private void OnError(Error error)
|
||||||
{
|
{
|
||||||
HubConnection.InvokeAsync("BeginInvokeDotNetFromJS", callId?.ToString(), assemblyName, methodIdentifier, dotNetObjectId ?? 0, argsJson);
|
OnCircuitError?.Invoke(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task OnClosedAsync(Exception ex)
|
||||||
|
{
|
||||||
|
if (ex == null)
|
||||||
|
{
|
||||||
|
TaskCompletionSource.TrySetResult(null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TaskCompletionSource.TrySetException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uri GetHubUrl(Uri uri)
|
||||||
|
{
|
||||||
|
if (uri.Segments.Length == 1)
|
||||||
|
{
|
||||||
|
return new Uri(uri, "_blazor");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var builder = new UriBuilder(uri);
|
||||||
|
builder.Path += builder.Path.EndsWith("/") ? "_blazor" : "/_blazor";
|
||||||
|
return builder.Uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeDotNetMethod(object callId, string assemblyName, string methodIdentifier, object dotNetObjectId, string argsJson)
|
||||||
|
{
|
||||||
|
await ExpectJSInterop(() => HubConnection.InvokeAsync("BeginInvokeDotNetFromJS", callId?.ToString(), assemblyName, methodIdentifier, dotNetObjectId ?? 0, argsJson));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> GetPrerenderedCircuitIdAsync(Uri uri)
|
private static async Task<string> GetPrerenderedCircuitIdAsync(Uri uri)
|
||||||
|
|
@ -144,5 +269,65 @@ namespace Ignitor
|
||||||
CancellationTokenSource.Cancel();
|
CancellationTokenSource.Cancel();
|
||||||
CancellationTokenSource.Dispose();
|
CancellationTokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ElementNode FindElementById(string id)
|
||||||
|
{
|
||||||
|
if (!Hive.TryFindElementById(id, out var element))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Element not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CancellableOperation
|
||||||
|
{
|
||||||
|
public CancellableOperation(TimeSpan? timeout)
|
||||||
|
{
|
||||||
|
Timeout = timeout;
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
Completion = new TaskCompletionSource<object>(TaskContinuationOptions.RunContinuationsAsynchronously);
|
||||||
|
Completion.Task.ContinueWith(
|
||||||
|
(task, state) =>
|
||||||
|
{
|
||||||
|
var operation = (CancellableOperation)state;
|
||||||
|
operation.Dispose();
|
||||||
|
},
|
||||||
|
this,
|
||||||
|
TaskContinuationOptions.ExecuteSynchronously); // We need to execute synchronously to clean-up before anything else continues
|
||||||
|
if (Timeout != null)
|
||||||
|
{
|
||||||
|
Cancellation = new CancellationTokenSource(Timeout.Value);
|
||||||
|
CancellationRegistration = Cancellation.Token.Register(
|
||||||
|
(self) =>
|
||||||
|
{
|
||||||
|
var operation = (CancellableOperation)self;
|
||||||
|
operation.Completion.TrySetCanceled(operation.Cancellation.Token);
|
||||||
|
operation.Cancellation.Dispose();
|
||||||
|
operation.CancellationRegistration.Dispose();
|
||||||
|
},
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose()
|
||||||
|
{
|
||||||
|
Completion = null;
|
||||||
|
Cancellation.Dispose();
|
||||||
|
CancellationRegistration.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan? Timeout { get; }
|
||||||
|
|
||||||
|
public TaskCompletionSource<object> Completion { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenSource Cancellation { get; set; }
|
||||||
|
|
||||||
|
public CancellationTokenRegistration CancellationRegistration { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Rendering;
|
||||||
using Microsoft.AspNetCore.Components.Web;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
|
|
||||||
|
|
@ -62,6 +63,42 @@ namespace Ignitor
|
||||||
_events[eventName] = descriptor;
|
_events[eventName] = descriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task SelectAsync(HubConnection connection, string value)
|
||||||
|
{
|
||||||
|
if (!Events.TryGetValue("change", out var changeEventDescriptor))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Element does not have a click event.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mouseEventArgs = new UIChangeEventArgs()
|
||||||
|
{
|
||||||
|
Type = changeEventDescriptor.EventName,
|
||||||
|
Value = value
|
||||||
|
};
|
||||||
|
|
||||||
|
var browserDescriptor = new RendererRegistryEventDispatcher.BrowserEventDescriptor()
|
||||||
|
{
|
||||||
|
BrowserRendererId = 0,
|
||||||
|
EventHandlerId = changeEventDescriptor.EventId,
|
||||||
|
EventArgsType = "change",
|
||||||
|
EventFieldInfo = new EventFieldInfo
|
||||||
|
{
|
||||||
|
ComponentId = 0,
|
||||||
|
FieldValue = value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var serializedJson = JsonSerializer.Serialize(mouseEventArgs, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||||
|
var argsObject = new object[] { browserDescriptor, serializedJson };
|
||||||
|
var callId = "0";
|
||||||
|
var assemblyName = "Microsoft.AspNetCore.Components.Web";
|
||||||
|
var methodIdentifier = "DispatchEvent";
|
||||||
|
var dotNetObjectId = 0;
|
||||||
|
var clickArgs = JsonSerializer.Serialize(argsObject, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
|
||||||
|
await connection.InvokeAsync("BeginInvokeDotNetFromJS", callId, assemblyName, methodIdentifier, dotNetObjectId, clickArgs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public class ElementEventDescriptor
|
public class ElementEventDescriptor
|
||||||
{
|
{
|
||||||
public ElementEventDescriptor(string eventName, int eventId)
|
public ElementEventDescriptor(string eventName, int eventId)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
namespace Ignitor
|
namespace Ignitor
|
||||||
{
|
{
|
||||||
internal class Error
|
public class Error
|
||||||
{
|
{
|
||||||
public string Stack { get; set; }
|
public string Stack { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ namespace Ignitor
|
||||||
{
|
{
|
||||||
if (batchId < 1000)
|
if (batchId < 1000)
|
||||||
{
|
{
|
||||||
client.ClickAsync("thecounter");
|
var _ = client.ClickAsync("thecounter");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue