Form.integration.test.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import React from 'react';
  2. import { describe, it, expect, vi, beforeEach } from 'vitest';
  3. import { render, screen, waitFor } from '@testing-library/react';
  4. import userEvent from '@testing-library/user-event';
  5. import { TestWrapper } from '../../__test_utils__/test-render';
  6. import { TestRouter } from '../../__test_utils__/test-router';
  7. import { TestQueryProvider } from '../../__test_utils__/test-query';
  8. // 模拟的表单组件
  9. function LoginForm({ onSubmit }: { onSubmit: (data: any) => Promise<void> }) {
  10. const [email, setEmail] = React.useState('');
  11. const [password, setPassword] = React.useState('');
  12. const [isSubmitting, setIsSubmitting] = React.useState(false);
  13. const handleSubmit = async (e: React.FormEvent) => {
  14. e.preventDefault();
  15. setIsSubmitting(true);
  16. try {
  17. await onSubmit({ email, password });
  18. } catch (error) {
  19. console.error('Login failed:', error);
  20. } finally {
  21. setIsSubmitting(false);
  22. }
  23. };
  24. return (
  25. <form onSubmit={handleSubmit} data-testid="login-form">
  26. <div>
  27. <label htmlFor="email">Email:</label>
  28. <input
  29. id="email"
  30. type="email"
  31. value={email}
  32. onChange={(e) => setEmail(e.target.value)}
  33. required
  34. data-testid="email-input"
  35. />
  36. </div>
  37. <div>
  38. <label htmlFor="password">Password:</label>
  39. <input
  40. id="password"
  41. type="password"
  42. value={password}
  43. onChange={(e) => setPassword(e.target.value)}
  44. required
  45. data-testid="password-input"
  46. />
  47. </div>
  48. <button
  49. type="submit"
  50. disabled={isSubmitting}
  51. data-testid="submit-button"
  52. >
  53. {isSubmitting ? 'Logging in...' : 'Login'}
  54. </button>
  55. </form>
  56. );
  57. }
  58. // 模拟的登录页面组件
  59. function LoginPage() {
  60. const loginMutation = {
  61. mutateAsync: vi.fn().mockResolvedValue({ success: true }),
  62. isLoading: false,
  63. };
  64. const handleLogin = async (credentials: any) => {
  65. return loginMutation.mutateAsync(credentials);
  66. };
  67. return (
  68. <div>
  69. <h1>Login</h1>
  70. <LoginForm onSubmit={handleLogin} />
  71. </div>
  72. );
  73. }
  74. describe('Form Component Integration Tests', () => {
  75. let user: ReturnType<typeof userEvent.setup>;
  76. let loginMock: any;
  77. beforeEach(() => {
  78. user = userEvent.setup();
  79. loginMock = vi.fn().mockResolvedValue({ success: true });
  80. });
  81. it('应该渲染表单并包含所有必要的字段', () => {
  82. render(
  83. <TestWrapper>
  84. <LoginForm onSubmit={loginMock} />
  85. </TestWrapper>
  86. );
  87. expect(screen.getByTestId('login-form')).toBeInTheDocument();
  88. expect(screen.getByTestId('email-input')).toBeInTheDocument();
  89. expect(screen.getByTestId('password-input')).toBeInTheDocument();
  90. expect(screen.getByTestId('submit-button')).toBeInTheDocument();
  91. });
  92. it('应该允许用户输入邮箱和密码', async () => {
  93. render(
  94. <TestWrapper>
  95. <LoginForm onSubmit={loginMock} />
  96. </TestWrapper>
  97. );
  98. const emailInput = screen.getByTestId('email-input');
  99. const passwordInput = screen.getByTestId('password-input');
  100. await user.type(emailInput, 'test@example.com');
  101. await user.type(passwordInput, 'password123');
  102. expect(emailInput).toHaveValue('test@example.com');
  103. expect(passwordInput).toHaveValue('password123');
  104. });
  105. it('应该在表单提交时调用onSubmit回调', async () => {
  106. render(
  107. <TestWrapper>
  108. <LoginForm onSubmit={loginMock} />
  109. </TestWrapper>
  110. );
  111. await user.type(screen.getByTestId('email-input'), 'test@example.com');
  112. await user.type(screen.getByTestId('password-input'), 'password123');
  113. await user.click(screen.getByTestId('submit-button'));
  114. await waitFor(() => {
  115. expect(loginMock).toHaveBeenCalledWith({
  116. email: 'test@example.com',
  117. password: 'password123'
  118. });
  119. });
  120. });
  121. it('应该在提交时显示加载状态', async () => {
  122. const slowLoginMock = vi.fn().mockImplementation(
  123. () => new Promise(resolve => setTimeout(() => resolve({ success: true }), 100))
  124. );
  125. render(
  126. <TestWrapper>
  127. <LoginForm onSubmit={slowLoginMock} />
  128. </TestWrapper>
  129. );
  130. await user.type(screen.getByTestId('email-input'), 'test@example.com');
  131. await user.type(screen.getByTestId('password-input'), 'password123');
  132. await user.click(screen.getByTestId('submit-button'));
  133. expect(screen.getByTestId('submit-button')).toBeDisabled();
  134. expect(screen.getByTestId('submit-button')).toHaveTextContent('Logging in...');
  135. await waitFor(() => {
  136. expect(screen.getByTestId('submit-button')).not.toBeDisabled();
  137. });
  138. });
  139. it('应该与React Router集成', () => {
  140. render(
  141. <TestQueryProvider>
  142. <TestRouter initialPath="/login">
  143. <LoginPage />
  144. </TestRouter>
  145. </TestQueryProvider>
  146. );
  147. expect(screen.getByRole('heading', { name: 'Login' })).toBeInTheDocument();
  148. expect(screen.getByTestId('login-form')).toBeInTheDocument();
  149. });
  150. it('应该处理表单验证错误', async () => {
  151. const errorMock = vi.fn().mockRejectedValue(new Error('Invalid credentials'));
  152. render(
  153. <TestWrapper>
  154. <LoginForm onSubmit={errorMock} />
  155. </TestWrapper>
  156. );
  157. // 不填写必填字段直接提交
  158. await user.click(screen.getByTestId('submit-button'));
  159. // 检查表单仍然可见(没有因为错误而崩溃)
  160. expect(screen.getByTestId('login-form')).toBeInTheDocument();
  161. });
  162. it('应该支持键盘导航和辅助功能', async () => {
  163. render(
  164. <TestWrapper>
  165. <LoginForm onSubmit={loginMock} />
  166. </TestWrapper>
  167. );
  168. const emailInput = screen.getByTestId('email-input');
  169. const passwordInput = screen.getByTestId('password-input');
  170. const submitButton = screen.getByTestId('submit-button');
  171. // Tab 导航
  172. await user.tab();
  173. expect(emailInput).toHaveFocus();
  174. await user.tab();
  175. expect(passwordInput).toHaveFocus();
  176. await user.tab();
  177. expect(submitButton).toHaveFocus();
  178. // Enter 提交
  179. await user.type(emailInput, 'test@example.com');
  180. await user.type(passwordInput, 'password123');
  181. await user.keyboard('{Enter}');
  182. await waitFor(() => {
  183. expect(loginMock).toHaveBeenCalled();
  184. });
  185. });
  186. });