Services.test.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { fireEvent, render, screen } from '@testing-library/react';
  2. import { beforeEach, expect, test, vi } from 'vitest';
  3. import ServicesPage from '@/pages/Services';
  4. import { apiRequest } from '@/services/api';
  5. vi.mock('@/services/api', () => ({
  6. apiRequest: vi.fn(),
  7. jsonOptions: vi.fn((method, body) => ({ method, body: JSON.stringify(body) })),
  8. }));
  9. const emptyPage = { items: [], total: 0, page: 1, page_size: 20 };
  10. beforeEach(() => {
  11. vi.mocked(apiRequest).mockImplementation(async (path) => {
  12. if (path === '/directory/fulfillment-metrics') {
  13. return {
  14. counts: {},
  15. valid_requests: 12,
  16. quoted_requests: 9,
  17. confirmed_requests: 6,
  18. request_submission_rate: 1,
  19. quote_rate: 0.75,
  20. customer_confirmation_rate: 0.6667,
  21. match_success_rate: 0.5,
  22. fulfillment_rate: 0.8333,
  23. cancellation_rate: 0.1,
  24. cancellation_distribution: { customer: 1, technician: 0 },
  25. scope_note: '仅统计平台服务单;直接 Telegram 私聊成交不在统计范围。',
  26. };
  27. }
  28. if (path === '/directory/service-settings') {
  29. return {
  30. customer_max_open_orders: 3,
  31. customer_daily_request_limit: 10,
  32. review_qr_expiry_hours: 24,
  33. };
  34. }
  35. if (path.startsWith('/directory/package-templates')) {
  36. return {
  37. items: [
  38. {
  39. template_id: 'tpl1',
  40. version: 2,
  41. name: '基础护理模板',
  42. status: 'enabled',
  43. sort_order: 1,
  44. created_at: '2026-07-26T00:00:00Z',
  45. updated_at: '2026-07-26T00:00:00Z',
  46. package: {
  47. package_id: 'pkg1',
  48. name: '基础护理',
  49. category: '护理',
  50. description: '标准服务',
  51. tags: ['护理'],
  52. service_modes: ['at_store'],
  53. price_mode: 'fixed',
  54. currency: 'CNY',
  55. price_unit: 'per_service',
  56. min_price: '100.00',
  57. max_price: '100.00',
  58. addons: [],
  59. travel_fee: { mode: 'quoted' },
  60. },
  61. },
  62. ],
  63. total: 1,
  64. };
  65. }
  66. if (path.startsWith('/directory/service-orders')) return emptyPage;
  67. if (path.startsWith('/directory/reviews')) return emptyPage;
  68. if (path.startsWith('/directory/review-qrs')) return emptyPage;
  69. if (path.startsWith('/directory/leaderboard')) return emptyPage;
  70. if (path.startsWith('/directory/customer-blocks')) return emptyPage;
  71. if (path.startsWith('/directory/reports')) return emptyPage;
  72. if (path === '/directory/review-template') {
  73. return {
  74. template_id: 'review1',
  75. version: 1,
  76. status: 'active',
  77. published_at: '2026-07-26T00:00:00Z',
  78. rating_questions: [
  79. { question_id: 'effect', label: '服务效果', required: true, weight: 100 },
  80. ],
  81. text_questions: [],
  82. };
  83. }
  84. throw new Error(`Unexpected request: ${path}`);
  85. });
  86. });
  87. test('展示履约漏斗、统计边界和套餐模板', async () => {
  88. render(<ServicesPage />);
  89. expect(screen.getByText('技师服务')).toBeInTheDocument();
  90. expect(screen.getByText('最小履约闭环')).toBeInTheDocument();
  91. expect(await screen.findByText('12')).toBeInTheDocument();
  92. expect(screen.getByText('仅统计平台服务单;直接 Telegram 私聊成交不在统计范围。')).toBeInTheDocument();
  93. fireEvent.click(screen.getByRole('tab', { name: /套餐模板/ }));
  94. expect(await screen.findByText('基础护理模板')).toBeInTheDocument();
  95. expect(screen.getByText('护理 · V2')).toBeInTheDocument();
  96. fireEvent.click(screen.getByRole('button', { name: '新建模板' }));
  97. expect(await screen.findByText('标准加项(最多 10 个)')).toBeInTheDocument();
  98. expect(screen.getByRole('button', { name: '增加标准加项' })).toBeInTheDocument();
  99. });