disability-person.integration.test.tsx 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  4. import DisabilityPersonManagement from '../../src/components/DisabilityPersonManagement';
  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: 'D123456789',
  36. disabilityType: '肢体残疾',
  37. disabilityLevel: '一级',
  38. idAddress: '北京市东城区',
  39. phone: '13800138000',
  40. province: '北京市',
  41. city: '北京市',
  42. district: '东城区',
  43. detailedAddress: '某街道某号',
  44. nation: '汉族',
  45. isMarried: 0,
  46. canDirectContact: 1,
  47. isInBlackList: 0,
  48. jobStatus: 1,
  49. createTime: '2024-01-01T00:00:00Z',
  50. updateTime: '2024-01-01T00:00:00Z'
  51. }
  52. ],
  53. total: 1
  54. }))),
  55. },
  56. createDisabledPerson: {
  57. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  58. id: 2,
  59. name: '李四',
  60. gender: '女',
  61. idCard: '110101199002021234',
  62. disabilityId: 'D123456790',
  63. disabilityType: '视力残疾',
  64. disabilityLevel: '二级',
  65. idAddress: '上海市黄浦区',
  66. phone: '13900139000',
  67. province: '上海市',
  68. city: '上海市',
  69. district: '黄浦区',
  70. detailedAddress: '某街道某号',
  71. nation: '汉族',
  72. isMarried: 1,
  73. canDirectContact: 1,
  74. isInBlackList: 0,
  75. jobStatus: 0,
  76. createTime: '2024-01-02T00:00:00Z',
  77. updateTime: '2024-01-02T00:00:00Z'
  78. }))),
  79. },
  80. updateDisabledPerson: {
  81. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  82. id: 1,
  83. name: '张三(已更新)',
  84. gender: '男',
  85. idCard: '110101199001011234',
  86. disabilityId: 'D123456789',
  87. disabilityType: '肢体残疾',
  88. disabilityLevel: '一级',
  89. idAddress: '北京市东城区',
  90. phone: '13800138001',
  91. province: '北京市',
  92. city: '北京市',
  93. district: '东城区',
  94. detailedAddress: '更新后的地址',
  95. nation: '汉族',
  96. isMarried: 1,
  97. canDirectContact: 1,
  98. isInBlackList: 0,
  99. jobStatus: 1,
  100. createTime: '2024-01-01T00:00:00Z',
  101. updateTime: '2024-01-03T00:00:00Z'
  102. }))),
  103. },
  104. deleteDisabledPerson: {
  105. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  106. success: true
  107. }))),
  108. },
  109. searchDisabledPersons: {
  110. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  111. data: [
  112. {
  113. id: 1,
  114. name: '张三',
  115. gender: '男',
  116. idCard: '110101199001011234',
  117. disabilityId: 'D123456789',
  118. disabilityType: '肢体残疾',
  119. disabilityLevel: '一级',
  120. idAddress: '北京市东城区',
  121. phone: '13800138000',
  122. province: '北京市',
  123. city: '北京市',
  124. district: '东城区',
  125. detailedAddress: '某街道某号',
  126. nation: '汉族',
  127. isMarried: 0,
  128. canDirectContact: 1,
  129. isInBlackList: 0,
  130. jobStatus: 1,
  131. createTime: '2024-01-01T00:00:00Z',
  132. updateTime: '2024-01-01T00:00:00Z'
  133. }
  134. ],
  135. total: 1
  136. }))),
  137. },
  138. getDisabledPerson: {
  139. ':id': {
  140. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  141. id: 1,
  142. name: '张三',
  143. gender: '男',
  144. idCard: '110101199001011234',
  145. disabilityId: 'D123456789',
  146. disabilityType: '肢体残疾',
  147. disabilityLevel: '一级',
  148. idAddress: '北京市东城区',
  149. phone: '13800138000',
  150. province: '北京市',
  151. city: '北京市',
  152. district: '东城区',
  153. detailedAddress: '某街道某号',
  154. nation: '汉族',
  155. isMarried: 0,
  156. canDirectContact: 1,
  157. isInBlackList: 0,
  158. jobStatus: 1,
  159. createTime: '2024-01-01T00:00:00Z',
  160. updateTime: '2024-01-01T00:00:00Z'
  161. }))),
  162. },
  163. },
  164. };
  165. const mockDisabilityClientManager = {
  166. get: vi.fn(() => mockDisabilityClient),
  167. };
  168. return {
  169. disabilityClientManager: mockDisabilityClientManager,
  170. disabilityClient: mockDisabilityClient,
  171. };
  172. });
  173. // Mock toast
  174. vi.mock('sonner', () => ({
  175. toast: {
  176. success: vi.fn(),
  177. error: vi.fn(),
  178. },
  179. }));
  180. // Mock 枚举包
  181. vi.mock('@d8d/allin-enums', () => ({
  182. DisabilityType: {
  183. VISION: 'vision',
  184. HEARING: 'hearing',
  185. SPEECH: 'speech',
  186. PHYSICAL: 'physical',
  187. INTELLECTUAL: 'intellectual',
  188. MENTAL: 'mental',
  189. MULTIPLE: 'multiple',
  190. },
  191. DISABILITY_TYPES: ['vision', 'hearing', 'speech', 'physical', 'intellectual', 'mental', 'multiple'],
  192. getDisabilityTypeLabel: vi.fn((type) => {
  193. const labels: Record<string, string> = {
  194. vision: '视力残疾',
  195. hearing: '听力残疾',
  196. speech: '言语残疾',
  197. physical: '肢体残疾',
  198. intellectual: '智力残疾',
  199. mental: '精神残疾',
  200. multiple: '多重残疾',
  201. };
  202. return labels[type] || type;
  203. }),
  204. DisabilityLevel: {
  205. ONE: 1,
  206. TWO: 2,
  207. THREE: 3,
  208. FOUR: 4,
  209. },
  210. DISABILITY_LEVELS: [1, 2, 3, 4],
  211. getDisabilityLevelLabel: vi.fn((level) => {
  212. const labels: Record<number, string> = {
  213. 1: '一级',
  214. 2: '二级',
  215. 3: '三级',
  216. 4: '四级',
  217. };
  218. return labels[level] || level.toString();
  219. }),
  220. }));
  221. // Mock 区域选择器组件
  222. vi.mock('@d8d/area-management-ui/components', () => ({
  223. AreaSelect: vi.fn(({ value, onChange }) => (
  224. <div data-testid="area-select">
  225. <select
  226. data-testid="province-select"
  227. value={value?.provinceId || ''}
  228. onChange={(e) => onChange({ ...value, provinceId: e.target.value ? Number(e.target.value) : undefined })}
  229. >
  230. <option value="">选择省份</option>
  231. <option value="1">北京市</option>
  232. </select>
  233. <select
  234. data-testid="city-select"
  235. value={value?.cityId || ''}
  236. onChange={(e) => onChange({ ...value, cityId: e.target.value ? Number(e.target.value) : undefined })}
  237. >
  238. <option value="">选择城市</option>
  239. <option value="2">北京市</option>
  240. </select>
  241. <select
  242. data-testid="district-select"
  243. value={value?.districtId || ''}
  244. onChange={(e) => onChange({ ...value, districtId: e.target.value ? Number(e.target.value) : undefined })}
  245. >
  246. <option value="">选择区县</option>
  247. <option value="3">东城区</option>
  248. </select>
  249. </div>
  250. )),
  251. }));
  252. // Mock 文件选择器组件
  253. vi.mock('@d8d/file-management-ui/components', () => ({
  254. FileSelector: vi.fn(({ onChange, placeholder }) => (
  255. <div data-testid="file-selector">
  256. <button
  257. data-testid="file-selector-button"
  258. onClick={() => onChange && onChange(1)}
  259. >
  260. {placeholder || '选择文件'}
  261. </button>
  262. </div>
  263. )),
  264. }));
  265. describe('残疾人个人管理集成测试', () => {
  266. let queryClient: QueryClient;
  267. beforeEach(() => {
  268. queryClient = new QueryClient({
  269. defaultOptions: {
  270. queries: {
  271. retry: false,
  272. },
  273. },
  274. });
  275. vi.clearAllMocks();
  276. });
  277. const renderComponent = () => {
  278. return render(
  279. <QueryClientProvider client={queryClient}>
  280. <DisabilityPersonManagement />
  281. </QueryClientProvider>
  282. );
  283. };
  284. it('应该正确渲染残疾人列表', async () => {
  285. renderComponent();
  286. // 等待数据加载
  287. await waitFor(() => {
  288. expect(screen.getByText('张三')).toBeInTheDocument();
  289. });
  290. // 验证表格内容
  291. expect(screen.getByText('张三')).toBeInTheDocument();
  292. expect(screen.getByText('男')).toBeInTheDocument();
  293. expect(screen.getByText('110101199001011234')).toBeInTheDocument();
  294. expect(screen.getByText('D123456789')).toBeInTheDocument();
  295. expect(screen.getByText('肢体残疾')).toBeInTheDocument();
  296. expect(screen.getByText('一级')).toBeInTheDocument();
  297. expect(screen.getByText('13800138000')).toBeInTheDocument();
  298. });
  299. it('应该打开创建模态框并填写表单', async () => {
  300. renderComponent();
  301. // 等待数据加载
  302. await waitFor(() => {
  303. expect(screen.getByText('张三')).toBeInTheDocument();
  304. });
  305. // 点击新增按钮 - 使用测试ID
  306. const addButton = screen.getByTestId('add-disabled-person-button');
  307. fireEvent.click(addButton);
  308. // 验证模态框打开 - 使用测试ID
  309. await waitFor(() => {
  310. expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
  311. });
  312. // 填写表单
  313. const nameInput = screen.getByPlaceholderText('请输入姓名');
  314. const idCardInput = screen.getByPlaceholderText('请输入身份证号');
  315. const disabilityIdInput = screen.getByPlaceholderText('请输入残疾证号');
  316. const phoneInput = screen.getByPlaceholderText('请输入联系电话');
  317. const idAddressInput = screen.getByPlaceholderText('请输入身份证地址');
  318. fireEvent.change(nameInput, { target: { value: '李四' } });
  319. fireEvent.change(idCardInput, { target: { value: '110101199002021234' } });
  320. fireEvent.change(disabilityIdInput, { target: { value: 'D123456790' } });
  321. fireEvent.change(phoneInput, { target: { value: '13900139000' } });
  322. fireEvent.change(idAddressInput, { target: { value: '上海市黄浦区' } });
  323. // 选择性别
  324. const genderSelect = screen.getByTestId('gender-select');
  325. fireEvent.change(genderSelect, { target: { value: '女' } });
  326. // 选择残疾类型
  327. const disabilityTypeSelect = screen.getByTestId('disability-type-select');
  328. fireEvent.change(disabilityTypeSelect, { target: { value: '视力残疾' } });
  329. // 选择残疾等级
  330. const disabilityLevelSelect = screen.getByTestId('disability-level-select');
  331. fireEvent.change(disabilityLevelSelect, { target: { value: '二级' } });
  332. // 选择省份和城市
  333. const provinceSelect = screen.getByTestId('province-select');
  334. const citySelect = screen.getByTestId('city-select');
  335. await act(async () => {
  336. fireEvent.change(provinceSelect, { target: { value: '1' } });
  337. });
  338. await act(async () => {
  339. fireEvent.change(citySelect, { target: { value: '2' } });
  340. });
  341. // 提交表单
  342. const submitButton = screen.getByText('创建');
  343. // 使用act包装状态更新
  344. await act(async () => {
  345. fireEvent.click(submitButton);
  346. });
  347. // 验证API调用 - 增加等待时间
  348. await waitFor(() => {
  349. const mockClient = (disabilityClientManager.get as any)();
  350. expect(mockClient.createDisabledPerson.$post).toHaveBeenCalled();
  351. }, { timeout: 3000 });
  352. // 验证具体的调用参数
  353. const mockClient = (disabilityClientManager.get as any)();
  354. const call = mockClient.createDisabledPerson.$post.mock.calls[0];
  355. expect(call).toBeDefined();
  356. if (call) {
  357. expect(call[0].json).toMatchObject({
  358. name: '李四',
  359. idCard: '110101199002021234',
  360. disabilityId: 'D123456790',
  361. phone: '13900139000',
  362. idAddress: '上海市黄浦区',
  363. });
  364. }
  365. });
  366. it('应该打开编辑模态框并更新数据', async () => {
  367. renderComponent();
  368. // 等待数据加载
  369. await waitFor(() => {
  370. expect(screen.getByText('张三')).toBeInTheDocument();
  371. });
  372. // 点击编辑按钮
  373. const editButton = screen.getByTestId('edit-person-1');
  374. fireEvent.click(editButton);
  375. // 验证模态框打开
  376. await waitFor(() => {
  377. expect(screen.getByText('编辑残疾人信息')).toBeInTheDocument();
  378. });
  379. // 修改电话号码
  380. const phoneInput = screen.getByPlaceholderText('请输入联系电话');
  381. fireEvent.change(phoneInput, { target: { value: '13800138001' } });
  382. // 提交表单
  383. const submitButton = screen.getByText('更新');
  384. fireEvent.click(submitButton);
  385. // 验证API调用
  386. await waitFor(() => {
  387. const mockClient = (disabilityClientManager.get as any)();
  388. expect(mockClient.updateDisabledPerson.$post).toHaveBeenCalledWith({
  389. json: expect.objectContaining({
  390. id: 1,
  391. phone: '13800138001',
  392. }),
  393. });
  394. });
  395. });
  396. it('应该打开查看详情模态框', async () => {
  397. renderComponent();
  398. // 等待数据加载
  399. await waitFor(() => {
  400. expect(screen.getByText('张三')).toBeInTheDocument();
  401. });
  402. // 点击查看按钮
  403. const viewButton = screen.getByTestId('view-person-1');
  404. fireEvent.click(viewButton);
  405. // 验证模态框打开并显示详情
  406. await waitFor(() => {
  407. expect(screen.getByText('残疾人详情')).toBeInTheDocument();
  408. expect(screen.getByText('张三')).toBeInTheDocument();
  409. expect(screen.getByText('110101199001011234')).toBeInTheDocument();
  410. expect(screen.getByText('D123456789')).toBeInTheDocument();
  411. });
  412. });
  413. it('应该打开删除确认对话框并删除数据', async () => {
  414. renderComponent();
  415. // 等待数据加载
  416. await waitFor(() => {
  417. expect(screen.getByText('张三')).toBeInTheDocument();
  418. });
  419. // 点击删除按钮
  420. const deleteButton = screen.getByTestId('delete-person-1');
  421. fireEvent.click(deleteButton);
  422. // 验证删除对话框打开 - 使用测试ID
  423. await waitFor(() => {
  424. expect(screen.getByTestId('delete-confirmation-dialog-title')).toBeInTheDocument();
  425. });
  426. // 确认删除 - 使用更精确的选择器
  427. const confirmButtons = screen.getAllByText('确认删除');
  428. const confirmButton = confirmButtons.find(btn => btn.getAttribute('type') === 'button' || btn.getAttribute('data-slot') === 'button') || confirmButtons[0];
  429. fireEvent.click(confirmButton);
  430. // 验证API调用
  431. await waitFor(() => {
  432. const mockClient = (disabilityClientManager.get as any)();
  433. expect(mockClient.deleteDisabledPerson.$post).toHaveBeenCalledWith({
  434. json: { id: 1 },
  435. });
  436. });
  437. });
  438. it('应该进行搜索操作', async () => {
  439. renderComponent();
  440. // 等待数据加载
  441. await waitFor(() => {
  442. expect(screen.getByText('张三')).toBeInTheDocument();
  443. });
  444. // 输入搜索关键词
  445. const searchInput = screen.getByPlaceholderText('搜索姓名或身份证号');
  446. fireEvent.change(searchInput, { target: { value: '张三' } });
  447. // 点击搜索按钮
  448. const searchButton = screen.getByText('搜索');
  449. fireEvent.click(searchButton);
  450. // 验证API调用
  451. await waitFor(() => {
  452. const mockClient = (disabilityClientManager.get as any)();
  453. expect(mockClient.getAllDisabledPersons.$get).toHaveBeenCalled();
  454. });
  455. });
  456. it('应该测试区域选择器集成', async () => {
  457. renderComponent();
  458. // 等待数据加载
  459. await waitFor(() => {
  460. expect(screen.getByText('张三')).toBeInTheDocument();
  461. });
  462. // 打开创建模态框 - 使用测试ID
  463. const addButton = screen.getByTestId('add-disabled-person-button');
  464. fireEvent.click(addButton);
  465. // 等待模态框打开 - 使用测试ID
  466. await waitFor(() => {
  467. expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
  468. });
  469. // 验证区域选择器存在
  470. expect(screen.getByTestId('area-select')).toBeInTheDocument();
  471. // 选择省份
  472. const provinceSelect = screen.getByTestId('province-select');
  473. fireEvent.change(provinceSelect, { target: { value: '1' } });
  474. // 选择城市
  475. const citySelect = screen.getByTestId('city-select');
  476. fireEvent.change(citySelect, { target: { value: '2' } });
  477. // 选择区县
  478. const districtSelect = screen.getByTestId('district-select');
  479. fireEvent.change(districtSelect, { target: { value: '3' } });
  480. });
  481. it('应该测试文件选择器集成', async () => {
  482. renderComponent();
  483. // 等待数据加载
  484. await waitFor(() => {
  485. expect(screen.getByText('张三')).toBeInTheDocument();
  486. });
  487. // 打开创建模态框 - 使用测试ID
  488. const addButton = screen.getByTestId('add-disabled-person-button');
  489. fireEvent.click(addButton);
  490. // 等待模态框打开 - 使用测试ID
  491. await waitFor(() => {
  492. expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
  493. });
  494. // 验证照片上传组件存在
  495. expect(screen.getByTestId('add-photo-button')).toBeInTheDocument();
  496. // 点击添加照片按钮
  497. const addPhotoButton = screen.getByTestId('add-photo-button');
  498. fireEvent.click(addPhotoButton);
  499. });
  500. it('应该测试枚举选择器集成', async () => {
  501. renderComponent();
  502. // 等待数据加载
  503. await waitFor(() => {
  504. expect(screen.getByText('张三')).toBeInTheDocument();
  505. });
  506. // 打开创建模态框 - 使用测试ID
  507. const addButton = screen.getByTestId('add-disabled-person-button');
  508. fireEvent.click(addButton);
  509. // 等待模态框打开 - 使用测试ID
  510. await waitFor(() => {
  511. expect(screen.getByTestId('create-disabled-person-dialog-title')).toBeInTheDocument();
  512. });
  513. // 验证残疾类型选择器 - 使用更稳健的选择器
  514. const disabilityTypeSelects = screen.getAllByRole('combobox');
  515. // 查找包含"残疾类型"标签的选择器
  516. const disabilityTypeSelect = disabilityTypeSelects.find(select => {
  517. const label = select.closest('.grid-cols-2')?.querySelector('label');
  518. return label?.textContent?.includes('残疾类型');
  519. });
  520. if (disabilityTypeSelect) {
  521. expect(disabilityTypeSelect).toBeInTheDocument();
  522. }
  523. // 验证残疾等级选择器
  524. const disabilityLevelSelect = disabilityTypeSelects.find(select => {
  525. const label = select.closest('.grid-cols-2')?.querySelector('label');
  526. return label?.textContent?.includes('残疾等级');
  527. });
  528. if (disabilityLevelSelect) {
  529. expect(disabilityLevelSelect).toBeInTheDocument();
  530. }
  531. });
  532. it('应该显示暂无数据提示', async () => {
  533. // 修改mock返回空数据
  534. const mockClient = (disabilityClientManager.get as any)();
  535. mockClient.getAllDisabledPersons.$get.mockResolvedValueOnce(
  536. createMockResponse(200, {
  537. data: [],
  538. total: 0
  539. })
  540. );
  541. renderComponent();
  542. // 等待暂无数据提示出现
  543. await waitFor(() => {
  544. expect(screen.getByText('暂无数据')).toBeInTheDocument();
  545. });
  546. // 验证暂无数据提示的样式
  547. const noDataElement = screen.getByText('暂无数据');
  548. expect(noDataElement).toHaveClass('text-muted-foreground');
  549. });
  550. });