131 lines
3.5 KiB
TypeScript
131 lines
3.5 KiB
TypeScript
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { describe, expect, it } from "vitest";
|
|
import { anyCapability, type HousekeepingWorkItem } from "../contracts";
|
|
import {
|
|
filterOperationalInboxItems,
|
|
OperationalInbox,
|
|
projectOperationalInboxHref,
|
|
} from "./operational-inbox";
|
|
|
|
const labels = {
|
|
title: "Operational inbox",
|
|
domainFilter: "Filter by domain",
|
|
stateFilter: "Filter by state",
|
|
priorityFilter: "Filter by priority",
|
|
all: "All",
|
|
loading: "Loading operational work",
|
|
empty: "No operational work",
|
|
partial: "Some sources are unavailable",
|
|
open: "Open workflow",
|
|
};
|
|
|
|
function item(
|
|
itemId: string,
|
|
options: Partial<HousekeepingWorkItem> = {},
|
|
): HousekeepingWorkItem {
|
|
return {
|
|
sourceId: "people.queue",
|
|
itemId,
|
|
deduplicationKey: `people.queue:${itemId}`,
|
|
domain: "people",
|
|
capability: anyCapability("admin.users.view"),
|
|
severity: "warning",
|
|
priority: "high",
|
|
ageMs: 1_000,
|
|
state: "open",
|
|
occurredAt: "2026-08-30T10:00:00.000Z",
|
|
titleKey: `Item ${itemId}`,
|
|
href: "/ase/people/users",
|
|
freshness: "fresh",
|
|
actions: [],
|
|
...options,
|
|
};
|
|
}
|
|
|
|
describe("OperationalInbox", () => {
|
|
it("filters deterministically by domain, state, and priority", () => {
|
|
const items = [
|
|
item("people-open"),
|
|
item("content-draft", {
|
|
domain: "content",
|
|
state: "draft",
|
|
priority: "normal",
|
|
href: "/ase/content/editorial/articles",
|
|
}),
|
|
];
|
|
expect(
|
|
filterOperationalInboxItems(items, {
|
|
domain: "people",
|
|
state: "open",
|
|
priority: "high",
|
|
}).map((entry) => entry.itemId),
|
|
).toEqual(["people-open"]);
|
|
expect(
|
|
filterOperationalInboxItems(items, {
|
|
domain: "all",
|
|
state: "draft",
|
|
priority: "all",
|
|
}).map((entry) => entry.itemId),
|
|
).toEqual(["content-draft"]);
|
|
});
|
|
|
|
it("renders accessible filters, partial errors, and preview links", () => {
|
|
const html = renderToStaticMarkup(
|
|
<OperationalInbox
|
|
labels={labels}
|
|
surface="preview"
|
|
response={{
|
|
items: [item("ticket-1")],
|
|
errors: [{ sourceId: "content.publication", code: "TIMEOUT" }],
|
|
correlationId: "inbox-ui",
|
|
}}
|
|
/>,
|
|
);
|
|
expect(html).toContain('aria-label="Filter by domain"');
|
|
expect(html).toContain('aria-label="Filter by state"');
|
|
expect(html).toContain('aria-label="Filter by priority"');
|
|
expect(html).toContain("Some sources are unavailable");
|
|
expect(html).not.toContain("content.publication");
|
|
expect(html).not.toContain("TIMEOUT");
|
|
expect(html).toContain('role="alert"');
|
|
expect(html).toContain('href="/ase-next/people/users"');
|
|
expect(html).not.toContain("Dismiss");
|
|
expect(html).not.toContain("Assign");
|
|
});
|
|
|
|
it("renders explicit loading and empty states", () => {
|
|
const response = {
|
|
items: [],
|
|
errors: [],
|
|
correlationId: "inbox-empty",
|
|
} as const;
|
|
const loading = renderToStaticMarkup(
|
|
<OperationalInbox
|
|
labels={labels}
|
|
surface="preview"
|
|
response={response}
|
|
loading
|
|
/>,
|
|
);
|
|
const empty = renderToStaticMarkup(
|
|
<OperationalInbox
|
|
labels={labels}
|
|
surface="preview"
|
|
response={response}
|
|
/>,
|
|
);
|
|
expect(loading).toContain('role="status"');
|
|
expect(loading).toContain("Loading operational work");
|
|
expect(empty).toContain("No operational work");
|
|
});
|
|
|
|
it("projects canonical hrefs without changing production destinations", () => {
|
|
expect(
|
|
projectOperationalInboxHref("/ase/system/operations/alerts", "preview"),
|
|
).toBe("/ase-next/system/operations/alerts");
|
|
expect(
|
|
projectOperationalInboxHref("/ase/system/operations/alerts", "canonical"),
|
|
).toBe("/ase/system/operations/alerts");
|
|
});
|
|
});
|