snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
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

This commit is contained in:
upstream-mirror
2026-09-15 21:11:02 +05:00
commit 083a46e93b
2115 changed files with 245958 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import { TableKit } from '@tiptap/extension-table'
import Text from '@tiptap/extension-text'
import { afterEach, beforeEach, describe, expect, it } from 'vite-plus/test'
const countCells = (editor: Editor, selector: string) =>
editor.view.dom.querySelectorAll(selector).length
const firstTableBounds = (editor: Editor) => {
let start = -1
let end = -1
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'table' && start === -1) {
start = pos
end = pos + node.nodeSize
}
})
return { start, end }
}
const selectFirstTwoHeaderCells = (editor: Editor) => {
const positions: number[] = []
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableHeader') {
positions.push(pos)
}
})
editor
.chain()
.focus()
.setCellSelection({ anchorCell: positions[0], headCell: positions[1] })
.run()
}
describe('Table commands', () => {
let editor: Editor
beforeEach(() => {
const element = document.createElement('div')
document.body.appendChild(element)
editor = new Editor({
element,
extensions: [Document, Paragraph, Text, TableKit],
content: '',
})
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()
})
afterEach(() => {
editor.destroy()
})
it('adds a table with three columns and three rows', () => {
expect(countCells(editor, 'table')).toBe(1)
expect(countCells(editor, 'table tr')).toBe(3)
expect(countCells(editor, 'table th')).toBe(3)
expect(countCells(editor, 'table td')).toBe(6)
})
it('adds & deletes columns', () => {
editor.commands.addColumnBefore()
expect(countCells(editor, 'table th')).toBe(4)
editor.commands.addColumnAfter()
expect(countCells(editor, 'table th')).toBe(5)
editor.commands.deleteColumn()
editor.commands.deleteColumn()
expect(countCells(editor, 'table th')).toBe(3)
})
it('adds & deletes rows', () => {
editor.commands.addRowBefore()
expect(countCells(editor, 'table tr')).toBe(4)
editor.commands.addRowAfter()
expect(countCells(editor, 'table tr')).toBe(5)
editor.commands.deleteRow()
editor.commands.deleteRow()
expect(countCells(editor, 'table tr')).toBe(3)
})
it('keeps the cursor inside the table when deleting the last row', () => {
editor.commands.insertContentAt(editor.state.doc.content.size, '<p>Text below table</p>')
let lastCellPos = 0
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableCell') {
lastCellPos = pos
}
})
editor
.chain()
.focus()
.setTextSelection(lastCellPos + 2)
.run()
editor.commands.deleteRow()
expect(countCells(editor, 'table tr')).toBe(2)
expect(editor.isActive('table')).toBe(true)
})
it('keeps the cursor inside the table when deleting the last column', () => {
editor.commands.insertContentAt(editor.state.doc.content.size, '<p>Text below table</p>')
let lastCellPos = 0
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableCell') {
lastCellPos = pos
}
})
editor
.chain()
.focus()
.setTextSelection(lastCellPos + 2)
.run()
editor.commands.deleteColumn()
expect(countCells(editor, 'table th')).toBe(2)
expect(editor.isActive('table')).toBe(true)
})
it('keeps the cursor in the edited table when deleting the last row with a table below', () => {
editor.commands.insertContentAt(
editor.state.doc.content.size,
'<table><tbody><tr><td><p>Following table</p></td></tr></tbody></table>',
)
const { end: firstTableEnd } = firstTableBounds(editor)
let lastCellPos = 0
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableCell' && pos < firstTableEnd) {
lastCellPos = pos
}
})
editor
.chain()
.focus()
.setTextSelection(lastCellPos + 2)
.run()
editor.commands.deleteRow()
const { start, end } = firstTableBounds(editor)
const cursor = editor.state.selection.from
expect(editor.isActive('table')).toBe(true)
expect(cursor).toBeGreaterThan(start)
expect(cursor).toBeLessThan(end)
})
it('keeps the cursor in the edited table when deleting the last column with a table below', () => {
editor.commands.insertContentAt(
editor.state.doc.content.size,
'<table><tbody><tr><td><p>Following table</p></td></tr></tbody></table>',
)
const { end: firstTableEnd } = firstTableBounds(editor)
let lastCellPos = 0
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableCell' && pos < firstTableEnd) {
lastCellPos = pos
}
})
editor
.chain()
.focus()
.setTextSelection(lastCellPos + 2)
.run()
editor.commands.deleteColumn()
const { start, end } = firstTableBounds(editor)
const cursor = editor.state.selection.from
expect(editor.isActive('table')).toBe(true)
expect(cursor).toBeGreaterThan(start)
expect(cursor).toBeLessThan(end)
})
it('deletes the table', () => {
editor.commands.deleteTable()
expect(countCells(editor, 'table')).toBe(0)
})
it('merges cells', () => {
selectFirstTwoHeaderCells(editor)
editor.commands.mergeCells()
expect(countCells(editor, 'table th')).toBe(2)
})
it('splits cells', () => {
selectFirstTwoHeaderCells(editor)
editor.commands.mergeCells()
expect(countCells(editor, 'table th')).toBe(2)
editor.commands.splitCell()
expect(countCells(editor, 'table th')).toBe(3)
})
it('toggles header columns', () => {
editor.commands.toggleHeaderColumn()
expect(countCells(editor, 'table th')).toBe(5)
})
it('toggles header row', () => {
editor.commands.toggleHeaderRow()
expect(countCells(editor, 'table th')).toBe(0)
})
it('merges and splits via mergeOrSplit', () => {
selectFirstTwoHeaderCells(editor)
editor.commands.mergeCells()
expect(editor.view.dom.querySelector('th[colspan="2"]')).not.toBeNull()
editor.commands.mergeOrSplit()
expect(editor.view.dom.querySelector('th[colspan="2"]')).toBeNull()
})
it('creates a 1x1 table', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
expect(countCells(editor, 'td')).toBe(1)
expect(countCells(editor, 'tr')).toBe(1)
})
it('creates a 3x1 table', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 3, rows: 1, withHeaderRow: false })
expect(countCells(editor, 'td')).toBe(3)
expect(countCells(editor, 'tr')).toBe(1)
})
it('creates a 1x3 table', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 1, rows: 3, withHeaderRow: false })
expect(countCells(editor, 'td')).toBe(3)
expect(countCells(editor, 'tr')).toBe(3)
})
it('creates a 1x3 table with header row', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 1, rows: 3, withHeaderRow: true })
expect(countCells(editor, 'th')).toBe(1)
expect(countCells(editor, 'td')).toBe(2)
expect(countCells(editor, 'tr')).toBe(3)
})
it('creates a 3x3 table with defaults', () => {
editor.commands.clearContent()
editor.commands.insertTable()
expect(countCells(editor, 'th')).toBe(3)
expect(countCells(editor, 'td')).toBe(6)
expect(countCells(editor, 'tr')).toBe(3)
})
it('sets a minimum width on colgroups', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 3, rows: 1, withHeaderRow: false })
const firstCol = editor.view.dom.querySelector('col')
expect(firstCol?.getAttribute('style')).toBe('min-width: 25px;')
})
it('generates correct markup for a 1x1 table', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: false })
expect(editor.getHTML()).toBe(
'<table style="min-width: 25px;"><colgroup><col style="min-width: 25px;"></colgroup><tbody><tr><td colspan="1" rowspan="1"><p></p></td></tr></tbody></table>',
)
})
it('generates correct markup for a 1x1 table with header', () => {
editor.commands.clearContent()
editor.commands.insertTable({ cols: 1, rows: 1, withHeaderRow: true })
expect(editor.getHTML()).toBe(
'<table style="min-width: 25px;"><colgroup><col style="min-width: 25px;"></colgroup><tbody><tr><th colspan="1" rowspan="1"><p></p></th></tr></tbody></table>',
)
})
it('moves focus between cells with goToNextCell / goToPreviousCell', () => {
let firstHeaderPos = -1
editor.state.doc.descendants((node, pos) => {
if (node.type.name === 'tableHeader' && firstHeaderPos === -1) {
firstHeaderPos = pos
}
})
editor
.chain()
.focus()
.setTextSelection(firstHeaderPos + 1)
.insertContent('Column 1')
.run()
editor.commands.goToNextCell()
editor.commands.insertContent('Column 2')
editor.commands.goToPreviousCell()
const ths = editor.view.dom.querySelectorAll('table th')
expect(ths[0].textContent).toContain('Column 1')
expect(ths[1].textContent).toContain('Column 2')
})
})