Files
PlantAI-rag-new/tests/frontend/unit/b2/markdown.test.js
T
jerryandClaude Fable 5 1e94de7464 PlantAI-rag-new 首次提交:文献知识工作台
- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化
- Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab
- 四档响应式(桌面/笔记本/平板/手机)、深浅主题
- 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测
- 真实凭据已脱敏,运行时配置在 backend/.env(不入 git)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 20:47:52 +08:00

51 lines
2.3 KiB
JavaScript
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.
// @vitest-environment jsdom
// markdown.test.js —— Markdown 渲染 + DOMPurify 消毒 + 自定义表格/代码类(FR-CHAT-004)
import { describe, it, expect } from 'vitest';
import { renderMarkdown, NON_MONO_HEADER } from '../../../../frontend/src/components/chat/markdown.js';
describe('renderMarkdown(FR-CHAT-004)', () => {
it('LLM 输出 markdown → 消毒 HTML', () => {
const html = renderMarkdown('**加粗** 与 *斜体* 与 `行内代码`');
expect(html).toContain('<strong>加粗</strong>');
expect(html).toContain('<em>斜体</em>');
expect(html).toContain('<code>行内代码</code>');
});
it('脚本注入被消毒(活 DOM 中无可执行元素/属性/协议)', () => {
const html = renderMarkdown('你好 <script>alert(1)</script> <img src=x onerror="alert(2)"> [x](javascript:alert(3))');
const doc = new DOMParser().parseFromString(html, 'text/html');
expect(doc.querySelectorAll('script').length).toBe(0);
expect(doc.querySelectorAll('img').length).toBe(0);
expect(doc.querySelectorAll('[onerror]').length).toBe(0);
const hrefs = [...doc.querySelectorAll('a')].map((a) => a.getAttribute('href') || '');
expect(hrefs.filter((h) => h.toLowerCase().startsWith('javascript:'))).toEqual([]);
});
it('表格挂 md-table 类(thead th / tbody td)', () => {
const html = renderMarkdown('| A | B |\n|---|---|\n| 1 | 2 |');
expect(html).toContain('<table class="md-table">');
expect(html).toContain('<thead>');
expect(html).toContain('<th>A</th>');
expect(html).toContain('<tbody>');
});
it('代码块挂 code-block 类并转义内容', () => {
const html = renderMarkdown('```\nconst x = "<script>";\n```');
expect(html).toContain('<div class="code-block">');
expect(html).toContain('&lt;script&gt;');
});
it('渲染失败时回退转义原文,不抛异常、活 DOM 无事件属性', () => {
const html = renderMarkdown(null);
expect(typeof html).toBe('string');
const evil = renderMarkdown('<b onclick=alert(1)>x</b>');
const doc = new DOMParser().parseFromString(evil, 'text/html');
expect(doc.querySelectorAll('[onclick]').length).toBe(0);
expect(evil).not.toContain('<b onclick');
});
it('NON_MONO_HEADER 常量为「说明」', () => {
expect(NON_MONO_HEADER).toBe('说明');
});
});