docs/plans/2026-04-16-slate-v2-bun-test-migration-plan.md
Move /Users/zbeyens/git/slate-v2 off Jest and Mocha as far as possible.
Default target:
bun testFallback target:
vitest only for lanes Bun cannot carry honestlyUnchanged:
bun:test.Current root test graph:
test: test:bun && test:vitesttest:vitest: root Vitest call for slate-reactplaywrightpackages/slateCurrent runner:
Shape:
packages/slate/test/index.spec.ts owns the full fixture corpuspackages/slate/test/index.js is now a pure helper export for legacy fixture
importspackages/slate/test/support/with-test.js.tsx hyperscript fixturesnode:assert/strictutils/string.ts as the other direct Bun test fileexport const skip = true fixturespackages/slate*slate-hyperscript JSX instead of a local
test-utils wrapperslate-hyperscript factory importTarget:
packages/slate-historyCurrent runner:
Shape:
packages/slate-history/test/index.spec.ts owns the full fixture corpuspackages/slate-history/test/index.js is now a pure helper export for legacy
fixture imports.js and .tsx fixturesnode:assert/strictTarget:
packages/slate-hyperscriptCurrent runner:
bunfig.tomlShape:
/Users/zbeyens/git/slate-v2/packages/slate-hyperscript/test/index.spec.ts/Users/zbeyens/git/slate-v2/packages/slate-hyperscript/test/jsx.tsTarget:
packages/slate-domCurrent runner:
tsx --test test/**/*.tsCurrent repo has no active packages/slate-dom/test/** lane.
Target:
packages/slate-reactCurrent runner:
Shape:
test/bun/**test/*.vitest.{ts,tsx} lanefn/spyOn compatibility through runner setupreact-editor.vitest.tsx needed jsdom 20.0.3; Bun + Happy DOM and Vitest +
Happy DOM were not lossless on that focus rowTarget:
Add:
config/bun-test-setup.tsconfig/bun-test-globals.d.tsSetup responsibilities:
expect with Testing Library matchersglobalThis.jest.fnglobalThis.jest.spyOnglobalThis.jest.restoreAllMocksmockspyOncleanup() after each testWhy:
bun:test only in setup, not in every test file.describe, it, test, and expect without
importing runner APIs.Do not:
import { test } from 'bun:test' across test filesReplace or adapt:
support/fixtures.jsBun-compatible requirements:
describe(...) grouping.it..ts / .tsx / .js fixture modules through dynamic import(...).Skip handling:
export const skip = true, register it.skip(...)it(...)Reason:
this.skip() after require(...).Fixture import:
pathToFileURL(p).hrefinputruntestoutputoperationsHyperscript JSX:
/** @jsx jsx */ + jsx value patternTarget scripts:
Root:
{
"test": "bun test",
"test:bun": "bun test",
"test:integration": "playwright install --with-deps && run-p -r serve playwright",
"test:integration-local": "playwright install && run-p -r serve playwright"
}
Package scripts:
{
"test": "bun test"
}
Potential root split if needed:
{
"test": "pnpm test:bun && pnpm test:vitest",
"test:bun": "bun test",
"test:vitest": "vitest run"
}
Use the split only if a real Vitest lane survives.
Update test tsconfigs to include Bun globals:
types should include Bun test globals@testing-library/jest-dom types for React tests@types/jest and @types/mocha after the files no longer reference
those type packagesExpected final removals:
jestjest-environment-jsdomts-jestbabel-jestmocha@types/jest@types/mochaKeep only if still used elsewhere:
@babel/registerGoal:
Tasks:
support/fixtures.js to dynamic-import fixture modules..ts utility test.tsx hyperscript fixtureRecommended first checks:
bun test packages/slate/test/utils/string.ts
bun test packages/slate/test/index.js --test-name-pattern above
Acceptance:
slate, slate-history, slate-hyperscript To BunGoal:
scripts/run-from-root.mjs from core/support package tests.Tasks:
packages/slate/package.jsonpackages/slate-history/package.jsonpackages/slate-hyperscript/package.jsontest:mocha to test:bun:packages or delete it.Acceptance:
pnpm --filter slate test
pnpm --filter slate-history test
pnpm --filter slate-hyperscript test
All pass with Bun.
No test count may be silently reduced.
slate-dom To BunGoal:
slate-dom on Bun even though it currently has little/no local
test surface.Tasks:
packages/slate-dom/package.json test script to bun test.test should:
bun test test only after tests existRecommendation:
test until slate-dom/test/** existsslate-react On BunGoal:
Tasks:
imports / aliases if Bun supports enough locallytsconfig paths for Bun-compatible resolutionjest.fn() globally to Bun mock.@testing-library/jest-dom matchers through setup.Acceptance:
bun test packages/slate-react/test
All seven existing suites pass:
chunking.spec.tsdecorations.spec.tsxeditable.spec.tsxreact-editor.spec.tsxuse-selected.spec.tsxuse-slate-selector.spec.tsxuse-slate.spec.tsxNo suppressions:
slate-react Only If NeededUse Vitest only if Bun has a real blocker, such as:
Vitest setup shape, copied from Better Convex:
vitest.config.mtsslate-reactenvironment: 'happy-dom'setupFiles: ['./config/vitest.setup.ts']globals: trueslate -> packages/slate/src/index.tsslate-dom -> packages/slate-dom/src/index.tsslate-history -> packages/slate-history/src/index.tsslate-hyperscript -> packages/slate-hyperscript/src/index.tsslate-react -> packages/slate-react/src/index.tsSetup responsibilities:
expect.extend(matchers)afterEach(cleanup)globalThis.jest = { fn: vi.fn, spyOn: vi.spyOn }Vitest file naming:
*.spec.tsx and include only packages/slate-react/testDo not migrate other packages to Vitest.
Goal:
Tasks:
jest.config.js if no Vitest fallback is needed.scripts/run-from-root.mjs once no package script uses it.jestjest-environment-jsdomts-jestbabel-jestmocha@types/jest@types/mocha@testing-library/* only if React tests still use them.Acceptance:
rg -n "jest|mocha|run-from-root|ts-jest|babel-jest" /Users/zbeyens/git/slate-v2 \
-g '!node_modules' -g '!pnpm-lock.yaml' -g '!docs/**'
Expected after full Bun migration:
Expected if Vitest fallback remains:
Run after each phase:
pnpm lint
pnpm typecheck
pnpm test
Run after final phase:
pnpm build
pnpm test:integration-local
For React test migration:
pnpm --filter slate-react test
For fixture migration:
pnpm --filter slate test
pnpm --filter slate-history test
pnpm --filter slate-hyperscript test
Impact:
Response:
Impact:
Response:
export const skip = trueit.skip(...)Impact:
jest.fn().Response:
Impact:
slate-react DOM tests may become unreliable.Response:
slate-react to Vitest/happy-domImpact:
Response:
slate-react coverage rather than dropping the gateBest case:
Likely pragmatic state:
slate, slate-history, slate-hyperscript, slate-dom: Bunslate-react: Bun if Happy DOM is good enough; Vitest if DOM/coverage parity
blocksDo not keep Jest.
Do not keep Mocha.
Do not keep scripts/run-from-root.mjs after package scripts stop using it.
Keep migrated package tests under the package's existing test/** tree.
Use:
packages/<pkg>/test/bun/**Do not use:
packages/<pkg>/test-bun/**Date: 2026-04-16
This slice intentionally did not migrate all tests. It added minimal Bun setup scaffolding and ran one representative check per lane to prove or disprove the setup assumptions.
/Users/zbeyens/git/slate-v2/bunfig.toml/Users/zbeyens/git/slate-v2/config/bun-test-setup.ts/Users/zbeyens/git/slate-v2/config/bun-test-globals.d.ts/Users/zbeyens/git/slate-v2/support/fixtures.js/Users/zbeyens/git/slate-v2/config/bun-test-setup.ts
via mock.module(...):
slateslate-domslate-historyslate-hyperscriptslate-react@happy-dom/global-registrator@testing-library/jest-dom@testing-library/reactThe setup now carries the useful Plate pieces:
jest.fn / jest.spyOn compatibility globalsmock / spyOn globalsTextEncoder, DOMParser, MessageChannelHTMLElement.prototype.isContentEditableslate non-fixture utility laneCommand:
bun test ./packages/slate/test/utils/string.ts --test-name-pattern "getWordDistance - ltr"
Result:
bun:test imports needed in the test fileslate-react non-DOM logic laneCommand:
bun test ./packages/slate-react/test/chunking.spec.ts --test-name-pattern "returns flat tree for 1 child"
Result:
slate-react logic tests can run under Bunslate-react DOM laneCommand:
bun test ./packages/slate-react/test/use-slate.spec.tsx --test-name-pattern "tracks a global"
Result:
bun:testCommands:
bun test ./packages/slate/test/index.js --test-name-pattern "block-highest|root"
bun test ./packages/slate-history/test/index.js --test-name-pattern "basic|non-contiguous"
Result:
Observed failures:
withHistory(...).Meaning:
slate-hyperscript is now beyond the old fixture harness: one first-class
Bun spec plus a scoped preload transform keeps the package on the root Bun
config with no package-local bunfig.toml.slate and slate-history should follow the same direction instead of
keeping their dynamic fixture harnesses.Next implementation target:
/** @jsx jsx */test scriptsStill proceed, but in this order:
slate with the same scoped preload-transform patternslate-history the same wayslate-react Bun coverage file-by-fileDo not delete Jest/Mocha yet.
Date: 2026-04-16
The proof slice moved real tests, not copies, under each package's existing
test/** tree:
packages/slate/test/bun/**packages/slate-history/test/bun/**packages/slate-hyperscript/test/bun/**packages/slate-react/test/bun/**slate, slate-history, slate-hyperscript)Attempted moved-slice integration under a single root bunfig exposed a real
constraint:
Final state for this turn:
Conclusion:
bunfig is keptslate-reactMoved 10 tests by moving these suites:
editable.spec.tsxuse-slate.spec.tsxuse-slate-selector.spec.tsxThe remaining Jest suite ignores packages/slate-react/test/bun/**.
Command:
pnpm --filter slate-react run test:bun
Result:
test:bunslate-react test runs test:bun before the remaining Jest lanetest:jest delegates to pnpm --filter slate-react testslate-react/test/bun/** filesCommands passed:
pnpm test:bun
pnpm test
pnpm typecheck
pnpm lint
Root-only-bunfig proof:
bun test ./packages/slate-react/test/bun
The setup is now proven enough to continue in batches.
Next batch should move another 10-25 tests per package, not the whole tree.