Skip to content

feat(effect-bun-test): carry bun's each, re-export Mock, refuse an Effect from the bare it — release 0.5.0 - #3

Merged
jfspencer merged 3 commits into
mainfrom
feat/each-and-mock-type
Aug 23, 2026
Merged

jfspencer merged 3 commits into
mainfrom
feat/each-and-mock-type

Conversation

@jfspencer

Copy link
Copy Markdown
Contributor

What

Three changes to @packages/effect-bun-test, each found and measured against a real consumer: a 615-file uniform-import sweep in the ultravisor repo, moving every test file off bun:test and onto this harness.

1. it.each was missing

The harness API object carried skip, only, skipIf, runIf, fails and for — but no each. Any consumer moving off bun:test silently lost every table-driven test.

The failure mode is the dangerous kind. bun raises the missing registrar as an "unhandled error between tests", which it does not count as a failure. Measured on the sweep: 106 tests vanished while one package still printed 0 fail. Exit codes and failure names both said the change was clean.

It is now delegated to bun's own each rather than reimplemented, so %s/%p name formatting, array-case spreading and the .skip/.only/.todo/.failing chain are bun's by construction and cannot drift. The delegation must be bound — an unbound test.each reference throws Expected this to be instanceof ScopeFunctions.

2. export type { Mock }

The values were re-exported; the type was not. That stranded any file typing a mock() result.

3. The bare it now refuses an Effect

TestCollectorCallable typed the callback's return as unknown. An Effect is not thenable, so one handed to the bare it is never run: the case passes with every assertion skipped and the test count does not move.

bun's own types reject this (void | Promise<unknown>). This harness's widening accepted it — so adopting the harness silently deleted a compile-time guard. It now matches bun.

Two facts worth keeping, both measured, both easy to undo by accident:

  • The union is the guard. Splitting void | Promise<unknown> into separate void and Promise<unknown> overloads re-opens the hole, because a bare void return type triggers TypeScript's void-assignability rule and accepts a callback returning anything. Only inside a union does that rule not apply. I shipped the overload form first and measured it failing to catch the probe.
  • Biome's noConfusingVoidType fires on exactly that union, so it carries a suppression. That suppression is the reason the guard exists — it is not tidy-up-able.

Verification

  • __tests__/bun-registrars.test.ts — red-on-repro proven: 0 pass / 1 fail / 1 error against the pre-change harness, 8 pass after.
  • __tests__/bare-it-rejects-effect.types.test.ts — a @ts-expect-error fixture, so a regression fails bun run tsc rather than going quiet.
  • bun run dod: 114 tests in effect-bun-test, 0 tsc errors, 0 biome findings.
  • Against the consumer: with this build installed, all 615 converted files typecheck at 0 errors (was 19, all Property 'each' does not exist on type 'Methods<never>'), and lose no test and no executed expect() call.

Also in this PR

packages/ddd-meta/__tests__/write-totality.test.ts carried two missedPipeableOpportunity errors at error severity. Those fail bun run lint:effect and therefore bun run dod — the exact command the release workflow gates on — so the release could not run while they were red. Converted to the pipe form the linter prescribes. Unrelated to the harness change.

One thing I could not verify locally

bun run dod is red on my machine in packages/ddd-meta and packages/ddd-path, with three failures that all pin bun 1.3.14's TOML parser message ("Unexpected =") against bun 1.4.0's ("TOML Parse error: Expected a value but found '='"). This repo pins bun@1.3.14 in packageManager and both workflows install 1.3.14, so CI exercises the pinned parser and this local red should not reproduce here. Everything bun-version-independent passes locally: tsc, lint:effect, check:ci, and all 114 effect-bun-test tests.

Please confirm CI is green before tagging v0.5.0 — the release workflow runs bun run dod, so a red tag produces no release.

Release

All seven manifests are at 0.5.0 and bun scripts/assert-tag-version.ts v0.5.0 passes. Tagging v0.5.0 on this commit once merged will trigger the release workflow.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BoLJY2Y4UejKpsN8ogVaip

jfspencer and others added 3 commits August 22, 2026 20:52
…se an Effect from the bare `it`

