Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -396,3 +396,4 @@ FodyWeavers.xsd

# JetBrains Rider
*.sln.iml
/output
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<DocsLink Href="@DemoRouteConstants.Docs_BarChart" />

<Prerequisites PageUrl="@pageUrl" />

Check warning on line 15 in BlazorExpress.ChartJS.Demo.RCL/Pages/Demos/BarChart/BarChartDocumentation.razor

View workflow job for this annotation

GitHub Actions / Build and Deploy Job

Found markup element with unexpected name 'Prerequisites'. If this is intended to be a component, add a @using directive for its namespace.

<Section Class="p-0" Size="HeadingSize.H4" Name="How it works" PageUrl="@pageUrl" Link="how-it-works">
<Block>
Expand All @@ -32,6 +32,13 @@
<Demo Type="typeof(BarChart_Demo_01_Examples)" Tabs="true" />
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Click event" PageUrl="@pageUrl" Link="click-event">
<Block>
Set the <code>OnClick</code> callback to react when a chart data item is selected. The callback receives <code>ChartClickEventArgs</code> with the dataset index and label, data-item index and label, and raw value.
The basic Bar Chart demo displays the selected item below the chart.
</Block>
</Section>

<Section Class="p-0" Size="HeadingSize.H4" Name="Combo bar/line" PageUrl="@pageUrl" Link="combo-bar-line">
<Block>
The <strong>Combo bar/line</strong> demo mixes <code>BarChartDataset</code> and <code>LineChartDataset</code> in the same <code>BarChart</code>.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
<BarChart @ref="barChart" Width="600" />
<BarChart @ref="barChart" Width="600" OnClick="HandleClickAsync" />

@if (selectedChartItem is not null)
{
<p class="mt-3 mb-0">
Selected: @selectedChartItem.DatasetLabel (dataset @selectedChartItem.DatasetIndex), @selectedChartItem.Label (index @selectedChartItem.Index), value @selectedChartItem.Value
</p>
}

<div class="mt-5">
<Button Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="RandomizeAsync"> Randomize </Button>
Expand All @@ -12,6 +19,7 @@
private BarChart barChart = default!;
private BarChartOptions barChartOptions = default!;
private ChartData chartData = default!;
private ChartClickEventArgs? selectedChartItem;

private int datasetsCount = 0;
private int labelsCount = 0;
Expand All @@ -33,6 +41,12 @@
await base.OnAfterRenderAsync(firstRender);
}

private Task HandleClickAsync(ChartClickEventArgs eventArgs)
{
selectedChartItem = eventArgs;
return Task.CompletedTask;
}

