@automattic/rtjson-to-wpblocks
Version:
Javascript code to convert Day One RTJson to WordPress Gutenberg Blocks
499 lines (453 loc) • 11.3 kB
JavaScript
// @ts-check
const dedent = require("dedent").default;
const { describe, it, expect } = require("@jest/globals");
const { rtjNodesToHtml } = require("../nodes2html.js");
// 🚨 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`
<p>It has a paragraph</p>
`;
expect(rtjNodesToHtml([node]).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("Should convert a header node as html", () => {
let node2 = {
attributes: {
line: {
header: 2,
},
},
text: "Header 2\n",
};
let expected2 = dedent`
<h2 class="wp-block-heading">Header 2</h2>
`;
expect(rtjNodesToHtml([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`
<h3 class="wp-block-heading">Header 3</h3>
`;
expect(rtjNodesToHtml([node3])).toBe(expected3);
let nodeForAnH1 = {
attributes: {
line: {
header: 1,
},
},
text: "Header 1\n",
};
let expectedForAnH1 = dedent`
<h1 class="wp-block-heading">Header 1</h1>
`;
expect(rtjNodesToHtml([nodeForAnH1])).toBe(expectedForAnH1);
});
it("should handle a simple list as html", () => {
/** @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(rtjNodesToHtml(nodes)).toMatchInlineSnapshot(
`"<ul><li>one</li><li>two</li></ul>"`
);
});
it("Should handle complex lists as html", () => {
/** @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`
<ul>
<li>And a list<ul>
<li>Indented<ul>
<li>again</li>
</ul>
</li>
</ul>
</li>
</ul>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("Should handle different lists following each other as html", () => {
/** @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`
<ul>
<li>And a bulleted list</li>
</ul>
<ol>
<li>A numbered list</li>
<li>numbered 2</li>
</ol>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("Should handle a photo node as html", () => {
/** @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`
<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>
`;
expect(rtjNodesToHtml(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`
<p>Here is a paragraph <strong>with bold text</strong> and <em>with italicized text</em> and <strong><em>with both</em></strong>.</p>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("Should handle highlighted text in the middle of a paragraph as html", () => {
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`
<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>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("Should handle inline code in the middle of a paragraph as html", () => {
/** @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`
<p>Here is a paragraph <code>with some code</code> in it.</p>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("should handle block quote as html", () => {
/** @type {Array<RTJNode>} */
let nodes = [
{
attributes: {
line: {
quote: true,
},
},
text: "Here is a quote block\n",
},
];
let expected = dedent`
<blockquote class="wp-block-quote">
<p>Here is a quote block</p>
</blockquote>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("should handle a multiple line block quote as html", () => {
/** @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`
<blockquote class="wp-block-quote">
<p>Here is a quote block</p>
<p>Here's the second line</p>
</blockquote>
`;
expect(rtjNodesToHtml(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`
<pre class="wp-block-code"><code>Here is a code block</code>
</pre>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("should handle a horizontal ruler as html", () => {
/** @type {Array<RTJNode>} */
let nodes = [
{
embeddedObjects: [{ type: "horizontalRuleLine" }],
},
];
let expected = dedent`
<hr class="wp-block-separator has-alpha-channel-opacity"/>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("should handle multiple quote nodes correctly as html", () => {
/** @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`
<blockquote class="wp-block-quote">
<p>First quote line still first paragraph</p>
<p>Second paragraph in quote</p>
</blockquote>
<p>Regular paragraph</p>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
it("should handle quotes with text formatting as html", () => {
/** @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`
<blockquote class="wp-block-quote">
<p><strong>Quote with </strong><em>mixed formatting</em>
</p>
</blockquote>
`;
expect(rtjNodesToHtml(nodes).fixWhitespace()).toBe(
expected.fixWhitespace()
);
});
});