// @vitest-environment jsdom
import { describe, expect, test } from "vitest";
import { ghostHtmlToBlocks } from "./ghostToBlocks";
import type { BuiltBlock } from "src/utils/paste/htmlToBlocks";
const SITE = "https://blog.example.com";
const convert = (html: string) =>
ghostHtmlToBlocks(html, { siteUrl: SITE, parent: "page" });
const fact = (b: BuiltBlock, attribute: string) =>
b.facts.find((f) => f.attribute === attribute)?.data as
| { value?: unknown }
| undefined;
const text = (b: BuiltBlock) => b.parsedContent?.textContent ?? "";
const outline = (blocks: BuiltBlock[]) =>
blocks
.filter((b) => b.parent === "page")
.map((b) => `${b.type}${text(b) ? ": " + text(b) : ""}`);
describe("ghostHtmlToBlocks", () => {
test("prose, lists, and quotes go through the paste pipeline", () => {
const r = convert(
`
Intro with link
Head
quote
more
`,
);
expect(outline(r.blocks)).toEqual([
"text: Intro with link",
"heading: Head",
"text: one",
"text: two",
"blockquote: quotemore",
"horizontal-rule",
]);
expect(fact(r.blocks[1], "block/heading-level")?.value).toBe(2);
expect(fact(r.blocks[2], "block/is-list")?.value).toBe(true);
});
test("drops the empty paragraphs Ghost leaves behind", () => {
const r = convert(`a
b
`);
expect(outline(r.blocks)).toEqual(["text: a", "text: b"]);
});
test("image cards become image blocks with alt, size, and caption", () => {
const r = convert(
`
A caption`,
);
expect(outline(r.blocks)).toEqual(["image", "text: A caption"]);
expect(fact(r.blocks[0], "image/alt")?.value).toBe("An apple");
expect(fact(r.blocks[1], "block/text-size")?.value).toBe("small");
expect(r.images).toEqual([
{
entityID: r.blocks[0].entityID,
url: `${SITE}/content/images/a.png`,
width: 800,
height: 600,
},
]);
});
test("images wrapped in a paragraph (markdown cards) become image blocks", () => {
const r = convert(
`md

`,
);
expect(outline(r.blocks)).toEqual(["text: md", "image"]);
expect(fact(r.blocks[1], "image/alt")?.value).toBe("x");
expect(r.images).toEqual([
{
entityID: r.blocks[1].entityID,
url: `${SITE}/c.png`,
width: 20,
height: 10,
},
]);
});
test("refuses images inline with text", () => {
expect(() =>
convert(`see
here
`),
).toThrow(/Inline image/);
});
test("gallery cards yield one image block per image", () => {
const r = convert(
``,
);
expect(outline(r.blocks)).toEqual(["image", "image"]);
expect(r.images.map((i) => i.url)).toEqual([
`${SITE}/1.png`,
`${SITE}/2.png`,
]);
});
test("embed cards and bare iframes become embed blocks", () => {
const r = convert(
``,
);
expect(outline(r.blocks)).toEqual(["embed", "embed"]);
expect(fact(r.blocks[0], "embed/url")?.value).toBe(
"https://www.youtube.com/embed/abc",
);
expect(fact(r.blocks[0], "embed/height")?.value).toBe(315);
expect(fact(r.blocks[1], "embed/height")?.value).toBe(167);
});
test("html cards: a lone iframe is an embed, anything else is raw html", () => {
const r = convert(
`raw
after
`,
);
expect(outline(r.blocks)).toEqual(["embed", "html", "text: after"]);
expect(fact(r.blocks[1], "embed/html")?.value).toBe(
`raw
`,
);
});
test("bookmark, button, callout, and signup cards", () => {
const r = convert(
`` +
`` +
`` +
``,
);
expect(outline(r.blocks)).toEqual([
"link",
"button",
"blockquote: 💡 Note this",
"signup",
]);
expect(fact(r.blocks[0], "link/title")?.value).toBe("Title");
expect(fact(r.blocks[0], "link/description")?.value).toBe("Desc");
expect(fact(r.blocks[1], "button/url")?.value).toBe("https://buy.com");
expect(fact(r.blocks[1], "button/text")?.value).toBe("Buy now");
});
test("the paywall comment becomes a members-only delimiter", () => {
const r = convert(`free
paid
`);
expect(outline(r.blocks)).toEqual([
"text: free",
"members-only-delimiter",
"text: paid",
]);
});
test("refuses cards it doesn't understand", () => {
expect(() =>
convert(
`PDF`,
),
).toThrow(/kg-file-card|file/);
expect(() =>
convert(
`tweet
`,
),
).toThrow(/iframe/);
});
});