Back to Plate

Test Suite Cleanup Strategy

docs/plans/2026-03-06-test-suite-cleanup-plan.md

53.0.564.5 KB
Original Source

Test Suite Cleanup Strategy

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.

No blind repo-wide coverage push in this phase. No e2e or browser work.

Completed

Pass 1

  • Rebuilt the first hotspot batch around smaller behavior seams in core, table, link, list-classic, markdown, and udecode/utils.
  • Removed low-signal tests like getHandler.spec.ts.
  • Removed the old markdown snapshot for deserializeMd.
  • Added initial testing taxonomy and helper-placement rules to testing.mdc.

Pass 2

  • Replaced dead skipped selection tests with direct API coverage in packages/selection/src/react/internal/api/setSelectedIds.spec.tsx.
  • Standardized NormalizeTypesPlugin and TrailingBlockPlugin specs around a shared normalizeRoot helper.
  • Cleaned small query specs in list-classic, slate, and core so titles describe behavior instead of placeholder text.
  • Removed the misleading resolvePlugin “deep clone” test that asserted the opposite of its own title.

Pass 3

  • Moved the shared table helper out of withNormalizeTable.spec.tsx into packages/table/src/lib/__tests__/getTestTablePlugins.ts.
  • Repointed table specs to the real helper module instead of importing from another spec file.
  • Deleted packages/table/src/react/onKeyDownTable.spec.tsx, which had zero active tests and only commented or skipped intent.
  • Deleted packages/docx-io/src/lib/__tests__/lists.spec.tsx, which was a fully skipped unsupported case.
  • Removed the skipped unsupported link case from streamDeserializeMd.spec.tsx.
  • Removed the skipped mixed-line-break case from splitLineBreaks.spec.tsx and compressed the active cases into a small matrix.
  • Trimmed Plate.spec.tsx by deleting dead skipped cases, removing one duplicate id test, and renaming the remaining low-signal titles.
  • Extended testing.mdc with cleanup-matrix guidance and a hard rule against aspirational skipped tests.

Pass 4

  • Moved the file-scoped static serializer specs out of packages/core/src/static/__tests__/ into adjacent serializeHtml.*.spec.* files and left create-static-editor.ts behind as the only helper there.
  • Removed the no-op placeholder from the old static node-to-props spec while moving it into serializeHtml.node-props.spec.ts.
  • Moved packages/udecode/react-hotkeys/src/__tests__/HotkeysProvider.test.tsx to packages/udecode/react-hotkeys/src/internal/HotkeysProvider.spec.tsx, switched it to .spec, and trimmed one overlapping scope test.
  • Replaced the old find-replace __tests__/decorateSearchHighlight specs with packages/find-replace/src/lib/decorateFindReplace.spec.ts, shifting most cases to direct unit tests and keeping one plugin wiring smoke test.
  • Extended testing.mdc with explicit .spec naming rules and a __tests__/ placement rule.

Pass 5

  • Rewrote packages/ai/src/react/ai-chat/streaming/streamInsertChunk.spec.tsx from a giant mixed-purpose integration file into a smaller contract suite with one chunk helper, targeted chunk-boundary assertions, and it.each markdown parity cases.
  • Deleted all five skipped streamInsertChunk cases instead of preserving wishful thinking.
  • Deleted the streamInsertChunk snapshot file and replaced both snapshot cases with explicit assertions.
  • Removed the prompt-specific “skills and weather” fixture and the giant hand-written expected tree in favor of one smaller mixed markdown smoke case.
  • Extended testing.mdc with a hard rule against casual snapshots and a streaming-markdown pattern for future tests.

Pass 6

  • Collapsed the eight-file mergeDeepToNodes split suite under packages/core/src/lib/utils/__tests__/mergeDeepToNodes/ into one adjacent unit file at packages/core/src/lib/utils/mergeDeepToNodes.spec.ts.
  • Replaced hyperscript fixtures in mergeDeepToNodes with plain object fixtures and one real editor object for the editor-root cases where NodeApi and ElementApi semantics differ.
  • Upgraded the old source-factory coverage from a trivial single-node case to a multi-node assertion that proves the factory runs once per matched node.
  • Refactored packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsx to use createSlateEditor, it.each, and behavior titles instead of the old repeated createPlateEditor setup.
  • Loaded the planning-with-files skill and added planning-memory notes so pass history, findings, and verification live on disk outside the chat transcript too. Those notes are consolidated in this plan file.

Pass 7

  • Added packages/autoformat/src/lib/__tests__/withAutoformat/createAutoformatEditor.ts so focused withAutoformat specs share one small editor helper instead of cloning setup in every file.
  • Rewrote text.spec.tsx around package-local rule arrays, explicit behavior titles, and one matrix plus one sequential percent case.
  • Rewrote markup.spec.tsx, ignoreTrim.spec.tsx, mark/multiple-marks.spec.tsx, trigger.spec.tsx, and block/singleCharTrigger.spec.tsx to use KEYS or local rules instead of @platejs/basic-nodes/react, @platejs/link/react, or www app-kit imports.
  • Cleared the @platejs/autoformat package typecheck blocker by removing the bad React-only key imports from the remaining failing hotspot specs.
  • Extended testing.mdc with a hard rule against app-registry imports in package tests and a rule to use KEYS or base plugins instead of React plugin .key access.

Pass 8

  • Rewrote block/list.spec.tsx, block/code-block.spec.tsx, block/heading.spec.tsx, block/blockquote.spec.tsx, block/preFormat.spec.tsx, and invalid.spec.tsx around local rules plus only the base plugins each rule actually needs.
  • Removed the last AutoformatKit, createPlateEditor, React-only .key, and placeholder-title usage from the entire withAutoformat suite.
  • Collapsed the four one-case mark files into one matrix file at mark/basic-marks.spec.tsx and deleted the old bold, italic, code, and strikethrough spec files.
  • Deleted the app-coupled toggle case from block/list.spec.tsx instead of keeping a misleading extra seam in a list-focused package suite.
  • Extended testing.mdc with a rule to collapse one-case mark or block specs into one matrix and to add only the base plugins a block rule actually needs.

Pass 9

  • Ran a repo-wide file-by-file title scan that included plain string titles, it.each(...)('...') format strings, String.raw\...`` titles, and snapshot keys derived from those titles.
  • Cleaned the remaining title debt in Plate.spec.tsx, init.spec.ts, removeMarks.spec.tsx, serializeMd.spec.tsx, and deserializeMdList.spec.tsx.
  • Replaced the two whitespace-sensitive markdown serializer snapshot assertions with direct toBe(...) string assertions.
  • Deleted and regenerated the markdown snapshot files after the title renames because bun test -u kept the dead keys.
  • Extended testing.mdc with the hidden-title scan rule and the Bun snapshot regeneration rule.

Pass 10

  • Re-ran the hotspot matrix after the title pass and focused the next cleanup on core plugin composition tests instead of chasing already-acceptable boundary suites.
  • Refactored resolvePlugins.spec.tsx around getResolvedKeys(...), getSortedKeys(...), and it.each(...) for the deterministic plugin ordering cases.
  • Moved the pure option-store and selector-extension cases in resolvePlugins-store.spec.tsx to createSlateEditor.
  • Deleted the overlapping rerender test in resolvePlugins-store.spec.tsx and kept the more focused React hook rerender coverage.
  • Extended testing.mdc with a rule to use createSlateEditor for non-React plugin store tests and to extract helpers for plugin-composition hotspots.