private async Task RandomizeAsync()
{
if (chartData is null || chartData.Datasets is null || !chartData.Datasets.Any()) return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BlazorExpress.ChartJS.MCP\BlazorExpress.ChartJS.MCP.csproj" />
</ItemGroup>

</Project>
69 changes: 69 additions & 0 deletions BlazorExpress.ChartJS.MCP.Tests/ChartCatalogTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace BlazorExpress.ChartJS.MCP.Tests;

public class ChartCatalogTests
{
[Fact]
public void All_Returns_Current_Public_Chart_Types()
{
var names = ChartCatalog.All.Select(x => x.Name).ToArray();

Assert.Equal(["Bar", "Bubble", "Doughnut", "Line", "Pie", "PolarArea", "Radar", "Scatter"], names);
}

[Theory]
[InlineData("Bar", "BarChart", "BarChartOptions", "BarChartDataset")]
[InlineData("polar-area", "PolarAreaChart", "PolarAreaChartOptions", "PolarAreaChartDataset")]
public void GetSchema_Returns_Metadata_For_Chart_Type(string chartType, string component, string options, string dataset)
{
var schema = ChartCatalog.GetSchema(chartType);

Assert.Equal(component, schema.Component);
Assert.Equal(options, schema.OptionsType);
Assert.Equal(dataset, schema.DatasetType);
Assert.NotEmpty(schema.CommonInputs);
Assert.NotEmpty(schema.Examples);
}

[Fact]
public void Radar_Is_Not_Datalabel_Safe()
{
var schema = ChartCatalog.GetSchema("Radar");

Assert.False(schema.SupportsDatalabels);
}

[Theory]
[InlineData("Bubble")]
[InlineData("Radar")]
public void Charts_Without_Plugins_Report_No_Title_Or_Legend_Support(string chartType)
{
var schema = ChartCatalog.GetSchema(chartType);

Assert.False(schema.SupportsPluginOptions);
Assert.False(schema.SupportsTitleOptions);
Assert.False(schema.SupportsLegendOptions);
}

[Theory]
[InlineData("Bar")]
[InlineData("Scatter")]
public void Charts_With_Plugins_Report_Title_And_Legend_Support(string chartType)
{
var schema = ChartCatalog.GetSchema(chartType);

Assert.True(schema.SupportsPluginOptions);
Assert.True(schema.SupportsTitleOptions);
Assert.True(schema.SupportsLegendOptions);
}

[Theory]
[InlineData("Bar", "numericDatasetsJson")]
[InlineData("Scatter", "pointDatasetsJson")]
[InlineData("Bubble", "pointDatasetsJson")]
public void GetSchema_Includes_Dataset_Examples(string chartType, string exampleKey)
{
var schema = ChartCatalog.GetSchema(chartType);

Assert.True(schema.Examples.ContainsKey(exampleKey));
}
}
87 changes: 87 additions & 0 deletions BlazorExpress.ChartJS.MCP.Tests/ChartClickEventArgsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using BlazorExpress.ChartJS;
using Microsoft.AspNetCore.Components;
using System.Text.Json;

namespace BlazorExpress.ChartJS.MCP.Tests;

public class ChartClickEventArgsTests
{
[Fact]
public async Task HandleClickAsync_Invokes_Configured_Callback_With_Selected_Item_Metadata()
{
var component = new TestChartComponent();
ChartClickEventArgs? receivedEventArgs = null;
component.ConfigureOnClick(EventCallback.Factory.Create<ChartClickEventArgs>(new object(), eventArgs => receivedEventArgs = eventArgs));

using var document = JsonDocument.Parse("""{"value":42}""");
var eventArgs = new ChartClickEventArgs
{
DatasetIndex = 1,
DatasetLabel = "Revenue",
Index = 2,
Label = "March",
Value = document.RootElement.Clone()
};

await component.HandleClickAsync(eventArgs);

Assert.NotNull(receivedEventArgs);
Assert.Equal(1, receivedEventArgs.DatasetIndex);
Assert.Equal("Revenue", receivedEventArgs.DatasetLabel);
Assert.Equal(2, receivedEventArgs.Index);
Assert.Equal("March", receivedEventArgs.Label);
Assert.Equal(42, receivedEventArgs.Value?.GetProperty("value").GetInt32());
}

[Fact]
public async Task HandleClickAsync_Without_Callback_Completes_Without_Throwing()
{
var component = new TestChartComponent();

var exception = await Record.ExceptionAsync(() => component.HandleClickAsync(new ChartClickEventArgs
{
DatasetIndex = 0,
Index = 0,
Value = null
}));

Assert.Null(exception);
}

[Fact]
public void JavaScript_Click_Bridge_Maps_Selected_Item_And_Protects_No_Item_Path()
{
var source = File.ReadAllText(GetRepositoryFile("BlazorExpress.ChartJS", "wwwroot", "blazorexpress.chartjs.js"));

Assert.Equal(9, source.Split("initialize: (elementId, type, data, options, plugins, dotNetObjectReference)").Length - 1);
Assert.Contains("chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true)", source);
Assert.Contains("if (!activeElements || activeElements.length === 0) return;", source);
Assert.Contains("dotNetObjectReference.invokeMethodAsync('HandleClickAsync'", source);
Assert.Contains("datasetIndex: activeElement.datasetIndex", source);
Assert.Contains("datasetLabel: dataset?.label ?? null", source);
Assert.Contains("index: activeElement.index", source);
Assert.Contains("label: chart.data.labels?.[activeElement.index] ?? null", source);
Assert.Contains("value: dataset?.data?.[activeElement.index] ?? null", source);
Assert.DoesNotContain("options.onClick", source);
}

private static string GetRepositoryFile(params string[] pathSegments)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);

while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "BlazorExpress.ChartJS.sln")))
return Path.Combine([directory.FullName, .. pathSegments]);

directory = directory.Parent;
}

throw new DirectoryNotFoundException("The ChartJS repository root could not be located.");
}

private sealed class TestChartComponent : ChartComponentBase
{
public void ConfigureOnClick(EventCallback<ChartClickEventArgs> onClick) => OnClick = onClick;
}
}
Loading
Loading