Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped
723 lines
23 KiB
TypeScript
723 lines
23 KiB
TypeScript
import Blockquote from '@tiptap/extension-blockquote'
|
|
import BulletList from '@tiptap/extension-bullet-list'
|
|
import Document from '@tiptap/extension-document'
|
|
import HardBreak from '@tiptap/extension-hard-break'
|
|
import HorizontalRule from '@tiptap/extension-horizontal-rule'
|
|
import { ListItem } from '@tiptap/extension-list'
|
|
import OrderedList from '@tiptap/extension-ordered-list'
|
|
import Paragraph from '@tiptap/extension-paragraph'
|
|
import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table'
|
|
import Text from '@tiptap/extension-text'
|
|
import { afterEach, describe, expect, it } from 'vite-plus/test'
|
|
|
|
import { Editor } from './Editor.js'
|
|
import { Node } from './Node.js'
|
|
|
|
// Simple inline node for testing (similar to mention)
|
|
const CustomInlineNode = Node.create({
|
|
name: 'customInline',
|
|
group: 'inline',
|
|
inline: true,
|
|
atom: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
id: {
|
|
default: null,
|
|
},
|
|
}
|
|
},
|
|
|
|
parseHTML() {
|
|
return [
|
|
{
|
|
tag: 'span[data-type="custom-inline"]',
|
|
},
|
|
]
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ['span', { 'data-type': 'custom-inline', ...HTMLAttributes }, 0]
|
|
},
|
|
})
|
|
|
|
const CustomBlockAtomNode = Node.create({
|
|
name: 'customBlockAtom',
|
|
group: 'block',
|
|
atom: true,
|
|
|
|
addAttributes() {
|
|
return {
|
|
id: {
|
|
default: null,
|
|
},
|
|
}
|
|
},
|
|
|
|
parseHTML() {
|
|
return [
|
|
{
|
|
tag: 'div[data-type="custom-block-atom"]',
|
|
},
|
|
]
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
return ['div', { 'data-type': 'custom-block-atom', ...HTMLAttributes }]
|
|
},
|
|
})
|
|
|
|
describe('NodePos', () => {
|
|
let editor: Editor
|
|
|
|
afterEach(() => {
|
|
editor?.destroy()
|
|
})
|
|
|
|
describe('$nodes', () => {
|
|
it('should return text nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello World</p>',
|
|
})
|
|
|
|
const textNodes = editor.$nodes('text')
|
|
|
|
expect(textNodes).not.toBeNull()
|
|
expect(textNodes!.length).toBe(1)
|
|
expect(textNodes![0].textContent).toBe('Hello World')
|
|
})
|
|
|
|
it('should return multiple text nodes across paragraphs', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>First</p><p>Second</p><p>Third</p>',
|
|
})
|
|
|
|
const textNodes = editor.$nodes('text')
|
|
|
|
expect(textNodes).not.toBeNull()
|
|
expect(textNodes!.length).toBe(3)
|
|
expect(textNodes![0].textContent).toBe('First')
|
|
expect(textNodes![1].textContent).toBe('Second')
|
|
expect(textNodes![2].textContent).toBe('Third')
|
|
})
|
|
|
|
it('should return inline atom nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p>Hello <span data-type="custom-inline" id="1"></span> World</p>',
|
|
})
|
|
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(1)
|
|
expect(inlineNodes![0].node.type.name).toBe('customInline')
|
|
})
|
|
|
|
it('should return multiple inline nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p>Hello <span data-type="custom-inline" id="1"></span> and <span data-type="custom-inline" id="2"></span> World</p>',
|
|
})
|
|
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(2)
|
|
})
|
|
|
|
it('should return inline nodes with attribute filtering', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p>Hello <span data-type="custom-inline" id="1"></span> and <span data-type="custom-inline" id="2"></span> World</p>',
|
|
})
|
|
|
|
// Note: HTML attribute parsing converts numeric strings to numbers
|
|
const inlineNodes = editor.$nodes('customInline', { id: 1 })
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(1)
|
|
expect(inlineNodes![0].node.attrs.id).toBe(1)
|
|
})
|
|
|
|
it('should return paragraph nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>First</p><p>Second</p>',
|
|
})
|
|
|
|
const paragraphNodes = editor.$nodes('paragraph')
|
|
|
|
expect(paragraphNodes).not.toBeNull()
|
|
expect(paragraphNodes!.length).toBe(2)
|
|
})
|
|
|
|
it('should return inline nodes nested in multiple paragraphs', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p>Para 1 <span data-type="custom-inline" id="1"></span></p><p>Para 2 <span data-type="custom-inline" id="2"></span></p>',
|
|
})
|
|
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(2)
|
|
// Note: HTML attribute parsing converts numeric strings to numbers
|
|
expect(inlineNodes![0].node.attrs.id).toBe(1)
|
|
expect(inlineNodes![1].node.attrs.id).toBe(2)
|
|
})
|
|
|
|
it('should find paragraphs inside blockquotes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote],
|
|
content: '<blockquote><p>Quoted text</p></blockquote>',
|
|
})
|
|
|
|
const paragraphs = editor.$nodes('paragraph')
|
|
|
|
expect(paragraphs).not.toBeNull()
|
|
expect(paragraphs!.length).toBe(1)
|
|
expect(paragraphs![0].textContent).toBe('Quoted text')
|
|
})
|
|
|
|
it('should find blockquote nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote],
|
|
content:
|
|
'<blockquote><p>First quote</p></blockquote><p>Normal</p><blockquote><p>Second quote</p></blockquote>',
|
|
})
|
|
|
|
const blockquotes = editor.$nodes('blockquote')
|
|
|
|
expect(blockquotes).not.toBeNull()
|
|
expect(blockquotes!.length).toBe(2)
|
|
})
|
|
|
|
it('should find text nodes inside nested block structures', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote],
|
|
content: '<blockquote><p>Quoted</p></blockquote><p>Normal</p>',
|
|
})
|
|
|
|
const textNodes = editor.$nodes('text')
|
|
|
|
expect(textNodes).not.toBeNull()
|
|
expect(textNodes!.length).toBe(2)
|
|
expect(textNodes![0].textContent).toBe('Quoted')
|
|
expect(textNodes![1].textContent).toBe('Normal')
|
|
})
|
|
|
|
it('should find list items in bullet lists', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, BulletList, ListItem],
|
|
content: '<ul><li><p>Item 1</p></li><li><p>Item 2</p></li></ul>',
|
|
})
|
|
|
|
const listItems = editor.$nodes('listItem')
|
|
|
|
expect(listItems).not.toBeNull()
|
|
expect(listItems!.length).toBe(2)
|
|
})
|
|
|
|
it('should find text nodes inside list items', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, BulletList, ListItem],
|
|
content: '<ul><li><p>Item 1</p></li><li><p>Item 2</p></li></ul>',
|
|
})
|
|
|
|
const textNodes = editor.$nodes('text')
|
|
|
|
expect(textNodes).not.toBeNull()
|
|
expect(textNodes!.length).toBe(2)
|
|
expect(textNodes![0].textContent).toBe('Item 1')
|
|
expect(textNodes![1].textContent).toBe('Item 2')
|
|
})
|
|
|
|
it('should find inline nodes inside list items', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, BulletList, ListItem, CustomInlineNode],
|
|
content:
|
|
'<ul><li><p>Item <span data-type="custom-inline" id="1"></span></p></li><li><p>Item <span data-type="custom-inline" id="2"></span></p></li></ul>',
|
|
})
|
|
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(2)
|
|
})
|
|
|
|
it('should handle empty paragraphs', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p></p><p>Content</p><p></p>',
|
|
})
|
|
|
|
const paragraphs = editor.$nodes('paragraph')
|
|
|
|
expect(paragraphs).not.toBeNull()
|
|
expect(paragraphs!.length).toBe(3)
|
|
})
|
|
|
|
it('should return empty array for text nodes when paragraph is empty', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p></p>',
|
|
})
|
|
|
|
const textNodes = editor.$nodes('text')
|
|
|
|
expect(textNodes).not.toBeNull()
|
|
expect(textNodes!.length).toBe(0)
|
|
})
|
|
|
|
it('should find all node types in mixed content', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote, CustomInlineNode],
|
|
content:
|
|
'<p>Normal <span data-type="custom-inline" id="1"></span></p><blockquote><p>Quoted <span data-type="custom-inline" id="2"></span></p></blockquote>',
|
|
})
|
|
|
|
const paragraphs = editor.$nodes('paragraph')
|
|
const blockquotes = editor.$nodes('blockquote')
|
|
const textNodes = editor.$nodes('text')
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(paragraphs!.length).toBe(2)
|
|
expect(blockquotes!.length).toBe(1)
|
|
expect(textNodes!.length).toBe(2)
|
|
expect(inlineNodes!.length).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('$node', () => {
|
|
it('should find a single text node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
const textNode = editor.$node('text')
|
|
|
|
expect(textNode).not.toBeNull()
|
|
expect(textNode!.textContent).toBe('Hello')
|
|
})
|
|
|
|
it('should find a single inline node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p><span data-type="custom-inline" id="found"></span></p>',
|
|
})
|
|
|
|
const inlineNode = editor.$node('customInline')
|
|
|
|
expect(inlineNode).not.toBeNull()
|
|
expect(inlineNode!.node.attrs.id).toBe('found')
|
|
})
|
|
})
|
|
|
|
describe('children', () => {
|
|
it('should include text nodes as children of paragraph', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello World</p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
|
|
expect(paragraph).not.toBeNull()
|
|
|
|
const children = paragraph!.children
|
|
|
|
expect(children.length).toBe(1)
|
|
expect(children[0].node.type.name).toBe('text')
|
|
expect(children[0].textContent).toBe('Hello World')
|
|
})
|
|
|
|
it('should include inline atom nodes as children of paragraph', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p>Hello <span data-type="custom-inline" id="test"></span> World</p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
|
|
expect(paragraph).not.toBeNull()
|
|
|
|
const children = paragraph!.children
|
|
|
|
// Should have: "Hello ", customInline, " World"
|
|
expect(children.length).toBe(3)
|
|
expect(children[0].node.type.name).toBe('text')
|
|
expect(children[0].textContent).toBe('Hello ')
|
|
expect(children[1].node.type.name).toBe('customInline')
|
|
expect(children[2].node.type.name).toBe('text')
|
|
expect(children[2].textContent).toBe(' World')
|
|
})
|
|
|
|
it('should return correct node reference for inline nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p><span data-type="custom-inline" id="test-id"></span></p>',
|
|
})
|
|
|
|
const inlineNodes = editor.$nodes('customInline')
|
|
|
|
expect(inlineNodes).not.toBeNull()
|
|
expect(inlineNodes!.length).toBe(1)
|
|
expect(inlineNodes![0].node.attrs.id).toBe('test-id')
|
|
})
|
|
})
|
|
|
|
describe('querySelector', () => {
|
|
it('should return first matching node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p><span data-type="custom-inline" id="1"></span><span data-type="custom-inline" id="2"></span></p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const firstInline = paragraph!.querySelector('customInline')
|
|
|
|
expect(firstInline).not.toBeNull()
|
|
expect(firstInline!.node.attrs.id).toBe(1)
|
|
})
|
|
|
|
it('should return all matching nodes with querySelectorAll', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p><span data-type="custom-inline" id="1"></span><span data-type="custom-inline" id="2"></span></p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const allInlines = paragraph!.querySelectorAll('customInline')
|
|
|
|
expect(allInlines.length).toBe(2)
|
|
})
|
|
|
|
it('should filter correctly with attributes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content:
|
|
'<p><span data-type="custom-inline" id="1"></span><span data-type="custom-inline" id="2"></span></p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const specific = paragraph!.querySelector('customInline', { id: 2 })
|
|
|
|
expect(specific).not.toBeNull()
|
|
expect(specific!.node.attrs.id).toBe(2)
|
|
})
|
|
})
|
|
|
|
describe('node position properties', () => {
|
|
it('should return correct from/to positions for inline nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p>Hi <span data-type="custom-inline" id="1"></span> there</p>',
|
|
})
|
|
|
|
const inlineNode = editor.$node('customInline')
|
|
|
|
expect(inlineNode).not.toBeNull()
|
|
expect(inlineNode!.from).toBeLessThan(inlineNode!.to)
|
|
expect(inlineNode!.size).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should return correct from/to positions for text nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
const textNode = editor.$node('text')
|
|
|
|
expect(textNode).not.toBeNull()
|
|
expect(textNode!.from).toBeLessThan(textNode!.to)
|
|
expect(textNode!.size).toBe(5) // "Hello" is 5 characters
|
|
})
|
|
|
|
it('should return correct range for nodes', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
|
|
expect(paragraph).not.toBeNull()
|
|
expect(paragraph!.range.from).toBe(paragraph!.from)
|
|
expect(paragraph!.range.to).toBe(paragraph!.to)
|
|
})
|
|
})
|
|
|
|
describe('firstChild and lastChild', () => {
|
|
it('should return correct firstChild for paragraph with text', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello World</p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const firstChild = paragraph!.firstChild
|
|
|
|
expect(firstChild).not.toBeNull()
|
|
expect(firstChild!.node.type.name).toBe('text')
|
|
expect(firstChild!.textContent).toBe('Hello World')
|
|
})
|
|
|
|
it('should return correct lastChild for paragraph with mixed content', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p>Hello <span data-type="custom-inline" id="1"></span></p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const lastChild = paragraph!.lastChild
|
|
|
|
expect(lastChild).not.toBeNull()
|
|
expect(lastChild!.node.type.name).toBe('customInline')
|
|
})
|
|
|
|
it('should return null for firstChild/lastChild on empty paragraph', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p></p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
|
|
expect(paragraph!.firstChild).toBeNull()
|
|
expect(paragraph!.lastChild).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('parent', () => {
|
|
it('should return parent for paragraph node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const parent = paragraph!.parent
|
|
|
|
expect(parent).not.toBeNull()
|
|
expect(parent!.node.type.name).toBe('doc')
|
|
})
|
|
|
|
it('should return parent for blockquote node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote],
|
|
content: '<blockquote><p>Quoted</p></blockquote>',
|
|
})
|
|
|
|
const blockquote = editor.$node('blockquote')
|
|
const parent = blockquote!.parent
|
|
|
|
expect(parent).not.toBeNull()
|
|
expect(parent!.node.type.name).toBe('doc')
|
|
})
|
|
|
|
it('should return parent for nested paragraph in blockquote', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote],
|
|
content: '<blockquote><p>Quoted</p></blockquote>',
|
|
})
|
|
|
|
const paragraph = editor.$node('paragraph')
|
|
const parent = paragraph!.parent
|
|
|
|
expect(parent).not.toBeNull()
|
|
expect(parent!.node.type.name).toBe('blockquote')
|
|
})
|
|
})
|
|
|
|
describe('$pos', () => {
|
|
it('should return the correct node when pointing at a non-text atom node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomInlineNode],
|
|
content: '<p><span data-type="custom-inline" id="atom"></span></p>',
|
|
})
|
|
|
|
const inlineNode = editor.$node('customInline')
|
|
|
|
expect(inlineNode).not.toBeNull()
|
|
|
|
const nodeAtPos = editor.$pos(inlineNode!.pos)
|
|
|
|
expect(nodeAtPos.node.type.name).toBe('customInline')
|
|
expect(nodeAtPos.node.type.name).not.toBe('doc')
|
|
})
|
|
|
|
it('should return doc node when resolving pos 0', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
const docPos = editor.$pos(0)
|
|
|
|
expect(docPos.node.type.name).toBe('doc')
|
|
})
|
|
|
|
it('should return the top-level block atom node for positions before non-text block atoms', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomBlockAtomNode],
|
|
content: '<p>Before</p><div data-type="custom-block-atom" id="top"></div>',
|
|
})
|
|
|
|
const blockAtom = editor.$node('customBlockAtom')
|
|
|
|
expect(blockAtom).not.toBeNull()
|
|
|
|
const nodeAtPos = editor.$pos(blockAtom!.pos)
|
|
|
|
expect(nodeAtPos.node.type.name).toBe('customBlockAtom')
|
|
expect(nodeAtPos.node.type.name).not.toBe('doc')
|
|
})
|
|
|
|
it('should return the containing node for positions inside containers, not the child after them', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, OrderedList, ListItem, Blockquote],
|
|
content: '<ol><li><p>Hello</p></li></ol><blockquote><p>World</p></blockquote>',
|
|
})
|
|
|
|
// pos 1 sits before the list item, pos 12 before the inner paragraph
|
|
expect(editor.$pos(1).node.type.name).toBe('orderedList')
|
|
expect(editor.$pos(12).node.type.name).toBe('blockquote')
|
|
})
|
|
|
|
it('should still resolve text positions to the containing node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hello</p>',
|
|
})
|
|
|
|
expect(editor.$pos(3).node.type.name).toBe('paragraph')
|
|
})
|
|
|
|
it('should return the list for the boundary between two list items', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, OrderedList, ListItem],
|
|
content: '<ol><li><p>a</p></li><li><p>b</p></li></ol>',
|
|
})
|
|
|
|
expect(editor.$pos(6).node.type.name).toBe('orderedList')
|
|
})
|
|
|
|
it('should resolve positions in nested lists to the containing node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, BulletList, OrderedList, ListItem],
|
|
content: '<ul><li><p>a</p><ol><li><p>b</p></li></ol></li></ul>',
|
|
})
|
|
|
|
expect(editor.$pos(5).node.type.name).toBe('listItem')
|
|
expect(editor.$pos(6).node.type.name).toBe('orderedList')
|
|
expect(editor.$pos(7).node.type.name).toBe('listItem')
|
|
})
|
|
|
|
it('should resolve positions in tables to the containing node', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Table, TableRow, TableCell, TableHeader],
|
|
content: '<table><tr><td><p>a</p></td><td><p>b</p></td></tr></table>',
|
|
})
|
|
|
|
expect(editor.$pos(1).node.type.name).toBe('table')
|
|
expect(editor.$pos(2).node.type.name).toBe('tableRow')
|
|
expect(editor.$pos(3).node.type.name).toBe('tableCell')
|
|
})
|
|
|
|
it('should return leaf nodes like hard breaks when pointing directly at them', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, HardBreak],
|
|
content: '<p>Hello<br>World</p>',
|
|
})
|
|
|
|
expect(editor.$pos(6).node.type.name).toBe('hardBreak')
|
|
expect(editor.$pos(3).node.type.name).toBe('paragraph')
|
|
})
|
|
|
|
it('should return leaf nodes like horizontal rules when pointing directly at them', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, HorizontalRule],
|
|
content: '<p>a</p><hr><p>b</p>',
|
|
})
|
|
|
|
expect(editor.$pos(3).node.type.name).toBe('horizontalRule')
|
|
expect(editor.$pos(4).node.type.name).toBe('doc')
|
|
})
|
|
|
|
it('should return the doc for the position at the very end of the document', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>Hi</p>',
|
|
})
|
|
|
|
expect(editor.$pos(editor.state.doc.content.size).node.type.name).toBe('doc')
|
|
})
|
|
|
|
it('should return the following atom for the boundary between two block atoms', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, CustomBlockAtomNode],
|
|
content:
|
|
'<p>a</p><div data-type="custom-block-atom"></div><div data-type="custom-block-atom"></div>',
|
|
})
|
|
|
|
expect(editor.$pos(4).node.type.name).toBe('customBlockAtom')
|
|
})
|
|
|
|
it('should return inline atoms nested inside list items', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, BulletList, ListItem, CustomInlineNode],
|
|
content: '<ul><li><p>x<span data-type="custom-inline"></span></p></li></ul>',
|
|
})
|
|
|
|
expect(editor.$pos(4).node.type.name).toBe('customInline')
|
|
})
|
|
})
|
|
|
|
describe('$doc', () => {
|
|
it('should correctly traverse $doc children', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text],
|
|
content: '<p>First</p><p>Second</p>',
|
|
})
|
|
|
|
const doc = editor.$doc
|
|
|
|
expect(doc).not.toBeNull()
|
|
expect(doc!.children.length).toBe(2)
|
|
expect(doc!.children[0].node.type.name).toBe('paragraph')
|
|
expect(doc!.children[1].node.type.name).toBe('paragraph')
|
|
})
|
|
|
|
it('should handle deeply nested structures', () => {
|
|
editor = new Editor({
|
|
extensions: [Document, Paragraph, Text, Blockquote, CustomInlineNode],
|
|
content:
|
|
'<blockquote><p>Quote with <span data-type="custom-inline" id="deep"></span></p></blockquote>',
|
|
})
|
|
|
|
const inlineNode = editor.$node('customInline')
|
|
|
|
expect(inlineNode).not.toBeNull()
|
|
expect(inlineNode!.node.attrs.id).toBe('deep')
|
|
|
|
// Verify the structure
|
|
const blockquote = editor.$node('blockquote')
|
|
const paragraph = editor.$node('paragraph')
|
|
|
|
expect(blockquote).not.toBeNull()
|
|
expect(paragraph).not.toBeNull()
|
|
expect(paragraph!.parent!.node.type.name).toBe('blockquote')
|
|
})
|
|
})
|
|
})
|