Giveaways.test.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import { fireEvent, render, screen, waitFor } from '@testing-library/react';
  2. import { beforeEach, expect, it, vi } from 'vitest';
  3. import GiveawaysPage from '@/pages/Giveaways';
  4. import { apiRequest } from '@/services/api';
  5. import { mockLocation } from '../umiMock';
  6. vi.mock('@/services/api', () => ({
  7. apiRequest: vi.fn(),
  8. downloadFile: vi.fn(),
  9. jsonOptions: (method: string, body: unknown) => ({ method, body: JSON.stringify(body) }),
  10. }));
  11. beforeEach(() => {
  12. vi.clearAllMocks();
  13. mockLocation.search = '';
  14. vi.mocked(apiRequest).mockImplementation(async (path) => {
  15. if (path.startsWith('/chats?')) {
  16. return {
  17. items: [{ chat_id: '-100123', title: '抽奖测试群', type: 'supergroup', accessible: true }],
  18. total: 1,
  19. page: 1,
  20. page_size: 100,
  21. };
  22. }
  23. if (path.startsWith('/giveaways?')) {
  24. return {
  25. items: [{
  26. giveaway_id: 'draw-1',
  27. chat_id: '-100123',
  28. title: '七月抽奖',
  29. status: 'running',
  30. prizes: [{ name: '一等奖', count: 1, points_reward: 50 }],
  31. minimum_points: 20,
  32. entry_cost: 5,
  33. participation_reward: 2,
  34. starts_at: '2026-07-24T00:00:00Z',
  35. ends_at: '2026-07-25T08:00:00Z',
  36. created_at: '2026-07-24T08:00:00Z',
  37. }],
  38. total: 1,
  39. page: 1,
  40. page_size: 20,
  41. };
  42. }
  43. if (path.includes('/participants?')) {
  44. return {
  45. items: [{ giveaway_id: 'draw-1', chat_id: '-100123', user_id: '10001', username: 'alice', display_name: '爱丽丝 示例', active: true, entry_cost: 5, joined_at: '2026-07-24T08:00:00Z' }],
  46. total: 1,
  47. page: 1,
  48. page_size: 100,
  49. };
  50. }
  51. if (path.endsWith('/finish')) return { status: 'finished' };
  52. return {};
  53. });
  54. });
  55. it('参与者优先显示 Telegram 昵称', async () => {
  56. render(<GiveawaysPage />);
  57. await screen.findByText('七月抽奖');
  58. fireEvent.click(screen.getByRole('button', { name: '参与者' }));
  59. expect(await screen.findByText('爱丽丝 示例')).toBeInTheDocument();
  60. expect(screen.queryByText('@alice')).not.toBeInTheDocument();
  61. });
  62. it('展示抽奖积分配置并在危险操作确认后开奖', async () => {
  63. render(<GiveawaysPage />);
  64. expect(await screen.findByText('七月抽奖')).toBeInTheDocument();
  65. expect(screen.getByText('门槛 20')).toBeInTheDocument();
  66. expect(screen.getByText('消耗 5')).toBeInTheDocument();
  67. expect(screen.getByText('参与 +2')).toBeInTheDocument();
  68. fireEvent.click(screen.getByRole('button', { name: /开\s*奖/ }));
  69. expect(vi.mocked(apiRequest).mock.calls.some(([path]) => path.endsWith('/finish'))).toBe(false);
  70. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*执\s*行/ }));
  71. await waitFor(() => {
  72. const call = vi.mocked(apiRequest).mock.calls.find(([path]) => path.endsWith('/finish'));
  73. expect(call).toBeTruthy();
  74. expect(JSON.parse(String(call?.[1]?.body))).toEqual({ confirm: true });
  75. });
  76. });
  77. it('创建表单包含积分联动字段且发布前必须二次确认', async () => {
  78. mockLocation.search = '?chat_id=-100123';
  79. render(<GiveawaysPage />);
  80. await screen.findByText('七月抽奖');
  81. fireEvent.click(screen.getByRole('button', { name: /创\s*建\s*抽\s*奖/ }));
  82. expect(await screen.findByLabelText('最低积分')).toBeInTheDocument();
  83. expect(screen.getByLabelText('报名消耗')).toBeInTheDocument();
  84. expect(screen.getByLabelText('参与奖励')).toBeInTheDocument();
  85. expect(screen.getByLabelText('报名开始时间')).toBeInTheDocument();
  86. expect(screen.getByLabelText('开奖时间')).toBeInTheDocument();
  87. expect(screen.getByPlaceholderText('中奖积分')).toBeInTheDocument();
  88. fireEvent.change(screen.getByLabelText('标题'), { target: { value: '八月抽奖' } });
  89. fireEvent.click(screen.getByRole('button', { name: /创\s*建\s*并\s*发\s*布/ }));
  90. expect(
  91. vi.mocked(apiRequest).mock.calls.some(
  92. ([path, options]) => path === '/giveaways' && options?.method === 'POST',
  93. ),
  94. ).toBe(false);
  95. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*发\s*布/ }));
  96. await waitFor(() => {
  97. const call = vi.mocked(apiRequest).mock.calls.find(
  98. ([path, options]) => path === '/giveaways' && options?.method === 'POST',
  99. );
  100. expect(call).toBeTruthy();
  101. expect(JSON.parse(String(call?.[1]?.body))).toMatchObject({
  102. chat_id: '-100123',
  103. title: '八月抽奖',
  104. minimum_points: 0,
  105. entry_cost: 0,
  106. participation_reward: 0,
  107. starts_at: expect.any(String),
  108. ends_at: expect.any(String),
  109. confirm: true,
  110. });
  111. expect(JSON.parse(String(call?.[1]?.body))).not.toHaveProperty('duration_minutes');
  112. });
  113. });
  114. it('选择群组后提供抽奖资格封禁管理', async () => {
  115. mockLocation.search = '?chat_id=-100123';
  116. vi.mocked(apiRequest).mockImplementation(async (path) => {
  117. if (path.startsWith('/chats?')) {
  118. return {
  119. items: [{ chat_id: '-100123', title: '抽奖测试群', type: 'supergroup', accessible: true }],
  120. total: 1,
  121. page: 1,
  122. page_size: 100,
  123. };
  124. }
  125. if (path.startsWith('/giveaways?')) return { items: [], total: 0, page: 1, page_size: 20 };
  126. if (path.includes('/giveaway-bans?')) {
  127. return {
  128. items: [{ chat_id: '-100123', user_id: '10009', reason: '恶意刷报名', created_at: '2026-07-24T08:00:00Z' }],
  129. total: 1,
  130. page: 1,
  131. page_size: 20,
  132. };
  133. }
  134. return {};
  135. });
  136. render(<GiveawaysPage />);
  137. fireEvent.click(await screen.findByRole('button', { name: /资\s*格\s*封\s*禁/ }));
  138. expect(await screen.findByText('恶意刷报名')).toBeInTheDocument();
  139. expect(screen.getByRole('button', { name: /添\s*加\s*封\s*禁/ })).toBeInTheDocument();
  140. });