import PasteMarkdown from '~/content_editor/extensions/paste_markdown'; import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; import Diagram from '~/content_editor/extensions/diagram'; import Frontmatter from '~/content_editor/extensions/frontmatter'; import Bold from '~/content_editor/extensions/bold'; import { VARIANT_DANGER } from '~/alert'; import eventHubFactory from '~/helpers/event_hub_factory'; import { ALERT_EVENT } from '~/content_editor/constants'; import waitForPromises from 'helpers/wait_for_promises'; import { createTestEditor, createDocBuilder, waitUntilNextDocTransaction } from '../test_utils'; const CODE_BLOCK_HTML = '
var a = 2;
'; const DIAGRAM_HTML = ''; const FRONTMATTER_HTML = '
key: value
'; const PARAGRAPH_HTML = '

Just a regular paragraph

'; describe('content_editor/extensions/paste_markdown', () => { let tiptapEditor; let doc; let p; let bold; let renderMarkdown; let eventHub; const defaultData = { 'text/plain': '**bold text**' }; beforeEach(() => { renderMarkdown = jest.fn(); eventHub = eventHubFactory(); jest.spyOn(eventHub, '$emit'); tiptapEditor = createTestEditor({ extensions: [ Bold, CodeBlockHighlight, Diagram, Frontmatter, PasteMarkdown.configure({ renderMarkdown, eventHub }), ], }); ({ builders: { doc, p, bold }, } = createDocBuilder({ tiptapEditor, names: { bold: { markType: Bold.name }, }, })); }); const buildClipboardEvent = ({ data = {}, types = ['text/plain'] } = {}) => { return Object.assign(new Event('paste'), { clipboardData: { types, getData: jest.fn((type) => data[type] || defaultData[type]) }, }); }; const triggerPasteEventHandler = (event) => { return new Promise((resolve) => { tiptapEditor.view.someProp('handlePaste', (eventHandler) => { resolve(eventHandler(tiptapEditor.view, event)); }); }); }; const triggerPasteEventHandlerAndWaitForTransaction = (event) => { return waitUntilNextDocTransaction({ tiptapEditor, action: () => { tiptapEditor.view.someProp('handlePaste', (eventHandler) => { return eventHandler(tiptapEditor.view, event); }); }, }); }; it.each` types | data | handled | desc ${['text/plain']} | ${{}} | ${true} | ${'handles plain text'} ${['text/plain', 'text/html']} | ${{}} | ${false} | ${'doesn’t handle html format'} ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "markdown" }' }} | ${true} | ${'handles vscode markdown'} ${['text/plain', 'text/html', 'vscode-editor-data']} | ${{ 'vscode-editor-data': '{ "mode": "ruby" }' }} | ${false} | ${'doesn’t vscode code snippet'} `('$desc', async ({ types, handled, data }) => { expect(await triggerPasteEventHandler(buildClipboardEvent({ types, data }))).toBe(handled); }); it.each` nodeType | html | handled | desc ${'codeBlock'} | ${CODE_BLOCK_HTML} | ${false} | ${'does not handle'} ${'diagram'} | ${DIAGRAM_HTML} | ${false} | ${'does not handle'} ${'frontmatter'} | ${FRONTMATTER_HTML} | ${false} | ${'does not handle'} ${'paragraph'} | ${PARAGRAPH_HTML} | ${true} | ${'handles'} `('$desc paste if currently a `$nodeType` is in focus', async ({ html, handled }) => { tiptapEditor.commands.insertContent(html); expect(await triggerPasteEventHandler(buildClipboardEvent())).toBe(handled); }); describe('when pasting raw markdown source', () => { describe('when rendering markdown succeeds', () => { beforeEach(() => { renderMarkdown.mockResolvedValueOnce('bold text'); }); it('transforms pasted text into a prosemirror node', async () => { const expectedDoc = doc(p(bold('bold text'))); await triggerPasteEventHandlerAndWaitForTransaction(buildClipboardEvent()); expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON()); }); }); describe('when rendering markdown fails', () => { beforeEach(() => { renderMarkdown.mockRejectedValueOnce(); }); it(`triggers ${ALERT_EVENT} event`, async () => { await triggerPasteEventHandler(buildClipboardEvent()); await waitForPromises(); expect(eventHub.$emit).toHaveBeenCalledWith(ALERT_EVENT, { message: expect.any(String), variant: VARIANT_DANGER, }); }); }); }); });