Run E2E tests for server execution as well as WebAssembly. Fixes several

server-execution bugs uncovered by the E2E tests.
This commit is contained in:
Steve Sanderson 2018-07-18 14:48:54 +01:00
Родитель 4708aa4f06
Коммит 90cf6ce5a7
30 изменённых файлов: 697 добавлений и 346 удалений

Просмотреть файл

@ -17,7 +17,7 @@ function boot() {
});
connection = new signalR.HubConnectionBuilder()
.withUrl('/_blazor')
.withUrl('_blazor')
.withHubProtocol(new MessagePackHubProtocol())
.configureLogging(signalR.LogLevel.Information)
.build();

Просмотреть файл

@ -118,7 +118,8 @@ class OutOfProcessRenderTreeFrameReader implements RenderTreeFrameReader {
}
elementReferenceCaptureId(frame: RenderTreeFrame) {
return readInt32LE(this.batchDataUint8, frame as any + 4); // 2nd int
const stringIndex = readInt32LE(this.batchDataUint8, frame as any + 4); // 2nd int
return this.stringReader.readString(stringIndex);
}
componentId(frame: RenderTreeFrame) {
@ -159,14 +160,15 @@ class OutOfProcessStringReader {
}
readString(index: number): string | null {
const stringTableEntry = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength);
if (stringTableEntry === -1) { // Special value encodes 'null'
if (index === -1) { // Special value encodes 'null'
return null;
} else {
const stringTableEntryPos = readInt32LE(this.batchDataUint8, this.stringTableStartIndex + index * stringTableEntryLength);
// By default, .NET's BinaryWriter gives LEB128-length-prefixed UTF-8 data.
// This is convenient enough to decode in JavaScript.
const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntry);
const charsStart = stringTableEntry + numLEB128Bytes(numUtf8Bytes);
const numUtf8Bytes = readLEB128(this.batchDataUint8, stringTableEntryPos);
const charsStart = stringTableEntryPos + numLEB128Bytes(numUtf8Bytes);
const utf8Data = new DataView(
this.batchDataUint8.buffer,
this.batchDataUint8.byteOffset + charsStart,

Просмотреть файл

@ -138,7 +138,21 @@ namespace Microsoft.AspNetCore.Blazor.Server.Circuits
{
case RenderTreeFrameType.Attribute:
WriteString(frame.AttributeName);
WriteString(frame.AttributeValue as string);
if (frame.AttributeValue is bool boolValue)
{
// Encoding the bool as either "" or null is pretty odd, but avoids
// having to pack any "what type of thing is this" info into the same
// 4 bytes as the string table index. If, later, we need a way of
// distinguishing whether an attribute value is really a bool or a string
// or something else, we'll need a different encoding mechanism. Since there
// would never be more than (say) 2^28 (268 million) distinct string table
// entries, we could use the first 4 bits to encode the value type.
WriteString(boolValue ? string.Empty : null);
}
else
{
WriteString(frame.AttributeValue as string);
}
_binaryWriter.Write(frame.AttributeEventHandlerId);
break;
case RenderTreeFrameType.Component:

Просмотреть файл

@ -72,14 +72,25 @@ namespace Microsoft.JSInterop
? null
: jsRuntimeBaseInstance.ArgSerializerStrategy.FindDotNetObject(dotNetObjectId);
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
object syncResult = null;
Exception syncException = null;
try
{
syncResult = InvokeSynchronously(assemblyName, methodIdentifier, targetInstance, argsJson);
}
catch (Exception ex)
{
syncException = ex;
}
// If there was no callId, the caller does not want to be notified about the result
if (callId != null)
{
// Invoke and coerce the result to a Task so the caller can use the same async API
// for both synchronous and asynchronous methods
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult);
var task = CoerceToTask(syncResult, syncException);
task.ContinueWith(completedTask =>
{
try
@ -96,6 +107,22 @@ namespace Microsoft.JSInterop
}
}
private static Task CoerceToTask(object syncResult, Exception syncException)
{
if (syncException != null)
{
return Task.FromException(syncException);
}
else if (syncResult is Task syncResultTask)
{
return syncResultTask;
}
else
{
return Task.FromResult(syncResult);
}
}
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, object targetInstance, string argsJson)
{
if (targetInstance != null)

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using BasicTestApp;
@ -11,13 +11,14 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
public class BasicTestAppTestBase : ServerTestBase<DevHostServerFixture<Program>>
public class BasicTestAppTestBase : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
{
public const string ServerPathBase = "/subdir";
public string ServerPathBase
=> "/subdir" + (_serverFixture.UsingAspNetHost ? "#server" : "");
public BasicTestAppTestBase(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@ -27,19 +28,18 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
protected IWebElement MountTestComponent<TComponent>() where TComponent : IComponent
{
var componentTypeName = typeof(TComponent).FullName;
WaitUntilDotNetRunningInBrowser();
((IJavaScriptExecutor)Browser).ExecuteScript(
$"mountTestComponent('{componentTypeName}')");
var testSelector = WaitUntilTestSelectorReady();
testSelector.SelectByValue("none");
testSelector.SelectByValue(componentTypeName);
return Browser.FindElement(By.TagName("app"));
}
protected void WaitUntilDotNetRunningInBrowser()
protected SelectElement WaitUntilTestSelectorReady()
{
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver =>
{
return ((IJavaScriptExecutor)driver)
.ExecuteScript("return window.isTestReady;");
});
var elemToFind = By.CssSelector("#test-selector > select");
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(elemToFind) != null);
return new SelectElement(Browser.FindElement(elemToFind));
}
}
}

Просмотреть файл

@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
try
{
var driver = new RemoteWebDriver(opts);
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
Browser = driver;
Logs = new RemoteLogs(driver);
}

Просмотреть файл

@ -0,0 +1,49 @@
// 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;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures
{
public class ToggleExecutionModeServerFixture<TClientProgram>
: ServerFixture
{
public string PathBase { get; set; }
public bool UsingAspNetHost { get; private set; }
private AspNetSiteServerFixture.BuildWebHost _buildWebHostMethod;
private IDisposable _serverToDispose;
public void UseAspNetHost(AspNetSiteServerFixture.BuildWebHost buildWebHostMethod)
{
_buildWebHostMethod = buildWebHostMethod
?? throw new ArgumentNullException(nameof(buildWebHostMethod));
UsingAspNetHost = true;
}
protected override string StartAndGetRootUri()
{
if (_buildWebHostMethod == null)
{
// Use Blazor's dev host server
var underlying = new DevHostServerFixture<TClientProgram>();
underlying.PathBase = PathBase;
_serverToDispose = underlying;
return underlying.RootUri.AbsoluteUri;
}
else
{
// Use specified ASP.NET host server
var underlying = new AspNetSiteServerFixture();
underlying.BuildWebHostMethod = _buildWebHostMethod;
_serverToDispose = underlying;
return underlying.RootUri.AbsoluteUri;
}
}
public override void Dispose()
{
_serverToDispose.Dispose();
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
: BrowserTestBase, IClassFixture<TServerFixture>
where TServerFixture: ServerFixture
{
private readonly TServerFixture _serverFixture;
protected readonly TServerFixture _serverFixture;
public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output)
: base(browserFixture, output)
@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
}
}
Browser.Navigate().GoToUrl("about:blank");
Browser.Navigate().GoToUrl(absoluteUrl);
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
// XUnit assertions, but hooked into Selenium's polling mechanism
public class WaitAssert
{
private readonly static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(1);
public static void Equal<T>(T expected, Func<T> actual)
=> WaitAssertCore(() => Assert.Equal(expected, actual()));
public static void True(Func<bool> actual)
=> WaitAssertCore(() => Assert.True(actual()));
public static void False(Func<bool> actual)
=> WaitAssertCore(() => Assert.False(actual()));
public static void Contains(string expectedSubstring, Func<string> actualString)
=> WaitAssertCore(() => Assert.Contains(expectedSubstring, actualString()));
public static void Collection<T>(Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
=> WaitAssertCore(() => Assert.Collection(actualValues(), elementInspectors));
public static void Empty(Func<IEnumerable> actualValues)
=> WaitAssertCore(() => Assert.Empty(actualValues()));
public static void Single(Func<IEnumerable> actualValues)
=> WaitAssertCore(() => Assert.Single(actualValues()));
private static void WaitAssertCore(Action assertion, TimeSpan timeout = default)
{
if (timeout == default)
{
timeout = DefaultTimeout;
}
try
{
new WebDriverWait(BrowserTestBase.Browser, timeout).Until(_ =>
{
try
{
assertion();
return true;
}
catch
{
return false;
}
});
}
catch (WebDriverTimeoutException)
{
// Instead of reporting it as a timeout, report the Xunit exception
assertion();
}
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// 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 Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests
{
internal static class ServerExecutionTestExtensions
{
public static ToggleExecutionModeServerFixture<T> WithServerExecution<T>(this ToggleExecutionModeServerFixture<T> serverFixture)
{
serverFixture.UseAspNetHost(TestServer.Program.BuildWebHost);
return serverFixture;
}
}
}

Просмотреть файл

@ -10,23 +10,20 @@ using System.Linq;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests
{
public class ServerSideBlazorTest : ServerTestBase<AspNetSiteServerFixture>, IDisposable
public class ServerSideAppTest : ServerTestBase<AspNetSiteServerFixture>
{
private readonly AspNetSiteServerFixture _serverFixture;
public ServerSideBlazorTest(
public ServerSideAppTest(
BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
_serverFixture = serverFixture;
_serverFixture.Environment = AspNetEnvironment.Development;
_serverFixture.BuildWebHostMethod = ServerSideBlazor.Server.Program.BuildWebHost;
Navigate("/", noReload: true);
Navigate("/", noReload: false);
WaitUntilLoaded();
}
@ -58,13 +55,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.LinkText("Counter")).Click();
// Verify we're now on the counter page, with that nav link (only) highlighted
Assert.Equal("Counter", Browser.FindElement(mainHeaderSelector).Text);
WaitAssert.Equal("Counter", () => Browser.FindElement(mainHeaderSelector).Text);
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
item => Assert.Equal("Counter", item.Text));
// Verify we can navigate back to home too
Browser.FindElement(By.LinkText("Home")).Click();
Assert.Equal("Hello, world!", Browser.FindElement(mainHeaderSelector).Text);
WaitAssert.Equal("Hello, world!", () => Browser.FindElement(mainHeaderSelector).Text);
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
item => Assert.Equal("Home", item.Text));
}
@ -74,7 +71,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
// Navigate to "Counter"
Browser.FindElement(By.LinkText("Counter")).Click();
Assert.Equal("Counter", Browser.FindElement(By.TagName("h1")).Text);
WaitAssert.Equal("Counter", () => Browser.FindElement(By.TagName("h1")).Text);
// Observe the initial value is zero
var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p"));
@ -83,17 +80,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Click the button; see it counts
var button = Browser.FindElement(By.CssSelector(".main button"));
button.Click();
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
button.Click();
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
button.Click();
Assert.Equal("Current count: 3", countDisplayElement.Text);
WaitAssert.Equal("Current count: 3", () => countDisplayElement.Text);
}
[Fact(Skip = "This is failing and I don't know why")]
[Fact]
public void HasFetchDataPage()
{
// Navigate to "Fetch Data"
Browser.FindElement(By.LinkText("Fetch data")).Click();
Assert.Equal("Weather forecast", Browser.FindElement(By.TagName("h1")).Text);
WaitAssert.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text);
// Wait until loaded
var tableSelector = By.CssSelector("table.table");
@ -115,12 +114,5 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(
driver => driver.FindElement(By.TagName("app")).Text != "Loading...");
}
public void Dispose()
{
// Make the tests run faster by navigating back to the home page when we are done
// If we don't, then the next test will reload the whole page before it starts
Browser.FindElement(By.LinkText("Home")).Click();
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// 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 BasicTestApp;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Blazor.E2ETest.Tests;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.ServerExecutionTests
{
public class ServerComponentRenderingTest : ComponentRenderingTest
{
public ServerComponentRenderingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerBindTest : BindTest
{
public ServerBindTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerEventBubblingTest : EventBubblingTest
{
public ServerEventBubblingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerEventTest : EventTest
{
public ServerEventTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerInteropTest : InteropTest
{
public ServerInteropTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
public class ServerRoutingTest : RoutingTest
{
public ServerRoutingTest(BrowserFixture browserFixture, ToggleExecutionModeServerFixture<Program> serverFixture, ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using BasicTestApp.HttpClientTest;
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public BinaryHttpClientTest(
BrowserFixture browserFixture,
DevHostServerFixture<BasicTestApp.Program> devHostServerFixture,
ToggleExecutionModeServerFixture<BasicTestApp.Program> devHostServerFixture,
AspNetSiteServerFixture apiServerFixture,
ITestOutputHelper output)
: base(browserFixture, devHostServerFixture, output)

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using BasicTestApp;
@ -14,12 +14,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class BindTest : BasicTestAppTestBase
{
public BindTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
// On WebAssembly, page reloads are expensive so skip if possible
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
MountTestComponent<BindCasesComponent>();
}
@ -39,12 +40,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Assert.Equal(string.Empty, boundValue.Text); // Doesn't update until change event
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
target.SendKeys("\t");
Assert.Equal("Changed value", boundValue.Text);
WaitAssert.Equal("Changed value", () => boundValue.Text);
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
// Remove the value altogether
setNullButton.Click();
Assert.Equal(string.Empty, target.GetAttribute("value"));
WaitAssert.Equal(string.Empty, () => target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
@ -63,12 +64,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("Changed value\t");
Assert.Equal("Changed value", boundValue.Text);
WaitAssert.Equal("Changed value", () => boundValue.Text);
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
// Remove the value altogether
setNullButton.Click();
Assert.Equal(string.Empty, target.GetAttribute("value"));
WaitAssert.Equal(string.Empty, () => target.GetAttribute("value"));
Assert.Equal(string.Empty, boundValue.Text);
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
}
@ -85,7 +86,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
target.SendKeys("Changed value");
Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet.
target.SendKeys("\t");
Assert.Equal("Changed value", boundValue.Text);
WaitAssert.Equal("Changed value", () => boundValue.Text);
}
[Fact]
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated
target.Clear();
target.SendKeys("Changed value\t");
Assert.Equal("Changed value", boundValue.Text);
WaitAssert.Equal("Changed value", () => boundValue.Text);
}
[Fact]
@ -113,12 +114,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated
target.Click();
Assert.True(target.Selected);
WaitAssert.True(() => target.Selected);
Assert.Equal("True", boundValue.Text);
// Modify data; verify checkbox is updated
invertButton.Click();
Assert.False(target.Selected);
WaitAssert.False(() => target.Selected);
Assert.Equal("False", boundValue.Text);
}
@ -133,12 +134,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated
target.Click();
Assert.False(target.Selected);
WaitAssert.False(() => target.Selected);
Assert.Equal("False", boundValue.Text);
// Modify data; verify checkbox is updated
invertButton.Click();
Assert.True(target.Selected);
WaitAssert.True(() => target.Selected);
Assert.Equal("True", boundValue.Text);
}
@ -152,13 +153,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated
target.SelectByText("Third choice");
Assert.Equal("Third", boundValue.Text);
WaitAssert.Equal("Third", () => boundValue.Text);
// Also verify we can add and select new options atomically
// Don't move this into a separate test, because then the previous assertions
// would be dependent on test execution order (or would require a full page reload)
Browser.FindElement(By.Id("select-box-add-option")).Click();
Assert.Equal("Fourth", boundValue.Text);
WaitAssert.Equal("Fourth", () => boundValue.Text);
Assert.Equal("Fourth choice", target.SelectedOption.Text);
}
@ -175,7 +176,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("42\t");
Assert.Equal("42", boundValue.Text);
WaitAssert.Equal("42", () => boundValue.Text);
Assert.Equal("42", mirrorValue.GetAttribute("value"));
}
@ -192,7 +193,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("-3000000000\t");
Assert.Equal("-3000000000", boundValue.Text);
WaitAssert.Equal("-3000000000", () => boundValue.Text);
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
}
@ -209,7 +210,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("-3.141\t");
Assert.Equal("-3.141", boundValue.Text);
WaitAssert.Equal("-3.141", () => boundValue.Text);
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
}
@ -226,14 +227,14 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Modify target; verify value is updated and that textboxes linked to the same data are updated
target.Clear();
target.SendKeys("-3.14159265359\t");
Assert.Equal("-3.14159265359", boundValue.Text);
WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
// Modify target; verify value is updated and that textboxes linked to the same data are updated
// Double shouldn't preserve trailing zeros
target.Clear();
target.SendKeys("0.010\t");
Assert.Equal("0.01", boundValue.Text);
WaitAssert.Equal("0.01", () => boundValue.Text);
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
}
@ -251,7 +252,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Decimal should preserve trailing zeros
target.Clear();
target.SendKeys("0.010\t");
Assert.Equal("0.010", boundValue.Text);
WaitAssert.Equal("0.010", () => boundValue.Text);
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
}
}

Просмотреть файл

@ -11,6 +11,7 @@ using BasicTestApp.HierarchicalImportsTest.Subdir;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
@ -20,11 +21,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public ComponentRenderingTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
}
[Fact]
@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking button increments count
appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("Current count: 1", countDisplayElement.Text);
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
}
[Fact]
@ -84,11 +85,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking 'tick' changes the state, and starts a task
appElement.FindElement(By.Id("tick")).Click();
Assert.Equal("Started", stateElement.Text);
WaitAssert.Equal("Started", () => stateElement.Text);
// Clicking 'tock' completes the task, which updates the state
appElement.FindElement(By.Id("tock")).Click();
Assert.Equal("Stopped", stateElement.Text);
WaitAssert.Equal("Stopped", () => stateElement.Text);
}
[Fact]
@ -103,12 +104,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Typing adds element
inputElement.SendKeys("a");
Assert.Collection(liElements(),
WaitAssert.Collection(liElements,
li => Assert.Equal("a", li.Text));
// Typing again adds another element
inputElement.SendKeys("b");
Assert.Collection(liElements(),
WaitAssert.Collection(liElements,
li => Assert.Equal("a", li.Text),
li => Assert.Equal("b", li.Text));
@ -127,17 +128,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Initial count is zero; clicking button increments count
Assert.Equal("Current count: 0", countDisplayElement.Text);
incrementButton.Click();
Assert.Equal("Current count: 1", countDisplayElement.Text);
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
// We can remove an event handler
toggleClickHandlerCheckbox.Click();
WaitAssert.Empty(() => appElement.FindElements(By.Id("listening-message")));
incrementButton.Click();
Assert.Equal("Current count: 1", countDisplayElement.Text);
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
// We can add an event handler
toggleClickHandlerCheckbox.Click();
appElement.FindElement(By.Id("listening-message"));
incrementButton.Click();
Assert.Equal("Current count: 2", countDisplayElement.Text);
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
}
[Fact]
@ -164,7 +167,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking increments count in child component
appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("Current count: 1", counterDisplay.Text);
WaitAssert.Equal("Current count: 1", () => counterDisplay.Text);
}
[Fact]
@ -179,7 +182,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Clicking increments count in child element
appElement.FindElement(By.TagName("button")).Click();
Assert.Equal("1", messageElementInChild.Text);
WaitAssert.Equal("1", () => messageElementInChild.Text);
}
[Fact]
@ -192,12 +195,22 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Func<IEnumerable<IWebElement>> childComponentWrappers = () => appElement.FindElements(By.TagName("p"));
Assert.Empty(childComponentWrappers());
// Click to add some child components
// Click to add/remove some child components
addButton.Click();
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
addButton.Click();
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
elem => Assert.Equal("Child 2", elem.FindElement(By.ClassName("message")).Text));
removeButton.Click();
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
addButton.Click();
Assert.Collection(childComponentWrappers(),
WaitAssert.Collection(childComponentWrappers,
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
elem => Assert.Equal("Child 3", elem.FindElement(By.ClassName("message")).Text));
}
@ -215,7 +228,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// When property changes, child is renotified before rerender
incrementButton.Click();
Assert.Equal("You supplied: 101", suppliedValueElement.Text);
WaitAssert.Equal("You supplied: 101", () => suppliedValueElement.Text);
Assert.Equal("I computed: 202", computedValueElement.Text);
}
@ -225,8 +238,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Initially, the region isn't shown
var appElement = MountTestComponent<RenderFragmentToggler>();
var originalButton = appElement.FindElement(By.TagName("button"));
var fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]"));
Assert.Empty(fragmentElements);
Func<IEnumerable<IWebElement>> fragmentElements = () => appElement.FindElements(By.CssSelector("p[name=fragment-element]"));
Assert.Empty(fragmentElements());
// The JS-side DOM builder handles regions correctly, placing elements
// after the region after the corresponding elements
@ -234,13 +247,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// When we click the button, the region is shown
originalButton.Click();
fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]"));
Assert.Single(fragmentElements);
WaitAssert.Single(fragmentElements);
// The button itself was preserved, so we can click it again and see the effect
originalButton.Click();
fragmentElements = appElement.FindElements(By.CssSelector("p[name=fragment-element]"));
Assert.Empty(fragmentElements);
WaitAssert.Empty(fragmentElements);
}
[Fact]
@ -263,11 +274,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// .NET code access to browser APIs
var showPromptButton = appElement.FindElements(By.TagName("button")).First();
showPromptButton.Click();
var modal = Browser.SwitchTo().Alert();
var modal = new WebDriverWait(Browser, TimeSpan.FromSeconds(3))
.Until(SwitchToAlert);
modal.SendKeys("Some value from test");
modal.Accept();
var promptResult = appElement.FindElement(By.TagName("strong"));
Assert.Equal("Some value from test", promptResult.Text);
WaitAssert.Equal("Some value from test", () => promptResult.Text);
// NuGet packages can also embed entire Blazor components (themselves
// authored as Razor files), including static content. The CSS value
@ -280,7 +293,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button"));
Assert.Equal("Click me", externalComponentButton.Text);
externalComponentButton.Click();
Assert.Equal("It works", externalComponentButton.Text);
WaitAssert.Equal("It works", () => externalComponentButton.Text);
}
[Fact]
@ -324,9 +337,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
buttonElement.Click();
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
buttonElement.Click();
Assert.Equal("Clicks: 2", inputElement.GetAttribute("value"));
WaitAssert.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
}
[Fact]
@ -342,17 +355,16 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Remove the captured element
checkbox.Click();
Assert.Empty(appElement.FindElements(By.Id("capturedElement")));
WaitAssert.Empty(() => appElement.FindElements(By.Id("capturedElement")));
// Re-add it; observe it starts empty again
checkbox.Click();
var inputElement = appElement.FindElement(By.Id("capturedElement"));
Assert.NotNull(inputElement);
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
// See that the capture variable was automatically updated to reference the new instance
buttonElement.Click();
Assert.Equal("Clicks: 1", inputElement.GetAttribute("value"));
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
}
[Fact]
@ -363,26 +375,27 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var currentCountTextSelector = By.CssSelector("#child-component p:first-of-type");
var resetButton = appElement.FindElement(By.Id("reset-child"));
var toggleChildCheckbox = appElement.FindElement(By.Id("toggle-child"));
Func<string> currentCountText = () => appElement.FindElement(currentCountTextSelector).Text;
// Verify the reference was captured initially
appElement.FindElement(incrementButtonSelector).Click();
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 1", currentCountText);
resetButton.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 0", currentCountText);
appElement.FindElement(incrementButtonSelector).Click();
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 1", currentCountText);
// Remove and re-add a new instance of the child, checking the text was reset
toggleChildCheckbox.Click();
Assert.Empty(appElement.FindElements(incrementButtonSelector));
WaitAssert.Empty(() => appElement.FindElements(incrementButtonSelector));
toggleChildCheckbox.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 0", currentCountText);
// Verify we have a new working reference
appElement.FindElement(incrementButtonSelector).Click();
Assert.Equal("Current count: 1", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 1", currentCountText);
resetButton.Click();
Assert.Equal("Current count: 0", appElement.FindElement(currentCountTextSelector).Text);
WaitAssert.Equal("Current count: 0", currentCountText);
}
[Fact]
@ -392,5 +405,17 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var inputElement = appElement.FindElement(By.TagName("input"));
Assert.Equal("Value set after render", inputElement.GetAttribute("value"));
}
static IAlert SwitchToAlert(IWebDriver driver)
{
try
{
return driver.SwitchTo().Alert();
}
catch (NoAlertPresentException)
{
return null;
}
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using BasicTestApp;
@ -20,12 +20,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// the one that doesn't match the 'bubbles' flag on the received event object.
public EventBubblingTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
MountTestComponent<EventBubblingComponent>();
}
@ -35,9 +35,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("button-with-onclick")).Click();
// Triggers event on target and ancestors with handler in upwards direction
Assert.Equal(
WaitAssert.Equal(
new[] { "target onclick", "parent onclick" },
GetLogLines());
GetLogLines);
}
[Fact]
@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("button-without-onclick")).Click();
// Triggers event on ancestors with handler in upwards direction
Assert.Equal(
WaitAssert.Equal(
new[] { "parent onclick" },
GetLogLines());
GetLogLines);
}
[Fact]
@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze");
// Triggers event on target and ancestors with handler in upwards direction
Assert.Equal(
WaitAssert.Equal(
new[] { "target onsneeze", "parent onsneeze" },
GetLogLines());
GetLogLines);
}
[Fact]
@ -68,9 +68,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze");
// Triggers event on ancestors with handler in upwards direction
Assert.Equal(
WaitAssert.Equal(
new[] { "parent onsneeze" },
GetLogLines());
GetLogLines);
}
[Fact]
@ -79,7 +79,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("input-with-onfocus")).Click();
// Triggers event only on target, not other ancestors with event handler
Assert.Equal(new[] { "target onfocus" }, GetLogLines());
WaitAssert.Equal(
new[] { "target onfocus" },
GetLogLines);
}
[Fact]
@ -88,7 +90,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
Browser.FindElement(By.Id("input-without-onfocus")).Click();
// Triggers no event
Assert.Empty(GetLogLines());
WaitAssert.Empty(GetLogLines);
}
private string[] GetLogLines()

