| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- import React from 'react';
- import { describe, it, expect, vi, beforeEach } from 'vitest';
- import { render, screen, waitFor } from '@testing-library/react';
- import userEvent from '@testing-library/user-event';
- import { TestWrapper } from '../../__test_utils__/test-render';
- import { TestRouter } from '../../__test_utils__/test-router';
- import { TestQueryProvider } from '../../__test_utils__/test-query';
- // 模拟的表单组件
- function LoginForm({ onSubmit }: { onSubmit: (data: any) => Promise<void> }) {
- const [email, setEmail] = React.useState('');
- const [password, setPassword] = React.useState('');
- const [isSubmitting, setIsSubmitting] = React.useState(false);
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsSubmitting(true);
- try {
- await onSubmit({ email, password });
- } catch (error) {
- console.error('Login failed:', error);
- } finally {
- setIsSubmitting(false);
- }
- };
- return (
- <form onSubmit={handleSubmit} data-testid="login-form">
- <div>
- <label htmlFor="email">Email:</label>
- <input
- id="email"
- type="email"
- value={email}
- onChange={(e) => setEmail(e.target.value)}
- required
- data-testid="email-input"
- />
- </div>
- <div>
- <label htmlFor="password">Password:</label>
- <input
- id="password"
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- required
- data-testid="password-input"
- />
- </div>
- <button
- type="submit"
- disabled={isSubmitting}
- data-testid="submit-button"
- >
- {isSubmitting ? 'Logging in...' : 'Login'}
- </button>
- </form>
- );
- }
- // 模拟的登录页面组件
- function LoginPage() {
- const loginMutation = {
- mutateAsync: vi.fn().mockResolvedValue({ success: true }),
- isLoading: false,
- };
- const handleLogin = async (credentials: any) => {
- return loginMutation.mutateAsync(credentials);
- };
- return (
- <div>
- <h1>Login</h1>
- <LoginForm onSubmit={handleLogin} />
- </div>
- );
- }
- describe('Form Component Integration Tests', () => {
- let user: ReturnType<typeof userEvent.setup>;
- let loginMock: any;
- beforeEach(() => {
- user = userEvent.setup();
- loginMock = vi.fn().mockResolvedValue({ success: true });
- });
- it('应该渲染表单并包含所有必要的字段', () => {
- render(
- <TestWrapper>
- <LoginForm onSubmit={loginMock} />
- </TestWrapper>
- );
- expect(screen.getByTestId('login-form')).toBeInTheDocument();
- expect(screen.getByTestId('email-input')).toBeInTheDocument();
- expect(screen.getByTestId('password-input')).toBeInTheDocument();
- expect(screen.getByTestId('submit-button')).toBeInTheDocument();
- });
- it('应该允许用户输入邮箱和密码', async () => {
- render(
- <TestWrapper>
- <LoginForm onSubmit={loginMock} />
- </TestWrapper>
- );
- const emailInput = screen.getByTestId('email-input');
- const passwordInput = screen.getByTestId('password-input');
- await user.type(emailInput, 'test@example.com');
- await user.type(passwordInput, 'password123');
- expect(emailInput).toHaveValue('test@example.com');
- expect(passwordInput).toHaveValue('password123');
- });
- it('应该在表单提交时调用onSubmit回调', async () => {
- render(
- <TestWrapper>
- <LoginForm onSubmit={loginMock} />
- </TestWrapper>
- );
- await user.type(screen.getByTestId('email-input'), 'test@example.com');
- await user.type(screen.getByTestId('password-input'), 'password123');
- await user.click(screen.getByTestId('submit-button'));
- await waitFor(() => {
- expect(loginMock).toHaveBeenCalledWith({
- email: 'test@example.com',
- password: 'password123'
- });
- });
- });
- it('应该在提交时显示加载状态', async () => {
- const slowLoginMock = vi.fn().mockImplementation(
- () => new Promise(resolve => setTimeout(() => resolve({ success: true }), 100))
- );
- render(
- <TestWrapper>
- <LoginForm onSubmit={slowLoginMock} />
- </TestWrapper>
- );
- await user.type(screen.getByTestId('email-input'), 'test@example.com');
- await user.type(screen.getByTestId('password-input'), 'password123');
- await user.click(screen.getByTestId('submit-button'));
- expect(screen.getByTestId('submit-button')).toBeDisabled();
- expect(screen.getByTestId('submit-button')).toHaveTextContent('Logging in...');
- await waitFor(() => {
- expect(screen.getByTestId('submit-button')).not.toBeDisabled();
- });
- });
- it('应该与React Router集成', () => {
- render(
- <TestQueryProvider>
- <TestRouter initialPath="/login">
- <LoginPage />
- </TestRouter>
- </TestQueryProvider>
- );
- expect(screen.getByRole('heading', { name: 'Login' })).toBeInTheDocument();
- expect(screen.getByTestId('login-form')).toBeInTheDocument();
- });
- it('应该处理表单验证错误', async () => {
- const errorMock = vi.fn().mockRejectedValue(new Error('Invalid credentials'));
- render(
- <TestWrapper>
- <LoginForm onSubmit={errorMock} />
- </TestWrapper>
- );
- // 不填写必填字段直接提交
- await user.click(screen.getByTestId('submit-button'));
- // 检查表单仍然可见(没有因为错误而崩溃)
- expect(screen.getByTestId('login-form')).toBeInTheDocument();
- });
- it('应该支持键盘导航和辅助功能', async () => {
- render(
- <TestWrapper>
- <LoginForm onSubmit={loginMock} />
- </TestWrapper>
- );
- const emailInput = screen.getByTestId('email-input');
- const passwordInput = screen.getByTestId('password-input');
- const submitButton = screen.getByTestId('submit-button');
- // Tab 导航
- await user.tab();
- expect(emailInput).toHaveFocus();
- await user.tab();
- expect(passwordInput).toHaveFocus();
- await user.tab();
- expect(submitButton).toHaveFocus();
- // Enter 提交
- await user.type(emailInput, 'test@example.com');
- await user.type(passwordInput, 'password123');
- await user.keyboard('{Enter}');
- await waitFor(() => {
- expect(loginMock).toHaveBeenCalled();
- });
- });
- });
|