import { expect, test } from '@playwright/test'; import { contextForUser } from './helpers'; /** * Pond fonts pack (issue #66, ADR 0016): the GDPR "zero external requests" * guarantee, and that a pond's font choice applies to its pages and persists. * Selectors are language-neutral (CSS classes, not button text). */ const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173'; const ORIGIN = new URL(BASE_URL).host; const DEFAULT_FONTS = { heading: { family: 'Roboto', weight: 400 }, body: { family: 'Roboto', weight: 200 }, mono: { family: 'Fira Code', weight: 400 }, }; async function personalPond( context: Awaited>, ): Promise<{ id: string; slug: string }> { const ponds = await context.request.get('/api/v1/ponds'); const pond = (await ponds.json()).find((p: { type: string }) => p.type === 'personal'); return { id: pond.id, slug: pond.slug }; } async function createPage( context: Awaited>, pondId: string, title: string, ): Promise { const created = await context.request.post(`/api/v1/ponds/${pondId}/pages`, { data: { title } }); return (await created.json()).slug; } test('renders a pond without any request leaving the origin (GDPR)', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const slug = await createPage(context, pond.id, `Fonts Net ${Date.now()}`); const page = await context.newPage(); const offOrigin: string[] = []; page.on('request', (request) => { const url = request.url(); if (url.startsWith('data:') || url.startsWith('blob:')) return; if (new URL(url).host !== ORIGIN) offOrigin.push(url); }); await page.goto(`/p/${pond.slug}/${slug}`); await page.locator('.ProseMirror').waitFor(); // Give any late asset/font/websocket request a chance to fire. await page.waitForTimeout(750); expect(offOrigin, `off-origin requests: ${offOrigin.join(', ')}`).toEqual([]); await context.close(); }); test('a pond font choice applies to its pages and persists', async ({ browser }) => { const context = await contextForUser(browser, BASE_URL, 'fixture-user'); const pond = await personalPond(context); const slug = await createPage(context, pond.id, `Fonts Apply ${Date.now()}`); const page = await context.newPage(); try { await page.goto(`/p/${pond.slug}/settings`); // Set the body font to a distinctive serif, then save. await page.locator('.appearance__slot--body select').first().selectOption('Merriweather'); await page.locator('.appearance__actions button').click(); await page.goto(`/p/${pond.slug}/${slug}`); const font = await page .locator('.ProseMirror') .evaluate((el) => getComputedStyle(el).fontFamily); expect(font).toContain('Merriweather'); // Persisted across a reload. await page.reload(); const afterReload = await page .locator('.ProseMirror') .evaluate((el) => getComputedStyle(el).fontFamily); expect(afterReload).toContain('Merriweather'); } finally { // Restore defaults so reruns / other tests see a clean pond. await context.request.patch(`/api/v1/ponds/${pond.id}`, { data: { fonts: DEFAULT_FONTS } }); await context.close(); } }); test('a pond with no font settings renders the vision defaults', async ({ browser }) => { // fixture-editor's personal pond is never reconfigured by these tests. const context = await contextForUser(browser, BASE_URL, 'fixture-editor'); const pond = await personalPond(context); const slug = await createPage(context, pond.id, `Fonts Default ${Date.now()}`); const page = await context.newPage(); await page.goto(`/p/${pond.slug}/${slug}`); const font = await page.locator('.ProseMirror').evaluate((el) => getComputedStyle(el).fontFamily); // Default body family is Roboto (ADR 0016). expect(font).toContain('Roboto'); await context.close(); });