完善客户端模块与工作区能力
This commit is contained in:
87
tests/unit/user-avatar.test.ts
Normal file
87
tests/unit/user-avatar.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user