evOo-local/packages/chat-ui/src/markdown.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

31 lines
1.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { closeUnterminatedFence, sanitizeMarkdownUrl } from "./markdown";
describe("sanitizeMarkdownUrl", () => {
it("allows normal external links and optionally allows local links", () => {
expect(sanitizeMarkdownUrl("https://example.com/docs")).toBe("https://example.com/docs");
expect(sanitizeMarkdownUrl("mailto:hello@example.com")).toBe("mailto:hello@example.com");
expect(sanitizeMarkdownUrl("/docs", true)).toBe("/docs");
expect(sanitizeMarkdownUrl("#section", true)).toBe("#section");
});
it("rejects executable and embedded-data URLs", () => {
expect(sanitizeMarkdownUrl("javascript:alert(1)", true)).toBeUndefined();
expect(sanitizeMarkdownUrl("data:text/html,<script>alert(1)</script>", true)).toBeUndefined();
expect(sanitizeMarkdownUrl("/docs")).toBeUndefined();
});
});
describe("closeUnterminatedFence", () => {
it("temporarily closes a partial streaming code fence", () => {
expect(closeUnterminatedFence("Before\n```ts\nconst value = 1;")).toBe(
"Before\n```ts\nconst value = 1;\n```",
);
});
it("leaves complete markdown unchanged", () => {
const markdown = "```ts\nconst value = 1;\n```\n\nDone";
expect(closeUnterminatedFence(markdown)).toBe(markdown);
});
});