Files
PlantAI-rag-new/ui/tests/r3debug/markdown.test.js
adminandClaude Fable 5 671783eb1e fix(ui): r3 前端 10 项缺陷修复(#7/#27/#28/#31/#39/#40/#42/#47b/#52/#55)
- #7a reveal 超时:长文按总时长 ≤8s 反推 interval(下限 12ms),短路径
  30ms/字符不变(step=1);跳过按钮与 reduced-motion 行为不变
- #7b 标题编号:heading_open 注入 h1「一、」/h2「1.1」/h3「1.2.3」,
  层级变化归零、自带编号跳过、md-hN class 恒挂、计数器逐次渲染重置;
  DocPane md 回退预览与私有参考块用 renderMarkdownPlain 关闭注入
  (防止编号被序列化写回正本)
- #27 会话聚合:删除前端二次聚合,会话域直接以后端 sources 为准;
  SourcesPane/SourceRow 以 paper_id/id 为 key 与高亮依据,徽标显示
  真实 paper_id(截断+悬浮),无 paper_id 退 index+1;
  setHighlightSource 参数 index→key,chip 编号契约保持
- #31 节点详情:KgEntityDetail fetchExtra 序号守卫;KgPane :key 强制
  重建;KgGraphView pointer 事件重构(pointerdown 不捕获、位移>4px
  才捕获+标记 dragging、pointerup release、onClickNode dragging 拦截)
- #39 日志刷新:打开总是重拉+「刷新日志」按钮+有 running 时 5s 独立
  轮询(onBeforeUnmount 清理)
- #40 轮询暂停:任何离开 extract 的切换 pausePolling;详情/日志展开态
  提升到 store(detailOpenId/logOpen/runLog/logError),切 TAB 保留
- #42 模板选择:Composer docgen 模式模板下拉(自动推荐+内置+as_template
  懒加载,失败降级),template_id 经 sendDocgen 传入
- #47b 会话切换 resetSession(selectSession 动态 import,只清不丢)
- #55 文档 TAB:docgen store viewMode + DocPane「资料库」/「关闭文档」/
  「返回文档」按钮
- #28 检索文案:searchNoAnswer/searchTimeoutEmpty;200 降级体按
  payload.degraded 分流,不再走 _errorMarker TIMEOUT 报错
- #52 异步轮询:api getTask/listTasks;sendDocgen 兼容 200/202,
  202 后释放 busy 锁(生成期间可继续对话)、消息块记录 task_id、
  2s 轮询 getTask、progress 驱动「生成中 x/y 节」、done 复用现有
  artifact 挂块+落库+adoptArtifact+切 TAB、error 可重试;
  会话恢复时 running 块含 task_id 续轮询(不误标「已中断」)

自测:29 个新增 r3debug 单测 + 23 个既有 private_docs 测试全绿;
npm run build 通过;playwright 冒烟(dev server 5199)无 JS 错误,
模板下拉/提取面板/日志刷新按钮渲染正常。
未 push;Debug-2 9090 未就绪,#52/#28 联调留待 9090 就绪后执行。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 16:17:11 +08:00

58 lines
2.7 KiB
JavaScript

// tests/r3debug/markdown.test.js —— 缺陷 #7b 标题编号注入
import { describe, it, expect } from 'vitest';
import { renderMarkdown, renderMarkdownPlain } from '../../src/components/chat/markdown.js';
describe('#7b 标题编号注入', () => {
it('h1→「一、」,h2→「1.1」,h3→「1.2.3」;层级变化归零', () => {
const out = renderMarkdown(
'# 第一章\n\n## 节一\n\n### 小节一\n\n## 节二\n\n# 第二章\n\n## 节一\n\n### 小节一',
);
const hs = out.split('\n').filter((l) => l.startsWith('<h'));
expect(hs[0]).toBe('<h1 class="md-h1">一、 第一章</h1>');
expect(hs[1]).toBe('<h2 class="md-h2">1.1 节一</h2>');
expect(hs[2]).toBe('<h3 class="md-h3">1.1.1 小节一</h3>');
expect(hs[3]).toBe('<h2 class="md-h2">1.2 节二</h2>');
expect(hs[4]).toBe('<h1 class="md-h1">二、 第二章</h1>');
expect(hs[5]).toBe('<h2 class="md-h2">2.1 节一</h2>'); // h1 变化后 h2 归零
expect(hs[6]).toBe('<h3 class="md-h3">2.1.1 小节一</h3>');
});
it('已带数字/中文序号开头的标题跳过注入(原文保持)', () => {
const out = renderMarkdown('## 1. 概述\n\n### 1.1 目标\n\n## 一、背景');
const hs = out.split('\n').filter((l) => l.startsWith('<h'));
expect(hs[0]).toBe('<h2 class="md-h2">1. 概述</h2>');
expect(hs[1]).toBe('<h3 class="md-h3">1.1 目标</h3>');
expect(hs[2]).toBe('<h2 class="md-h2">一、背景</h2>');
});
it('计数器每次渲染入口重置(渲染器单例不跨消息延续编号)', () => {
const a = renderMarkdown('# A');
const b = renderMarkdown('# B');
expect(a).toContain('一、 A');
expect(b).toContain('一、 B'); // 若未重置会是「二、 B」
});
it('文档以 h2 开头:父级计数兜底 1(不出现 0.1)', () => {
const out = renderMarkdown('## 直接节\n\n### 小节');
const hs = out.split('\n').filter((l) => l.startsWith('<h'));
expect(hs[0]).toBe('<h2 class="md-h2">1.1 直接节</h2>');
expect(hs[1]).toBe('<h3 class="md-h3">1.1.1 小节</h3>');
});
it('行内强调(strong 等)保留', () => {
const out = renderMarkdown('## 加粗**重点**标题');
expect(out).toContain('<h2 class="md-h2">1.1 加粗<strong>重点</strong>标题</h2>');
});
it('renderMarkdownPlain 不注入编号(DocPane md 回退预览保护正本)', () => {
const out = renderMarkdownPlain('# 不应编号');
expect(out.trim()).toBe('<h1 class="md-h1">不应编号</h1>');
});
it('所有 h1-h6 均挂 md-hN class(含非 1-3 级)', () => {
const out = renderMarkdown('#### 四级\n\n##### 五级');
expect(out).toContain('<h4 class="md-h4">');
expect(out).toContain('<h5 class="md-h5">');
});
});