Skip to Content
API Referencecorecore/testing

@stesura/core/testing

Helpers for writing tests against the Stesura schema. Build documents with the tb builders and testDoc, which wraps content in a section and validates it against the schema.

Requires prosemirror-test-builder, an optional peer dependency that is not auto-installed: add it to your devDependencies. tempEditor mounts a real EditorView, so it needs a DOM (for example jsdom).

Functions & values

checkedDoc

Validates a built doc against the schema (doc.check()) before handing it to a test, so schema-invalid fixtures fail loudly at build time instead of silently producing states no real editor could reach.

Parameters:
NameTypeDefault
docT
Returns:
T

colCount

Cell count of the first row of the table around the selection.

Parameters:
NameTypeDefault
stateEditorState
Returns:
number

collectAttr

attrName values with their positions, across nodes matching predicate (default: block nodes) that declare the attr.

Parameters:
NameTypeDefault
docNode
attrNamestring
predicate(node: Node) => boolean
Returns:
{ pos: number; value: unknown; }[]

expectSectionIntegrity

Throws (with every problem listed) unless after is a sound sectioned doc: it passes doc.check() and its section ids are unique and non-null. Without allowStructure, it must also keep before’s sections: the same [sectionId, headerId, footerId] triples, each header and footer eq its old self.

Framework-free, so any test runner can use it.

Parameters:
NameTypeDefault
beforeNode
afterNode
__2{ allowStructure?: boolean; }
Returns:
void

findFirstInContent

Finds the first node of typeName inside the first section_content, returning the node and its absolute position in the document.

Use this instead of doc.descendants when you want to ignore the identical empty paragraphs that live in the section header and footer.

Parameters:
NameTypeDefault
docNode
typeNamestring
Returns:
{ node: Node; pos: number; } | null

getAllSections

Returns all section nodes in the doc as an array.

Parameters:
NameTypeDefault
stateEditorState
Returns:
Node[]

getCommentAttrs

Attrs of the LAST comment_open and comment_close in the doc (meant for single-comment fixtures).

Parameters:
NameTypeDefault
stateEditorState
Returns:
{ open?: CommentOpenAttrs; close?: CommentOpenAttrs; }

getCursorSection

Returns the section node at depth 1 that contains the cursor.

Parameters:
NameTypeDefault
stateEditorState
Returns:
Node

getTableNode

The table around the selection head; throws when there is none.

Parameters:
NameTypeDefault
stateEditorState
Returns:
Node

make2x2State

A 2×2 table (2 rows, 2 cols) with cursor in cell (0,0).

Parameters:
This function does not accept any parameters.
Returns:
EditorState

make3x3State

A 3×3 table with cursor in cell (0,0).

Parameters:
This function does not accept any parameters.
Returns:
EditorState

makeCell

Creates a table_cell with an empty paragraph inside.

Parameters:
NameTypeDefault
colwidthnumber
Returns:
Node

makeCellWithText

Creates a table_cell with the given text inside.

Parameters:
NameTypeDefault
textstring
colwidthnumber
Returns:
Node

makeLinkEditorState

EditorState with the given range selected, for exercising the link commands.

Parameters:
NameTypeDefault
docTaggedNode
fromnumber
tonumber
Returns:
EditorState

makeRow

Creates a table_row from an array of cells.

Parameters:
NameTypeDefault
cellsNode[]
Returns:
Node

makeTable

Creates a table node from an array of rows.

Parameters:
NameTypeDefault
rowsNode[]
Returns:
Node

rowCount

Row count of the table around the selection.

Parameters:
NameTypeDefault
stateEditorState
Returns:
number

selectionInContent

Returns a TextSelection with the cursor at the start of the first section_content in the document.

Use this instead of hardcoding positions — the header and footer each contain an empty paragraph (required by block+), so a naive position like 1 or 4 lands inside the header, not the content.

Parameters:
NameTypeDefault
docNode
Returns:
TextSelection

singleSectionState

EditorState with cursor in the section_content of a single-section doc.

