1
0

feat: 完成前端重构,采用 Ant Design 5 和完整测试体系

- 采用 Ant Design 5 作为 UI 组件库,替换自定义组件
- 集成 React Router v7 提供路由导航
- 使用 TanStack Query v5 管理数据获取和缓存
- 建立 Vitest + React Testing Library 测试体系
- 添加 Playwright E2E 测试覆盖
- 使用 MSW mock API 响应
- 配置 TypeScript strict 模式
- 采用 SCSS Modules 组织样式
- 更新 OpenSpec 规格以反映前端架构变更
- 归档 frontend-refactor 变更记录
This commit is contained in:
2026-04-16 11:21:48 +08:00
parent c17903dcbc
commit 9359ca7f62
61 changed files with 4588 additions and 1095 deletions

View File

@@ -0,0 +1,165 @@
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';
import { listProviders, createProvider, updateProvider, deleteProvider } from '@/api/providers';
const mockProviders = [
{
id: 'prov-1',
name: 'OpenAI',
api_key: 'sk-xxx',
base_url: 'https://api.openai.com',
enabled: true,
created_at: '2025-01-01T00:00:00Z',
updated_at: '2025-01-01T00:00:00Z',
},
{
id: 'prov-2',
name: 'Anthropic',
api_key: 'sk-yyy',
base_url: 'https://api.anthropic.com',
enabled: false,
created_at: '2025-01-02T00:00:00Z',
updated_at: '2025-01-02T00:00:00Z',
},
];
describe('providers API', () => {
const server = setupServer();
beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe('listProviders', () => {
it('returns array of Provider objects with camelCase keys', async () => {
server.use(
http.get('/api/providers', () => {
return HttpResponse.json(mockProviders);
}),
);
const result = await listProviders();
expect(result).toEqual([
{
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
enabled: true,
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
},
{
id: 'prov-2',
name: 'Anthropic',
apiKey: 'sk-yyy',
baseUrl: 'https://api.anthropic.com',
enabled: false,
createdAt: '2025-01-02T00:00:00Z',
updatedAt: '2025-01-02T00:00:00Z',
},
]);
});
});
describe('createProvider', () => {
it('sends POST with correct body and returns provider', async () => {
let receivedMethod: string | null = null;
let receivedBody: Record<string, unknown> | null = null;
server.use(
http.post('/api/providers', async ({ request }) => {
receivedMethod = request.method;
receivedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json(mockProviders[0]);
}),
);
const input = {
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
enabled: true,
};
const result = await createProvider(input);
expect(receivedMethod).toBe('POST');
expect(receivedBody).toEqual({
id: 'prov-1',
name: 'OpenAI',
api_key: 'sk-xxx',
base_url: 'https://api.openai.com',
enabled: true,
});
expect(result).toEqual({
id: 'prov-1',
name: 'OpenAI',
apiKey: 'sk-xxx',
baseUrl: 'https://api.openai.com',
enabled: true,
createdAt: '2025-01-01T00:00:00Z',
updatedAt: '2025-01-01T00:00:00Z',
});
});
});
describe('updateProvider', () => {
it('sends PUT with correct body and returns provider', async () => {
let receivedMethod: string | null = null;
let receivedUrl: string | null = null;
let receivedBody: Record<string, unknown> | null = null;
server.use(
http.put('/api/providers/:id', async ({ request, params }) => {
receivedMethod = request.method;
receivedUrl = new URL(request.url).pathname;
receivedBody = (await request.json()) as Record<string, unknown>;
return HttpResponse.json({
...mockProviders[0],
name: 'Updated',
api_key: 'sk-updated',
});
}),
);
const result = await updateProvider('prov-1', {
name: 'Updated',
apiKey: 'sk-updated',
});
expect(receivedMethod).toBe('PUT');
expect(receivedUrl).toBe('/api/providers/prov-1');
expect(receivedBody).toEqual({
name: 'Updated',
api_key: 'sk-updated',
});
expect(result.name).toBe('Updated');
expect(result.apiKey).toBe('sk-updated');
});
});
describe('deleteProvider', () => {
it('sends DELETE and returns void', async () => {
let receivedMethod: string | null = null;
let receivedUrl: string | null = null;
server.use(
http.delete('/api/providers/:id', ({ request, params }) => {
receivedMethod = request.method;
receivedUrl = new URL(request.url).pathname;
return new HttpResponse(null, { status: 204 });
}),
);
const result = await deleteProvider('prov-1');
expect(receivedMethod).toBe('DELETE');
expect(receivedUrl).toBe('/api/providers/prov-1');
expect(result).toBeUndefined();
});
});
});