From b3529c0db533d7a062f47d7ae811fe0be199ed74 Mon Sep 17 00:00:00 2001 From: Adam Ratzman Date: Mon, 21 Jul 2025 12:44:58 -0400 Subject: [PATCH] Keyboard enter on menu items should trigger OnClick --- .../List/ListComponentBase.razor.cs | 2 +- src/Core/Components/Menu/FluentMenuItem.razor | 5 +- .../Components/Menu/FluentMenuItem.razor.cs | 13 ++++ tests/Core/_ToDo/Menu/FluentMenuTests.cs | 42 ----------- tests/Core/_ToDo/Menu/FluentMenuTests.razor | 33 +++++++++ .../Core/_ToDo/Menu/FluentMenuTests.razor.cs | 69 +++++++++++++++++++ 6 files changed, 119 insertions(+), 45 deletions(-) delete mode 100644 tests/Core/_ToDo/Menu/FluentMenuTests.cs create mode 100644 tests/Core/_ToDo/Menu/FluentMenuTests.razor create mode 100644 tests/Core/_ToDo/Menu/FluentMenuTests.razor.cs diff --git a/src/Core/Components/List/ListComponentBase.razor.cs b/src/Core/Components/List/ListComponentBase.razor.cs index b0f780d486..4c44d2e33d 100644 --- a/src/Core/Components/List/ListComponentBase.razor.cs +++ b/src/Core/Components/List/ListComponentBase.razor.cs @@ -606,7 +606,7 @@ protected virtual async Task OnKeydownHandlerAsync(KeyboardEventArgs e) { return; } - if (!ChangeOnEnterOnly || (ChangeOnEnterOnly && e.Code == "Enter")) + if (!ChangeOnEnterOnly || (ChangeOnEnterOnly && e.Code == nameof(KeyCode.Enter))) { await item.OnClickHandlerAsync(); } diff --git a/src/Core/Components/Menu/FluentMenuItem.razor b/src/Core/Components/Menu/FluentMenuItem.razor index dadf33efc6..bb68a41ddf 100644 --- a/src/Core/Components/Menu/FluentMenuItem.razor +++ b/src/Core/Components/Menu/FluentMenuItem.razor @@ -4,13 +4,14 @@ class="@Class" style="@Style" id="@Id" - disabled="@Disabled" + disabled="@Disabled" expanded=@Expanded role="@GetRole()" checked="@Checked" @onchange="@OnChangeHandlerAsync" @onclick="@OnClickHandlerAsync" - @attributes="AdditionalAttributes"> + @attributes="AdditionalAttributes" + @onkeydown="@OnKeyDownHandlerAsync"> @Label @ChildContent @if (MenuItems != null) diff --git a/src/Core/Components/Menu/FluentMenuItem.razor.cs b/src/Core/Components/Menu/FluentMenuItem.razor.cs index f3b5a27c94..8c39778b78 100644 --- a/src/Core/Components/Menu/FluentMenuItem.razor.cs +++ b/src/Core/Components/Menu/FluentMenuItem.razor.cs @@ -131,5 +131,18 @@ protected async Task OnChangeHandlerAsync(ChangeEventArgs ev) return null; } + private async Task OnKeyDownHandlerAsync(KeyboardEventArgs e) + { + if (e.ShiftKey || e.AltKey || e.CtrlKey) + { + return; + } + + if (e.Code is nameof(KeyCode.Enter)) + { + await OnClickHandlerAsync(new MouseEventArgs()); + } + } + public void Dispose() => Owner?.Unregister(this); } diff --git a/tests/Core/_ToDo/Menu/FluentMenuTests.cs b/tests/Core/_ToDo/Menu/FluentMenuTests.cs deleted file mode 100644 index 0d70cf6637..0000000000 --- a/tests/Core/_ToDo/Menu/FluentMenuTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Bunit; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Menu; -public class FluentMenuTests : TestBase -{ - public FluentMenuTests() - { - TestContext.JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Menu/FluentMenu.razor.js"); - TestContext.Services.AddSingleton(LibraryConfiguration.ForUnitTests); - } - - [Fact] - public void FluentMenu_Default() - { - //Arrange - var childContent = "render me"; - string anchor = default!; - MouseButton trigger = default!; - bool open = default!; - HorizontalPosition horizontalPosition = default!; - string width = default!; - Action openChanged = _ => { }; - bool anchored = default!; - var cut = TestContext.RenderComponent(parameters => parameters - .Add(p => p.Anchor, anchor) - .Add(p => p.Trigger, trigger) - .Add(p => p.Open, open) - .AddChildContent(childContent) - .Add(p => p.HorizontalPosition, horizontalPosition) - .Add(p => p.Width, width) - .Add(p => p.OpenChanged, openChanged) - .Add(p => p.Anchored, anchored) - ); - //Act - - //Assert - cut.Verify(); - } -} - diff --git a/tests/Core/_ToDo/Menu/FluentMenuTests.razor b/tests/Core/_ToDo/Menu/FluentMenuTests.razor new file mode 100644 index 0000000000..76ad14f146 --- /dev/null +++ b/tests/Core/_ToDo/Menu/FluentMenuTests.razor @@ -0,0 +1,33 @@ +@namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Menu + +@using Xunit +@inherits TestContext + +@code { + [Fact] + public void FluentMenu_ChildMenuItem_OnKeyboardEnter_Works() + { + // Arrange && Act + var clickCount = 0; + var lockObj = new object(); + var cut = Render(@ + test + ); + + cut.Find("#to-select").KeyDown("Enter", "Enter"); + cut.Find("#to-select").Click(); + + // Assert + cut.WaitForAssertion(() => Assert.Equal(2, clickCount)); + return; + + void OnClick() + { + lock (lockObj) + { + clickCount++; + } + } + } + +} diff --git a/tests/Core/_ToDo/Menu/FluentMenuTests.razor.cs b/tests/Core/_ToDo/Menu/FluentMenuTests.razor.cs new file mode 100644 index 0000000000..ff27af48f2 --- /dev/null +++ b/tests/Core/_ToDo/Menu/FluentMenuTests.razor.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------ +// This file is licensed to you under the MIT License. +// ------------------------------------------------------------------------ +using Bunit; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Menu; +public partial class FluentMenuTests : TestContext +{ + public FluentMenuTests() + { + JSInterop.SetupModule("./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Menu/FluentMenu.razor.js"); + Services.AddSingleton(LibraryConfiguration.ForUnitTests); + Services.AddSingleton(); + } + + [Fact] + public void FluentMenu_Default() + { + //Arrange + var childContent = "render me"; + string anchor = default!; + MouseButton trigger = default!; + bool open = default!; + HorizontalPosition horizontalPosition = default!; + string width = default!; + Action openChanged = _ => { }; + bool anchored = default!; + var cut = RenderComponent(parameters => parameters + .Add(p => p.Anchor, anchor) + .Add(p => p.Trigger, trigger) + .Add(p => p.Open, open) + .AddChildContent(childContent) + .Add(p => p.HorizontalPosition, horizontalPosition) + .Add(p => p.Width, width) + .Add(p => p.OpenChanged, openChanged) + .Add(p => p.Anchored, anchored) + ); + //Act + + //Assert + cut.Verify(); + } + + [Fact] + public void FluentMenuProvider_ShouldUseFluentMenuClass() + { + //Arrange + var className = "some-class"; + var menuProviderCut = RenderComponent(); + var menuCut = RenderComponent(parameters => parameters + .Add(p => p.UseMenuService, true) + .Add(p => p.Class, className) + .Add(p => p.Anchored, true) + .Add(p => p.Id, "menu1") + .Add(p => p.Anchor, "menuAnchor") + + ); + + //Act + menuProviderCut.Render(); + + //Assert + var menuInProvider = menuProviderCut.FindComponent(); + Assert.Equal(className, menuInProvider.Instance.Class, StringComparer.Ordinal); + } +} +