Task-Checkboxen tragen in beiden Renderpfaden einen Namen: docToHtml setzt aria-label aus dem Aufgabentext, die Editor-NodeView ebenso. Die NodeView rendert ihr Host-Element jetzt selbst als li (ReactNodeView- Renderer as/attrs) — TipTaps zusätzliches div-Host-Element zwischen ul und li brach die Listensemantik; der Wrapper flacht per display:contents ab, die #137-Pixel-Abstimmung bleibt erhalten (Selektor auf die neue Tiefe nachgeführt, Ausrichtung nachgemessen: 1px-Versatz unverändert). Der Wissensgraph-SVG bekommt ein beschreibendes aria-label inklusive Verweis auf die Backlinks als gleichwertige Listenform. Der Bild-Alt-Editor existierte bereits (Bild-Controls bei Auswahl) — kein Änderungsbedarf. Hinweis: gecachte Seiten übernehmen das Checkbox-Label wie bei jeder docToHtml-Änderung erst mit dem nächsten Persist ihrer Inhalte. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AGM8jo3hwoV9wsCVGfy8iq
78 lines
3.3 KiB
TypeScript
78 lines
3.3 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 a plugin block as a data-carrying placeholder (issue #76)', () => {
|
|
const block = editorSchema.nodes.plugin_block.create({
|
|
pluginId: 'mermaid',
|
|
blockType: 'diagram',
|
|
data: { source: 'A-->B "quoted" <tag>' },
|
|
});
|
|
const html = docToHtml(editorSchema.node('doc', null, [block]));
|
|
expect(html).toContain('data-plugin-block="mermaid/diagram"');
|
|
// The data JSON is attribute-escaped — no raw quotes or angle brackets.
|
|
expect(html).toContain('"quoted\\"');
|
|
expect(html).not.toContain('<tag>');
|
|
expect(html).toContain('[mermaid/diagram]');
|
|
});
|
|
|
|
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"');
|
|
// The item text names the checkbox (#169, WCAG 4.1.2).
|
|
expect(html).toContain('<input type="checkbox" disabled checked aria-label="Done">');
|
|
expect(html).toContain('<input type="checkbox" disabled aria-label="Todo">');
|
|
});
|
|
|
|
it('gives wikilinks a relative href so public/static HTML is clickable', () => {
|
|
const html = docToHtml(markdownToDoc('See [[api-guide|the API guide]].'));
|
|
// Relative slug href resolves to the sibling page under both
|
|
// /public/<pond>/… and /p/<pond>/… without pond context in the renderer.
|
|
expect(html).toContain(
|
|
'<a class="wikilink" href="api-guide" data-wikilink="api-guide" data-display="the API guide">the API guide</a>',
|
|
);
|
|
});
|
|
});
|