fix(app): restore release validation coverage

- Keep the development MCP bridge alive until Vite shuts down
- Ignore empty host-font responses and cover current browser font policy
- Update interaction selectors, menu labels, resize accounting, and reviewed UI snapshots
This commit is contained in:
Danila Poyarkov 2026-07-20 16:26:07 +03:00
parent 49828cdac1
commit cff46fbc50
12 changed files with 51 additions and 45 deletions

View file

@ -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
})
}
}
}

View file

@ -207,7 +207,8 @@ async function loadSystemFont(family: string, style = 'Regular'): Promise<ArrayB
if (!isTauri()) return null
try {
const { invoke } = await import('@tauri-apps/api/core')
const data = await invoke<number[]>('load_system_font', { family, style })
const data = await invoke<number[] | null>('load_system_font', { family, style })
if (!data?.length) return null
return new Uint8Array(data).buffer
} catch {
return null

View file

@ -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')
})

View file

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -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)

View file

@ -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 ({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 29 KiB