/* RFC-0086 regression tests for the text-mode failure diagnostics printer. Exercises all four precedence shapes (diagnostics / violations / findings / details), the 50-line cap, and the empty/missing fallback paths. Do not assert on logger console output here — that is logger.ts territory. Fixtures stay inline and minimal so each case states its own setup. RFC-1311: Compass header sweep — declares the diagnostics suite's contract for test-file scaffold coverage. */ // @ai-invariant fixtures must not couple to internals beyond the public surface under test import { test, expect } from "vitest"; import { formatFailureDiagnostics } from "../runtime.ts"; test("formatFailureDiagnostics returns [] for non-object data", () => { expect(formatFailureDiagnostics(null)).toEqual([]); expect(formatFailureDiagnostics(undefined)).toEqual([]); expect(formatFailureDiagnostics(42)).toEqual([]); }); test("formatFailureDiagnostics returns [] when no recognized array is present", () => { expect(formatFailureDiagnostics({ ok: false })).toEqual([]); }); test("formatFailureDiagnostics: diagnostics[] (strings) wins over violations[]", () => { const lines = formatFailureDiagnostics({ diagnostics: ["first remediation line"], violations: ["should not be picked"], }); expect(lines).toEqual([" first remediation line"]); }); test("formatFailureDiagnostics: violations[] (objects) when diagnostics is absent", () => { const lines = formatFailureDiagnostics({ violations: [{ ruleId: "OS-09", file: "apps/foo/", message: "exists" }], }); expect(lines).toEqual([" OS-09 · apps/foo/ · exists"]); }); test("formatFailureDiagnostics: findings[] (RFC-0074 audit shape)", () => { const lines = formatFailureDiagnostics({ findings: [{ ruleId: "PV-01", severity: "error", file: "dist/missing", message: "not found" }], }); expect(lines).toEqual([" PV-01 · dist/missing · not found"]); }); test("formatFailureDiagnostics: details[] (generic shape)", () => { const lines = formatFailureDiagnostics({ details: [{ file: "packages/x.yaml", message: "stale" }], }); expect(lines).toEqual([" packages/x.yaml · stale"]); }); test("formatFailureDiagnostics: severity used when ruleId is missing", () => { const lines = formatFailureDiagnostics({ findings: [{ severity: "warn", message: "tone slip" }], }); expect(lines).toEqual([" warn · tone slip"]); }); test("formatFailureDiagnostics: RFC-0203 file:line:column locator", () => { const lines = formatFailureDiagnostics({ diagnostics: [ { ruleId: "KEL-01", severity: "error", file: "src/registry.ts", line: 472, column: 5, message: "drifted", }, ], }); expect(lines).toEqual([" KEL-01 · src/registry.ts:472:5 · drifted"]); }); test("formatFailureDiagnostics: RFC-0203 fixHint renders on its own indented line", () => { const lines = formatFailureDiagnostics({ diagnostics: [ { ruleId: "KEL-01", severity: "error", file: "src/registry.ts", line: 472, message: "drifted", fixHint: "run uni.registry.build", }, ], }); expect(lines).toEqual([ " KEL-01 · src/registry.ts:472 · drifted", " fix: run uni.registry.build", ]); }); test("formatFailureDiagnostics: RFC-0203 deterministic order (severity, file, line, ruleId)", () => { const lines = formatFailureDiagnostics({ diagnostics: [ { ruleId: "B", severity: "warning", file: "a.ts", line: 1, message: "w" }, { ruleId: "A", severity: "error", file: "z.ts", line: 9, message: "e2" }, { ruleId: "A", severity: "error", file: "a.ts", line: 2, message: "e1" }, ], }); expect(lines).toEqual([" A · a.ts:2 · e1", " A · z.ts:9 · e2", " B · a.ts:1 · w"]); }); test("formatFailureDiagnostics: multiline message keeps indent per line", () => { const lines = formatFailureDiagnostics({ diagnostics: ["line one\nline two"], }); expect(lines).toEqual([" line one", " line two"]); }); test("formatFailureDiagnostics: 50-line cap with truncation footer", () => { const many = Array.from({ length: 73 }, (_, i) => `item ${i + 1}`); const lines = formatFailureDiagnostics({ diagnostics: many }); expect(lines.length).toBe(51); expect(lines[0]).toBe(" item 1"); expect(lines[49]).toBe(" item 50"); expect(lines[50]).toBe(" … and 23 more (run with --json for the full list)"); }); test("formatFailureDiagnostics: exactly 50 items produces no footer", () => { const fifty = Array.from({ length: 50 }, (_, i) => `item ${i + 1}`); const lines = formatFailureDiagnostics({ diagnostics: fifty }); expect(lines.length).toBe(50); }); test("formatFailureDiagnostics: empty diagnostics array returns []", () => { expect(formatFailureDiagnostics({ diagnostics: [] })).toEqual([]); }); test("formatFailureDiagnostics: object without recognized fields falls back to JSON", () => { const lines = formatFailureDiagnostics({ details: [{ unknown: "shape" }], }); expect(lines).toEqual([' {"unknown":"shape"}']); }); test("formatFailureDiagnostics: errors[] (string[]) is recognized", () => { const lines = formatFailureDiagnostics({ errors: ["[metadata-write-failed] batch: File not found"], }); expect(lines).toEqual([" [metadata-write-failed] batch: File not found"]); }); test("formatFailureDiagnostics: errors[] wins over details[] but not over diagnostics[]", () => { const lines = formatFailureDiagnostics({ diagnostics: ["diag line"], errors: ["error line"], details: [{ file: "x", message: "detail" }], }); expect(lines).toEqual([" diag line"]); });