Improvements for delegate types (#516)
* Improve support for more types of event handlers
Improves support for for other types of event handlers with eventargs
types derived from UIEventArgs. Additionally fleshes out the set of
event handler types.
This change improves support for using more specific event handler types
like:
```
<button onclick="@Clicked" />
@functions {
public void Clicked(UIMouseEventArgs e) { ... }
}
```
And:
```
builder.AddAttribute(12, "onkeypressed", KeyPressed);
...
void KeyPressed(UIKeyboardEventArgs e) { ... }
```
In particular what got better is:
- overload resolution for the AddAttribute method
- performance of different cases for AddAttribute
-----
The runtime now treats delegates as one of three types:
- arbitrary delegate: not attached to DOM events, not tracked by
renderer
- UIEventHandler: can attach to DOM events, tracked by renderer, first
class in IHandleEvents
- UIEventHandler-like: can attach to DOM events, tracked by renderer,
requires some special runtime support.
The set of overloads on AddAttribute has been tuned with a few specific
cases in mind.
Lambda expressions in an attribute will be inferred as UIEventHandler
unless the compiler does something more specific. So for instance,
passing a lambda as an attribute value for a component, where the
component doesn't define a matching attribute, will always be inferred
as UIEventHandler.
We now support method-group to delegate conversion for methods that
accept a derived UIEventArgs type. This means you can use a signature
like `void KeyPressed(UIKeyboardEventArgs e)` without any compiler
magic, and this will work in the runtime as long as the event type
produced by the runtime matches.
We also allow user-defined UIEventArgs-derived types. There's a pattern
for this and it requires defining an extension method and delegate type.
The method-group to delegate conversion part required some doing. It
doesn't play well with generics (Action<T> where T : UIEventArgs)
doesn't work at all. Adding more actual overloads (as opposed to
extensions) would cause lambda cases we want to work to be ambiguous.
----
The performance win here is to remove the need for a 'wrapper' delegate
created by the event handler tag helper code. This wrapper is now
created by the runtime, but only *after* we have checked the frame for
changes. This requires more heavy lifting in the runtime, but has the
advantage of producing no-op diffs as often as possible.
You will still get some inefficient behavior if your component uses a
capturing lambda in an event handler, so don't do that.
* Add selenium logs to test output
* Minor feedback
* WIP
This commit is contained in:
parent
281d5a8751
commit
df13669362
|
|
@ -35,10 +35,10 @@ namespace Microsoft.AspNetCore.Blazor.Components
|
|||
/// <summary>
|
||||
/// Not intended to be used directly.
|
||||
/// </summary>
|
||||
public static UIEventHandler GetEventHandlerValue<T>(Action<T> value)
|
||||
public static MulticastDelegate GetEventHandlerValue<T>(Action<T> value)
|
||||
where T : UIEventArgs
|
||||
{
|
||||
return e => value((T)e);
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing a bool-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>false</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
|
|
@ -124,9 +128,13 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing a string-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
|
|
@ -141,14 +149,44 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends a frame representing an <see cref="UIEventArgs"/>-valued attribute.
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="UIEventHandler"/>-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="RenderTreeBuilder"/>.</param>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
public void AddAttribute(int sequence, string name, UIEventHandler value)
|
||||
{
|
||||
AddAttribute(sequence, name, (MulticastDelegate)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing a delegate-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
/// <remarks>
|
||||
/// This method is provided for infrastructure purposes, and is used to be
|
||||
/// <see cref="UIEventHandlerRenderTreeBuilderExtensions"/> 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
|
||||
/// <see cref="UIEventHandlerRenderTreeBuilderExtensions.AddAttribute(RenderTreeBuilder, int, string, UIChangeEventHandler)"/>
|
||||
/// that calls this method.
|
||||
/// </remarks>
|
||||
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
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,15 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supplies information about a mouse event that is being raised.
|
||||
/// Supplies information about an input change event that is being raised.
|
||||
/// </summary>
|
||||
public class UIMouseEventArgs : UIEventArgs
|
||||
public class UIChangeEventArgs : UIEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the new value of the input. This may be a <see cref="string"/>
|
||||
/// or a <see cref="bool"/>.
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -33,14 +38,9 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supplies information about an input change event that is being raised.
|
||||
/// Supplies information about a mouse event that is being raised.
|
||||
/// </summary>
|
||||
public class UIChangeEventArgs : UIEventArgs
|
||||
public class UIMouseEventArgs : UIEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the new value of the input. This may be a <see cref="string"/>
|
||||
/// or a <see cref="bool"/>.
|
||||
/// </summary>
|
||||
public object Value { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,22 @@
|
|||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles an event raised for a <see cref="RenderTreeFrame"/>.
|
||||
/// Handles an <see cref="UIEventArgs"/> event raised for a <see cref="RenderTreeFrame"/>.
|
||||
/// </summary>
|
||||
public delegate void UIEventHandler(UIEventArgs eventArgs);
|
||||
public delegate void UIEventHandler(UIEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Handles an <see cref="UIChangeEventArgs"/> event raised for a <see cref="RenderTreeFrame"/>.
|
||||
/// </summary>
|
||||
public delegate void UIChangeEventHandler(UIChangeEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Handles an <see cref="UIKeyboardEventArgs"/> event raised for a <see cref="RenderTreeFrame"/>.
|
||||
/// </summary>
|
||||
public delegate void UIKeyboardEventHandler(UIKeyboardEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Handles an <see cref="UIMouseEventArgs"/> event raised for a <see cref="RenderTreeFrame"/>.
|
||||
/// </summary>
|
||||
public delegate void UIMouseEventHandler(UIMouseEventArgs e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions methods on <see cref="RenderTreeBuilder"/> for event handlers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// These methods enable method-group to delegate conversion for delegates and methods that accept
|
||||
/// types derived from <see cref="UIEventArgs"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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:
|
||||
/// <code>
|
||||
/// builder.AddAttribute(0, "onkeypress", MyKeyPressHandler);
|
||||
/// </code>
|
||||
/// Where <c>void MyKeyPressHandler(UIKeyboardEventArgs e)</c> is a method defined in the same class.
|
||||
/// In this example, the author knows that the <c>onclick</c> event is associated with the
|
||||
/// <see cref="UIKeyboardEventArgs"/> 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.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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:
|
||||
/// <code>
|
||||
/// builder.AddAttribute(0, "onkeypress", BindMethods.GetEventHandlerValue<UIKeyboardEventArgs>(MyKeyPressHandler));
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class UIEventHandlerRenderTreeBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="UIChangeEventArgs"/>-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="RenderTreeBuilder"/>.</param>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="UIKeyboardEventHandler"/>-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="RenderTreeBuilder"/>.</param>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Appends a frame representing an <see cref="UIMouseEventHandler"/>-valued attribute.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The attribute is associated with the most recently added element. If the value is <c>null</c> and the
|
||||
/// current element is not a component, the frame will be omitted.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="RenderTreeBuilder"/>.</param>
|
||||
/// <param name="sequence">An integer that represents the position of the instruction in the source code.</param>
|
||||
/// <param name="name">The name of the attribute.</param>
|
||||
/// <param name="value">The value of the attribute.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UIEventHandler>(frame.AttributeValue);
|
||||
var handler = Assert.IsType<UIMouseEventHandler>(frame.AttributeValue);
|
||||
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
||||
},
|
||||
frame => AssertFrame.Whitespace(frame, 2));
|
||||
|
|
|
|||
|
|
@ -519,7 +519,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
{
|
||||
AssertFrame.Attribute(frame, "onclick", 1);
|
||||
|
||||
var func = Assert.IsType<UIEventHandler>(frame.AttributeValue);
|
||||
var func = Assert.IsType<Action<UIMouseEventArgs>>(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<UIEventHandler>(frame.AttributeValue);
|
||||
var func = Assert.IsType<Action<UIMouseEventArgs>>(frame.AttributeValue);
|
||||
Assert.False((bool)clicked.GetValue(component));
|
||||
|
||||
func(new UIMouseEventArgs());
|
||||
|
|
|
|||
|
|
@ -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<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
public BasicTestAppTestBase(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
serverFixture.PathBase = ServerPathBase;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<BrowserFixture>
|
||||
{
|
||||
public IWebDriver Browser { get; }
|
||||
private static readonly AsyncLocal<IWebDriver> _browser = new AsyncLocal<IWebDriver>();
|
||||
private static readonly AsyncLocal<ILogs> _logs = new AsyncLocal<ILogs>();
|
||||
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
public BindTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
MountTestComponent<BindCasesComponent>();
|
||||
|
|
|
|||
|
|
@ -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<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
public ComponentRenderingTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<Program> 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<SvgComponent>();
|
||||
|
||||
|
||||
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<SvgWithChildComponent>();
|
||||
|
||||
|
||||
var svgElement = appElement.FindElement(By.XPath("//*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg']"));
|
||||
Assert.NotNull(svgElement);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<AspNetSiteServerFixture>
|
||||
{
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<BasicTestApp.Program> devHostServerFixture,
|
||||
AspNetSiteServerFixture apiServerFixture)
|
||||
: base(browserFixture, devHostServerFixture)
|
||||
AspNetSiteServerFixture apiServerFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, devHostServerFixture, output)
|
||||
{
|
||||
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||
_apiServerFixture = apiServerFixture;
|
||||
|
|
|
|||
|
|
@ -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<AspNetSiteServerFixture>
|
||||
{
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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<Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
public RoutingTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
_server = serverFixture;
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
|
|
|
|||
|
|
@ -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<StandaloneApp.Program> serverFixture)
|
||||
: base(browserFixture, serverFixture)
|
||||
public StandaloneAppTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<StandaloneApp.Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
_serverFixture = serverFixture;
|
||||
Navigate("/", noReload: true);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string, object>
|
||||
{
|
||||
{ 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<EventComponent>(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<RendersSelfAfterEventComponent>(1);
|
||||
builder.AddAttribute(2, nameof(RendersSelfAfterEventComponent.OnClick), (Action)(() =>
|
||||
builder.AddAttribute(2, "onclick", (Action<object>)((object obj) =>
|
||||
{
|
||||
// First we queue (1) a re-render of the root component, then the child component
|
||||
// will queue (2) its own re-render. But by the time (1) completes, the child will
|
||||
|
|
@ -843,7 +871,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
.ComponentId;
|
||||
var origEventHandlerId = renderer.Batches.Single()
|
||||
.ReferenceFrames
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute)
|
||||
.Where(f => f.FrameType == RenderTreeFrameType.Attribute && f.AttributeName == "onclick")
|
||||
.Single(f => f.AttributeEventHandlerId != 0)
|
||||
.AttributeEventHandlerId;
|
||||
|
||||
|
|
@ -925,7 +953,9 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
private class EventComponent : AutoRenderComponent, IComponent, IHandleEvent
|
||||
{
|
||||
public UIEventHandler Handler { get; set; }
|
||||
public UIEventHandler OnTest { get; set; }
|
||||
public UIMouseEventHandler OnClick { get; set; }
|
||||
|
||||
public bool SkipElement { get; set; }
|
||||
private int renderCount = 0;
|
||||
|
||||
|
|
@ -936,15 +966,19 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
builder.OpenElement(1, "parent");
|
||||
builder.OpenElement(2, "some element");
|
||||
if (Handler != null)
|
||||
if (OnTest != null)
|
||||
{
|
||||
builder.AddAttribute(3, "some event", Handler);
|
||||
builder.AddAttribute(3, "ontest", OnTest);
|
||||
}
|
||||
if (OnClick != null)
|
||||
{
|
||||
builder.AddAttribute(4, "onclick", OnClick);
|
||||
}
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
}
|
||||
builder.CloseElement();
|
||||
builder.AddContent(4, $"Render count: {++renderCount}");
|
||||
builder.AddContent(5, $"Render count: {++renderCount}");
|
||||
}
|
||||
|
||||
public void HandleEvent(UIEventHandler handler, UIEventArgs args)
|
||||
|
|
@ -995,7 +1029,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
|
||||
private class RendersSelfAfterEventComponent : IComponent, IHandleEvent
|
||||
{
|
||||
public Action OnClick { get; set; }
|
||||
public Action<object> OnClick { get; set; }
|
||||
|
||||
private RenderHandle _renderHandle;
|
||||
|
||||
|
|
@ -1018,7 +1052,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
=> _renderHandle.Render(builder =>
|
||||
{
|
||||
builder.OpenElement(0, "my button");
|
||||
builder.AddAttribute(1, "my click handler", eventArgs => OnClick());
|
||||
builder.AddAttribute(1, "my click handler", eventArgs => OnClick(eventArgs));
|
||||
builder.CloseElement();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ Type here: <input onkeypress=@OnKeyPressed />
|
|||
@functions {
|
||||
List<string> keysPressed = new List<string>();
|
||||
|
||||
void OnKeyPressed(UIEventArgs eventArgs)
|
||||
void OnKeyPressed(UIKeyboardEventArgs eventArgs)
|
||||
{
|
||||
keysPressed.Add(((UIKeyboardEventArgs)eventArgs).Key);
|
||||
keysPressed.Add(eventArgs.Key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue