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('FR-T7:重命名文档后标签页路径同步', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-t7-')); 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.page.getByRole('button', { name: /新建文档/ }).click(); await a.page.getByPlaceholder(/输入文档标题/).fill('原稿'); await a.page.getByPlaceholder(/输入文档标题/).press('Enter'); await expect(a.page.getByTestId('tabbar').getByText('原稿', { exact: true })).toBeVisible({ timeout: 10000 }); // 文件树悬停「原稿」→ 点重命名 → 输入「改稿」→ 回车 await a.page.getByTestId('filetree').getByText('原稿', { exact: true }).hover(); await a.page.getByTestId('filetree').getByTitle('重命名').click(); // 重命名 input 是 filetree 内唯一的 input(搜索框在 filetree 外) const renameInput = a.page.getByTestId('filetree').locator('input'); await renameInput.fill('改稿'); await renameInput.press('Enter'); // 标签页文本应从「原稿」变为「改稿」(路径已同步) await expect(a.page.getByTestId('tabbar').getByText('改稿', { exact: true })).toBeVisible({ timeout: 10000 }); // 「原稿」标签页文本应消失 await expect(a.page.getByTestId('tabbar').getByText('原稿', { exact: true })).toHaveCount(0); await a.app.close(); }); test('FR-V5+V6:设置页切换默认库路径 + 切库清空标签页', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-v56-')); const userData = nodePath.join(tmp, 'userdata'); const vault1 = nodePath.join(tmp, 'vault1'); const vault2 = nodePath.join(tmp, 'vault2'); await fs.mkdir(userData, { recursive: true }); await fs.mkdir(vault1, { recursive: true }); await fs.mkdir(vault2, { recursive: true }); const a = await launch(userData); await a.invoke('vault:switch', { root: vault1 }); // 在 vault1 新建并打开文档 → 标签页出现 await a.page.getByRole('button', { name: /新建文档/ }).click(); await a.page.getByPlaceholder(/输入文档标题/).fill('库1文档'); await a.page.getByPlaceholder(/输入文档标题/).press('Enter'); await expect(a.page.getByTestId('tabbar').getByText('库1文档', { exact: true })).toBeVisible({ timeout: 10000 }); // 设置 → 文件路径页 → 填入 vault2 路径 → 立即切换 await a.page.getByTitle('设置').click(); await a.page.getByRole('button', { name: '文件路径' }).click(); await a.page.getByPlaceholder(/填写或浏览/).fill(vault2); await a.page.getByRole('button', { name: '立即切换' }).click(); // 等待切换完成 await expect(a.page.getByText(/已切换/)).toBeVisible({ timeout: 10000 }); // FR-V6:标签页应被清空(TabBar 返回 null → DOM 中无 tabbar) await expect(a.page.getByTestId('tabbar')).toHaveCount(0); // FR-V5:库元数据应更新为 vault2 const meta: { currentRoot: string } = await a.invoke('vault:list', null); expect(meta.currentRoot).toBe(vault2); // 关闭设置面板(点击遮罩层),在 vault2 新建文档应正常工作 await a.page.mouse.click(5, 5); await a.page.getByRole('button', { name: /新建文档/ }).click(); await a.page.getByPlaceholder(/输入文档标题/).fill('库2文档'); await a.page.getByPlaceholder(/输入文档标题/).press('Enter'); await expect(a.page.getByTestId('tabbar').getByText('库2文档', { exact: true })).toBeVisible({ timeout: 10000 }); await a.app.close(); });