Parameters:
NameTypeDefault
content[attrsOrFirstChild?: Attrs | ChildSpec | undefined, ...children: ChildSpec[]]
Returns:
EditorState

stateWithCursorAt

Builds an EditorState whose cursor is inside the cell at (rowIndex, colIndex) of the given table.

Parameters:
NameTypeDefault
tableNode
rowIndexnumber
colIndexnumber
Returns:
EditorState

stateWithSelectedImage

EditorState with a single image in section_content selected via NodeSelection.

Parameters:
NameTypeDefault
imageAttrsRecord<string, unknown>
Returns:
EditorState

stateWithTable

EditorState with the cursor in the table’s first cell.

Parameters:
NameTypeDefault
tableNode
Returns:
EditorState

tb

Node/mark builders bound to testSchema: every node and mark name plus the short aliases below. Accessed with ! (tb.p!) under noUncheckedIndexedAccess.

NameTypeDefault
schemaSchema<any, any>

tempEditor

Creates a real ProseMirror EditorView mounted to a throwaway DOM element.

Unlike EditorState.apply(tr), the view runs the full plugin lifecycle: appendTransaction, view hooks, handleDOMEvents, transformPasted, etc.

Call destroy() when done (typically in afterEach).

Parameters:
NameTypeDefault
opts{ doc?: Node; plugins?: Plugin<any>[]; }
Returns:
EditorView

testDoc

A schema-checked doc with content in one section’s section_content, plus the footnotes container.

testDoc(tb.p!("hello"))
Parameters:
NameTypeDefault
content[attrsOrFirstChild?: Attrs | ChildSpec | undefined, ...children: ChildSpec[]]
Returns:
TaggedNode

testSection

A section node wrapping the given content in the required header/content/footer shape.

Parameters:
NameTypeDefault
content[attrsOrFirstChild?: Attrs | ChildSpec | undefined, ...children: ChildSpec[]]
attrsPartial<SectionAttrs>
Returns:
Node & { tag: Tags; }

twoSectionState

A two-section doc with the cursor at the end of the second section’s paragraph.

Parameters:
This function does not accept any parameters.
Returns:
EditorState

uncheckedTestDoc

testDoc WITHOUT the schema check — only for specs that deliberately build invalid docs (e.g. the nested-tables normalize heal, which exercises the Node.fromJSON door). Everything else should use testDoc.

Parameters:
NameTypeDefault
content[attrsOrFirstChild?: Attrs | ChildSpec | undefined, ...children: ChildSpec[]]
Returns:
TaggedNode

Types

TaggedNode

A node with position tags attached by prosemirror-test-builder.

NameTypeDefault
typeNodeType

The type of node that this is.

attrsAttrs

An object mapping attribute names to values. The kind of attributes allowed and required are determined  by the node type.

marksreadonly Mark[]

The marks (things like whether it is emphasized or part of a link) applied to this node.

contentFragment

A container holding the node’s children.

childrenreadonly Node[]

The array of this node’s child nodes.

textstring | undefined

For text nodes, this contains the node’s text content.

nodeSizenumber

The size of this node, as defined by the integer-based indexing scheme . For text nodes, this is the amount of characters. For other leaf nodes, it is one. For non-leaf nodes, it is the size of the content plus two (the start and end token).

childCountnumber

The number of children that the node has.

child(index: number) => Node

Get the child node at the given index. Raises an error when the index is out of range.

maybeChild(index: number) => Node | null

Get the child node at the given index, if it exists.

forEach(f: (node: Node, offset: number, index: number) => void) => void

Call f for every child node, passing the node, its offset into this parent node, and its index.

nodesBetween(from: number, to: number, f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void, startPos?: number | undefined) => void

Invoke a callback for all descendant nodes recursively overlapping the given two positions that are relative to start of this node’s content. This includes all ancestors of the nodes containing the two positions. The callback is invoked with the node, its position relative to the original node (method receiver), its parent node, and its child index. When the callback returns false for a given node, that node’s children will not be recursed over. The last parameter can be used to specify a starting position to count from.

descendants(f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void) => void

Call the given callback for every descendant node. Doesn’t descend into a node when the callback returns false.

