Skip to main content

jest-prosemirror

package jest-prosemirror

Write expressive tests for your prosemirror editor

Remarks:

## The problem

You want to write tests for some of your prosemirror editor but you don't know where to start. You know you should avoid testing implementation details and just want to be sure that your commands and plugins produce the correct underlying prosemirror state.

## This solution

jest-prosemirror takes inspiration from the [testing-library](https://github.com/testing-library/react-testing-library) mantra and enables you to write more intuitive tests for your prosemirror editor.

## Installation

yarn add jest-prosemirror # yarn
pnpm add jest-prosemirror # pnpm
npm install jest-prosemirror # npm

## Getting started

### Quick setup

For a quick setup add the following to your jest.config.js file.

module.exports = {
setupFilesAfterEnv: ['jest-prosemirror/environment'],
testEnvironment: 'jsdom', // Required for dom manipulation
};

This will automatically:

  • Add the jest assertions toTransformNode and toEqualProsemirrorNode.

If you are using typescript then add this to your tsconfig.json file for global type support.

{
"compilerOptions": {
"types": ["jest-prosemirror"]
}
}

### Manual setup

Create a jest.framework.dom.ts file and add the following

import { prosemirrorMatchers } from 'jest-prosemirror';

// Add jest-prosemirror assertions
expect.extend(prosemirrorMatchers);

In your jest.config.js add the created file to your configuration.

module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.framework.dom.ts'],
testEnvironment: 'jsdom', // Required for dom manipulation
};

## Snapshot serializer

This package exports a serializer for better snapshot testing of prosemirror primitives. To set this up add the following to your jest.config.js file.

module.exports = {
snapshotSerializers: ['jest-prosemirror/serializer'],
};

Alternatively, you can add the following to your jest.framework.dom.ts file.

import { prosemirrorSerializer } from 'jest-prosemirror';

// Add the serializer for use throughout all the configured test files.
expect.addSnapshotSerializer(prosemirrorSerializer);

class ProsemirrorTestChain​

An instance of this class is returned when using createEditor. It allows for chaining of test operations and adds some useful helpers to your testing toolkit.

Signature:

export declare class ProsemirrorTestChain 

ProsemirrorTestChain.(constructor)​

Constructs a new instance of the ProsemirrorTestChain class

Signature:

constructor(view: TestEditorView);

Parameters:

ParameterTypeDescription
viewTestEditorView

property copied​

The content to write to the clipboard if copy the current selection.

Signature:

get copied(): {
text: string;
html: string;
};

property debug​

Logs to the dom for help debugging your tests.

Signature:

debug: () => this;

property doc​

The current prosemirror node document

Signature:

get doc(): ProsemirrorNode;

property end​

The end of the current selection.

Signature:

get end(): number;

property remirrorCommand​

Takes any remirror command as an input and dispatches it within the document context.

Signature:

readonly remirrorCommand: (command: CommandFunction) => this;

property schema​

The prosemirror schema.

Signature:

get schema(): Schema;

property selection​

The prosemirror selection.

Signature:

get selection(): Selection;

property selectText​

Selects the text between the provided selection primitive.

Signature:

readonly selectText: (selection: PrimitiveSelection) => this;

property start​

The start of the current selection.

Signature:

get start(): number;

property state​

The prosemirror state.

Signature:

get state(): EditorState;

property view​

The prosemirror view.

Signature:

view: TestEditorView;

method backspace​

Simulates a backspace keypress and deletes text backwards.

Signature:

backspace(times?: number): this;

Parameters:

ParameterTypeDescription
timesnumber(Optional)

Returns:

this

method callback​

Callback function which receives the start, end, state, view, schema and selection properties and allows for easier testing of the current state of the editor.

Signature:

callback(fn: (content: ReturnValueCallbackProps) => void): this;

Parameters:

ParameterTypeDescription
fn(content: ReturnValueCallbackProps) => void

Returns:

this

method command​

Run the command within the prosemirror editor.

Signature:

command(command: ProsemirrorCommandFunction): this;

Parameters:

ParameterTypeDescription
commandProsemirrorCommandFunctionthe command function to run

Returns:

this

Remarks:

test('commands are run', () => {
createEditor(doc(p('<cursor>')))
.command((state, dispatch) => {
if (dispatch) {
dispatch(state.tr.insertText('hello'));
}
})
.callback(content => {
expect(content.state.doc).toEqualProsemirrorDocument(doc(p('hello')));
})
})

