// 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.Model;
using Aspire.Dashboard.Otlp.Model;
using Aspire.Dashboard.Components.Tests.Shared;
using Bunit;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Xunit;
namespace Aspire.Dashboard.Components.Tests.Controls;
[UseCulture("en-US")]
public class ChartFiltersTests : DashboardTestContext
{
[Fact]
public void AreAllValuesSelected_SetFalse_ClearsOnlyWhenAllValuesAreStored()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "GET", Value = "GET", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "POST", Value = "POST", });
dimensionFilter.SetSelectedValues(dimensionFilter.Values);
Assert.True(dimensionFilter.AreAllValuesSelected);
dimensionFilter.AreAllValuesSelected = false;
Assert.Empty(dimensionFilter.SelectedValues);
}
[Fact]
public void AreAllValuesSelected_SetFalse_DoesNotClearWhenPartiallySelected()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "GET", Value = "GET", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "POST", Value = "POST", });
dimensionFilter.SetSelectedValues([dimensionFilter.Values[0]]);
Assert.Null(dimensionFilter.AreAllValuesSelected);
dimensionFilter.AreAllValuesSelected = false;
Assert.Single(dimensionFilter.SelectedValues);
Assert.Equal("GET", dimensionFilter.SelectedValues.First().Value);
}
[Fact]
public void AreAllValuesSelected_SetTrue_SelectsAllValues()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "GET", Value = "GET", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "POST", Value = "POST", });
dimensionFilter.SetSelectedValues([dimensionFilter.Values[0]]);
dimensionFilter.AreAllValuesSelected = true;
Assert.Equal(dimensionFilter.Values.Count, dimensionFilter.SelectedValues.Count);
Assert.True(dimensionFilter.AreAllValuesSelected);
}
[Fact]
public void OnTagSelectionChanged_ReplacesSnapshotAndRemovesValue()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
var getValue = new DimensionValueViewModel { Text = "GET", Value = "GET", };
var postValue = new DimensionValueViewModel { Text = "POST", Value = "POST", };
dimensionFilter.Values.Add(getValue);
dimensionFilter.Values.Add(postValue);
dimensionFilter.SetSelectedValues([getValue, postValue]);
var selectedValuesSnapshot = dimensionFilter.SelectedValues;
dimensionFilter.OnTagSelectionChanged(getValue, isChecked: false);
Assert.Single(dimensionFilter.SelectedValues);
Assert.Contains(postValue, dimensionFilter.SelectedValues);
Assert.True(selectedValuesSnapshot.SetEquals([getValue, postValue]));
}
[Fact]
public void Render_AllValuesSelected_ShowsAllState()
{
SetupChartFilters();
var dimensionFilter = CreateDimensionFilter();
dimensionFilter.AreAllValuesSelected = true;
var cut = RenderChartFilters(dimensionFilter);
Assert.DoesNotContain("(None)", cut.Markup);
Assert.Single(cut.FindAll(".chart-filter-button-container"));
Assert.Contains("aria-label=\"All tags\"", cut.Markup);
}
[Fact]
public void Render_FilterValueTags_UseNativeButtons()
{
SetupChartFilters();
var dimensionFilter = CreateDimensionFilter();
dimensionFilter.SetSelectedValues([dimensionFilter.Values.Single(v => v.Text == "POST")]);
var cut = RenderChartFilters(dimensionFilter);
var getTag = cut.FindAll(".filter-value-tag").Single(e => e.TextContent.Trim() == "GET");
Assert.Equal("BUTTON", getTag.TagName);
Assert.Equal("button", getTag.GetAttribute("type"));
}
[Fact]
public void Click_FilterValueTag_SelectsOnlyClickedValue()
{
SetupChartFilters();
var dimensionFilter = CreateDimensionFilter();
dimensionFilter.SetSelectedValues([dimensionFilter.Values.Single(v => v.Text == "POST")]);
var cut = RenderChartFilters(dimensionFilter);
var getTag = cut.FindAll(".filter-value-tag").Single(e => e.TextContent.Trim() == "GET");
getTag.Click(new MouseEventArgs());
var selectedValue = Assert.Single(dimensionFilter.SelectedValues);
Assert.Equal("GET", selectedValue.Text);
}
[Fact]
public void ShiftClick_UnselectedFilterValueTag_AddsClickedValue()
{
SetupChartFilters();
var dimensionFilter = CreateDimensionFilter();
dimensionFilter.SetSelectedValues([dimensionFilter.Values.Single(v => v.Text == "POST")]);
var cut = RenderChartFilters(dimensionFilter);
var getTag = cut.FindAll(".filter-value-tag").Single(e => e.TextContent.Trim() == "GET");
getTag.Click(new MouseEventArgs { ShiftKey = true });
Assert.Equal(["GET", "POST"], dimensionFilter.SelectedValues.Select(v => v.Text).Order());
}
[Fact]
public void ShiftClick_SelectedFilterValueTag_RemovesClickedValue()
{
SetupChartFilters();
var dimensionFilter = CreateDimensionFilter();
dimensionFilter.SetSelectedValues(dimensionFilter.Values);
var cut = RenderChartFilters(dimensionFilter);
var getTag = cut.FindAll(".filter-value-tag").Single(e => e.TextContent.Trim() == "GET");
getTag.Click(new MouseEventArgs { ShiftKey = true });
var selectedValue = Assert.Single(dimensionFilter.SelectedValues);
Assert.Equal("POST", selectedValue.Text);
}
[Fact]
public void GetOrderedValues_NumericValues_OrdersNumerically()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.status_code" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "200", Value = "200", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "404", Value = "404", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "50", Value = "50", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "1000", Value = "1000", });
var ordered = ChartFilterTags.GetOrderedValues(dimensionFilter.Values).Select(v => v.Text).ToList();
Assert.Equal(["50", "200", "404", "1000"], ordered);
}
[Fact]
public void GetOrderedValues_TextValues_OrdersAlphabetically()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "POST", Value = "POST", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "GET", Value = "GET", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "DELETE", Value = "DELETE", });
var ordered = ChartFilterTags.GetOrderedValues(dimensionFilter.Values).Select(v => v.Text).ToList();
Assert.Equal(["DELETE", "GET", "POST"], ordered);
}
private IRenderedComponent<ChartFilters> RenderChartFilters(
DimensionFilterViewModel dimensionFilter,
Action<DimensionFilterViewModel>? onDimensionValuesChanged = null)
{
return RenderComponent<ChartFilters>(builder =>
{
builder.Add(p => p.InstrumentType, OtlpInstrumentType.Sum);
builder.Add(p => p.ShowCount, false);
builder.Add(p => p.DimensionFilters, [dimensionFilter]);
if (onDimensionValuesChanged is not null)
{
builder.Add(p => p.OnDimensionValuesChanged, EventCallback.Factory.Create(this, onDimensionValuesChanged));
}
});
}
private void SetupChartFilters()
{
FluentUISetupHelpers.AddCommonDashboardServices(this);
FluentUISetupHelpers.SetupFluentDataGrid(this);
FluentUISetupHelpers.SetupFluentCheckbox(this);
FluentUISetupHelpers.SetupFluentAnchoredRegion(this);
MetricsSetupHelpers.SetupChartContainer(this);
FluentUISetupHelpers.SetupFluentUIComponents(this);
}
private static DimensionFilterViewModel CreateDimensionFilter()
{
var dimensionFilter = new DimensionFilterViewModel { Name = "http.method" };
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "GET", Value = "GET", });
dimensionFilter.Values.Add(new DimensionValueViewModel { Text = "POST", Value = "POST", });
return dimensionFilter;
}
}