1import { expect, it } from "vite-plus/test";
2import { unified } from "unified";
3import remarkParse from "remark-parse";
4import remarkRehype from "remark-rehype";
5import { CHAT_MARKDOWN_REHYPE_PLUGINS, CHAT_MARKDOWN_REMARK_PLUGINS } from "./markdownPipeline.ts";
6
7it("preserves in-app thread links through the shared Markdown sanitizer", () => {
8 const processor = unified()
9 .use(remarkParse)
10 .use(CHAT_MARKDOWN_REMARK_PLUGINS)
11 .use(remarkRehype, { allowDangerousHtml: true })
12 .use(CHAT_MARKDOWN_REHYPE_PLUGINS);
13 const tree = processor.runSync(
14 processor.parse(
15 '[Open thread](t3-thread://v1/environment/thread)\n\n<a href="javascript:alert(1)">Unsafe</a>',
16 ),
17 );
18 expect(tree.children[0]).toMatchObject({
19 tagName: "p",
20 children: [
21 {
22 tagName: "a",
23 properties: { href: "t3-thread://v1/environment/thread" },
24 children: [{ type: "text", value: "Open thread" }],
25 },
26 ],
27 });
28 expect(JSON.stringify(tree)).not.toContain("javascript:");
29});
30
31it("preserves safe inline HTML semantics while rejecting executable markup and attributes", () => {
32 const processor = unified()
33 .use(remarkParse)
34 .use(CHAT_MARKDOWN_REMARK_PLUGINS)
35 .use(remarkRehype, { allowDangerousHtml: true })
36 .use(CHAT_MARKDOWN_REHYPE_PLUGINS);
37 const tree = processor.runSync(
38 processor.parse(
39 '<kbd onclick="alert(1)">Cmd</kbd> <mark style="color:red" onmouseover="alert(1)">marked</mark> <u>underlined</u> <script>alert(1)</script> <a href="javascript:alert(1)">unsafe</a>',
40 ),
41 );
42 expect(tree.children[0]).toMatchObject({
43 tagName: "p",
44 children: expect.arrayContaining([
45 expect.objectContaining({
46 tagName: "kbd",
47 properties: {},
48 children: [expect.objectContaining({ type: "text", value: "Cmd" })],
49 }),
50 expect.objectContaining({
51 tagName: "mark",
52 properties: {},
53 children: [expect.objectContaining({ type: "text", value: "marked" })],
54 }),
55 expect.objectContaining({
56 tagName: "u",
57 properties: {},
58 children: [expect.objectContaining({ type: "text", value: "underlined" })],
59 }),
60 ]),
61 });
62 const rendered = JSON.stringify(tree);
63 expect(rendered).not.toContain("javascript:");
64 expect(rendered).not.toContain('"tagName":"script"');
65 expect(rendered).not.toContain("onclick");
66 expect(rendered).not.toContain("onmouseover");
67 expect(rendered).not.toContain('"style"');
68});