disability.integration.test.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react';
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  4. import DisabilityManagement from '../../src/components/DisabilityManagement';
  5. import { disabilityClientManager } from '../../src/api/disabilityClient';
  6. // 完整的mock响应对象
  7. const createMockResponse = (status: number, data?: any) => ({
  8. status,
  9. ok: status >= 200 && status < 300,
  10. body: null,
  11. bodyUsed: false,
  12. statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
  13. headers: new Headers(),
  14. url: '',
  15. redirected: false,
  16. type: 'basic' as ResponseType,
  17. json: async () => data || {},
  18. text: async () => '',
  19. blob: async () => new Blob(),
  20. arrayBuffer: async () => new ArrayBuffer(0),
  21. formData: async () => new FormData(),
  22. clone: function() { return this; }
  23. });
  24. // Mock API client
  25. vi.mock('../../src/api/disabilityClient', () => {
  26. const mockDisabilityClient = {
  27. getAllDisabledPersons: {
  28. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  29. data: [
  30. {
  31. id: 1,
  32. name: '张三',
  33. gender: '男',
  34. idCard: '110101199001011234',
  35. disabilityId: 'CJZ20240001',
  36. disabilityType: '视力残疾',
  37. disabilityLevel: '一级',
  38. idAddress: '北京市东城区',
  39. phone: '13800138000',
  40. province: '北京市',
  41. city: '北京市',
  42. district: '东城区',
  43. createTime: '2024-01-01T00:00:00Z',
  44. updateTime: '2024-01-01T00:00:00Z'
  45. },
  46. {
  47. id: 2,
  48. name: '李四',
  49. gender: '女',
  50. idCard: '110101199002021234',
  51. disabilityId: 'CJZ20240002',
  52. disabilityType: '听力残疾',
  53. disabilityLevel: '二级',
  54. idAddress: '上海市黄浦区',
  55. phone: '13900139000',
  56. province: '上海市',
  57. city: '上海市',
  58. district: '黄浦区',
  59. createTime: '2024-01-02T00:00:00Z',
  60. updateTime: '2024-01-02T00:00:00Z'
  61. }
  62. ],
  63. total: 2
  64. }))),
  65. },
  66. searchDisabledPersons: {
  67. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  68. data: [
  69. {
  70. id: 1,
  71. name: '张三',
  72. gender: '男',
  73. idCard: '110101199001011234',
  74. disabilityId: 'CJZ20240001',
  75. disabilityType: '视力残疾',
  76. disabilityLevel: '一级',
  77. idAddress: '北京市东城区',
  78. phone: '13800138000',
  79. province: '北京市',
  80. city: '北京市',
  81. district: '东城区',
  82. createTime: '2024-01-01T00:00:00Z',
  83. updateTime: '2024-01-01T00:00:00Z'
  84. }
  85. ],
  86. total: 1
  87. }))),
  88. },
  89. createDisabledPerson: {
  90. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  91. id: 3,
  92. name: '王五',
  93. gender: '男',
  94. idCard: '110101199003031234',
  95. disabilityId: 'CJZ20240003',
  96. disabilityType: '肢体残疾',
  97. disabilityLevel: '三级',
  98. idAddress: '广州市天河区',
  99. phone: '13700137000',
  100. province: '广东省',
  101. city: '广州市',
  102. district: '天河区',
  103. createTime: '2024-01-03T00:00:00Z',
  104. updateTime: '2024-01-03T00:00:00Z'
  105. }))),
  106. },
  107. updateDisabledPerson: {
  108. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  109. id: 1,
  110. name: '张三(已更新)',
  111. gender: '男',
  112. idCard: '110101199001011234',
  113. disabilityId: 'CJZ20240001',
  114. disabilityType: '视力残疾',
  115. disabilityLevel: '一级',
  116. idAddress: '北京市东城区',
  117. phone: '13800138000',
  118. province: '北京市',
  119. city: '北京市',
  120. district: '东城区',
  121. createTime: '2024-01-01T00:00:00Z',
  122. updateTime: '2024-01-03T00:00:00Z'
  123. }))),
  124. },
  125. deleteDisabledPerson: {
  126. $post: vi.fn(() => Promise.resolve(createMockResponse(200, { success: true }))),
  127. },
  128. getDisabledPerson: {
  129. ':id': {
  130. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  131. id: 1,
  132. name: '张三',
  133. gender: '男',
  134. idCard: '110101199001011234',
  135. disabilityId: 'CJZ20240001',
  136. disabilityType: '视力残疾',
  137. disabilityLevel: '一级',
  138. idAddress: '北京市东城区',
  139. phone: '13800138000',
  140. province: '北京市',
  141. city: '北京市',
  142. district: '东城区',
  143. createTime: '2024-01-01T00:00:00Z',
  144. updateTime: '2024-01-01T00:00:00Z'
  145. }))),
  146. }
  147. },
  148. findByIdCard: {
  149. ':idCard': {
  150. $get: vi.fn(() => Promise.resolve(createMockResponse(200, null))),
  151. }
  152. },
  153. batchCreateDisabledPersons: {
  154. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  155. success: true,
  156. createdCount: 2,
  157. failedItems: []
  158. }))),
  159. }
  160. };
  161. const mockDisabilityClientManager = {
  162. get: vi.fn(() => mockDisabilityClient),
  163. init: vi.fn(() => mockDisabilityClient),
  164. reset: vi.fn(),
  165. };
  166. return {
  167. disabilityClientManager: mockDisabilityClientManager,
  168. disabilityClient: mockDisabilityClient,
  169. };
  170. });
  171. // Mock AreaSelect组件
  172. vi.mock('@d8d/area-management-ui', () => ({
  173. AreaSelect: ({ onChange, value, disabled, 'data-testid': testId }: any) => (
  174. <div data-testid={testId || 'area-select'}>
  175. <select
  176. data-testid="area-province-select"
  177. onChange={(e) => onChange && onChange({ provinceId: e.target.value ? 1 : undefined })}
  178. value={value?.provinceId || ''}
  179. disabled={disabled}
  180. >
  181. <option value="">选择省份</option>
  182. <option value="1">北京市</option>
  183. </select>
  184. <select
  185. data-testid="area-city-select"
  186. onChange={(e) => onChange && onChange({ provinceId: 1, cityId: e.target.value ? 2 : undefined })}
  187. value={value?.cityId || ''}
  188. disabled={disabled}
  189. >
  190. <option value="">选择城市</option>
  191. <option value="2">北京市</option>
  192. </select>
  193. <select
  194. data-testid="area-district-select"
  195. onChange={(e) => onChange && onChange({ provinceId: 1, cityId: 2, districtId: e.target.value ? 3 : undefined })}
  196. value={value?.districtId || ''}
  197. disabled={disabled}
  198. >
  199. <option value="">选择区县</option>
  200. <option value="3">东城区</option>
  201. </select>
  202. </div>
  203. ),
  204. }));
  205. describe('残疾人管理集成测试', () => {
  206. let queryClient: QueryClient;
  207. beforeEach(() => {
  208. queryClient = new QueryClient({
  209. defaultOptions: {
  210. queries: {
  211. retry: false,
  212. },
  213. },
  214. });
  215. vi.clearAllMocks();
  216. });
  217. const renderComponent = () => {
  218. return render(
  219. <QueryClientProvider client={queryClient}>
  220. <DisabilityManagement />
  221. </QueryClientProvider>
  222. );
  223. };
  224. it('应该正确渲染残疾人管理组件', async () => {
  225. renderComponent();
  226. // 验证标题
  227. expect(screen.getByText('残疾人管理')).toBeInTheDocument();
  228. expect(screen.getByText('管理残疾人信息,包括创建、更新、删除和查询功能')).toBeInTheDocument();
  229. // 验证搜索框
  230. expect(screen.getByTestId('search-input')).toBeInTheDocument();
  231. expect(screen.getByTestId('search-button')).toBeInTheDocument();
  232. // 验证新增按钮
  233. expect(screen.getByTestId('create-button')).toBeInTheDocument();
  234. // 等待数据加载
  235. await waitFor(() => {
  236. expect(screen.getByText('张三')).toBeInTheDocument();
  237. expect(screen.getByText('李四')).toBeInTheDocument();
  238. });
  239. // 验证表格列
  240. expect(screen.getByText('姓名')).toBeInTheDocument();
  241. expect(screen.getByText('性别')).toBeInTheDocument();
  242. expect(screen.getByText('身份证号')).toBeInTheDocument();
  243. expect(screen.getByText('残疾证号')).toBeInTheDocument();
  244. expect(screen.getByText('残疾类型')).toBeInTheDocument();
  245. expect(screen.getByText('联系电话')).toBeInTheDocument();
  246. expect(screen.getByText('创建时间')).toBeInTheDocument();
  247. expect(screen.getByText('操作')).toBeInTheDocument();
  248. });
  249. it('应该能够搜索残疾人', async () => {
  250. renderComponent();
  251. // 等待初始数据加载
  252. await waitFor(() => {
  253. expect(screen.getByText('张三')).toBeInTheDocument();
  254. });
  255. // 输入搜索关键词
  256. const searchInput = screen.getByTestId('search-input');
  257. fireEvent.change(searchInput, { target: { value: '张三' } });
  258. // 点击搜索按钮
  259. const searchButton = screen.getByTestId('search-button');
  260. fireEvent.click(searchButton);
  261. // 验证搜索功能被调用
  262. await waitFor(() => {
  263. expect(screen.getByText('张三')).toBeInTheDocument();
  264. expect(screen.queryByText('李四')).not.toBeInTheDocument();
  265. });
  266. });
  267. it('应该能够打开创建模态框并填写表单', async () => {
  268. renderComponent();
  269. // 等待初始数据加载
  270. await waitFor(() => {
  271. expect(screen.getByText('张三')).toBeInTheDocument();
  272. });
  273. // 点击新增按钮
  274. const createButton = screen.getByTestId('create-button');
  275. fireEvent.click(createButton);
  276. // 验证模态框打开 - 检查对话框标题
  277. await waitFor(() => {
  278. const elements = screen.getAllByText('新增残疾人');
  279. expect(elements.length).toBeGreaterThan(1); // 按钮和对话框标题
  280. });
  281. // 填写表单
  282. fireEvent.change(screen.getByTestId('name-input'), { target: { value: '王五' } });
  283. fireEvent.change(screen.getByTestId('gender-select'), { target: { value: '男' } });
  284. fireEvent.change(screen.getByTestId('id-card-input'), { target: { value: '110101199003031234' } });
  285. fireEvent.change(screen.getByTestId('disability-id-input'), { target: { value: 'CJZ20240003' } });
  286. fireEvent.change(screen.getByTestId('disability-type-input'), { target: { value: '肢体残疾' } });
  287. fireEvent.change(screen.getByTestId('disability-level-input'), { target: { value: '三级' } });
  288. fireEvent.change(screen.getByTestId('phone-input'), { target: { value: '13700137000' } });
  289. fireEvent.change(screen.getByTestId('id-address-input'), { target: { value: '广州市天河区' } });
  290. // 选择地区
  291. fireEvent.change(screen.getByTestId('area-province-select'), { target: { value: '1' } });
  292. fireEvent.change(screen.getByTestId('area-city-select'), { target: { value: '2' } });
  293. fireEvent.change(screen.getByTestId('area-district-select'), { target: { value: '3' } });
  294. // 验证表单字段
  295. expect(screen.getByTestId('name-input')).toHaveValue('王五');
  296. expect(screen.getByTestId('gender-select')).toHaveValue('男');
  297. expect(screen.getByTestId('id-card-input')).toHaveValue('110101199003031234');
  298. });
  299. it('应该能够编辑残疾人信息', async () => {
  300. renderComponent();
  301. // 等待初始数据加载
  302. await waitFor(() => {
  303. expect(screen.getByText('张三')).toBeInTheDocument();
  304. });
  305. // 点击编辑按钮
  306. const editButton = screen.getByTestId('edit-button-1');
  307. fireEvent.click(editButton);
  308. // 验证编辑模态框打开
  309. await waitFor(() => {
  310. expect(screen.getByText('编辑残疾人')).toBeInTheDocument();
  311. });
  312. // 验证表单已填充数据
  313. expect(screen.getByTestId('update-name-input')).toHaveValue('张三');
  314. expect(screen.getByTestId('update-gender-select')).toHaveValue('男');
  315. expect(screen.getByTestId('update-id-card-input')).toHaveValue('110101199001011234');
  316. // 修改姓名
  317. fireEvent.change(screen.getByTestId('update-name-input'), { target: { value: '张三(已更新)' } });
  318. // 点击更新按钮
  319. const updateButton = screen.getByTestId('update-submit-button');
  320. fireEvent.click(updateButton);
  321. // 验证更新API被调用
  322. await waitFor(() => {
  323. expect(screen.queryByText('编辑残疾人')).not.toBeInTheDocument();
  324. });
  325. });
  326. it('应该能够删除残疾人', async () => {
  327. renderComponent();
  328. // 等待初始数据加载
  329. await waitFor(() => {
  330. expect(screen.getByText('张三')).toBeInTheDocument();
  331. });
  332. // 点击删除按钮
  333. const deleteButton = screen.getByTestId('delete-button-1');
  334. fireEvent.click(deleteButton);
  335. // 验证删除确认对话框打开
  336. await waitFor(() => {
  337. const elements = screen.getAllByText('确认删除');
  338. expect(elements.length).toBeGreaterThan(1); // 对话框标题和按钮
  339. });
  340. // 点击确认删除按钮
  341. const confirmButton = screen.getByTestId('confirm-delete-button');
  342. fireEvent.click(confirmButton);
  343. // 验证删除API被调用
  344. await waitFor(() => {
  345. expect(screen.queryByText('确认删除')).not.toBeInTheDocument();
  346. });
  347. });
  348. it('应该处理表单验证错误', async () => {
  349. renderComponent();
  350. // 等待初始数据加载
  351. await waitFor(() => {
  352. expect(screen.getByText('张三')).toBeInTheDocument();
  353. });
  354. // 点击新增按钮
  355. const createButton = screen.getByTestId('create-button');
  356. fireEvent.click(createButton);
  357. // 验证模态框打开 - 检查对话框标题
  358. await waitFor(() => {
  359. const elements = screen.getAllByText('新增残疾人');
  360. expect(elements.length).toBeGreaterThan(1); // 按钮和对话框标题
  361. });
  362. // 尝试提交空表单
  363. const submitButton = screen.getByTestId('create-submit-button');
  364. fireEvent.click(submitButton);
  365. // 验证验证错误信息
  366. await waitFor(() => {
  367. expect(screen.getByText('姓名不能为空')).toBeInTheDocument();
  368. expect(screen.getByText('身份证号不能为空')).toBeInTheDocument();
  369. expect(screen.getByText('残疾证号不能为空')).toBeInTheDocument();
  370. });
  371. });
  372. it('应该处理API错误', async () => {
  373. // Mock API错误 - 使用mockImplementationOnce
  374. const mockCreate = disabilityClientManager.get().createDisabledPerson.$post as any;
  375. mockCreate.mockImplementationOnce(() => Promise.reject(new Error('创建失败')));
  376. renderComponent();
  377. // 等待初始数据加载
  378. await waitFor(() => {
  379. expect(screen.getByText('张三')).toBeInTheDocument();
  380. });
  381. // 打开创建模态框
  382. const createButton = screen.getByTestId('create-button');
  383. fireEvent.click(createButton);
  384. // 等待模态框打开 - 检查对话框标题
  385. await waitFor(() => {
  386. const dialogTitles = screen.getAllByText('新增残疾人');
  387. expect(dialogTitles.length).toBeGreaterThan(1); // 按钮和对话框标题
  388. });
  389. fireEvent.change(screen.getByTestId('name-input'), { target: { value: '测试用户' } });
  390. fireEvent.change(screen.getByTestId('gender-select'), { target: { value: '男' } });
  391. fireEvent.change(screen.getByTestId('id-card-input'), { target: { value: '110101199001011234' } });
  392. fireEvent.change(screen.getByTestId('disability-id-input'), { target: { value: 'CJZ20240001' } });
  393. fireEvent.change(screen.getByTestId('disability-type-input'), { target: { value: '视力残疾' } });
  394. fireEvent.change(screen.getByTestId('disability-level-input'), { target: { value: '一级' } });
  395. fireEvent.change(screen.getByTestId('phone-input'), { target: { value: '13800138000' } });
  396. fireEvent.change(screen.getByTestId('id-address-input'), { target: { value: '北京市东城区' } });
  397. // 提交表单
  398. const submitButton = screen.getByTestId('create-submit-button');
  399. fireEvent.click(submitButton);
  400. // 验证错误处理(toast错误消息由sonner处理,这里我们验证表单没有关闭)
  401. await waitFor(() => {
  402. const elements = screen.getAllByText('新增残疾人');
  403. expect(elements.length).toBeGreaterThan(1); // 按钮和对话框标题
  404. });
  405. });
  406. });