File: Controls\AspireMenuButtonTests.cs
Web Access
Project: src\tests\Aspire.Dashboard.Components.Tests\Aspire.Dashboard.Components.Tests.csproj (Aspire.Dashboard.Components.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Dashboard.Components.Tests.Shared;
using Aspire.Dashboard.Model;
using Bunit;
using Microsoft.FluentUI.AspNetCore.Components;
using Xunit;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;
 
namespace Aspire.Dashboard.Components.Tests.Controls;
 
public class AspireMenuButtonTests : DashboardTestContext
{
    [Fact]
    public void Disabled_DisablesButton()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var cut = RenderComponent<AspireMenuButton>(builder => builder
            .Add(component => component.MenuButtonId, "disabled-menu-button")
            .Add(component => component.ItemsProvider, () => [new MenuButtonItem { Text = "Item" }])
            .Add(component => component.Disabled, true));
 
        Assert.True(cut.FindComponent<FluentButton>().Instance.Disabled);
    }
 
    [Fact]
    public void Render_AddsCollapsedMenuPopupAria()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "view-options-button");
            builder.Add(p => p.Text, "View options");
            builder.Add(p => p.ItemsProvider, () => [new MenuButtonItem { Text = "Show hidden resources" }]);
        });
 
        var button = cut.Find("#view-options-button");
 
        Assert.Equal("menu", button.GetAttribute("aria-haspopup"));
        Assert.Equal("false", button.GetAttribute("aria-expanded"));
        Assert.False(button.HasAttribute("icon-only"));
    }
 
    [Fact]
    public void Render_WithoutText_AddsIconOnlyAttribute()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "icon-only-button");
            builder.Add(p => p.ItemsProvider, () => [new MenuButtonItem { Text = "Action" }]);
        });
 
        Assert.True(cut.Find("#icon-only-button").HasAttribute("icon-only"));
    }
 
    [Fact]
    public void Render_DefaultsIconColorToPrimaryAndAllowsOverride()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "icon-color-button");
            builder.Add(p => p.ItemsProvider, () => [new MenuButtonItem { Text = "Action" }]);
        });
 
        Assert.Equal(Color.Primary, cut.FindComponent<FluentIcon<Icon>>().Instance.Color);
 
        cut.SetParametersAndRender(builder => builder.Add(p => p.IconColor, Color.Default));
 
        Assert.Equal(Color.Default, cut.FindComponent<FluentIcon<Icon>>().Instance.Color);
    }
 
    [Fact]
    public void Render_WithoutTextAndWithStartIcon_SizesForBothIcons()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "icon-only-button");
            builder.Add(p => p.IconStart, new Icons.Regular.Size16.Delete());
            builder.Add(p => p.IconStartClass, "start-icon");
            builder.Add(p => p.IconStartColor, Color.Success);
            builder.Add(p => p.ItemsProvider, () => [new MenuButtonItem { Text = "Action" }]);
        });
 
        var button = cut.Find("#icon-only-button");
        Assert.False(button.HasAttribute("icon-only"));
        Assert.Collection(
            button.QuerySelectorAll("svg"),
            startIcon =>
            {
                Assert.Equal("start", startIcon.GetAttribute("slot"));
                Assert.Contains("start-icon", startIcon.ClassList);
                Assert.Contains("fill: var(--success)", startIcon.GetAttribute("style"), StringComparison.Ordinal);
            },
            menuIcon => Assert.Null(menuIcon.GetAttribute("slot")));
    }
 
    [Fact]
    public async Task ItemsProvider_AddsMenuWhenButtonIsClicked()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var providerInvocationCount = 0;
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "lazy-menu-button");
            builder.Add(p => p.Text, "View options");
            builder.Add(p => p.ItemsProvider, () =>
            {
                providerInvocationCount++;
                return [new MenuButtonItem { Text = $"Item {providerInvocationCount}" }];
            });
        });
 
        Assert.Equal(0, providerInvocationCount);
        Assert.Empty(cut.FindComponents<AspireMenu>());
 
        cut.Find("#lazy-menu-button").Click();
 
        Assert.Equal(1, providerInvocationCount);
        Assert.Single(cut.FindComponents<AspireMenu>());
        Assert.Single(cut.FindComponents<FluentMenu>());
        cut.WaitForAssertion(() => Assert.True(cut.FindComponent<AspireMenu>().Instance.Open));
        Assert.Equal("Item 1", cut.FindComponent<FluentMenuItem>().Instance.Label);
 
        var menu = cut.FindComponent<AspireMenu>().Instance;
        cut.Find("#lazy-menu-button").Click();
 
        Assert.Equal(1, providerInvocationCount);
        Assert.Same(menu, cut.FindComponent<AspireMenu>().Instance);
        Assert.False(cut.FindComponent<AspireMenu>().Instance.Open);
 
        cut.Find("#lazy-menu-button").Click();
 
        Assert.Equal(2, providerInvocationCount);
        Assert.Same(menu, cut.FindComponent<AspireMenu>().Instance);
        Assert.True(cut.FindComponent<AspireMenu>().Instance.Open);
        cut.WaitForAssertion(() => Assert.Equal("Item 2", cut.FindComponent<FluentMenuItem>().Instance.Label));
 
        var fluentMenu = cut.FindComponent<FluentMenu>();
        await fluentMenu.InvokeAsync(() => fluentMenu.Instance.OpenedChanged.InvokeAsync(false));
 
        Assert.False(cut.FindComponent<AspireMenu>().Instance.Open);
    }
 
    [Fact]
    public void ItemsProvider_RefreshesOpenMenuWhenParametersChange()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
        FluentUISetupHelpers.SetupFluentButton(this);
        FluentUISetupHelpers.SetupFluentMenu(this);
 
        var itemText = "First item";
        Func<IList<MenuButtonItem>> itemsProvider = () => [new MenuButtonItem { Text = itemText }];
        var cut = RenderComponent<AspireMenuButton>(builder =>
        {
            builder.Add(p => p.MenuButtonId, "refresh-menu-button");
            builder.Add(p => p.Text, "View options");
            builder.Add(p => p.ItemsProvider, itemsProvider);
        });
 
        cut.Find("#refresh-menu-button").Click();
        cut.WaitForAssertion(() => Assert.Equal("First item", cut.FindComponent<FluentMenuItem>().Instance.Label));
 
        itemText = "Second item";
        cut.SetParametersAndRender(builder => builder.Add(p => p.Text, "Updated view options"));
 
        cut.WaitForAssertion(() => Assert.Equal("Second item", cut.FindComponent<FluentMenuItem>().Instance.Label));
    }
 
}