feat(centralui): Site Call KPI tiles on the Health dashboard
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
using Bunit;
|
||||
using Bunit.TestDoubles;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ScadaLink.CentralUI.Components.Health;
|
||||
using ScadaLink.Commons.Messages.Audit;
|
||||
|
||||
namespace ScadaLink.CentralUI.Tests.Components.Health;
|
||||
|
||||
/// <summary>
|
||||
/// bUnit tests for <see cref="SiteCallKpiTiles"/> (Site Call Audit #22, Task 7).
|
||||
/// The component renders three Bootstrap-card tiles — Buffered, Stuck, Parked —
|
||||
/// from a single <see cref="SiteCallKpiResponse"/> snapshot. The tests pin:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>Three-tile render contract (data-test attributes for stable selectors).</item>
|
||||
/// <item>Tile values render the snapshot's counters.</item>
|
||||
/// <item>Threshold borders fire correctly — danger on Parked > 0, warning
|
||||
/// on Stuck > 0, none when those counts are zero, none on Buffered.</item>
|
||||
/// <item>Unavailable snapshot renders em dashes plus the error message.</item>
|
||||
/// <item>Tile clicks navigate to the correct pre-filtered Site Calls report URL.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class SiteCallKpiTilesTests : BunitContext
|
||||
{
|
||||
private static SiteCallKpiResponse MakeSnapshot(int buffered, int parked, int stuck) =>
|
||||
new(
|
||||
CorrelationId: "k",
|
||||
Success: true,
|
||||
ErrorMessage: null,
|
||||
BufferedCount: buffered,
|
||||
ParkedCount: parked,
|
||||
FailedLastInterval: 0,
|
||||
DeliveredLastInterval: 0,
|
||||
OldestPendingAge: null,
|
||||
StuckCount: stuck);
|
||||
|
||||
[Fact]
|
||||
public void Renders_ThreeTiles_FromSnapshot()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 120, parked: 3, stuck: 7))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
// Three stable data-test selectors — the contract for both these tests
|
||||
// and any future Playwright sweep.
|
||||
Assert.Contains("data-test=\"site-call-kpi-buffered\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"site-call-kpi-stuck\"", cut.Markup);
|
||||
Assert.Contains("data-test=\"site-call-kpi-parked\"", cut.Markup);
|
||||
|
||||
// Tile values render the snapshot's counters.
|
||||
Assert.Contains(">120<", cut.Markup); // buffered
|
||||
Assert.Contains(">7<", cut.Markup); // stuck
|
||||
Assert.Contains(">3<", cut.Markup); // parked
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnavailableSnapshot_RendersEmDashes_AndErrorMessage()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, (SiteCallKpiResponse?)null)
|
||||
.Add(c => c.IsAvailable, false)
|
||||
.Add(c => c.ErrorMessage, "site call repository unavailable"));
|
||||
|
||||
// All three tiles show em dashes — em dash (U+2014) "—" must appear.
|
||||
Assert.Contains("—", cut.Markup);
|
||||
// Inline error message renders below.
|
||||
Assert.Contains("Site Call KPIs unavailable", cut.Markup);
|
||||
Assert.Contains("site call repository unavailable", cut.Markup);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_GetsDangerBorder_WhenParkedAboveZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 4, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
Assert.Contains("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_NoDangerBorder_WhenParkedZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 9, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
Assert.DoesNotContain("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_GetsWarningBorder_WhenStuckAboveZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 0, stuck: 6))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
Assert.Contains("border-warning", tile.GetAttribute("class") ?? string.Empty);
|
||||
// Warning, not danger — Stuck is the softer signal.
|
||||
Assert.DoesNotContain("border-danger", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_NoWarningBorder_WhenStuckZero()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 9, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
Assert.DoesNotContain("border-warning", tile.GetAttribute("class") ?? string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferedTile_HasNoThresholdBorder_EvenWithHighCount()
|
||||
{
|
||||
// A non-zero buffer is normal operation — the Buffered tile is a plain
|
||||
// count tile and never gets a danger/warning border.
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 5000, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-buffered\"]");
|
||||
var cls = tile.GetAttribute("class") ?? string.Empty;
|
||||
Assert.DoesNotContain("border-danger", cls);
|
||||
Assert.DoesNotContain("border-warning", cls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BufferedTile_Click_NavigatesToUnfilteredSiteCallsReport()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 50, parked: 0, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-buffered\"]");
|
||||
tile.Click();
|
||||
|
||||
// Unfiltered /site-calls/report — no query string.
|
||||
Assert.EndsWith("/site-calls/report", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StuckTile_Click_NavigatesToSiteCallsReport_WithStuckFilter()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 0, stuck: 6))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-stuck\"]");
|
||||
tile.Click();
|
||||
|
||||
// Spec: Stuck tile drills into the report's "stuck only" filter.
|
||||
Assert.Contains("/site-calls/report?stuck=true", nav.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParkedTile_Click_NavigatesToSiteCallsReport_WithParkedStatusFilter()
|
||||
{
|
||||
var cut = Render<SiteCallKpiTiles>(p => p
|
||||
.Add(c => c.Snapshot, MakeSnapshot(buffered: 0, parked: 4, stuck: 0))
|
||||
.Add(c => c.IsAvailable, true));
|
||||
|
||||
var nav = (BunitNavigationManager)Services.GetRequiredService<NavigationManager>();
|
||||
var tile = cut.Find("[data-test=\"site-call-kpi-parked\"]");
|
||||
tile.Click();
|
||||
|
||||
// Spec: Parked tile drills into ?status=Parked.
|
||||
Assert.Contains("/site-calls/report?status=Parked", nav.Uri);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user