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)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { renderMarkdown } from '../markdown'
|
||||
import { renderMarkdown, renderMarkdownSync } from '../markdown'
|
||||
|
||||
describe('renderMarkdown', () => {
|
||||
describe('renderMarkdown (MetonaEditor 内置解析器)', () => {
|
||||
it('should render a simple heading', async () => {
|
||||
const result = await renderMarkdown('# Hello World')
|
||||
expect(result).toContain('<h1')
|
||||
@@ -26,7 +26,7 @@ describe('renderMarkdown', () => {
|
||||
expect(result).toContain('code')
|
||||
})
|
||||
|
||||
it('should render code blocks with syntax highlighting', async () => {
|
||||
it('should render code blocks', async () => {
|
||||
const md = '```javascript\nconst x = 1;\n```'
|
||||
const result = await renderMarkdown(md)
|
||||
expect(result).toContain('<pre')
|
||||
@@ -75,32 +75,106 @@ describe('renderMarkdown', () => {
|
||||
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 return error HTML on rendering failure with invalid input', async () => {
|
||||
// unified should still handle gracefully, but verify error path works
|
||||
const result = await renderMarkdown('normal text')
|
||||
expect(result).not.toContain('渲染错误')
|
||||
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 cache processors for same filePath', async () => {
|
||||
// Call twice with same filePath to test caching
|
||||
const result1 = await renderMarkdown('# Test', '/test/file.md')
|
||||
const result2 = await renderMarkdown('# Test 2', '/test/file.md')
|
||||
expect(result1).toContain('<h1')
|
||||
expect(result2).toContain('Test 2')
|
||||
it('should be sync-callable via renderMarkdownSync', () => {
|
||||
const result = renderMarkdownSync('# Sync')
|
||||
expect(result).toContain('<h1')
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle null filePath', async () => {
|
||||
const result = await renderMarkdown('# No File', null)
|
||||
expect(result).toContain('No File')
|
||||
describe('fixImageSrcs — 相对路径图片修复', () => {
|
||||
const FILE = '/home/user/docs/note.md'
|
||||
|
||||
it('should fix relative image paths to file://', async () => {
|
||||
const result = await renderMarkdown('', FILE)
|
||||
expect(result).toContain('src="file:///home/user/docs/pic.png"')
|
||||
})
|
||||
|
||||
it('should fix nested relative paths', async () => {
|
||||
const result = await renderMarkdown('', FILE)
|
||||
expect(result).toContain('src="file:///home/user/docs/assets/a.png"')
|
||||
})
|
||||
|
||||
it('should fix unix absolute paths', async () => {
|
||||
const result = await renderMarkdown('', FILE)
|
||||
expect(result).toContain('src="file:///abs/pic.png"')
|
||||
})
|
||||
|
||||
it('should not touch http/https/data:/file: URLs', async () => {
|
||||
const md = '  '
|
||||
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 = ' '
|
||||
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('', 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('')
|
||||
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('', '/note.md')
|
||||
expect(result).toContain('src="file:///pic.png"')
|
||||
})
|
||||
|
||||
it('should escape special chars in fixed path', async () => {
|
||||
const result = await renderMarkdown('', FILE)
|
||||
expect(result).toContain('src="file:///home/user/docs/a&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 handle undefined filePath', async () => {
|
||||
const result = await renderMarkdown('# No File')
|
||||
expect(result).toContain('No File')
|
||||
it('should not emit img for javascript: image src', async () => {
|
||||
const result = await renderMarkdown(')')
|
||||
expect(result).not.toContain('<img')
|
||||
expect(result).not.toContain('src=')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user