AreaCascader.test.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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 { AreaCascader } from '../../src/components/AreaCascader'
  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('AreaCascader 组件', () => {
  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 onChange = jest.fn()
  91. render(
  92. <Wrapper>
  93. <AreaCascader onChange={onChange} />
  94. </Wrapper>
  95. )
  96. // 检查选择器标签
  97. expect(screen.getByText('省份')).toBeInTheDocument()
  98. expect(screen.getByText('城市')).toBeInTheDocument()
  99. expect(screen.getByText('区县')).toBeInTheDocument()
  100. // 检查默认占位符
  101. expect(screen.getByText('请选择省市区')).toBeInTheDocument()
  102. // 等待数据加载
  103. await waitFor(() => {
  104. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  105. })
  106. })
  107. test('应该显示自定义占位符', () => {
  108. const onChange = jest.fn()
  109. render(
  110. <Wrapper>
  111. <AreaCascader
  112. onChange={onChange}
  113. placeholder="选择出发地"
  114. />
  115. </Wrapper>
  116. )
  117. expect(screen.getByText('选择出发地')).toBeInTheDocument()
  118. })
  119. test('应该初始化选择值', async () => {
  120. const onChange = jest.fn()
  121. render(
  122. <Wrapper>
  123. <AreaCascader
  124. value={[1, 11, 101]}
  125. onChange={onChange}
  126. />
  127. </Wrapper>
  128. )
  129. // 等待数据加载
  130. await waitFor(() => {
  131. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  132. })
  133. // 检查是否调用了城市和区县查询
  134. await waitFor(() => {
  135. expect(mockAreaClient.cities.$get).toHaveBeenCalledWith({
  136. query: { provinceId: 1 }
  137. })
  138. })
  139. await waitFor(() => {
  140. expect(mockAreaClient.districts.$get).toHaveBeenCalledWith({
  141. query: { cityId: 11 }
  142. })
  143. })
  144. })
  145. test('应该处理省份选择', async () => {
  146. const onChange = jest.fn()
  147. render(
  148. <Wrapper>
  149. <AreaCascader onChange={onChange} />
  150. </Wrapper>
  151. )
  152. // 等待数据加载
  153. await waitFor(() => {
  154. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  155. })
  156. // 模拟选择省份
  157. const provincePicker = screen.getByText('请选择省份')
  158. fireEvent.click(provincePicker)
  159. // 这里我们模拟 Picker 的 onChange 事件
  160. // 在实际测试中,可能需要更复杂的模拟
  161. })
  162. test('应该正确显示已选择的省市区文本', async () => {
  163. const onChange = jest.fn()
  164. render(
  165. <Wrapper>
  166. <AreaCascader
  167. value={[1, 11, 101]}
  168. onChange={onChange}
  169. />
  170. </Wrapper>
  171. )
  172. // 等待数据加载
  173. await waitFor(() => {
  174. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  175. })
  176. await waitFor(() => {
  177. expect(mockAreaClient.cities.$get).toHaveBeenCalled()
  178. })
  179. await waitFor(() => {
  180. expect(mockAreaClient.districts.$get).toHaveBeenCalled()
  181. })
  182. // 检查显示文本
  183. await waitFor(() => {
  184. expect(screen.getByText('北京市 北京市 朝阳区')).toBeInTheDocument()
  185. })
  186. })
  187. test('应该处理部分选择的情况', async () => {
  188. const onChange = jest.fn()
  189. render(
  190. <Wrapper>
  191. <AreaCascader
  192. value={[1, 11]} // 只选择了省份和城市
  193. onChange={onChange}
  194. />
  195. </Wrapper>
  196. )
  197. // 等待数据加载
  198. await waitFor(() => {
  199. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  200. })
  201. await waitFor(() => {
  202. expect(mockAreaClient.cities.$get).toHaveBeenCalled()
  203. })
  204. // 检查显示文本
  205. await waitFor(() => {
  206. expect(screen.getByText('北京市 北京市')).toBeInTheDocument()
  207. })
  208. })
  209. test('应该处理只有省份选择的情况', async () => {
  210. const onChange = jest.fn()
  211. render(
  212. <Wrapper>
  213. <AreaCascader
  214. value={[1]} // 只选择了省份
  215. onChange={onChange}
  216. />
  217. </Wrapper>
  218. )
  219. // 等待数据加载
  220. await waitFor(() => {
  221. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  222. })
  223. // 检查显示文本
  224. await waitFor(() => {
  225. expect(screen.getByText('北京市')).toBeInTheDocument()
  226. })
  227. })
  228. test('应该处理 API 错误', async () => {
  229. // 模拟 API 错误
  230. mockAreaClient.provinces.$get.mockResolvedValue({
  231. status: 500,
  232. json: async () => ({ success: false, message: '服务器错误' })
  233. })
  234. const onChange = jest.fn()
  235. render(
  236. <Wrapper>
  237. <AreaCascader onChange={onChange} />
  238. </Wrapper>
  239. )
  240. // 组件应该正常渲染,即使 API 调用失败
  241. expect(screen.getByText('省份')).toBeInTheDocument()
  242. // 等待 API 调用
  243. await waitFor(() => {
  244. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  245. })
  246. })
  247. test('应该禁用城市选择器当没有选择省份时', async () => {
  248. const onChange = jest.fn()
  249. render(
  250. <Wrapper>
  251. <AreaCascader onChange={onChange} />
  252. </Wrapper>
  253. )
  254. // 等待数据加载
  255. await waitFor(() => {
  256. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  257. })
  258. // 检查城市选择器是否被禁用
  259. const cityPicker = screen.getByText('请选择城市')
  260. expect(cityPicker.parentElement).toHaveClass('bg-gray-100')
  261. expect(cityPicker.parentElement).toHaveClass('text-gray-400')
  262. })
  263. test('应该禁用区县选择器当没有选择城市时', async () => {
  264. const onChange = jest.fn()
  265. render(
  266. <Wrapper>
  267. <AreaCascader onChange={onChange} />
  268. </Wrapper>
  269. )
  270. // 等待数据加载
  271. await waitFor(() => {
  272. expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
  273. })
  274. // 检查区县选择器是否被禁用
  275. const districtPicker = screen.getByText('请选择区县')
  276. expect(districtPicker.parentElement).toHaveClass('bg-gray-100')
  277. expect(districtPicker.parentElement).toHaveClass('text-gray-400')
  278. })
  279. })