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
594 lines
14 KiB
TypeScript
594 lines
14 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 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()
|
|
})
|
|
})
|