From c51e32d6a68425cc95810d860662e7210115fc84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 17 Sep 2026 13:23:29 +0000 Subject: [PATCH] :memo: Record the test-helper home decision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit development/testing.md § Test helpers: why name beats path under import/no-relative-parent-imports, why the key needs `#`, why the `__tests__` folder name, and what was rejected (flat placement, tsconfig paths, rule exemptions). --- development/testing.md | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/development/testing.md b/development/testing.md index 5c674d5..7b6629e 100644 --- a/development/testing.md +++ b/development/testing.md @@ -63,6 +63,54 @@ separated by a blank line; an empty block drops its label. - Unlabeled ordering (bare blank lines): the seams still have to be found by reading; the labels cost nothing. +## Test helpers + +The rule is enforced by `import/no-relative-parent-imports`; this section records +why the mechanism is shaped like this. + +#### Decision (2026-09) + +A shared test helper — code that scattered `*.test.ts` files import to do their +testing — lives under `src/util/__tests__/` and is addressed by the +`#test-utils/…` self-reference (`package.json#imports`: +`"#test-utils/*": "./src/util/__tests__/*"`), never by a relative path: + +```ts +import { LspSession } from "#test-utils/lsp-completion.ts"; +``` + +#### Why + +- The lint rule bans upward (`../`) imports, and a cross-cutting helper can + always be placed above _some_ scattered consumer, wherever it goes. Name + beats path: a `#test-utils/…` specifier has no direction, so the rule never + fires and file moves only touch the one mapping in `package.json`. +- `#…` is Node's reserved prefix for _private_ subpath imports: publishing + `package.json` leaks nothing and resolves nothing for consumers. +- `__tests__` as the folder name is not about tests living there; it is the + directory pattern `tsconfig.build.json` already excludes, so a helper can + never be emitted into `dist/` and shipped by accident. +- Node's own resolver handles `#…` under `--strip-types`, and `tsc` resolves it + via the same `imports` field — one mechanism for runtime and type gate, no + loader needed. +- The helper uses `node:` builtins (it drives a language server), so + `import/no-nodejs-modules` is off for `src/util/__tests__/**` in + `.oxlintrc.json` — the same exception the old `scripts/**` scope carried. + +#### Rejected + +- A helper beside the tests (`src/util/test.ts`, flat `src/`): composes only + while `src/` stays flat; the first nested test reaching it reintroduces the + banned upward import. +- Bare `~/…` specifier: not valid in `imports` (keys must start with `#`) — + resolution fails at runtime with `ERR_MODULE_NOT_FOUND`. A `#~/…` “home” + shorthand was dropped in review; `#test-utils/…` states what it is. +- tsconfig `paths` alias: resolves for `tsc` but not for plain + `node --test --strip-types` (no loader hook), breaking the fast tier. +- Turning `import/no-relative-parent-imports` off for test files: the rule + still guards non-test helpers importing each other, and the exemption is + only needed for the one specifier the mapping already solves cleanly. + ## Known issues - The type-aware linter misidentifies `expectTypeOf()` as a floating promise, so