import { describe, expect, it } from 'vitest'; import { Editor } from '@tiptap/core'; import StarterKit from '@tiptap/starter-kit'; import { PlainTextPaste } from '../plain-text-paste'; function makeEditor(content = '

') { return new Editor({ element: document.createElement('div'), extensions: [StarterKit, PlainTextPaste], coreExtensionOptions: { clipboardTextSerializer: { blockSeparator: '\n' } }, content, }); } // jsdom has no ClipboardEvent, which pasteText constructs when given none. function paste(editor: Editor, text: string) { editor.view.pasteText(text, new Event('paste') as ClipboardEvent); } describe('plain-text paste', () => { // A paste that ends in a list leaves the empty paragraph the cursor was in // below it, hence the trailing

in those expectations. it('keeps blank lines as empty paragraphs', () => { const editor = makeEditor(); paste(editor, 'Hi Joost,\n\nThanks for the write-up.\n\n\nPHASE 1'); expect(editor.getHTML()).toBe( '

Hi Joost,

Thanks for the write-up.

PHASE 1

', ); editor.destroy(); }); it('treats CRLF and CR line endings like LF', () => { const editor = makeEditor(); paste(editor, 'one\r\n\r\ntwo\rthree'); expect(editor.getHTML()).toBe('

one

two

three

'); editor.destroy(); }); it('pastes a single line inline without splitting the paragraph', () => { const editor = makeEditor('

Hello world

'); editor.commands.setTextSelection(7); paste(editor, 'big '); expect(editor.getHTML()).toBe('

Hello big world

'); editor.destroy(); }); it('turns dash items into a bullet list, dropping the blank lines between them', () => { const editor = makeEditor(); paste(editor, 'How it works:\n\n- One\n\n- Two\n\nAfter'); expect(editor.getHTML()).toBe( '

How it works:

' + '' + '

After

', ); editor.destroy(); }); it('accepts * and • as bullet markers', () => { const editor = makeEditor(); paste(editor, '* One\n• Two'); expect(editor.getHTML()).toBe('

'); editor.destroy(); }); it('turns numbered items into an ordered list that keeps its first number', () => { const editor = makeEditor(); paste(editor, '1. One\n2) Two\n\nText\n\n3. Three\n4. Four'); expect(editor.getHTML()).toBe( '
  1. One

  2. Two

Text

' + '
  1. Three

  2. Four

', ); editor.destroy(); }); it('starts a new list where a numbered list switches to dashes', () => { const editor = makeEditor(); paste(editor, '1. One\n\n- Dash'); expect(editor.getHTML()).toBe( '
  1. One

', ); editor.destroy(); }); it('keeps indented lines inside the item above them', () => { const editor = makeEditor(); paste(editor, '- Both drafts:\n filenode-14\n blobext-01\n\nAfter'); expect(editor.getHTML()).toBe( '' + '

After

', ); editor.destroy(); }); it('nests indented items', () => { const editor = makeEditor(); paste(editor, '- Outer\n - Inner\n- Next'); expect(editor.getHTML()).toBe( '

', ); editor.destroy(); }); it('keeps a hard-wrapped item whole', () => { const editor = makeEditor(); paste(editor, '- A long item that the\nsender wrapped\n- Next'); expect(editor.getHTML()).toBe( '

', ); editor.destroy(); }); it('leaves a signature delimiter and a dash inside a sentence alone', () => { const editor = makeEditor(); paste(editor, 'Costs - roughly\n-- \nLinus'); expect(editor.getHTML()).toBe('

Costs - roughly

--

Linus

'); editor.destroy(); }); it('copies back out as the same text', () => { const text = 'Hi Joost,\n\nThanks for the write-up.\n\n\nPHASE 1'; const editor = makeEditor(); paste(editor, text); // TipTap's serializer reads the editor selection, not the slice it gets. editor.commands.selectAll(); const slice = editor.state.selection.content(); const copied = editor.view.someProp('clipboardTextSerializer', (f) => f(slice, editor.view)); expect(copied).toBe(text); editor.destroy(); }); });