diff --git a/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs
index b8d8994c2a..39d8d0d90e 100644
--- a/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Components/BindMethods.cs
@@ -35,10 +35,10 @@ namespace Microsoft.AspNetCore.Blazor.Components
///
/// Not intended to be used directly.
///
- public static UIEventHandler GetEventHandlerValue(Action value)
+ public static MulticastDelegate GetEventHandlerValue(Action value)
where T : UIEventArgs
{
- return e => value((T)e);
+ return value;
}
///
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs
index 73f27914c1..17fb4a7e6d 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeBuilder.cs
@@ -1,10 +1,10 @@
// 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.Components;
-using Microsoft.AspNetCore.Blazor.Rendering;
using System;
using System.Collections.Generic;
+using Microsoft.AspNetCore.Blazor.Components;
+using Microsoft.AspNetCore.Blazor.Rendering;
namespace Microsoft.AspNetCore.Blazor.RenderTree
{
@@ -101,9 +101,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
=> AddContent(sequence, textContent?.ToString());
///
+ ///
/// Appends a frame representing a bool-valued attribute.
+ ///
+ ///
/// The attribute is associated with the most recently added element. If the value is false and the
/// current element is not a component, the frame will be omitted.
+ ///
///
/// An integer that represents the position of the instruction in the source code.
/// The name of the attribute.
@@ -124,9 +128,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
}
///
+ ///
/// Appends a frame representing a string-valued attribute.
+ ///
+ ///
/// The attribute is associated with the most recently added element. If the value is null and the
/// current element is not a component, the frame will be omitted.
+ ///
///
/// An integer that represents the position of the instruction in the source code.
/// The name of the attribute.
@@ -141,14 +149,44 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
}
///
- /// Appends a frame representing an -valued attribute.
+ ///
+ /// Appends a frame representing an -valued attribute.
+ ///
+ ///
/// The attribute is associated with the most recently added element. If the value is null and the
/// current element is not a component, the frame will be omitted.
+ ///
///
+ /// The .
/// An integer that represents the position of the instruction in the source code.
/// The name of the attribute.
/// The value of the attribute.
public void AddAttribute(int sequence, string name, UIEventHandler value)
+ {
+ AddAttribute(sequence, name, (MulticastDelegate)value);
+ }
+
+ ///
+ ///
+ /// Appends a frame representing a delegate-valued attribute.
+ ///
+ ///
+ /// The attribute is associated with the most recently added element. If the value is null and the
+ /// current element is not a component, the frame will be omitted.
+ ///
+ ///
+ /// An integer that represents the position of the instruction in the source code.
+ /// The name of the attribute.
+ /// The value of the attribute.
+ ///
+ /// This method is provided for infrastructure purposes, and is used to be
+ /// to provide support for delegates of specific
+ /// types. For a good programming experience when using a custom delegate type, define an
+ /// extension method similar to
+ ///
+ /// that calls this method.
+ ///
+ public void AddAttribute(int sequence, string name, MulticastDelegate value)
{
AssertCanAddAttribute();
if (value != null || _lastNonAttributeFrameType == RenderTreeFrameType.Component)
@@ -185,7 +223,7 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
// Don't add anything for false bool value.
}
- else if (value is UIEventHandler eventHandler)
+ else if (value is MulticastDelegate)
{
Append(RenderTreeFrame.Attribute(sequence, name, value));
}
@@ -207,8 +245,12 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
}
///
+ ///
/// Appends a frame representing an attribute.
+ ///
+ ///
/// The attribute is associated with the most recently added element.
+ ///
///
/// An integer that represents the position of the instruction in the source code.
/// The name of the attribute.
diff --git a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs
index 31828e4da2..83c25bdd7e 100644
--- a/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs
+++ b/src/Microsoft.AspNetCore.Blazor/RenderTree/RenderTreeDiffBuilder.cs
@@ -597,7 +597,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
private static void InitializeNewAttributeFrame(ref DiffContext diffContext, ref RenderTreeFrame newFrame)
{
- if (newFrame.AttributeValue is UIEventHandler)
+ // Any attribute with an event handler id will be callable via DOM events
+ //
+ // We're following a simple heuristic here that's reflected in the ts runtime
+ // based on the common usage of attributes for DOM events.
+ if (newFrame.AttributeValue is MulticastDelegate &&
+ newFrame.AttributeName.Length >= 3 &&
+ newFrame.AttributeName.StartsWith("on"))
{
diffContext.Renderer.AssignEventHandlerId(ref newFrame);
}
diff --git a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
index b758382e45..78c221f643 100644
--- a/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
+++ b/src/Microsoft.AspNetCore.Blazor/Rendering/Renderer.cs
@@ -113,7 +113,30 @@ namespace Microsoft.AspNetCore.Blazor.Rendering
internal void AssignEventHandlerId(ref RenderTreeFrame frame)
{
var id = ++_lastEventHandlerId;
- _eventHandlersById.Add(id, (UIEventHandler)frame.AttributeValue);
+
+ // The attribute value might be a more specialized type like UIKeyboardEventHandler.
+ // In that case, it won't be a UIEventHandler, and it will go down the MulticastDelegate
+ // code path (MulticastDelegate is any delegate).
+ //
+ // In order to dispatch the event, we need a UIEventHandler, so we're going weakly
+ // typed here. The user will get a cast exception if they map the wrong type of
+ // delegate to the event.
+ if (frame.AttributeValue is UIEventHandler wrapper)
+ {
+ _eventHandlersById.Add(id, wrapper);
+ }
+ else if (frame.AttributeValue is MulticastDelegate @delegate)
+ {
+ // IMPORTANT: we're creating an additional delegate when necessary. This is
+ // going to get cached in _eventHandlersById, but the render tree diff
+ // will operate on 'AttributeValue' which means that we'll only create a new
+ // wrapper delegate when the underlying delegate changes.
+ //
+ // TLDR: If the component uses a method group or a non-capturing lambda
+ // we don't allocate much.
+ _eventHandlersById.Add(id, (UIEventArgs e) => @delegate.DynamicInvoke(e));
+ }
+
frame = frame.WithAttributeEventHandlerId(id);
}
diff --git a/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs b/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs
index c11149579a..a333af2716 100644
--- a/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs
+++ b/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs
@@ -15,10 +15,15 @@ namespace Microsoft.AspNetCore.Blazor
}
///
- /// Supplies information about a mouse event that is being raised.
+ /// Supplies information about an input change event that is being raised.
///
- public class UIMouseEventArgs : UIEventArgs
+ public class UIChangeEventArgs : UIEventArgs
{
+ ///
+ /// Gets or sets the new value of the input. This may be a
+ /// or a .
+ ///
+ public object Value { get; set; }
}
///
@@ -33,14 +38,9 @@ namespace Microsoft.AspNetCore.Blazor
}
///
- /// Supplies information about an input change event that is being raised.
+ /// Supplies information about a mouse event that is being raised.
///
- public class UIChangeEventArgs : UIEventArgs
+ public class UIMouseEventArgs : UIEventArgs
{
- ///
- /// Gets or sets the new value of the input. This may be a
- /// or a .
- ///
- public object Value { get; set; }
}
}
diff --git a/src/Microsoft.AspNetCore.Blazor/UIEventHandler.cs b/src/Microsoft.AspNetCore.Blazor/UIEventHandler.cs
index 2e9b983ed4..0b51e5fc0b 100644
--- a/src/Microsoft.AspNetCore.Blazor/UIEventHandler.cs
+++ b/src/Microsoft.AspNetCore.Blazor/UIEventHandler.cs
@@ -4,7 +4,22 @@
namespace Microsoft.AspNetCore.Blazor
{
///
- /// Handles an event raised for a .
+ /// Handles an event raised for a .
///
- public delegate void UIEventHandler(UIEventArgs eventArgs);
+ public delegate void UIEventHandler(UIEventArgs e);
+
+ ///
+ /// Handles an event raised for a .
+ ///
+ public delegate void UIChangeEventHandler(UIChangeEventArgs e);
+
+ ///
+ /// Handles an event raised for a .
+ ///
+ public delegate void UIKeyboardEventHandler(UIKeyboardEventArgs e);
+
+ ///
+ /// Handles an event raised for a .
+ ///
+ public delegate void UIMouseEventHandler(UIMouseEventArgs e);
}
diff --git a/src/Microsoft.AspNetCore.Blazor/UIEventHandlerRenderTreeBuilderExtensions.cs b/src/Microsoft.AspNetCore.Blazor/UIEventHandlerRenderTreeBuilderExtensions.cs
new file mode 100644
index 0000000000..6b84556d5b
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Blazor/UIEventHandlerRenderTreeBuilderExtensions.cs
@@ -0,0 +1,109 @@
+// 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 Microsoft.AspNetCore.Blazor.RenderTree;
+
+namespace Microsoft.AspNetCore.Blazor
+{
+ ///
+ /// Extensions methods on for event handlers.
+ ///
+ ///
+ ///
+ /// These methods enable method-group to delegate conversion for delegates and methods that accept
+ /// types derived from .
+ ///
+ ///
+ /// This enhances the programming experience for using event handlers with the render tree builder
+ /// in components written in pure C#. These extension methods make it possible to write code like:
+ ///
+ /// builder.AddAttribute(0, "onkeypress", MyKeyPressHandler);
+ ///
+ /// Where void MyKeyPressHandler(UIKeyboardEventArgs e) is a method defined in the same class.
+ /// In this example, the author knows that the onclick event is associated with the
+ /// event args type. The component author is responsible for
+ /// providing a delegate that matches the expected event args type, an error will result in a failure
+ /// at runtime.
+ ///
+ ///
+ /// When a component is authored in Razor (.cshtml), the Razor code generator will maintain a mapping
+ /// between event names and event arg types that can be used to generate more strongly typed code.
+ /// Generated code for the same case will look like:
+ ///
+ /// builder.AddAttribute(0, "onkeypress", BindMethods.GetEventHandlerValue<UIKeyboardEventArgs>(MyKeyPressHandler));
+ ///
+ ///
+ ///
+ public static class UIEventHandlerRenderTreeBuilderExtensions
+ {
+ ///
+ ///
+ /// Appends a frame representing an -valued attribute.
+ ///
+ ///
+ /// The attribute is associated with the most recently added element. If the value is null and the
+ /// current element is not a component, the frame will be omitted.
+ ///
+ ///
+ /// The .
+ /// An integer that represents the position of the instruction in the source code.
+ /// The name of the attribute.
+ /// The value of the attribute.
+ public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, UIChangeEventHandler value)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddAttribute(sequence, name, (MulticastDelegate)value);
+ }
+
+ ///
+ ///
+ /// Appends a frame representing an -valued attribute.
+ ///
+ ///
+ /// The attribute is associated with the most recently added element. If the value is null and the
+ /// current element is not a component, the frame will be omitted.
+ ///
+ ///
+ /// The .
+ /// An integer that represents the position of the instruction in the source code.
+ /// The name of the attribute.
+ /// The value of the attribute.
+ public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, UIKeyboardEventHandler value)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddAttribute(sequence, name, (MulticastDelegate)value);
+ }
+
+ ///
+ ///
+ /// Appends a frame representing an -valued attribute.
+ ///
+ ///
+ /// The attribute is associated with the most recently added element. If the value is null and the
+ /// current element is not a component, the frame will be omitted.
+ ///
+ ///
+ /// The .
+ /// An integer that represents the position of the instruction in the source code.
+ /// The name of the attribute.
+ /// The value of the attribute.
+ public static void AddAttribute(this RenderTreeBuilder builder, int sequence, string name, UIMouseEventHandler value)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ builder.AddAttribute(sequence, name, (MulticastDelegate)value);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentRenderingRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentRenderingRazorIntegrationTest.cs
index 3f59e323f0..cff219def8 100644
--- a/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentRenderingRazorIntegrationTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/ComponentRenderingRazorIntegrationTest.cs
@@ -174,7 +174,7 @@ namespace Test
{
public class MyComponent : BlazorComponent
{
- public UIEventHandler OnClick { get; set; }
+ public UIMouseEventHandler OnClick { get; set; }
}
}
"));
@@ -186,7 +186,7 @@ namespace Test
@functions {{
private int counter;
- private void Increment(UIEventArgs e) {{
+ private void Increment(UIMouseEventArgs e) {{
counter++;
}}
}}");
@@ -203,7 +203,7 @@ namespace Test
AssertFrame.Attribute(frame, "OnClick", 1);
// The handler will have been assigned to a lambda
- var handler = Assert.IsType(frame.AttributeValue);
+ var handler = Assert.IsType(frame.AttributeValue);
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
},
frame => AssertFrame.Whitespace(frame, 2));
diff --git a/test/Microsoft.AspNetCore.Blazor.Build.Test/RenderingRazorIntegrationTest.cs b/test/Microsoft.AspNetCore.Blazor.Build.Test/RenderingRazorIntegrationTest.cs
index 35b48b36c4..ebf50539e4 100644
--- a/test/Microsoft.AspNetCore.Blazor.Build.Test/RenderingRazorIntegrationTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Build.Test/RenderingRazorIntegrationTest.cs
@@ -519,7 +519,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
{
AssertFrame.Attribute(frame, "onclick", 1);
- var func = Assert.IsType(frame.AttributeValue);
+ var func = Assert.IsType>(frame.AttributeValue);
Assert.False((bool)clicked.GetValue(component));
func(new UIMouseEventArgs());
@@ -552,7 +552,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
{
AssertFrame.Attribute(frame, "onclick", 1);
- var func = Assert.IsType(frame.AttributeValue);
+ var func = Assert.IsType>(frame.AttributeValue);
Assert.False((bool)clicked.GetValue(component));
func(new UIMouseEventArgs());
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs
index 6ef63822f6..5bd5e73875 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BasicTestAppTestBase.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
@@ -14,8 +15,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
public const string ServerPathBase = "/subdir";
- public BasicTestAppTestBase(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public BasicTestAppTestBase(
+ BrowserFixture browserFixture,
+ DevHostServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
serverFixture.PathBase = ServerPathBase;
}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs
index 3a3bc298a0..1d41aae7bf 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserFixture.cs
@@ -5,6 +5,7 @@ using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
using System;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
@@ -12,6 +13,10 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
public IWebDriver Browser { get; }
+ public ILogs Logs { get; }
+
+ public ITestOutputHelper Output { get; set; }
+
public BrowserFixture()
{
var opts = new ChromeOptions();
@@ -19,6 +24,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
// Comment this out if you want to watch or interact with the browser (e.g., for debugging)
opts.AddArgument("--headless");
+ // Log errors
+ opts.SetLoggingPreference(LogType.Browser, LogLevel.All);
+
// On Windows/Linux, we don't need to set opts.BinaryLocation
// But for Travis Mac builds we do
var binaryLocation = Environment.GetEnvironmentVariable("TEST_CHROME_BINARY");
@@ -30,7 +38,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
try
{
- Browser = new RemoteWebDriver(opts);
+ var driver = new RemoteWebDriver(opts);
+ Browser = driver;
+ Logs = new RemoteLogs(driver);
}
catch (WebDriverException ex)
{
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserTestBase.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserTestBase.cs
index 063f9cf47e..855aefcd4b 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserTestBase.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/BrowserTestBase.cs
@@ -1,18 +1,31 @@
// 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.Threading;
using OpenQA.Selenium;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
+ [CaptureSeleniumLogs]
public class BrowserTestBase : IClassFixture
{
- public IWebDriver Browser { get; }
+ private static readonly AsyncLocal _browser = new AsyncLocal();
+ private static readonly AsyncLocal _logs = new AsyncLocal();
+ private static readonly AsyncLocal _output = new AsyncLocal();
- public BrowserTestBase(BrowserFixture browserFixture)
+ public static IWebDriver Browser => _browser.Value;
+
+ public static ILogs Logs => _logs.Value;
+
+ public static ITestOutputHelper Output => _output.Value;
+
+ public BrowserTestBase(BrowserFixture browserFixture, ITestOutputHelper output)
{
- Browser = browserFixture.Browser;
+ _browser.Value = browserFixture.Browser;
+ _logs.Value = browserFixture.Logs;
+ _output.Value = output;
}
}
}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/CaptureSeleniumLogsAttribute.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/CaptureSeleniumLogsAttribute.cs
new file mode 100644
index 0000000000..2ea3a34d6f
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/CaptureSeleniumLogsAttribute.cs
@@ -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;
+using System.Linq;
+using System.Reflection;
+using OpenQA.Selenium;
+using Xunit.Sdk;
+
+namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
+{
+ // This has to use BeforeAfterTestAttribute because running the log capture
+ // in the BrowserFixture.Dispose method is too late, and we can't add logging
+ // to the test.
+ public class CaptureSeleniumLogsAttribute : BeforeAfterTestAttribute
+ {
+ public override void Before(MethodInfo methodUnderTest)
+ {
+ if (!typeof(BrowserTestBase).IsAssignableFrom(methodUnderTest.DeclaringType))
+ {
+ throw new InvalidOperationException("This should only be used with BrowserTestBase");
+ }
+ }
+
+ public override void After(MethodInfo methodUnderTest)
+ {
+ var browser = BrowserTestBase.Browser;
+ var logs = BrowserTestBase.Logs;
+ var output = BrowserTestBase.Output;
+
+ // Put browser logs first, the test UI will truncate output after a certain length
+ // and the browser logs will include exceptions thrown by js in the browser.
+ foreach (var kind in logs.AvailableLogTypes.OrderBy(k => k == LogType.Browser ? 0 : 1))
+ {
+ output.WriteLine($"{kind} Logs from Selenium:");
+
+ var entries = logs.GetLog(kind);
+ foreach (LogEntry entry in entries)
+ {
+ output.WriteLine($"[{entry.Timestamp}] - {entry.Level} - {entry.Message}");
+ }
+
+ output.WriteLine("");
+ output.WriteLine("");
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs
index 4ce3559026..deb029d89a 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Infrastructure/ServerTestBase.cs
@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using System;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
@@ -13,8 +14,8 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure
{
private readonly TServerFixture _serverFixture;
- public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture)
- : base(browserFixture)
+ public ServerTestBase(BrowserFixture browserFixture, TServerFixture serverFixture, ITestOutputHelper output)
+ : base(browserFixture, output)
{
_serverFixture = serverFixture;
}
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs
index 1304d54b29..be32b10252 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/BindTest.cs
@@ -7,13 +7,17 @@ using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class BindTest : BasicTestAppTestBase
{
- public BindTest(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public BindTest(
+ BrowserFixture browserFixture,
+ DevHostServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
MountTestComponent();
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs
index 2bd952dd37..82897365d7 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/ComponentRenderingTest.cs
@@ -12,13 +12,17 @@ using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class ComponentRenderingTest : BasicTestAppTestBase
{
- public ComponentRenderingTest(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public ComponentRenderingTest(
+ BrowserFixture browserFixture,
+ DevHostServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
Navigate(ServerPathBase, noReload: true);
}
@@ -233,7 +237,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public void CanRenderSvgWithCorrectNamespace()
{
var appElement = MountTestComponent();
-
+
var svgElement = appElement.FindElement(By.XPath("//*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg']"));
Assert.NotNull(svgElement);
@@ -245,7 +249,7 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public void CanRenderSvgChildComponentWithCorrectNamespace()
{
var appElement = MountTestComponent();
-
+
var svgElement = appElement.FindElement(By.XPath("//*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg']"));
Assert.NotNull(svgElement);
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HostedInAspNetTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HostedInAspNetTest.cs
index af552df1a7..b9f3a454a5 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HostedInAspNetTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HostedInAspNetTest.cs
@@ -7,13 +7,17 @@ using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class HostedInAspNetTest : ServerTestBase
{
- public HostedInAspNetTest(BrowserFixture browserFixture, AspNetSiteServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public HostedInAspNetTest(
+ BrowserFixture browserFixture,
+ AspNetSiteServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
serverFixture.Environment = AspNetEnvironment.Development;
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs
index b18bcf8455..4d6101ebb4 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/HttpClientTest.cs
@@ -10,6 +10,7 @@ using System;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
@@ -24,8 +25,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
public HttpClientTest(
BrowserFixture browserFixture,
DevHostServerFixture devHostServerFixture,
- AspNetSiteServerFixture apiServerFixture)
- : base(browserFixture, devHostServerFixture)
+ AspNetSiteServerFixture apiServerFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, devHostServerFixture, output)
{
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
_apiServerFixture = apiServerFixture;
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/MonoSanityTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/MonoSanityTest.cs
index ee4d78351c..513a0a85c5 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/MonoSanityTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/MonoSanityTest.cs
@@ -7,13 +7,17 @@ using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
public class MonoSanityTest : ServerTestBase
{
- public MonoSanityTest(BrowserFixture browserFixture, AspNetSiteServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public MonoSanityTest(
+ BrowserFixture browserFixture,
+ AspNetSiteServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
serverFixture.BuildWebHostMethod = MonoSanity.Program.BuildWebHost;
Navigate("/", noReload: true);
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
index 7d28b3dd03..edb8fc288f 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/RoutingTest.cs
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
@@ -16,8 +17,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
private readonly ServerFixture _server;
- public RoutingTest(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public RoutingTest(
+ BrowserFixture browserFixture,
+ DevHostServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
_server = serverFixture;
Navigate(ServerPathBase, noReload: true);
diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs
index 217fdaaba5..c0093f0dfc 100644
--- a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/StandaloneAppTest.cs
@@ -8,6 +8,7 @@ using OpenQA.Selenium.Support.UI;
using System;
using System.Linq;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
@@ -16,8 +17,11 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
{
private readonly ServerFixture _serverFixture;
- public StandaloneAppTest(BrowserFixture browserFixture, DevHostServerFixture serverFixture)
- : base(browserFixture, serverFixture)
+ public StandaloneAppTest(
+ BrowserFixture browserFixture,
+ DevHostServerFixture serverFixture,
+ ITestOutputHelper output)
+ : base(browserFixture, serverFixture, output)
{
_serverFixture = serverFixture;
Navigate("/", noReload: true);
diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs
index 61b3992fdf..f9a708f242 100644
--- a/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Test/RenderTreeDiffBuilderTest.cs
@@ -1156,10 +1156,10 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Arrange
UIEventHandler retainedHandler = _ => { };
oldTree.OpenElement(0, "My element");
- oldTree.AddAttribute(1, "will remain", retainedHandler);
+ oldTree.AddAttribute(1, "ontest", retainedHandler);
oldTree.CloseElement();
newTree.OpenElement(0, "My element");
- newTree.AddAttribute(1, "will remain", retainedHandler);
+ newTree.AddAttribute(1, "ontest", retainedHandler);
newTree.CloseElement();
// Act
@@ -1169,8 +1169,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Assert
Assert.Empty(result.Edits);
- AssertFrame.Attribute(oldAttributeFrame, "will remain", retainedHandler);
- AssertFrame.Attribute(newAttributeFrame, "will remain", retainedHandler);
+ AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
+ AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
}
@@ -1181,11 +1181,11 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Arrange
UIEventHandler retainedHandler = _ => { };
oldTree.OpenElement(0, "My element");
- oldTree.AddAttribute(0, "will remain", retainedHandler);
+ oldTree.AddAttribute(0, "ontest", retainedHandler);
oldTree.CloseElement();
newTree.OpenElement(0, "My element");
newTree.AddAttribute(0, "another-attribute", "go down the slow path please");
- newTree.AddAttribute(0, "will remain", retainedHandler);
+ newTree.AddAttribute(0, "ontest", retainedHandler);
newTree.CloseElement();
// Act
@@ -1195,8 +1195,8 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Assert
Assert.Single(result.Edits);
- AssertFrame.Attribute(oldAttributeFrame, "will remain", retainedHandler);
- AssertFrame.Attribute(newAttributeFrame, "will remain", retainedHandler);
+ AssertFrame.Attribute(oldAttributeFrame, "ontest", retainedHandler);
+ AssertFrame.Attribute(newAttributeFrame, "ontest", retainedHandler);
Assert.NotEqual(0, oldAttributeFrame.AttributeEventHandlerId);
Assert.Equal(oldAttributeFrame.AttributeEventHandlerId, newAttributeFrame.AttributeEventHandlerId);
}
diff --git a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
index 8db5dd9eec..019f6f988b 100644
--- a/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
+++ b/test/Microsoft.AspNetCore.Blazor.Test/RendererTest.cs
@@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
var component = new EventComponent
{
- Handler = args => { receivedArgs = args; }
+ OnTest = args => { receivedArgs = args; }
};
var componentId = renderer.AssignComponentId(component);
component.TriggerRender();
@@ -189,6 +189,34 @@ namespace Microsoft.AspNetCore.Blazor.Test
Assert.Same(eventArgs, receivedArgs);
}
+ [Fact]
+ public void CanDispatchTypedEventsToTopLevelComponents()
+ {
+ // Arrange: Render a component with an event handler
+ var renderer = new TestRenderer();
+ UIMouseEventArgs receivedArgs = null;
+
+ var component = new EventComponent
+ {
+ OnClick = args => { receivedArgs = args; }
+ };
+ var componentId = renderer.AssignComponentId(component);
+ component.TriggerRender();
+
+ var eventHandlerId = renderer.Batches.Single()
+ .ReferenceFrames
+ .First(frame => frame.AttributeValue != null)
+ .AttributeEventHandlerId;
+
+ // Assert: Event not yet fired
+ Assert.Null(receivedArgs);
+
+ // Act/Assert: Event can be fired
+ var eventArgs = new UIMouseEventArgs();
+ renderer.DispatchEvent(componentId, eventHandlerId, eventArgs);
+ Assert.Same(eventArgs, receivedArgs);
+ }
+
[Fact]
public void CanDispatchEventsToNestedComponents()
{
@@ -209,7 +237,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
.ReferenceFrames
.Single(frame => frame.FrameType == RenderTreeFrameType.Component);
var nestedComponent = (EventComponent)nestedComponentFrame.Component;
- nestedComponent.Handler = args => { receivedArgs = args; };
+ nestedComponent.OnTest = args => { receivedArgs = args; };
var nestedComponentId = nestedComponentFrame.ComponentId;
nestedComponent.TriggerRender();
@@ -237,7 +265,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
var component = new TestComponent(builder =>
{
builder.OpenElement(0, "mybutton");
- builder.AddAttribute(1, "my click event", handler);
+ builder.AddAttribute(1, "onclick", handler);
builder.CloseElement();
});
@@ -476,7 +504,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
var renderer = new TestRenderer();
var eventCount = 0;
UIEventHandler origEventHandler = args => { eventCount++; };
- var component = new EventComponent { Handler = origEventHandler };
+ var component = new EventComponent { OnTest = origEventHandler };
var componentId = renderer.AssignComponentId(component);
component.TriggerRender();
var origEventHandlerId = renderer.Batches.Single()
@@ -492,7 +520,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
// Now change the attribute value
var newEventCount = 0;
- component.Handler = args => { newEventCount++; };
+ component.OnTest = args => { newEventCount++; };
component.TriggerRender();
// Act/Assert 2: Can no longer fire the original event, but can fire the new event
@@ -513,7 +541,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
var renderer = new TestRenderer();
var eventCount = 0;
UIEventHandler origEventHandler = args => { eventCount++; };
- var component = new EventComponent { Handler = origEventHandler };
+ var component = new EventComponent { OnTest = origEventHandler };
var componentId = renderer.AssignComponentId(component);
component.TriggerRender();
var origEventHandlerId = renderer.Batches.Single()
@@ -528,7 +556,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
Assert.Equal(1, eventCount);
// Now remove the event attribute
- component.Handler = null;
+ component.OnTest = null;
component.TriggerRender();
// Act/Assert 2: Can no longer fire the original event
@@ -551,7 +579,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
IncludeChild = true,
ChildParameters = new Dictionary
{
- { nameof(EventComponent.Handler), origEventHandler }
+ { nameof(EventComponent.OnTest), origEventHandler }
}
};
var rootComponentId = renderer.AssignComponentId(component);
@@ -595,7 +623,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
var renderer = new TestRenderer();
var eventCount = 0;
UIEventHandler origEventHandler = args => { eventCount++; };
- var component = new EventComponent { Handler = origEventHandler };
+ var component = new EventComponent { OnTest = origEventHandler };
var componentId = renderer.AssignComponentId(component);
component.TriggerRender();
var origEventHandlerId = renderer.Batches.Single()
@@ -634,7 +662,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
{
builder.AddContent(0, "Child event count: " + eventCount);
builder.OpenComponent(1);
- builder.AddAttribute(2, nameof(EventComponent.Handler), args =>
+ builder.AddAttribute(2, nameof(EventComponent.OnTest), args =>
{
eventCount++;
rootComponent.TriggerRender();
@@ -822,7 +850,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
if (shouldRenderChild)
{
builder.OpenComponent(1);
- builder.AddAttribute(2, nameof(RendersSelfAfterEventComponent.OnClick), (Action)(() =>
+ builder.AddAttribute(2, "onclick", (Action