118 lines
3.5 KiB
JavaScript
118 lines
3.5 KiB
JavaScript
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));
|
|
},
|
|
);
|
|
});
|