File: Controls\ResourceTagEditorTests.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.Controls;
using Aspire.Dashboard.Components.Tests.Shared;
using Aspire.Dashboard.Model;
using Bunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Xunit;
 
namespace Aspire.Dashboard.Components.Tests.Controls;
 
public class ResourceTagEditorTests : DashboardTestContext
{
    [Fact]
    public void AppHostTagsAreLockedAndOnlyUserTagsCanBeRemoved()
    {
        var store = Services.GetRequiredService<ResourceTagStore>();
        store.AddTag("api", "Personal");
        store.AddTag("api", "backend");
        var cut = Render<ResourceTagEditor>(p => p.Add(c => c.ResourceName, "api")
            .Add(c => c.AppHostTags, new[] { "Backend", "API" }));
 
        Assert.Equal(["Backend", "API", "Personal"], cut.FindAll(".tag-chip-name").Select(c => c.TextContent));
        Assert.Single(cut.FindAll(".tag-chip-remove"));
        Assert.Equal(["Defined by the AppHost. This tag cannot be removed.", "Defined by the AppHost. This tag cannot be removed."],
            cut.FindAll(".tag-chip-lock[role=img]").Select(e => e.GetAttribute("aria-label")));
        cut.Find(".tag-add").Click();
        cut.Find("[role=combobox]").KeyDown("Backspace");
        cut.Find("[role=combobox]").KeyDown("Backspace");
 
        Assert.Equal(["Backend", "API"], cut.FindAll(".tag-chip-name").Select(c => c.TextContent));
        Assert.Empty(cut.FindAll(".tag-chip-remove"));
        Assert.Equal(["backend"], store.GetTags("api"));
    }
 
    private readonly ResourceTagStore _tagStore = new(databasePath: null, NullLogger.Instance);
 
    [Fact]
    public void AppHostTagsFromOtherResourcesCanBeAddedAsEditableUserTags()
    {
        var cut = Render<ResourceTagEditor>(p => p.Add(c => c.ResourceName, "api")
            .Add(c => c.AvailableTags, new[] { "Backend" }));
        cut.Find(".tag-add").Click();
        cut.Find(".tag-input").Input("back");
 
        Assert.Equal(["Backend", "Create tag \"back\""], cut.FindAll("[role=option]").Select(o => o.TextContent.Trim()));
        cut.Find(".tag-input").KeyDown("Enter");
        Assert.Equal(["Backend"], _tagStore.GetTags("api"));
        Assert.Single(cut.FindAll(".tag-chip-remove"));
    }
 
    public ResourceTagEditorTests()
    {
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        Services.AddLocalization();
        Services.AddSingleton(_tagStore);
    }
 
    [Fact]
    public void Render_ShowsResourceTags()
    {
        _tagStore.AddTag("api", "backend");
        _tagStore.AddTag("api", "core");
        _tagStore.AddTag("worker", "jobs");
 
        var cut = RenderEditor("api");
 
        Assert.Equal(["backend", "core"], cut.FindAll(".tag-chip-name").Select(e => e.TextContent));
    }
 
    [Fact]
    public void Input_SuggestsExistingTagsAndCreateOption()
    {
        _tagStore.AddTag("api", "backend");
        _tagStore.AddTag("worker", "background");
        _tagStore.AddTag("worker", "jobs");
 
        var cut = RenderEditor("api");
        cut.Find(".tag-add").Click();
        cut.Find(".tag-input").Input("ba");
 
        var options = cut.FindAll("[role='option']");
        Assert.Equal(["background", "Create tag \"ba\""], options.Select(o => o.TextContent.Trim()));
        Assert.Equal("true", options[0].GetAttribute("aria-selected"));
        Assert.Equal(options[0].Id, cut.Find(".tag-input").GetAttribute("aria-activedescendant"));
    }
 
    [Fact]
    public void Enter_AddsActiveSuggestionAndKeepsEditorOpen()
    {
        _tagStore.AddTag("worker", "Background");
 
        var cut = RenderEditor("api");
        cut.Find(".tag-add").Click();
        cut.Find(".tag-input").Input("back");
        cut.Find(".tag-input").KeyDown("Enter");
 
        Assert.Equal(["Background"], _tagStore.GetTags("api"));
        Assert.Equal(["Background"], cut.FindAll(".tag-chip-name").Select(e => e.TextContent));
        Assert.Equal(string.Empty, cut.Find(".tag-input").GetAttribute("value"));
    }
 
    [Fact]
    public void Enter_CreatesNewTag()
    {
        var cut = RenderEditor("api");
        cut.Find(".tag-add").Click();
        cut.Find(".tag-input").Input("  new   tag ");
        cut.Find(".tag-input").KeyDown("Enter");
 
        Assert.Equal(["new tag"], _tagStore.GetTags("api"));
    }
 
    [Fact]
    public void RemoveButton_RemovesTag()
    {
        _tagStore.AddTag("api", "backend");
        _tagStore.AddTag("api", "core");
 
        var cut = RenderEditor("api");
        cut.Find("button[aria-label='Remove tag backend']").Click();
 
        Assert.Equal(["core"], _tagStore.GetTags("api"));
        Assert.Equal(["core"], cut.FindAll(".tag-chip-name").Select(e => e.TextContent));
    }
 
    [Fact]
    public void Backspace_InEmptyInputRemovesLastTag()
    {
        _tagStore.AddTag("api", "backend");
        _tagStore.AddTag("api", "core");
 
        var cut = RenderEditor("api");
        cut.Find(".tag-add").Click();
        cut.Find(".tag-input").KeyDown("Backspace");
 
        Assert.Equal(["backend"], _tagStore.GetTags("api"));
    }
 
    [Fact]
    public void TagStoreChange_UpdatesChips()
    {
        var cut = RenderEditor("api");
 
        _tagStore.AddTag("api", "backend");
 
        cut.WaitForAssertion(() => Assert.Equal(["backend"], cut.FindAll(".tag-chip-name").Select(e => e.TextContent)));
    }
 
    private IRenderedComponent<ResourceTagEditor> RenderEditor(string resourceName) =>
        Render<ResourceTagEditor>(builder => builder.Add(p => p.ResourceName, resourceName));
}