import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; const scriptsDirectory = dirname(fileURLToPath(import.meta.url)); const projectDirectory = dirname(scriptsDirectory); const dependencyCruiser = join( projectDirectory, "node_modules/dependency-cruiser/bin/dependency-cruise.mjs", ); const dependencyConfig = join(projectDirectory, "dependency-cruiser.cjs"); async function withFixture(files, run) { const fixtureDirectory = await mkdtemp(join(tmpdir(), "wealth-ledger-deps-")); try { await Promise.all( Object.entries(files).map(async ([relativePath, contents]) => { const filePath = join(fixtureDirectory, relativePath); await mkdir(dirname(filePath), { recursive: true }); await writeFile(filePath, contents); }), ); return await run(fixtureDirectory); } finally { await rm(fixtureDirectory, { recursive: true, force: true }); } } function cruise(fixtureDirectory) { return spawnSync( process.execPath, [ dependencyCruiser, "src", "--config", dependencyConfig, "--output-type", "err-long", ], { cwd: fixtureDirectory, encoding: "utf8" }, ); } function output(result) { return `${result.stdout}${result.stderr}`; } test("rejects a source import cycle", async () => { await withFixture( { "tsconfig.app.json": JSON.stringify({ compilerOptions: {} }), "src/shared/i18n/index.ts": 'export { value } from "./zod";\n', "src/shared/i18n/zod.ts": 'export { value } from ".";\n', }, (fixtureDirectory) => { const result = cruise(fixtureDirectory); assert.notEqual(result.status, 0, output(result)); assert.match(output(result), /no-circular/); }, ); }); test("rejects a deep import into another slice", async () => { await withFixture( { "tsconfig.app.json": JSON.stringify({ compilerOptions: {} }), "src/features/auth/ui/Form.ts": 'import "../../../shared/i18n/zod";\n', "src/shared/i18n/index.ts": "export {};\n", "src/shared/i18n/zod.ts": "export {};\n", }, (fixtureDirectory) => { const result = cruise(fixtureDirectory); assert.notEqual(result.status, 0, output(result)); assert.match(output(result), /slices-must-use-public-api/); }, ); }); test("rejects an index.tsx import from another slice", async () => { await withFixture( { "tsconfig.app.json": JSON.stringify({ compilerOptions: {} }), "src/features/auth/ui/Form.ts": 'import "../../../shared/i18n";\n', "src/shared/i18n/index.tsx": "export {};\n", }, (fixtureDirectory) => { const result = cruise(fixtureDirectory); assert.notEqual(result.status, 0, output(result)); assert.match(output(result), /slices-must-use-public-api/); }, ); }); test("allows imports within a slice and through another slice public API", async () => { await withFixture( { "tsconfig.app.json": JSON.stringify({ compilerOptions: {} }), "src/features/auth/model/local.ts": "export const local = true;\n", "src/features/auth/ui/Form.ts": [ 'import { local } from "../model/local";\n', 'import { value } from "../../../shared/i18n";\n', "void local;\n", "void value;\n", ].join(""), "src/shared/i18n/index.ts": 'export { value } from "./zod";\n', "src/shared/i18n/zod.ts": "export const value = true;\n", }, (fixtureDirectory) => { const result = cruise(fixtureDirectory); assert.equal(result.status, 0, output(result)); }, ); });