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
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:
593
packages/extension-twitch/tests/integration/twitch.test.ts
Normal file
593
packages/extension-twitch/tests/integration/twitch.test.ts
Normal file
@@ -0,0 +1,593 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Twitch from '@tiptap/extension-twitch'
|
||||
import { describe, expect, it } from 'vite-plus/test'
|
||||
|
||||
describe('extension-twitch', () => {
|
||||
const editorElClass = 'tiptap'
|
||||
let editor: Editor | null = null
|
||||
|
||||
const createEditorEl = () => {
|
||||
const editorEl = document.createElement('div')
|
||||
|
||||
editorEl.classList.add(editorElClass)
|
||||
document.body.appendChild(editorEl)
|
||||
return editorEl
|
||||
}
|
||||
|
||||
const getEditorEl = () => document.querySelector(`.${editorElClass}`)
|
||||
|
||||
const invalidUrls = [
|
||||
// We have to disable the oxlint rule here because we're trying to purposely test eval urls
|
||||
// oxlint-disable-next-line no-script-url
|
||||
'javascript:alert(window.origin)//embed/',
|
||||
'https://twitch.google.com/videos/1234567890',
|
||||
'https://twitch.com.bad/videos/1234567890',
|
||||
'https://www.twtich.tv/videos/1234567890',
|
||||
]
|
||||
|
||||
invalidUrls.forEach(url => {
|
||||
it(`does not output html for javascript schema or non-twitch links for url ${url}`, () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [Document, Text, Paragraph, Twitch],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: url,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).not.toContain(url)
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
})
|
||||
|
||||
it('outputs html with iframe for valid twitch video id', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('player.twitch.tv')
|
||||
expect(editor.getHTML()).toContain('1234567890')
|
||||
expect(editor.getHTML()).toContain('example.com')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('outputs html with iframe for twitch clip url', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://clips.twitch.tv/ExampleClipName-ABC123',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('clips.twitch.tv/embed')
|
||||
expect(editor.getHTML()).toContain('ExampleClipName-ABC123')
|
||||
expect(editor.getHTML()).toContain('example.com')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('outputs html with iframe for twitch channel url', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/examplechannel',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('player.twitch.tv')
|
||||
expect(editor.getHTML()).toContain('examplechannel')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects allowFullscreen option', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
allowFullscreen: true,
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('allowfullscreen="true"')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects custom width and height', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
width: 800,
|
||||
height: 600,
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
width: 800,
|
||||
height: 600,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('width="800"')
|
||||
expect(html).toContain('height="600"')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('can insert a video using the setTwitchVideo command', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
editor.commands.setTwitchVideo({
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('data-twitch-video')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('does not insert video with invalid URL using setTwitchVideo command', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [Document, Text, Paragraph, Twitch],
|
||||
})
|
||||
|
||||
const result = editor.commands.setTwitchVideo({
|
||||
src: 'https://invalid.com/video',
|
||||
})
|
||||
|
||||
expect(result).toBe(false)
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('handles video URL with search parameters', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890?filter=archives&sort=time',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('player.twitch.tv')
|
||||
expect(editor.getHTML()).toContain('1234567890')
|
||||
expect(editor.getHTML()).not.toContain('filter=archives')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('handles clip URL with search parameters', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://clips.twitch.tv/ExampleClipName-ABC123?foo=bar&baz=qux',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
expect(editor.getHTML()).toContain('clips.twitch.tv/embed')
|
||||
expect(editor.getHTML()).toContain('ExampleClipName-ABC123')
|
||||
expect(editor.getHTML()).not.toContain('foo=bar')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects muted option', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
muted: true,
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('muted=true')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects time option for videos', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
time: '1h2m3s',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('time=1h2m3s')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects attribute overrides for autoplay', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
autoplay: true,
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
autoplay: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
// Should NOT have autoplay=true since attribute overrides option
|
||||
expect(html).not.toContain('autoplay=true')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects attribute overrides for muted', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
muted: false,
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
muted: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('muted=true')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('respects attribute overrides for time', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
time: '1h0m0s',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: 'https://www.twitch.tv/videos/1234567890',
|
||||
time: '2h30m15s',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
expect(html).toContain('time=2h30m15s')
|
||||
expect(html).not.toContain('1h0m0s')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it.each([
|
||||
'https://www.twitch.tv/videos/1234567890',
|
||||
'https://clips.twitch.tv/ExampleClipName-ABC123',
|
||||
'https://www.twitch.tv/examplechannel',
|
||||
])(
|
||||
'preserves the twitch src when content is loaded back from rendered HTML for %s',
|
||||
originalSrc => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: originalSrc,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const html = editor.getHTML()
|
||||
|
||||
editor.destroy()
|
||||
getEditorEl()?.remove()
|
||||
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content: html,
|
||||
})
|
||||
|
||||
expect(editor.getJSON()).toMatchObject({
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'twitch',
|
||||
attrs: {
|
||||
src: originalSrc,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
},
|
||||
)
|
||||
|
||||
it('does not persist NaN width or height when parsing iframe dimensions from HTML', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [
|
||||
Document,
|
||||
Text,
|
||||
Paragraph,
|
||||
Twitch.configure({
|
||||
parent: 'example.com',
|
||||
}),
|
||||
],
|
||||
content:
|
||||
'<div data-twitch-video><iframe src="https://player.twitch.tv/?video=1234567890&parent=example.com" width="100%" height="auto"></iframe></div>',
|
||||
})
|
||||
|
||||
const attrs = editor.getJSON().content?.[0]?.attrs ?? {}
|
||||
const html = editor.getHTML()
|
||||
|
||||
expect(Number.isNaN(attrs.width)).toBe(false)
|
||||
expect(Number.isNaN(attrs.height)).toBe(false)
|
||||
expect(html).not.toContain('width="NaN"')
|
||||
expect(html).not.toContain('height="NaN"')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
|
||||
it('renders invalid Twitch content without crashing when parsed HTML contains an unsupported src', () => {
|
||||
editor = new Editor({
|
||||
element: createEditorEl(),
|
||||
extensions: [Document, Text, Paragraph, Twitch],
|
||||
content:
|
||||
'<div data-twitch-video><iframe src="https://example.com/not-a-twitch-url"></iframe></div>',
|
||||
})
|
||||
|
||||
expect(() => editor?.getHTML()).not.toThrow()
|
||||
expect(editor.getHTML()).toContain('Invalid Twitch URL')
|
||||
|
||||
editor?.destroy()
|
||||
getEditorEl()?.remove()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user