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 }; } async function newDoc(a: { page: any; invoke: any }, title: string) { await a.page.getByRole('button', { name: /新建文档/ }).click(); await a.page.getByPlaceholder(/输入文档标题/).fill(title); await a.page.getByPlaceholder(/输入文档标题/).press('Enter'); await expect(a.page.getByTestId('tabbar').getByText(title, { exact: true })).toBeVisible({ timeout: 10000 }); } // FR-M4:编辑区星标按钮 test('FR-M4:编辑区星标按钮', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-m4-')); 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 newDoc(a, 'StarTest'); await a.page.getByTitle('星标').click(); await a.page.waitForTimeout(500); const star = await a.page.evaluate(() => (window as any).__STORE__?.getState?.().meta?.star); expect(star).toBe(true); await a.app.close(); }); // FR-B6:Esc 关闭设置面板 test('FR-B6:Esc 关闭设置面板', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-b6-')); 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.page.getByTitle('设置').click(); await a.page.waitForTimeout(500); await a.page.keyboard.press('Escape'); await a.page.waitForTimeout(500); await expect(a.page.getByText('设置', { exact: true })).toHaveCount(0, { timeout: 3000 }); await a.app.close(); }); // FR-D1:迁移旧版板块数据 test('FR-D1:检测并迁移旧版板块数据', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-d1-')); 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 oldDir = nodePath.join(v, '.hunter', 'boards', 'MyBoard'); await fs.mkdir(oldDir, { recursive: true }); await fs.writeFile(nodePath.join(oldDir, 'note.json'), JSON.stringify({ title: 'OldNote', content: 'legacy content', createdAt: 1700000000000 })); const a = await launch(ud); await a.invoke('vault:switch', { root: v }); await a.page.waitForTimeout(1000); const convertedDir = nodePath.join(v, 'MyBoard'); const files = await fs.readdir(convertedDir).catch(() => []); const mdFiles = files.filter(f => f.endsWith('.md')); expect(mdFiles.length).toBeGreaterThan(0); const mdContent = await fs.readFile(nodePath.join(convertedDir, mdFiles[0]), 'utf8'); expect(mdContent).toContain('legacy content'); await a.app.close(); });