Files
MyNote/apps/desktop/tests/e2e/info-association.spec.ts
T
hunter-dev dc8d844a1f feat(info): 信息面板 UI 重做 + 双向批量关联 + 代码块内全选复制
1. 信息面板 UI 重做:设计 token 统一、lucide 图标、分区小节、星标/标签/关联 chip 化
2. 关联文档升级:新增 doc:link-batch IPC,A↔B 双向写入 links;面板支持快速双向关联 + 批量勾选关联/解除
3. 代码块内全选复制:Ctrl+A 在代码块内仅全选该代码块(TyporaKeys selectInsideCodeBlock),Ctrl+C 复制纯代码;代码块内 Ctrl+V 按纯文本粘贴
4. 修复 React error #62:btn 改为函数后遗漏 style={btn} 裸用,导致 render 期间更新
2026-09-09 20:11:08 +08:00

94 lines
4.3 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');
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');
await page.waitForTimeout(800);
const invoke = (ch: string, args: any) => page.evaluate(({ ch, args }) => (globalThis as any).dsh.invoke(ch, args), { ch, args });
return { app, page, invoke };
}
async function setupVault(a: { invoke: any; page: any }, names: string[]) {
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-assoc-'));
const ud = nodePath.join(tmp, 'ud'); const v = nodePath.join(tmp, 'v');
await fs.mkdir(ud, { recursive: true }); await fs.mkdir(v, { recursive: true });
await a.invoke('vault:switch', { root: v });
for (const n of names) {
await a.invoke('doc:write', { path: n + '.md', meta: { title: n }, body: '# ' + n });
}
await a.page.evaluate(() => (globalThis as any).__STORE__?.getState?.().loadTree());
await a.page.waitForTimeout(500);
return { v };
}
async function openInfo(a: { page: any }, docName: string) {
await a.page.getByTestId('filetree').getByText(docName, { exact: true }).click();
await a.page.waitForTimeout(500);
await a.page.getByRole('button', { name: '信息', exact: true }).click();
await a.page.waitForTimeout(300);
}
// 双向关联:当前文档关联目标后,目标文档的 links 也包含当前文档
test('关联文档:快速双向关联互相写入 links', async () => {
const a = await launch(nodePath.join(os.tmpdir(), 'hn-a1-'));
await setupVault(a, ['DocA', 'DocB', 'DocC']);
await openInfo(a, 'DocA');
const select = a.page.locator('select').first();
await select.selectOption('DocB');
await a.page.getByRole('button', { name: '关联', exact: true }).click();
await a.page.waitForTimeout(500);
await expect(a.page.getByTitle('DocB.md')).toBeVisible({ timeout: 3000 });
const docb = await a.invoke('doc:read', { path: 'DocB.md' });
const doca = await a.invoke('doc:read', { path: 'DocA.md' });
expect(docb.meta.links).toContain('DocA.md');
expect(doca.meta.links).toContain('DocB.md');
await a.app.close();
});
// 批量关联:勾选多个文档一次性建立双向关联
test('关联文档:批量勾选多文档双向关联', async () => {
const a = await launch(nodePath.join(os.tmpdir(), 'hn-a2-'));
await setupVault(a, ['DocA', 'DocB', 'DocC']);
await openInfo(a, 'DocA');
await a.page.getByRole('button', { name: /批量关联/ }).click();
await a.page.waitForTimeout(200);
const boxes = a.page.locator('input[type=checkbox]');
const count = await boxes.count();
expect(count).toBeGreaterThanOrEqual(2);
await boxes.nth(0).check();
await boxes.nth(1).check();
await a.page.getByRole('button', { name: /关联选中/ }).click();
await a.page.waitForTimeout(600);
const doca = await a.invoke('doc:read', { path: 'DocA.md' });
const docb = await a.invoke('doc:read', { path: 'DocB.md' });
const docc = await a.invoke('doc:read', { path: 'DocC.md' });
expect(doca.meta.links).toContain('DocB.md');
expect(doca.meta.links).toContain('DocC.md');
expect(docb.meta.links).toContain('DocA.md');
expect(docc.meta.links).toContain('DocA.md');
await a.app.close();
});
// 解除关联为双向:移除后两侧 links 均清除
test('关联文档:解除关联双向清除', async () => {
const a = await launch(nodePath.join(os.tmpdir(), 'hn-a3-'));
await setupVault(a, ['DocA', 'DocB']);
await a.invoke('doc:link-batch', { path: 'DocA.md', targets: ['DocB.md'], remove: true });
await a.invoke('doc:link-batch', { path: 'DocA.md', targets: ['DocB.md'], remove: false });
await openInfo(a, 'DocA');
await expect(a.page.getByTitle('DocB.md')).toBeVisible({ timeout: 3000 });
await a.page.getByTitle('解除关联(双向)').click();
await a.page.waitForTimeout(500);
const doca = await a.invoke('doc:read', { path: 'DocA.md' });
const docb = await a.invoke('doc:read', { path: 'DocB.md' });
expect(doca.meta.links ?? []).not.toContain('DocB.md');
expect(docb.meta.links ?? []).not.toContain('DocA.md');
await a.app.close();
});