372 lines
18 KiB
TypeScript
372 lines
18 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeAll, describe, it, vi } from "vitest";
|
|
|
|
vi.mock("@hivegrid/ui-mock", async () => import("../packages/ui-mock/src/index.ts"));
|
|
|
|
let uiMockVisual: typeof import("./verify-ui-mock-visual.ts");
|
|
|
|
const fixtures: string[] = [];
|
|
|
|
function createFixture() {
|
|
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "ui-mock-visual-"));
|
|
fixtures.push(rootDir);
|
|
return rootDir;
|
|
}
|
|
|
|
afterEach(() => {
|
|
while (fixtures.length > 0) {
|
|
fs.rmSync(fixtures.pop()!, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
uiMockVisual = await import("./verify-ui-mock-visual.ts");
|
|
});
|
|
|
|
describe("verify-ui-mock-visual", () => {
|
|
it("generates a real hot-swap mock module, page, and snapshot", () => {
|
|
const drill = uiMockVisual.buildUiMockVisualDrill();
|
|
|
|
assert.match(drill.module, /export const mockPage/);
|
|
assert.match(drill.module, /"route": "\/mock\/phase-c\/hot-swap"/);
|
|
assert.match(drill.html, /data-ui-mock-page="phase-c-hot-swap"/);
|
|
assert.match(drill.snapshot, /"pageId": "phase-c-hot-swap"/);
|
|
});
|
|
|
|
it("generates the AK-P20 interim fixture from all five view-model slots", () => {
|
|
const drill = uiMockVisual.buildP20InterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"demoData":/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-p20-interim"/);
|
|
assert.match(drill.html, /\/:tenant\/onboarding\/checklist/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-p20-interim"/);
|
|
});
|
|
|
|
it("generates the VA-P24a interim fixture from the self-service subscription VM", () => {
|
|
const drill = uiMockVisual.buildVaP24aInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"minimumSeatQuantity": 3/);
|
|
assert.match(drill.module, /"requiresPaymentMethod": true/);
|
|
assert.match(drill.html, /data-ui-mock-page="va-p24a-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "va-p24a-interim"/);
|
|
});
|
|
|
|
it("generates the VA-P26a interim fixture from the access-scoped portal dashboard VM", () => {
|
|
const drill = uiMockVisual.buildVaP26aInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"portalName": "Acme Kundenportal"/);
|
|
assert.match(drill.module, /"screenRoutes":/);
|
|
assert.match(drill.html, /data-ui-mock-page="va-p26a-customer-portal-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "va-p26a-customer-portal-interim"/);
|
|
});
|
|
|
|
it("generates the VA-P26b interim fixture from interaction, lifecycle and admin VMs", () => {
|
|
const drill = uiMockVisual.buildVaP26bInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"viewerKind": "customer"/);
|
|
assert.match(drill.module, /"kind": "subcontractor"/);
|
|
assert.match(drill.module, /"eventType": "lifecycle"/);
|
|
assert.match(drill.html, /data-ui-mock-page="va-p26b-customer-portal-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "va-p26b-customer-portal-interim"/);
|
|
});
|
|
|
|
it("generates the VA-P26c domain-admin interim fixture from the entitlement and switch VM", () => {
|
|
const drill = uiMockVisual.buildVaP26cInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"pendingSwitch":/);
|
|
assert.match(drill.module, /"canActivateSwitch": true/);
|
|
assert.match(drill.html, /data-ui-mock-page="va-p26c-customer-portal-domain-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "va-p26c-customer-portal-domain-interim"/);
|
|
});
|
|
|
|
it("generates the AK-P15a telephony interim fixture from all four view-model surfaces", () => {
|
|
const drill = uiMockVisual.buildTelephonyInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"clickToCall":/);
|
|
assert.match(drill.module, /"permission": "telephony\.manage"/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-p15a-telephony-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-p15a-telephony-interim"/);
|
|
});
|
|
|
|
it("generates AK-P15b chat index and detail fixtures from the shared chat view models", () => {
|
|
const indexDrill = uiMockVisual.buildChatIndexInterimVisualDrill();
|
|
const detailDrill = uiMockVisual.buildChatDetailInterimVisualDrill();
|
|
|
|
assert.match(indexDrill.module, /"screenRoute": "\/chat"/);
|
|
assert.match(indexDrill.module, /"permission": "chat\.read_own_channels"/);
|
|
assert.match(indexDrill.html, /data-ui-mock-page="ak-p15b-chat-index-interim"/);
|
|
assert.match(indexDrill.snapshot, /"pageId": "ak-p15b-chat-index-interim"/);
|
|
|
|
assert.match(detailDrill.module, /"screenRoute": "\/chat\/:id"/);
|
|
assert.match(detailDrill.module, /"source": "http_ack"/);
|
|
assert.doesNotMatch(detailDrill.module, /"source": "local_echo"/);
|
|
assert.match(detailDrill.html, /data-ui-mock-page="ak-p15b-chat-detail-interim"/);
|
|
assert.match(detailDrill.snapshot, /"pageId": "ak-p15b-chat-detail-interim"/);
|
|
});
|
|
|
|
it("generates the R-P11a Spotlight interim fixture from the shared search VM", () => {
|
|
const drill = uiMockVisual.buildSpotlightInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"desktop": "modal"/);
|
|
assert.match(drill.module, /"mobile": "viewport"/);
|
|
assert.match(drill.html, /data-ui-mock-page="r-p11a-spotlight-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "r-p11a-spotlight-interim"/);
|
|
});
|
|
|
|
it("passes when committed static baselines match the generated drill", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
const output = uiMockVisual.formatUiMockVisualResult(result);
|
|
|
|
assert.deepEqual(result.failures, []);
|
|
// 7 hot-swap + 7 AK-P20 + 6 R-P11b dashboard + 10 R-P11a Spotlight + 6 R-P12b flow editor
|
|
// + 7 AK-UI-01 email inbox + 7 AK-UI-02 email detail + 7 AK-UI-03 email compose
|
|
// + 7 AK-UI-05 plugin catalog + 8 AK-P15a telephony + 8 AK-P15b chat index
|
|
// + 9 AK-P15b chat detail + 7 VA-P24a + 7 VA-P26a + 9 VA-P26b
|
|
// + 9 VA-P26c domain-admin + 3 R-P6 CRM contact-list + 3 R-P7 lead-inbox
|
|
// + 3 R-P8 pipeline-kanban checks.
|
|
assert.equal(result.passes.length, 130);
|
|
assert.match(output, /^PASS UI mock static artifact baseline verification/);
|
|
assert.match(output, /renders through JSDOM in CI/);
|
|
assert.match(output, /rendered hot-swap navigation has exactly one active route/);
|
|
assert.match(output, /rendered R-P11b dashboard state payload carries the derived role layout/);
|
|
assert.match(output, /rendered R-P11b dashboard state payload ships only the active layout/);
|
|
assert.match(output, /r-p11b-interim\.snapshot\.json: matches generated R-P11b dashboard interim artifact/);
|
|
assert.match(output, /r-p11a-spotlight-interim\.snapshot\.json: matches generated R-P11a Spotlight interim artifact/);
|
|
});
|
|
|
|
it("generates the R-P12b flow editor fixture with the reverse recipient contract", () => {
|
|
const drill = uiMockVisual.buildAutomationEditorVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "subframe"/);
|
|
assert.match(drill.module, /"recipients":/);
|
|
assert.match(drill.html, /data-ui-mock-page="r-p12b-editor"/);
|
|
assert.match(drill.snapshot, /"pageId": "r-p12b-editor"/);
|
|
});
|
|
|
|
it("generates the AK-UI-01 email inbox interim fixture from the shared inbox VM", () => {
|
|
const drill = uiMockVisual.buildEmailInboxInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"screenRoute": "\/emails"/);
|
|
assert.match(drill.module, /"emptyHeadlineKey": "emailInbox\.list\.empty\.headline"/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-ui-01-email-inbox-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-ui-01-email-inbox-interim"/);
|
|
});
|
|
|
|
it("fails when the AK-UI-01 email inbox interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-ui-01-email-inbox.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-ui-01-email-inbox\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("generates the AK-UI-02 email detail interim fixture from the shared detail VM", () => {
|
|
const drill = uiMockVisual.buildEmailDetailInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"screenRoute": "\/emails\/:id"/);
|
|
assert.match(drill.module, /"sourceKey": "emailInbox\.detail\.match\.manual"/);
|
|
// The sanitized body must round-trip into the baseline untouched.
|
|
assert.match(drill.module, /"allowExternalImages": false/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-ui-02-email-detail-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-ui-02-email-detail-interim"/);
|
|
});
|
|
|
|
it("fails when the AK-UI-02 email detail interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-ui-02-email-detail.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-ui-02-email-detail\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("generates the AK-UI-03 email compose interim fixture from the shared compose VM", () => {
|
|
const drill = uiMockVisual.buildEmailComposeInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"screenRoute": "\/emails\/new"/);
|
|
assert.match(drill.module, /"noMailboxKey": "emailInbox\.compose\.noMailbox"/);
|
|
assert.match(drill.module, /blockquote/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-ui-03-email-compose-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-ui-03-email-compose-interim"/);
|
|
});
|
|
|
|
it("fails when the AK-UI-03 email compose interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-ui-03-email-compose.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-ui-03-email-compose\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("generates the AK-UI-05 plugin catalog interim fixture from the shared plugin catalog VM", () => {
|
|
const drill = uiMockVisual.buildPluginCatalogInterimVisualDrill();
|
|
|
|
assert.match(drill.module, /"designStatus": "interim"/);
|
|
assert.match(drill.module, /"screenRoute": "\/settings\/integrations\/plugins"/);
|
|
// The consent gate travels with the VM: a plugin with a missing grant folds
|
|
// the enable into the approval instead of exposing a plain enable button.
|
|
assert.match(drill.module, /"grantOp": "grantAndEnable"/);
|
|
assert.match(drill.module, /"hook:workflow\.executed"/);
|
|
assert.match(drill.html, /data-ui-mock-page="ak-ui-05-plugins-interim"/);
|
|
assert.match(drill.snapshot, /"pageId": "ak-ui-05-plugins-interim"/);
|
|
});
|
|
|
|
it("fails when the AK-UI-05 plugin catalog interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-ui-05-plugins.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-ui-05-plugins\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("fails when the R-P12b flow editor baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/r-p12b-editor.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /r-p12b-editor\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("fails when the R-P11a Spotlight interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/r-p11a-spotlight-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /r-p11a-spotlight-interim\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("fails when the R-P11b dashboard interim baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/r-p11b-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /r-p11b-interim\.html: interim baseline drifted/);
|
|
});
|
|
|
|
it("fails when the HTML visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/hot-swap.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /hot-swap\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the AK-P20 interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-p20-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-p20-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the VA-P24a interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/va-p24a-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /va-p24a-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the VA-P26a customer-portal interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/va-p26a-customer-portal-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /va-p26a-customer-portal-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the VA-P26b customer-portal interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/va-p26b-customer-portal-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /va-p26b-customer-portal-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the VA-P26c domain-admin interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/va-p26c-customer-portal-domain-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /va-p26c-customer-portal-domain-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the AK-P15a telephony interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-p15a-telephony-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-p15a-telephony-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the AK-P15b chat index interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-p15b-chat-index-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-p15b-chat-index-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("fails when the AK-P15b chat detail interim visual baseline drifts", () => {
|
|
const rootDir = createFixture();
|
|
uiMockVisual.writeUiMockVisualBaseline(rootDir);
|
|
fs.appendFileSync(path.join(rootDir, "packages/ui-mock/visual-baseline/ak-p15b-chat-detail-interim.html"), "\n<!-- drift -->\n", "utf8");
|
|
|
|
const result = uiMockVisual.verifyUiMockVisualBaseline(rootDir);
|
|
|
|
assert.match(result.failures.join("\n"), /ak-p15b-chat-detail-interim\.html: static artifact baseline drifted/);
|
|
});
|
|
|
|
it("blocks visual baseline update mode when CI is set", () => {
|
|
assert.throws(
|
|
() => uiMockVisual.assertVisualBaselineUpdateAllowed({ CI: "true" }),
|
|
/Refusing to update UI mock static artifact baselines while CI is set\./
|
|
);
|
|
});
|
|
});
|