mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* test(architecture): ratchet top-level layer direction and mutual imports Declare a layer for every top-level src/ directory and root source file (domain, contracts, foundation, providers, application, composition, adapters) in scripts/lib/module-layers.mjs. verify:module-boundaries now also builds the production import graph between those units and compares it with a counted baseline: 45 unit pairs import a higher layer (248 imports) and 23 unit pairs import each other. A new upward pair, more imports on a recorded pair, a new mutual import, or an undeclared unit fails; shrinkage is reported so the baseline can be lowered. * fix(architecture): enforce layers for isolated units
91 lines
3.1 KiB
TypeScript
91 lines
3.1 KiB
TypeScript
import { readdir } from "node:fs/promises";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
UNIT_LAYERS,
|
|
compareLayerBaseline,
|
|
layerGraphFacts,
|
|
sourceUnit,
|
|
} from "../../scripts/lib/module-layers.mjs";
|
|
|
|
const edge = (file: string, target: string) => ({ file, target });
|
|
|
|
describe("top-level module layers", () => {
|
|
it("declares a layer for every top-level source unit", async () => {
|
|
const entries = await readdir("src", { withFileTypes: true });
|
|
const units = entries
|
|
.filter(
|
|
(entry) =>
|
|
entry.isDirectory() ||
|
|
(entry.name.endsWith(".ts") &&
|
|
sourceUnit(`src/${entry.name}`) !== undefined),
|
|
)
|
|
.map(({ name }) => name);
|
|
expect(units.filter((unit) => UNIT_LAYERS[unit] === undefined)).toEqual([]);
|
|
});
|
|
|
|
it("maps files, root files and built specifiers to units, ignoring tests", () => {
|
|
expect(sourceUnit("src/native/elf/Reader.ts")).toBe("native");
|
|
expect(sourceUnit("src/native/elf/Reader.js")).toBe("native");
|
|
expect(sourceUnit("src/cli.js")).toBe("cli.ts");
|
|
expect(sourceUnit("src/native/elf/Reader.test.ts")).toBeUndefined();
|
|
expect(sourceUnit("tests/fixtures/x.ts")).toBeUndefined();
|
|
});
|
|
|
|
it("counts upward imports per unit pair and reports mutual imports", () => {
|
|
const facts = layerGraphFacts([
|
|
edge("src/native/A.ts", "src/application/B.js"),
|
|
edge("src/native/C.ts", "src/application/D.js"),
|
|
edge("src/application/B.ts", "src/native/A.js"),
|
|
edge("src/application/B.ts", "src/domain/E.js"),
|
|
edge("src/application/B.ts", "src/application/F.js"),
|
|
edge("src/native/A.test.ts", "src/cli/G.js"),
|
|
]);
|
|
expect(facts).toEqual({
|
|
undeclared: [],
|
|
upward: { "native -> application": 2 },
|
|
cycles: ["application <-> native"],
|
|
});
|
|
});
|
|
|
|
it("refuses an undeclared unit instead of leaving it unconstrained", () => {
|
|
expect(
|
|
layerGraphFacts([edge("src/newProvider/A.ts", "src/domain/B.js")])
|
|
.undeclared,
|
|
).toEqual(["newProvider"]);
|
|
expect(layerGraphFacts([], ["src/isolated/A.ts"]).undeclared).toEqual([
|
|
"isolated",
|
|
]);
|
|
expect(
|
|
layerGraphFacts(
|
|
[edge("src/isolated/A.ts", "src/isolated/B.js")],
|
|
["src/isolated/A.ts"],
|
|
).undeclared,
|
|
).toEqual(["isolated"]);
|
|
});
|
|
|
|
it("fails on growth and only reports shrinkage", () => {
|
|
const baseline = {
|
|
upward: { "native -> application": 2, "gdb -> application": 1 },
|
|
cycles: ["application <-> native", "application <-> gdb"],
|
|
};
|
|
const grown = compareLayerBaseline(
|
|
{
|
|
undeclared: [],
|
|
upward: { "native -> application": 3, "rizin -> application": 1 },
|
|
cycles: ["application <-> native", "application <-> rizin"],
|
|
},
|
|
baseline,
|
|
);
|
|
expect(grown.failures).toEqual([
|
|
"upward import native -> application: 3 imports, baseline 2",
|
|
"upward import rizin -> application: 1 imports, baseline 0",
|
|
"mutual import not in baseline: application <-> rizin",
|
|
]);
|
|
expect(grown.lowerable).toEqual([
|
|
"upward import gdb -> application: 0, baseline 1",
|
|
"recorded mutual import no longer present: application <-> gdb",
|
|
]);
|
|
});
|
|
});
|