// @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( `
An apple
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

x

`, ); 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( `
Title
Desc
` + `
Buy now
` + `
💡
Note this
` + `
…
`, ); 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/); }); });