Files
MyNote/apps/desktop/tests/e2e/graph-render.spec.ts
T
hunter-dev e20fcab837 fix: 图谱/思维导图高度塌陷导致不显示
真正根因: GraphView 容器有 flex:1(在行布局中)但不是 display:flex,
   子组件 MindmapTree/LinkGraph 的 flex:1 被忽略 → 高度 auto = 0
   (子内容全是绝对定位,无在流内容) → SVG 高度 0 → fit() 计算 NaN
   持续报 translate(NaN,NaN) 错误,内容不可见

   之前 E2E 只数 DOM 元素数量不检查可见性,掩盖了此 bug

修复: GraphView 容器加 display:flex + flexDirection:column,
   子组件 flex:1 正常生效,容器高度正确,内容可见

附带:
- 主进程转发渲染进程 console 到终端(便于 dev 调试)
- graph-render E2E 测试改为检查可见性(getBoundingClientRect)
- 修复 TabBar 关闭全部测试的 flaky 断言(expect.poll 自动重试)
2026-08-27 17:22:49 +08:00

78 lines
3.7 KiB
TypeScript

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);
// Switch to graph view (default mode is mindmap)
await page.getByTitle('切换 编辑器/图谱').click();
await page.waitForTimeout(2000);
// Mindmap: verify SVG has non-zero size AND visible content
const mindmap = await page.evaluate(() => {
const svg = document.querySelector('.markmap-dark svg');
if (!svg) return { found: false };
const rect = svg.getBoundingClientRect();
const fo = svg.querySelectorAll('foreignObject').length;
const g = svg.querySelectorAll('g').length;
// Check that content is actually visible (has bounding boxes within the svg)
const visibleContent = Array.from(svg.querySelectorAll('g.markmap-node')).some((el) => {
const r = el.getBoundingClientRect();
return r.width > 0 && r.height > 0;
});
return { found: true, w: rect.width, h: rect.height, foreignObject: fo, g, visibleContent };
});
console.log('Mindmap:', JSON.stringify(mindmap));
expect(mindmap.found).toBe(true);
expect(mindmap.h).toBeGreaterThan(100); // svg must have real height
expect(mindmap.foreignObject).toBeGreaterThan(0);
expect(mindmap.visibleContent).toBe(true); // content must be visibly rendered
// Switch to knowledge graph mode
await page.getByRole('button', { name: '知识图谱' }).click();
await page.waitForTimeout(3000);
const graph = await page.evaluate(() => {
const svgs = document.querySelectorAll('svg');
let svg: SVGElement | null = null;
for (const s of svgs) { if (s.querySelectorAll('g[transform]').length > 0 || s.querySelectorAll('rect').length > 1) { svg = s; break; } }
if (!svg) return { found: false };
const rect = svg.getBoundingClientRect();
const rects = svg.querySelectorAll('rect').length;
const texts = svg.querySelectorAll('text').length;
const lines = svg.querySelectorAll('line').length;
// Check a node card is visible within the svg
const visibleCard = Array.from(svg.querySelectorAll('rect')).some((r) => {
const b = r.getBoundingClientRect();
return b.width > 0 && b.height > 0;
});
return { found: true, w: rect.width, h: rect.height, rects, texts, lines, visibleCard };
});
console.log('Knowledge graph:', JSON.stringify(graph));
expect(graph.found).toBe(true);
expect(graph.h).toBeGreaterThan(100);
expect(graph.rects).toBeGreaterThanOrEqual(5);
expect(graph.lines).toBeGreaterThanOrEqual(1);
expect(graph.visibleCard).toBe(true);
await app.close();
});