Files
siftlode/frontend/src/components/ui/uiI18n.contract.test.ts
T
peter 8018cd4685 fix(ui): the platform layer speaks platform words; boxes scale with the text
- 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 `;`.
2026-09-19 07:46:33 +02:00

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"]);
});
});