import { describe, expect, it } from "vitest"; import { BlockNoteEditor } from "../../.."; import { nestedListsToBlockNoteStructure } from "./util/nestedLists"; async function parseHTMLAndCompareSnapshots( html: string, snapshotName: string ) { // use a dynamic import because we want to access // __parseFromClipboard which is not exposed in types const view: any = await import("prosemirror-view"); const editor = BlockNoteEditor.create(); const blocks = await editor.tryParseHTMLToBlocks(html); const snapshotPath = "./__snapshots__/paste/" + snapshotName + ".json"; expect(JSON.stringify(blocks, undefined, 2)).toMatchFileSnapshot( snapshotPath ); // Now, we also want to test actually pasting in the editor, and not just calling // tryParseHTMLToBlocks directly. // The reason is that the prosemirror logic for pasting can be a bit different, because // it's related to the context of where the user is pasting exactly (selection) // // The internal difference come that in tryParseHTMLToBlocks, we use DOMParser.parse, // while when pasting, Prosemirror uses DOMParser.parseSlice, and then tries to fit the // slice in the document. This fitting might change the structure / interpretation of the pasted blocks // Simulate a paste event (this uses DOMParser.parseSlice internally) (window as any).__TEST_OPTIONS.mockID = 0; // reset id counter const htmlNode = nestedListsToBlockNoteStructure(html); const tt = editor._tiptapEditor; const slice = view.__parseFromClipboard( tt.view, "", htmlNode.innerHTML, false, tt.view.state.selection.$from ); tt.view.dispatch(tt.view.state.tr.replaceSelection(slice)); // alternative paste simulation doesn't work in a non-browser vitest env // editor._tiptapEditor.view.pasteHTML(html, { // preventDefault: () => { // // noop // }, // clipboardData: { // types: ["text/html"], // getData: () => html, // }, // } as any); const pastedBlocks = editor.topLevelBlocks; pastedBlocks.pop(); // trailing paragraph expect(pastedBlocks).toStrictEqual(blocks); } describe("Parse HTML", () => { it("Parse basic block types", async () => { const html = `

Heading 1

Heading 2

Heading 3

Paragraph

Image Caption

None Bold Italic Underline Strikethrough All

`; await parseHTMLAndCompareSnapshots(html, "parse-basic-block-types"); }); it("list test", async () => { const html = ``; await parseHTMLAndCompareSnapshots(html, "list-test"); }); it("Parse nested lists", async () => { const html = `
  1. Numbered List Item
    1. Nested Numbered List Item
    2. Nested Numbered List Item
  2. Numbered List Item
`; await parseHTMLAndCompareSnapshots(html, "parse-nested-lists"); }); it("Parse nested lists with paragraphs", async () => { const html = `
  1. Numbered List Item

    1. Nested Numbered List Item

    2. Nested Numbered List Item

  2. Numbered List Item

`; await parseHTMLAndCompareSnapshots( html, "parse-nested-lists-with-paragraphs" ); }); it("Parse mixed nested lists", async () => { const html = `
  1. Numbered List Item
  2. Numbered List Item
`; await parseHTMLAndCompareSnapshots(html, "parse-mixed-nested-lists"); }); it("Parse divs", async () => { const html = `
Single Div
Div
Nested Div
Nested Div
Single Div 2
Nested Div
Nested Div
`; await parseHTMLAndCompareSnapshots(html, "parse-divs"); }); it("Parse two divs", async () => { const html = `
Single Div
second Div
`; await parseHTMLAndCompareSnapshots(html, "parse-two-divs"); }); it("Parse fake image caption", async () => { const html = `

Image Caption

`; await parseHTMLAndCompareSnapshots(html, "parse-fake-image-caption"); }); // TODO: this one fails it.skip("Parse deep nested content", async () => { const html = `
Outer 1 Div Before
Outer 2 Div Before
Outer 3 Div Before
Outer 4 Div Before

Heading 1

Heading 2

Heading 3

Paragraph

Image Caption

Bold Italic Underline Strikethrough All

Outer 4 Div After
Outer 3 Div After
Outer 2 Div After
Outer 1 Div After
`; await parseHTMLAndCompareSnapshots(html, "parse-deep-nested-content"); }); it("Parse div with inline content and nested blocks", async () => { const html = `
None Bold Italic Underline Strikethrough All
Nested Div

Nested Paragraph

`; await parseHTMLAndCompareSnapshots(html, "parse-div-with-inline-content"); }); it("Parse Notion HTML", async () => { // A few notes on Notion output HTML: // - Does not preserve text/background colors // - Does not preserve non-list-item block nesting // - Hard breaks are represented using white space, not `
` elements // - Images are converted to links with a "!" at the start // - Cells in first row of a table are converted to `th` elements, regardless // of if the row is set as a header row const html = `

Heading 1

Heading 2

Heading 3

Paragraph 1

Nested Paragraph 1

Nested Paragraph 2

Paragraph With Hard Break

Bold Italic Underline Strikethrough All

  1. Numbered List Item 1
  2. Numbered List Item 2

Background Color Paragraph

!https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg

Cell 1 Cell 2 Cell 3
Cell 4 Cell 5 Cell 6
Cell 7 Cell 8 Cell 9

Paragraph

`; await parseHTMLAndCompareSnapshots(html, "parse-notion-html"); }); // Currently breaking, seems related to parsing `
` elements it.skip("Parse Google Docs HTML", async () => { // A few notes on Google Docs output HTML: // - All inline markup is represented as `` elements with inline // styles (bold, italic, etc.) // - The nested list structure is not valid, i.e. `