Pass 11

  • Re-ran the hotspot matrix after pass 10 and confirmed extendApi.spec.ts was still using the Plate editor seam for pure plugin API and transform composition.
  • Switched extendApi.spec.ts to createSlateEditor via one local helper and left the file scoped to pure plugin composition behavior.
  • Re-verified extendApi.spec.ts alongside the two refactored core plugin-composition specs.
  • Left the remaining large suites untouched after the matrix showed they are now mostly genuine React or transform boundary coverage rather than obvious fake integration.

Pass 12

  • Ran a repo-wide scan for commented-out it(...), test(...), and describe(...) blocks inside spec files.
  • Deleted the dead commented test blocks from shortcuts, useEditableProps, EditorMethodsEffect, pluginDeserializeHtml, SlateExtensionPlugin, withList, setSelectedCellsBorder, cleanDocx, withImageUpload, and computeDiff.
  • Extended testing.mdc with a hard rule to delete commented-out tests and to include them in dead-spec cleanup scans.
  • Re-verified the touched specs plus build and typecheck for the affected packages.

Pass 13

  • Re-ran the hotspot matrix after the commented-spec pass and picked withBreakRules.spec.tsx as the last worthwhile cleanup target.
  • Rewrote withBreakRules.spec.tsx around createElementPlugin(...), runInsertBreak(...), and it.each(...) for the duplicated empty-reset and default-behavior cases.
  • Renamed the raw config-driven describe(...) blocks to semantic behavior groups and kept the remaining edge cases as focused one-off tests.
  • Re-verified the touched core spec plus core build and typecheck.

Pass 14

  • Picked withDeleteRules.spec.tsx as the obvious sibling of the break-rule hotspot after the withBreakRules cleanup worked.
  • Rewrote withDeleteRules.spec.tsx around createElementPlugin(...), getEditorAfterAction(...), and it.each(...) for the repeated start-reset and default-behavior cases.
  • Kept assertions inside it() bodies and used helpers only for setup and actions so the file stays Biome-clean.
  • Re-verified the touched core spec.

Pass 15

  • Moved the remaining pure core and table transform specs from createPlateEditor to createSlateEditor, including overrideEditor, SlateEditorMethods, BaseHeadingPlugin, createSlatePlugin, LengthPlugin, insertTable, insertTableColumn, deleteColumn, insertTableRow, withGetFragmentTable, setBorderSize, withTableCellSelection, getSelectedCellsBorders, getSelectedCellsBoundingBox, setCellBackground, withDeleteTable, isTableBorderHidden, withInsertTextTable, and setSelectedCellsBorder.
  • Left the single real component-override case in resolvePlugins.spec.tsx on the Plate seam.
  • Cleaned the duplicate/sloppy titles in TPlateEditor.spec.ts and TPlateEditorCore.spec.ts without widening those suites.

Pass 16

  • Moved the HTML parser and deserializer specs to createSlateEditor.
  • Moved selectBlocks, setSelectedIds, and copySelectedBlocks to createSlateEditor.
  • Tried moveSelection and shiftSelection on createSlateEditor, proved that both files are genuinely Plate-bound, and reverted them.
  • Documented the selection exception in testing.mdc instead of pretending the generic rule has no limits.

Pass 17

  • Moved the remaining deterministic code-block specs to createSlateEditor.
  • Moved the pure DebugPlugin and getEditorPlugin specs to createSlateEditor.
  • Re-verified the touched code-block and core packages.

Pass 18

  • Moved deterministic date, dnd, layout, link, media, slate, and list specs to createSlateEditor.
  • Moved normalizeDescendantsToDocumentFragment.spec.tsx to createSlateEditor and createSlatePlugin.
  • Re-verified the touched packages with targeted Bun tests plus package build and typecheck.

Pass 19

  • Replaced the markdown package helper import of apps/www MarkdownKit with local MarkdownPlugin.configure(...) plus the same remark plugins.
  • Moved defaultRule.spec.ts and deserializeMd.spec.tsx to createSlateEditor.
  • Replaced the tiny defaultRule snapshot assertions with explicit string assertions and deleted the snapshot file.
  • Re-ran the repo-wide seam scan and confirmed the remaining createPlateEditor files are now intentional.

Pass 20

  • Re-ran repo-wide scans for skipped tests, placeholder titles, commented-out tests, cross-spec imports, and remaining createPlateEditor seams.
  • Found no new actionable cleanup.
  • Confirmed the remaining createPlateEditor files are still the intentional React/provider/render/store allowlist plus the two Plate-only selection APIs.
  • Confirmed the remaining __tests__/ specs are the expected fixture-heavy docx and docx-io suites plus the intentionally split withAutoformat suite.

Pass 21

  • Moved the @platejs/slate specs that were really exercising cross-package editor wiring out of slate and into apps/www, which already carries the full package surface without adding new internal package devDependencies.
  • Consolidated the moved coverage into one www integration spec for inline-link traversal, inline isEmpty, mark toggling, and void delete boundaries.
  • Removed the two platejs-only type imports from @platejs/core test helpers and dropped the now-unused platejs devDependency from core.

Pass 22

  • Moved the @platejs/core static serializeHtml integration specs out of packages/core and into apps/www, because they depended on BaseEditorKit and registry fixture values from the app anyway.
  • Deleted the last packages/core/src/static/__tests__/create-static-editor.ts helper after moving its only remaining callers into the app-level integration folder.
  • Confirmed the remaining www-coupled package tests still live in docx-io and ai, so the next cleanup pass should move those clusters too instead of adding more internal package devDependencies.

