63 lines
3.4 KiB
TypeScript
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();
|
|
});
|
|
});
|