/** * Parity gate for the shared graph-style lib. Asserts the small set of * load-bearing fixtures every UI and plugin caller relies on. Richer * coverage lives in the UI's existing tests under * platform/ui/app/lib/__tests__/graph-labels.test.ts and * platform/ui/app/graph/__tests__/*; those tests now exercise the * re-exported symbols and pass unchanged if extraction was faithful. */ import { describe, it, expect } from "vitest"; import { GRAPH_LABEL_COLOURS, FALLBACK_LABEL_COLOUR, pickDisplayLabel, resolveNodeColour, pickShortLabel, pickDisplayName, EXTERNAL_LABEL_SHAPES, FALLBACK_LABEL_SHAPE, shapeForLabel, } from "../index.js"; describe("palette invariants", () => { it("registers every label family with a hex colour", () => { for (const [label, colour] of Object.entries(GRAPH_LABEL_COLOURS)) { expect(colour, `${label} colour`).toMatch(/^#[0-9A-Fa-f]{6}$/); } }); it("FALLBACK_LABEL_COLOUR is a hex", () => { expect(FALLBACK_LABEL_COLOUR).toMatch(/^#[0-9A-Fa-f]{6}$/); }); it("UserMessage and Preference deliberately share a hue (Person-family cue)", () => { expect(GRAPH_LABEL_COLOURS.UserMessage).toBe(GRAPH_LABEL_COLOURS.Preference); }); it("registers CashEntry as its own hue, distinct from the invoice labels", () => { expect(GRAPH_LABEL_COLOURS.CashEntry).toBe("#7D5A2B"); expect(GRAPH_LABEL_COLOURS.CashEntry).not.toBe(GRAPH_LABEL_COLOURS.Invoice); expect(GRAPH_LABEL_COLOURS.CashEntry).not.toBe(GRAPH_LABEL_COLOURS.InboundInvoice); }); }); describe("shape registry", () => { // The seven vis-network shapes that render the caption OUTSIDE the node. const INTERNAL_LABEL_SHAPES = ["box", "circle", "ellipse", "database", "text"]; it("EXTERNAL_LABEL_SHAPES holds only outside-caption shapes, pairwise-unique", () => { expect(EXTERNAL_LABEL_SHAPES.length).toBeGreaterThanOrEqual(5); expect(new Set(EXTERNAL_LABEL_SHAPES).size).toBe(EXTERNAL_LABEL_SHAPES.length); for (const s of EXTERNAL_LABEL_SHAPES) { expect(INTERNAL_LABEL_SHAPES, `${s} must not be an internal-label shape`).not.toContain(s); } }); it("every registered label maps to an allowed external-label shape", () => { for (const label of Object.keys(GRAPH_LABEL_COLOURS)) { expect(EXTERNAL_LABEL_SHAPES as readonly string[], `${label} shape`).toContain( shapeForLabel(label), ); } }); it("FALLBACK_LABEL_SHAPE is an allowed shape and used for unknown labels", () => { expect(EXTERNAL_LABEL_SHAPES as readonly string[]).toContain(FALLBACK_LABEL_SHAPE); expect(shapeForLabel("NoSuchLabel")).toBe(FALLBACK_LABEL_SHAPE); }); it("assignment is deterministic", () => { expect(shapeForLabel("Person")).toBe(shapeForLabel("Person")); }); it("spreads shapes within a colour family (People band is pairwise-distinct)", () => { const people = ["Person", "UserProfile", "Preference", "AdminUser", "AccessGrant"]; const shapes = people.map(shapeForLabel); expect(new Set(shapes).size, `People shapes ${shapes.join(",")}`).toBe(people.length); }); }); describe("pickDisplayLabel", () => { it("returns the first registered sublabel", () => { expect(pickDisplayLabel(["Conversation", "AdminConversation"])).toBe( "AdminConversation", ); }); it("falls back to labels[0] when no sublabel is registered", () => { expect(pickDisplayLabel(["Person", "UnregisteredSublabel"])).toBe("Person"); }); it("returns null on empty labels", () => { expect(pickDisplayLabel([])).toBeNull(); }); }); describe("resolveNodeColour", () => { it("counts drift candidates", () => { const r = resolveNodeColour(["Conversation", "PublicConversation", "AssistantMessage"]); expect(r.driftCandidates).toBe(2); expect(r.colour).toBe(GRAPH_LABEL_COLOURS.PublicConversation); }); }); describe("pickShortLabel", () => { it("Person composes from given+family", () => { expect( pickShortLabel({ id: "x", labels: ["Person"], properties: { givenName: "Dan", familyName: "Brett" }, }), ).toBe("Dan Brett"); }); it("Message renders role + HH:mm:ss", () => { expect( pickShortLabel({ id: "x", labels: ["Message"], properties: { role: "user", createdAt: "2026-05-25T14:33:42Z" }, }), ).toBe("user 14:33:42"); }); it("Conversation falls back to Conv ", () => { expect( pickShortLabel({ id: "x", labels: ["Conversation"], properties: { sessionId: "abcdef0123456789" }, }), ).toBe("Conv abcdef01"); }); it("truncates over-cap labels to 24 chars + ellipsis", () => { const longName = "This is a very long string that exceeds twenty-four chars"; const out = pickShortLabel({ id: "x", labels: ["LocalBusiness"], properties: { name: longName }, }); expect(out.length).toBeLessThanOrEqual(25); // 24 + '…' expect(out.endsWith("…")).toBe(true); }); }); describe("pickDisplayName", () => { it("untruncated counterpart to pickShortLabel", () => { const longName = "This is a very long string that exceeds twenty-four chars"; expect( pickDisplayName({ id: "x", labels: ["LocalBusiness"], properties: { name: longName }, }), ).toBe(longName); }); }); describe("pickShortLabel — Job", () => { it("composes client + #jobId", () => { expect( pickShortLabel({ id: "x", labels: ["Job"], properties: { client: "Mr R French", jobId: "1842" }, }), ).toBe("Mr R French #1842"); }); it("client only", () => { expect( pickShortLabel({ id: "x", labels: ["Job"], properties: { client: "Mr R French" } }), ).toBe("Mr R French"); }); it("jobId only", () => { expect( pickShortLabel({ id: "x", labels: ["Job"], properties: { jobId: "1842" } }), ).toBe("#1842"); }); it("neither falls back to the label", () => { expect( pickShortLabel({ id: "x", labels: ["Job"], properties: { status: "quoting" } }), ).toBe("Job"); }); it("truncates an over-cap composition to 24 chars + ellipsis", () => { const out = pickShortLabel({ id: "x", labels: ["Job"], properties: { client: "Construction Client With An Extremely Long Trading Name Ltd", jobId: "1842" }, }); expect(out.length).toBeLessThanOrEqual(25); expect(out.endsWith("…")).toBe(true); }); }); describe("pickDisplayName — Job", () => { it("returns the untruncated composition", () => { expect( pickDisplayName({ id: "x", labels: ["Job"], properties: { client: "Construction Client With An Extremely Long Trading Name Ltd", jobId: "1842" }, }), ).toBe("Construction Client With An Extremely Long Trading Name Ltd #1842"); }); it("Job with neither falls back to the label", () => { expect( pickDisplayName({ id: "x", labels: ["Job"], properties: { status: "quoting" } }), ).toBe("Job"); }); }); describe("pickShortLabel — Quote", () => { it("renders the ref document number", () => { expect( pickShortLabel({ id: "x", labels: ["Quote"], properties: { ref: "Q-1042" } }), ).toBe("Q-1042"); }); it("falls back to the label when ref is absent", () => { expect( pickShortLabel({ id: "x", labels: ["Quote"], properties: { status: "draft" } }), ).toBe("Quote"); }); it("truncates an over-cap ref to 24 chars + ellipsis", () => { const out = pickShortLabel({ id: "x", labels: ["Quote"], properties: { ref: "Q-this-quote-reference-is-far-too-long-to-fit" }, }); expect(out.length).toBeLessThanOrEqual(25); expect(out.endsWith("…")).toBe(true); }); }); describe("pickDisplayName — Quote", () => { it("returns the untruncated ref", () => { expect( pickDisplayName({ id: "x", labels: ["Quote"], properties: { ref: "Q-this-quote-reference-is-far-too-long-to-fit" }, }), ).toBe("Q-this-quote-reference-is-far-too-long-to-fit"); }); it("falls back to the label when ref is absent", () => { expect( pickDisplayName({ id: "x", labels: ["Quote"], properties: { status: "draft" } }), ).toBe("Quote"); }); }); describe("pickShortLabel — InboundInvoice", () => { it("composes supplier + #confirmationNumber", () => { expect( pickShortLabel({ id: "x", labels: ["InboundInvoice"], properties: { supplier: "Travis Perkins", confirmationNumber: "88123" }, }), ).toBe("Travis Perkins #88123"); }); it("supplier only", () => { expect( pickShortLabel({ id: "x", labels: ["InboundInvoice"], properties: { supplier: "Travis Perkins" } }), ).toBe("Travis Perkins"); }); it("confirmationNumber only", () => { expect( pickShortLabel({ id: "x", labels: ["InboundInvoice"], properties: { confirmationNumber: "88123" } }), ).toBe("#88123"); }); it("neither falls back to the label", () => { expect( pickShortLabel({ id: "x", labels: ["InboundInvoice"], properties: { status: "pending" } }), ).toBe("InboundInvoice"); }); it("truncates an over-cap composition to 24 chars + ellipsis", () => { const out = pickShortLabel({ id: "x", labels: ["InboundInvoice"], properties: { supplier: "Builders Merchant With An Extremely Long Trading Name Ltd", confirmationNumber: "88123" }, }); expect(out.length).toBeLessThanOrEqual(25); expect(out.endsWith("…")).toBe(true); }); }); describe("pickDisplayName — InboundInvoice", () => { it("returns the untruncated composition", () => { expect( pickDisplayName({ id: "x", labels: ["InboundInvoice"], properties: { supplier: "Builders Merchant With An Extremely Long Trading Name Ltd", confirmationNumber: "88123" }, }), ).toBe("Builders Merchant With An Extremely Long Trading Name Ltd #88123"); }); it("neither falls back to the label", () => { expect( pickDisplayName({ id: "x", labels: ["InboundInvoice"], properties: { status: "pending" } }), ).toBe("InboundInvoice"); }); }); describe("pickShortLabel — WhatsAppGroup", () => { it("renders clientName", () => { expect( pickShortLabel({ id: "x", labels: ["WhatsAppGroup"], properties: { clientName: "Mr R French", groupId: "123@g.us" } }), ).toBe("Mr R French"); }); it("falls back to the label when clientName is absent (groupId is not used for display)", () => { expect( pickShortLabel({ id: "x", labels: ["WhatsAppGroup"], properties: { groupId: "123@g.us" } }), ).toBe("WhatsAppGroup"); }); it("truncates an over-cap clientName to 24 chars + ellipsis", () => { const out = pickShortLabel({ id: "x", labels: ["WhatsAppGroup"], properties: { clientName: "Construction Client With An Extremely Long Trading Name Ltd" }, }); expect(out.length).toBeLessThanOrEqual(25); expect(out.endsWith("…")).toBe(true); }); }); describe("pickDisplayName — WhatsAppGroup", () => { it("returns the untruncated clientName", () => { expect( pickDisplayName({ id: "x", labels: ["WhatsAppGroup"], properties: { clientName: "Construction Client With An Extremely Long Trading Name Ltd" }, }), ).toBe("Construction Client With An Extremely Long Trading Name Ltd"); }); it("falls back to the label when clientName is absent", () => { expect( pickDisplayName({ id: "x", labels: ["WhatsAppGroup"], properties: { groupId: "123@g.us" } }), ).toBe("WhatsAppGroup"); }); }); describe("regression — Job and generic labels unaffected by the new branches", () => { it("Job still composes client + #jobId", () => { expect( pickShortLabel({ id: "x", labels: ["Job"], properties: { client: "Mr R French", jobId: "1842" } }), ).toBe("Mr R French #1842"); }); it("Person still composes given+family", () => { expect( pickShortLabel({ id: "x", labels: ["Person"], properties: { givenName: "Dan", familyName: "Brett" } }), ).toBe("Dan Brett"); }); it("generic name-bearing node unaffected", () => { expect( pickShortLabel({ id: "x", labels: ["LocalBusiness"], properties: { name: "Acme" } }), ).toBe("Acme"); }); });