From 87375f6ba1176bc57705ec0aa685697a7f57545f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 30 Apr 2018 21:44:15 -0700 Subject: [PATCH] Add support for most (if not all events) This change adds tag helpers and defines event types for all of the DOM events we could find. You'll find it much easier now to subcribe to these events. While we did define new event types, we didn't substantially expand the set of data that we serialize for events. We're looking for feedback on what is most critical to have, and looking for contributions adding those data and tests. --- .../src/Rendering/EventForDotNet.ts | 89 ++++- .../BrowserRendererEventDispatcher.cs | 26 +- .../Components/EventHandlers.cs | 100 ++++- .../UIEventArgs.cs | 58 +++ .../UIEventArgsRenderTreeBuilderExtensions.cs | 368 ++++++++++++++++++ .../Tests/EventTest.cs | 130 +++++++ .../BasicTestApp/FocusEventComponent.cshtml | 52 +++ .../BasicTestApp/MouseEventComponent.cshtml | 67 ++++ test/testapps/BasicTestApp/wwwroot/index.html | 2 + 9 files changed, 878 insertions(+), 14 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs create mode 100644 test/testapps/BasicTestApp/FocusEventComponent.cshtml create mode 100644 test/testapps/BasicTestApp/MouseEventComponent.cshtml diff --git a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/EventForDotNet.ts b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/EventForDotNet.ts index 6f68e19acb..ec20c7003d 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/EventForDotNet.ts +++ b/src/Microsoft.AspNetCore.Blazor.Browser.JS/src/Rendering/EventForDotNet.ts @@ -5,17 +5,66 @@ static fromDOMEvent(event: Event): EventForDotNet { const element = event.target as Element; switch (event.type) { - case 'click': - case 'mousedown': - case 'mouseup': - return new EventForDotNet('mouse', { Type: event.type }); + case 'change': { const targetIsCheckbox = isCheckbox(element); const newValue = targetIsCheckbox ? !!element['checked'] : element['value']; return new EventForDotNet('change', { Type: event.type, Value: newValue }); } + + case 'copy': + case 'cut': + case 'paste': + return new EventForDotNet('clipboard', { Type: event.type }); + + case 'drag': + case 'dragend': + case 'dragenter': + case 'dragleave': + case 'dragover': + case 'dragstart': + case 'drop': + return new EventForDotNet('drag', { Type: event.type }); + + case 'error': + return new EventForDotNet('error', { Type: event.type }); + + case 'focus': + case 'blur': + case 'focusin': + case 'focusout': + return new EventForDotNet('focus', { Type: event.type }); + + case 'keydown': + case 'keyup': case 'keypress': return new EventForDotNet('keyboard', { Type: event.type, Key: (event as any).key }); + + case 'click': + case 'mouseover': + case 'mouseout': + case 'mousemove': + case 'mousedown': + case 'mouseup': + case 'dblclick': + return new EventForDotNet('mouse', { Type: event.type }); + + case 'contextmenu': + return new EventForDotNet('pointer', { Type: event.type }); + + case 'progress': + return new EventForDotNet('progress', { Type: event.type }); + + case 'touchcancel': + case 'touchend': + case 'touchmove': + case 'touchstart': + return new EventForDotNet('touch', { Type: event.type }); + + case 'mousewheel': + return new EventForDotNet('wheel', { Type: event.type }); + + default: return new EventForDotNet('unknown', { Type: event.type }); } @@ -28,19 +77,43 @@ function isCheckbox(element: Element | null) { // The following interfaces must be kept in sync with the UIEventArgs C# classes -type EventArgsType = 'mouse' | 'keyboard' | 'change' | 'unknown'; +type EventArgsType = 'change' | 'clipboard' | 'drag' | 'error' | 'focus' | 'keyboard' | 'mouse' | 'pointer' | 'progress' | 'touch' | 'unknown' | 'wheel'; export interface UIEventArgs { Type: string; } -interface UIMouseEventArgs extends UIEventArgs { +interface UIChangeEventArgs extends UIEventArgs { + Value: string | boolean; +} + +interface UIClipboardEventArgs extends UIEventArgs { +} + +interface UIDragEventArgs extends UIEventArgs { +} + +interface UIErrorEventArgs extends UIEventArgs { +} + +interface UIFocusEventArgs extends UIEventArgs { } interface UIKeyboardEventArgs extends UIEventArgs { Key: string; } -interface UIChangeEventArgs extends UIEventArgs { - Value: string | boolean; +interface UIMouseEventArgs extends UIEventArgs { +} + +interface UIPointerEventArgs extends UIMouseEventArgs { +} + +interface UIProgressEventArgs extends UIEventArgs { +} + +interface UITouchEventArgs extends UIEventArgs { +} + +interface UIWheelEventArgs extends UIEventArgs { } diff --git a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs index 7103e3484d..2af78e9535 100644 --- a/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs +++ b/src/Microsoft.AspNetCore.Blazor.Browser/Rendering/BrowserRendererEventDispatcher.cs @@ -32,16 +32,32 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering { switch (eventArgsType) { - case "mouse": - return JsonUtil.Deserialize(eventArgsJson); - case "keyboard": - return JsonUtil.Deserialize(eventArgsJson); case "change": return JsonUtil.Deserialize(eventArgsJson); + case "clipboard": + return JsonUtil.Deserialize(eventArgsJson); + case "drag": + return JsonUtil.Deserialize(eventArgsJson); + case "error": + return JsonUtil.Deserialize(eventArgsJson); + case "focus": + return JsonUtil.Deserialize(eventArgsJson); + case "keyboard": + return JsonUtil.Deserialize(eventArgsJson); + case "mouse": + return JsonUtil.Deserialize(eventArgsJson); + case "pointer": + return JsonUtil.Deserialize(eventArgsJson); + case "progress": + return JsonUtil.Deserialize(eventArgsJson); + case "touch": + return JsonUtil.Deserialize(eventArgsJson); case "unknown": return JsonUtil.Deserialize(eventArgsJson); + case "wheel": + return JsonUtil.Deserialize(eventArgsJson); default: - throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType)); + throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType)); } } diff --git a/src/Microsoft.AspNetCore.Blazor/Components/EventHandlers.cs b/src/Microsoft.AspNetCore.Blazor/Components/EventHandlers.cs index 0cf0bb187a..6e050b6ed2 100644 --- a/src/Microsoft.AspNetCore.Blazor/Components/EventHandlers.cs +++ b/src/Microsoft.AspNetCore.Blazor/Components/EventHandlers.cs @@ -9,8 +9,106 @@ namespace Microsoft.AspNetCore.Blazor.Components /// Holds attributes to configure the mappings between event names and /// event argument types. /// - [EventHandler("onchange", typeof(UIChangeEventArgs))] + + // Focus events + [EventHandler("onfocus", typeof(UIFocusEventArgs))] + [EventHandler("onblur", typeof(UIFocusEventArgs))] + [EventHandler("onfocusin", typeof(UIFocusEventArgs))] + [EventHandler("onfocusout", typeof(UIFocusEventArgs))] + + // Mouse events + [EventHandler("onmouseover", typeof(UIMouseEventArgs))] + [EventHandler("onmouseout", typeof(UIMouseEventArgs))] + [EventHandler("onmousemove", typeof(UIMouseEventArgs))] + [EventHandler("onmousedown", typeof(UIMouseEventArgs))] + [EventHandler("onmouseup", typeof(UIMouseEventArgs))] [EventHandler("onclick", typeof(UIMouseEventArgs))] + [EventHandler("ondblclick", typeof(UIMouseEventArgs))] + [EventHandler("onmousewheel", typeof(UIWheelEventArgs))] + + // Drag events + [EventHandler("ondrag", typeof(UIDragEventArgs))] + [EventHandler("ondragend", typeof(UIDragEventArgs))] + [EventHandler("ondragenter", typeof(UIDragEventArgs))] + [EventHandler("ondragleave", typeof(UIDragEventArgs))] + [EventHandler("ondragover", typeof(UIDragEventArgs))] + [EventHandler("ondragstart", typeof(UIDragEventArgs))] + [EventHandler("ondrop", typeof(UIDragEventArgs))] + + // Keyboard events + [EventHandler("onkeydown", typeof(UIKeyboardEventArgs))] + [EventHandler("onkeyup", typeof(UIKeyboardEventArgs))] + [EventHandler("onkeypress", typeof(UIKeyboardEventArgs))] + + // Pointer events + [EventHandler("oncontextmenu", typeof(UIPointerEventArgs))] + + // Input events + [EventHandler("onchange", typeof(UIChangeEventArgs))] + [EventHandler("oninput", typeof(UIEventArgs))] + [EventHandler("oninvalid", typeof(UIEventArgs))] + [EventHandler("onreset", typeof(UIEventArgs))] + [EventHandler("onselect", typeof(UIEventArgs))] + [EventHandler("onselectstart", typeof(UIEventArgs))] + [EventHandler("onselectionchange", typeof(UIEventArgs))] + [EventHandler("onsubmit", typeof(UIEventArgs))] + + // Clipboard events + [EventHandler("onbeforecopy", typeof(UIEventArgs))] + [EventHandler("onbeforecut", typeof(UIEventArgs))] + [EventHandler("onbeforepaste", typeof(UIEventArgs))] + [EventHandler("oncopy", typeof(UIClipboardEventArgs))] + [EventHandler("oncut", typeof(UIClipboardEventArgs))] + [EventHandler("onpaste", typeof(UIClipboardEventArgs))] + + // Touch events + [EventHandler("ontouchcancel", typeof(UITouchEventArgs))] + [EventHandler("ontouchend", typeof(UITouchEventArgs))] + [EventHandler("ontouchmove", typeof(UITouchEventArgs))] + [EventHandler("ontouchstart", typeof(UITouchEventArgs))] + + // Media events + [EventHandler("oncanplay", typeof(UIEventArgs))] + [EventHandler("oncanplaythrough", typeof(UIEventArgs))] + [EventHandler("oncuechange", typeof(UIEventArgs))] + [EventHandler("ondurationchange", typeof(UIEventArgs))] + [EventHandler("onemptied", typeof(UIEventArgs))] + [EventHandler("onpause", typeof(UIEventArgs))] + [EventHandler("onplay", typeof(UIEventArgs))] + [EventHandler("onplaying", typeof(UIEventArgs))] + [EventHandler("onratechange", typeof(UIEventArgs))] + [EventHandler("onseeked", typeof(UIEventArgs))] + [EventHandler("onseeking", typeof(UIEventArgs))] + [EventHandler("onstalled", typeof(UIEventArgs))] + [EventHandler("onstop", typeof(UIEventArgs))] + [EventHandler("onsuspend", typeof(UIEventArgs))] + [EventHandler("ontimeupdate", typeof(UIEventArgs))] + [EventHandler("onvolumechange", typeof(UIEventArgs))] + [EventHandler("onwaiting", typeof(UIEventArgs))] + + // Error events + [EventHandler("onerror", typeof(UIErrorEventArgs))] + + // Progress events + [EventHandler("onprogress", typeof(UIProgressEventArgs))] + + // General events + [EventHandler("onabort", typeof(UIEventArgs))] + [EventHandler("onactivate", typeof(UIEventArgs))] + [EventHandler("onbeforeactivate", typeof(UIEventArgs))] + [EventHandler("onbeforedeactivate", typeof(UIEventArgs))] + [EventHandler("ondeactivate", typeof(UIEventArgs))] + [EventHandler("onended", typeof(UIEventArgs))] + [EventHandler("onfullscreenchange", typeof(UIEventArgs))] + [EventHandler("onfullscreenerror", typeof(UIEventArgs))] + [EventHandler("onload", typeof(UIEventArgs))] + [EventHandler("onloadeddata", typeof(UIEventArgs))] + [EventHandler("onloadedmetadata", typeof(UIEventArgs))] + [EventHandler("onloadstart", typeof(UIEventArgs))] + [EventHandler("onpointerlockchange", typeof(UIEventArgs))] + [EventHandler("onpointerlockerror", typeof(UIEventArgs))] + [EventHandler("onreadystatechange", typeof(UIEventArgs))] + [EventHandler("onscroll", typeof(UIEventArgs))] public static class EventHandlers { } diff --git a/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs b/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs index a333af2716..200bb15d53 100644 --- a/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs +++ b/src/Microsoft.AspNetCore.Blazor/UIEventArgs.cs @@ -26,6 +26,36 @@ namespace Microsoft.AspNetCore.Blazor public object Value { get; set; } } + /// + /// Supplies information about an clipboard event that is being raised. + /// + public class UIClipboardEventArgs : UIEventArgs + { + } + + /// + /// Supplies information about an drag event that is being raised. + /// + public class UIDragEventArgs : UIEventArgs + { + } + + /// + /// Supplies information about an error event that is being raised. + /// + public class UIErrorEventArgs : UIEventArgs + { + } + + /// + /// Supplies information about a focus event that is being raised. + /// + public class UIFocusEventArgs : UIEventArgs + { + // Not including support for 'relatedTarget' since we don't have a good way to represent it. + // see: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent + } + /// /// Supplies information about a keyboard event that is being raised. /// @@ -43,4 +73,32 @@ namespace Microsoft.AspNetCore.Blazor public class UIMouseEventArgs : UIEventArgs { } + + /// + /// Supplies information about a mouse event that is being raised. + /// + public class UIPointerEventArgs : UIMouseEventArgs + { + } + + /// + /// Supplies information about a progress event that is being raised. + /// + public class UIProgressEventArgs : UIMouseEventArgs + { + } + + /// + /// Supplies information about a touch event that is being raised. + /// + public class UITouchEventArgs : UIEventArgs + { + } + + /// + /// Supplies information about a mouse wheel event that is being raised. + /// + public class UIWheelEventArgs : UIEventArgs + { + } } diff --git a/src/Microsoft.AspNetCore.Blazor/UIEventArgsRenderTreeBuilderExtensions.cs b/src/Microsoft.AspNetCore.Blazor/UIEventArgsRenderTreeBuilderExtensions.cs index 594c397c05..66f89a673b 100644 --- a/src/Microsoft.AspNetCore.Blazor/UIEventArgsRenderTreeBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Blazor/UIEventArgsRenderTreeBuilderExtensions.cs @@ -84,6 +84,190 @@ namespace Microsoft.AspNetCore.Blazor 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, Action 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, Func 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, Action 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, Func 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, Action 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, Func 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, Action 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, Func value) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddAttribute(sequence, name, (MulticastDelegate)value); + } + /// /// /// Appends a frame representing an -valued attribute. @@ -175,5 +359,189 @@ namespace Microsoft.AspNetCore.Blazor 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, Action 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, Func 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, Action 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, Func 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, Action 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, Func 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, Action 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, Func value) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddAttribute(sequence, name, (MulticastDelegate)value); + } } } diff --git a/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs new file mode 100644 index 0000000000..596ae8b55f --- /dev/null +++ b/test/Microsoft.AspNetCore.Blazor.E2ETest/Tests/EventTest.cs @@ -0,0 +1,130 @@ +// 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; + +namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests +{ + public class EventTest : BasicTestAppTestBase + { + public EventTest( + BrowserFixture browserFixture, + DevHostServerFixture serverFixture, + ITestOutputHelper output) + : base(browserFixture, serverFixture, output) + { + Navigate(ServerPathBase, noReload: true); + MountTestComponent(); + } + + [Fact] + public void FocusEvents_CanTrigger() + { + MountTestComponent(); + + var input = Browser.FindElement(By.Id("input")); + + var output = Browser.FindElement(By.Id("output")); + Assert.Equal(string.Empty, output.Text); + + // Focus the target, verify onfocusin is fired + input.Click(); + + Assert.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); + } + + [Fact] + public void MouseOverAndMouseOut_CanTrigger() + { + MountTestComponent(); + + var input = Browser.FindElement(By.Id("mouseover_input")); + + var output = Browser.FindElement(By.Id("output")); + Assert.Equal(string.Empty, output.Text); + + var other = Browser.FindElement(By.Id("other")); + + // Mouse over the button and then back off + var actions = new Actions(Browser) + .MoveToElement(input) + .MoveToElement(other); + + actions.Perform(); + Assert.Equal("onmouseover,onmouseout,", output.Text); + } + + [Fact] + public void MouseMove_CanTrigger() + { + MountTestComponent(); + + var input = Browser.FindElement(By.Id("mousemove_input")); + + var output = Browser.FindElement(By.Id("output")); + Assert.Equal(string.Empty, output.Text); + + // Move a little bit + var actions = new Actions(Browser) + .MoveToElement(input) + .MoveToElement(input, 10, 10); + + actions.Perform(); + Assert.Contains("onmousemove,", output.Text); + } + + [Fact] + public void MouseDownAndMouseUp_CanTrigger() + { + MountTestComponent(); + + var input = Browser.FindElement(By.Id("mousedown_input")); + + var output = Browser.FindElement(By.Id("output")); + Assert.Equal(string.Empty, output.Text); + + var other = Browser.FindElement(By.Id("other")); + + // Mousedown + var actions = new Actions(Browser).ClickAndHold(input); + + actions.Perform(); + Assert.Equal("onmousedown,", output.Text); + + actions = new Actions(Browser).Release(input); + + actions.Perform(); + Assert.Equal("onmousedown,onmouseup,", output.Text); + } + + private string[] GetLogLines() + => Browser.FindElement(By.TagName("textarea")) + .GetAttribute("value") + .Replace("\r\n", "\n") + .Split('\n', StringSplitOptions.RemoveEmptyEntries); + + private void TriggerCustomBubblingEvent(string elementId, string eventName) + { + var jsExecutor = (IJavaScriptExecutor)Browser; + jsExecutor.ExecuteScript( + $"document.getElementById('{elementId}').dispatchEvent(" + + $" new Event('{eventName}', {{ bubbles: true }})" + + $")"); + MountTestComponent(); + } + } +} diff --git a/test/testapps/BasicTestApp/FocusEventComponent.cshtml b/test/testapps/BasicTestApp/FocusEventComponent.cshtml new file mode 100644 index 0000000000..b57d0f14ff --- /dev/null +++ b/test/testapps/BasicTestApp/FocusEventComponent.cshtml @@ -0,0 +1,52 @@ +@using System.Collections.Generic +@using Microsoft.AspNetCore.Blazor + +

