- 替换 antd 为 tdesign-react 作为主要 UI 组件库 - 引入 Recharts 替代 @ant-design/charts 实现图表功能 - 移除主题系统相关代码(ThemeContext、themes 目录) - 更新所有组件以适配 TDesign 组件 API - 更新测试用例以匹配新的组件实现 - 新增 TDesign 和 Recharts 集成规范文档
269 lines
7.5 KiB
TypeScript
269 lines
7.5 KiB
TypeScript
import { renderHook, waitFor } from '@testing-library/react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import React from 'react';
|
|
import { http, HttpResponse } from 'msw';
|
|
import { setupServer } from 'msw/node';
|
|
import { useProviders, useCreateProvider, useUpdateProvider, useDeleteProvider } from '@/hooks/useProviders';
|
|
import type { Provider, CreateProviderInput, UpdateProviderInput } from '@/types';
|
|
import { MessagePlugin } from 'tdesign-react';
|
|
|
|
// Mock MessagePlugin
|
|
vi.mock('tdesign-react', () => ({
|
|
MessagePlugin: {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
// Test data
|
|
const mockProviders: Provider[] = [
|
|
{
|
|
id: 'provider-1',
|
|
name: 'OpenAI',
|
|
apiKey: 'sk-xxx',
|
|
baseUrl: 'https://api.openai.com',
|
|
enabled: true,
|
|
createdAt: '2026-01-01T00:00:00Z',
|
|
updatedAt: '2026-01-01T00:00:00Z',
|
|
},
|
|
{
|
|
id: 'provider-2',
|
|
name: 'Anthropic',
|
|
apiKey: 'sk-yyy',
|
|
baseUrl: 'https://api.anthropic.com',
|
|
enabled: false,
|
|
createdAt: '2026-02-01T00:00:00Z',
|
|
updatedAt: '2026-02-01T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
const mockCreatedProvider: Provider = {
|
|
id: 'provider-3',
|
|
name: 'NewProvider',
|
|
apiKey: 'sk-zzz',
|
|
baseUrl: 'https://api.newprovider.com',
|
|
enabled: true,
|
|
createdAt: '2026-03-01T00:00:00Z',
|
|
updatedAt: '2026-03-01T00:00:00Z',
|
|
};
|
|
|
|
// MSW handlers
|
|
const handlers = [
|
|
http.get('/api/providers', () => {
|
|
return HttpResponse.json(mockProviders);
|
|
}),
|
|
http.post('/api/providers', async ({ request }) => {
|
|
const body = await request.json() as Record<string, unknown>;
|
|
return HttpResponse.json({
|
|
...mockCreatedProvider,
|
|
...body,
|
|
});
|
|
}),
|
|
http.put('/api/providers/:id', async ({ request, params }) => {
|
|
const body = await request.json() as Record<string, unknown>;
|
|
const existing = mockProviders.find((p) => p.id === params['id']);
|
|
return HttpResponse.json({ ...existing, ...body });
|
|
}),
|
|
http.delete('/api/providers/:id', () => {
|
|
return new HttpResponse(null, { status: 204 });
|
|
}),
|
|
];
|
|
|
|
const server = setupServer(...handlers);
|
|
|
|
function createTestQueryClient() {
|
|
return new QueryClient({
|
|
defaultOptions: {
|
|
queries: { retry: false },
|
|
mutations: { retry: false },
|
|
},
|
|
});
|
|
}
|
|
|
|
function createWrapper() {
|
|
const testQueryClient = createTestQueryClient();
|
|
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={testQueryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
};
|
|
}
|
|
|
|
beforeAll(() => server.listen());
|
|
afterEach(() => {
|
|
server.resetHandlers();
|
|
vi.clearAllMocks();
|
|
});
|
|
afterAll(() => server.close());
|
|
|
|
describe('useProviders', () => {
|
|
it('fetches and returns provider list', async () => {
|
|
const { result } = renderHook(() => useProviders(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
|
|
expect(result.current.data).toEqual(mockProviders);
|
|
expect(result.current.data).toHaveLength(2);
|
|
expect(result.current.data![0]!.name).toBe('OpenAI');
|
|
expect(result.current.data![1]!.name).toBe('Anthropic');
|
|
});
|
|
});
|
|
|
|
describe('useCreateProvider', () => {
|
|
it('calls API and invalidates provider queries', async () => {
|
|
const queryClient = createTestQueryClient();
|
|
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
|
|
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
const { result } = renderHook(() => useCreateProvider(), {
|
|
wrapper: Wrapper,
|
|
});
|
|
|
|
const input: CreateProviderInput = {
|
|
id: 'provider-3',
|
|
name: 'NewProvider',
|
|
apiKey: 'sk-zzz',
|
|
baseUrl: 'https://api.newprovider.com',
|
|
enabled: true,
|
|
};
|
|
|
|
result.current.mutate(input);
|
|
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
|
|
expect(result.current.data).toMatchObject({
|
|
id: 'provider-3',
|
|
name: 'NewProvider',
|
|
});
|
|
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] });
|
|
expect(MessagePlugin.success).toHaveBeenCalledWith('供应商创建成功');
|
|
});
|
|
|
|
it('calls message.error on failure', async () => {
|
|
server.use(
|
|
http.post('/api/providers', () => {
|
|
return HttpResponse.json({ message: '创建失败' }, { status: 500 });
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() => useCreateProvider(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
const input: CreateProviderInput = {
|
|
id: 'provider-3',
|
|
name: 'NewProvider',
|
|
apiKey: 'sk-zzz',
|
|
baseUrl: 'https://api.newprovider.com',
|
|
enabled: true,
|
|
};
|
|
|
|
result.current.mutate(input);
|
|
|
|
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
expect(MessagePlugin.error).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('useUpdateProvider', () => {
|
|
it('calls API and invalidates provider queries', async () => {
|
|
const queryClient = createTestQueryClient();
|
|
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
|
|
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
const { result } = renderHook(() => useUpdateProvider(), {
|
|
wrapper: Wrapper,
|
|
});
|
|
|
|
const input: UpdateProviderInput = { name: 'UpdatedProvider' };
|
|
|
|
result.current.mutate({ id: 'provider-1', input });
|
|
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
|
|
expect(result.current.data).toMatchObject({
|
|
name: 'UpdatedProvider',
|
|
});
|
|
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] });
|
|
expect(MessagePlugin.success).toHaveBeenCalledWith('供应商更新成功');
|
|
});
|
|
|
|
it('calls message.error on failure', async () => {
|
|
server.use(
|
|
http.put('/api/providers/:id', () => {
|
|
return HttpResponse.json({ message: '更新失败' }, { status: 500 });
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() => useUpdateProvider(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
result.current.mutate({ id: 'provider-1', input: { name: 'Updated' } });
|
|
|
|
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
expect(MessagePlugin.error).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('useDeleteProvider', () => {
|
|
it('calls API and invalidates provider queries', async () => {
|
|
const queryClient = createTestQueryClient();
|
|
const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
|
|
|
|
function Wrapper({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<QueryClientProvider client={queryClient}>
|
|
{children}
|
|
</QueryClientProvider>
|
|
);
|
|
}
|
|
|
|
const { result } = renderHook(() => useDeleteProvider(), {
|
|
wrapper: Wrapper,
|
|
});
|
|
|
|
result.current.mutate('provider-1');
|
|
|
|
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
|
|
|
expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['providers'] });
|
|
expect(MessagePlugin.success).toHaveBeenCalledWith('供应商删除成功');
|
|
});
|
|
|
|
it('calls message.error on failure', async () => {
|
|
server.use(
|
|
http.delete('/api/providers/:id', () => {
|
|
return HttpResponse.json({ message: '删除失败' }, { status: 500 });
|
|
}),
|
|
);
|
|
|
|
const { result } = renderHook(() => useDeleteProvider(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
result.current.mutate('provider-1');
|
|
|
|
await waitFor(() => expect(result.current.isError).toBe(true));
|
|
expect(MessagePlugin.error).toHaveBeenCalled();
|
|
});
|
|
});
|