/** * Tests for `check-registry-eject.ts`. * * Builds fixtures filesystems on disk under `mkdtemp` and exercises * the exported helpers (`audit`, `loadEntry`) directly. The script's * `main()` is exercised indirectly through these helpers — its * top-level behaviour (process.exit) is covered by the lint gate * itself running on real source. */ import { afterEach, beforeEach, describe, expect, it } from "@suaveplan/testing/runner"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { audit, loadEntry } from "./check-registry-eject.js"; let workDir: string; beforeEach(async () => { workDir = await mkdtemp(join(tmpdir(), "registry-eject-")); }); afterEach(async () => { await rm(workDir, { recursive: true, force: true }); }); async function writeJson(rel: string, body: unknown): Promise { const full = join(workDir, rel); await mkdir(join(full, ".."), { recursive: true }); await writeFile(full, JSON.stringify(body, null, 2), "utf8"); return full; } async function writeFixture(rel: string, content = ""): Promise { const full = join(workDir, rel); await mkdir(join(full, ".."), { recursive: true }); await writeFile(full, content, "utf8"); return full; } describe("loadEntry", () => { it("loads a valid registry entry", { timeout: 5000 }, async () => { const entry = await writeJson("button.json", { name: "@suaveplan/button", type: "registry:ui", files: [{ path: "button.tsx", type: "registry:ui" }], }); const result = await loadEntry(entry); expect("error" in result).toBe(false); }); it("rejects an entry without a name", { timeout: 5000 }, async () => { const entry = await writeJson("bad.json", { type: "registry:ui", files: [], }); const result = await loadEntry(entry); expect("error" in result).toBe(true); }); it("rejects an entry with non-array files", { timeout: 5000, }, async () => { const entry = await writeJson("bad.json", { name: "x", type: "registry:ui", files: "oops", }); const result = await loadEntry(entry); expect("error" in result).toBe(true); }); it("rejects malformed JSON", { timeout: 5000 }, async () => { const entry = await writeFixture("bad.json", "{ not json"); const result = await loadEntry(entry); expect("error" in result).toBe(true); }); }); describe("audit", () => { it("returns no diagnostics on a clean fixtures tree", { timeout: 5000, }, async () => { const componentsDir = join(workDir, "src/components"); await writeFixture("src/components/button/button.tsx", "export {}"); const entry = await writeJson("registry/r/button.json", { name: "@x/button", type: "registry:ui", files: [{ path: "button.tsx", type: "registry:ui" }], }); const diagnostics = await audit({ registryEntries: [entry], componentDirs: ["button"], componentsDir, }); expect(diagnostics).toEqual([]); }); it("flags a component directory with no registry entry", { timeout: 5000, }, async () => { const componentsDir = join(workDir, "src/components"); await writeFixture("src/components/button/button.tsx", "export {}"); const diagnostics = await audit({ registryEntries: [], componentDirs: ["button"], componentsDir, }); expect( diagnostics.find( (d) => d.kind === "MISSING_REGISTRY_ENTRY" )?.message ).toMatch(/button/); }); it("flags a registry entry whose source file is missing", { timeout: 5000, }, async () => { const componentsDir = join(workDir, "src/components"); await mkdir(join(componentsDir, "ghost"), { recursive: true }); const entry = await writeJson("registry/r/ghost.json", { name: "@x/ghost", type: "registry:ui", files: [{ path: "ghost.tsx", type: "registry:ui" }], }); const diagnostics = await audit({ registryEntries: [entry], componentDirs: ["ghost"], componentsDir, }); expect( diagnostics.find((d) => d.kind === "MISSING_SOURCE_FILE")?.message ).toMatch(/ghost\.tsx/); }); it("flags an orphan registry entry (no matching component dir)", { timeout: 5000, }, async () => { const componentsDir = join(workDir, "src/components"); await writeFixture( "src/components/button/button.tsx", "export {}" ); const entryButton = await writeJson("registry/r/button.json", { name: "@x/button", type: "registry:ui", files: [{ path: "button.tsx", type: "registry:ui" }], }); const entryStale = await writeJson("registry/r/removed.json", { name: "@x/removed", type: "registry:ui", files: [{ path: "removed.tsx", type: "registry:ui" }], }); const diagnostics = await audit({ registryEntries: [entryButton, entryStale], componentDirs: ["button"], componentsDir, }); expect( diagnostics.find( (d) => d.kind === "ORPHAN_REGISTRY_ENTRY" )?.message ).toMatch(/removed/); }); it("flags an entry with empty files[]", { timeout: 5000 }, async () => { const componentsDir = join(workDir, "src/components"); await writeFixture("src/components/foo/foo.tsx", "export {}"); const entry = await writeJson("registry/r/foo.json", { name: "@x/foo", type: "registry:ui", files: [], }); const diagnostics = await audit({ registryEntries: [entry], componentDirs: ["foo"], componentsDir, }); expect( diagnostics.find((d) => d.kind === "EMPTY_FILES_LIST")?.message ).toMatch(/foo\.json/); }); it("flags malformed JSON entries", { timeout: 5000 }, async () => { const componentsDir = join(workDir, "src/components"); await writeFixture("src/components/foo/foo.tsx", ""); const entry = await writeFixture( "registry/r/foo.json", "{ not json" ); const diagnostics = await audit({ registryEntries: [entry], componentDirs: ["foo"], componentsDir, }); expect( diagnostics.find((d) => d.kind === "MALFORMED_ENTRY")?.message ).toMatch(/foo\.json/); }); });