Files
upstream-mirror 083a46e93b
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
snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
2026-09-15 21:11:02 +05:00

262 lines
7.1 KiB
TypeScript

import { Editor } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { afterEach, describe, expect, it } from 'vite-plus/test'
import { Details, DetailsContent, DetailsSummary } from '@tiptap/extension-details'
describe('Details', () => {
let editor: Editor
let editorElement: HTMLDivElement | undefined
afterEach(() => {
editor?.destroy()
editorElement?.remove()
editorElement = undefined
})
it('adds a default aria-label to the toggle button', () => {
editor = new Editor({
extensions: [Document, Paragraph, Text, Details, DetailsSummary, DetailsContent],
content: {
type: 'doc',
content: [
{
type: 'details',
content: [
{
type: 'detailsSummary',
content: [{ type: 'text', text: 'Summary' }],
},
{
type: 'detailsContent',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Content' }],
},
],
},
],
},
],
},
})
const toggleButton = editor.view.dom.querySelector('div[data-type="details"] > button')
expect(toggleButton?.getAttribute('aria-label')).toBe('Expand details content')
})
it('supports custom toggle button rendering and updates it when the node open state changes', () => {
editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Details.configure({
persist: true,
renderToggleButton: ({ element, isOpen, node }) => {
element.setAttribute(
'aria-label',
`${isOpen ? 'Collapse' : 'Expand'} ${node.textContent || 'details content'}`,
)
},
}),
DetailsSummary,
DetailsContent,
],
content: {
type: 'doc',
content: [
{
type: 'details',
attrs: {
open: false,
},
content: [
{
type: 'detailsSummary',
content: [{ type: 'text', text: 'Summary' }],
},
{
type: 'detailsContent',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Content' }],
},
],
},
],
},
],
},
})
const getToggleButton = () => editor.view.dom.querySelector('div[data-type="details"] > button')
expect(getToggleButton()?.getAttribute('aria-label')).toBe('Expand SummaryContent')
editor.commands.updateAttributes('details', { open: true })
expect(getToggleButton()?.getAttribute('aria-label')).toBe('Collapse SummaryContent')
})
it('updates a persisted details node at the start of the document when toggled', () => {
editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Details.configure({ persist: true }),
DetailsSummary,
DetailsContent,
],
content: {
type: 'doc',
content: [
{
type: 'details',
attrs: {
open: true,
},
content: [
{
type: 'detailsSummary',
content: [{ type: 'text', text: 'Summary' }],
},
{
type: 'detailsContent',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Content' }],
},
],
},
],
},
],
},
})
const toggleButton = editor.view.dom.querySelector<HTMLButtonElement>(
'div[data-type="details"] > button',
)
toggleButton?.click()
expect(editor.state.doc.firstChild?.attrs.open).toBe(false)
expect(editor.getHTML()).toBe(
'<details><summary>Summary</summary><div data-type="detailsContent"><p>Content</p></div></details>',
)
})
it('keeps the cursor in details content after typing at the start of the document', () => {
editorElement = document.createElement('div')
document.body.appendChild(editorElement)
editor = new Editor({
element: editorElement,
extensions: [Document, Paragraph, Text, Details, DetailsSummary, DetailsContent],
content: {
type: 'doc',
content: [
{
type: 'details',
content: [
{
type: 'detailsSummary',
content: [{ type: 'text', text: 'Summary' }],
},
{
type: 'detailsContent',
content: [{ type: 'paragraph' }],
},
],
},
],
},
})
Object.defineProperty(editor.view.dom, 'offsetParent', { value: document.body })
const paragraph = editor.view.dom.querySelector('p')
if (!paragraph) {
throw new Error('Expected details content to contain a paragraph')
}
Object.defineProperty(paragraph, 'offsetParent', {
value: editor.view.dom,
})
const toggleButton = editor.view.dom.querySelector<HTMLButtonElement>(
'div[data-type="details"] > button',
)
const summary = editor.state.doc.firstChild?.firstChild
const contentPosition = 1 + (summary?.nodeSize ?? 0) + 2
toggleButton?.click()
editor.commands.setTextSelection(contentPosition)
editor.commands.insertContent('a')
editor.commands.setTextSelection(contentPosition + 1)
expect(editor.state.selection.$from.parent.type).toBe(editor.schema.nodes.paragraph)
expect(editor.state.doc.textContent).toBe('Summarya')
})
it('ignores button attribute mutations triggered by renderToggleButton', async () => {
let renderToggleButtonCallCount = 0
editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
Details.configure({
renderToggleButton: ({ element, isOpen }) => {
renderToggleButtonCallCount += 1
element.setAttribute(
'aria-label',
isOpen ? 'Collapse details content' : 'Expand details content',
)
},
}),
DetailsSummary,
DetailsContent,
],
content: {
type: 'doc',
content: [
{
type: 'details',
content: [
{
type: 'detailsSummary',
content: [{ type: 'text', text: 'Summary' }],
},
{
type: 'detailsContent',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: 'Content' }],
},
],
},
],
},
],
},
})
await new Promise<void>(resolve => {
setTimeout(resolve, 0)
})
expect(renderToggleButtonCallCount).toBe(1)
})
})