method fire​

Fire an event in the editor (very hit and miss).

Signature:

fire(props: Omit<FireEventAtPositionProps, 'view'>): this;

Parameters:

ParameterTypeDescription
propsOmit<FireEventAtPositionProps, 'view'>the props when firing an event

Returns:

this

method insertText​

Insert text into the editor at the current position.

Signature:

insertText(text: string): this;

Parameters:

ParameterTypeDescription
textstringthe text to insert

Returns:

this

method of​

A static helper utility for creating new TestReturn values.

Signature:

static of(view: TestEditorView): ProsemirrorTestChain;

Parameters:

ParameterTypeDescription
viewTestEditorView

Returns:

ProsemirrorTestChain

method overwrite​

Overwrite all the current content within the editor.

Signature:

overwrite(newDocument: ProsemirrorNode): this;

Parameters:

ParameterTypeDescription
newDocumentProsemirrorNode

Returns:

this

method paste​

Paste text into the editor.

Signature:

paste(content: ProsemirrorNode | string | {
text: string;
html: string;
plainText?: boolean;
}): this;

Parameters:

ParameterTypeDescription
contentProsemirrorNode | string | { text: string; html: string; plainText?: boolean; }

Returns:

this

method press​

Simulate a keypress which is run through the editor's key handlers.

**NOTE** This only simulates the events. For example an Enter would run all enter key handlers but not actually create a new line.

Signature:

press(char: string): this;

Parameters:

ParameterTypeDescription
charstringthe character to type

Returns:

this

method shortcut​

Type a keyboard shortcut - e.g. Mod-Enter.

**NOTE** This only simulates the events. For example an Mod-Enter would run all enter key handlers but not actually create a new line.

Signature:

shortcut(mod: string): this;

Parameters:

ParameterTypeDescription
modstringthe keyboard shortcut to type

Returns:

this

function apply()​

Apply the command to the prosemirror node passed in.

Returns a tuple matching the following structure [ bool => was the command successfully applied taggedDoc => the new doc as a result of the command state => The new editor state after applying the command ]

Signature:

export declare function apply(taggedDocument: ProsemirrorNode, command: ProsemirrorCommandFunction, result?: ProsemirrorNode): ApplyReturn;

Parameters:

ParameterTypeDescription
taggedDocumentProsemirrorNode
commandProsemirrorCommandFunction
resultProsemirrorNode(Optional)

Returns:

ApplyReturn

function backspace()​

Simulate a backspace key press.

Signature:

export declare function backspace(props: BackspaceProps): void;

Parameters:

ParameterTypeDescription
propsBackspaceProps

Returns:

void

function createEditor()​

Create a test prosemirror editor an pass back helper properties and methods.

Signature:

export declare function createEditor(taggedDocument: ProsemirrorNode, options?: CreateEditorOptions): ProsemirrorTestChain;

Parameters:

ParameterTypeDescription
taggedDocumentProsemirrorNode
optionsCreateEditorOptions(Optional) the CreateEditorOptions interface which includes all DirectEditorProps except for state.

Returns:

ProsemirrorTestChain

Remarks:

The call to create editor can be chained with various commands to enable testing of the editor at each step along it's state without the need for intermediate holding variables.

The created editor is automatically cleaned after each test.

import { createEditor } from 'jest-remirror';

test('`keyBindings`', () => {
const keyBindings = {
Enter: jest.fn((params: SuggestKeyBindingProps) => {
params.command();
}),
};

const plugin = suggest({char: '@', name: 'at', keyBindings, matchOffset: 0,
createCommand: ({ view }) => () =>
view.dispatch(view.state.tr.insertText('awesome')),
});

createEditor(doc(p('<cursor>')), { plugins: [plugin] }) .insertText('@')
.press('Enter')
.callback(content => {
expect(content.state.doc).toEqualProsemirrorNode(doc(p('@awesome')));
});
});

function createEvents()​

Signature:

export declare function createEvents<CreatedEvent extends Event>(event: EventType, options: Shape): CreatedEvent[];

Parameters:

ParameterTypeDescription
eventEventType
optionsShape

Returns:

CreatedEvent[]

function createState()​

Create the editor state for a tagged prosemirror doc

Signature:

export declare function createState(taggedDoc: ProsemirrorNode, plugins?: ProsemirrorPlugin[]): EditorState;

