- PriorityStepper labelled itself with channels.row.* and told every caller it orders the Feed; Combobox said plex.loading / plex.filter.moreRefine / sidebar.match on the feed sidebar. The generic strings moved to common.*, and what a priority MEANS arrives as the stepper's `hint` prop. uiI18n.contract.test.ts pins components/ui/ to the platform namespaces — the import boundary could never see an i18n key. - 32 px-frozen box utilities (w-[150px], max-w-[1600px], left-[18px], …) became spacing-scale steps (w-37.5, max-w-400, left-4.5): identical at the default text size, and now they grow with the Settings slider instead of clipping or re-wrapping their own labels. An eslint rule refuses the px form beside the older text-[Npx] one, pinned by pxBox.contract.test.ts; measured in the browser that every new step is generated at its old px. - chipLane's import-strip regex ran past a side-effect import into the next statement's `from`, hiding a lane between them; it now stops at the import's own `;`.
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
// The platform layer (`components/ui/`) may not import a feature component or a domain API slice —
|
|
// eslint's `no-restricted-imports` enforces that. It cannot see an i18n KEY, though, and two
|
|
// primitives spoke domain vocabulary through one: `PriorityStepper` labelled itself with
|
|
// `channels.row.*` (and told every caller that it orders the Feed), `Combobox` said `plex.loading`
|
|
// and `plex.filter.moreRefine` on the feed sidebar. A primitive's strings live in a platform
|
|
// namespace; what a value MEANS where it is used arrives as a prop.
|
|
const PLATFORM_NAMESPACES = new Set([
|
|
"common",
|
|
"errors",
|
|
"datatable",
|
|
"keys",
|
|
"palette",
|
|
"sidebar",
|
|
"notifications",
|
|
"time",
|
|
]);
|
|
|
|
const files = import.meta.glob("./**/*.{ts,tsx}", {
|
|
query: "?raw",
|
|
import: "default",
|
|
eager: true,
|
|
});
|
|
// `t("ns.…")` and `t(\`ns.…\`)` — the namespace is everything before the first dot.
|
|
const KEY = /\bt\(\s*["`]([A-Za-z]+)\./g;
|
|
|
|
describe("components/ui/ speaks only platform i18n namespaces", () => {
|
|
it("names no feature namespace", () => {
|
|
const sources = Object.entries(files).filter(([p]) => !/\.test\.tsx?$/.test(p));
|
|
expect(sources.length).toBeGreaterThan(20); // the glob matched the layer
|
|
const offenders = sources.flatMap(([p, src]) =>
|
|
[...String(src).matchAll(KEY)]
|
|
.map((m) => m[1]!)
|
|
.filter((ns) => !PLATFORM_NAMESPACES.has(ns))
|
|
.map((ns) => `${p}: ${ns}.*`),
|
|
);
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
it("sees a feature namespace when one is used", () => {
|
|
expect([...'t("plex.loading")'.matchAll(KEY)].map((m) => m[1])).toEqual(["plex"]);
|
|
});
|
|
});
|