testing
Installation
SKILL.md
Testing Goal
Push the suite toward three layers only:
- Pure unit tests for deterministic logic.
- Thin editor or plugin contract tests for real Plate or Slate wiring.
- Golden input or output tests for serializer and parser behavior.
Hard constraints:
- Bun-first and speed-first.
bun run testis the default iterative workflow. pnpm test:allis the full end-of-task and CI run. Do not use it as the default inner-loop command.- Keep the default iterative suite fast.
- No browser or e2e coverage in this program.
- Coverage is hotspot telemetry, not a vanity target. Do not chase repo-wide numbers blindly.
- Use coverage after each phase only to choose the next hotspot.
- No one-smoke-test-per-package sweep.
- Do not add broad smoke coverage for thin wrapper packages.
Coverage Strategy
- Coverage is for regression detection during breaking changes and rearchitecture, not for winning a percentage contest.
- Rank files from fresh
lcov. Trust file order more than package totals. - Work in passes:
- high-value contract pass: do every honest file with score
>= 6 - medium-value follow-up: rerun coverage, then do worthwhile
>= 5files while skipping crumbs, wrappers, and sludge - architecture-safety pass: stop following coverage blindly and harden the contracts you most refuse to break
- high-value contract pass: do every honest file with score
- File-first beats package sweeps once the obvious packages are already covered.
- Good architecture-safety targets:
- plugin resolution and composition
- normalization contracts
- parser and serializer behavior
- structural transforms and merge helpers
- history, diff, and change-tracking behavior
- public editor invariants
/reactis not permanently excluded. Exclude it only when the current pass explicitly says so.- React work should wait until non-React seams are exhausted only when that is the active phase goal, not because the skill hardcodes it forever.
- Stop when the remaining misses are mostly:
- thin wrappers
- DOM-only or provider-only seams
- giant low-ROI sludge files
- tiny uncovered crumbs
- code likely to be deleted or rewritten soon
Core Rules
- Assert public behavior through editor APIs, plugin APIs, hooks, transforms, or rendered output. Do not assert private state, call order, or implementation detail when public behavior already proves the contract.
- Prefer file-ranked batches over package sweeps. Package sweeps are for early broad passes, not the endgame.
- Do not use coverage to justify testing files with no meaningful contract.
- Bun globals come from
tooling/config/global.d.ts. Do not importdescribe,it,expect,mock,spyOn, or other globals frombun:test. - Use
*.spec.ts[x]for the fast lane and*.slow.ts[x]for the slow lane. - Keep helpers package-local. Never import a helper from another spec file.
- No spec should import another spec.
- Put compile-only type contracts in
type-tests/, not mixed into runtime specs. - Titles should describe behavior semantically, not echo raw option names.
- Prefer explicit assertions over snapshots by default.
- Delete skipped tests, commented-out tests, and dead placeholder cases. Do not preserve wishful thinking.
- No fake smoke tests.
- No app-registry imports in package tests.
- Do not add package
devDependenciesjust to support cross-package or app-shaped test setups. If a test needs other package kits, app aliases, or multi-package wiring, move it toapps/www/src/__tests__/package-integration. - When adding tests, inspect fast-suite outliers with
bun run test:profile.bun run test:slowestis the hard gate. If a fast-suite spec crosses the thresholds intooling/config/test-suites.mjs, rename it to*.slow.ts[x]. - Do not trust a fast laptop. Treat the local warning zone as real debt before CI proves you wrong:
60ms/testor120ms/fileis already a move candidate, especially for React-heavy specs. - For borderline cases, run
pnpm test:slowest -- --top 25 --rerun-each 3and move repeat offenders before they drift over the CI line. - Treat known third-party resource logs and serializer fallback warnings as test noise. Suppress them narrowly in the shared Bun setup at
tooling/config/bunTestSetup.ts, not by changing runtime code or sprinkling per-spec console mocks.
Seam Selection
- Use
createEditorfrom@platejs/slatefor pure Slate query, transform, interface, and history contracts. - Use
createSlateEditorfor non-React plugin or editor wiring:- plugin option stores
- selector extension
- pure plugin API composition
- pure transform composition
- parser and deserializer contracts
- HTML
insertData - DnD-style contracts
- Use
createPlateEditoronly when the contract is genuinely Plate-specific. - Use rendered React tests only when the contract is genuinely React-specific: hooks, providers, stores, DOM behavior, or rerender semantics.
- Remaining
createPlateEditorusage is a reviewed allowlist, not a future cleanup queue.
File Organization
- File-scoped specs live beside the implementation.
- Keep
__tests__/only for:- package-local helpers
- fixture banks
- intentionally split multi-file behavior suites
- intentionally kept integration suites
- Move lone file-scoped specs out of
__tests__/. - Collapse tiny split suites into one adjacent table-driven file when the only variation is fixture shape.
- Action helpers may create the editor and perform the transform, but assertions stay in the
it()body. - Rule-action helpers should return the editor and other setup results, not assert internally.
- For composition-heavy suites, extract focused helpers before adding more inline setup. Small helpers like
getSortedKeys(...)andcreateStoreEditor(...)beat repeated editor construction sludge.
Fixtures And Assertions
- Use JSX hyperscript only when tree shape or selection shape is the contract.
- Use plain object fixtures for option, state, and pure helper tests.
- Use a real editor object only when editor-root semantics matter.
NodeApiandElementApido not treat plain{ children: [...] }objects the same way as real editors. - Keep inputs and outputs small.
- Use
it.eachfor small behavior matrices. - In this Bun + Testing Library setup, prefer render-returned queries over
screen. - Snapshots are allowed only when serialized text, AST, or similar output is the contract and inline assertions would be worse.
- Whitespace-sensitive serializer outputs should prefer direct
toBe(...)string assertions. - Avoid
toHaveStylehere. Use direct style-property assertions instead. - After broad title renames on snapshot-backed suites, delete and regenerate the snapshot file.
bun test -uupdates and adds keys, but does not reliably prune dead ones.
Cleanup Heuristics
- Score files before cleanup waves instead of skimming randomly.
- Use fresh
lcovafter each pass. Do not keep working from a stale hotspot map. - Rewrite large hotspot specs before chasing broad title debt. Bigger signal first.
- Scan title debt across:
- plain string titles
it.each(...)format stringsString.rawtitles- snapshot keys derived from those titles
- Scan for commented-out
it,test, anddescribeblocks during dead-spec cleanup waves. - End cleanup waves with repo scans for:
- skipped tests
- commented-out tests
- cross-spec imports
- placeholder titles
- non-allowlisted
createPlateEditorseams
- Use
bun run test:profilefor the fast suite when deciding whether a spec belongs in the slow lane.pnpm test:slowestandpnpm checkenforce those thresholds. - For rule-override hotspots, extract one editor helper and table-drive repeated node-type cases instead of cloning the same transform assertions.
- For plugin-composition hotspots, extract helpers before adding more inline setup.
- Adapt upstream invariants when local runtime semantics differ. Keep the invariant, rewrite the fixture around the real public contract.
- Treat tiny one-branch crumbs as crumbs. Do not let a coverage number talk you into fake work.
- Penalize giant files with poor test ROI unless they hold a central contract you actually care about during the next rewrite.
Package Rules
autoformat
- Use package-local rule arrays.
- Use
KEYSor base plugins, not React plugin.key. - Add only the base plugins a rule actually needs.
- Collapse tiny mark or block suites into matrices when the contract is the same.
- Do not import
AutoformatKitor app registries in package tests. - If an
autoformatcase needs cross-package behavior likecode-blockwiring or app-owned integration setup, move it toapps/www/src/__tests__/package-integrationinstead of expandingpackages/autoformat/package.json.
markdown
- Configure
MarkdownPluginlocally in the package helper. - Do not import
MarkdownKitor any app registry fromapps/www. - Prefer direct string assertions for tiny whitespace-sensitive serializer outputs.
ai / streaming markdown
- Keep one mixed-document smoke case.
- Add a few explicit chunk-boundary tests.
- Do not hide streaming behavior behind snapshots or giant hand-written trees.
core
- Use
createSlateEditorfor:- pure plugin option stores
- selector extension
- plugin API composition
- transform composition
- parser and deserializer contracts
- HTML
insertData - DnD-style contracts
- Grow the compile-only type lane here first:
- plugin creation
- editor creation
- inference
- option merging
- API merging
- Port upstream Slate React invariants by behavior, not by file.
- When a core source test mounts
Platewhile the same run also loads public-package React entrypoints, treat duplicate-instance warnings as test noise. Suppress them in the test wrapper withsuppressInstanceWarninginstead of changing runtime warning logic. - For provider-only React specs in core, reuse the shared
packages/core/src/react/__tests__/TestPlate.tsxhelper instead of re-declaring localconst Plate = ...wrappers.
selection
moveSelectionandshiftSelectionstay on Plate. They are genuinely Plate-bound exceptions, not cleanup debt.
docx, docx-io, and app integration
- Keep app-owned cross-package integration tests under
apps/www/src/__tests__/package-integration. - Keep buckets local under that folder instead of scattering app-owned integration coverage through
src/lib. - Package tests must not pull app aliases, app kits, or registries into package graphs.
- Fixture-heavy
docxanddocx-iosuites are valid reasons to keep__tests__/.
slate
- Focus on pure editor, query, and transform behavior first.
- Keep runtime coverage on navigation, selection math, structural queries, transform edge cases, extension transforms, and
createEditorlegacy sync. - Keep a small compile-only type lane for public
@platejs/slatecontracts. - Use selective upstream mining. Pull invariants that cheaply improve local public-contract coverage; do not mirror upstream blindly.
- Add direct helper specs for custom Slate code when indirect coverage is lying.
- Use
lcovas package truth. Bun’s text coverage summary is noisy for targeted package runs. - Stop once the remaining misses are mostly deferred DOM wrappers plus low-risk non-DOM dust.
- Later utility and core work should mine these upstream
slate-reactinvariants:use-slate-selector: selector equality and stale-rerender preventionuse-slate: editor version and subscription behavioruse-selected: selection rerender and path stabilityeditable: value-change vs selection-change partitioningdecorations: decoration propagation and redecorate behaviorchunking: chunk or index invalidation only if remaining core gaps justify it
- Skip
react-editorDOM focus coverage unless a real Plate bug forces it. - Playwright example coverage stays out.
Reviewed Exceptions
createPlateEditor allowlist
Keep createPlateEditor when the contract is actually about:
- React or provider wiring
- rendered output or DOM behavior
- store rerender semantics
- Plate plugin conversion boundaries
- the known Plate-only selection APIs:
moveSelectionandshiftSelection
Do not treat these files as backlog just because they still use Plate.
__tests__/ allowlist
Keep __tests__/ when it holds:
- package-local helpers or fixture banks
- intentionally split multi-file suites like
withAutoformat - fixture-heavy integration suites like
docxanddocx-io - app-owned cross-package integration suites under
apps/www/src/__tests__/package-integration
Quick Reference
- Start with the smallest seam that proves the contract:
createEditor->createSlateEditor->createPlateEditor. - Work in passes:
>= 6first, rerun coverage, then worthwhile>= 5, then switch to architecture-safety targets. - Use
bun run testfor the fast default loop. Usepnpm test:allfor the full suite. - Use
bun run test:profileto inspect the fast loop andbun run test:slowestto enforce it. pnpm test:slowesthas two bands: warning zone for local drift and hard CI failure thresholds. Fix the warning zone before it becomes a PR failure.- Use Bun globals. Do not import them from
bun:test. - Keep specs beside the implementation. Use
__tests__/only for helpers, fixtures, or intentional split or integration suites. - Use plain objects for simple state. Use JSX hyperscript only when tree or selection shape is the contract.
- Prefer explicit assertions. Use render-returned queries over
screen, and use direct style-property assertions overtoHaveStyle. - Delete skips, commented tests, dead smoke tests, and stale snapshot files after broad title renames.
- Package tests must stay package-local. No app registries, no app kits, no cross-spec imports.
- For package-only coverage decisions, trust
lcov, not Bun’s broad text summary. - Keep exactly two lanes:
- fast lane:
*.spec.ts[x], run bypnpm test - slow lane:
*.slow.ts[x], run bypnpm test:slow
- fast lane:
- Use
pnpm test:allfor the full repo test run instead of relying on barebun test. - Stop before 100%. When the remaining misses are wrappers, DOM/provider dust, sludge, or crumbs, you are done.