b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { renderMarkdown } from "../../src/lib/markdown";
|
|
|
|
describe("renderMarkdown", () => {
|
|
it("renders standard markdown", () => {
|
|
const html = renderMarkdown("# Title\n\n**bold** and *italic*");
|
|
expect(html).toContain("<h1>Title</h1>");
|
|
expect(html).toContain("<strong>bold</strong>");
|
|
expect(html).toContain("<em>italic</em>");
|
|
});
|
|
|
|
it("strips a <script> tag", () => {
|
|
const html = renderMarkdown("ok\n\n<script>alert(1)</script>");
|
|
expect(html).not.toContain("<script");
|
|
expect(html).not.toContain("alert(1)");
|
|
});
|
|
|
|
it("drops raw HTML blocks, including event-handler attributes", () => {
|
|
const html = renderMarkdown('<div onclick="steal()">hi</div>');
|
|
expect(html).not.toContain("<div");
|
|
expect(html).not.toContain("onclick");
|
|
});
|
|
|
|
it("drops images entirely so nothing loads from a foreign host", () => {
|
|
const html = renderMarkdown("");
|
|
expect(html).not.toContain("<img");
|
|
expect(html).not.toContain("evil.example");
|
|
expect(html).toContain("alt text");
|
|
});
|
|
|
|
it("renders an https link with target=_blank and rel=noopener noreferrer", () => {
|
|
const html = renderMarkdown("[ok](https://example.com)");
|
|
expect(html).toContain('href="https://example.com"');
|
|
expect(html).toContain('rel="noopener noreferrer"');
|
|
expect(html).toContain('target="_blank"');
|
|
});
|
|
|
|
it("renders inline markdown inside link text", () => {
|
|
const html = renderMarkdown("[**bold** link](https://example.com)");
|
|
expect(html).toContain('href="https://example.com"');
|
|
expect(html).toContain("<strong>bold</strong>");
|
|
expect(html).not.toContain("**bold**");
|
|
});
|
|
|
|
it("drops a javascript: link, keeping only its text", () => {
|
|
const html = renderMarkdown("[click](javascript:alert(1))");
|
|
expect(html).not.toContain("javascript:");
|
|
expect(html).not.toContain("<a");
|
|
expect(html).toContain("click");
|
|
});
|
|
|
|
it("drops a data: link", () => {
|
|
const html = renderMarkdown("[x](data:text/html,<script>alert(1)</script>)");
|
|
expect(html).not.toContain("data:");
|
|
expect(html).not.toContain("<script");
|
|
});
|
|
|
|
it("never emits an inline style attribute", () => {
|
|
const html = renderMarkdown('<p style="position:fixed;inset:0">x</p>');
|
|
expect(html).not.toContain("style=");
|
|
});
|
|
});
|