AreaPicker.test.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import React from 'react'
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react'
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  4. import { AreaPicker } from '../../src/components/AreaPicker'
  5. // Mock API 客户端
  6. const mockAreaClient = {
  7. provinces: {
  8. $get: jest.fn()
  9. },
  10. cities: {
  11. $get: jest.fn()
  12. },
  13. districts: {
  14. $get: jest.fn()
  15. }
  16. }
  17. jest.mock('../../src/api', () => ({
  18. areaClient: mockAreaClient
  19. }))
  20. // 创建测试用的 QueryClient
  21. const createTestQueryClient = () => new QueryClient({
  22. defaultOptions: {
  23. queries: {
  24. retry: false,
  25. },
  26. },
  27. })
  28. // 包装组件
  29. const Wrapper = ({ children }: { children: React.ReactNode }) => {
  30. const queryClient = createTestQueryClient()
  31. return (
  32. <QueryClientProvider client={queryClient}>
  33. {children}
  34. </QueryClientProvider>
  35. )
  36. }
  37. // 模拟数据
  38. const mockProvinces = {
  39. success: true,
  40. data: {
  41. provinces: [
  42. { id: 1, name: '北京市' },
  43. { id: 2, name: '上海市' },
  44. { id: 3, name: '广东省' }
  45. ]
  46. },
  47. message: ''
  48. }
  49. const mockCities = {
  50. success: true,
  51. data: {
  52. cities: [
  53. { id: 11, name: '北京市' },
  54. { id: 12, name: '朝阳区' },
  55. { id: 13, name: '海淀区' }
  56. ]
  57. },
  58. message: ''
  59. }
  60. const mockDistricts = {
  61. success: true,
  62. data: {
  63. districts: [
  64. { id: 101, name: '朝阳区' },
  65. { id: 102, name: '海淀区' },
  66. { id: 103, name: '西城区' }
  67. ]
  68. },
  69. message: ''
  70. }
  71. describe('AreaPicker 组件', () => {
  72. beforeEach(() => {
  73. // 重置所有 mock
  74. jest.clearAllMocks()
  75. // 设置默认的 mock 返回值
  76. mockAreaClient.provinces.$get.mockResolvedValue({
  77. status: 200,
  78. json: async () => mockProvinces
  79. })
  80. mockAreaClient.cities.$get.mockResolvedValue({
  81. status: 200,
  82. json: async () => mockCities
  83. })
  84. mockAreaClient.districts.$get.mockResolvedValue({
  85. status: 200,
  86. json: async () => mockDistricts
  87. })
  88. })
  89. test('应该正确渲染组件', async () => {
  90. const onClose = jest.fn()
  91. const onConfirm = jest.fn()
  92. render(
  93. <Wrapper>
  94. <AreaPicker
  95. visible={true}
  96. onClose={onClose}
  97. onConfirm={onConfirm}
  98. />
  99. </Wrapper>
  100. )
  101. // 检查标题
  102. expect(screen.getByText('选择地区')).toBeInTheDocument()
  103. // 检查选择器标签
  104. expect(screen.getByText('省份')).toBeInTheDocument()
  105. expect(screen.getByText('城市')).toBeInTheDocument()
  106. expect(screen.getByText('区县')).toBeInTheDocument()
  107. // 检查按钮
  108. expect(screen.getByText('取消')).toBeInTheDocument()
  109. expect(screen.getByText('确定')).toBeInTheDocument()
  110. // 等待数据加载
  111. await waitFor(() => {
  112. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  113. })
  114. })
  115. test('应该显示自定义标题', () => {
  116. const onClose = jest.fn()
  117. const onConfirm = jest.fn()
  118. render(
  119. <Wrapper>
  120. <AreaPicker
  121. visible={true}
  122. onClose={onClose}
  123. onConfirm={onConfirm}
  124. title="选择出发地"
  125. />
  126. </Wrapper>
  127. )
  128. expect(screen.getByText('选择出发地')).toBeInTheDocument()
  129. })
  130. test('应该初始化选择值', async () => {
  131. const onClose = jest.fn()
  132. const onConfirm = jest.fn()
  133. render(
  134. <Wrapper>
  135. <AreaPicker
  136. visible={true}
  137. onClose={onClose}
  138. onConfirm={onConfirm}
  139. value={[1, 11, 101]}
  140. />
  141. </Wrapper>
  142. )
  143. // 等待数据加载
  144. await waitFor(() => {
  145. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  146. })
  147. // 检查是否调用了城市和区县查询
  148. await waitFor(() => {
  149. expect(mockAreaClient.cities.$get).toHaveBeenCalledWith({
  150. query: { provinceId: 1, page: 1, pageSize: 50 }
  151. })
  152. })
  153. await waitFor(() => {
  154. expect(mockAreaClient.districts.$get).toHaveBeenCalledWith({
  155. query: { cityId: 11, page: 1, pageSize: 50 }
  156. })
  157. })
  158. })
  159. test('应该处理取消操作', () => {
  160. const onClose = jest.fn()
  161. const onConfirm = jest.fn()
  162. render(
  163. <Wrapper>
  164. <AreaPicker
  165. visible={true}
  166. onClose={onClose}
  167. onConfirm={onConfirm}
  168. />
  169. </Wrapper>
  170. )
  171. const cancelButton = screen.getByText('取消')
  172. fireEvent.click(cancelButton)
  173. expect(onClose).toHaveBeenCalledTimes(1)
  174. expect(onConfirm).not.toHaveBeenCalled()
  175. })
  176. test('应该处理确认操作', async () => {
  177. const onClose = jest.fn()
  178. const onConfirm = jest.fn()
  179. render(
  180. <Wrapper>
  181. <AreaPicker
  182. visible={true}
  183. onClose={onClose}
  184. onConfirm={onConfirm}
  185. />
  186. </Wrapper>
  187. )
  188. // 等待数据加载
  189. await waitFor(() => {
  190. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  191. })
  192. const confirmButton = screen.getByText('确定')
  193. fireEvent.click(confirmButton)
  194. // 由于没有选择任何值,应该传递空数组
  195. expect(onConfirm).toHaveBeenCalledWith([], [])
  196. expect(onClose).toHaveBeenCalledTimes(1)
  197. })
  198. test('当不可见时应该返回 null', () => {
  199. const onClose = jest.fn()
  200. const onConfirm = jest.fn()
  201. const { container } = render(
  202. <Wrapper>
  203. <AreaPicker
  204. visible={false}
  205. onClose={onClose}
  206. onConfirm={onConfirm}
  207. />
  208. </Wrapper>
  209. )
  210. // 检查容器是否为空
  211. expect(container.firstChild).toBeNull()
  212. })
  213. test('应该处理 API 错误', async () => {
  214. // 模拟 API 错误
  215. mockAreaClient.provinces.$get.mockResolvedValue({
  216. status: 500,
  217. json: async () => ({ success: false, message: '服务器错误' })
  218. })
  219. const onClose = jest.fn()
  220. const onConfirm = jest.fn()
  221. render(
  222. <Wrapper>
  223. <AreaPicker
  224. visible={true}
  225. onClose={onClose}
  226. onConfirm={onConfirm}
  227. />
  228. </Wrapper>
  229. )
  230. // 组件应该正常渲染,即使 API 调用失败
  231. expect(screen.getByText('选择地区')).toBeInTheDocument()
  232. // 等待 API 调用
  233. await waitFor(() => {
  234. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  235. })
  236. })
  237. test('应该正确显示已选择的省市区文本', async () => {
  238. const onClose = jest.fn()
  239. const onConfirm = jest.fn()
  240. render(
  241. <Wrapper>
  242. <AreaPicker
  243. visible={true}
  244. onClose={onClose}
  245. onConfirm={onConfirm}
  246. value={[1, 11, 101]}
  247. />
  248. </Wrapper>
  249. )
  250. // 等待数据加载
  251. await waitFor(() => {
  252. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  253. })
  254. await waitFor(() => {
  255. expect(mockAreaClient.cities.$get).toHaveBeenCalled()
  256. })
  257. await waitFor(() => {
  258. expect(mockAreaClient.districts.$get).toHaveBeenCalled()
  259. })
  260. // 检查显示文本
  261. await waitFor(() => {
  262. expect(screen.getByText('北京市 北京市 朝阳区')).toBeInTheDocument()
  263. })
  264. })
  265. })