refactor(ui): extract reusable component package
This commit is contained in:
parent
87261b5a3f
commit
2664804e91
86 changed files with 102 additions and 85 deletions
202
packages/ui/src/components/render.test.tsx
Normal file
202
packages/ui/src/components/render.test.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import { renderToString } from "react-dom/server";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { Button } from "./Button";
|
||||
import { DashboardFrame } from "./DashboardFrame";
|
||||
import { FooterCell } from "./FooterCell";
|
||||
import { IconButton } from "./IconButton";
|
||||
import { ServiceRow } from "./ServiceRow";
|
||||
import { StatusStrip } from "./StatusStrip";
|
||||
import { TelemetryCard } from "./TelemetryCard";
|
||||
import { TelemetryGrid } from "./TelemetryGrid";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
import { dashboardPreviewFixtures } from "../fixtures";
|
||||
|
||||
describe("dashboard UI components", () => {
|
||||
test("renders the primary generic dashboard fixture", () => {
|
||||
const body = renderToString(
|
||||
<DashboardFrame dashboard={dashboardPreviewFixtures.primary} />,
|
||||
);
|
||||
|
||||
expect(body).toContain("Operations Console");
|
||||
expect(body).toContain("Core Throughput");
|
||||
expect(body).toContain("Queue Workers");
|
||||
expect(body).toContain("console-header");
|
||||
expect(body).toContain("telemetry-section");
|
||||
expect(body).toContain("6 Metrics");
|
||||
expect(body).toContain('data-variant="system-bus"');
|
||||
expect(body).toContain("data-icon-name=\"mdi:server-network\"");
|
||||
expect(body).toContain("data-severity=\"warning\"");
|
||||
expect(body).not.toContain("dashboard-title");
|
||||
});
|
||||
|
||||
test("renders another generic dashboard fixture through the same component", () => {
|
||||
const body = renderToString(
|
||||
<DashboardFrame dashboard={dashboardPreviewFixtures.secondary} />,
|
||||
);
|
||||
|
||||
expect(body).toContain("Support Desk");
|
||||
expect(body).toContain("Response Window");
|
||||
expect(body).toContain("Regional Nodes");
|
||||
expect(body).toContain("data-icon-name=\"mdi:headset\"");
|
||||
expect(body).toContain("data-severity=\"loading\"");
|
||||
});
|
||||
|
||||
test("renders optional service and status links as focusable anchors", () => {
|
||||
const service = renderToString(
|
||||
<ServiceRow
|
||||
service={{
|
||||
id: "linked-service",
|
||||
label: "Linked Service",
|
||||
description: "Generic linked destination",
|
||||
severity: "ok",
|
||||
detail: "ready",
|
||||
link: { href: "https://example.test/service", external: true },
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const footer = renderToString(
|
||||
<FooterCell
|
||||
item={{
|
||||
id: "linked-status",
|
||||
label: "Linked Status",
|
||||
value: "open",
|
||||
severity: "neutral",
|
||||
link: { href: "https://example.test/status" },
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(service).toContain("<a ");
|
||||
expect(service).toContain("href=\"https://example.test/service\"");
|
||||
expect(service).toContain("target=\"_blank\"");
|
||||
expect(service).toContain('class="service-row__status"');
|
||||
expect(service).toContain('class="service-row__launch"');
|
||||
expect(footer).toContain("<a ");
|
||||
expect(footer).toContain("href=\"https://example.test/status\"");
|
||||
});
|
||||
|
||||
test("renders the status strip as a compact system bus", () => {
|
||||
const body = renderToString(
|
||||
<StatusStrip
|
||||
id="dashboard-status"
|
||||
items={[
|
||||
{ id: "system", label: "System", value: "Nominal", severity: "ok" },
|
||||
{ id: "refresh", label: "Refresh", value: "15s", severity: "neutral" },
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(body).toContain('class="status-strip"');
|
||||
expect(body).toContain('data-variant="system-bus"');
|
||||
expect(body).toContain("Nominal");
|
||||
});
|
||||
|
||||
test("renders stable model IDs on group and status containers", () => {
|
||||
const body = renderToString(
|
||||
<DashboardFrame dashboard={dashboardPreviewFixtures.primary} />,
|
||||
);
|
||||
|
||||
expect(body).toContain("data-model-id=\"queue-workers\"");
|
||||
expect(body).toContain("data-model-id=\"dashboard-status\"");
|
||||
});
|
||||
|
||||
test("does not render progress bars for non-percent metrics without explicit progress", () => {
|
||||
const withoutProgress = renderToString(
|
||||
<TelemetryCard
|
||||
card={{
|
||||
id: "bytes",
|
||||
label: "Bytes Metric",
|
||||
value: { kind: "bytes", value: 2048 },
|
||||
severity: "neutral",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const withProgress = renderToString(
|
||||
<TelemetryCard
|
||||
card={{
|
||||
id: "bytes-with-progress",
|
||||
label: "Bytes With Progress",
|
||||
value: { kind: "bytes", value: 2048 },
|
||||
progress: 42,
|
||||
severity: "neutral",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(withoutProgress).not.toContain("telemetry-card__bar");
|
||||
expect(withProgress).toContain("--metric-progress:42%");
|
||||
});
|
||||
|
||||
test("renders telemetry trends through the uPlot chart surface", () => {
|
||||
const body = renderToString(
|
||||
<TelemetryCard
|
||||
card={{
|
||||
id: "trend-card",
|
||||
label: "Trend Card",
|
||||
value: { kind: "percent", value: 64 },
|
||||
sparkline: [18, 24, 64],
|
||||
severity: "ok",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(body).toContain('data-chart-library="uplot"');
|
||||
});
|
||||
|
||||
test("renders telemetry as a framed instrument section with indexed cards", () => {
|
||||
const body = renderToString(
|
||||
<TelemetryGrid cards={dashboardPreviewFixtures.primary.telemetry.slice(0, 2)} />,
|
||||
);
|
||||
|
||||
expect(body).toContain('class="telemetry-section"');
|
||||
expect(body).toContain("Telemetry");
|
||||
expect(body).toContain("2 Metrics");
|
||||
expect(body).toContain('data-metric-index="01"');
|
||||
expect(body).toContain('class="telemetry-card__index"');
|
||||
expect(body).toContain('class="telemetry-card__source"');
|
||||
});
|
||||
|
||||
test("base button controls forward native attributes", () => {
|
||||
const button = renderToString(
|
||||
<Button
|
||||
label="Refresh"
|
||||
id="refresh-action"
|
||||
className="custom-action"
|
||||
aria-controls="refresh-target"
|
||||
/>,
|
||||
);
|
||||
const iconButton = renderToString(
|
||||
<IconButton
|
||||
icon="mdi:refresh"
|
||||
label="Refresh status"
|
||||
id="refresh-icon-action"
|
||||
className="custom-icon-action"
|
||||
aria-expanded="false"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(button).toContain("id=\"refresh-action\"");
|
||||
expect(button).toContain("class=\"ui-button custom-action ");
|
||||
expect(button).toContain("aria-controls=\"refresh-target\"");
|
||||
expect(iconButton).toContain("id=\"refresh-icon-action\"");
|
||||
expect(iconButton).toContain("class=\"icon-button custom-icon-action ");
|
||||
expect(iconButton).toContain("aria-expanded=\"false\"");
|
||||
});
|
||||
|
||||
test("renders the theme toggle as a segmented instrument control", () => {
|
||||
const body = renderToString(
|
||||
<ThemeToggle theme="light" onThemeChange={() => undefined} />,
|
||||
);
|
||||
|
||||
expect(body).toContain('class="theme-toggle"');
|
||||
expect(body).toContain('data-ui-theme-current="light"');
|
||||
expect(body).toContain('aria-label="Light theme"');
|
||||
expect(body).toContain('aria-pressed="true"');
|
||||
expect(body).toContain('class="theme-toggle__label"');
|
||||
expect(body).toContain('class="theme-toggle__switch"');
|
||||
expect(body).toContain('data-active="true"');
|
||||
expect(body).toContain("Theme");
|
||||
expect(body).toContain("Dark");
|
||||
expect(body).toContain("Light");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue