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'); const PNG_B64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; 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 }); } test('FR-E6:链接气泡编辑(添加+移除)', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-e6-link-')); 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 newDoc(a, '链接测试'); const editor = a.page.locator('.ProseMirror'); await editor.click(); await a.page.keyboard.type('点这里'); await a.page.keyboard.press('Control+A'); await a.page.locator('button[title="链接"]').click(); const dlg = a.page.getByPlaceholder(/example\.com/); await dlg.fill('https://example.com'); await dlg.press('Enter'); let html: string = await a.page.evaluate(() => document.querySelector('.ProseMirror')?.innerHTML ?? ''); expect(html).toContain('href="https://example.com"'); await a.page.locator('.ProseMirror a').click(); await expect(a.page.locator('.link-bubble')).toBeVisible({ timeout: 5000 }); await expect(a.page.locator('.link-bubble input')).toHaveValue('https://example.com'); await a.page.locator('.link-bubble').getByText('移除').click(); html = await a.page.evaluate(() => document.querySelector('.ProseMirror')?.innerHTML ?? ''); expect(html).not.toContain(' { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-e6-url-')); 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 newDoc(a, '网图测试'); await a.page.locator('.ProseMirror').click(); await a.page.locator('button[title="网络图片(手输网址)"]').click(); const dlg = a.page.getByPlaceholder(/粘贴图片地址/); await dlg.fill('https://cdn.example.com/x.png'); await dlg.press('Enter'); await expect(a.page.locator('.ProseMirror img')).toHaveCount(1, { timeout: 10000 }); const src = await a.page.locator('.ProseMirror img').getAttribute('src'); expect(src).toBe('https://cdn.example.com/x.png'); await a.app.close(); }); test('FR-E6:本地图片入库+展示(vaultasset 协议)', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-e6-local-')); 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 }); const rel: string = await a.invoke('asset:write', { name: 't.png', dataBase64: PNG_B64 }); expect(rel.startsWith('.attachments/')).toBeTruthy(); // 磁盘文件确实写入 const disk = await fs.readFile(nodePath.join(vault, rel)); expect(disk.length).toBeGreaterThan(0); await newDoc(a, 'doca'); await newDoc(a, 'docb'); await a.invoke('doc:write', { path: 'doca.md', meta: { title: 'doca' }, body: '![](' + rel + ')' }); await a.page.getByTestId('filetree').getByText('doca', { exact: true }).click(); await expect(a.page.locator('.ProseMirror img')).toHaveCount(1, { timeout: 10000 }); const src = await a.page.locator('.ProseMirror img').getAttribute('src'); expect(src?.startsWith('vaultasset:')).toBeTruthy(); await expect.poll( () => a.page.locator('.ProseMirror img').evaluate((el: HTMLImageElement) => el.naturalWidth), { timeout: 8000 }, ).toBeGreaterThan(0); await a.app.close(); });