Focus and activation

+ +

+ Input: +

+

+ Output: @message +

+

+ +

+ +

+ Another input (to distract you) +

+ +@functions { + + string message; + + void OnFocus(UIFocusEventArgs e) + { + message += "onfocus,"; + StateHasChanged(); + } + + void OnBlur(UIFocusEventArgs e) + { + message += "onblur,"; + StateHasChanged(); + } + + void OnFocusIn(UIFocusEventArgs e) + { + message += "onfocusin,"; + StateHasChanged(); + } + + void OnFocusOut(UIFocusEventArgs e) + { + message += "onfocusout,"; + StateHasChanged(); + } + + void Clear() + { + message = string.Empty; + } +} \ No newline at end of file diff --git a/test/testapps/BasicTestApp/MouseEventComponent.cshtml b/test/testapps/BasicTestApp/MouseEventComponent.cshtml new file mode 100644 index 0000000000..885d9a68ab --- /dev/null +++ b/test/testapps/BasicTestApp/MouseEventComponent.cshtml @@ -0,0 +1,67 @@ +@using System.Collections.Generic +@using Microsoft.AspNetCore.Blazor + + + +
+

Mouse position

+

+ Output: @message +

+

+ Mouseover: +

+

+ Mousemove city! +

+

+ Mousedown: +

+

+ +

+ +

+ Another input (to distract you) +

+
+ +@functions { + + string message; + + void OnMouseOver(UIMouseEventArgs e) + { + message += "onmouseover,"; + StateHasChanged(); + } + + void OnMouseOut(UIMouseEventArgs e) + { + message += "onmouseout,"; + StateHasChanged(); + } + + void OnMouseMove(UIMouseEventArgs e) + { + message += "onmousemove,"; + StateHasChanged(); + } + + void OnMouseDown(UIMouseEventArgs e) + { + message += "onmousedown,"; + StateHasChanged(); + } + + void OnMouseUp(UIMouseEventArgs e) + { + message += "onmouseup,"; + StateHasChanged(); + } + + void Clear() + { + message = string.Empty; + } +} \ No newline at end of file diff --git a/test/testapps/BasicTestApp/wwwroot/index.html b/test/testapps/BasicTestApp/wwwroot/index.html index d335360895..a99aca087e 100644 --- a/test/testapps/BasicTestApp/wwwroot/index.html +++ b/test/testapps/BasicTestApp/wwwroot/index.html @@ -15,7 +15,9 @@ + +