Parameters:

ParameterTypeDescription
taggedDocProsemirrorNode
pluginsProsemirrorPlugin[](Optional)

Returns:

EditorState

function dispatchAllSelection()​

Select everything in the current doc.

Signature:

export declare function dispatchAllSelection(view: TestEditorView): void;

Parameters:

ParameterTypeDescription
viewTestEditorView

Returns:

void

function dispatchAnchorTextSelection()​

Dispatch a text selection from start to [end]

Signature:

export declare function dispatchAnchorTextSelection(props: DispatchAnchorTextSelectionProps): void;

Parameters:

ParameterTypeDescription
propsDispatchAnchorTextSelectionProps

Returns:

void

function dispatchCellSelection()​

Signature:

export declare function dispatchCellSelection(props: DispatchNodeSelectionProps): void;

Parameters:

ParameterTypeDescription
propsDispatchNodeSelectionProps

Returns:

void

function dispatchNodeSelection()​

Dispatch a text selection from the provided pos.

Signature:

export declare function dispatchNodeSelection(props: DispatchNodeSelectionProps): void;

Parameters:

ParameterTypeDescription
propsDispatchNodeSelectionProps

Returns:

void

function dispatchTextSelection()​

Dispatch a text selection from start to [end]

Signature:

export declare function dispatchTextSelection(props: DispatchTextSelectionProps): void;

Parameters:

ParameterTypeDescription
propsDispatchTextSelectionProps

Returns:

void

function findTextNode()​

Find the first text node with the provided string.

Signature:

export declare function findTextNode(node: Node, text: string): Node | undefined;

Parameters:

ParameterTypeDescription
nodeNode
textstring

Returns:

Node | undefined

function fireEventAtPosition()​

Fires an event at the provided position or the current selected position in the dom.

Signature:

export declare function fireEventAtPosition(props: FireEventAtPositionProps): void;

Parameters:

ParameterTypeDescription
propsFireEventAtPositionProps

Returns:

void

function flush()​

Flushes the dom

Signature:

export declare function flush(view: TestEditorView): void;

Parameters:

ParameterTypeDescription
viewTestEditorView

Returns:

void

function forwardDelete()​

Simulate a backspace key press.

Signature:

export declare function forwardDelete(props: BackspaceProps): void;

Parameters:

ParameterTypeDescription
propsBackspaceProps

Returns:

void

function initSelection()​

Initialize the selection based on the passed in tagged node via it's cursor.

The supported tags are ['cursor', 'node', 'start', 'end', 'anchor', 'all']

Signature:

export declare function initSelection(taggedDoc: ProsemirrorNode): Selection | undefined;

Parameters:

ParameterTypeDescription
taggedDocProsemirrorNode

Returns:

Selection | undefined

function insertText()​

Insert text from the provided index. Each key is entered individually to better simulate calls to handleTextInput.

Signature:

export declare function insertText(props: InsertTextProps): void;

Parameters:

ParameterTypeDescription
propsInsertTextProps

Returns:

void

function pmBuild()​

A short hand way for building prosemirror test builders with the core nodes already provided - doc - paragraph | 'p' - text

Signature:

export declare function pmBuild<Types extends BuilderTypes = BuilderTypes>(testSchema: EditorSchema, names: Types): BuilderReturns<Types & DefaultBuilderTypes>;

Parameters:

ParameterTypeDescription
testSchemaEditorSchemaThe schema to use which provided a doc, paragraph and text schema
namesTypesthe extra marks and nodes to provide with their attributes

Returns:

BuilderReturns<Types & DefaultBuilderTypes>

function press()​

Press a key.

Signature:

export declare function press(props: PressProps): void;

Parameters:

ParameterTypeDescription
propsPressProps

Returns:

void

function selectionFor()​

Returns a selection regardless of whether anything is tagged in the provided doc

Signature:

export declare function selectionFor(taggedDoc: ProsemirrorNode): Selection;

Parameters:

ParameterTypeDescription
taggedDocProsemirrorNode

Returns:

Selection

function serializeForClipboard()​

Warning: This API is now obsolete.

You can now use view.serializeForClipboard() directly.

Signature:

export declare function serializeForClipboard(view: EditorView, slice: Slice): {
dom: HTMLElement;
text: string;
slice: Slice;
};

Parameters:

