Files
hunter-dev c0ba8df9b6 perf(renderer): 整体优化响应速度 — rAF 合并编辑器刷新/自动保存缓存/粒子引擎空闲自停
同时: 不透明窗口 + 精简主题(8到2)、TabBar 简化、图谱宽松布局、MoreActions 层级修复
2026-09-10 00:41:24 +08:00

102 lines
5.2 KiB
TypeScript
Raw Permalink 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.
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');
async function launch(userData: string) {
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 });
return { app, page, invoke };
}
test('知识图谱:卡片+连线(消费 IPC.graph)', async () => {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-graph-'));
const userData = nodePath.join(tmp, 'userdata');
const vault = nodePath.join(tmp, 'vault');
await fs.mkdir(userData, { recursive: true });
await fs.mkdir(vault, { recursive: true });
const a = await launch(userData);
await a.invoke('vault:switch', { root: vault });
await a.invoke('file:create', { basePath: '', name: 'a', isFolder: false });
await a.invoke('file:create', { basePath: '', name: 'b', isFolder: false });
await a.invoke('doc:write', { path: 'a.md', meta: { title: 'A篇', links: ['b.md'] }, body: '# A\n' });
await a.invoke('doc:write', { path: 'b.md', meta: { title: 'B篇' }, body: '# B\n' });
// IPC.graph:2 节点 + a→b 边
const gd: { nodes: { id: string; title: string; star: boolean; color: string }[]; edges: { source: string; target: string }[] } = await a.invoke('graph', null);
expect(gd.nodes.length).toBe(2);
expect(gd.nodes.some(n => n.title === 'A篇')).toBe(true);
expect(gd.edges.some(e => e.source === 'a.md' && e.target === 'b.md')).toBe(true);
// UI:先经 TitleBar 切到图谱视图(view 默认 editor),再切「知识图谱」模式
await a.page.getByTitle('切换 编辑器/图谱').click();
await a.page.getByRole('button', { name: '知识图谱' }).click();
// 限定在知识图谱画布内统计卡片 rect(标题栏等 UI 图标也是 svg,不能全页统计)
// 2 张卡片(2 卡 rect)+ 1 背景 rect = 3(demo 卡片无左侧色条)
await expect(a.page.locator('[data-testid="graph-canvas"] rect')).toHaveCount(3, { timeout: 10000 });
await a.app.close();
});
// FR-G4:画布控件——适配视图 + 缩放控件可见
test('FR-G4:画布控件可见 + 适配视图', async () => {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-g4-'));
const ud = nodePath.join(tmp, 'ud'); const v = nodePath.join(tmp, 'v');
await fs.mkdir(ud, { recursive: true }); await fs.mkdir(v, { recursive: true });
const a = await launch(ud);
await a.invoke('vault:switch', { root: v });
await a.invoke('file:create', { basePath: '', name: 'x', isFolder: false });
await a.invoke('file:create', { basePath: '', name: 'y', isFolder: false });
await a.invoke('doc:write', { path: 'x.md', meta: { title: 'X篇', links: ['y.md'] }, body: '# X' });
await a.invoke('doc:write', { path: 'y.md', meta: { title: 'Y篇' }, body: '# Y' });
await a.page.getByTitle('切换 编辑器/图谱').click();
await a.page.getByRole('button', { name: '知识图谱' }).click();
await a.page.waitForTimeout(1000);
// 控件应可见
await expect(a.page.getByTestId('graph-controls')).toBeVisible({ timeout: 10000 });
// 点击适配视图不应报错
await a.page.getByTestId('fit-view-btn').click({ force: true });
await a.page.waitForTimeout(500);
await a.app.close();
});
// FR-G5:点击卡片切回编辑视图
test('FR-G5:点击卡片切回编辑视图', async () => {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-g5-'));
const ud = nodePath.join(tmp, 'ud'); const v = nodePath.join(tmp, 'v');
await fs.mkdir(ud, { recursive: true }); await fs.mkdir(v, { recursive: true });
const a = await launch(ud);
await a.invoke('vault:switch', { root: v });
await a.invoke('file:create', { basePath: '', name: 'g5doc', isFolder: false });
await a.invoke('doc:write', { path: 'g5doc.md', meta: { title: 'G5doc' }, body: '# G5' });
await a.page.getByTitle('切换 编辑器/图谱').click();
await a.page.getByRole('button', { name: '知识图谱' }).click();
await a.page.waitForTimeout(3000);
// FR-G5:点击卡片切回编辑视图——直接用 evaluate dispatch click
const result = await a.page.evaluate(() => {
const texts = document.querySelectorAll('svg text');
for (const t of texts) {
if (t.textContent && t.textContent.includes('G5doc')) {
const g = t.closest('g') as Element | null;
if (g) { g.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true })); return true; }
}
}
return false;
});
expect(result).toBe(true);
await a.page.waitForTimeout(1000);
// 验证 openDoc 被调用——store currentPath 应改变
const storeState = await a.page.evaluate(() => {
const s = (window as any).__STORE__;
return s ? { currentPath: s.getState().currentPath } : null;
});
expect(storeState?.currentPath).toContain('g5doc');
await expect(a.page.locator('.ProseMirror')).toBeVisible({ timeout: 5000 });
await a.app.close();
});