Some checks failed
CD / Build and push images (push) Successful in 1m50s
CI / Lint, typecheck, test (push) Failing after 52s
CI / Auth e2e pack (push) Successful in 1m47s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
ProseMirror schema (headings 1-4, lists incl. task lists, blockquote, code block, tables via prosemirror-tables, images, hard breaks; bold/ italic/code/strikethrough/link marks) plus docToMarkdown, markdownToDoc, docToPlainText, docToHtml, and extractOutline built on it. Markdown parsing extends markdown-it's default preset with a token-stream transform for GFM task lists and table-cell paragraph wrapping. docToHtml hand-rolls escaping and link-protocol allowlisting with zero DOM dependencies, so it runs in the API/collab server as well as the browser. Node names `wikilink` and `plugin_block` are reserved for later stories. Closes #24
49 lines
2.1 KiB
TypeScript
49 lines
2.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { docToHtml } from './html';
|
|
import { markdownToDoc } from './markdown';
|
|
import { editorSchema } from './schema';
|
|
|
|
describe('docToHtml (issue #24)', () => {
|
|
it('escapes text content, including angle brackets and quotes', () => {
|
|
const doc = markdownToDoc('Contains <script>alert("x")</script> literally.');
|
|
const html = docToHtml(doc);
|
|
expect(html).not.toContain('<script>');
|
|
expect(html).toContain('<script>');
|
|
expect(html).toContain('"x"');
|
|
});
|
|
|
|
it('renders inline marks and a table', () => {
|
|
const doc = markdownToDoc('**bold** and *italic* and `code`');
|
|
expect(docToHtml(doc)).toBe('<p><strong>bold</strong> and <em>italic</em> and <code>code</code></p>');
|
|
|
|
const table = markdownToDoc('| A | B |\n| --- | --- |\n| 1 | 2 |');
|
|
expect(docToHtml(table)).toBe('<table><tr><th><p>A</p></th><th><p>B</p></th></tr><tr><td><p>1</p></td><td><p>2</p></td></tr></table>');
|
|
});
|
|
|
|
it('allowlists link protocols, neutralizing javascript: hrefs', () => {
|
|
const safe = markdownToDoc('[go](https://example.org)');
|
|
expect(docToHtml(safe)).toContain('href="https://example.org"');
|
|
|
|
// markdown-it itself already refuses to tokenize `javascript:` links
|
|
// (falls back to plain text), so the schema is built directly here to
|
|
// exercise docToHtml's own allowlist (security.md) independently of
|
|
// that upstream defense.
|
|
const linkMark = editorSchema.marks.link.create({ href: 'javascript:evil' });
|
|
const doc = editorSchema.node('doc', null, [
|
|
editorSchema.node('paragraph', null, [editorSchema.text('click me', [linkMark])]),
|
|
]);
|
|
const html = docToHtml(doc);
|
|
expect(html).not.toContain('javascript:');
|
|
expect(html).toContain('href="#"');
|
|
});
|
|
|
|
it('renders task list checkboxes with their checked state', () => {
|
|
const doc = markdownToDoc('- [ ] Todo\n- [x] Done');
|
|
const html = docToHtml(doc);
|
|
expect(html).toContain('data-checked="false"');
|
|
expect(html).toContain('data-checked="true"');
|
|
expect(html).toContain('<input type="checkbox" disabled checked>');
|
|
});
|
|
});
|