Files
makelore/tests/unit/agent-avatar-upload.test.ts
inman 22add3f01f
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
完善客户端模块与工作区能力
2026-08-13 19:51:02 +08:00

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();
});
});