mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 21:07:15 +01:00
Surfaced by making ts_core and web_ui the first consumers:
- base({ project }) uses an explicit tsconfig list instead of the project
service. Both packages exclude tests from tsconfig.json so they never land
in dist/, and widen the include in tsconfig.lint.json for lint only; the
project service would have reported every test file as "not found".
- unicorn/no-barrel-files is off for src/index.ts: a package entry point IS
a barrel, that is its job. Stays on everywhere else.
- unicorn/no-keyword-prefix is off in the React layer: `className` is
React's prop name and fires on every styled element.
- pnpm-workspace.yaml: allowBuilds for unrs-resolver set to false explicitly
(pnpm 12 had written a placeholder); the resolver's prebuilt binding is
used, no install script runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
142 lines
5.7 KiB
JavaScript
142 lines
5.7 KiB
JavaScript
// The factories: what each option adds, and what a bad call refuses.
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { base, DEFAULT_IGNORES } from "../eslint/base.js";
|
|
import { boundaries, DEFAULT_ALLOW, folderElements } from "../eslint/boundaries.js";
|
|
import fast from "../eslint/fast.js";
|
|
import { defineConfig } from "../eslint/index.js";
|
|
|
|
const ROOT = import.meta.dirname;
|
|
|
|
/** Rule ids that appear anywhere in a flat config array. */
|
|
function ruleIds(config) {
|
|
return new Set(config.flatMap((entry) => Object.keys(entry.rules ?? {})));
|
|
}
|
|
|
|
/** Plugin names registered anywhere in a flat config array. */
|
|
function pluginNames(config) {
|
|
return new Set(config.flatMap((entry) => Object.keys(entry.plugins ?? {})));
|
|
}
|
|
|
|
describe("base()", () => {
|
|
it("refuses to run without a tsconfigRootDir", () => {
|
|
expect(() => base({})).toThrow(/tsconfigRootDir/);
|
|
expect(() => base({ tsconfigRootDir: "" })).toThrow(/tsconfigRootDir/);
|
|
});
|
|
|
|
it("prepends the default ignores and keeps the caller's", () => {
|
|
const [ignores] = base({ ignores: ["public"], tsconfigRootDir: ROOT });
|
|
expect(ignores.ignores).toEqual([...DEFAULT_IGNORES, "public"]);
|
|
});
|
|
|
|
it("wires the type-aware parser at the given root", () => {
|
|
const entry = base({ tsconfigRootDir: ROOT }).find(
|
|
(candidate) => candidate.languageOptions?.parserOptions?.projectService === true,
|
|
);
|
|
expect(entry?.languageOptions.parserOptions.tsconfigRootDir).toBe(ROOT);
|
|
});
|
|
|
|
it("uses an explicit project list instead of the project service when given", () => {
|
|
const entry = base({ project: ["./tsconfig.lint.json"], tsconfigRootDir: ROOT }).find(
|
|
(candidate) => candidate.languageOptions?.parserOptions?.project,
|
|
);
|
|
expect(entry.languageOptions.parserOptions).toEqual({
|
|
project: ["./tsconfig.lint.json"],
|
|
tsconfigRootDir: ROOT,
|
|
});
|
|
expect(entry.languageOptions.parserOptions.projectService).toBeUndefined();
|
|
});
|
|
|
|
it("drops the alias-only import ban when asked", () => {
|
|
expect(ruleIds(base({ tsconfigRootDir: ROOT }))).toContain("no-restricted-imports");
|
|
expect(ruleIds(base({ aliasOnly: false, tsconfigRootDir: ROOT }))).not.toContain(
|
|
"no-restricted-imports",
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("boundaries()", () => {
|
|
it("refuses an empty element list", () => {
|
|
expect(() => boundaries({ elements: [], tsconfigRootDir: ROOT })).toThrow(/elements/);
|
|
expect(() => boundaries({ tsconfigRootDir: ROOT })).toThrow(/elements/);
|
|
});
|
|
|
|
it("refuses to run without a tsconfigRootDir", () => {
|
|
expect(() => boundaries({ elements: folderElements(["utils"]) })).toThrow(/tsconfigRootDir/);
|
|
});
|
|
|
|
it("emits a policy only for element types the repo declared", () => {
|
|
const [entry] = boundaries({
|
|
elements: folderElements(["components", "services"]),
|
|
tsconfigRootDir: ROOT,
|
|
});
|
|
const { policies } = entry.rules["boundaries/dependencies"][1];
|
|
const froms = policies.filter((p) => p.from.element).map((p) => p.from.element.type);
|
|
expect(froms.toSorted((a, b) => a.localeCompare(b))).toEqual(["components", "services"]);
|
|
const components = policies.find((p) => p.from.element?.type === "components");
|
|
// utils/types are not declared here, so they are filtered out of the
|
|
// allow list rather than left dangling.
|
|
expect(components.allow.to.element.types.anyOf).toEqual(["services", "components"]);
|
|
});
|
|
|
|
it("merges a repo's allow override over one layer only", () => {
|
|
const [entry] = boundaries({
|
|
allow: { services: ["components"] },
|
|
elements: folderElements(["components", "services"]),
|
|
tsconfigRootDir: ROOT,
|
|
});
|
|
const { policies } = entry.rules["boundaries/dependencies"][1];
|
|
const services = policies.find((p) => p.from.element?.type === "services");
|
|
expect(services.allow.to.element.types.anyOf).toEqual(["components"]);
|
|
expect(DEFAULT_ALLOW.services).toEqual(["utils", "types", "services"]);
|
|
});
|
|
|
|
it("points the TS resolver at the repo root and marks tests", () => {
|
|
const [entry] = boundaries({
|
|
elements: folderElements(["utils"], "lib"),
|
|
testFiles: ["**/*.spec.ts"],
|
|
tsconfigRootDir: ROOT,
|
|
});
|
|
expect(entry.settings["boundaries/elements"]).toEqual([{ pattern: "lib/utils", type: "utils" }]);
|
|
expect(entry.settings["boundaries/files"]).toEqual([{ category: "test", pattern: ["**/*.spec.ts"] }]);
|
|
expect(entry.settings["import/resolver"].typescript.project).toBe(ROOT);
|
|
expect(entry.settings["boundaries/root-path"]).toBe(ROOT);
|
|
});
|
|
});
|
|
|
|
describe("defineConfig()", () => {
|
|
it("stacks nothing optional by default except vitest", () => {
|
|
const plugins = pluginNames(defineConfig({ tsconfigRootDir: ROOT }));
|
|
expect(plugins).toContain("vitest");
|
|
expect(plugins).not.toContain("boundaries");
|
|
expect(plugins).not.toContain("react-hooks");
|
|
expect(plugins).not.toContain("playwright");
|
|
});
|
|
|
|
it("stacks every layer when asked and appends overrides last", () => {
|
|
const override = { rules: { "unicorn/no-null": "error" } };
|
|
const config = defineConfig({
|
|
boundaries: { elements: folderElements(["components"]) },
|
|
overrides: [override],
|
|
playwright: true,
|
|
react: true,
|
|
tsconfigRootDir: ROOT,
|
|
vitest: false,
|
|
});
|
|
const plugins = pluginNames(config);
|
|
for (const name of ["boundaries", "react-hooks", "playwright", "jsx-a11y"]) {
|
|
expect(plugins).toContain(name);
|
|
}
|
|
expect(plugins).not.toContain("vitest");
|
|
expect(config.at(-1)).toBe(override);
|
|
});
|
|
});
|
|
|
|
describe("fast", () => {
|
|
it("is type-free: no projectService anywhere", () => {
|
|
const typeAware = fast.some((entry) => entry.languageOptions?.parserOptions?.projectService);
|
|
expect(typeAware).toBe(false);
|
|
expect(ruleIds(fast)).toContain("@typescript-eslint/no-explicit-any");
|
|
});
|
|
});
|