Просмотреть файл

@ -1,13 +1,11 @@
// 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.
using System;
using BasicTestApp;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
@ -16,8 +14,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class EventTest : BasicTestAppTestBase
{
public EventTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@ -38,13 +36,13 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// Focus the target, verify onfocusin is fired
input.Click();
Assert.Equal("onfocus,onfocusin,", output.Text);
WaitAssert.Equal("onfocus,onfocusin,", () => output.Text);
// Focus something else, verify onfocusout is also fired
var other = Browser.FindElement(By.Id("other"));
other.Click();
Assert.Equal("onfocus,onfocusin,onblur,onfocusout,", output.Text);
WaitAssert.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
}
[Fact]
@ -65,7 +63,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
.MoveToElement(other);
actions.Perform();
Assert.Equal("onmouseover,onmouseout,", output.Text);
WaitAssert.Equal("onmouseover,onmouseout,", () => output.Text);
}
[Fact]
@ -84,7 +82,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
.MoveToElement(input, 10, 10);
actions.Perform();
Assert.Contains("onmousemove,", output.Text);
WaitAssert.Contains("onmousemove,", () => output.Text);
}
[Fact]
@ -103,12 +101,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
var actions = new Actions(Browser).ClickAndHold(input);
actions.Perform();
Assert.Equal("onmousedown,", output.Text);
WaitAssert.Equal("onmousedown,", () => output.Text);
actions = new Actions(Browser).Release(input);
actions.Perform();
Assert.Equal("onmousedown,onmouseup,", output.Text);
WaitAssert.Equal("onmousedown,onmouseup,", () => output.Text);
}
}
}

