// @vitest-environment jsdom import { renderHook, waitFor } from '@testing-library/react'; import { afterEach, describe, expect, it, vi } from 'vitest'; import type { FontAsset } from '../src/features/ui-editor/types/FontAsset'; import { useUiEditorFontFaces } from '../src/features/ui-editor/useUiEditorFontFaces'; const { invoke } = vi.hoisted(() => ({ invoke: vi.fn(async () => new Uint8Array([0, 1, 2, 3]).buffer), })); vi.mock('@tauri-apps/api/core', () => ({ invoke })); const FONT: FontAsset = { asset_id: 'font-1', metadata: { family_name: '测试字体', face_name: 'Regular', weight: 400, italic: false, format: 'TrueType', source_file_name: 'test.ttf', }, path: 'assets/fonts/test.ttf', content_sha256: 'a'.repeat(64), }; afterEach(() => { vi.restoreAllMocks(); invoke.mockClear(); }); describe('useUiEditorFontFaces', () => { it('loads registered bytes into a private FontFace and releases browser resources', async () => { const add = vi.fn(); const remove = vi.fn(); Object.defineProperty(document, 'fonts', { configurable: true, value: { add, delete: remove }, }); const load = vi.fn(async function (this: FontFace) { return this; }); vi.stubGlobal( 'FontFace', class { load = load; constructor( readonly family: string, readonly source: string, readonly descriptors?: FontFaceDescriptors, ) {} }, ); const createObjectURL = vi.fn(() => 'blob:font-preview'); const revokeObjectURL = vi.fn(); vi.stubGlobal('URL', { createObjectURL, revokeObjectURL }); const { result, unmount } = renderHook(() => useUiEditorFontFaces('/project', { [FONT.asset_id]: FONT }), ); await waitFor(() => { expect(result.current['font-1']?.status).toBe('loaded'); }); expect(invoke).toHaveBeenCalledWith('read_ui_editor_font_bytes', { projectPath: '/project', assetId: 'font-1', relativePath: FONT.path, expectedSha256: FONT.content_sha256, }); expect(add).toHaveBeenCalledTimes(1); expect(createObjectURL).toHaveBeenCalledTimes(1); unmount(); expect(remove).toHaveBeenCalledTimes(1); expect(revokeObjectURL).toHaveBeenCalledWith('blob:font-preview'); }); });