Three changes, each measured against a real consumer (a 615-file uniform-import sweep in the
ultravisor repo).

`it.each` was MISSING. The harness API object carried `skip`, `only`, `skipIf`, `runIf`, `fails`
and `for`, but no `each`, so a consumer moving off `bun:test` lost every table-driven test. The
failure mode is silent: bun raises the missing registrar as an "unhandled error between tests",
which it does NOT count as a failure. Measured on the sweep, 106 tests vanished while one package
still reported "0 fail".

It is delegated to bun's own `each` rather than reimplemented, so `%s`/`%p` formatting, array-case
spreading and the `.skip`/`.only`/`.todo`/`.failing` chain are bun's by construction and cannot
drift. The delegation must be BOUND — an unbound `test.each` reference throws "Expected this to be
instanceof ScopeFunctions".

`export type { Mock }` — the values were re-exported but the type was not, which stranded any file
typing a `mock()` result.

`TestCollectorCallable` now types the callback's return as bun's own `void | Promise<unknown>`
instead of `unknown`. An Effect is not thenable, so one returned from the bare `it` is never run:
the case passes with every assertion skipped and the test count does not move. bun's types reject
that; this harness's widening had accepted it, which meant adopting the harness silently DELETED a
compile-time guard. Now it errors.

The union is load-bearing: splitting it into `void` and `Promise<unknown>` overloads re-opens the
hole, because a bare `void` return type triggers TypeScript's void-assignability rule and accepts a
callback returning anything. Biome's noConfusingVoidType fires on the union, so it carries a
suppression — that suppression is the reason the guard exists.

Guarded by __tests__/bun-registrars.test.ts (red-on-repro: 0 pass / 1 fail / 1 error before, 8 pass
after) and __tests__/bare-it-rejects-effect.types.test.ts, a @ts-expect-error fixture that fails
`bun run tsc` if the narrowing regresses.

`bun run dod` green: 114 tests in effect-bun-test, 0 tsc errors, 0 biome findings. With this build
installed, the consumer's 615 converted files typecheck at 0 errors and lose no test and no
executed expect() call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoLJY2Y4UejKpsN8ogVaip
`bun scripts/set-version.ts 0.5.0` across all seven manifests; assert-tag-version v0.5.0 passes.

packages/ddd-meta/__tests__/write-totality.test.ts carried two `missedPipeableOpportunity`
errors at error severity, which fail `bun run lint:effect` and therefore `bun run dod` — the
exact command the release workflow gates on. Converted to the pipe form the linter prescribes.
Unrelated to the harness change; fixed here only because the release cannot run while it is red.

NOT VERIFIED LOCALLY, and the reason is recorded rather than papered over: `bun run dod` is red
on this machine in packages/ddd-meta and packages/ddd-path, with three failures that all pin
bun 1.3.14's TOML parser message ("Unexpected =") against bun 1.4.0's ("TOML Parse error:
Expected a value but found '='"). This repo pins bun@1.3.14 in packageManager and both workflows
install 1.3.14, so CI exercises the pinned parser and this local red does not reproduce there.
Every check that IS bun-version-independent passes locally: tsc, lint:effect, check:ci, and all
114 effect-bun-test tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoLJY2Y4UejKpsN8ogVaip
`useTemplate` at __tests__/host-validity.test.ts:112 (a `.padEnd(77) + '#'` concatenation) and
`noShadowRestrictedNames` at __tests__/write-totality.test.ts:30 (a local named `valueOf`,
renamed `frontMatterOf`).

Both fail `bun run check:ci`, which `bun run dod` runs and which the release workflow gates on,
so 0.5.0 cannot be cut while they are red. Neither is reachable from a local `bun run dod` on
bun 1.4.0, because the run aborts at `test:unit:once` first — CI at the pinned 1.3.14 gets past
that point and reports them. Unrelated to the harness change.

check:ci, tsc and lint:effect all exit 0 locally after this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoLJY2Y4UejKpsN8ogVaip
@jfspencer
jfspencer merged commit 6f43764 into main Aug 23, 2026
2 checks passed
@jfspencer
jfspencer deleted the feat/each-and-mock-type branch August 23, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant