Files
MarkLite/src/renderer/lib/__tests__/markdown.test.ts
T
thzxx ee5f35177e release: v0.6.0 — 内置解析器迁移 / Toast 全面接入 / Sqlark 深度集成
- 渲染管线迁移至 MetonaEditor 内置解析器,移除 unified/rehype 全家桶(9 个依赖)
- 修复相对路径图片修复的目录前缀碰撞与路径解析 bug
- ConfirmDialog/useConfirm/LoadingSpinner/useDocStats 移除,改用 MeToast.confirm/loading/promise
- 新增状态栏(字数/行数/阅读时间/光标位置)、Zen 模式、数据备份导出导入
- Sqlark: 版本化迁移(addMigration/migrateTo)、subscribe 表变更、备份 exportAll/importTable
- 数据库损坏自愈:异常退出残留残缺 SSTable 导致打开失败时自动重建
- 大纲导航改用 scrollToLine 官方 API
- 修复 rollup 平台包互删(postinstall 自动补齐)+ 集成测试(fake-indexeddb)
2026-08-09 16:50:06 +08:00

181 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest'
import { renderMarkdown, renderMarkdownSync } from '../markdown'
describe('renderMarkdown (MetonaEditor 内置解析器)', () => {
it('should render a simple heading', async () => {
const result = await renderMarkdown('# Hello World')
expect(result).toContain('<h1')
expect(result).toContain('Hello World')
})
it('should render bold text', async () => {
const result = await renderMarkdown('**bold**')
expect(result).toContain('<strong')
expect(result).toContain('bold')
})
it('should render italic text', async () => {
const result = await renderMarkdown('*italic*')
expect(result).toContain('<em')
expect(result).toContain('italic')
})
it('should render inline code', async () => {
const result = await renderMarkdown('`code`')
expect(result).toContain('<code')
expect(result).toContain('code')
})
it('should render code blocks', async () => {
const md = '```javascript\nconst x = 1;\n```'
const result = await renderMarkdown(md)
expect(result).toContain('<pre')
expect(result).toContain('<code')
expect(result).toContain('const')
})
it('should render links', async () => {
const result = await renderMarkdown('[link](https://example.com)')
expect(result).toContain('<a')
expect(result).toContain('https://example.com')
expect(result).toContain('link')
})
it('should render unordered lists', async () => {
const result = await renderMarkdown('- item 1\n- item 2')
expect(result).toContain('<ul')
expect(result).toContain('<li')
expect(result).toContain('item 1')
expect(result).toContain('item 2')
})
it('should render ordered lists', async () => {
const result = await renderMarkdown('1. first\n2. second')
expect(result).toContain('<ol')
expect(result).toContain('first')
expect(result).toContain('second')
})
it('should render blockquotes', async () => {
const result = await renderMarkdown('> quote')
expect(result).toContain('<blockquote')
expect(result).toContain('quote')
})
it('should render tables via GFM', async () => {
const md = '| A | B |\n|---|---|\n| 1 | 2 |'
const result = await renderMarkdown(md)
expect(result).toContain('<table')
expect(result).toContain('<td')
})
it('should render strikethrough via GFM', async () => {
const result = await renderMarkdown('~~deleted~~')
expect(result).toContain('<del')
expect(result).toContain('deleted')
})
it('should render task list', async () => {
const result = await renderMarkdown('- [x] done\n- [ ] todo')
expect(result).toContain('me-task-item')
expect(result).toContain('checked')
})
it('should render mermaid as .me-mermaid container', async () => {
const md = '```mermaid\ngraph TD\nA-->B\n```'
const result = await renderMarkdown(md)
expect(result).toContain('me-mermaid')
expect(result).toContain('class="mermaid"')
expect(result).toContain('graph TD')
})
it('should handle empty content', async () => {
const result = await renderMarkdown('')
expect(result).toBe('')
})
it('should handle null and undefined filePath', async () => {
expect(await renderMarkdown('# No File', null)).toContain('No File')
expect(await renderMarkdown('# No File')).toContain('No File')
})
it('should be sync-callable via renderMarkdownSync', () => {
const result = renderMarkdownSync('# Sync')
expect(result).toContain('<h1')
})
})
describe('fixImageSrcs — 相对路径图片修复', () => {
const FILE = '/home/user/docs/note.md'
it('should fix relative image paths to file://', async () => {
const result = await renderMarkdown('![img](./pic.png)', FILE)
expect(result).toContain('src="file:///home/user/docs/pic.png"')
})
it('should fix nested relative paths', async () => {
const result = await renderMarkdown('![img](assets/a.png)', FILE)
expect(result).toContain('src="file:///home/user/docs/assets/a.png"')
})
it('should fix unix absolute paths', async () => {
const result = await renderMarkdown('![img](/abs/pic.png)', FILE)
expect(result).toContain('src="file:///abs/pic.png"')
})
it('should not touch http/https/data:/file: URLs', async () => {
const md = '![a](https://x.com/a.png) ![b](data:image/png;base64,xx) ![c](file:///c.png)'
const result = await renderMarkdown(md, FILE)
expect(result).toContain('https://x.com/a.png')
expect(result).toContain('data:image/png;base64,xx')
expect(result).toContain('file:///c.png')
})
it('should skip out-of-directory traversal (../ and ../../)', async () => {
const md = '![a](../outside.png) ![b](../../outside.png)'
const result = await renderMarkdown(md, FILE)
// 越界路径保持原样(不注入 file://)
expect(result).not.toContain('file:')
expect(result).toContain('../outside.png')
expect(result).toContain('../../outside.png')
})
it('should not treat sibling directory with same prefix as inside', async () => {
// /home/user/docs-other/ 与 /home/user/docs/ 前缀相似但目录不同
const result = await renderMarkdown('![img](../docs-other/pic.png)', FILE)
expect(result).not.toContain('file:')
expect(result).toContain('../docs-other/pic.png')
})
it('should not touch images when filePath is null', async () => {
const result = await renderMarkdown('![img](./pic.png)')
expect(result).toContain('./pic.png')
expect(result).not.toContain('file:')
})
it('should fix relative image paths from root-level file', async () => {
const result = await renderMarkdown('![img](./pic.png)', '/note.md')
expect(result).toContain('src="file:///pic.png"')
})
it('should escape special chars in fixed path', async () => {
const result = await renderMarkdown('![img](./a&b.png)', FILE)
expect(result).toContain('src="file:///home/user/docs/a&amp;b.png"')
})
})
describe('XSS 防护(内置 safeUrl', () => {
it('should not emit anchor for javascript: URLs', async () => {
const result = await renderMarkdown('[x](javascript:alert(1))')
// 内置解析器将危险 URL 转义原样输出(不生成 <a>),文本不构成可执行链接
expect(result).not.toContain('<a')
expect(result).not.toContain('href=')
})
it('should not emit img for javascript: image src', async () => {
const result = await renderMarkdown('![x](javascript:alert(1))')
expect(result).not.toContain('<img')
expect(result).not.toContain('src=')
})
})