fix: 知识图谱与思维导图不显示内容

三个独立 bug 修复:
1. MindmapTree StrictMode 双重渲染: Effect 1 创建 mm 实例, Effect 2 设置数据,
   StrictMode 下 Effect 1 重建 mm 但 Effect 2 不重新运行 → 新 mm 无数据
   修复: 在 Effect 1 中用 useStore.getState().tree 设置初始数据

2. LinkGraph onSwitchToEditor 不稳定: App 传内联箭头函数每次渲染新引用,
   导致 D3 力导向图 Effect 2 依赖变化不断重置 → 图谱闪烁/不显示
   修复: useCallback 稳定 switchToEditor 引用

3. frontmatter 块式 YAML 列表解析: parseList 只支持内联 [a, b] 格式,
   用户手写 links:/tags: 多行 - item 格式无法解析 → 知识图谱无边
   修复: parseYaml 支持 YAML 块式列表 (- item 多行)

新增 E2E 测试 graph-render.spec.ts 验证思维导图+知识图谱渲染内容
新增单元测试覆盖块式 YAML 列表解析
This commit is contained in:
hunter-dev
2026-08-27 16:35:47 +08:00
parent ebd955ce4b
commit 96f5c72fa7
5 changed files with 105 additions and 5 deletions
@@ -0,0 +1,63 @@
import { test, expect } from '@playwright/test';
import { _electron as electron } from 'playwright';
import { promises as fs } from 'node:fs';
import nodePath from 'node:path';
import os from 'node:os';
const MAIN_JS = nodePath.join(process.cwd(), 'dist-electron/main/index.js');
test('思维导图+知识图谱渲染验证', async () => {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-graph-fix-'));
const userData = nodePath.join(tmp, 'userdata');
const vault = nodePath.join(tmp, 'vault');
await fs.mkdir(userData, { recursive: true });
await fs.mkdir(vault, { recursive: true });
await fs.writeFile(nodePath.join(vault, 'doc1.md'), '---\ntitle: 文档一\nlinks:\n - doc2.md\n---\n# 文档一\n链接到 doc2');
await fs.writeFile(nodePath.join(vault, 'doc2.md'), '---\ntitle: 文档二\n---\n# 文档二\n内容');
const app = await electron.launch({ args: ['--user-data-dir=' + userData, MAIN_JS] });
const page = await app.firstWindow();
await page.waitForLoadState('domcontentloaded');
const invoke = (ch: string, args: any) => page.evaluate(({ ch, args }) => (globalThis as any).dsh.invoke(ch, args), { ch, args });
await invoke('vault:switch', { root: vault });
await page.evaluate(() => (window as any).__STORE__.getState().loadTree());
await page.waitForTimeout(1000);
// Verify IPC.graph returns correct edges
const gd: any = await invoke('graph', null);
console.log('IPC.graph nodes:', gd.nodes.length, 'edges:', JSON.stringify(gd.edges));
// Switch to graph view (default mode is mindmap)
await page.getByTitle('切换 编辑器/图谱').click();
await page.waitForTimeout(2000);
const mindmapSvg = await page.evaluate(() => {
const svg = document.querySelector('.markmap-dark svg');
if (!svg) return { found: false, total: 0 };
const g = svg.querySelectorAll('g').length;
const fo = svg.querySelectorAll('foreignObject').length;
const paths = svg.querySelectorAll('path').length;
return { found: true, total: g + fo + paths };
});
console.log('Mindmap SVG content:', JSON.stringify(mindmapSvg));
expect(mindmapSvg.found).toBe(true);
expect(mindmapSvg.total).toBeGreaterThan(5);
// Switch to knowledge graph mode
await page.getByRole('button', { name: '知识图谱' }).click();
await page.waitForTimeout(3000);
const graphContent = await page.evaluate(() => {
return {
rects: document.querySelectorAll('svg rect').length,
texts: document.querySelectorAll('svg text').length,
lines: document.querySelectorAll('svg line').length,
};
});
console.log('Knowledge graph SVG content:', JSON.stringify(graphContent));
expect(graphContent.rects).toBeGreaterThanOrEqual(5);
expect(graphContent.texts).toBeGreaterThanOrEqual(3);
expect(graphContent.lines).toBeGreaterThanOrEqual(1); // doc1 -> doc2 edge
await app.close();
});