| 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { markdownHtmlContent, markdownWithHtml, parseMarkdownDocument } from "./markdownDocument"; |
| 3 | import { markdownImageSources } from "./markdownImageSources"; |
| 4 | |
| 5 | describe("HTML in native Markdown", () => { |
| 6 | it("leaves code inside an HTML details block literal", () => { |
| 7 | const content = |
| 8 | '<details>\n<summary>Example</summary>\n\n```html\n<img src="literal.png">\n```\n\nUse `<img>` here.\n</details>'; |
| 9 | const result = markdownHtmlContent(content); |
| 10 | expect(result).toContain('```html\n<img src="literal.png">\n```'); |
| 11 | expect(result).toContain("Use `<img>` here."); |
| 12 | expect(result).not.toContain("<details>"); |
| 13 | }); |
| 14 | it("preserves screenshot URLs and captions from GitHub HTML image embeds", () => { |
| 15 | expect( |
| 16 | markdownHtmlContent( |
| 17 | '<img width="900" alt="Before > after [settings]" src="https://example.com/before.png?a=1&amp;b=2" />', |
| 18 | ), |
| 19 | ).toBe("![Before > after \\[settings\\]](<https://example.com/before.png?a=1\\&b=2>)"); |
| 20 | expect( |
| 21 | markdownHtmlContent( |
| 22 | "<a href='https://example.com'><img src='https://example.com/banner.svg' alt='Review' /></a>", |
| 23 | ), |
| 24 | ).toBe(""); |
| 25 | }); |
| 26 | |
| 27 | it("decodes HTML attributes once and preserves entity-shaped query values through Markdown", () => { |
| 28 | const markdown = |
| 29 | '<img alt="&amp;lt;" src="https://example.com/shot.png?label=&amp;lt;&amp;sig=a%26b">'; |
| 30 | expect(markdownWithHtml(markdown)).toBe( |
| 31 | "", |
| 32 | ); |
| 33 | expect(markdownImageSources(markdown)).toEqual([ |
| 34 | "https://example.com/shot.png?label=&lt;&sig=a%26b", |
| 35 | ]); |
| 36 | const paragraph = parseMarkdownDocument(markdown).children[0]; |
| 37 | expect(paragraph?.type).toBe("paragraph"); |
| 38 | if (paragraph?.type === "paragraph") |
| 39 | expect(paragraph.children[0]).toMatchObject({ type: "image", alt: "&lt;" }); |
| 40 | }); |
| 41 | |
| 42 | it("ignores code delimiters in HTML attributes and comments when collecting screenshots", () => { |
| 43 | const markdown = |
| 44 | '<details data-example="`">\n<summary>Proof</summary>\n<img src="https://example.com/shot.png" alt="Screenshot">\n<!-- ` -->\n</details>'; |
| 45 | expect(markdownHtmlContent(markdown)).toContain( |
| 46 | "", |
| 47 | ); |
| 48 | expect(markdownImageSources(markdown)).toEqual(["https://example.com/shot.png"]); |
| 49 | expect(markdownWithHtml(markdown)).not.toContain("<img"); |
| 50 | }); |
| 51 | |
| 52 | it("removes bot metadata and details wrappers while keeping the content", () => { |
| 53 | const markdown = |
| 54 | "<!-- generated bot metadata -->\n<details>\n<summary>Walkthrough</summary>\n\n## Changes\n\nText\n\n</details>"; |
| 55 | const result = markdownWithHtml(markdown); |
| 56 | expect(result).not.toContain("<!--"); |
| 57 | expect(result).not.toContain("<details>"); |
| 58 | expect(result).toContain("**Walkthrough**"); |
| 59 | expect(result).toContain("## Changes"); |
| 60 | expect(parseMarkdownDocument(markdown).children.some((node) => node.type === "html")).toBe( |
| 61 | false, |
| 62 | ); |
| 63 | }); |
| 64 | |
| 65 | it("collects HTML and Markdown screenshots while leaving inline and fenced code literal", () => { |
| 66 | const markdown = |
| 67 | '```html\n<img src="literal.png">\n```\n\nUse `<img src="inline.png">` here.\n\n<img src="one.png">\n\n\n\n<img src="one.png">'; |
| 68 | expect(markdownImageSources(markdown)).toEqual(["one.png", "two.png"]); |
| 69 | const result = markdownWithHtml(markdown); |
| 70 | expect(result).toContain('```html\n<img src="literal.png">\n```'); |
| 71 | expect(result).toContain('`<img src="inline.png">`'); |
| 72 | }); |
| 73 | }); |