docs/solutions/logic-errors/2026-04-22-slate-react-cut-proof-must-use-real-shortcut-and-assert-selection.md
Cut behavior can look correct if tests only assert clipboard payload or model text deletion.
That is not enough for Slate browser editing. After cutting selected content, the model selection and visible DOM selection must land at the cut start.
ClipboardEvent('cut') copied the right fragment data and deleted
the selected text.null.editor.clipboard.assert.types(...) after
the cut, but that helper performs another copy and changed the collapsed
selection again.onCut path, but it does not prove the real browser
shortcut lifecycle.Use the real browser shortcut path for the browser proof:
await editor.selection.select({
anchor: { path: [0, 0], offset: 1 },
focus: { path: [0, 0], offset: 9 },
})
await editor.root.press('ControlOrMeta+X')
expect(await editor.clipboard.readText()).toBe('lpha bet')
expect(await editor.clipboard.readHtml()).toContain('data-slate-fragment')
await editor.assert.text('aa')
await editor.assert.selection({
anchor: { path: [0, 0], offset: 1 },
focus: { path: [0, 0], offset: 1 },
})
Fix the product path by preserving the cut-start point, deleting the fragment, restoring a collapsed selection, and syncing DOM focus/selection:
const collapsePointRef = Editor.pointRef(editor, Range.start(selection))
Editor.deleteFragment(editor)
const collapsePoint = collapsePointRef.unref()
if (collapsePoint) {
Transforms.select(editor, {
anchor: collapsePoint,
focus: collapsePoint,
})
ReactEditor.focus(editor)
}
For selected block voids, also assert the React cut handler returns the same model-owned repair contract as expanded cuts. Deleting the void node is not enough; the runtime needs a command trace and repair request so the DOM caret does not depend on browser luck:
const result = applyEditableCut({ editor, event, readOnly: false })
expect(result.command).toEqual({ kind: 'delete-fragment' })
expect(result.repair).toEqual({
focus: true,
kind: 'repair-caret',
selectionSourceTransition: {
preferModelSelection: true,
reason: 'model-command',
selectionSource: 'model-owned',
},
})
The user-facing cut contract is not just "clipboard got data" or "text was deleted".
The full contract is:
Editor.pointRef(...) tracks the intended collapsed point through the deletion.
ReactEditor.focus(...) then syncs the browser selection back to that model
selection.
ControlOrMeta+X when the claim is user-path
editing behavior.ClipboardEvent('cut') as a narrow React handler diagnostic,
not full browser editing proof.