Points.test.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { fireEvent, render, screen, waitFor } from '@testing-library/react';
  2. import { beforeEach, expect, it, vi } from 'vitest';
  3. import PointsPage from '@/pages/Points';
  4. import { apiRequest } from '@/services/api';
  5. vi.mock('@/services/api', () => ({
  6. apiRequest: vi.fn(),
  7. downloadFile: vi.fn(),
  8. jsonOptions: (method: string, body: unknown) => ({ method, body: JSON.stringify(body) }),
  9. }));
  10. const rules = {
  11. enabled: true,
  12. timezone: 'Asia/Shanghai',
  13. checkin_enabled: true,
  14. checkin_button_enabled: true,
  15. checkin_points: 10,
  16. activity_enabled: true,
  17. activity_points: 1,
  18. activity_cooldown_seconds: 300,
  19. activity_daily_cap: 10,
  20. upvote_enabled: true,
  21. upvote_points: 2,
  22. upvote_pair_cooldown_seconds: 86400,
  23. upvote_daily_cap: 10,
  24. };
  25. beforeEach(() => {
  26. vi.clearAllMocks();
  27. vi.mocked(apiRequest).mockImplementation(async (path) => {
  28. if (path.startsWith('/chats?')) {
  29. return {
  30. items: [{ chat_id: '-100123', title: '积分测试群', type: 'supergroup', accessible: true }],
  31. total: 1,
  32. page: 1,
  33. page_size: 100,
  34. };
  35. }
  36. if (path.startsWith('/points/accounts?')) {
  37. return {
  38. items: [{ chat_id: '-100123', user_id: '10001', username: 'alice', display_name: '爱丽丝 示例', balance: 180, lifetime_earned: 220, lifetime_spent: 40 }],
  39. total: 1,
  40. page: 1,
  41. page_size: 20,
  42. };
  43. }
  44. if (path.startsWith('/points/transactions?')) {
  45. return {
  46. items: [{ transaction_id: 'tx-1', chat_id: '-100123', user_id: '10001', username: 'alice', display_name: '爱丽丝 示例', delta: 10, balance_after: 180, source: 'checkin', reason: 'Daily check-in', created_at: '2026-07-24T08:00:00Z' }],
  47. total: 1,
  48. page: 1,
  49. page_size: 20,
  50. };
  51. }
  52. if (path.endsWith('/points/settings')) return rules;
  53. if (path === '/points/adjustments') return { created: true };
  54. return {};
  55. });
  56. });
  57. it('展示余额排行、流水并保存积分规则', async () => {
  58. render(<PointsPage />);
  59. expect(await screen.findByText('爱丽丝 示例')).toBeInTheDocument();
  60. expect(screen.queryByText('@alice')).not.toBeInTheDocument();
  61. fireEvent.click(screen.getByRole('tab', { name: /流水/ }));
  62. expect(await screen.findByText('Daily check-in')).toBeInTheDocument();
  63. fireEvent.click(screen.getByRole('tab', { name: '自动积分规则' }));
  64. expect(await screen.findByRole('heading', { name: '签到' })).toBeInTheDocument();
  65. expect(screen.getByRole('switch', { name: '底部签到按钮' })).toBeChecked();
  66. await waitFor(() =>
  67. expect(apiRequest).toHaveBeenCalledWith('/chats/-100123/points/settings'),
  68. );
  69. const saveButton = screen.getByRole('button', { name: /保\s*存\s*规\s*则/ });
  70. await waitFor(() => expect(saveButton).toBeEnabled());
  71. fireEvent.click(saveButton);
  72. expect(
  73. vi.mocked(apiRequest).mock.calls.some(
  74. ([path, options]) => path === '/chats/-100123/points/settings' && options?.method === 'PUT',
  75. ),
  76. ).toBe(false);
  77. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*保\s*存/ }));
  78. await waitFor(() => {
  79. const call = vi.mocked(apiRequest).mock.calls.find(
  80. ([path, options]) => path === '/chats/-100123/points/settings' && options?.method === 'PUT',
  81. );
  82. expect(call).toBeTruthy();
  83. expect(JSON.parse(String(call?.[1]?.body))).toMatchObject({
  84. checkin_button_enabled: true,
  85. confirm: true,
  86. });
  87. });
  88. });
  89. it('手工调整必须二次确认并携带确认标记', async () => {
  90. render(<PointsPage />);
  91. await screen.findByText('爱丽丝 示例');
  92. fireEvent.click(screen.getByRole('button', { name: /手\s*工\s*调\s*整/ }));
  93. fireEvent.change(screen.getByLabelText('用户 ID'), { target: { value: '10001' } });
  94. fireEvent.change(screen.getByLabelText('积分数值'), { target: { value: '25' } });
  95. fireEvent.change(screen.getByLabelText('原因'), { target: { value: '活动补发' } });
  96. fireEvent.click(screen.getByRole('button', { name: /继\s*续\s*确\s*认/ }));
  97. expect(vi.mocked(apiRequest).mock.calls.some(([path]) => path === '/points/adjustments')).toBe(false);
  98. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*写\s*入/ }));
  99. await waitFor(() => {
  100. const call = vi.mocked(apiRequest).mock.calls.find(([path]) => path === '/points/adjustments');
  101. expect(call).toBeTruthy();
  102. expect(JSON.parse(String(call?.[1]?.body))).toMatchObject({
  103. chat_id: '-100123',
  104. user_id: '10001',
  105. amount: 25,
  106. reason: '活动补发',
  107. confirm: true,
  108. });
  109. });
  110. });