ParameterTypeDescription
viewEditorView
sliceSlice

Returns:

{ dom: HTMLElement; text: string; slice: Slice; }

function shortcut()​

Runs a keyboard shortcut.

Signature:

export declare function shortcut(props: KeyboardShortcutProps): void;

Parameters:

ParameterTypeDescription
propsKeyboardShortcutProps

Returns:

void

function taggedDocHasSelection()​

Checks that the tagged doc has a selection

Signature:

export declare function taggedDocHasSelection(taggedDoc: ProsemirrorNode): boolean;

Parameters:

ParameterTypeDescription
taggedDocProsemirrorNode

Returns:

boolean

variable a​

Signature:

a: pm.MarkBuilder

variable atomBlock​

Signature:

atomBlock: pm.NodeBuilder

variable atomContainer​

Signature:

atomContainer: pm.NodeBuilder

variable atomInline​

Signature:

atomInline: pm.NodeBuilder

variable blockquote​

Signature:

blockquote: pm.NodeBuilder

variable br​

Signature:

br: NodeBuilder

variable code_block​

Signature:

code_block: NodeBuilder

variable code​

Signature:

code: pm.MarkBuilder

variable codeBlock​

Signature:

codeBlock: NodeBuilder

variable doc​

Signature:

doc: pm.NodeBuilder

variable em​

Signature:

em: pm.MarkBuilder

variable h1​

Signature:

h1: pm.NodeBuilder

variable h2​

Signature:

h2: pm.NodeBuilder

variable h3​

Signature:

h3: pm.NodeBuilder

variable h4​

Signature:

h4: pm.NodeBuilder

variable h5​

Signature:

h5: pm.NodeBuilder

variable h6​

Signature:

h6: pm.NodeBuilder

variable hardBreak​

Signature:

hardBreak: NodeBuilder

variable heading​

Signature:

heading: NodeBuilder

variable horizontalRule​

Signature:

horizontalRule: NodeBuilder

variable hr​

Signature:

hr: NodeBuilder

variable image​

Signature:

image: NodeBuilder

variable img​

Signature:

img: NodeBuilder

variable li​

Signature:

li: pm.NodeBuilder

Signature:

link: pm.MarkBuilder

variable ol​

Signature:

ol: pm.NodeBuilder

variable p​

Signature:

p: pm.NodeBuilder

variable paragraph​

Signature:

paragraph: pm.NodeBuilder

variable parseFromClipboard​

Signature:

parseFromClipboard: (view: EditorView, text: string, html: string | null, plainText: boolean, $context: ResolvedPos) => Slice | null

variable pre​

Signature:

pre: NodeBuilder

variable prosemirrorMatchers​

Signature:

prosemirrorMatchers: jest.ExpectExtendMap

variable prosemirrorSerializer​

Jest serializer for prosemirror nodes and the editor state.

Signature:

prosemirrorSerializer: jest.SnapshotSerializerPlugin

variable schema​

Signature:

schema: Schema<"doc" | "paragraph" | "text" | "blockquote" | "heading" | "code_block" | "hard_break" | "image" | "table" | "table_row" | "table_cell" | "table_header" | "atomInline" | "atomBlock" | "containerWithRestrictedContent" | "orderedList" | "bulletList" | "listItem" | "horizontalRule" | "atomContainer", "link" | "em" | "strong" | "code" | "strike">

variable setupProsemirrorEnvironment​

Setup the environment automatically for jest-prosemirror

Signature:

setupProsemirrorEnvironment: () => void

variable strong​

Signature:

strong: pm.MarkBuilder

variable table​

Signature:

table: pm.NodeBuilder

variable tableCell​

Signature:

tableCell: pm.NodeBuilder

variable tableHeaderCell​

Signature:

tableHeaderCell: pm.NodeBuilder

variable tableRow​

Signature:

tableRow: pm.NodeBuilder

variable td​

Signature:

td: pm.NodeBuilder

variable tdCursor​

Signature:

tdCursor: ProsemirrorNode

variable tdEmpty​

Signature:

tdEmpty: ProsemirrorNode

variable text​

Signature:

text: pm.NodeBuilder

variable th​

Signature:

th: pm.NodeBuilder

variable thCursor​

Signature:

thCursor: ProsemirrorNode

variable thEmpty​

Signature:

thEmpty: ProsemirrorNode

variable tr​

Signature:

tr: pm.NodeBuilder

