import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { AGENT_AVATAR_MAX_DIMENSION, AGENT_AVATAR_WEBP_QUALITY, prepareAgentAvatar, } from '@/lib/agent-avatar-upload'; const decodeMock = vi.hoisted(() => vi.fn()); const inspectFrameCountMock = vi.hoisted(() => vi.fn()); vi.mock('@/lib/browser-image-codec', () => ({ browserImageCodec: { inspectFrameCount: (...args: unknown[]) => inspectFrameCountMock(...args), decode: (...args: unknown[]) => decodeMock(...args), }, })); describe('agent avatar preparation', () => { beforeEach(() => { decodeMock.mockReset(); inspectFrameCountMock.mockReset(); inspectFrameCountMock.mockResolvedValue(1); }); afterEach(() => { vi.restoreAllMocks(); }); it('center-crops and compresses a large image to a small square avatar', async () => { const dispose = vi.fn(); const source = {} as CanvasImageSource; const drawImage = vi.fn(); const toBlob = vi.fn((callback: BlobCallback) => callback(new Blob(['avatar'], { type: 'image/webp' }))); const context = { imageSmoothingEnabled: false, imageSmoothingQuality: 'low' as ImageSmoothingQuality, clearRect: vi.fn(), drawImage, } as unknown as CanvasRenderingContext2D; const canvas = { width: 0, height: 0, getContext: vi.fn(() => context), toBlob, } as unknown as HTMLCanvasElement; vi.spyOn(document, 'createElement').mockReturnValue(canvas as unknown as HTMLElement); decodeMock.mockResolvedValue({ source, width: 1000, height: 500, dispose }); const result = await prepareAgentAvatar(new File(['source'], 'portrait.png', { type: 'image/png' })); expect(drawImage).toHaveBeenCalledWith( source, 250, 0, 500, 500, 0, 0, AGENT_AVATAR_MAX_DIMENSION, AGENT_AVATAR_MAX_DIMENSION, ); expect(toBlob.mock.calls[0]?.slice(1)).toEqual(['image/webp', AGENT_AVATAR_WEBP_QUALITY]); expect(result).toMatchObject({ fileName: 'avatar.webp', mimeType: 'image/webp', width: AGENT_AVATAR_MAX_DIMENSION, height: AGENT_AVATAR_MAX_DIMENSION, previewUrl: expect.stringMatching(/^data:image\/webp;base64,/), }); expect(dispose).toHaveBeenCalledOnce(); expect(canvas.width).toBe(1); expect(canvas.height).toBe(1); }); it('rejects unsupported and animated images before decoding', async () => { await expect(prepareAgentAvatar(new File(['text'], 'avatar.gif', { type: 'image/gif' }))) .rejects.toThrow('伙伴头像仅支持 PNG、JPEG 或 WebP 图片'); expect(inspectFrameCountMock).not.toHaveBeenCalled(); expect(decodeMock).not.toHaveBeenCalled(); inspectFrameCountMock.mockResolvedValue(2); await expect(prepareAgentAvatar(new File(['animated'], 'avatar.webp', { type: 'image/webp' }))) .rejects.toThrow('伙伴头像不支持动态图片'); expect(decodeMock).not.toHaveBeenCalled(); }); });