Pass 23

  • Moved the @platejs/docx-io roundtrip fixture suite out of packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx and into apps/www/src/__tests__/package-integration/docx-io.roundtrip.spec.tsx, because that coverage depends on BaseEditorKit and DocxExportKit from the app registry.
  • Deleted the @/* and www/* aliases from packages/docx-io/tsconfig.json, so package typecheck no longer drags apps/www/src through an app-only import seam.
  • Gave apps/www the direct test deps the moved integration actually uses: mammoth for Node-side DOCX fixture import and @types/unist for the app's existing local src/types/unist.ts type import.
  • Re-ran @platejs/docx-io typecheck on the real workspace graph with no root overrides, so the package now passes without the fake pinning detour.

Pass 24

  • Moved the entire apps/www app-owned package integration cluster from src/lib/package-integration to src/__tests__/package-integration.
  • Kept the existing buckets under package-integration so cross-package contracts and static HTML coverage stay grouped without dumping everything into one root-level test folder.

Pass 25

  • Made @platejs/slate the explicit phase-1 priority and used the slate phase-1 execution log as the working source, while keeping this file as the canonical cleanup history.
  • Mined upstream ../slate and slate-history tests selectively instead of mirroring them blindly, pulling only the invariants that improved our local public-contract coverage cheaply.
  • Finished the slate package at 395 passing tests, with packages/slate/src at 100.00% function coverage and 96.97% line coverage from lcov.
  • Covered the real slate contract buckets:
    • slate-history and history helper behavior
    • Path, Range, Node, and location-ref contracts
    • editor query and navigation seams like above, next, previous, marks, string, nodes, isAt, and getPointBefore
    • transform and helper seams like deleteText, deleteMerge, mergeNodes, toggleMark, setNodes, moveSelection, duplicateNodes, and addMarks
  • Stopped phase 1 once the remaining misses were mostly deferred DOM wrappers plus a few low-risk non-DOM files like isEmpty, prop, getFragment, and queryNode.

Pass 25 Methodology

  • Bun-first and speed-first: bun test stays the default workflow; no browser or e2e coverage in this program.
  • Coverage is hotspot telemetry, not a vanity target.
  • For package-only truth, prefer lcov over Bun’s broad text summary.
  • Test public behavior through editor APIs, transforms, plugin APIs, or rendered output only when the contract is actually React or DOM specific.
  • Use the narrowest seam that proves the contract:
    • createEditor for pure Slate behavior
    • createSlateEditor for non-React plugin or editor wiring
    • createPlateEditor only when the contract is genuinely Plate-specific
  • Prefer explicit assertions over snapshots; keep snapshots only when the serialized form is the contract and inline assertions would be worse.
  • Put compile-only type contracts in type-tests/, not runtime specs.
  • No aspirational skipped tests, no fake smoke tests, no cross-spec helper imports, and no app-registry imports in package tests.
  • Adapt upstream Slate invariants when local runtime semantics differ; do not cargo-cult upstream fixtures.

Pass 25 Learnings

  • Direct helper specs were worth it for custom slate code like deleteMerge, location-ref, toggleMark, and mergeNodes; indirect coverage was lying.
  • Bun’s text coverage summary is noisy for targeted package runs, so lcov is the trustworthy package-level source of truth.
  • Upstream invariants are reference material, not holy scripture; when local runtime behavior differs, keep the invariant and rewrite the fixture around the real public contract.

Matrix Scan

Snapshot taken during pass 3 before edits:

FileLinesSkipscreatePlateEditorcreateSlateEditorjsxt hitsPlaceholder hitsNotes
packages/core/src/react/components/Plate.spec.tsx6902230014High ROI React/provider cleanup
packages/table/src/react/onKeyDownTable.spec.tsx53540030Dead file: only skipped/commented cases
packages/ai/src/react/ai-chat/streaming/streamInsertChunk.spec.tsx86450232Large future pass
packages/ai/src/react/ai-chat/streaming/streamDeserializeMd.spec.tsx8110030Easy skip cleanup
packages/markdown/src/lib/deserializer/splitLineBreaks.spec.tsx14710030Easy skip cleanup
packages/docx-io/src/lib/__tests__/lists.spec.tsx6610000Fully skipped unsupported case

Snapshot taken during pass 5 before edits:

FileLinesSkipscreatePlateEditorcreateSlateEditorjsxt hitsPlaceholder hitsNotes
packages/ai/src/react/ai-chat/streaming/streamInsertChunk.spec.tsx864503418Main cleanup target
packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx28900948Heavy editor-harness text cases
packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsx15004043React/editor seam likely too wide
packages/core/src/lib/utils/__tests__/mergeDeepToNodes/default-options.spec.tsx3300041Pure logic still living in __tests__
packages/core/src/lib/utils/__tests__/mergeDeepToNodes/elements-text.spec.tsx2600041Pure logic still using hyperscript

Snapshot taken during pass 6 after edits:

FileLinesSkipscreatePlateEditorcreateSlateEditorjsxt hitsPlaceholder hitsit.eachNotes
packages/core/src/lib/utils/mergeDeepToNodes.spec.ts216000002Collapsed 8 tiny specs into one unit suite
packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsx103001401Same contract, smaller seam, no React editor

Snapshot taken during pass 7 after edits:

FileLinesSkipscreatePlateEditorcreateSlateEditorjsxt hitsPlaceholder hitsit.eachNotes
packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx227000401Uses package-local text rules instead of AutoformatKit
packages/autoformat/src/lib/__tests__/withAutoformat/markup.spec.tsx56000401One matrix covers both match shapes
packages/autoformat/src/lib/__tests__/withAutoformat/ignoreTrim.spec.tsx78000400Focused mark-rule contract only
packages/autoformat/src/lib/__tests__/withAutoformat/mark/multiple-marks.spec.tsx90000400No www app-kit import left
packages/autoformat/src/lib/__tests__/withAutoformat/block/singleCharTrigger.spec.tsx58001400Uses KEYS.link, no React plugin import
packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsx100000401Shared helper plus KEYS, package typecheck now passes

Snapshot taken during pass 8 after edits:

FileLinesSkipscreatePlateEditorcreateSlateEditorjsxt hitsPlaceholder hitsit.eachNotes
packages/autoformat/src/lib/__tests__/withAutoformat/block/list.spec.tsx149000401Local list rules plus BaseListPlugin and BaseIndentPlugin only
packages/autoformat/src/lib/__tests__/withAutoformat/block/code-block.spec.tsx148000401Local code-block rule plus BaseCodeBlockPlugin only
packages/autoformat/src/lib/__tests__/withAutoformat/block/heading.spec.tsx60000401No app-kit dependency left
packages/autoformat/src/lib/__tests__/withAutoformat/block/blockquote.spec.tsx35000400Single focused block rule
packages/autoformat/src/lib/__tests__/withAutoformat/block/preFormat.spec.tsx39000400Focused nested-block contract only
packages/autoformat/src/lib/__tests__/withAutoformat/invalid.spec.tsx85000401Duplicate invalid case deleted
packages/autoformat/src/lib/__tests__/withAutoformat/mark/basic-marks.spec.tsx105000401Replaces 4 tiny one-case mark files

Snapshot taken during pass 9 before edits:

FileTitle hitsSnapshot stale keysOther issuesNotes
packages/core/src/react/components/Plate.spec.tsx10echoed raw option name in describeSemantic rename only
packages/core/src/lib/plugins/slate-extension/transforms/init.spec.ts20one misleading does not call ... titleNeeded behavior-title cleanup
packages/slate/src/internal/transforms-extension/removeMarks.spec.tsx10raw option name in titleSemantic rename only
packages/markdown/src/lib/serializer/serializeMd.spec.tsx012fixures, qoute, base-verb titles, whitespace-sensitive snapshotsNeeded title cleanup plus snapshot rewrite
packages/markdown/src/lib/deserializer/deserializeMdList.spec.tsx02deserialize a ... grammarSnapshot regeneration required

Snapshot taken during pass 10 after pass 9 cleanup:

FileEditorsLinesit.eachNotes
packages/core/src/lib/plugins/affinity/AffinityPlugin.spec.tsx2711900Large, but still a real mark/link boundary contract suite
packages/core/src/internal/plugin/resolvePlugins-store.spec.tsx134190Reduced pure-store cases to createSlateEditor; remaining React hook coverage is legitimate
packages/core/src/lib/utils/extendApi.spec.ts205890Still large, but largely public API composition behavior
packages/core/src/react/components/Plate.spec.tsx186180React/provider boundary coverage
packages/core/src/internal/plugin/resolvePlugins.spec.tsx146782Deterministic sort cases collapsed into it.each

Snapshot taken during pass 11 after pass 10 cleanup:

FileEditorsHelper callsLinesNotes
packages/core/src/lib/plugins/affinity/AffinityPlugin.spec.tsx2701190Still large, but clearly a mark/link boundary suite
packages/core/src/react/components/Plate.spec.tsx180618React/provider boundary coverage
packages/core/src/lib/plugins/override/withBreakRules.spec.tsx160725Transform boundary coverage
packages/core/src/lib/plugins/slate-extension/SlateExtensionPlugin.spec.tsx160535Plugin wiring boundary coverage
packages/core/src/lib/utils/extendApi.spec.ts121561Dropped to the Slate seam with a local helper

Snapshot taken during pass 12 before edits:

FileCommented test blocksNotes
packages/core/src/react/utils/shortcuts.spec.tsx9Biggest remaining junk pocket after the seam cleanup
packages/core/src/react/hooks/useEditableProps.spec.tsx1Dead redecorate block
packages/core/src/react/components/EditorMethodsEffect.spec.tsx2Dead redecorate follow-ups
packages/core/src/lib/plugins/html/utils/pluginDeserializeHtml.spec.ts1No-op commented type experiment
packages/core/src/lib/plugins/slate-extension/SlateExtensionPlugin.spec.tsx1Legacy commented case next to its active replacement
packages/list-classic/src/lib/withList.spec.tsx2Dead nested-list cases
packages/table/src/react/components/TableCellElement/setSelectedCellsBorder.spec.tsx2Dead border: none branch attempts
packages/docx/src/lib/docx-cleaner/cleanDocx.spec.ts2One unsupported list case and one no-op stylesheet placeholder
packages/media/src/lib/image/withImageUpload.spec.tsx1Dead async upload placeholder
packages/diff/src/lib/computeDiff.spec.ts1Giant commented fixture fossil

Snapshot taken during pass 13 before edits:

FileEditorsLinesit.eachNotes
packages/core/src/lib/plugins/override/withBreakRules.spec.tsx167250Real boundary suite, but still repetitive enough to compress

Snapshot taken during pass 14 before edits:

FileEditorsLinesit.eachNotes
packages/core/src/lib/plugins/override/withDeleteRules.spec.tsx156480Same sibling pattern as withBreakRules: real contract, too much repeated setup

Snapshot taken during pass 19 after the repo-wide seam sweep:

FileDecisionReason
packages/selection/src/react/internal/api/moveSelection.spec.tsxKeep PlateThe API is genuinely PlateEditor-bound
packages/selection/src/react/internal/api/shiftSelection.spec.tsxKeep PlateThe API is genuinely PlateEditor-bound
packages/core/src/react/editor/TPlateEditorCore.spec.tsKeep PlateIntentional API comparison between Slate and Plate editors
packages/core/src/react/editor/TPlateEditor.spec.tsKeep PlateIntentional API comparison between Slate and Plate editors
packages/core/src/react/hooks/useEditableProps.spec.tsxKeep PlateReact hook and provider behavior
packages/core/src/react/plugin/toPlatePlugin.spec.tsKeep PlatePlate plugin conversion is the contract
packages/core/src/react/utils/pipeRenderLeaf.spec.tsxKeep PlateReact render pipeline is the contract
packages/core/src/react/utils/shortcuts.spec.tsxKeep PlateReact hotkey wiring and createPlatePlugin behavior
packages/core/src/react/components/PlateControllerEffect.spec.tsxKeep PlateReact controller/provider wiring
packages/core/src/react/components/EditorHotkeysEffect.spec.tsxKeep PlateReact hotkey effect wiring
packages/core/src/react/components/EditorMethodsEffect.spec.tsxKeep PlateReact effect attaches methods on mount
packages/core/src/react/stores/element/useElementStore.spec.tsxKeep PlateReact context/store behavior
packages/core/src/react/components/Plate.spec.tsxKeep PlatePlate provider behavior
packages/core/src/react/plugins/react/ReactPlugin.spec.tsKeep PlateReact plugin override behavior
packages/core/src/internal/plugin/resolvePlugins-store.spec.tsxMixedPure store cases already moved to Slate; remaining cases are React hook rerender coverage
packages/core/src/internal/plugin/resolvePlugins.spec.tsxMixedOne remaining component-override case is legitimately Plate-specific
packages/core/src/internal/plugin/pipeNormalizeInitialValue.spec.tsxKeep PlatePlate plus useEditorValue are the contract

__tests__ Directory Scan

Snapshot taken during pass 4 before edits:

DirectorySpecsHelpers or fixturesDecision
packages/core/src/static/__tests__61Move file-scoped specs out, keep helper
packages/udecode/react-hotkeys/src/__tests__10Move lone file-scoped spec out
packages/find-replace/src/lib/__tests__20Replace with adjacent unit spec
packages/autoformat/src/lib/__tests__160Keep for now as a split multi-file behavior suite
packages/core/src/lib/utils/__tests__80Keep for now as a split multi-file matrix suite
packages/docx/src/lib/__tests__2147Keep as fixture-heavy integration suite
packages/docx-io/src/lib/__tests__61Keep as fixture-backed integration suite

Current Standards

  • Use JSX hyperscript only when tree shape or selection shape is the contract.
  • Use plain object fixtures for option and state tests.
  • Use *.spec.ts[x] for all tests. File-scoped specs live beside the implementation.
  • Put shared helpers in __tests__/, never in another spec file.
  • Keep __tests__/ only for helpers, fixture banks, and intentionally split multi-file or integration suites.
  • Avoid snapshots unless serialized text or AST output is the contract.
  • When renaming many snapshot-backed tests, delete and regenerate the snapshot file. bun test -u updates and adds keys, but it does not reliably prune dead ones.
  • Collapse tiny split helper suites into one table-driven file when the only variation is fixture shape.
  • For streaming markdown, keep one mixed-document smoke case and a few explicit chunk-boundary tests. Do not hide behavior behind snapshots or giant manual trees.
  • For plugin option stores, selector extension, and other non-React plugin composition tests, use createSlateEditor. Reserve createPlateEditor for React hooks, providers, and Plate-only wiring.
  • Pure plugin API and transform composition tests should also use createSlateEditor.
  • Parser, deserializer, HTML insertData, and DnD contract tests should also default to createSlateEditor unless React rendering is literally the behavior under test.
  • Markdown package tests must configure MarkdownPlugin locally in the package helper. Do not import MarkdownKit or other app registries from apps/www.
  • Delete dead skip tests instead of preserving wishful thinking.
  • For cleanup waves, score files first instead of skimming randomly.
  • For title cleanup waves, scan plain titles, it.each format strings, String.raw titles, and snapshot keys together.
  • For dead-spec cleanup waves, scan for commented-out it, test, and describe blocks too.
  • For plugin composition hotspots, extract helpers like getSortedKeys(...) or createStoreEditor(...) before adding more inline editor setup.
  • For rule-override hotspots, extract one editor helper and table-drive repeated node-type cases instead of cloning the same transform assertions.
  • Action helpers may create the editor and perform the transform, but assertions stay in the it() body.
  • Do not create task_plan.md, findings.md, or progress.md at repo root. Merge that content into this docs/plans/... file.

Consolidated Planning Notes

Former repo-root planning files were merged here on 2026-03-07:

  • task_plan.md
  • findings.md
  • progress.md

The phase-by-phase pass history already lives above in ## Completed, ## Matrix Scan, ## __tests__ Directory Scan, and ## Current State. The sections below preserve the extra task snapshot, findings, and verification detail that used to live in those repo-root files.

Task Snapshot

Goal

Raise the Plate test suite quality by replacing low-signal Slate integration tests with smaller unit or contract tests, while keeping pass history and decisions on disk.

Current Phase

Complete

Key Questions

  1. Which editor-level specs still earn their keep after the title cleanup is done?
  2. Which remaining fixture-heavy suites should collapse only after their contracts are rewritten?
  3. How do we keep snapshot-backed title renames from leaving dead keys behind?

Planning Decisions

DecisionRationale
Keep three layers only: unit, thin contract, golden I/OReduces fake integration coverage
Use *.spec.ts[x] for all testsConsistent file naming and Bun behavior
Reserve __tests__/ for helpers, fixtures, and intentional split suitesKeeps file-scoped tests beside the code
Prefer plain object fixtures for pure tree utilitiesHyperscript adds noise when structure is simple
Avoid snapshots unless serialized output is the contractSnapshots were hiding behavior instead of proving it
Use a real editor object only for editor-root semanticsNodeApi and ElementApi do not treat plain objects like real editors
Package tests should use local rule lists or package exports, not app registriesPrevents cross-package coupling and cleaner seams
Autoformat mark tests should use KEYS instead of React plugin .key importsThe rules only need stable type strings
Block autoformat specs should add only the base plugins the rule actually needsKeeps the seam honest and the setup small
Tiny one-case mark specs should collapse into one matrix fileBetter scanability, less file noise
Snapshot-backed title renames should delete and regenerate the snapshot filebun test -u adds and updates keys but does not reliably prune dead ones
Titles should describe behavior semantically, not echo raw option nameswhen normalization is disabled reads better than when shouldNormalizeEditor false
Markdown package tests must configure MarkdownPlugin locallyImporting MarkdownKit from apps/www is app-coupled test sludge
Remaining createPlateEditor usage is an allowlist, not a backlogThe final scan leaves only React/provider/render/store suites and known Plate-only APIs

Requirements

  • Improve the test suite without adding coverage work.
  • Remove useless or overlapping tests.
  • Prefer smaller seams over broad Slate integration tests.
  • Keep testing style consistent and documented for future contributors.
  • Avoid slow e2e or browser testing in this phase.

Research Findings

  • streamInsertChunk.spec.tsx was the highest-ROI remaining AI hotspot: 864 lines, 5 skips, 2 snapshots, and heavy overlap with markdown parser behavior.
  • The mergeDeepToNodes suite was eight tiny hyperscript specs for one pure helper. The split added noise without adding seams.
  • NodeApi.isDescendant and ElementApi.isElement behave differently on a real editor object than on a plain { children: [...] } object. Editor-root tests need a real editor when that distinction matters.
  • withAutoformat/trigger.spec.tsx was using createPlateEditor even though the contract is pure Slate behavior.
  • The autoformat package typecheck blocker was self-inflicted test debt: package specs were importing @platejs/basic-nodes/react and @platejs/link/react just to read .key.
  • withAutoformat text and mark specs get cleaner fast when they use package-local rule arrays plus one shared createAutoformatEditor helper.
  • Importing AutoformatKit from www is fine for true app-level integration coverage, but it is the wrong seam for file-scoped package specs.
  • Block autoformat specs only needed a few base plugins: BaseListPlugin + BaseIndentPlugin for list rules, and BaseCodeBlockPlugin for code-block rules.
  • The four one-off basic mark specs added zero signal as separate files. One matrix file is strictly better.
  • The whole withAutoformat suite now has zero AutoformatKit, createPlateEditor, bad React-only key imports, or placeholder should ... titles.
  • Hidden title debt survives naive greps. The remaining pockets were in it.each(...)('...'), String.raw\...`` titles, config names echoed inside test descriptions, and snapshot keys generated from those titles.
  • serializeMd.spec.tsx had a small but stupid snapshot trap: two assertions were only there to prove whitespace-sensitive string output, and the snapshot file itself started failing git diff --check because those strings ended with meaningful spaces.
  • bun test -u updates snapshot files but does not reliably prune old keys after title renames. If snapshot-backed titles change in bulk, deleting and regenerating the snapshot file is faster and cleaner than trusting Bun to do the right thing.
  • The repo-wide title debt scan is now clean for the tracked patterns: no should ..., fixures, or qoute matches remain in spec titles or snapshot keys.
  • After the title pass, the remaining hotspots were mostly concentrated in core plugin composition tests. The best remaining cleanup was not more title churn; it was narrowing pure option-store tests to createSlateEditor and table-driving deterministic plugin-order cases.
  • The post-pass matrix looks healthy enough now: the biggest remaining files are still large, but they are mostly real plugin or transform boundary suites rather than skipped, duplicated, or obviously fake unit coverage.
  • A second post-matrix pass confirmed the same pattern for extendApi.spec.ts: it was still using the Plate editor seam for pure plugin API composition with zero React behavior in scope. Dropping it to createSlateEditor was the right cleanup.
  • The last obvious non-runtime junk was dead commented-out tests. They were scattered across core, table, list-classic, docx, media, and diff, and they added zero signal while making scans look worse than reality.
  • After the commented-spec cleanup, the best remaining hotspot was withBreakRules.spec.tsx: not fake integration, but still bloated with duplicated node-type cases and repeated editor setup.
  • withDeleteRules.spec.tsx had the same sibling pattern: real boundary coverage, but too much repeated plugin setup and repeated delete action scaffolding.
  • createSlateEditor works cleanly for more than the early hotspots: HTML deserializers, code-block, date, dnd, layout, link, media, slate utilities, table transforms, and markdown parser contracts all passed after seam narrowing.
  • The selection holdouts are real exceptions, not laziness. moveSelection and shiftSelection depend on Plate-only capabilities, and forcing them onto createSlateEditor breaks the contract.
  • The markdown package helper was still cheating by importing MarkdownKit from apps/www, but that kit was just MarkdownPlugin.configure(...) plus remark plugins. The package tests do not need the app registry at all.
  • After the final repo-wide seam scan, the only remaining createPlateEditor usages are in React/provider/render/store suites, Plate plugin conversion tests, and the two known Plate-only selection APIs.
  • A final no-change audit found no remaining skips, placeholder titles, commented-out tests, or cross-spec imports in package specs.
  • The remaining __tests__/ specs are the expected ones: fixture-heavy docx and docx-io, plus the intentionally split withAutoformat suite.

Technical Decisions

DecisionRationale
Rewrite large hotspot specs before chasing broad title debtBigger signal gain per pass
Collapse single-helper split suites into one adjacent spec with it.eachEasier to scan, less duplicated setup
Keep one mixed-document smoke case for streaming markdownProves the broad contract without giant hand-written trees
Use explicit assertions instead of snapshots for AI streamingEasier to read and less fragile
Use KEYS or base plugins for mark and link type strings in package testsAvoids React-only import churn and typecheck failures
Build focused autoformat tests from local rule arrays before reaching for AutoformatKitCleaner contract, less duplicate setup
Add only the base plugins a block rule needs in package testsAvoids app-level coupling while keeping behavior real
Collapse multiple one-case mark specs into one matrix fileKeeps the suite dense without losing behavior coverage
Describe behavior semantically instead of mirroring raw option names in titleswhen normalization is disabled is clearer than when shouldNormalizeEditor false
Use explicit toBe(...) assertions for tiny whitespace-sensitive serializer outputsEasier to read, avoids snapshot churn, and keeps git diff --check happy
Delete and regenerate snapshot files after broad title renamesBun snapshot updates keep dead keys around
Use createSlateEditor for pure plugin option-store tests and selector extensionPlate React wiring adds noise when hooks and providers are not under test
For plugin composition suites, extract helper functions before adding more inline editor setupSorting and option-store cases are easier to read as getSortedKeys(...) and createStoreEditor(...) than repeated editor constructions
Pure plugin API and transform composition tests should also use createSlateEditorIf no React hook, provider, or Plate-only wiring is involved, the Plate seam is just noise
Commented-out tests should be deleted, not parked in the fileThey rot, confuse matrix scans, and are worse than an honest missing test
Rule-override suites should use one helper and table-drive repeated node-type caseswithBreakRules dropped from 725 lines to 472 without losing any behavior
Rule-action helpers should return the editor, not assert internallyKeeps setup DRY without tripping Biome's misplaced-assertion rule
Parser, deserializer, DnD, and insertData contract tests should default to createSlateEditorPlugin stores and transforms are enough; React adds nothing there
Markdown package tests must configure MarkdownPlugin locally instead of importing MarkdownKit from apps/wwwPackage tests should not depend on app registries
Remaining createPlateEditor files should be treated as a reviewed allowlist, not a future cleanup queueThose files are now legitimate React/provider/render/store or Plate-only boundary suites

Verification Log

TestInputExpectedActualStatus
AI streaming specsbun test packages/ai/src/react/ai-chat/streaming/streamInsertChunk.spec.tsx packages/ai/src/react/ai-chat/streaming/streamDeserializeMd.spec.tsx packages/ai/src/react/ai-chat/streaming/streamSerializeMd.spec.tsxRewritten streaming suite passes27 pass, 0 fail
AI buildpnpm turbo build --filter=./packages/aiPackage buildsPassed
AI typecheckpnpm turbo typecheck --filter=./packages/aiPackage typechecksPassed
Core + autoformat buildpnpm turbo build --filter=./packages/core --filter=./packages/autoformatPackages buildPassed
Core utils + autoformat specsbun test packages/core/src/lib/utils/mergeDeepToNodes.spec.ts packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsxRewritten suites pass11 pass, 0 fail
Core typecheckpnpm turbo typecheck --filter=./packages/corePackage typechecksPassed
Autoformat hotspot specsbun test packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/markup.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/ignoreTrim.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/mark/multiple-marks.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/singleCharTrigger.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsxRewritten suites pass18 pass, 0 fail
Autoformat buildpnpm turbo build --filter=./packages/autoformatPackage buildsPassed
Autoformat typecheckpnpm turbo typecheck --filter=./packages/autoformatPackage typechecksPassed
Autoformat touched-file Biomepnpm exec biome check --write packages/autoformat/src/lib/__tests__/withAutoformat/createAutoformatEditor.ts packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/markup.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/ignoreTrim.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/mark/multiple-marks.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/singleCharTrigger.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsxFormatting and lint passPassed, 1 file auto-fixed
Autoformat suite sweepbun test packages/autoformat/src/lib/__tests__/withAutoformat/block/list.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/code-block.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/heading.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/blockquote.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/preFormat.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/invalid.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/mark/basic-marks.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/mark/multiple-marks.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/markup.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/ignoreTrim.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/block/singleCharTrigger.spec.tsx packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsxRewritten block and mark suites pass36 pass, 0 fail
Autoformat rebuild after block cleanuppnpm turbo build --filter=./packages/autoformatPackage builds after final test editsPassed
Autoformat re-typecheck after block cleanuppnpm turbo typecheck --filter=./packages/autoformatPackage typechecks after final test editsPassed
Title-hardening targeted sweepbun test packages/core/src/react/components/Plate.spec.tsx packages/core/src/lib/plugins/slate-extension/transforms/init.spec.ts packages/slate/src/internal/transforms-extension/removeMarks.spec.tsx packages/markdown/src/lib/deserializer/deserializeMdList.spec.tsx packages/markdown/src/lib/serializer/serializeMd.spec.tsxCleaned suites pass with regenerated snapshots79 pass, 0 fail
Markdown snapshot regenerationbun test -u packages/markdown/src/lib/deserializer/deserializeMdList.spec.tsx packages/markdown/src/lib/serializer/serializeMd.spec.tsxSnapshot files recreated without stale keys18 pass, 0 fail
Repo-wide changed-spec sweep`git diff --name-only -- packagesrg '\.spec\.'xargs bun test`All changed specs still pass
Core + slate + markdown buildpnpm turbo build --filter=./packages/core --filter=./packages/slate --filter=./packages/markdownPackages buildPassed
Core + slate + markdown typecheckpnpm turbo typecheck --filter=./packages/core --filter=./packages/slate --filter=./packages/markdownPackages typecheckPassed
Repo-wide touched-file Biome`git diff --name-only -- . ':(exclude)pnpm-lock.yaml'xargs pnpm exec biome check --write`Formatting and lint pass on changed filesChecked 211 files, fixed 4
Title-debt scan`rg -n '^[[:space:]]*(ittestdescribe)\([^\n]*(shouldfixures
Snapshot-key scanrg -n 'exports\\[\\.*(shouldfixuresqouteserialize a
Diff checkgit diff --checkNo whitespace or patch-format issuesClean
Core plugin-composition specsbun test packages/core/src/internal/plugin/resolvePlugins.spec.tsx packages/core/src/internal/plugin/resolvePlugins-store.spec.tsxRefactored core plugin specs pass52 pass, 0 fail
Core rebuild after plugin-composition cleanuppnpm turbo build --filter=./packages/corePackage builds after refactorPassed
Core re-typecheck after plugin-composition cleanuppnpm turbo typecheck --filter=./packages/corePackage typechecks after refactorPassed
Pure plugin API seam sweepbun test packages/core/src/lib/utils/extendApi.spec.ts packages/core/src/internal/plugin/resolvePlugins.spec.tsx packages/core/src/internal/plugin/resolvePlugins-store.spec.tsxRefactored pure core composition specs pass72 pass, 0 fail
Core rebuild after pure seam cleanuppnpm turbo build --filter=./packages/corePackage builds after seam narrowingPassed
Core re-typecheck after pure seam cleanuppnpm turbo typecheck --filter=./packages/corePackage typechecks after seam narrowingPassed
Dead commented-spec sweepbun test packages/core/src/react/utils/shortcuts.spec.tsx packages/core/src/react/hooks/useEditableProps.spec.tsx packages/core/src/react/components/EditorMethodsEffect.spec.tsx packages/core/src/lib/plugins/html/utils/pluginDeserializeHtml.spec.ts packages/core/src/lib/plugins/slate-extension/SlateExtensionPlugin.spec.tsx packages/list-classic/src/lib/withList.spec.tsx packages/table/src/react/components/TableCellElement/setSelectedCellsBorder.spec.tsx packages/docx/src/lib/docx-cleaner/cleanDocx.spec.ts packages/media/src/lib/image/withImageUpload.spec.tsx packages/diff/src/lib/computeDiff.spec.tsTouched cleanup specs still pass after comment deletion90 pass, 0 fail
Dead commented-spec package buildpnpm turbo build --filter=./packages/core --filter=./packages/table --filter=./packages/list-classic --filter=./packages/docx --filter=./packages/media --filter=./packages/diffAffected packages buildPassed
Dead commented-spec package typecheckpnpm turbo typecheck --filter=./packages/core --filter=./packages/table --filter=./packages/list-classic --filter=./packages/docx --filter=./packages/media --filter=./packages/diffAffected packages typecheckPassed
Commented test scan`rg -n '^\s*//\s*(ittestdescribe)\(' packages --glob '.spec.ts' --glob '.spec.tsx'`No commented-out test blocks remain
Override-rule matrix cleanupbun test packages/core/src/lib/plugins/override/withBreakRules.spec.tsxRefactored break-rule suite still passes15 pass, 0 fail
Override-rule core buildpnpm turbo build --filter=./packages/coreCore still builds after break-rule refactorPassed
Override-rule core typecheckpnpm turbo typecheck --filter=./packages/coreCore still typechecks after break-rule refactorPassed
Delete-rule sibling cleanupbun test packages/core/src/lib/plugins/override/withDeleteRules.spec.tsxRefactored delete-rule suite still passes14 pass, 0 fail
Delete-rule core buildpnpm turbo build --filter=./packages/coreCore still builds after delete-rule refactorPassed
Delete-rule core typecheckpnpm turbo typecheck --filter=./packages/coreCore still typechecks after delete-rule refactorPassed
Core + table seam narrowingbun test packages/core/src/lib/utils/overrideEditor.spec.ts packages/core/src/lib/editor/SlateEditorMethods.spec.ts packages/basic-nodes/src/lib/BaseHeadingPlugin.spec.ts packages/core/src/lib/plugin/createSlatePlugin.spec.ts packages/table/src/lib/transforms/insertTable.spec.tsx packages/table/src/lib/transforms/insertTableColumn.spec.tsx packages/table/src/lib/transforms/deleteColumn.spec.tsx packages/table/src/lib/transforms/insertTableRow.spec.tsxDeterministic core and table seams pass on createSlateEditor93 pass, 0 fail
Core + table seam buildpnpm turbo build --filter=./packages/core --filter=./packages/basic-nodes --filter=./packages/tableTouched packages build after seam narrowingPassed
Core + table seam typecheckpnpm turbo typecheck --filter=./packages/core --filter=./packages/basic-nodes --filter=./packages/tableTouched packages typecheck after seam narrowingPassed
HTML + selection sweepbun test packages/core/src/lib/plugins/html/HtmlPlugin.spec.tsx packages/core/src/lib/plugins/html/utils/pluginDeserializeHtml.spec.ts packages/core/src/lib/plugins/html/utils/deserializeHtml.spec.tsx packages/core/src/lib/plugins/html/utils/deserializeHtmlElement.spec.tsx packages/core/src/lib/plugins/html/utils/deserializeHtmlNode.spec.tsx packages/core/src/lib/plugins/html/utils/deserializeHtmlNodeGoogleDocs.spec.tsx packages/core/src/lib/plugins/html/utils/htmlElementToLeaf.spec.tsx packages/core/src/lib/plugins/html/utils/htmlElementToElement.spec.tsx packages/core/src/lib/plugins/html/utils/htmlBodyToFragment.spec.tsx packages/selection/src/internal/transforms/selectBlocks.spec.tsx packages/selection/src/react/internal/api/setSelectedIds.spec.tsx packages/selection/src/react/utils/copySelectedBlocks.spec.tsx packages/selection/src/react/internal/api/moveSelection.spec.tsx packages/selection/src/react/internal/api/shiftSelection.spec.tsxHTML seams narrowed; selection exceptions verified90 pass, 0 fail
HTML + selection typecheckpnpm turbo typecheck --filter=./packages/core --filter=./packages/selectionTouched packages typecheck after seam and exception validationPassed
Code-block seam cleanupbun test packages/code-block/src/lib/queries/isCodeBlockEmpty.spec.tsx packages/code-block/src/lib/queries/isSelectionAtCodeBlockStart.spec.tsx packages/code-block/src/lib/setCodeBlockToDecorations.spec.ts packages/code-block/src/lib/transforms/indentCodeLine.spec.tsx packages/code-block/src/lib/transforms/insertCodeBlock.spec.tsx packages/code-block/src/lib/transforms/insertCodeLine.spec.tsx packages/code-block/src/lib/transforms/insertEmptyCodeBlock.spec.tsx packages/code-block/src/lib/transforms/outdentCodeLine.spec.tsx packages/code-block/src/lib/transforms/toggleCodeBlock.spec.tsx packages/code-block/src/lib/transforms/unwrapCodeBlock.spec.tsx packages/code-block/src/lib/withCodeBlock.spec.tsx packages/code-block/src/lib/withInsertDataCodeBlock.spec.tsx packages/code-block/src/lib/withInsertFragmentCodeBlock.spec.tsx packages/code-block/src/lib/withNormalizeCodeBlock.spec.tsx packages/code-block/src/react/CodeBlockPlugin.spec.tsx packages/core/src/lib/plugins/debug/DebugPlugin.spec.ts packages/core/src/lib/plugin/getEditorPlugin.spec.tsDeterministic code-block and pure plugin utility seams pass49 pass, 0 fail
Code-block seam buildpnpm turbo build --filter=./packages/code-block --filter=./packages/coreTouched packages build after seam cleanupPassed
Code-block seam typecheckpnpm turbo typecheck --filter=./packages/code-block --filter=./packages/coreTouched packages typecheck after seam cleanupPassed
Non-core seam sweepbun test packages/date/src/lib/BaseDatePlugin.spec.tsx packages/dnd/src/transforms/onHoverNode.spec.ts packages/dnd/src/transforms/onDropNode.spec.ts packages/layout/src/lib/transforms/setColumns.spec.tsx packages/layout/src/lib/transforms/toggleColumnGroup.spec.tsx packages/link/src/lib/withLink.spec.tsx packages/link/src/react/utils/getLinkAttributes.spec.ts packages/media/src/lib/image/withImageUpload.spec.tsx packages/slate/src/internal/editor/isEmpty.spec.tsx packages/slate/src/interfaces/node-children.spec.tsx packages/core/src/lib/utils/normalizeDescendantsToDocumentFragment.spec.tsx packages/markdown/src/lib/rules/defaultRule.spec.ts packages/markdown/src/lib/deserializer/deserializeMd.spec.tsx packages/list/src/lib/ListPlugin.spec.tsxDeterministic package seams pass on createSlateEditor119 pass, 0 fail
Non-core seam buildpnpm turbo build --filter=./packages/date --filter=./packages/dnd --filter=./packages/layout --filter=./packages/link --filter=./packages/media --filter=./packages/slate --filter=./packages/core --filter=./packages/markdown --filter=./packages/listTouched packages build after the final seam cleanupPassed
Non-core seam typecheckpnpm turbo typecheck --filter=./packages/date --filter=./packages/dnd --filter=./packages/layout --filter=./packages/link --filter=./packages/media --filter=./packages/slate --filter=./packages/core --filter=./packages/markdown --filter=./packages/listTouched packages typecheck after the final seam cleanupPassed
Final no-change audit: skips`rg -n "\b(ittest)\.skip\b\bdescribe\.skip\b" packages --glob ".spec.ts" --glob ".spec.tsx"`No skipped tests remain in package specs
Final no-change audit: title debt`rg -n "^[[:space:]]*(ittestdescribe)\([^\n]*(shouldfixures
Final no-change audit: commented tests`rg -n "^\s*//\s*(ittestdescribe)\(" packages --glob ".spec.ts" --glob ".spec.tsx"`No commented-out tests remain
Final no-change audit: cross-spec imports`rg -n "(\.spec\.test)['\"]from ['\"][^'\"]*\.specfrom ['\"][^'\"]\.test" packages --glob ".spec.ts" --glob "*.spec.tsx"`
Final no-change audit: intentional Plate allowlistrg -n "createPlateEditor\\(" packages --glob "*.spec.ts" --glob "*.spec.tsx"Remaining usages are intentional React/provider/render/store or Plate-only suitesAllowlist only
Final no-change audit: markdown helper leak`rg -n "MarkdownKitapps/www/src/registry/components/editor/plugins/markdown-kit" packages/markdown/src -g '!**/*.snap'`Markdown package no longer imports app registry helpersNo matches
DOCX app integration roundtripbun test apps/www/src/__tests__/package-integration/docx-io.roundtrip.spec.tsxMoved app-level docx-io roundtrip suite still passes5 pass, 0 fail
DOCX package buildpnpm turbo build --filter=./packages/docx-io@platejs/docx-io builds after removing app aliasesPassed
DOCX package typecheckpnpm --filter @platejs/docx-io typecheck@platejs/docx-io typechecks without pulling apps/www/srcPassed
App typecheck after moved DOCX seampnpm --filter www typecheckwww typechecks after owning the moved DOCX integration and direct depsPassed
Repo package typecheck after DOCX seam cleanuppnpm typecheckWorkspace packages still typecheck on the real graphPassed
Repo lint fix after DOCX seam cleanupbun lint:fixFormatting and Biome checks stay clean after the moveChecked 2585 files, 0 fixes

Error Log

TimestampErrorAttemptResolution
2026-03-06streamInsertChunk.spec.tsx helper triggered noMisplacedAssertion1Moved the assertion back into each it()
2026-03-06mergeDeepToNodes.spec.ts editor-root cases compared whole editor objects1Used a real editor and asserted on children plus root boundaries
2026-03-06autoformat typecheck failed on unresolved imports from @platejs/basic-nodes/react and @platejs/link/react in existing specs1Removed the React-only .key imports from the hotspot specs and typecheck passed
2026-03-06planning-with-files catchup script pointed at a missing ~/.codex/skills/planning-with-files path1Used the existing planning notes directly and later consolidated them into this plan file
2026-03-06bun test -u left dead snapshot keys in markdown after title renames1Deleted the affected snapshot files and regenerated them from scratch
2026-03-06Markdown serializer snapshots contained meaningful trailing spaces, so git diff --check flagged them1Replaced those two cases with explicit string assertions instead of snapshots
2026-03-06moveSelection and shiftSelection looked like easy seam reductions but failed when switched to createSlateEditor1Kept them on Plate and documented the exception instead of cargo-culting the rule
2026-03-06The markdown helper imported MarkdownKit from apps/www1Replaced it with local MarkdownPlugin.configure(...) plus remark plugins
2026-03-09Moving the DOCX roundtrip spec into www broke on Cannot find package 'mammoth' and then exposed src/types/unist.ts missing direct types2Added mammoth and @types/unist to apps/www so the moved app-owned integration and existing app types both resolve honestly

Resources

  • .agents/rules/testing.mdc
  • docs/plans/2026-03-06-test-suite-cleanup-plan.md
  • packages/ai/src/react/ai-chat/streaming/streamInsertChunk.spec.tsx
  • packages/core/src/lib/utils/mergeDeepToNodes.spec.ts
  • packages/autoformat/src/lib/__tests__/withAutoformat/trigger.spec.tsx
  • packages/autoformat/src/lib/__tests__/withAutoformat/createAutoformatEditor.ts
  • packages/autoformat/src/lib/__tests__/withAutoformat/text.spec.tsx
  • packages/autoformat/src/lib/__tests__/withAutoformat/markup.spec.tsx
  • packages/autoformat/src/lib/__tests__/withAutoformat/block/list.spec.tsx
  • packages/autoformat/src/lib/__tests__/withAutoformat/block/code-block.spec.tsx
  • packages/autoformat/src/lib/__tests__/withAutoformat/mark/basic-marks.spec.tsx
  • packages/core/src/react/components/Plate.spec.tsx
  • packages/core/src/lib/plugins/slate-extension/transforms/init.spec.ts
  • packages/slate/src/internal/transforms-extension/removeMarks.spec.tsx
  • packages/markdown/src/lib/serializer/serializeMd.spec.tsx
  • packages/markdown/src/lib/deserializer/deserializeMdList.spec.tsx
  • packages/core/src/internal/plugin/resolvePlugins.spec.tsx
  • packages/core/src/internal/plugin/resolvePlugins-store.spec.tsx
  • packages/core/src/lib/utils/extendApi.spec.ts
  • packages/markdown/src/lib/__tests__/createTestEditor.tsx
  • packages/date/src/lib/BaseDatePlugin.spec.tsx
  • packages/dnd/src/transforms/onHoverNode.spec.ts
  • packages/dnd/src/transforms/onDropNode.spec.ts
  • packages/layout/src/lib/transforms/setColumns.spec.tsx
  • packages/layout/src/lib/transforms/toggleColumnGroup.spec.tsx
  • packages/link/src/lib/withLink.spec.tsx
  • packages/link/src/react/utils/getLinkAttributes.spec.ts
  • packages/media/src/lib/image/withImageUpload.spec.tsx
  • packages/slate/src/internal/editor/isEmpty.spec.tsx
  • packages/slate/src/interfaces/node-children.spec.tsx
  • packages/core/src/lib/utils/normalizeDescendantsToDocumentFragment.spec.tsx
  • packages/markdown/src/lib/rules/defaultRule.spec.ts
  • packages/markdown/src/lib/deserializer/deserializeMd.spec.tsx
  • packages/list/src/lib/ListPlugin.spec.tsx

Visual or Browser Findings

  • None. This cleanup phase stayed fully local and terminal-based.

Current State

  • Repo-wide changed-spec sweep: 1590 pass, 0 fail across 209 files.
  • pnpm turbo build --filter=./packages/core --filter=./packages/slate --filter=./packages/markdown passed.
  • pnpm turbo typecheck --filter=./packages/core --filter=./packages/slate --filter=./packages/markdown passed.
  • git diff --check is clean.
  • The tracked title-debt scan for should, fixures, and qoute in spec titles returns no matches.
  • The snapshot-key scan for stale pre-cleanup titles returns no matches.
  • The remaining largest specs are mostly boundary suites, not obvious dead-weight integration tests.
  • extendApi.spec.ts is no longer using the Plate seam for pure plugin composition.
  • Repo-wide scan for commented-out it, test, and describe blocks in packages/**/*.spec.* returns no matches.
  • withBreakRules.spec.tsx dropped from 725 lines to 472 while keeping the same contract coverage.
  • withDeleteRules.spec.tsx dropped from 648 lines to 496 while keeping the same contract coverage.
  • The repo-wide createPlateEditor scan is now an intentional allowlist of React/provider/render/store suites plus the two Plate-only selection APIs.
  • packages/markdown/src/lib/__tests__/createTestEditor.tsx no longer imports apps/www.
  • packages/docx-io/tsconfig.json no longer aliases apps/www, so package typecheck stops pulling app-only registry code into the package graph.
  • apps/www/src/__tests__/package-integration/docx-io.roundtrip.spec.tsx now owns the DOCX export/import roundtrip coverage that depends on app kits and static registry components.
  • apps/www/src/__tests__/package-integration is the home for app-owned cross-package integration tests; buckets stay local under that folder instead of spreading through src/lib.