88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
prepareUserAvatar,
|
|
USER_AVATAR_MAX_DIMENSION,
|
|
} from '@/lib/user-avatar';
|
|
|
|
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('user avatar preparation', () => {
|
|
beforeEach(() => {
|
|
decodeMock.mockReset();
|
|
inspectFrameCountMock.mockReset();
|
|
inspectFrameCountMock.mockResolvedValue(1);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('center-crops a portrait image to a square before encoding it', async () => {
|
|
const dispose = vi.fn();
|
|
const source = {} as CanvasImageSource;
|
|
const drawImage = vi.fn();
|
|
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: vi.fn((callback: BlobCallback) => callback(new Blob(['avatar'], { type: 'image/webp' }))),
|
|
} as unknown as HTMLCanvasElement;
|
|
vi.spyOn(document, 'createElement').mockReturnValue(canvas as unknown as HTMLElement);
|
|
decodeMock.mockResolvedValue({ source, width: 100, height: 200, dispose });
|
|
|
|
const result = await prepareUserAvatar(new File(['source'], 'portrait.png', { type: 'image/png' }));
|
|
|
|
expect(drawImage).toHaveBeenCalledWith(
|
|
source,
|
|
0,
|
|
50,
|
|
100,
|
|
100,
|
|
0,
|
|
0,
|
|
100,
|
|
100,
|
|
);
|
|
expect(result).toMatchObject({
|
|
fileName: 'avatar.webp',
|
|
mimeType: 'image/webp',
|
|
width: 100,
|
|
height: 100,
|
|
previewUrl: expect.stringMatching(/^data:image\/webp;base64,/),
|
|
});
|
|
expect(result.width).toBeLessThanOrEqual(USER_AVATAR_MAX_DIMENSION);
|
|
expect(dispose).toHaveBeenCalledOnce();
|
|
expect(canvas.width).toBe(1);
|
|
expect(canvas.height).toBe(1);
|
|
});
|
|
|
|
it('rejects unsupported avatar formats before decoding', async () => {
|
|
await expect(prepareUserAvatar(new File(['text'], 'avatar.gif', { type: 'image/gif' })))
|
|
.rejects.toThrow('头像仅支持 PNG、JPEG 或 WebP 图片');
|
|
expect(inspectFrameCountMock).not.toHaveBeenCalled();
|
|
expect(decodeMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rejects animated avatar files before decoding', async () => {
|
|
inspectFrameCountMock.mockResolvedValue(2);
|
|
|
|
await expect(prepareUserAvatar(new File(['animated'], 'avatar.webp', { type: 'image/webp' })))
|
|
.rejects.toThrow('头像不支持动态图片');
|
|
expect(decodeMock).not.toHaveBeenCalled();
|
|
});
|
|
});
|