diff --git a/src/app/automation/bridge/vite-plugin.ts b/src/app/automation/bridge/vite-plugin.ts index 9f442ed1a..3d1ba28de 100644 --- a/src/app/automation/bridge/vite-plugin.ts +++ b/src/app/automation/bridge/vite-plugin.ts @@ -8,7 +8,7 @@ export function automationPlugin(authToken: string | null, corsOrigin: string): return { name: 'open-pencil-automation', - configureServer() { + configureServer(server) { if (child) return child = spawn('bun', ['run', 'packages/mcp/src/index.ts'], { @@ -41,10 +41,11 @@ export function automationPlugin(authToken: string | null, corsOrigin: string): } child = null }) - }, - buildEnd() { - child?.kill() - child = null + + server.httpServer?.once('close', () => { + child?.kill() + child = null + }) } } } diff --git a/src/app/editor/fonts/index.ts b/src/app/editor/fonts/index.ts index 2f842a98a..89172977d 100644 --- a/src/app/editor/fonts/index.ts +++ b/src/app/editor/fonts/index.ts @@ -207,7 +207,8 @@ async function loadSystemFont(family: string, style = 'Regular'): Promise('load_system_font', { family, style }) + const data = await invoke('load_system_font', { family, style }) + if (!data?.length) return null return new Uint8Array(data).buffer } catch { return null diff --git a/tests/e2e/app/menu.spec.ts b/tests/e2e/app/menu.spec.ts index 6b3e7630d..7371cabe0 100644 --- a/tests/e2e/app/menu.spec.ts +++ b/tests/e2e/app/menu.spec.ts @@ -21,7 +21,7 @@ test('File menu opens and shows items', async () => { const items = await menu.locator('[role="menuitem"]').allTextContents() expect(items.some((t) => t.includes('Open'))).toBe(true) expect(items.some((t) => t.includes('Save'))).toBe(true) - expect(items.some((t) => t.includes('Save As'))).toBe(true) + expect(items.some((t) => t.includes('Save as'))).toBe(true) await editor.page.keyboard.press('Escape') }) @@ -47,8 +47,8 @@ test('View menu shows zoom options', async () => { const items = await menu.locator('[role="menuitem"]').allTextContents() expect(items.some((t) => t.includes('Zoom to fit'))).toBe(true) - expect(items.some((t) => t.includes('Zoom In'))).toBe(true) - expect(items.some((t) => t.includes('Zoom Out'))).toBe(true) + expect(items.some((t) => t.includes('Zoom in'))).toBe(true) + expect(items.some((t) => t.includes('Zoom out'))).toBe(true) await editor.page.keyboard.press('Escape') }) diff --git a/tests/e2e/color-picker/demo-card.spec.ts b/tests/e2e/color-picker/demo-card.spec.ts index d0ac8fc2a..bd07f3352 100644 --- a/tests/e2e/color-picker/demo-card.spec.ts +++ b/tests/e2e/color-picker/demo-card.spec.ts @@ -8,7 +8,7 @@ async function dragSlider( testId: string, ratio: number ) { - const slider = page.getByTestId(testId).locator('input[type="range"]') + const slider = page.getByTestId(testId).getByRole('slider') const box = await slider.boundingBox() if (!box) throw new Error(`Missing slider: ${testId}`) const y = box.y + box.height / 2 diff --git a/tests/e2e/editor/auto-layout/basic.spec.ts-snapshots/layout-size-controls-openpencil-darwin.png b/tests/e2e/editor/auto-layout/basic.spec.ts-snapshots/layout-size-controls-openpencil-darwin.png index 47a43e961..3058dcefe 100644 Binary files a/tests/e2e/editor/auto-layout/basic.spec.ts-snapshots/layout-size-controls-openpencil-darwin.png and b/tests/e2e/editor/auto-layout/basic.spec.ts-snapshots/layout-size-controls-openpencil-darwin.png differ diff --git a/tests/e2e/editor/resize-performance.spec.ts b/tests/e2e/editor/resize-performance.spec.ts index 88f3487d4..bdcf66655 100644 --- a/tests/e2e/editor/resize-performance.spec.ts +++ b/tests/e2e/editor/resize-performance.spec.ts @@ -39,7 +39,7 @@ test('resizing uses repaint-only previews until mouseup', async ({ page }) => { }) await canvas.waitForRender() - await page.evaluate(() => { + await page.evaluate((selectedId) => { const store = window.openPencil?.getStore?.() if (!store) throw new Error('OpenPencil store not initialized') const originalStoreUpdate = store.updateNode.bind(store) @@ -48,11 +48,11 @@ test('resizing uses repaint-only previews until mouseup', async ({ page }) => { let graphUpdateCount = 0 let repaintCount = 0 store.updateNode = ((nodeId, changes) => { - storeUpdateCount++ + if (nodeId === selectedId) storeUpdateCount++ return originalStoreUpdate(nodeId, changes) }) as typeof store.updateNode store.graph.updateNode = ((nodeId, changes) => { - graphUpdateCount++ + if (nodeId === selectedId) graphUpdateCount++ return originalGraphUpdate(nodeId, changes) }) as typeof store.graph.updateNode store.onEditorEvent('repaint:requested', () => { @@ -61,7 +61,7 @@ test('resizing uses repaint-only previews until mouseup', async ({ page }) => { Object.assign(window, { __openPencilResizeCounters: () => ({ storeUpdateCount, graphUpdateCount, repaintCount }) }) - }) + }, id) await canvas.drag(340, 240, 420, 290, 12) diff --git a/tests/e2e/fonts/picker.spec.ts b/tests/e2e/fonts/picker.spec.ts index 834cb412f..ab5d27678 100644 --- a/tests/e2e/fonts/picker.spec.ts +++ b/tests/e2e/fonts/picker.spec.ts @@ -39,27 +39,32 @@ async function installGoogleFontsMock(page: Page, families = ['Inter', 'OpenPenc win.__googleFontPreviewFetchCount = (win.__googleFontPreviewFetchCount ?? 0) + 1 return new Response(new ArrayBuffer(8), { status: 200 }) } - if (url.startsWith('https://www.googleapis.com/webfonts/v1/webfonts')) { - if (!url.includes('family=')) - win.__googleFontsFetchCount = (win.__googleFontsFetchCount ?? 0) + 1 + if (url.startsWith('https://fonts.google.com/metadata/fonts')) { + win.__googleFontsFetchCount = (win.__googleFontsFetchCount ?? 0) + 1 return new Response( JSON.stringify({ - items: googleFamilies.map((family) => ({ + familyMetadataList: googleFamilies.map((family) => ({ family, - files: { regular: `https://fonts.openpencil.test/${encodeURIComponent(family)}.ttf` } + axes: [], + fonts: { '400': {} } })) }), { status: 200, headers: { 'content-type': 'application/json' } } ) } + if (url.startsWith('https://fonts.googleapis.com/css2')) { + const family = new URL(url).searchParams.get('family')?.split(':')[0] ?? 'Inter' + return new Response( + `@font-face { font-family: '${family}'; font-style: normal; font-weight: 400; src: url(https://fonts.openpencil.test/${encodeURIComponent(family)}.ttf) format('truetype'); }`, + { status: 200, headers: { 'content-type': 'text/css' } } + ) + } return originalFetch(input, init) } }, families) } -test('font picker preloads Google fonts and selects local fonts after first-open access', async ({ - page -}) => { +test('font picker selects local fonts without browser web-font access', async ({ page }) => { await installGoogleFontsMock(page) await page.addInitScript(() => { Object.defineProperty(window, 'queryLocalFonts', { @@ -82,13 +87,6 @@ test('font picker preloads Google fonts and selects local fonts after first-open }) const textId = await openTypographyForText(page) - await expect - .poll(() => - page.evaluate( - () => (window as Window & { __googleFontsFetchCount?: number }).__googleFontsFetchCount - ) - ) - .toBe(1) await openFontPicker(page) await expect( @@ -106,9 +104,16 @@ test('font picker preloads Google fonts and selects local fonts after first-open }, textId) ) .toBe('OpenPencil Local Font') + expect( + await page.evaluate( + () => (window as Window & { __googleFontsFetchCount?: number }).__googleFontsFetchCount + ) + ).toBe(0) }) -test('font picker lists Google fonts when local font API is unavailable', async ({ page }) => { +test('font picker keeps bundled fonts when local and web fonts are unavailable', async ({ + page +}) => { await installGoogleFontsMock(page) await page.addInitScript(() => { Reflect.deleteProperty(window, 'queryLocalFonts') @@ -117,24 +122,18 @@ test('font picker lists Google fonts when local font API is unavailable', async await openTypographyForText(page) await openFontPicker(page) + await expect(page.getByTestId('font-picker-item').filter({ hasText: 'Inter' })).toBeVisible() await expect( page.getByTestId('font-picker-item').filter({ hasText: 'OpenPencil Google Font' }) - ).toBeVisible() - await expect - .poll(() => - page.evaluate( - () => - (window as Window & { __googleFontPreviewFetchCount?: number }) - .__googleFontPreviewFetchCount - ) + ).toHaveCount(0) + expect( + await page.evaluate( + () => (window as Window & { __googleFontsFetchCount?: number }).__googleFontsFetchCount ) - .toBeGreaterThan(0) - await expect(page.getByText('Local fonts are not available in this browser.')).toHaveCount(0) + ).toBe(0) }) -test('font picker still lists Google fonts when local font permission is rejected', async ({ - page -}) => { +test('font picker keeps bundled fonts when local font permission is rejected', async ({ page }) => { await installGoogleFontsMock(page) await page.addInitScript(() => { Object.defineProperty(window, 'queryLocalFonts', { @@ -148,10 +147,15 @@ test('font picker still lists Google fonts when local font permission is rejecte await openTypographyForText(page) await openFontPicker(page) + await expect(page.getByTestId('font-picker-item').filter({ hasText: 'Inter' })).toBeVisible() await expect( page.getByTestId('font-picker-item').filter({ hasText: 'OpenPencil Google Font' }) - ).toBeVisible() - await expect(page.getByText('Local font access is blocked for this site.')).toHaveCount(0) + ).toHaveCount(0) + expect( + await page.evaluate( + () => (window as Window & { __googleFontsFetchCount?: number }).__googleFontsFetchCount + ) + ).toBe(0) }) test('font picker keeps bundled Inter available when local and Google fonts are unavailable', async ({ diff --git a/tests/e2e/panels/visual.spec.ts-snapshots/instance-openpencil-darwin.png b/tests/e2e/panels/visual.spec.ts-snapshots/instance-openpencil-darwin.png index 0cd5b81b8..59569b798 100644 Binary files a/tests/e2e/panels/visual.spec.ts-snapshots/instance-openpencil-darwin.png and b/tests/e2e/panels/visual.spec.ts-snapshots/instance-openpencil-darwin.png differ diff --git a/tests/e2e/panels/visual.spec.ts-snapshots/mixed-selection-openpencil-darwin.png b/tests/e2e/panels/visual.spec.ts-snapshots/mixed-selection-openpencil-darwin.png index 8eadc7e6a..127e6f1da 100644 Binary files a/tests/e2e/panels/visual.spec.ts-snapshots/mixed-selection-openpencil-darwin.png and b/tests/e2e/panels/visual.spec.ts-snapshots/mixed-selection-openpencil-darwin.png differ diff --git a/tests/e2e/panels/visual.spec.ts-snapshots/nested-groups-layer-tree-openpencil-darwin.png b/tests/e2e/panels/visual.spec.ts-snapshots/nested-groups-layer-tree-openpencil-darwin.png index bc8f2ef2d..b91c4aef4 100644 Binary files a/tests/e2e/panels/visual.spec.ts-snapshots/nested-groups-layer-tree-openpencil-darwin.png and b/tests/e2e/panels/visual.spec.ts-snapshots/nested-groups-layer-tree-openpencil-darwin.png differ diff --git a/tests/e2e/panels/visual.spec.ts-snapshots/single-rectangle-openpencil-darwin.png b/tests/e2e/panels/visual.spec.ts-snapshots/single-rectangle-openpencil-darwin.png index a63f09bd9..0d32fad9a 100644 Binary files a/tests/e2e/panels/visual.spec.ts-snapshots/single-rectangle-openpencil-darwin.png and b/tests/e2e/panels/visual.spec.ts-snapshots/single-rectangle-openpencil-darwin.png differ diff --git a/tests/e2e/panels/visual.spec.ts-snapshots/text-selection-openpencil-darwin.png b/tests/e2e/panels/visual.spec.ts-snapshots/text-selection-openpencil-darwin.png index 7e8795685..896b6001f 100644 Binary files a/tests/e2e/panels/visual.spec.ts-snapshots/text-selection-openpencil-darwin.png and b/tests/e2e/panels/visual.spec.ts-snapshots/text-selection-openpencil-darwin.png differ