📝 Record the test-helper home decision
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).
This commit is contained in:
1 parent
9e5522c131
commit
c51e32d6a6
1 file changed
+48
@@ -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
|
||||
|
||||
Reference in new issue
Block a user