Giveaways.test.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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: [
  18. { chat_id: '-100999', title: '不可用抽奖群', type: 'supergroup', accessible: false },
  19. { chat_id: '-100123', title: '抽奖测试群', type: 'supergroup', accessible: true },
  20. ],
  21. total: 2,
  22. page: 1,
  23. page_size: 100,
  24. };
  25. }
  26. if (path.startsWith('/giveaways?')) {
  27. return {
  28. items: [{
  29. giveaway_id: 'draw-1',
  30. chat_id: '-100123',
  31. title: '七月抽奖',
  32. description: '原说明',
  33. status: 'running',
  34. prizes: [{ name: '一等奖', count: 1, points_reward: 50 }],
  35. minimum_points: 20,
  36. entry_cost: 5,
  37. participation_reward: 2,
  38. starts_at: '2099-07-24T00:00:00Z',
  39. ends_at: '2099-07-25T08:00:00Z',
  40. created_at: '2026-07-24T08:00:00Z',
  41. updated_at: '2026-07-24T08:00:01Z',
  42. winners: [{ user_id: '10002', display_name: '中奖用户', tier: '一等奖', points_reward: 50 }],
  43. }],
  44. total: 1,
  45. page: 1,
  46. page_size: 20,
  47. };
  48. }
  49. if (path.includes('/participants?')) {
  50. return {
  51. 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' }],
  52. total: 1,
  53. page: 1,
  54. page_size: 100,
  55. };
  56. }
  57. if (path.endsWith('/finish')) return { status: 'finished' };
  58. return {};
  59. });
  60. });
  61. it('参与者优先显示 Telegram 昵称', async () => {
  62. render(<GiveawaysPage />);
  63. await screen.findByText('七月抽奖');
  64. fireEvent.click(screen.getByRole('button', { name: '参与者 / 中奖者' }));
  65. expect(await screen.findByText('爱丽丝 示例')).toBeInTheDocument();
  66. expect(screen.queryByText('@alice')).not.toBeInTheDocument();
  67. expect(screen.getByText('中奖用户')).toBeInTheDocument();
  68. expect(screen.getByText('一等奖')).toBeInTheDocument();
  69. expect(screen.getByText('+50')).toBeInTheDocument();
  70. });
  71. it('展示抽奖积分配置并在危险操作确认后开奖', async () => {
  72. render(<GiveawaysPage />);
  73. expect(await screen.findByText('七月抽奖')).toBeInTheDocument();
  74. expect(screen.getByText('门槛 20')).toBeInTheDocument();
  75. expect(screen.getByText('消耗 5')).toBeInTheDocument();
  76. expect(screen.getByText('参与 +2')).toBeInTheDocument();
  77. fireEvent.click(screen.getByRole('button', { name: /开\s*奖/ }));
  78. expect(vi.mocked(apiRequest).mock.calls.some(([path]) => path.endsWith('/finish'))).toBe(false);
  79. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*执\s*行/ }));
  80. await waitFor(() => {
  81. const call = vi.mocked(apiRequest).mock.calls.find(([path]) => path.endsWith('/finish'));
  82. expect(call).toBeTruthy();
  83. expect(JSON.parse(String(call?.[1]?.body))).toEqual({ confirm: true });
  84. });
  85. });
  86. it('进行中的抽奖可以编辑并刷新群消息', async () => {
  87. render(<GiveawaysPage />);
  88. await screen.findByText('七月抽奖');
  89. fireEvent.click(screen.getByRole('button', { name: /编\s*辑/ }));
  90. expect(await screen.findByText('奖项与积分规则不可编辑,避免报名后改变抽奖条件。')).toBeInTheDocument();
  91. fireEvent.change(screen.getByLabelText('标题'), { target: { value: '七月抽奖更新版' } });
  92. fireEvent.change(screen.getByLabelText('说明'), { target: { value: '有效期一个月' } });
  93. fireEvent.click(screen.getByRole('button', { name: /保\s*存\s*并\s*刷\s*新\s*群\s*消\s*息/ }));
  94. await waitFor(() => {
  95. const call = vi.mocked(apiRequest).mock.calls.find(
  96. ([path, options]) => path === '/giveaways/draw-1' && options?.method === 'PATCH',
  97. );
  98. expect(call).toBeTruthy();
  99. expect(JSON.parse(String(call?.[1]?.body))).toEqual({
  100. title: '七月抽奖更新版',
  101. description: '有效期一个月',
  102. starts_at: '2099-07-24T00:00:00.000Z',
  103. ends_at: '2099-07-25T08:00:00.000Z',
  104. expected_updated_at: '2026-07-24T08:00:01Z',
  105. confirm: true,
  106. });
  107. });
  108. });
  109. it('创建表单包含积分联动字段且发布前必须二次确认', async () => {
  110. mockLocation.search = '?chat_id=-100123';
  111. render(<GiveawaysPage />);
  112. await screen.findByText('七月抽奖');
  113. fireEvent.click(screen.getByRole('button', { name: /创\s*建\s*抽\s*奖/ }));
  114. expect(await screen.findByLabelText('最低积分')).toBeInTheDocument();
  115. expect(screen.getByLabelText('报名消耗')).toBeInTheDocument();
  116. expect(screen.getByLabelText('参与奖励')).toBeInTheDocument();
  117. expect(screen.getByLabelText('报名开始时间')).toBeInTheDocument();
  118. expect(screen.getByLabelText('开奖时间')).toBeInTheDocument();
  119. expect(screen.getByPlaceholderText('中奖积分')).toBeInTheDocument();
  120. fireEvent.change(screen.getByLabelText('标题'), { target: { value: '八月抽奖' } });
  121. fireEvent.click(screen.getByRole('button', { name: /创\s*建\s*并\s*发\s*布/ }));
  122. expect(
  123. vi.mocked(apiRequest).mock.calls.some(
  124. ([path, options]) => path === '/giveaways' && options?.method === 'POST',
  125. ),
  126. ).toBe(false);
  127. fireEvent.click(await screen.findByRole('button', { name: /确\s*认\s*发\s*布/ }));
  128. await waitFor(() => {
  129. const call = vi.mocked(apiRequest).mock.calls.find(
  130. ([path, options]) => path === '/giveaways' && options?.method === 'POST',
  131. );
  132. expect(call).toBeTruthy();
  133. expect(JSON.parse(String(call?.[1]?.body))).toMatchObject({
  134. chat_id: '-100123',
  135. title: '八月抽奖',
  136. minimum_points: 0,
  137. entry_cost: 0,
  138. participation_reward: 0,
  139. starts_at: expect.any(String),
  140. ends_at: expect.any(String),
  141. confirm: true,
  142. });
  143. expect(JSON.parse(String(call?.[1]?.body))).not.toHaveProperty('duration_minutes');
  144. });
  145. });
  146. it('不可用群不会成为筛选或创建目标', async () => {
  147. mockLocation.search = '?chat_id=-100999';
  148. render(<GiveawaysPage />);
  149. await waitFor(() => {
  150. const requests = vi.mocked(apiRequest).mock.calls
  151. .map(([path]) => path)
  152. .filter((path) => path.startsWith('/giveaways?'));
  153. expect(requests.length).toBeGreaterThan(0);
  154. expect(requests.every((path) => !path.includes('chat_id=-100999'))).toBe(true);
  155. });
  156. fireEvent.click(screen.getByRole('button', { name: /创\s*建\s*抽\s*奖/ }));
  157. fireEvent.mouseDown(await screen.findByLabelText('群组'));
  158. expect(screen.queryByText('不可用抽奖群')).not.toBeInTheDocument();
  159. expect(screen.getByText('抽奖测试群')).toBeInTheDocument();
  160. });
  161. it('选择群组后提供抽奖资格封禁管理', async () => {
  162. mockLocation.search = '?chat_id=-100123';
  163. vi.mocked(apiRequest).mockImplementation(async (path) => {
  164. if (path.startsWith('/chats?')) {
  165. return {
  166. items: [{ chat_id: '-100123', title: '抽奖测试群', type: 'supergroup', accessible: true }],
  167. total: 1,
  168. page: 1,
  169. page_size: 100,
  170. };
  171. }
  172. if (path.startsWith('/giveaways?')) return { items: [], total: 0, page: 1, page_size: 20 };
  173. if (path.includes('/giveaway-bans?')) {
  174. return {
  175. items: [{ chat_id: '-100123', user_id: '10009', reason: '恶意刷报名', created_at: '2026-07-24T08:00:00Z' }],
  176. total: 1,
  177. page: 1,
  178. page_size: 20,
  179. };
  180. }
  181. return {};
  182. });
  183. render(<GiveawaysPage />);
  184. fireEvent.click(await screen.findByRole('button', { name: /资\s*格\s*封\s*禁/ }));
  185. expect(await screen.findByText('恶意刷报名')).toBeInTheDocument();
  186. expect(screen.getByRole('button', { name: /添\s*加\s*封\s*禁/ })).toBeInTheDocument();
  187. });