Просмотреть файл

@ -1,4 +1,4 @@
// 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.
using BasicTestApp.HttpClientTest;
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public HttpClientTest(
BrowserFixture browserFixture,
DevHostServerFixture<BasicTestApp.Program> devHostServerFixture,
ToggleExecutionModeServerFixture<BasicTestApp.Program> devHostServerFixture,
AspNetSiteServerFixture apiServerFixture,
ITestOutputHelper output)
: base(browserFixture, devHostServerFixture, output)

Просмотреть файл

@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public InteropTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
@ -26,7 +26,42 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public void CanInvokeDotNetMethods()
{
// Arrange
var expectedValues = new Dictionary<string, string>
var expectedAsyncValues = new Dictionary<string, string>
{
["VoidParameterlessAsync"] = "[]",
["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["result1Async"] = @"[0.1,0.2]",
["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
["resultReturnDotNetObjectByRefAsync"] = "1001",
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
["instanceMethodIncomingByRefAsync"] = "123",
["instanceMethodOutgoingByRefAsync"] = "1234",
["stringValueUpperAsync"] = "MY STRING",
["testDtoNonSerializedValueAsync"] = "99999",
["testDtoAsync"] = "Same",
["returnPrimitiveAsync"] = "123",
};
var expectedSyncValues = new Dictionary<string, string>
{
["VoidParameterless"] = "[]",
["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
@ -37,15 +72,6 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
["VoidWithSixParameters"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["VoidWithSevenParameters"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["VoidWithEightParameters"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["VoidParameterlessAsync"] = "[]",
["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["result1"] = @"[0.1,0.2]",
["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
["result3"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
@ -55,40 +81,29 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["result1Async"] = @"[0.1,0.2]",
["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,123]",
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,123,16]",
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,123,20,40]",
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,123,24,48,6.25]",
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,123,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,123,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
["ExceptionFromSyncMethod"] = "Function threw an exception!",
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
["resultReturnDotNetObjectByRefSync"] = "1000",
["resultReturnDotNetObjectByRefAsync"] = "1001",
["instanceMethodThisTypeName"] = @"""JavaScriptInterop""",
["instanceMethodStringValueUpper"] = @"""MY STRING""",
["instanceMethodIncomingByRef"] = "123",
["instanceMethodOutgoingByRef"] = "1234",
["instanceMethodThisTypeNameAsync"] = @"""JavaScriptInterop""",
["instanceMethodStringValueUpperAsync"] = @"""MY STRING""",
["instanceMethodIncomingByRefAsync"] = "123",
["instanceMethodOutgoingByRefAsync"] = "1234",
["stringValueUpperSync"] = "MY STRING",
["testDtoNonSerializedValueSync"] = "99999",
["testDtoSync"] = "Same",
["stringValueUpperAsync"] = "MY STRING",
["testDtoNonSerializedValueAsync"] = "99999",
["testDtoAsync"] = "Same",
["returnPrimitive"] = "123",
["returnPrimitiveAsync"] = "123",
};
// Include the sync assertions only when running under WebAssembly
var expectedValues = expectedAsyncValues;
if (!_serverFixture.UsingAspNetHost)
{
foreach (var kvp in expectedSyncValues)
{
expectedValues.Add(kvp.Key, kvp.Value);
}
}
var actualValues = new Dictionary<string, string>();
// Act

Просмотреть файл

@ -15,25 +15,22 @@ using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class RoutingTest : BasicTestAppTestBase, IDisposable
public class RoutingTest : BasicTestAppTestBase
{
private readonly ServerFixture _server;
public RoutingTest(
BrowserFixture browserFixture,
DevHostServerFixture<Program> serverFixture,
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
_server = serverFixture;
Navigate(ServerPathBase, noReload: true);
WaitUntilDotNetRunningInBrowser();
Navigate(ServerPathBase, noReload: false);
WaitUntilTestSelectorReady();
}
[Fact]
public void CanArriveAtDefaultPage()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
@ -47,7 +44,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
// servers to enforce a canonical URL (with trailing slash) for the homepage.
// But in case they don't want to, we need to handle it the same as if the URL does
// have a trailing slash.
SetUrlViaPushState($"{ServerPathBase}");
SetUrlViaPushState("");
var app = MountTestComponent<TestRouter>();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
@ -57,7 +54,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact]
public void CanArriveAtPageWithParameters()
{
SetUrlViaPushState($"{ServerPathBase}/WithParameters/Name/Ghi/LastName/O'Jkl");
SetUrlViaPushState("/WithParameters/Name/Ghi/LastName/O'Jkl");
var app = MountTestComponent<TestRouter>();
Assert.Equal("Your full name is Ghi O'Jkl.", app.FindElement(By.Id("test-info")).Text);
@ -67,7 +64,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact]
public void CanArriveAtNonDefaultPage()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
@ -77,11 +74,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact]
public void CanFollowLinkToOtherPage()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other")).Click();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
}
@ -93,20 +90,20 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
try
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
var button = app.FindElement(By.LinkText("Other"));
new Actions(Browser).KeyDown(key).Click(button).Build().Perform();
Assert.Equal(2, Browser.WindowHandles.Count);
WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
}
finally
{
// Leaving the ctrl key up
new Actions(Browser).KeyUp(key).Build().Perform();
// Closing newly opened windows if a new one was opened
while (Browser.WindowHandles.Count > 1)
{
@ -123,7 +120,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact]
public void CanFollowLinkToOtherPageDoesNotOpenNewWindow()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
@ -135,106 +132,106 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
[Fact]
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
}
[Fact]
public void CanFollowLinkToEmptyStringHrefAsBaseRelativeUrl()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
}
[Fact]
public void CanFollowLinkToPageWithParameters()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("With parameters")).Click();
Assert.Equal("Your full name is Abc McDef.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("Your full name is Abc McDef.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("With parameters");
}
[Fact]
public void CanFollowLinkToDefaultPage()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default (matches all)")).Click();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
}
[Fact]
public void CanFollowLinkToOtherPageWithQueryString()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with query")).Click();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Other", "Other with query");
}
[Fact]
public void CanFollowLinkToDefaultPageWithQueryString()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with query")).Click();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Default with query");
}
[Fact]
public void CanFollowLinkToOtherPageWithHash()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Other with hash")).Click();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Other", "Other with hash");
}
[Fact]
public void CanFollowLinkToDefaultPageWithHash()
{
SetUrlViaPushState($"{ServerPathBase}/Other");
SetUrlViaPushState("/Other");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.LinkText("Default with hash")).Click();
Assert.Equal("This is the default page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Default with hash");
}
[Fact]
public void CanNavigateProgrammatically()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var app = MountTestComponent<TestRouter>();
app.FindElement(By.TagName("button")).Click();
Assert.Equal("This is another page.", app.FindElement(By.Id("test-info")).Text);
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
}
[Fact]
public void ClickingAnchorWithNoHrefShouldNotNavigate()
{
SetUrlViaPushState($"{ServerPathBase}/");
SetUrlViaPushState("/");
var initialUrl = Browser.Url;
var app = MountTestComponent<TestRouter>();
@ -244,25 +241,19 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
}
public void Dispose()
{
// Clear any existing state
SetUrlViaPushState(ServerPathBase);
MountTestComponent<TextOnlyComponent>();
}
private void SetUrlViaPushState(string relativeUri)
{
var pathBaseWithoutHash = ServerPathBase.Split('#')[0];
var jsExecutor = (IJavaScriptExecutor)Browser;
var absoluteUri = new Uri(_server.RootUri, relativeUri);
var absoluteUri = new Uri(_serverFixture.RootUri, $"{pathBaseWithoutHash}{relativeUri}");
jsExecutor.ExecuteScript($"Blazor.navigateTo('{absoluteUri.ToString().Replace("'", "\\'")}')");
}
private void AssertHighlightedLinks(params string[] linkTexts)
{
var actual = Browser.FindElements(By.CssSelector("a.active"));
var actualTexts = actual.Select(x => x.Text);
Assert.Equal(linkTexts, actualTexts);
WaitAssert.Equal(linkTexts, () => Browser
.FindElements(By.CssSelector("a.active"))
.Select(x => x.Text));
}
}
}

