|
| 1 | +import { createServer, type Server } from 'node:http' |
| 2 | +import { expect, test } from '@playwright/test' |
| 3 | +import type { ElectronApplication, Page } from 'playwright' |
| 4 | +import { getMarkdownContent, launchWithMarkdown, placeCaretInEditor } from './helpers' |
| 5 | + |
| 6 | +let app: ElectronApplication |
| 7 | +let page: Page |
| 8 | +let server: Server |
| 9 | +let url: string |
| 10 | +let requests: string[] |
| 11 | + |
| 12 | +test.beforeAll(async() => { |
| 13 | + requests = [] |
| 14 | + server = createServer((request, response) => { |
| 15 | + requests.push(request.url ?? '') |
| 16 | + response.setHeader('content-type', 'text/html; charset=utf-8') |
| 17 | + response.end('<!doctype html><title>Example & page title</title><p>Content</p>') |
| 18 | + }) |
| 19 | + await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve)) |
| 20 | + const address = server.address() |
| 21 | + if (!address || typeof address === 'string') throw new Error('Missing test server port') |
| 22 | + url = `http://127.0.0.1:${address.port}` |
| 23 | +}) |
| 24 | + |
| 25 | +test.beforeEach(async() => { |
| 26 | + const launched = await launchWithMarkdown('\n') |
| 27 | + app = launched.app |
| 28 | + page = launched.page |
| 29 | + await placeCaretInEditor(page) |
| 30 | + requests.length = 0 |
| 31 | +}) |
| 32 | + |
| 33 | +test.afterEach(async() => { |
| 34 | + if (app) await app.close() |
| 35 | +}) |
| 36 | + |
| 37 | +test.afterAll(async() => { |
| 38 | + server.closeAllConnections() |
| 39 | + await new Promise<void>((resolve) => server.close(() => resolve())) |
| 40 | +}) |
| 41 | + |
| 42 | +const paste = async(text: string) => { |
| 43 | + await page.evaluate((text) => { |
| 44 | + const target = document.querySelector('.mu-editor[contenteditable="true"]') |
| 45 | + if (!target) throw new Error('Missing editor') |
| 46 | + const data = new DataTransfer() |
| 47 | + data.setData('text/plain', text) |
| 48 | + target.dispatchEvent(new ClipboardEvent('paste', { clipboardData: data, bubbles: true, cancelable: true })) |
| 49 | + }, text) |
| 50 | +} |
| 51 | + |
| 52 | +test('pasted URL gets its title despite renderer CSP and absent CORS headers', async() => { |
| 53 | + expect(await page.evaluate(async(url) => { |
| 54 | + try { await fetch(url); return true } catch { return false } |
| 55 | + }, url)).toBe(false) |
| 56 | + expect(requests).toEqual([]) |
| 57 | + |
| 58 | + await paste(url) |
| 59 | + await expect(page.locator('.editor-component .mu-link').filter({ hasText: 'Example & page title' })).toHaveCount(1) |
| 60 | + expect(requests).toEqual(['/']) |
| 61 | + await page.screenshot({ path: test.info().outputPath('paste-url-title.png') }) |
| 62 | + expect(await getMarkdownContent(page, app)).toContain(`[Example & page title](${url})`) |
| 63 | +}) |
| 64 | + |
| 65 | +test('plain-text paste does not fetch a page title', async() => { |
| 66 | + await app.evaluate(({ clipboard }, value) => clipboard.writeText(value), url) |
| 67 | + await app.evaluate(({ BrowserWindow }) => { |
| 68 | + BrowserWindow.getAllWindows()[0].webContents.send('mt::cm-paste-as-plain-text') |
| 69 | + }) |
| 70 | + await expect(page.locator('.editor-component')).toContainText(url) |
| 71 | + expect(requests).toEqual([]) |
| 72 | + expect(await getMarkdownContent(page, app)).not.toContain('Example & page title') |
| 73 | +}) |
| 74 | + |
| 75 | +test('failed title lookup preserves the URL', async() => { |
| 76 | + await paste('http://127.0.0.1:1/unreachable') |
| 77 | + await expect(page.locator('.editor-component')).toContainText('http://127.0.0.1:1/unreachable') |
| 78 | +}) |
0 commit comments