docs/(guides)/unit-testing.mdx
This guide outlines best practices for unit testing Plate plugins and components using @platejs/test-utils.
npm install @platejs/test-utils
Add the JSX pragma at the top of your test file:
/** @jsx jsx */
import { jsx } from '@platejs/test-utils';
jsx; // so Biome doesn't remove unused imports
This allows you to use JSX syntax for creating editor values.
Use JSX to represent editor states:
const input = (
<editor>
<hp>
Hello<cursor /> world
</hp>
</editor>
) as any as PlateEditor;
Node elements like <hp />, <hul />, <hli /> represent different types of nodes.
Special elements like <cursor />, <anchor />, and <focus /> represent selection states.
createPlateEditor to set up the editorExample testing bold formatting:
it('should apply bold formatting', () => {
const input = (
<editor>
<hp>
Hello <anchor />
world
<focus />
</hp>
</editor>
) as any as PlateEditor;
const output = (
<editor>
<hp>
Hello <htext bold>world</htext>
</hp>
</editor>
) as any as PlateEditor;
const editor = createPlateEditor({
plugins: [BoldPlugin],
value: input.children,
selection: input.selection,
});
// Apply transform directly
editor.tf.toggleMark('bold');
expect(editor.children).toEqual(output.children);
});
Test how operations affect the editor's selection:
it('should collapse selection on backspace', () => {
const input = (
<editor>
<hp>
He<anchor />llo wor<focus />ld
</hp>
</editor>
) as any as PlateEditor;
const output = (
<editor>
<hp>
He<cursor />ld
</hp>
</editor>
) as any as PlateEditor;
const editor = createPlateEditor({
value: input.children,
selection: input.selection,
});
editor.tf.deleteBackward();
expect(editor.children).toEqual(output.children);
expect(editor.selection).toEqual(output.selection);
});
When you need to test keyboard handlers directly:
it('should call the onKeyDown handler', () => {
const input = (
<editor>
<hp>
Hello <anchor />world<focus />
</hp>
</editor>
) as any as PlateEditor;
// Create a mock handler to verify it's called
const onKeyDownMock = jest.fn();
const editor = createPlateEditor({
value: input.children,
selection: input.selection,
plugins: [
{
key: 'test',
handlers: {
onKeyDown: onKeyDownMock,
},
},
],
});
// Create the keyboard event
const event = new KeyboardEvent('keydown', {
key: 'Enter',
}) as any;
// Call the handler directly
editor.plugins.test.handlers.onKeyDown({
...getEditorPlugin(editor, { key: 'test' }),
event,
});
// Verify the handler was called
expect(onKeyDownMock).toHaveBeenCalled();
});
For complex plugins like tables, test various scenarios by directly applying transforms:
describe('Table plugin', () => {
it('should insert a table', () => {
const input = (
<editor>
<hp>
Test<cursor />
</hp>
</editor>
) as any as PlateEditor;
const output = (
<editor>
<hp>Test</hp>
<htable>
<htr>
<htd>
<hp>
<cursor />
</hp>
</htd>
<htd>
<hp></hp>
</htd>
</htr>
<htr>
<htd>
<hp></hp>
</htd>
<htd>
<hp></hp>
</htd>
</htr>
</htable>
</editor>
) as any as PlateEditor;
const editor = createPlateEditor({
value: input.children,
selection: input.selection,
plugins: [TablePlugin],
});
// Call transform directly
editor.tf.insertTable({ rows: 2, columns: 2 });
expect(editor.children).toEqual(output.children);
expect(editor.selection).toEqual(output.selection);
});
});
Test how different plugin options affect behavior:
describe('when undo is enabled', () => {
it('should undo text format upon delete', () => {
const input = (
<fragment>
<hp>
1/<cursor />
</hp>
</fragment>
) as any;
const output = (
<fragment>
<hp>
1/4<cursor />
</hp>
</fragment>
) as any;
const editor = createPlateEditor({
plugins: [
AutoformatPlugin.configure({
options: {
enableUndoOnDelete: true,
rules: [
{
format: '¼',
match: '1/4',
mode: 'text',
},
],
},
}),
],
value: input,
});
// Trigger the autoformat
editor.tf.insertText('4');
// Simulate backspace key
const event = new KeyboardEvent('keydown', {
key: 'backspace',
}) as any;
// Call the handler
editor.getPlugin({key: KEYS.autoformat}).handlers.onKeyDown({
...getEditorPlugin(editor, AutoformatPlugin),
event,
});
// With enableUndoOnDelete: true, pressing backspace should restore the original text
expect(input.children).toEqual(output.children);
});
});
While mocking can be useful for isolating specific behaviors, Plate tests often assess actual editor children and selection after transforms. This approach ensures that plugins work correctly with the entire editor state.