// src/plugin/__tests__/markdown-renderer.test.ts /** * Tests for the markdown rendering pipeline: * - parseMarkdownToHtml basic rendering * - MarkdownRenderer.render and .renderMarkdown * - Post-processor invocation, ordering, and error isolation * - MarkdownRenderChild lifecycle (component-managed) * - Plugin reload cleanup * - Security: XSS via raw HTML, javascript: URLs, CSS selector injection */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { parseMarkdownToHtml, renderMarkdownToElement, registerPostProcessor, clearPostProcessorsForPlugin, clearAllPostProcessors, getPostProcessorCount, isDangerousUrl, _resetMarkdownRendererWarnedState, } from '../markdown-renderer-impl' import { MarkdownRenderer, MarkdownRenderChild } from '../obsidian-stubs' import { Component, _resetComponentWarnedState } from '../component' import { App } from '../app' import { Vault } from '../vault' import { Workspace } from '../workspace' import { FileIndex } from '../../services/file-index' import { Plugin, _resetPluginWarnedStubs } from '../plugin' import type { PluginManifest } from '../types' import type { AutomergeUrl } from '@automerge/automerge-repo' function createMockDocumentManager() { return { createFile: vi.fn(), createFolder: vi.fn(), createFileInFolder: vi.fn().mockImplementation( async () => `automerge:file-${Math.random().toString(36).slice(2)}` as AutomergeUrl ), readText: vi.fn(async (url: AutomergeUrl) => ({ status: 'unavailable' as const, documentId: url })), updateContent: vi.fn(), } as any } function createTestApp(): App { const dm = createMockDocumentManager() const vault = new Vault('Test', new FileIndex(dm, 'automerge:root' as AutomergeUrl), dm) const workspace = new Workspace(document.createElement('div')) return new App(vault, workspace) } function createTestPlugin(app: App, id = 'test-plugin'): Plugin { const manifest: PluginManifest = { id, name: 'Test Plugin', version: '1.0.0', minAppVersion: '1.0.0', author: 'Test', description: 'Test plugin', } class TestPlugin extends Plugin { onload() {} } return new TestPlugin(app, manifest) } // Top-level isolation: ensure no test leaks processor registrations or // warn-once state to the next. afterEach(() => { clearAllPostProcessors() _resetMarkdownRendererWarnedState() _resetComponentWarnedState() _resetPluginWarnedStubs() }) // ----------------------------------------------------------------------------- // parseMarkdownToHtml // ----------------------------------------------------------------------------- describe('parseMarkdownToHtml', () => { it('renders bold text', () => { const html = parseMarkdownToHtml('**bold**') expect(html).toContain('bold') }) it('renders italic text', () => { const html = parseMarkdownToHtml('*italic*') expect(html).toContain('italic') }) it('renders headings', () => { const html = parseMarkdownToHtml('# Heading 1\n## Heading 2') expect(html).toContain('

Heading 1

') expect(html).toContain('

Heading 2

') }) it('renders unordered lists', () => { const html = parseMarkdownToHtml('- item 1\n- item 2') expect(html).toContain('