variable ul​

Signature:

ul: pm.NodeBuilder

interface ApplyReturn​

The return type for the apply method which

Signature:

export interface ApplyReturn extends TaggedDocProps, EditorStateProps 

Extends: TaggedDocProps, EditorStateProps

Remarks:

(Some inherited members may not be shown because they are not represented in the documentation.)

property pass​

True when the command was applied successfully.

Signature:

pass: boolean;

property taggedDoc​

A tagged ProsemirrorNode which can hold cursor information from the passed in text.

Signature:

taggedDoc: ProsemirrorNode;

interface CommandTransformation​

Tests that a command run transform the nodes from one state to another. The second state is optional if nothing has changed.

Signature:

export interface CommandTransformation 

property from​

The initial prosemirror node.

import { doc, p, strong} from 'jest-prosemirror';

const from = doc(p('Hello ', strong('Friend')));

Signature:

from: ProsemirrorNode;

property to​

The output of the command transformation.

import { doc, p, strong} from 'jest-prosemirror';

const to = doc(p(strong('Friend')));

This is optional and can be omitted if the transformation doesn't produce any results.

Signature:

to?: ProsemirrorNode;

interface CreateEditorOptions​

Signature:

export interface CreateEditorOptions extends Omit<DirectEditorProps, 'state' | 'plugins'> 

Extends: Omit<DirectEditorProps, 'state' | 'plugins'>

(Some inherited members may not be shown because they are not represented in the documentation.)

property autoClean​

Whether to auto remove the editor from the dom after each test. It is advisable to leave this unchanged.

Signature:

autoClean?: boolean;

property plugins​

The plugins that the test editor should use.

Signature:

plugins?: ProsemirrorPlugin[];

property rules​

The input rules that the test editor should use.

Signature:

rules?: InputRule[];

interface FireProps​

Signature:

export interface FireProps 

property event​

The event to fire on the view

Signature:

event: EventType;

property options​

Options passed into the event

Signature:

options?: Shape;

property position​

Override the default position to use

Signature:

position?: number;

interface InsertTextProps​

Signature:

export interface InsertTextProps extends TestEditorViewProps, TextProps 

Extends: TestEditorViewProps, TextProps

(Some inherited members may not be shown because they are not represented in the documentation.)

property start​

The start point of text insertion

Signature:

start: number;

property view​

An instance of the test editor view which allows for dispatching events and also containers TaggedProsemirrorNodes

Signature:

view: TestEditorView;

interface ReturnValueCallbackProps​

Signature:

export interface ReturnValueCallbackProps extends TestEditorViewProps, EditorStateProps, SelectionProps 

Extends: TestEditorViewProps, EditorStateProps, SelectionProps

(Some inherited members may not be shown because they are not represented in the documentation.)

property debug​

Pretty log the current view to the dom.

Signature:

debug: () => void;

property doc​

Signature:

doc: ProsemirrorNode;

property end​

Signature:

end: number;

property schema​

Signature:

schema: Schema;

property start​

Signature:

start: number;

property view​

An instance of the test editor view which allows for dispatching events and also containers TaggedProsemirrorNodes

Signature:

view: TestEditorView;

interface TaggedDocProps​

Signature:

export interface TaggedDocProps 

property taggedDoc​

A tagged ProsemirrorNode which can hold cursor information from the passed in text.

Signature:

taggedDoc: ProsemirrorNode;

interface TaggedProsemirrorNode​

Signature:

export interface TaggedProsemirrorNode extends TaggedFlatObject, ProsemirrorNode 

Extends: TaggedFlatObject, ProsemirrorNode

(Some inherited members may not be shown because they are not represented in the documentation.)

interface TestEditorView​

Signature:

export interface TestEditorView extends EditorView 

Extends: EditorView

(Some inherited members may not be shown because they are not represented in the documentation.)

property dispatchEvent​

Signature:

dispatchEvent: (event: string | CustomEvent | {
type: string;
}) => void;

property domObserver​

Signature:

domObserver: {
flush: () => void;
};

interface TestEditorViewProps​

Signature:

export interface TestEditorViewProps 

property view​

An instance of the test editor view which allows for dispatching events and also containers TaggedProsemirrorNodes

Signature:

view: TestEditorView;

type EventType​

Signature:

export type EventType = keyof typeof rawEventMap | 'doubleClick' | 'tripleClick';