Просмотреть файл

@ -15,15 +15,12 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public class StandaloneAppTest
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable
{
private readonly ServerFixture _serverFixture;
public StandaloneAppTest(
BrowserFixture browserFixture,
DevHostServerFixture<StandaloneApp.Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
_serverFixture = serverFixture;
Navigate("/", noReload: true);
WaitUntilLoaded();
}

Просмотреть файл

@ -1,4 +1,4 @@
<h1>Counter</h1>
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<p><button onclick="@((handleClicks ? (Action)IncrementCount : null))">Click me</button></p>
@ -7,6 +7,11 @@
Toggle click handler registration
</label>
@if (handleClicks)
{
<p id="listening-message">Listening</p>
}
@functions {
int currentCount = 0;
bool handleClicks = true;

Просмотреть файл

@ -0,0 +1,64 @@
@using Microsoft.AspNetCore.Blazor.RenderTree
<div id="test-selector">
Select test:
<select bind=@SelectedComponentTypeName>
<option value="none">Choose...</option>
<option value="BasicTestApp.InteropComponent">Interop component</option>
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</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.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.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.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.RouterTest.TestRouter">Router</option>
</select>
@if (SelectedComponentType != null)
{
<span id="source-info"><code><tt>@(SelectedComponentType.Name.Replace(".", "/")).cshtml</tt></code></span>
}
<hr />
</div>
<app>
@((RenderFragment)RenderSelectedComponent)
</app>
@functions {
string SelectedComponentTypeName { get; set; } = "none";
Type SelectedComponentType
=> SelectedComponentTypeName == "none" ? null : Type.GetType(SelectedComponentTypeName);
void RenderSelectedComponent(RenderTreeBuilder builder)
{
if (SelectedComponentType != null)
{
builder.OpenComponent(0, SelectedComponentType);
builder.CloseComponent();
}
}
}

Просмотреть файл

@ -1,5 +1,6 @@
@using Microsoft.JSInterop
@using BasicTestApp.InteropTest
@using System.Runtime.InteropServices
<button id="btn-interop" onclick="@InvokeInteropAsync">Invoke interop!</button>
@ -65,18 +66,24 @@
public async Task InvokeInteropAsync()
{
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
var shouldSupportSyncInterop = RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY"));
var testDTOTOPassByRef = new TestDTO(nonSerializedValue: 123);
var instanceMethodsTarget = new JavaScriptInterop();
Console.WriteLine("Starting interop invocations.");
await JSRuntime.Current.InvokeAsync<object>(
"jsInteropTests.invokeDotNetInteropMethodsAsync",
shouldSupportSyncInterop,
new DotNetObjectRef(testDTOTOPassByRef),
new DotNetObjectRef(instanceMethodsTarget));
if (shouldSupportSyncInterop)
{
InvokeInProcessInterop();
}
Console.WriteLine("Showing interop invocation results.");
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
var collectResults = await JSRuntime.Current.InvokeAsync<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
ReturnValues = collectResults.ToDictionary(kvp => kvp.Key,kvp => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(kvp.Value)));
@ -87,15 +94,6 @@
invocations[interopResult.Key] = interopResultValue;
}
try
{
inProcRuntime.Invoke<object>("jsInteropTests.functionThrowsException");
}
catch (JSException e)
{
ExceptionFromSyncMethod = e;
}
try
{
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.asyncFunctionThrowsSyncException");
@ -120,15 +118,39 @@
{ "stringValue", "My string" },
{ "testDto", new DotNetObjectRef(passDotNetObjectByRef) },
};
ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke<Dictionary<string, object>>("receiveDotNetObjectByRef", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefAsyncResult = await JSRuntime.Current.InvokeAsync<Dictionary<string, object>>("receiveDotNetObjectByRefAsync", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
ReceiveDotNetObjectByRefAsyncResult["testDto"] = ReceiveDotNetObjectByRefAsyncResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
ReturnValues["returnPrimitive"] = inProcRuntime.Invoke<int>("returnPrimitive").ToString();
ReturnValues["returnPrimitiveAsync"] = (await JSRuntime.Current.InvokeAsync<int>("returnPrimitiveAsync")).ToString();
if (shouldSupportSyncInterop)
{
ReturnValues["returnPrimitive"] = ((IJSInProcessRuntime)JSRuntime.Current).Invoke<int>("returnPrimitive").ToString();
}
Invocations = invocations;
DoneWithInterop = true;
}
public void InvokeInProcessInterop()
{
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
try
{
inProcRuntime.Invoke<object>("jsInteropTests.functionThrowsException");
}
catch (JSException e)
{
ExceptionFromSyncMethod = e;
}
var passDotNetObjectByRef = new TestDTO(99999);
var passDotNetObjectByRefArg = new Dictionary<string, object>
{
{ "stringValue", "My string" },
{ "testDto", new DotNetObjectRef(passDotNetObjectByRef) },
};
ReceiveDotNetObjectByRefResult = inProcRuntime.Invoke<Dictionary<string, object>>("receiveDotNetObjectByRef", passDotNetObjectByRefArg);
ReceiveDotNetObjectByRefResult["testDto"] = ReceiveDotNetObjectByRefResult["testDto"] == passDotNetObjectByRef ? "Same" : "Different";
}
}

Просмотреть файл

@ -1,32 +1,19 @@
// 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 Microsoft.AspNetCore.Blazor.Browser.Http;
using Microsoft.AspNetCore.Blazor.Browser.Rendering;
using Microsoft.AspNetCore.Blazor.Browser.Services;
using Microsoft.JSInterop;
using System;
using Microsoft.AspNetCore.Blazor.Hosting;
namespace BasicTestApp
{
public class Program
{
static void Main(string[] args)
public static void Main(string[] args)
{
// Needed because the test server runs on a different port than the client app,
// and we want to test sending/receiving cookies undering this config
BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
// Signal to tests that we're ready
GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated());
JSRuntime.Current.InvokeAsync<object>("testReady");
CreateHostBuilder(args).Build().Run();
}
[JSInvokable(nameof(MountTestComponent))]
public static void MountTestComponent(string componentTypeName)
{
var componentType = Type.GetType(componentTypeName);
new BrowserRenderer().AddComponent(componentType, "app");
}
public static IWebAssemblyHostBuilder CreateHostBuilder(string[] args) =>
BlazorWebAssemblyHost.CreateDefaultBuilder()
.UseBlazorStartup<Startup>();
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// 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 Microsoft.AspNetCore.Blazor.Browser.Http;
using Microsoft.AspNetCore.Blazor.Browser.Services;
using Microsoft.AspNetCore.Blazor.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Runtime.InteropServices;
namespace BasicTestApp
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IBlazorApplicationBuilder app)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
{
// Needed because the test server runs on a different port than the client app,
// and we want to test sending/receiving cookies undering this config
BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated());
}
app.AddComponent<Index>("root");
}
}
}

