company.integration.test.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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 CompanyManagement from '../../src/components/CompanyManagement';
  5. import { companyClientManager } from '../../src/api/companyClient';
  6. // Mock PlatformSelector
  7. vi.mock('@d8d/allin-platform-management-ui/components', () => ({
  8. PlatformSelector: vi.fn(({ value, onChange, placeholder, testId }) => (
  9. <select
  10. data-testid={testId}
  11. value={value?.toString() || ''}
  12. onChange={(e) => onChange?.(parseInt(e.target.value))}
  13. >
  14. <option value="">{placeholder || '请选择平台'}</option>
  15. <option value="1">测试平台</option>
  16. <option value="2">另一个平台</option>
  17. </select>
  18. ))
  19. }));
  20. // 完整的mock响应对象
  21. const createMockResponse = (status: number, data?: any) => ({
  22. status,
  23. ok: status >= 200 && status < 300,
  24. body: null,
  25. bodyUsed: false,
  26. statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
  27. headers: new Headers(),
  28. url: '',
  29. redirected: false,
  30. type: 'basic' as ResponseType,
  31. json: async () => data || {},
  32. text: async () => '',
  33. blob: async () => new Blob(),
  34. arrayBuffer: async () => new ArrayBuffer(0),
  35. formData: async () => new FormData(),
  36. clone: function() { return this; }
  37. });
  38. // Mock API client
  39. vi.mock('../../src/api/companyClient', () => {
  40. const mockCompanyClient = {
  41. getAllCompanies: {
  42. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  43. data: [
  44. {
  45. id: 1,
  46. platformId: 1,
  47. companyName: '测试公司',
  48. contactPerson: '张三',
  49. contactPhone: '13800138000',
  50. contactEmail: 'zhangsan@example.com',
  51. address: '北京市朝阳区',
  52. status: 1,
  53. createTime: '2024-01-01T00:00:00Z',
  54. updateTime: '2024-01-01T00:00:00Z',
  55. platform: {
  56. id: 1,
  57. platformName: '测试平台'
  58. }
  59. }
  60. ],
  61. total: 1
  62. }))),
  63. },
  64. searchCompanies: {
  65. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  66. data: [
  67. {
  68. id: 2,
  69. platformId: 1,
  70. companyName: '搜索测试公司',
  71. contactPerson: '李四',
  72. contactPhone: '13900139000',
  73. contactEmail: 'lisi@example.com',
  74. address: '上海市浦东新区',
  75. status: 1,
  76. createTime: '2024-01-02T00:00:00Z',
  77. updateTime: '2024-01-02T00:00:00Z',
  78. platform: {
  79. id: 1,
  80. platformName: '测试平台'
  81. }
  82. }
  83. ],
  84. total: 1
  85. }))),
  86. },
  87. createCompany: {
  88. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  89. success: true
  90. }))),
  91. },
  92. updateCompany: {
  93. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  94. success: true
  95. }))),
  96. },
  97. deleteCompany: {
  98. $post: vi.fn(() => Promise.resolve(createMockResponse(200, {
  99. success: true
  100. }))),
  101. },
  102. getCompany: {
  103. ':id': {
  104. $get: vi.fn(() => Promise.resolve(createMockResponse(200, {
  105. id: 1,
  106. platformId: 1,
  107. companyName: '测试公司',
  108. contactPerson: '张三',
  109. contactPhone: '13800138000',
  110. contactEmail: 'zhangsan@example.com',
  111. address: '北京市朝阳区',
  112. status: 1,
  113. createTime: '2024-01-01T00:00:00Z',
  114. updateTime: '2024-01-01T00:00:00Z',
  115. platform: {
  116. id: 1,
  117. platformName: '测试平台'
  118. }
  119. }))),
  120. }
  121. },
  122. getCompaniesByPlatform: {
  123. ':platformId': {
  124. $get: vi.fn(() => Promise.resolve(createMockResponse(200, []))),
  125. }
  126. }
  127. };
  128. const mockClientManager = {
  129. get: vi.fn(() => mockCompanyClient),
  130. reset: vi.fn()
  131. };
  132. return {
  133. companyClientManager: mockClientManager
  134. };
  135. });
  136. // Mock sonner
  137. vi.mock('sonner', () => ({
  138. toast: {
  139. success: vi.fn(),
  140. error: vi.fn(),
  141. warning: vi.fn(),
  142. info: vi.fn()
  143. }
  144. }));
  145. describe('CompanyManagement 集成测试', () => {
  146. let queryClient: QueryClient;
  147. beforeEach(() => {
  148. queryClient = new QueryClient({
  149. defaultOptions: {
  150. queries: {
  151. retry: false,
  152. },
  153. },
  154. });
  155. vi.clearAllMocks();
  156. });
  157. const renderComponent = () => {
  158. return render(
  159. <QueryClientProvider client={queryClient}>
  160. <CompanyManagement />
  161. </QueryClientProvider>
  162. );
  163. };
  164. it('应该渲染公司管理页面', async () => {
  165. renderComponent();
  166. // 检查页面标题
  167. expect(screen.getByText('公司管理')).toBeInTheDocument();
  168. expect(screen.getByText('管理所有公司信息,包括创建、编辑、删除和搜索功能')).toBeInTheDocument();
  169. // 检查搜索输入框
  170. expect(screen.getByTestId('search-company-input')).toBeInTheDocument();
  171. expect(screen.getByTestId('search-company-button')).toBeInTheDocument();
  172. // 检查创建按钮
  173. expect(screen.getByTestId('create-company-button')).toBeInTheDocument();
  174. // 等待数据加载
  175. await waitFor(() => {
  176. expect(screen.getByText('测试公司')).toBeInTheDocument();
  177. });
  178. // 检查表格列
  179. expect(screen.getByText('公司名称')).toBeInTheDocument();
  180. expect(screen.getByText('平台')).toBeInTheDocument();
  181. expect(screen.getByText('联系人')).toBeInTheDocument();
  182. expect(screen.getByText('联系电话')).toBeInTheDocument();
  183. expect(screen.getByText('状态')).toBeInTheDocument();
  184. expect(screen.getByText('创建时间')).toBeInTheDocument();
  185. expect(screen.getByText('操作')).toBeInTheDocument();
  186. });
  187. it('应该显示公司列表数据', async () => {
  188. renderComponent();
  189. // 等待数据加载
  190. await waitFor(() => {
  191. expect(screen.getByText('测试公司')).toBeInTheDocument();
  192. });
  193. // 检查公司数据
  194. expect(screen.getByText('测试公司')).toBeInTheDocument();
  195. expect(screen.getByText('测试平台')).toBeInTheDocument();
  196. expect(screen.getByText('张三')).toBeInTheDocument();
  197. expect(screen.getByText('13800138000')).toBeInTheDocument();
  198. expect(screen.getByText('启用')).toBeInTheDocument();
  199. });
  200. it('应该打开创建公司模态框', async () => {
  201. renderComponent();
  202. // 点击创建按钮
  203. fireEvent.click(screen.getByTestId('create-company-button'));
  204. // 检查模态框标题
  205. await waitFor(() => {
  206. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('创建公司');
  207. });
  208. // 检查表单字段
  209. expect(screen.getByTestId('create-company-platform-selector')).toBeInTheDocument();
  210. expect(screen.getByTestId('create-company-name-input')).toBeInTheDocument();
  211. expect(screen.getByTestId('create-company-contact-person-input')).toBeInTheDocument();
  212. expect(screen.getByTestId('create-company-contact-phone-input')).toBeInTheDocument();
  213. expect(screen.getByTestId('create-company-contact-email-input')).toBeInTheDocument();
  214. expect(screen.getByTestId('create-company-address-input')).toBeInTheDocument();
  215. });
  216. it('应该创建新公司', async () => {
  217. renderComponent();
  218. // 打开创建模态框
  219. fireEvent.click(screen.getByTestId('create-company-button'));
  220. await waitFor(() => {
  221. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('创建公司');
  222. });
  223. // 填写表单
  224. fireEvent.change(screen.getByTestId('create-company-platform-selector'), { target: { value: '1' } });
  225. fireEvent.change(screen.getByTestId('create-company-name-input'), { target: { value: '新公司' } });
  226. fireEvent.change(screen.getByTestId('create-company-contact-person-input'), { target: { value: '王五' } });
  227. fireEvent.change(screen.getByTestId('create-company-contact-phone-input'), { target: { value: '13700137000' } });
  228. fireEvent.change(screen.getByTestId('create-company-contact-email-input'), { target: { value: 'wangwu@example.com' } });
  229. fireEvent.change(screen.getByTestId('create-company-address-input'), { target: { value: '深圳市南山区' } });
  230. // 提交表单
  231. fireEvent.click(screen.getByTestId('submit-create-company-button'));
  232. // 验证API调用
  233. await waitFor(() => {
  234. expect(companyClientManager.get().createCompany.$post).toHaveBeenCalledWith({
  235. json: expect.objectContaining({
  236. platformId: 1,
  237. companyName: '新公司',
  238. contactPerson: '王五',
  239. contactPhone: '13700137000',
  240. contactEmail: 'wangwu@example.com',
  241. address: '深圳市南山区'
  242. })
  243. });
  244. });
  245. });
  246. it('应该打开编辑公司模态框', async () => {
  247. renderComponent();
  248. // 等待数据加载
  249. await waitFor(() => {
  250. expect(screen.getByText('测试公司')).toBeInTheDocument();
  251. });
  252. // 点击编辑按钮
  253. fireEvent.click(screen.getByTestId('edit-company-button-1'));
  254. // 检查模态框标题
  255. await waitFor(() => {
  256. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('编辑公司');
  257. });
  258. // 检查表单字段已填充
  259. expect(screen.getByTestId('edit-company-name-input')).toHaveValue('测试公司');
  260. expect(screen.getByTestId('edit-company-contact-person-input')).toHaveValue('张三');
  261. expect(screen.getByTestId('edit-company-contact-phone-input')).toHaveValue('13800138000');
  262. });
  263. it('应该更新公司信息', async () => {
  264. renderComponent();
  265. // 等待数据加载
  266. await waitFor(() => {
  267. expect(screen.getByText('测试公司')).toBeInTheDocument();
  268. });
  269. // 点击编辑按钮
  270. fireEvent.click(screen.getByTestId('edit-company-button-1'));
  271. await waitFor(() => {
  272. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('编辑公司');
  273. });
  274. // 修改表单
  275. fireEvent.change(screen.getByTestId('edit-company-name-input'), { target: { value: '更新后的公司' } });
  276. fireEvent.change(screen.getByTestId('edit-company-contact-person-input'), { target: { value: '赵六' } });
  277. // 提交表单
  278. fireEvent.click(screen.getByTestId('submit-edit-company-button'));
  279. // 验证API调用
  280. await waitFor(() => {
  281. expect(companyClientManager.get().updateCompany.$post).toHaveBeenCalledWith({
  282. json: expect.objectContaining({
  283. id: 1,
  284. companyName: '更新后的公司',
  285. contactPerson: '赵六'
  286. })
  287. });
  288. });
  289. });
  290. it('应该打开删除确认对话框', async () => {
  291. renderComponent();
  292. // 等待数据加载
  293. await waitFor(() => {
  294. expect(screen.getByText('测试公司')).toBeInTheDocument();
  295. });
  296. // 点击删除按钮
  297. fireEvent.click(screen.getByTestId('delete-company-button-1'));
  298. // 检查删除确认对话框 - 使用getByRole和getByText
  299. // 对话框标题(使用heading角色)
  300. const dialogTitle = screen.getByRole('heading', { name: '确认删除' });
  301. expect(dialogTitle).toBeInTheDocument();
  302. // 对话框描述
  303. expect(screen.getByText('确定要删除这个公司吗?此操作不可恢复。')).toBeInTheDocument();
  304. // 按钮
  305. expect(screen.getByTestId('cancel-delete-company-button')).toBeInTheDocument();
  306. expect(screen.getByTestId('confirm-delete-company-button')).toBeInTheDocument();
  307. });
  308. it('应该搜索公司', async () => {
  309. renderComponent();
  310. // 输入搜索关键词
  311. fireEvent.change(screen.getByTestId('search-company-input'), { target: { value: '搜索' } });
  312. fireEvent.click(screen.getByTestId('search-company-button'));
  313. // 验证搜索API调用
  314. await waitFor(() => {
  315. expect(companyClientManager.get().searchCompanies.$get).toHaveBeenCalledWith({
  316. query: {
  317. name: '搜索',
  318. skip: 0,
  319. take: 10
  320. }
  321. });
  322. });
  323. });
  324. it('应该处理API错误', async () => {
  325. // Mock API错误
  326. (companyClientManager.get().getAllCompanies.$get as any).mockImplementationOnce(() =>
  327. Promise.resolve(createMockResponse(500, { message: '服务器错误' }))
  328. );
  329. renderComponent();
  330. // 等待错误处理 - 检查表格是否为空
  331. await waitFor(() => {
  332. // 表格应该显示空状态
  333. const tableBody = screen.getByRole('table').querySelector('tbody');
  334. expect(tableBody).toBeInTheDocument();
  335. // 检查是否有"暂无数据"或空状态
  336. const emptyCells = tableBody!.querySelectorAll('td');
  337. if (emptyCells.length > 0) {
  338. // 如果有单元格,检查是否包含空状态文本
  339. const hasEmptyState = Array.from(emptyCells).some(cell =>
  340. cell.textContent?.includes('暂无数据') ||
  341. cell.textContent?.includes('No data') ||
  342. cell.textContent?.includes('Empty')
  343. );
  344. expect(hasEmptyState).toBe(true);
  345. }
  346. });
  347. });
  348. it('应该仅公司名称为必填字段', async () => {
  349. renderComponent();
  350. // 打开创建模态框
  351. fireEvent.click(screen.getByTestId('create-company-button'));
  352. await waitFor(() => {
  353. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('创建公司');
  354. });
  355. // 检查FormLabel标记 - 使用更精确的选择器
  356. expect(screen.getByText('平台(可选)')).toBeInTheDocument();
  357. // 查找模态框内的公司名称标签 - 使用更可靠的方法
  358. const modal = screen.getByRole('dialog');
  359. // 查找包含"公司名称"文本的label元素
  360. const companyNameLabel = Array.from(modal.querySelectorAll('label')).find(
  361. label => label.textContent === '公司名称'
  362. );
  363. expect(companyNameLabel).toBeInTheDocument();
  364. expect(companyNameLabel?.textContent).toBe('公司名称'); // 必填字段没有"(可选)"
  365. expect(screen.getByText('联系人(可选)')).toBeInTheDocument();
  366. expect(screen.getByText('联系电话(可选)')).toBeInTheDocument();
  367. expect(screen.getByText('联系邮箱(可选)')).toBeInTheDocument();
  368. expect(screen.getByText('地址(可选)')).toBeInTheDocument();
  369. });
  370. it('应该允许可选字段留空创建公司', async () => {
  371. renderComponent();
  372. // 打开创建模态框
  373. fireEvent.click(screen.getByTestId('create-company-button'));
  374. await waitFor(() => {
  375. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('创建公司');
  376. });
  377. // 只填写公司名称,其他字段留空
  378. fireEvent.change(screen.getByTestId('create-company-name-input'), { target: { value: '仅名称公司' } });
  379. // 提交表单
  380. fireEvent.click(screen.getByTestId('submit-create-company-button'));
  381. // 验证API调用 - 只有公司名称被传递,其他字段为undefined
  382. await waitFor(() => {
  383. expect(companyClientManager.get().createCompany.$post).toHaveBeenCalledWith({
  384. json: expect.objectContaining({
  385. companyName: '仅名称公司'
  386. })
  387. });
  388. });
  389. });
  390. it('应该处理空字符串转换为undefined', async () => {
  391. renderComponent();
  392. // 打开创建模态框
  393. fireEvent.click(screen.getByTestId('create-company-button'));
  394. await waitFor(() => {
  395. expect(screen.getByTestId('company-modal-title')).toHaveTextContent('创建公司');
  396. });
  397. // 填写公司名称,其他字段输入空字符串
  398. fireEvent.change(screen.getByTestId('create-company-name-input'), { target: { value: '测试公司' } });
  399. fireEvent.change(screen.getByTestId('create-company-contact-person-input'), { target: { value: '' } });
  400. fireEvent.change(screen.getByTestId('create-company-contact-phone-input'), { target: { value: '' } });
  401. fireEvent.change(screen.getByTestId('create-company-contact-email-input'), { target: { value: '' } });
  402. fireEvent.change(screen.getByTestId('create-company-address-input'), { target: { value: '' } });
  403. // 提交表单
  404. fireEvent.click(screen.getByTestId('submit-create-company-button'));
  405. // 验证API调用 - 空字符串应该被转换为undefined
  406. await waitFor(() => {
  407. expect(companyClientManager.get().createCompany.$post).toHaveBeenCalledWith({
  408. json: expect.objectContaining({
  409. companyName: '测试公司'
  410. // 其他字段应该为undefined,不会被包含在请求中
  411. })
  412. });
  413. });
  414. });
  415. });