import { expect, test } from '@playwright/test'; import type { Page } from '@playwright/test'; import { contextForUser } from './helpers'; /** * Markdown copy/paste/export pack (issue #30). Runs against the local dev * stack (api + web); no Mailpit needed. */ const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173'; async function createPage( context: Awaited>, title: string, ): Promise<{ pondSlug: string; pageSlug: string; pageId: string }> { const ponds = await context.request.get('/api/v1/ponds'); const pond = (await ponds.json()).find((p: { type: string }) => p.type === 'personal'); const created = await context.request.post(`/api/v1/ponds/${pond.id}/pages`, { data: { title }, }); const page = await created.json(); return { pondSlug: pond.slug, pageSlug: page.slug, pageId: page.id }; } async function enterEditMode(page: Page): Promise { await page.getByRole('button', { name: /edit|bearbeiten/i }).click(); await expect(page.locator('.ProseMirror')).toHaveAttribute('contenteditable', 'true'); } test('copying a heading+list selection yields Markdown on the clipboard', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); await context.grantPermissions(['clipboard-read', 'clipboard-write']); const { pondSlug, pageSlug } = await createPage(context, `E2E MD Copy ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pondSlug}/${pageSlug}`); await enterEditMode(page); await page.locator('.ProseMirror').click(); await page.keyboard.type('# Title'); await page.keyboard.press('Enter'); await page.keyboard.type('one'); await page.keyboard.press('Enter'); await page.keyboard.type('two'); // Turn the second/third lines into a bullet list, then copy everything. await page.keyboard.press('Shift+Home'); await page.keyboard.press('Shift+ArrowUp'); await page.getByRole('button', { name: /bullet list|aufzählungsliste/i }).click(); await page.keyboard.press('ControlOrMeta+a'); await page.keyboard.press('ControlOrMeta+c'); const clipboardText = await page.evaluate(() => navigator.clipboard.readText()); expect(clipboardText).toContain('- one'); expect(clipboardText).toContain('- two'); await context.close(); }); test('pasting a Markdown document into an empty page recreates structure', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const { pondSlug, pageSlug } = await createPage(context, `E2E MD Paste ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pondSlug}/${pageSlug}`); await enterEditMode(page); await page.locator('.ProseMirror').click(); await page.evaluate(() => { const el = document.querySelector('.ProseMirror'); const dataTransfer = new DataTransfer(); dataTransfer.setData('text/plain', '# Welcome\n\n- alpha\n- beta\n\n1. first\n2. second\n'); el!.dispatchEvent( new ClipboardEvent('paste', { clipboardData: dataTransfer, bubbles: true, cancelable: true }), ); }); const content = page.locator('.ProseMirror'); await expect(content.locator('h1')).toHaveText('Welcome'); await expect(content.locator('ul li')).toHaveCount(2); await expect(content.locator('ol li')).toHaveCount(2); await context.close(); }); test('a plain-text paste is not mangled into rich structure', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const { pondSlug, pageSlug } = await createPage(context, `E2E MD Plain ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pondSlug}/${pageSlug}`); await enterEditMode(page); await page.locator('.ProseMirror').click(); const plainText = "Just a *reminder* to buy milk - don't forget!"; await page.evaluate((text) => { const el = document.querySelector('.ProseMirror'); const dataTransfer = new DataTransfer(); dataTransfer.setData('text/plain', text); el!.dispatchEvent( new ClipboardEvent('paste', { clipboardData: dataTransfer, bubbles: true, cancelable: true }), ); }, plainText); const content = page.locator('.ProseMirror'); await expect(content).toContainText(plainText); await expect(content.locator('ul, ol, h1, h2, h3, h4')).toHaveCount(0); await context.close(); }); test('page menu downloads the page as Markdown matching its content', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const { pondSlug, pageSlug, pageId } = await createPage(context, `E2E MD Export ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pondSlug}/${pageSlug}`); await enterEditMode(page); await page.locator('.ProseMirror').click(); await page.keyboard.type('export me please'); await expect(page.getByRole('status')).toHaveText(/saved|gespeichert/i, { timeout: 10000 }); const exported = await context.request.get(`/api/v1/pages/${pageId}/export/markdown`); expect(exported.headers()['content-disposition']).toContain(`${pageSlug}.md`); expect(await exported.text()).toBe('export me please'); await context.close(); });