File size: 5,429 Bytes
fae625a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { describe, expect, it } from "vitest";
import {
	buildArtifactSrcdoc,
	buildDeployableHtml,
	isDeployableKind,
} from "$lib/utils/previewSrcdoc";
import { BADGE_HOST_ATTRIBUTE } from "$lib/utils/deployBadge";

/** Drop the injected badge script so the rest of the document can be compared. */
function stripBadge(html: string): string {
	return html.replace(/\n<script data-huggingchat-badge[\s\S]*?<\/script>\n/, "");
}

describe("buildDeployableHtml", () => {
	it("flags only self-contained kinds as deployable", () => {
		expect(isDeployableKind("html")).toBe(true);
		expect(isDeployableKind("svg")).toBe(true);
		expect(isDeployableKind("react")).toBe(true);
		expect(isDeployableKind("mermaid")).toBe(true);
		expect(isDeployableKind("code")).toBe(false);
		expect(isDeployableKind("markdown")).toBe(false);
	});

	it("ships raw HTML untouched apart from the badge (no base tag or preview hook)", () => {
		const html = `<!doctype html><html><head><title>App</title></head><body><a href="/page">x</a></body></html>`;
		const out = buildDeployableHtml("html", html);
		expect(out).not.toContain("base target");
		expect(stripBadge(out)).toBe(html);
	});

	// The deployed page has no parent window, so the postMessage hook (which the
	// preview builders inject) must never ship to a Space.
	it("never embeds the preview postMessage hook", () => {
		const kinds = [
			["svg", "<svg xmlns='http://www.w3.org/2000/svg'><rect width='10' height='10'/></svg>"],
			["react", "export default function App(){ return <div>hi</div>; }"],
			["mermaid", "graph TD; A-->B;"],
		] as const;
		for (const [kind, content] of kinds) {
			const out = buildDeployableHtml(kind, content);
			expect(out, kind).not.toContain("parent.postMessage");
			expect(out, kind).not.toContain("chatui.preview");
		}
	});

	it("wraps React with the React + Babel CDNs and embeds the source", () => {
		const out = buildDeployableHtml(
			"react",
			"export default function App(){ return <div>hi</div>; }"
		);
		expect(out).toContain("unpkg.com/react@18");
		expect(out).toContain("@babel/standalone");
		expect(out).toContain("artifact-root");
	});

	it("wraps Mermaid with the Mermaid CDN", () => {
		const out = buildDeployableHtml("mermaid", "graph TD; A-->B;");
		expect(out).toContain("mermaid@11");
		expect(out).toContain("graph TD");
	});

	it("wraps bare SVG into a full HTML document", () => {
		const svg = "<svg xmlns='http://www.w3.org/2000/svg'><rect width='10' height='10'/></svg>";
		const out = buildDeployableHtml("svg", svg);
		expect(out).toContain("<!doctype html>");
		expect(out).toContain("<svg");
	});
});

describe("HuggingChat badge", () => {
	const kinds = [
		["html", "<!doctype html><html><body><p>hi</p></body></html>"],
		["svg", "<svg xmlns='http://www.w3.org/2000/svg'><rect width='10' height='10'/></svg>"],
		["react", "export default function App(){ return <div>hi</div>; }"],
		["mermaid", "graph TD; A-->B;"],
	] as const;

	it("is injected into every deployed kind, inside a closed shadow root", () => {
		for (const [kind, content] of kinds) {
			const out = buildDeployableHtml(kind, content);
			expect(out, kind).toContain(BADGE_HOST_ATTRIBUTE);
			expect(out, kind).toContain("Made with HuggingChat");
			expect(out, kind).toContain("https://huggingface.co/chat");
			expect(out, kind).toContain('mode: "closed"');
		}
	});

	it("is never injected into previews", () => {
		for (const [kind, content] of kinds) {
			expect(buildArtifactSrcdoc(kind, content, "chan"), kind).not.toContain(BADGE_HOST_ATTRIBUTE);
		}
	});

	it("goes after the whole document, never inside it", () => {
		const doc = "<!doctype html><html><body><p>hi</p></body></html>";
		const out = buildDeployableHtml("html", doc);
		expect(out.startsWith(doc)).toBe(true);
		expect(out.indexOf(BADGE_HOST_ATTRIBUTE)).toBeGreaterThan(out.lastIndexOf("</html>"));
	});

	// A `</body>` in a trailing script's string literal used to capture the
	// injection point, so the badge's own `</script>` terminated the artifact's
	// script — breaking the page *and* losing the badge.
	it("never injects into a trailing script that mentions a closing body tag", () => {
		const doc = `<!doctype html><html><body><h1>hi</h1></body>\n<script>var tpl = "</body>";</script>\n</html>`;
		const out = buildDeployableHtml("html", doc);
		expect(out.startsWith(doc)).toBe(true);
		expect(out).not.toContain('var tpl = "\n<script');
	});

	it("appends at the end when the document has no body tag", () => {
		const out = buildDeployableHtml("html", "<p>fragment only</p>");
		expect(out.startsWith("<p>fragment only</p>")).toBe(true);
		expect(out).toContain(BADGE_HOST_ATTRIBUTE);
	});

	// Selecting an injection point used to be quadratic in the number of
	// `</body>` occurrences; appending is linear regardless of content.
	it("stays fast on content that repeats the closing body tag", () => {
		const doc = "<html><body>" + "</body>".repeat(20_000) + "</html>";
		const start = performance.now();
		buildDeployableHtml("html", doc);
		expect(performance.now() - start).toBeLessThan(100);
	});

	it("cannot be terminated early by artifact content", () => {
		const out = buildDeployableHtml("html", "<body></body>");
		// The badge script must contain no raw `</script` other than its own terminator.
		const script = out.slice(out.indexOf("<script data-huggingchat-badge"));
		expect(script.match(/<\/script/gi)).toHaveLength(1);
	});
});