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 test is the default iterative workflow.
  • pnpm test:all is 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:
    1. high-value contract pass: do every honest file with score >= 6
    2. medium-value follow-up: rerun coverage, then do worthwhile >= 5 files while skipping crumbs, wrappers, and sludge
    3. architecture-safety pass: stop following coverage blindly and harden the contracts you most refuse to break
  • 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
  • /react is 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 import describe, it, expect, mock, spyOn, or other globals from bun: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 devDependencies just to support cross-package or app-shaped test setups. If a test needs other package kits, app aliases, or multi-package wiring, move it to apps/www/src/__tests__/package-integration.
  • When adding tests, inspect fast-suite outliers with bun run test:profile. bun run test:slowest is the hard gate. If a fast-suite spec crosses the thresholds in tooling/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/test or 120ms/file is already a move candidate, especially for React-heavy specs.
  • For borderline cases, run pnpm test:slowest -- --top 25 --rerun-each 3 and 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 createEditor from @platejs/slate for pure Slate query, transform, interface, and history contracts.
  • Use createSlateEditor for 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 createPlateEditor only 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 createPlateEditor usage 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(...) and createStoreEditor(...) 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. NodeApi and ElementApi do not treat plain { children: [...] } objects the same way as real editors.
  • Keep inputs and outputs small.
  • Use it.each for 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 toHaveStyle here. Use direct style-property assertions instead.
  • After broad title renames on snapshot-backed suites, delete and regenerate the snapshot file. bun test -u updates and adds keys, but does not reliably prune dead ones.

Cleanup Heuristics

  • Score files before cleanup waves instead of skimming randomly.
  • Use fresh lcov after 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 strings
    • String.raw titles
    • snapshot keys derived from those titles
  • Scan for commented-out it, test, and describe blocks during dead-spec cleanup waves.
  • End cleanup waves with repo scans for:
    • skipped tests
    • commented-out tests
    • cross-spec imports
    • placeholder titles
    • non-allowlisted createPlateEditor seams
  • Use bun run test:profile for the fast suite when deciding whether a spec belongs in the slow lane. pnpm test:slowest and pnpm check enforce 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 KEYS or 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 AutoformatKit or app registries in package tests.
  • If an autoformat case needs cross-package behavior like code-block wiring or app-owned integration setup, move it to apps/www/src/__tests__/package-integration instead of expanding packages/autoformat/package.json.

markdown

  • Configure MarkdownPlugin locally in the package helper.
  • Do not import MarkdownKit or any app registry from apps/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 createSlateEditor for:
    • 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 Plate while the same run also loads public-package React entrypoints, treat duplicate-instance warnings as test noise. Suppress them in the test wrapper with suppressInstanceWarning instead of changing runtime warning logic.
  • For provider-only React specs in core, reuse the shared packages/core/src/react/__tests__/TestPlate.tsx helper instead of re-declaring local const Plate = ... wrappers.

selection

  • moveSelection and shiftSelection stay 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 docx and docx-io suites 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 createEditor legacy sync.
  • Keep a small compile-only type lane for public @platejs/slate contracts.
  • 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 lcov as 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-react invariants:
    • use-slate-selector: selector equality and stale-rerender prevention
    • use-slate: editor version and subscription behavior
    • use-selected: selection rerender and path stability
    • editable: value-change vs selection-change partitioning
    • decorations: decoration propagation and redecorate behavior
    • chunking: chunk or index invalidation only if remaining core gaps justify it
  • Skip react-editor DOM 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: moveSelection and shiftSelection

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 docx and docx-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: >= 6 first, rerun coverage, then worthwhile >= 5, then switch to architecture-safety targets.
  • Use bun run test for the fast default loop. Use pnpm test:all for the full suite.
  • Use bun run test:profile to inspect the fast loop and bun run test:slowest to enforce it.
  • pnpm test:slowest has 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 over toHaveStyle.
  • 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 by pnpm test
    • slow lane: *.slow.ts[x], run by pnpm test:slow
  • Use pnpm test:all for the full repo test run instead of relying on bare bun test.
  • Stop before 100%. When the remaining misses are wrappers, DOM/provider dust, sludge, or crumbs, you are done.
Weekly Installs
20
Repository
udecode/plate
GitHub Stars
16.2K
First Seen
Mar 3, 2026