* fix(text): prepare browser fonts atomically - Fetch approved Fontsource resources directly in browsers with bounded responses and retryable provider failures - Limit page font resolution concurrency and retain live Inter substitution paragraphs after baked glyph invalidation - Shape frame, section, and component labels through a bounded native Paragraph cache - Cover real Geist, Geist Mono, and Roboto Mono browser loading plus substitution and cache lifecycle behavior * test(canvas): cover substitution and label shaping - Capture baked missing-font text before editing, live Inter substitution on first input, and visible undo output - Assert text-picture and derived-glyph invalidation with finalized substituted readiness - Cover shaped frame, rotated frame, section, component, component-set, ellipsis, kerning, and zoom label presentation * fix(text): preserve same-origin fetches during font resolution - Route same-origin application and fixture requests through native fetch while Unifont temporarily proxies global HTTP requests - Keep the external provider HTTPS allowlist enforced for cross-origin font resources - Cover the production race discovered while loading gold-preview.fig during provider initialization * fix(text): preserve substituted path glyphs - Keep imported derived curved glyphs for text-on-path layers when exact fonts finalize as substituted - Cover substituted path rendering through the runtime renderer and refresh the corrected visual oracle - Update shaped Inter measurement badges and merged typography panel snapshots after visual inspection - Search virtualized font catalogs explicitly and restore the canvas screenshot helper used by broad E2E coverage * fix(text): use browser font transport without desktop warning - Keep the browser provider implementation aligned with current settings behavior after splitting from preparation * test(text): inject same-origin browser font context - Keep the transport test deterministic outside a Window global * fix(text): preserve final browser font transport hardening - Carry the same-origin large-document bypass and bounded cross-origin provider checks from the preserved integration snapshot * fix(text): initialize font transport outside Window contexts - Keep unit and headless module imports safe while browsers use their current origin * test(text): satisfy async visual fixture contract - Return from the resolver setup continuation after requesting render * test(canvas): include paragraph cache lifecycle * fix(text): cancel queued browser font loads - Race serialized provider work against preparation cancellation\n- Release queue slots after cancelled waiters without disturbing active requests\n- Reuse one section-title paragraph cache entry for measurement and drawing * fix(text): close font queue cancellation races - Release reserved proxy queue slots when cancellation lands after queue acquisition\n- Skip paragraph work for zero-width section labels * fix(text): abort active provider resolution - Race active provider resolution against its preparation signal and restore the temporary fetch proxy promptly\n- Forward cancellation through proxied provider requests\n- Align the renderer font-readiness facade with substituted text
81 lines
2.6 KiB
TypeScript
81 lines
2.6 KiB
TypeScript
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
|
|
import { browserWebFontFetch, createBrowserWebFontFetch } from '@/app/editor/fonts/browser-fetch'
|
|
|
|
const originalFetch = globalThis.fetch
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch
|
|
})
|
|
|
|
describe('browser web font fetch', () => {
|
|
test('rejects unapproved and non-HTTPS provider URLs', async () => {
|
|
await expect(browserWebFontFetch('https://example.com/font.ttf')).rejects.toThrow(
|
|
'Unsupported web font host'
|
|
)
|
|
await expect(browserWebFontFetch('http://fonts.gstatic.com/font.ttf')).rejects.toThrow(
|
|
'Unsupported web font host'
|
|
)
|
|
})
|
|
|
|
test('allows same-origin application resources through the temporary font proxy', async () => {
|
|
const fontFetch = createBrowserWebFontFetch(
|
|
mock(
|
|
async () =>
|
|
new Response(new Uint8Array([1]), {
|
|
headers: { 'content-length': String(36 * 1024 * 1024) }
|
|
})
|
|
) as typeof fetch,
|
|
'http://127.0.0.1:4301'
|
|
)
|
|
await expect(fontFetch('http://127.0.0.1:4301/gold-preview.fig')).resolves.toBeInstanceOf(
|
|
Response
|
|
)
|
|
})
|
|
test('returns bounded responses from approved provider hosts', async () => {
|
|
const fontFetch = createBrowserWebFontFetch(
|
|
mock(
|
|
async () =>
|
|
new Response(new Uint8Array([1, 2, 3]), {
|
|
status: 200,
|
|
headers: { 'content-type': 'font/ttf' }
|
|
})
|
|
) as typeof fetch
|
|
)
|
|
|
|
const response = await fontFetch('https://fonts.gstatic.com/font.ttf')
|
|
|
|
expect(response.status).toBe(200)
|
|
expect([...new Uint8Array(await response.arrayBuffer())]).toEqual([1, 2, 3])
|
|
})
|
|
|
|
test('passes cancellation through browser font downloads', async () => {
|
|
let observedSignal: AbortSignal | null = null
|
|
const fontFetch = createBrowserWebFontFetch(
|
|
mock(async (request: Request) => {
|
|
observedSignal = request.signal
|
|
return new Response(new Uint8Array([1, 2, 3]))
|
|
}) as typeof fetch
|
|
)
|
|
const abort = new AbortController()
|
|
|
|
await fontFetch('https://fonts.gstatic.com/font.ttf', { signal: abort.signal })
|
|
abort.abort()
|
|
|
|
expect(observedSignal?.aborted).toBe(true)
|
|
})
|
|
test('rejects provider responses over the font size limit', async () => {
|
|
const fontFetch = createBrowserWebFontFetch(
|
|
mock(
|
|
async () =>
|
|
new Response(null, {
|
|
status: 200,
|
|
headers: { 'content-length': String(9 * 1024 * 1024) }
|
|
})
|
|
) as typeof fetch
|
|
)
|
|
|
|
await expect(fontFetch('https://cdn.jsdelivr.net/font.ttf')).rejects.toThrow('size limit')
|
|
})
|
|
})
|