import test from "node:test"; import assert from "node:assert/strict"; import React from "react"; import { renderToString } from "react-dom/server"; (globalThis as any).React = React; import { MarkdownContentViewer } from "../src/workspaces/GraphWorkspace/MarkdownContentViewer.tsx"; import { isSafeUrl } from "../src/workspaces/GraphWorkspace/markdownUrlSafety.ts"; test("isSafeUrl permits safe http, https, and mailto URLs and relative paths", () => { assert.equal(isSafeUrl("https://example.com"), true); assert.equal(isSafeUrl("http://localhost:8000"), true); assert.equal(isSafeUrl("mailto:user@example.com"), true); assert.equal(isSafeUrl("#section-1"), true); assert.equal(isSafeUrl("/relative/path"), true); }); test("isSafeUrl rejects protocol-relative URLs and dangerous schemes", () => { // Protocol-relative URLs (must be blocked) assert.equal(isSafeUrl("//evil.com"), false); assert.equal(isSafeUrl("//localhost:8000"), false); assert.equal(isSafeUrl("//"), false); // Dangerous schemes assert.equal(isSafeUrl("javascript:alert('xss')"), false); assert.equal(isSafeUrl("JAVASCRIPT:alert(1)"), false); assert.equal(isSafeUrl("data:text/html;base64,PHNjcmlwdD4="), false); assert.equal(isSafeUrl("vbscript:MsgBox(1)"), false); assert.equal(isSafeUrl(""), false); assert.equal(isSafeUrl(undefined), false); }); // ─── C URL contract: whitespace-only strings ──────────────────────────────── // The CommonMark parser normalises whitespace-only link destinations to "" so // these values are unreachable through normal markdown rendering. However, the // function is exported and its direct-call contract must be correct. test("isSafeUrl rejects whitespace-only strings (contract correctness)", () => { assert.equal(isSafeUrl(" "), false, "single space must be rejected"); assert.equal(isSafeUrl("\t"), false, "tab must be rejected"); assert.equal(isSafeUrl("\n"), false, "newline must be rejected"); assert.equal(isSafeUrl(" "), false, "multiple spaces must be rejected"); assert.equal(isSafeUrl(" \t\n "), false, "mixed whitespace must be rejected"); }); test("renders Preview mode with formatted Markdown elements and tabs", () => { const markdown = `# Main Title\n\n**Bold Statement**\n\n* Item A\n* Item B`; const html = renderToString(React.createElement(MarkdownContentViewer, { content: markdown, defaultMode: "preview" })); // Tab buttons are present assert.equal(html.includes("Preview"), true); assert.equal(html.includes("Source"), true); assert.equal(html.includes("Copy"), true); // Formatted preview elements assert.equal(html.includes("Main Title"), true); assert.equal(html.includes("Bold Statement"), true); assert.equal(html.includes("Bold Statement"), true); assert.equal(html.includes("Item A"), true); assert.equal(html.includes("Item B"), true); }); test("renders Source mode with exact unmodified text inside pre/code", () => { const markdown = `# Title 🚀\n\n * Indented item\n\n\`\`\`python\ndef test():\n return "α + β"\n\`\`\``; const html = renderToString(React.createElement(MarkdownContentViewer, { content: markdown, defaultMode: "source" })); assert.equal(html.includes(" { const dangerousHtml = ``; const html = renderToString(React.createElement(MarkdownContentViewer, { content: dangerousHtml, defaultMode: "preview" })); // Script and iframe tags must NOT be rendered as active DOM tags assert.equal(html.includes("