import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { buildMarkdown, formatDelta, formatKb, indexBaseline, readReport } from "./render-bundle-budget-comment.mjs"; let tmpDir; beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "bundle-budget-render-")); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); describe("formatKb", () => { it("formats bytes as KB to 1 decimal", () => { expect(formatKb(2048)).toBe("2.0 KB"); expect(formatKb(1536)).toBe("1.5 KB"); }); it("returns dash for non-numeric", () => { expect(formatKb(undefined)).toBe("—"); }); }); describe("formatDelta", () => { it("returns signed KB delta vs baseline", () => { expect(formatDelta(3000, 1000)).toBe("+2.0 KB"); expect(formatDelta(1000, 3000)).toBe("-2.0 KB"); expect(formatDelta(1000, 1000)).toBe("+0.0 KB"); }); it("returns dash when no baseline", () => { expect(formatDelta(1000, undefined)).toBe("—"); }); }); describe("readReport", () => { it("returns null for missing path", () => { expect(readReport(path.join(tmpDir, "missing.json"))).toBeNull(); }); it("returns null for empty file", () => { const p = path.join(tmpDir, "empty.json"); fs.writeFileSync(p, ""); expect(readReport(p)).toBeNull(); }); it("parses valid json", () => { const p = path.join(tmpDir, "ok.json"); fs.writeFileSync(p, JSON.stringify({ a: 1 })); expect(readReport(p)).toEqual({ a: 1 }); }); }); describe("indexBaseline", () => { it("indexes by key", () => { const idx = indexBaseline({ entries: [ { key: "auth/login", gzipSize: 1000 }, { key: "vendor-react", gzipSize: 200 }, ], }); expect(idx.get("auth/login")?.gzipSize).toBe(1000); expect(idx.get("vendor-react")?.gzipSize).toBe(200); }); it("returns empty map for null", () => { expect(indexBaseline(null).size).toBe(0); }); }); describe("buildMarkdown", () => { it("includes marker for sticky-comment upsert", () => { const md = buildMarkdown({ skipped: true }); expect(md).toContain(""); }); it("renders summary + per-asset table sorted by severity", () => { const current = { skipped: false, totals: { ok: 1, warning: 1, exceeds: 1, unbudgeted: 0 }, entries: [ { key: "auth/login", kind: "route", gzipSize: 50 * 1024, status: "ok", limitKb: 100 }, { key: "app/$tenant/crm", kind: "route", gzipSize: 270 * 1024, status: "exceeds", limitKb: 250 }, { key: "vendor-react", kind: "vendor", gzipSize: 140 * 1024, status: "warning", limitKb: 150 }, ], }; const baseline = { entries: [ { key: "auth/login", gzipSize: 52 * 1024 }, { key: "app/$tenant/crm", gzipSize: 260 * 1024 }, ], }; const md = buildMarkdown(current, baseline); expect(md).toContain("exceeds=1"); expect(md).toContain("`app/$tenant/crm`"); expect(md).toContain("[EXCEED]"); // exceeds row appears before warning row const exceedIdx = md.indexOf("[EXCEED]"); const warnIdx = md.indexOf("[WARN]"); expect(exceedIdx).toBeLessThan(warnIdx); // delta is rendered for matched key (current 270KB vs baseline 260KB) expect(md).toContain("+10.0 KB"); // unmatched vendor-react has dash delta expect(md).toContain("| —"); }); it("handles a skipped current report gracefully", () => { const md = buildMarkdown({ skipped: true }); expect(md).toContain("No bundle artifacts measured"); }); });