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 @@ + +