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 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 jumpTo

Warning: This API is now obsolete.

  • use selectText instead.

Jump to the specified position in the editor.

Signature:

jumpTo(start: 'start' | 'end' | number, end?: number): this;

Parameters:

ParameterTypeDescription
start'start' | 'end' | numbera number position or the shorthand 'start' | 'end'
endnumber(Optional) the option end position of the new selection

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.

TODO - this is overly simplistic and doesn't fully express what prosemirror can do so will need to be improved.

Signature:

paste(content: ProsemirrorNode | string): this;

Parameters:

ParameterTypeDescription
contentProsemirrorNode | string

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 pasteContent()

A very basic broken paste implementation for jsdom and prosemirror.

Signature:

export declare function pasteContent(props: TestEditorViewProps & TestEditorViewProps & {
content: ProsemirrorNode | string;
}): void;

Parameters:

ParameterTypeDescription
propsTestEditorViewProps & TestEditorViewProps & { content: ProsemirrorNode | string; }

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 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 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_cell" | "table_header" | "table_row" | "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 declare type EventType = keyof typeof rawEventMap | 'doubleClick' | 'tripleClick';