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<never> = []; constructor(readonly textContent: string) {}} class FakeElement { readonly nodeType = ELEMENT_NODE; readonly childNodes: Array<FakeElement | FakeText> = []; readonly classList = { contains: (name: string) => this.classNames.includes(name), }; constructor( readonly tagName: string, private readonly classNames: ReadonlyArray<string> = [], private readonly attributes: Readonly<Record<string, string>> = {}, ) {} get localName(): string { return this.tagName.toLowerCase(); } get textContent(): string { return this.childNodes.map((child) => child.textContent).join(""); } append(...children: Array<FakeElement | FakeText>): 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)", ); });});
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<never> = [];
constructor(readonly textContent: string) {}
}
class FakeElement {
readonly nodeType = ELEMENT_NODE;
readonly childNodes: Array<FakeElement | FakeText> = [];
readonly classList = {
contains: (name: string) => this.classNames.includes(name),
};
constructor(
readonly tagName: string,
private readonly classNames: ReadonlyArray<string> = [],
private readonly attributes: Readonly<Record<string, string>> = {},
) {}
get localName(): string {
return this.tagName.toLowerCase();
}
get textContent(): string {
return this.childNodes.map((child) => child.textContent).join("");
}
append(...children: Array<FakeElement | FakeText>): 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)",
);
});
});