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 }); const clip = () => app.evaluate(({ clipboard }) => clipboard.readText()); return { app, page, invoke, clip }; } // 代码块内 Ctrl+A 全选后 Ctrl+C:应只复制代码块内容(纯代码无围栏),即使文档有其他内容 test('代码块内 Ctrl+A 全选复制纯代码(不受文档其他内容影响)', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-sel1-')); 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 }); // 文档:代码块上下都有其他内容,确保 Ctrl+A 只选代码块而非整篇 const body = '# 标题\n\n上方段落\n\n```bash\nsudo apt-get update\nls -la\n```\n\n下方段落'; await a.invoke('doc:write', { path: 'code.md', meta: {}, body }); await a.page.evaluate((p: string) => (globalThis as any).__STORE__?.getState?.().openDoc(p), 'code.md'); await a.page.waitForTimeout(1500); const code = a.page.locator('.ProseMirror pre code'); await code.click(); await a.page.keyboard.press('Control+a'); await a.page.waitForTimeout(200); // 校验选中范围只是代码块内容(可通过 selection anchor 判断,或直接 Ctrl+C 后看剪贴板) await a.page.keyboard.press('Control+c'); await a.page.waitForTimeout(300); const text = await a.clip(); console.log('SELALL_CLIP', JSON.stringify(text)); expect(text.trim().replace(/\r\n/g, '\n')).toBe('sudo apt-get update\nls -la'); await a.app.close(); }); // 代码块内 Ctrl+V 粘贴按纯文本插入(不产生 markdown 转换/围栏) test('代码块内 Ctrl+V 粘贴纯文本', async () => { const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-sel2-')); 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('doc:write', { path: 'code2.md', meta: {}, body: '```js\nlet x = 1;\n```' }); await a.page.evaluate((p: string) => (globalThis as any).__STORE__?.getState?.().openDoc(p), 'code2.md'); await a.page.waitForTimeout(1500); const code = a.page.locator('.ProseMirror pre code'); await code.click(); // 全选代码块,然后粘贴纯文本 await a.page.keyboard.press('Control+a'); await a.page.evaluate(() => navigator.clipboard.writeText('# not a heading\n- not a list')); await a.page.waitForTimeout(200); await a.page.keyboard.press('Control+v'); await a.page.waitForTimeout(500); const inner = await a.page.evaluate(() => (document.querySelector('.ProseMirror pre code') as HTMLElement)?.innerText ?? ''); console.log('PASTE_INNER', JSON.stringify(inner)); // 粘贴内容按纯文本插入(不出现标题/列表节点结构变化) expect(inner).toContain('# not a heading'); expect(inner).toContain('- not a list'); await a.app.close(); });