* 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
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import {
|
|
normalizedCoverageText,
|
|
WebFontResolver,
|
|
webFontSubsetsForText
|
|
} from '@open-pencil/core/text'
|
|
|
|
describe('web font coverage requests', () => {
|
|
test('normalizes coverage without splitting supplementary code points', () => {
|
|
expect(normalizedCoverageText('界A界𠀀A')).toBe(normalizedCoverageText('A界𠀀'))
|
|
expect(Array.from(normalizedCoverageText('𠀀'))).toEqual(['𠀀'])
|
|
})
|
|
|
|
test('aborts a font load queued behind an active provider request', async () => {
|
|
const resolver = new WebFontResolver()
|
|
resolver.setEnabled({ google: true })
|
|
let requestStarted: (() => void) | null = null
|
|
let releaseRequest: (() => void) | null = null
|
|
const started = new Promise<void>((resolve) => {
|
|
requestStarted = resolve
|
|
})
|
|
const blocked = new Promise<Response>((resolve) => {
|
|
releaseRequest = () => resolve(new Response('{}', { status: 200 }))
|
|
})
|
|
resolver.setRemoteFetch(async () => {
|
|
requestStarted?.()
|
|
return blocked
|
|
})
|
|
const first = resolver.listFamilies('google')
|
|
await started
|
|
const abort = new AbortController()
|
|
const queued = resolver.fetchFont(['Inter'], 'Regular', '', abort.signal)
|
|
|
|
abort.abort()
|
|
|
|
await expect(queued).rejects.toHaveProperty('name', 'AbortError')
|
|
releaseRequest?.()
|
|
await first
|
|
})
|
|
|
|
test('aborts promptly while provider resolution is pending', async () => {
|
|
const resolver = new WebFontResolver()
|
|
resolver.setEnabled({ google: true })
|
|
let providerRequestStarted: (() => void) | null = null
|
|
let releaseProviderRequest: (() => void) | null = null
|
|
const started = new Promise<void>((resolve) => {
|
|
providerRequestStarted = resolve
|
|
})
|
|
const blocked = new Promise<Response>((resolve) => {
|
|
releaseProviderRequest = () => resolve(new Response('{}', { status: 200 }))
|
|
})
|
|
resolver.setRemoteFetch(async () => {
|
|
providerRequestStarted?.()
|
|
return blocked
|
|
})
|
|
const abort = new AbortController()
|
|
const loading = resolver.fetchFont(['Inter'], 'Regular', '', abort.signal)
|
|
await started
|
|
|
|
abort.abort()
|
|
|
|
await expect(loading).rejects.toHaveProperty('name', 'AbortError')
|
|
releaseProviderRequest?.()
|
|
})
|
|
|
|
test('requests script-specific subsets instead of Latin only', () => {
|
|
expect(webFontSubsetsForText('مرحبا')).toContain('arabic')
|
|
expect(webFontSubsetsForText('한글')).toContain('korean')
|
|
expect(webFontSubsetsForText('かな')).toContain('japanese')
|
|
expect(webFontSubsetsForText('你好')).toEqual(
|
|
expect.arrayContaining(['chinese-simplified', 'chinese-traditional', 'japanese'])
|
|
)
|
|
})
|
|
})
|