UNPKG

@automattic/rtjson-to-wpblocks

Version:

Javascript code to convert Day One RTJson to WordPress Gutenberg Blocks

735 lines (669 loc) 18.7 kB
// @ts-check const dedent = require("dedent").default; const { describe, it, expect } = require("@jest/globals"); const { rtjNodesToGBHtml } = require("../index.js"); const exp = require("constants"); const fs = require("fs").promises; // 🚨 HEY! We're patching the String prototype here to make tests easier to write. String.prototype.fixWhitespace = function () { return this.replaceAll(/>[\n\r\s]+</g, ">\n<").replaceAll(/></g, ">\n<"); }; describe("Converting RTJson to GB Serialized Block format", () => { it("Should convert a paragraph node", () => { let node = { attributes: {}, text: "It has a paragraph\n", }; let expected = dedent` <!-- wp:paragraph --> <p>It has a paragraph</p> <!-- /wp:paragraph --> `; expect(rtjNodesToGBHtml([node]).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should convert a header node", () => { let node2 = { attributes: { line: { header: 2, }, }, text: "Header 2\n", }; let expected2 = dedent` <!-- wp:heading {"level":2} --> <h2 class="wp-block-heading">Header 2</h2> <!-- /wp:heading --> `; expect(rtjNodesToGBHtml([node2])).toBe(expected2); let node3 = { attributes: { line: { header: 3, }, }, text: "Header 3\n", }; // Quick strange note here. In actual testing with the GB editor, it seems that // An h2 can have the "{level: 2}" part completely omitted, but the other levels // require it. This library will always include it because that seems to be valid // behavior according to GB experimentation. let expected3 = dedent` <!-- wp:heading {"level":3} --> <h3 class="wp-block-heading">Header 3</h3> <!-- /wp:heading --> `; expect(rtjNodesToGBHtml([node3])).toBe(expected3); let nodeForAnH1 = { attributes: { line: { header: 1, }, }, text: "Header 1\n", }; let expectedForAnH1 = dedent` <!-- wp:heading {"level":1} --> <h1 class="wp-block-heading">Header 1</h1> <!-- /wp:heading --> `; expect(rtjNodesToGBHtml([nodeForAnH1])).toBe(expectedForAnH1); }); it("should handle a simple list", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { attributes: { line: { listStyle: "bulleted", indentLevel: 1, }, }, text: "one\n", }, { attributes: { line: { listStyle: "bulleted", indentLevel: 1, }, }, text: "two\n", }, ]; expect(rtjNodesToGBHtml(nodes)).toMatchInlineSnapshot(` "<!-- wp:list --> <ul><!-- wp:list-item --> <li>one</li> <!-- /wp:list-item --><!-- wp:list-item --> <li>two</li> <!-- /wp:list-item --></ul> <!-- /wp:list -->" `); }); it("Should handle complex lists", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { attributes: { line: { listStyle: "bulleted", indentLevel: 1, }, }, text: "And a list\n", }, { attributes: { line: { listStyle: "bulleted", indentLevel: 2, }, }, text: "Indented\n", }, { attributes: { line: { listStyle: "bulleted", indentLevel: 3, }, }, text: "again\n", }, ]; let expected = dedent` <!-- wp:list --> <ul><!-- wp:list-item --> <li>And a list<!-- wp:list --> <ul><!-- wp:list-item --> <li>Indented<!-- wp:list --> <ul><!-- wp:list-item --> <li>again</li> <!-- /wp:list-item --></ul> <!-- /wp:list --></li> <!-- /wp:list-item --></ul> <!-- /wp:list --></li> <!-- /wp:list-item --></ul> <!-- /wp:list --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle different lists following each other", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { attributes: { line: { listStyle: "bulleted", indentLevel: 1, }, }, text: "And a bulleted list\n", }, { attributes: { line: { listStyle: "numbered", indentLevel: 1, }, }, text: "A numbered list\n", }, { attributes: { line: { listStyle: "numbered", indentLevel: 1, }, }, text: "numbered 2\n", }, ]; let expected = dedent` <!-- wp:list --> <ul><!-- wp:list-item --> <li>And a bulleted list</li> <!-- /wp:list-item --></ul> <!-- /wp:list --> <!-- wp:list --> <ol><!-- wp:list-item --> <li>A numbered list</li> <!-- /wp:list-item --> <!-- wp:list-item --> <li>numbered 2</li> <!-- /wp:list-item --></ol> <!-- /wp:list --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle a photo node", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "photo", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { id: 11, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg", }, }; let expected = dedent` <!-- wp:image {"id":11,"sizeSlug":"large","linkDestination":"none"} --> <figure class="wp-block-image size-large"><img src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg" alt="" class="wp-image-11"/></figure> <!-- /wp:image --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle a photo gallery node", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "photo", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, { type: "photo", identifier: "A7C0EB0794D04DD3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { id: 11, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg", }, A7C0EB0794D04DD3B496308ED46D179A: { id: 12, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg", }, }; let expected = dedent` <!-- wp:gallery --> <figure class="wp-block-gallery has-nested-images"> <!-- wp:image {"id":11,"sizeSlug":"large","linkDestination":"none"} --> <figure class="wp-block-image size-large"><img src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg" alt="" class="wp-image-11"/></figure> <!-- /wp:image --> <!-- wp:image {"id":12,"sizeSlug":"large","linkDestination":"none"} --> <figure class="wp-block-image size-large"><img src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg" alt="" class="wp-image-12"/></figure> <!-- /wp:image --> </figure> <!-- /wp:gallery --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); // If the ID is a string in the output HTML, gutenberg will report the block as broken. it("Should always write the photo ID as a number, even if the identifier is passed as a string", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "photo", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { // @ts-expect-error: testing id: "11", url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg", }, }; let expected = dedent` <!-- wp:image {"id":11,"sizeSlug":"large","linkDestination":"none"} --> <figure class="wp-block-image size-large"><img src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/IMG_7738-768x1024.jpeg" alt="" class="wp-image-11"/></figure> <!-- /wp:image --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle a video node", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "video", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { id: 18, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/biking.mp4", }, }; let expected = dedent` <!-- wp:video {"id":18} --> <figure class="wp-block-video"> <video controls src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/biking.mp4"></video> </figure> <!-- /wp:video --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle an audio node", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "audio", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { id: 21, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/ForTheDirtMan.mp3", }, }; let expected = dedent` <!-- wp:audio {"id":21} --> <figure class="wp-block-audio"><audio controls src="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/ForTheDirtMan.mp3"></audio></figure> <!-- /wp:audio --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle a PDF node", () => { /** @type {Array<RTJ.Node>} */ let nodes = [ { embeddedObjects: [ { type: "pdfAttachment", identifier: "B7C0EB0794D04FF3B496308ED46D179A", }, ], }, ]; /** @type {MediaLookup} */ let lookup = { B7C0EB0794D04FF3B496308ED46D179A: { id: 17, url: "https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/it-is-a-mystery.pdf", }, }; let expected = dedent` <!-- wp:file {"id":17,"href":"https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/it-is-a-mystery.pdf","displayPreview":true} --> <div class="wp-block-file"><object class="wp-block-file__embed" data="https://testposting.mystagingwebsite.com/wp-content/uploads/2024/06/it-is-a-mystery.pdf" type="application/pdf" style="width:100%;height:600px"></object></div> <!-- /wp:file --> `; expect(rtjNodesToGBHtml(nodes, lookup).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle italics and bold in the middle of a paragraph", () => { /** @type {Array<RTJNode>} */ let nodes = [ { attributes: {}, text: "Here is a paragraph ", }, { attributes: { bold: true, }, text: "with bold text", }, { attributes: {}, text: " and ", }, { attributes: { italic: true, }, text: "with italicized text", }, { attributes: {}, text: " and ", }, { attributes: { bold: true, italic: true, }, text: "with both", }, { attributes: {}, text: ".\n", }, ]; let expected = dedent` <!-- wp:paragraph --> <p>Here is a paragraph <strong>with bold text</strong> and <em>with italicized text</em> and <strong><em>with both</em></strong>.</p> <!-- /wp:paragraph --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle highlighted text in the middle of a paragraph", () => { let color = "#ffdddd"; /** @type {Array<RTJNode>} */ let nodes = [ { attributes: {}, text: "Here is a paragraph ", }, { attributes: { highlightedColor: color, }, text: "with highlighted text", }, { attributes: {}, text: ".\n", }, ]; let expected = dedent` <!-- wp:paragraph --> <p>Here is a paragraph <mark data-rich-text-format-boundary="true" style="background-color:${color}" class="has-inline-color">with highlighted text</mark>.</p> <!-- /wp:paragraph --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("Should handle inlines code in the middle of a paragraph", () => { /** @type {Array<RTJNode>} */ let nodes = [ { attributes: {}, text: "Here is a paragraph ", }, { attributes: { inlineCode: true, }, text: "with some code", }, { attributes: {}, text: " in it.\n", }, ]; let expected = dedent` <!-- wp:paragraph --> <p>Here is a paragraph <code>with some code</code> in it.</p> <!-- /wp:paragraph --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle block quote", () => { /** @type {Array<RTJNode>} */ let nodes = [ { attributes: { line: { quote: true, }, }, text: "Here is a quote block\n", }, ]; let expected = dedent` <!-- wp:quote --> <blockquote class="wp-block-quote"><!-- wp:paragraph --> <p>Here is a quote block</p> <!-- /wp:paragraph --></blockquote> <!-- /wp:quote -->`; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle a multiple line block quote", () => { /** @type {Array<RTJNode>} */ let nodes = [ { attributes: { line: { quote: true, }, }, text: "Here is a quote block\n", }, { attributes: { line: { quote: true, }, }, text: "Here's the second line\n", }, ]; let expected = dedent` <!-- wp:quote --> <blockquote class="wp-block-quote"> <!-- wp:paragraph --> <p>Here is a quote block</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>Here's the second line</p> <!-- /wp:paragraph --> </blockquote> <!-- /wp:quote --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle a code block", () => { /** @type {Array<RTJNode>} */ let nodes = [ { attributes: { line: { codeBlock: true, }, }, text: "Here is a code block\n", }, ]; let expected = dedent` <!-- wp:code --> <pre class="wp-block-code"><code>Here is a code block</code> </pre> <!-- /wp:code --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle a horizontal ruler", () => { /** @type {Array<RTJNode>} */ let nodes = [ { embeddedObjects: [{ type: "horizontalRuleLine" }], }, ]; let expected = dedent` <!-- wp:separator --> <hr class="wp-block-separator has-alpha-channel-opacity"/> <!-- /wp:separator --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle multiple quote nodes correctly", () => { /** @type {Array<RTJNode>} */ let nodes = [ { text: "First quote line ", attributes: { line: { quote: true }, }, }, { text: "still first paragraph\n", attributes: { line: { quote: true }, }, }, { text: "Second paragraph in quote\n", attributes: { line: { quote: true }, }, }, // Add a non-quote node to ensure quote block closes properly { text: "Regular paragraph\n", }, ]; let expected = dedent` <!-- wp:quote --> <blockquote class="wp-block-quote"> <!-- wp:paragraph --> <p>First quote line still first paragraph</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>Second paragraph in quote</p> <!-- /wp:paragraph --> </blockquote> <!-- /wp:quote --> <!-- wp:paragraph --> <p>Regular paragraph</p> <!-- /wp:paragraph --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); it("should handle quotes with text formatting", () => { /** @type {Array<RTJNode>} */ let nodes = [ { text: "Quote with ", attributes: { line: { quote: true }, bold: true, }, }, { text: "mixed formatting\n", attributes: { line: { quote: true }, italic: true, }, }, ]; let expected = dedent` <!-- wp:quote --> <blockquote class="wp-block-quote"> <!-- wp:paragraph --> <p><strong>Quote with </strong><em>mixed formatting</em> </p> <!-- /wp:paragraph --> </blockquote> <!-- /wp:quote --> `; expect(rtjNodesToGBHtml(nodes).fixWhitespace()).toBe( expected.fixWhitespace() ); }); });