Просмотреть файл

@ -6,71 +6,26 @@
<base href="/subdir/" />
</head>
<body>
<div id="test-selector" style="display: none;">
Select test:
<select onchange="mountTestComponent(event.target.value)">
<option value="">Choose...</option>
<option value="BasicTestApp.InteropComponent">Interop component</option>
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
<option value="BasicTestApp.CounterComponent">Counter</option>
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
<option value="BasicTestApp.FocusEventComponent">Focus events</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.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.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.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.RouterTest.Default">Router</option> Excluded because it requires additional setup to work correctly when loaded manually -->
</select>
&nbsp;
<span id="source-info"></span>
<hr />
</div>
<app>Loading...</app>
<script src="_framework/blazor.webassembly.js"></script>
<root>Loading...</root>
<!-- Used for testing interop scenarios between JS and .NET -->
<script src="js/jsinteroptests.js"></script>
<script>
// The client-side .NET code calls this when it is ready to be called from test code
// The Xunit test code polls until it sees the flag is set
function testReady() {
window.isTestReady = true;
document.getElementsByTagName('APP')[0].textContent = '';
document.getElementById('test-selector').style.display = 'block';
}
// The Xunit test code calls this when setting up tests for specific components
function mountTestComponent(typeName) {
document.getElementById('source-info').innerHTML = '<code><tt>' + typeName.replace(/\./g, '/') + '.cshtml</code></strong>';
DotNet.invokeMethodAsync('BasicTestApp', 'MountTestComponent', typeName);
}
// Used by ElementRefComponent
function setElementValue(element, newValue) {
element.value = newValue;
}
(function () {
// Load either blazor.webassembly.js or blazor.server.js depending
// on the hash part of the URL. This is just to give a way for the
// test runner to make the selection.
var src = location.hash === '#server'
? 'blazor.server.js'
: 'blazor.webassembly.js';
document.write('<script src="_framework/' + src + '"><' + '/script>');
})();
</script>
</body>
</html>

