Files
tiku-saas-web/src/tests/assetApi.test.tsx

63 lines
3.4 KiB
TypeScript

import { render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AssetLibraryPanel } from '../site-builder/AssetLibraryPanel';
import { createDefaultConfig } from '../shared/defaults';
class SuccessfulUploadRequest {
static last: SuccessfulUploadRequest | null = null;
method = '';
url = '';
status = 0;
headers: Record<string, string> = {};
upload: { onprogress: ((event: ProgressEvent) => void) | null } = { onprogress: null };
onerror: ((event: ProgressEvent) => void) | null = null;
onload: ((event: ProgressEvent) => void) | null = null;
constructor() { SuccessfulUploadRequest.last = this; }
open(method: string, url: string) { this.method = method; this.url = url; }
setRequestHeader(key: string, value: string) { this.headers[key] = value; }
send() {
this.upload.onprogress?.({ lengthComputable: true, loaded: 10, total: 10 } as ProgressEvent);
this.status = 200;
this.onload?.({} as ProgressEvent);
}
}
afterEach(() => { vi.unstubAllGlobals(); });
describe('theme asset workflow', () => {
it('signs, uploads with returned method and headers, then confirms', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce(new Response(JSON.stringify({ item: { id: 'asset-1' }, upload: { method: 'PUT', url: 'https://oss.example/upload', headers: { 'x-oss-token': 'signed' } } }), { status: 200, headers: { 'content-type': 'application/json' } }))
.mockResolvedValueOnce(new Response(JSON.stringify({ status: 'Completed' }), { status: 200, headers: { 'content-type': 'application/json' } }));
vi.stubGlobal('fetch', fetchMock);
vi.stubGlobal('XMLHttpRequest', SuccessfulUploadRequest);
vi.resetModules();
const { assetApi } = await import('../api/assetApi');
const progress: number[] = [];
const result = await assetApi.uploadThemeImage(new File(['image'], 'hero.webp', { type: 'image/webp' }), '首页主视觉', (value) => progress.push(value));
expect(result).toEqual({ assetId: 'asset-1', fileName: 'hero.webp' });
expect(SuccessfulUploadRequest.last).toMatchObject({ method: 'PUT', url: 'https://oss.example/upload', headers: { 'x-oss-token': 'signed' } });
expect(progress.at(-1)).toBe(100);
const signRequest = fetchMock.mock.calls[0]?.[0] as Request;
expect(await signRequest.clone().json()).toMatchObject({ category: 'theme', visibility: 'Public', isPublic: true });
const confirmRequest = fetchMock.mock.calls[1]?.[0] as Request;
expect(await confirmRequest.clone().json()).toMatchObject({ assetId: 'asset-1' });
});
it('keeps existing assets read-only when upload permission is absent', () => {
const branding = createDefaultConfig().branding;
branding.logoAssetId = 'existing-logo';
const onRemove = vi.fn();
render(<AssetLibraryPanel branding={branding} canUpload={false} uploads={{}} onUpload={vi.fn()} onRetry={vi.fn()} onRemove={onRemove} onAltChange={vi.fn()} />);
expect(screen.getByText('当前账号没有主题资产上传权限')).toBeInTheDocument();
const inputs = document.querySelectorAll<HTMLInputElement>('input[type="file"]');
expect(inputs.length).toBeGreaterThan(0);
inputs.forEach((input) => expect(input).toBeDisabled());
expect(screen.getByRole('button', { name: /移除引用/ })).toBeDisabled();
expect(onRemove).not.toHaveBeenCalled();
expect(screen.getByText('existing-logo')).toBeInTheDocument();
});
});