feat(kpi): K12 — reusable KpiTrendChart SVG component
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
using Bunit;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Components.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit tests for <see cref="KpiTrendChart"/> (M6 K12) — the reusable
|
||||
/// dependency-free SVG trend chart. Coverage pins the three render states
|
||||
/// (multi-point chart, unavailable placeholder, single-sample note), the stable
|
||||
/// <c>data-test</c> slug, and the coordinate-math edge guards (all-equal
|
||||
/// timestamps, all-zero values) that must not divide by zero or throw.
|
||||
/// </summary>
|
||||
public class KpiTrendChartTests : BunitContext
|
||||
{
|
||||
private static readonly DateTime Base = new(2026, 6, 15, 10, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static IReadOnlyList<KpiSeriesPoint> ThreePoints() => new[]
|
||||
{
|
||||
new KpiSeriesPoint(Base, 1.0),
|
||||
new KpiSeriesPoint(Base.AddMinutes(5), 4.0),
|
||||
new KpiSeriesPoint(Base.AddMinutes(10), 2.0),
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Available_WithThreePoints_RendersPolylineTitleAndDataTest()
|
||||
{
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, ThreePoints())
|
||||
.Add(c => c.Title, "Queue Depth")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var markup = cut.Markup;
|
||||
Assert.Contains("<polyline", markup);
|
||||
Assert.Contains("Queue Depth", markup);
|
||||
Assert.Contains("data-test=\"kpi-trend-queue-depth\"", markup);
|
||||
// The SVG must carry an accessible title.
|
||||
Assert.Contains("<title>", markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Available_WithUnit_AppendsUnitToValueLabels()
|
||||
{
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, ThreePoints())
|
||||
.Add(c => c.Title, "Oldest Pending")
|
||||
.Add(c => c.Unit, "s")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
// Max value label is "4 s" with the unit suffix applied.
|
||||
Assert.Contains("max 4 s", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unavailable_RendersEmDashPlaceholderAndError_NoPolyline()
|
||||
{
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, ThreePoints())
|
||||
.Add(c => c.Title, "Queue Depth")
|
||||
.Add(c => c.IsAvailable, false)
|
||||
.Add(c => c.ErrorMessage, "KPI query failed"));
|
||||
|
||||
var markup = cut.Markup;
|
||||
Assert.Contains("—", markup); // em dash
|
||||
Assert.Contains("KPI query failed", markup);
|
||||
Assert.DoesNotContain("<polyline", markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullPoints_RendersPlaceholder_NoPolyline()
|
||||
{
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, (IReadOnlyList<KpiSeriesPoint>?)null)
|
||||
.Add(c => c.Title, "Stuck Count")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var markup = cut.Markup;
|
||||
Assert.Contains("—", markup);
|
||||
Assert.DoesNotContain("<polyline", markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SinglePoint_RendersSingleSampleState_NoPolyline_DoesNotThrow()
|
||||
{
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, new[] { new KpiSeriesPoint(Base, 7.0) })
|
||||
.Add(c => c.Title, "Backlog")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var markup = cut.Markup;
|
||||
Assert.Contains("Only one sample in range.", markup);
|
||||
Assert.DoesNotContain("<polyline", markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllEqualTimestamps_DoesNotThrow_AndRendersPolyline()
|
||||
{
|
||||
// Zero time span would divide by zero without the even-spacing guard.
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, new[]
|
||||
{
|
||||
new KpiSeriesPoint(Base, 1.0),
|
||||
new KpiSeriesPoint(Base, 2.0),
|
||||
new KpiSeriesPoint(Base, 3.0),
|
||||
})
|
||||
.Add(c => c.Title, "Flat Time")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
Assert.Contains("<polyline", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllZeroValues_DoesNotThrow_AndRendersFlatPolyline()
|
||||
{
|
||||
// Zero max value would divide by zero without the flat-line guard.
|
||||
var cut = Render<KpiTrendChart>(p => p
|
||||
.Add(c => c.Points, new[]
|
||||
{
|
||||
new KpiSeriesPoint(Base, 0.0),
|
||||
new KpiSeriesPoint(Base.AddMinutes(5), 0.0),
|
||||
new KpiSeriesPoint(Base.AddMinutes(10), 0.0),
|
||||
})
|
||||
.Add(c => c.Title, "Quiet")
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
Assert.Contains("<polyline", cut.Markup);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Queue Depth", "kpi-trend-queue-depth")]
|
||||
[InlineData("Oldest Pending (s)", "kpi-trend-oldest-pending-s")]
|
||||
[InlineData(" Trailing Spaces ", "kpi-trend-trailing-spaces")]
|
||||
[InlineData("!!!", "kpi-trend-chart")]
|
||||
[InlineData("", "kpi-trend-chart")]
|
||||
public void Slugify_ProducesStableSlug(string title, string expectedDataTest)
|
||||
{
|
||||
Assert.Equal(expectedDataTest, $"kpi-trend-{KpiTrendChart.Slugify(title)}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user