import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { serializeRenderedMarkdownFragment } from "./markdown-clipboard"; const TEXT_NODE = 3; const ELEMENT_NODE = 1; class FakeText { readonly nodeType = TEXT_NODE; readonly childNodes: ReadonlyArray = []; constructor(readonly textContent: string) {} } class FakeElement { readonly nodeType = ELEMENT_NODE; readonly childNodes: Array = []; readonly classList = { contains: (name: string) => this.classNames.includes(name), }; constructor( readonly tagName: string, private readonly classNames: ReadonlyArray = [], private readonly attributes: Readonly> = {}, ) {} get localName(): string { return this.tagName.toLowerCase(); } get textContent(): string { return this.childNodes.map((child) => child.textContent).join(""); } append(...children: Array): this { this.childNodes.push(...children); return this; } getAttribute(name: string): string | null { return this.attributes[name] ?? null; } hasAttribute(name: string): boolean { return Object.hasOwn(this.attributes, name); } } function asNode(element: FakeElement): Node { return element as unknown as Node; } function shikiCodeLine(text: string): FakeElement { const token = new FakeElement("SPAN").append(new FakeText(text)); return new FakeElement("SPAN", ["line"]).append(token); } describe("serializeRenderedMarkdownFragment", () => { beforeEach(() => { vi.stubGlobal("Node", { TEXT_NODE, ELEMENT_NODE }); }); afterEach(() => { vi.unstubAllGlobals(); }); it("wraps inline code in backticks", () => { const paragraph = new FakeElement("P").append( new FakeText("run "), new FakeElement("CODE").append(new FakeText("git status")), new FakeText(" first"), ); const container = new FakeElement("DIV").append(paragraph); expect(serializeRenderedMarkdownFragment(asNode(container))).toBe("run `git status` first"); }); it("keeps a highlighted block code selection plain when its pre wrapper is outside the range", () => { const code = new FakeElement("CODE").append( shikiCodeLine("git show-ref --verify refs/remotes/origin/opt/deploy/dev"), ); const container = new FakeElement("DIV").append(code); expect(serializeRenderedMarkdownFragment(asNode(container))).toBe( "git show-ref --verify refs/remotes/origin/opt/deploy/dev", ); }); it("keeps a multi-line code selection plain instead of inline-wrapping it", () => { const code = new FakeElement("CODE").append(new FakeText("first line\nsecond line")); const container = new FakeElement("DIV").append(code); expect(serializeRenderedMarkdownFragment(asNode(container))).toBe("first line\nsecond line"); }); it("uses a rendered card's explicit Markdown copy representation", () => { const card = new FakeElement("DIV", [], { "data-markdown-copy": "Hello World (Document template)\n\n", }).append( new FakeElement("SPAN").append(new FakeText("Hello World")), new FakeElement("SPAN").append(new FakeText("Document template")), new FakeElement("BUTTON").append(new FakeText("Use template")), ); const container = new FakeElement("DIV").append(card); expect(serializeRenderedMarkdownFragment(asNode(container))).toBe( "Hello World (Document template)", ); }); });