Make demo search, filters, exports, and controls functional while exposing dedicated KPI detail pages with tested breakdowns. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
43
tests/analytics.test.ts
Normal file
43
tests/analytics.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getDashboardData, getMetricDetails } from "@/domain/analytics";
|
||||
|
||||
describe("metric drill-down analytics", () => {
|
||||
const dashboard = getDashboardData();
|
||||
const details = getMetricDetails();
|
||||
|
||||
it("reconciles inventory value with batch totals", () => {
|
||||
expect(details.inventoryValue.retailValue).toBe(dashboard.metrics.retailValue);
|
||||
expect(details.inventoryValue.costValue).toBe(
|
||||
dashboard.inventory.reduce((sum, item) => sum + item.costExposure, 0),
|
||||
);
|
||||
expect(details.inventoryValue.potentialMargin).toBe(
|
||||
details.inventoryValue.retailValue - details.inventoryValue.costValue,
|
||||
);
|
||||
expect(details.inventoryValue.byBranch).toHaveLength(4);
|
||||
});
|
||||
|
||||
it("uses complete current and prior revenue periods", () => {
|
||||
expect(details.revenue.current30d).toBe(dashboard.metrics.revenue30d);
|
||||
expect(details.revenue.current30d).toBeGreaterThan(0);
|
||||
expect(details.revenue.prior30d).toBeGreaterThan(0);
|
||||
expect(Number.isFinite(details.revenue.changePercent)).toBe(true);
|
||||
expect(details.revenue.byProduct).toHaveLength(8);
|
||||
});
|
||||
|
||||
it("limits headline risk exposure to at-risk and expired batches", () => {
|
||||
expect(details.risk.affected.every(
|
||||
(item) => item.status === "at_risk" || item.status === "expired",
|
||||
)).toBe(true);
|
||||
expect(details.risk.costValue).toBe(dashboard.metrics.atRiskValue);
|
||||
expect(details.risk.byStatus.some((row) => row.status === "watch")).toBe(true);
|
||||
});
|
||||
|
||||
it("provides bounded freshness rollups", () => {
|
||||
expect(details.freshness.averageScore).toBe(dashboard.metrics.averageFreshness);
|
||||
expect(details.freshness.weightedScore).toBeGreaterThanOrEqual(0);
|
||||
expect(details.freshness.weightedScore).toBeLessThanOrEqual(100);
|
||||
expect(details.freshness.freshUnitsPercent).toBeGreaterThanOrEqual(0);
|
||||
expect(details.freshness.freshUnitsPercent).toBeLessThanOrEqual(100);
|
||||
expect(details.freshness.byProduct).toHaveLength(8);
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,22 @@ test("shows inventory intelligence overview", async ({ page }) => {
|
||||
await expect(page.getByText("Freshness watchlist")).toBeVisible();
|
||||
});
|
||||
|
||||
test("opens every overview metric drill-down", async ({ page }) => {
|
||||
const metrics = [
|
||||
{ label: "Inventory value", path: "/metrics/inventory-value", heading: "Inventory value" },
|
||||
{ label: "Revenue · 30 days", path: "/metrics/revenue", heading: "Revenue · 30 days" },
|
||||
{ label: "At-risk exposure", path: "/metrics/at-risk", heading: "At-risk exposure" },
|
||||
{ label: "Average freshness", path: "/metrics/freshness", heading: "Average freshness" },
|
||||
];
|
||||
|
||||
for (const metric of metrics) {
|
||||
await page.goto("/");
|
||||
await page.getByRole("link", { name: `View details for ${metric.label}` }).click();
|
||||
await expect(page).toHaveURL(metric.path);
|
||||
await expect(page.getByRole("heading", { name: metric.heading })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("moves from inventory to a product", async ({ page }) => {
|
||||
await page.goto("/inventory");
|
||||
await expect(page.getByRole("heading", { name: "Inventory" })).toBeVisible();
|
||||
|
||||
Reference in New Issue
Block a user