textContentstring

Concatenates all the text nodes found in this fragment and its children.

textBetween(from: number, to: number, blockSeparator?: string | null | undefined, leafText?: string | ((leafNode: Node) => string) | null | undefined) => string

Get all text between positions from and to. When blockSeparator is given, it will be inserted to separate text from different block nodes. If leafText is given, it’ll be inserted for every non-text leaf node encountered, otherwise leafText will be used.

firstChildNode | null

Returns this node’s first child, or null if there are no children.

lastChildNode | null

Returns this node’s last child, or null if there are no children.

eq(other: Node) => boolean

Test whether two nodes represent the same piece of document.

sameMarkup(other: Node) => boolean

Compare the markup (type, attributes, and marks) of this node to those of another. Returns true if both have the same markup.

hasMarkup(type: NodeType, attrs?: Attrs | null | undefined, marks?: readonly Mark[] | undefined) => boolean

Check whether this node’s markup correspond to the given type, attributes, and marks.

copy(content?: Fragment | null | undefined) => Node

Create a new node with the same markup as this node, containing the given content (or empty, if no content is given).

mark(marks: readonly Mark[]) => Node

Create a copy of this node, with the given set of marks instead of the node’s own marks.

cut(from: number, to?: number | undefined) => Node

Create a copy of this node with only the content between the given positions. If to is not given, it defaults to the end of the node.

slice(from: number, to?: number | undefined, includeParents?: boolean | undefined) => Slice

Cut out the part of the document between the given positions, and return it as a Slice object.

replace(from: number, to: number, slice: Slice) => Node

Replace the part of the document between the given positions with the given slice. The slice must ‘fit’, meaning its open sides must be able to connect to the surrounding content, and its content nodes must be valid children for the node they are placed into. If any of this is violated, an error of type ReplaceError is thrown.

nodeAt(pos: number) => Node | null

Find the node directly after the given position.

childAfter(pos: number) => { node: Node | null; index: number; offset: number; }

Find the (direct) child node after the given offset, if any, and return it along with its index and offset relative to this node.

childBefore(pos: number) => { node: Node | null; index: number; offset: number; }

Find the (direct) child node before the given offset, if any, and return it along with its index and offset relative to this node.

resolve(pos: number) => ResolvedPos

Resolve the given position in the document, returning an object  with information about its context.

rangeHasMark(from: number, to: number, type: Mark | MarkType) => boolean

Test whether a given mark or mark type occurs in this document between the two given positions.

isBlockboolean

True when this is a block (non-inline node)

isTextblockboolean

True when this is a textblock node, a block node with inline content.

inlineContentboolean

True when this node allows inline content.

isInlineboolean

True when this is an inline node (a text node or a node that can appear among text).

isTextboolean

True when this is a text node.

isLeafboolean

True when this is a leaf node.

isAtomboolean

True when this is an atom, i.e. when it does not have directly editable content. This is usually the same as isLeaf, but can be configured with the atom property on a node’s spec (typically used when the node is displayed as an uneditable node view ).

toString() => string

Return a string representation of this node for debugging purposes.

contentMatchAt(index: number) => ContentMatch

Get the content match in this node at the given index.

canReplace(from: number, to: number, replacement?: Fragment | undefined, start?: number | undefined, end?: number | undefined) => boolean

Test whether replacing the range between from and to (by child index) with the given replacement fragment (which defaults to the empty fragment) would leave the node’s content valid. You can optionally pass start and end indices into the replacement fragment.

canReplaceWith(from: number, to: number, type: NodeType, marks?: readonly Mark[] | undefined) => boolean

Test whether replacing the range from to to (by index) with a node of the given type would leave the node’s content valid.

canAppend(other: Node) => boolean

Test whether the given node’s content could be appended to this node. If that node is empty, this will only return true if there is at least one node type that can appear in both nodes (to avoid merging completely incompatible nodes).

check() => void

Check whether this node and its descendants conform to the schema, and raise an exception when they do not.

toJSON() => any

Return a JSON-serializeable representation of this node.

tagRecord<string, number>
Last updated on