// ============================================================ // Prompts 页面测试 // ============================================================ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import { http, HttpResponse } from 'msw' import { setupServer } from 'msw/node' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import Prompts from '@/pages/Prompts' // ── Mock data ──────────────────────────────────────────────── const mockPrompts = { items: [ { id: 'pt-001', name: 'system-default', category: 'system', description: 'Default system prompt for all agents', source: 'builtin' as const, current_version: 3, status: 'active' as const, created_at: '2026-01-15T08:00:00Z', updated_at: '2026-03-20T12:00:00Z', }, { id: 'pt-002', name: 'custom-research', category: 'tool', description: 'Custom research prompt template', source: 'custom' as const, current_version: 1, status: 'active' as const, created_at: '2026-03-01T10:00:00Z', updated_at: '2026-03-01T10:00:00Z', }, { id: 'pt-003', name: 'legacy-summary', category: 'system', description: 'Legacy summary prompt', source: 'builtin' as const, current_version: 5, status: 'archived' as const, created_at: '2025-06-01T00:00:00Z', updated_at: '2026-02-28T00:00:00Z', }, ], total: 3, page: 1, page_size: 20, } // ── MSW server ─────────────────────────────────────────────── const server = setupServer() beforeEach(() => { server.listen({ onUnhandledRequest: 'bypass' }) }) afterEach(() => { server.close() }) // ── Helper: render with QueryClient ────────────────────────── function renderWithProviders(ui: React.ReactElement) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, }, }) return render( {ui} , ) } // ── Tests ──────────────────────────────────────────────────── describe('Prompts page', () => { it('renders page title and create button', async () => { server.use( http.get('*/api/v1/prompts', () => { return HttpResponse.json(mockPrompts) }), ) renderWithProviders() expect(screen.getByText('提示词管理')).toBeInTheDocument() expect(screen.getByText('管理系统提示词模板和版本历史')).toBeInTheDocument() expect(screen.getByText('新建提示词')).toBeInTheDocument() }) it('fetches and displays prompt templates', async () => { server.use( http.get('*/api/v1/prompts', () => { return HttpResponse.json(mockPrompts) }), ) renderWithProviders() await waitFor(() => { expect(screen.getByText('system-default')).toBeInTheDocument() }) expect(screen.getByText('custom-research')).toBeInTheDocument() expect(screen.getByText('legacy-summary')).toBeInTheDocument() // Category "tool" appears once in data expect(screen.getByText('tool')).toBeInTheDocument() }) it('shows loading spinner before data arrives', async () => { server.use( http.get('*/api/v1/prompts', async () => { await new Promise((resolve) => setTimeout(resolve, 500)) return HttpResponse.json(mockPrompts) }), ) renderWithProviders() const spinner = document.querySelector('.ant-spin') expect(spinner).toBeTruthy() // Wait for loading to complete so afterEach cleanup is clean await waitFor(() => { expect(screen.getByText('system-default')).toBeInTheDocument() }) }) it('renders source as tag with correct labels', async () => { server.use( http.get('*/api/v1/prompts', () => { return HttpResponse.json(mockPrompts) }), ) renderWithProviders() await waitFor(() => { expect(screen.getByText('system-default')).toBeInTheDocument() }) // sourceLabels: { builtin: '内置', custom: '自定义' } // '内置' appears twice (2 builtin items), '自定义' appears once const builtinTags = screen.getAllByText('内置') expect(builtinTags.length).toBe(2) expect(screen.getByText('自定义')).toBeInTheDocument() }) it('shows error state on API failure', async () => { server.use( http.get('*/api/v1/prompts', () => { return HttpResponse.json( { error: 'internal_error', message: '获取提示词列表失败' }, { status: 500 }, ) }), ) renderWithProviders() // React Query error propagation: ProTable receives empty data // but the query error should be visible via the table state // Check that no prompt names are rendered await waitFor(() => { expect(screen.queryByText('system-default')).not.toBeInTheDocument() }) }) })