87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
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();
|
|
});
|
|
});
|