area-select-form.integration.test.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
  3. import { useForm, FormProvider } from 'react-hook-form';
  4. import { zodResolver } from '@hookform/resolvers/zod';
  5. import { z } from 'zod';
  6. import { AreaSelectForm } from '../../src/components/AreaSelectForm';
  7. import { Button } from '@d8d/shared-ui-components/components/ui/button';
  8. import { Form } from '@d8d/shared-ui-components/components/ui/form';
  9. // 测试用的schema
  10. const TestSchema = z.object({
  11. province: z.string().min(1, '省份不能为空'),
  12. city: z.string().min(1, '城市不能为空'),
  13. district: z.string().optional(),
  14. });
  15. type TestFormData = z.infer<typeof TestSchema>;
  16. // Mock AreaSelect 组件
  17. vi.mock('../../src/components/AreaSelect', () => ({
  18. AreaSelect: vi.fn(({ value, onChange, disabled, required }) => {
  19. return (
  20. <div data-testid="area-select">
  21. <select
  22. data-testid="province-select"
  23. value={value?.provinceId || ''}
  24. onChange={(e) => {
  25. const newValue = {
  26. ...value,
  27. provinceId: e.target.value ? Number(e.target.value) : undefined,
  28. };
  29. onChange(newValue);
  30. }}
  31. disabled={disabled}
  32. // 移除 required 属性,让 react-hook-form 处理验证
  33. >
  34. <option value="">选择省份</option>
  35. <option value="1">北京市</option>
  36. <option value="2">上海市</option>
  37. </select>
  38. <select
  39. data-testid="city-select"
  40. value={value?.cityId || ''}
  41. onChange={(e) => {
  42. const newValue = {
  43. ...value,
  44. cityId: e.target.value ? Number(e.target.value) : undefined,
  45. };
  46. onChange(newValue);
  47. }}
  48. disabled={disabled}
  49. // 移除 required 属性,让 react-hook-form 处理验证
  50. >
  51. <option value="">选择城市</option>
  52. <option value="3">北京市</option>
  53. <option value="4">上海市</option>
  54. </select>
  55. <select
  56. data-testid="district-select"
  57. value={value?.districtId || ''}
  58. onChange={(e) => {
  59. const newValue = {
  60. ...value,
  61. districtId: e.target.value ? Number(e.target.value) : undefined,
  62. };
  63. onChange(newValue);
  64. }}
  65. disabled={disabled}
  66. >
  67. <option value="">选择区县</option>
  68. <option value="5">东城区</option>
  69. <option value="6">黄浦区</option>
  70. </select>
  71. </div>
  72. );
  73. }),
  74. }));
  75. // 测试组件
  76. const TestForm = () => {
  77. const form = useForm<TestFormData>({
  78. resolver: zodResolver(TestSchema),
  79. defaultValues: {
  80. province: '',
  81. city: '',
  82. district: '',
  83. },
  84. mode: 'onSubmit', // 使用onSubmit模式,这是默认值
  85. });
  86. const onSubmit = vi.fn();
  87. return (
  88. <FormProvider {...form}>
  89. <Form {...form}>
  90. <form onSubmit={form.handleSubmit(onSubmit)}>
  91. <AreaSelectForm<TestFormData>
  92. provinceName="province"
  93. cityName="city"
  94. districtName="district"
  95. label="地区选择"
  96. required={true}
  97. control={form.control}
  98. />
  99. <Button type="submit" data-testid="submit-button">
  100. 提交
  101. </Button>
  102. </form>
  103. </Form>
  104. </FormProvider>
  105. );
  106. };
  107. describe('AreaSelectForm 集成测试', () => {
  108. beforeEach(() => {
  109. vi.clearAllMocks();
  110. });
  111. it('应该正确渲染AreaSelectForm组件', () => {
  112. render(<TestForm />);
  113. expect(screen.getByText('地区选择')).toBeInTheDocument();
  114. // 检查是否包含必填标记(星号)
  115. const label = screen.getByText('地区选择');
  116. expect(label.parentElement).toContainHTML('*');
  117. expect(screen.getByTestId('area-select')).toBeInTheDocument();
  118. });
  119. it('应该显示省份和城市的验证错误当表单提交时', async () => {
  120. let formRef: any;
  121. const TestFormWithRef = () => {
  122. const form = useForm<TestFormData>({
  123. resolver: zodResolver(TestSchema),
  124. defaultValues: {
  125. province: '',
  126. city: '',
  127. district: '',
  128. },
  129. mode: 'onSubmit',
  130. });
  131. formRef = form;
  132. const onSubmit = vi.fn();
  133. return (
  134. <FormProvider {...form}>
  135. <Form {...form}>
  136. <form onSubmit={form.handleSubmit(onSubmit)}>
  137. <AreaSelectForm<TestFormData>
  138. provinceName="province"
  139. cityName="city"
  140. districtName="district"
  141. label="地区选择"
  142. required={true}
  143. control={form.control}
  144. />
  145. <Button type="submit" data-testid="submit-button">
  146. 提交
  147. </Button>
  148. </form>
  149. </Form>
  150. </FormProvider>
  151. );
  152. };
  153. render(<TestFormWithRef />);
  154. // 初始状态不应该有验证错误
  155. expect(screen.queryByText('省份不能为空')).not.toBeInTheDocument();
  156. expect(screen.queryByText('城市不能为空')).not.toBeInTheDocument();
  157. // 提交表单(不选择任何省份和城市)
  158. const submitButton = screen.getByTestId('submit-button');
  159. await act(async () => {
  160. fireEvent.click(submitButton);
  161. });
  162. // 等待一下,让验证完成
  163. await new Promise(resolve => setTimeout(resolve, 100));
  164. // 等待验证错误显示
  165. await waitFor(() => {
  166. expect(screen.getByText('省份不能为空')).toBeInTheDocument();
  167. expect(screen.getByText('城市不能为空')).toBeInTheDocument();
  168. }, { timeout: 3000 });
  169. });
  170. it('应该正确更新表单字段值当选择省份和城市时', async () => {
  171. render(<TestForm />);
  172. // 选择省份
  173. const provinceSelect = screen.getByTestId('province-select');
  174. fireEvent.change(provinceSelect, { target: { value: '1' } });
  175. // 选择城市
  176. const citySelect = screen.getByTestId('city-select');
  177. fireEvent.change(citySelect, { target: { value: '3' } });
  178. // 提交表单
  179. const submitButton = screen.getByTestId('submit-button');
  180. fireEvent.click(submitButton);
  181. // 不应该有验证错误
  182. await waitFor(() => {
  183. expect(screen.queryByText('省份不能为空')).not.toBeInTheDocument();
  184. expect(screen.queryByText('城市不能为空')).not.toBeInTheDocument();
  185. });
  186. });
  187. it('应该显示验证错误当只选择省份不选择城市时', async () => {
  188. render(<TestForm />);
  189. // 只选择省份
  190. const provinceSelect = screen.getByTestId('province-select');
  191. fireEvent.change(provinceSelect, { target: { value: '1' } });
  192. // 提交表单
  193. const submitButton = screen.getByTestId('submit-button');
  194. fireEvent.click(submitButton);
  195. // 应该只有城市验证错误
  196. await waitFor(() => {
  197. expect(screen.queryByText('省份不能为空')).not.toBeInTheDocument();
  198. expect(screen.getByText('城市不能为空')).toBeInTheDocument();
  199. });
  200. });
  201. it('应该显示验证错误当只选择城市不选择省份时', async () => {
  202. render(<TestForm />);
  203. // 只选择城市
  204. const citySelect = screen.getByTestId('city-select');
  205. fireEvent.change(citySelect, { target: { value: '3' } });
  206. // 提交表单
  207. const submitButton = screen.getByTestId('submit-button');
  208. fireEvent.click(submitButton);
  209. // 应该只有省份验证错误
  210. await waitFor(() => {
  211. expect(screen.getByText('省份不能为空')).toBeInTheDocument();
  212. expect(screen.queryByText('城市不能为空')).not.toBeInTheDocument();
  213. });
  214. });
  215. it('应该正确处理区县字段(可选)', async () => {
  216. render(<TestForm />);
  217. // 选择省份和城市
  218. const provinceSelect = screen.getByTestId('province-select');
  219. const citySelect = screen.getByTestId('city-select');
  220. fireEvent.change(provinceSelect, { target: { value: '1' } });
  221. fireEvent.change(citySelect, { target: { value: '3' } });
  222. // 选择区县(可选)
  223. const districtSelect = screen.getByTestId('district-select');
  224. fireEvent.change(districtSelect, { target: { value: '5' } });
  225. // 提交表单
  226. const submitButton = screen.getByTestId('submit-button');
  227. fireEvent.click(submitButton);
  228. // 不应该有验证错误
  229. await waitFor(() => {
  230. expect(screen.queryByText('省份不能为空')).not.toBeInTheDocument();
  231. expect(screen.queryByText('城市不能为空')).not.toBeInTheDocument();
  232. });
  233. });
  234. it('应该支持禁用状态', () => {
  235. const DisabledTestForm = () => {
  236. const form = useForm<TestFormData>({
  237. resolver: zodResolver(TestSchema),
  238. defaultValues: {
  239. province: '',
  240. city: '',
  241. district: '',
  242. },
  243. });
  244. return (
  245. <FormProvider {...form}>
  246. <Form {...form}>
  247. <AreaSelectForm<TestFormData>
  248. provinceName="province"
  249. cityName="city"
  250. districtName="district"
  251. label="地区选择"
  252. required={true}
  253. disabled={true}
  254. control={form.control}
  255. />
  256. </Form>
  257. </FormProvider>
  258. );
  259. };
  260. render(<DisabledTestForm />);
  261. const provinceSelect = screen.getByTestId('province-select');
  262. const citySelect = screen.getByTestId('city-select');
  263. const districtSelect = screen.getByTestId('district-select');
  264. expect(provinceSelect).toBeDisabled();
  265. expect(citySelect).toBeDisabled();
  266. expect(districtSelect).toBeDisabled();
  267. });
  268. });