Просмотреть файл

@ -4,40 +4,42 @@
var results = {};
var assemblyName = 'BasicTestApp';
function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarget) {
console.log('Invoking void sync methods.');
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
function invokeDotNetInteropMethodsAsync(shouldSupportSyncInterop, dotNetObjectByRef, instanceMethodsTarget) {
if (shouldSupportSyncInterop) {
console.log('Invoking void sync methods.');
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
DotNet.invokeMethod(assemblyName, 'VoidWithEightParameters', ...createArgumentList(8, dotNetObjectByRef));
console.log('Invoking returning sync methods.');
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef));
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef));
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef));
console.log('Invoking returning sync methods.');
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1, dotNetObjectByRef));
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2, dotNetObjectByRef));
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3, dotNetObjectByRef));
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4, dotNetObjectByRef));
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5, dotNetObjectByRef));
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6, dotNetObjectByRef));
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7, dotNetObjectByRef));
results['result9'] = DotNet.invokeMethod(assemblyName, 'EchoEightParameters', ...createArgumentList(8, dotNetObjectByRef));
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
var returnDotNetObjectByRefResult = DotNet.invokeMethod(assemblyName, 'ReturnDotNetObjectByRef');
results['resultReturnDotNetObjectByRefSync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', returnDotNetObjectByRefResult['Some sync instance']);
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
stringValue: 'My string',
dtoByRef: dotNetObjectByRef
});
results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName;
results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper;
results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef;
results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef);
var instanceMethodResult = instanceMethodsTarget.invokeMethod('InstanceMethod', {
stringValue: 'My string',
dtoByRef: dotNetObjectByRef
});
results['instanceMethodThisTypeName'] = instanceMethodResult.thisTypeName;
results['instanceMethodStringValueUpper'] = instanceMethodResult.stringValueUpper;
results['instanceMethodIncomingByRef'] = instanceMethodResult.incomingByRef;
results['instanceMethodOutgoingByRef'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', instanceMethodResult.outgoingByRef);
}
console.log('Invoking void async methods.');
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
@ -70,8 +72,9 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8, dotNetObjectByRef)))
.then(r => results['result9Async'] = r)
.then(() => DotNet.invokeMethodAsync(assemblyName, 'ReturnDotNetObjectByRefAsync'))
.then(r => DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']))
.then(r => {
results['resultReturnDotNetObjectByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r['Some async instance']);
results['resultReturnDotNetObjectByRefAsync'] = r;
})
.then(() => instanceMethodsTarget.invokeMethodAsync('InstanceMethodAsync', {
stringValue: 'My string',
@ -81,13 +84,15 @@ function invokeDotNetInteropMethodsAsync(dotNetObjectByRef, instanceMethodsTarge
results['instanceMethodThisTypeNameAsync'] = r.thisTypeName;
results['instanceMethodStringValueUpperAsync'] = r.stringValueUpper;
results['instanceMethodIncomingByRefAsync'] = r.incomingByRef;
results['instanceMethodOutgoingByRefAsync'] = DotNet.invokeMethod(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef);
return DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', r.outgoingByRef);
}).then(r => {
results['instanceMethodOutgoingByRefAsync'] = r;
})
})
.then(() => {
console.log('Invoking methods that throw exceptions');
try {
DotNet.invokeMethod(assemblyName, 'ThrowException');
shouldSupportSyncInterop && DotNet.invokeMethod(assemblyName, 'ThrowException');
} catch (e) {
results['ThrowException'] = e.message;
}
@ -224,10 +229,18 @@ function receiveDotNetObjectByRef(incomingData) {
}
function receiveDotNetObjectByRefAsync(incomingData) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
const promiseResult = receiveDotNetObjectByRef(incomingData);
resolve(promiseResult);
}, 100);
const stringValue = incomingData.stringValue;
const testDto = incomingData.testDto;
// To verify we received a proper reference to testDto, pass it back into .NET
// to have it evaluate something that only .NET can know
return DotNet.invokeMethodAsync(assemblyName, 'ExtractNonSerializedValue', testDto).then(testDtoNonSerializedValue => {
// To show we can return a .NET object by ref anywhere in a complex structure,
// return it among other values
return {
stringValueUpper: stringValue.toUpperCase(),
testDtoNonSerializedValue: testDtoNonSerializedValue,
testDto: testDto
};
});
}

Просмотреть файл

@ -1,4 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -22,6 +22,7 @@ namespace TestServer
{
options.AddPolicy("AllowAll", _ => { /* Controlled below */ });
});
services.AddServerSideBlazor<BasicTestApp.Startup>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -34,6 +35,12 @@ namespace TestServer
AllowCorsForAnyLocalhostPort(app);
app.UseMvc();
// Mount the server-side Blazor app on /subdir
app.Map("/subdir", subdirApp =>
{
subdirApp.UseServerSideBlazor<BasicTestApp.Startup>();
});
}
private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app)

Просмотреть файл

@ -7,4 +7,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="$(AspNetCorePackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AspNetCore.Blazor.Server\Microsoft.AspNetCore.Blazor.Server.csproj" />
<ProjectReference Include="..\BasicTestApp\BasicTestApp.csproj" />
</ItemGroup>
</Project>