File: Dialogs\DashboardFeedbackTests.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.Dialogs;
using Aspire.Dashboard.Components.Tests.Shared;
using Aspire.Dashboard.Model;
using Bunit;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.FluentUI.AspNetCore.Components;
using Xunit;
 
namespace Aspire.Dashboard.Components.Tests.Dialogs;
 
[UseCulture("en-US")]
public sealed class DashboardFeedbackTests : DashboardTestContext
{
    [Fact]
    public void BuildMarkdown_OmitsUnansweredSections()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        var loc = Services.GetRequiredService<IStringLocalizer<Resources.Dialogs>>();
        var feedback = new DashboardFeedback { IsAppHost = false };
 
        var markdown = feedback.BuildMarkdown(loc);
 
        Assert.Equal(
            """
            ### Dashboard experience feedback
 
            **How do you run the dashboard?**
            Standalone, for telemetry only
            """,
            markdown);
    }
 
    [Fact]
    public void BuildMarkdown_IncludesOnlySelectedAndEnteredFeedback()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        var loc = Services.GetRequiredService<IStringLocalizer<Resources.Dialogs>>();
        var feedback = new DashboardFeedback
        {
            IsAppHost = true,
            UsageDuration = DashboardFeedbackUsageDuration.TwoToSevenDays,
            MissingCapabilities = "A direct link to my frontend.",
            Difficulties = "Primary navigation was difficult to scan.",
            OtherReason = "The layout used too much horizontal space."
        };
        feedback.Reasons.Add(DashboardFeedbackReason.Navigation);
        feedback.Reasons.Add(DashboardFeedbackReason.Other);
 
        var markdown = feedback.BuildMarkdown(loc);
 
        Assert.Equal(
            """
            ### Dashboard experience feedback
 
            **How long have you used the new dashboard?**
            2 to 7 days
 
            **What made you go back? Select all that apply.**
            - It was hard to find my way around
            - Another reason: The layout used too much horizontal space.
 
            **What is missing from the new dashboard?**
            A direct link to my frontend.
 
            **What was harder or less clear than in the previous dashboard?**
            Primary navigation was difficult to scan.
 
            **How do you run the dashboard?**
            With an AppHost
            """,
            markdown);
    }
 
    [Fact]
    public async Task CopyAndOpenDiscussion_CopiesConditionalMarkdownAndOpensConfiguredDiscussion()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentList(this);
        JSInterop.Setup<bool>("copyTextAndOpen", _ => true).SetResult(true);
        var feedback = new DashboardFeedback
        {
            IsAppHost = true,
            MissingCapabilities = "A missing workflow."
        };
        var cut = Render<DashboardFeedbackDialog>(builder => builder
            .Add(p => p.Content, feedback)
            .AddCascadingValue(CreateDialogInstance()));
 
        await cut.InvokeAsync(() => Assert.Single(
            cut.FindAll("fluent-button"),
            button => button.TextContent.Contains("Continue", StringComparison.Ordinal)).Click());
        var copyAndOpenButton = Assert.Single(
            cut.FindAll("fluent-button"),
            button => button.TextContent.Contains("Copy and open discussion", StringComparison.Ordinal));
        await cut.InvokeAsync(() => copyAndOpenButton.Click());
 
        var invocation = Assert.Single(JSInterop.Invocations["copyTextAndOpen"]);
        var markdown = Assert.IsType<string>(invocation.Arguments[0]);
        Assert.Contains("A missing workflow.", markdown);
        var discussionUrl = new Uri(Assert.IsType<string>(invocation.Arguments[1]));
        var query = QueryHelpers.ParseQuery(discussionUrl.Query);
        Assert.Equal("dashboard-feedback", query["category"]);
        Assert.Equal("[Dashboard feedback] Dashboard experience feedback", query["title"]);
        Assert.Equal(markdown, query["body"]);
    }
 
    [Fact]
    public void DurationPicker_UsesFluentSelect()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentList(this);
        var feedback = new DashboardFeedback { IsAppHost = true };
        var cut = Render<DashboardFeedbackDialog>(builder => builder
            .Add(p => p.Content, feedback)
            .AddCascadingValue(CreateDialogInstance()));
 
        Assert.Empty(cut.FindAll("select"));
        var select = cut.FindComponent<FluentSelect<DashboardFeedbackUsageDuration, DashboardFeedbackUsageDuration>>();
        Assert.NotNull(select.Find("fluent-dropdown"));
        Assert.Equal(DashboardFeedbackUsageDuration.NotSpecified, select.Instance.Value);
        Assert.Equal("How long have you used the new dashboard?", select.Instance.AriaLabel);
    }
 
    [Fact]
    public async Task CopyFailure_DoesNotPreventSwitchingToPreviousDashboard()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentList(this);
        JSInterop.Setup<bool>("copyTextAndOpen", _ => true).SetResult(false);
        var switched = false;
        var feedback = new DashboardFeedback
        {
            IsAppHost = true,
            SwitchToLegacyAsync = () =>
            {
                switched = true;
                return Task.CompletedTask;
            }
        };
        var cut = Render<DashboardFeedbackDialog>(builder => builder
            .Add(p => p.Content, feedback)
            .AddCascadingValue(CreateDialogInstance()));
 
        await cut.InvokeAsync(() => Assert.Single(
            cut.FindAll("fluent-button"),
            button => button.TextContent.Contains("Continue", StringComparison.Ordinal)).Click());
        var copyAndOpenButton = Assert.Single(
            cut.FindAll("fluent-button"),
            button => button.TextContent.Contains("Open new discussion and switch", StringComparison.Ordinal));
        await cut.InvokeAsync(() => copyAndOpenButton.Click());
 
        Assert.True(switched);
    }
 
    [Fact]
    public async Task SkipAndSwitch_SwitchesWithoutSendingFeedbackAndIsOnlyOfferedWhenSwitching()
    {
        FluentUISetupHelpers.AddCommonDashboardServices(this);
        FluentUISetupHelpers.SetupFluentUIComponents(this);
        FluentUISetupHelpers.SetupFluentList(this);
        var switched = false;
        var feedback = new DashboardFeedback
        {
            IsAppHost = true,
            SwitchToLegacyAsync = () =>
            {
                switched = true;
                return Task.CompletedTask;
            }
        };
        var cut = Render<DashboardFeedbackDialog>(builder => builder
            .Add(p => p.Content, feedback)
            .AddCascadingValue(CreateDialogInstance()));
 
        await cut.InvokeAsync(() => Assert.Single(
            cut.FindAll("fluent-button"),
            button => button.TextContent.Contains("Skip and switch", StringComparison.Ordinal)).Click());
 
        Assert.True(switched);
        Assert.DoesNotContain(JSInterop.Invocations, i => i.Identifier == "copyTextAndOpen");
 
        var feedbackOnly = Render<DashboardFeedbackDialog>(builder => builder
            .Add(p => p.Content, new DashboardFeedback { IsAppHost = true })
            .AddCascadingValue(CreateDialogInstance()));
        Assert.Equal(["Continue"], feedbackOnly.FindAll(".dashboard-feedback-actions fluent-button").Select(b => b.TextContent.Trim()));
    }
 
    private static IDialogInstance CreateDialogInstance()
    {
        var dialogService = new Aspire.Tests.Shared.TestDialogService();
        var options = new DialogOptions
        {
            Parameters = new Dictionary<string, object?>(),
            Data = new DialogParameters()
        };
        _ = dialogService.ShowDialogAsync(typeof(DashboardFeedbackDialog), options);
        return dialogService.LastInstance!;
    }
}