area-select.integration.test.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react';
  3. import userEvent from '@testing-library/user-event';
  4. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  5. import { AreaSelect } from '../../src/components/areas/composite/AreaSelect';
  6. // Mock API 调用 - 注意:新的组件使用相对路径导入api
  7. vi.mock('../../src/api/areaClient', () => ({
  8. areaClientManager: {
  9. get: vi.fn(() => ({
  10. index: {
  11. $get: vi.fn(async ({ query }) => {
  12. const filters = JSON.parse(query.filters);
  13. if (filters.level === 1) {
  14. // 省份数据
  15. return {
  16. status: 200,
  17. json: async () => ({
  18. data: [
  19. { id: 1, name: '北京市', level: 1, parentId: null },
  20. { id: 2, name: '上海市', level: 1, parentId: null }
  21. ]
  22. })
  23. };
  24. } else if (filters.level === 2 && filters.parentId === 1) {
  25. // 北京市的城市数据
  26. return {
  27. status: 200,
  28. json: async () => ({
  29. data: [
  30. { id: 3, name: '北京市', level: 2, parentId: 1 }
  31. ]
  32. })
  33. };
  34. } else if (filters.level === 2 && filters.parentId === 2) {
  35. // 上海市的城市数据
  36. return {
  37. status: 200,
  38. json: async () => ({
  39. data: [
  40. { id: 4, name: '上海市', level: 2, parentId: 2 }
  41. ]
  42. })
  43. };
  44. } else if (filters.level === 3 && filters.parentId === 3) {
  45. // 北京市的区县数据
  46. return {
  47. status: 200,
  48. json: async () => ({
  49. data: [
  50. { id: 5, name: '东城区', level: 3, parentId: 3 },
  51. { id: 6, name: '西城区', level: 3, parentId: 3 }
  52. ]
  53. })
  54. };
  55. } else if (filters.level === 3 && filters.parentId === 4) {
  56. // 上海市的区县数据
  57. return {
  58. status: 200,
  59. json: async () => ({
  60. data: [
  61. { id: 7, name: '黄浦区', level: 3, parentId: 4 },
  62. { id: 8, name: '徐汇区', level: 3, parentId: 4 }
  63. ]
  64. })
  65. };
  66. }
  67. return {
  68. status: 200,
  69. json: async () => ({ data: [] })
  70. };
  71. })
  72. }
  73. }))
  74. }
  75. }));
  76. // 创建测试用的 QueryClient
  77. const createTestQueryClient = () => new QueryClient({
  78. defaultOptions: {
  79. queries: {
  80. retry: false,
  81. },
  82. },
  83. });
  84. // 测试组件包装器
  85. const TestWrapper = ({ children }: { children: React.ReactNode }) => {
  86. return (
  87. <QueryClientProvider client={createTestQueryClient()}>
  88. {children}
  89. </QueryClientProvider>
  90. );
  91. };
  92. describe('AreaSelect 集成测试', () => {
  93. beforeEach(() => {
  94. vi.clearAllMocks();
  95. });
  96. it('应该正确渲染 AreaSelect 组件', async () => {
  97. render(
  98. <TestWrapper>
  99. <AreaSelect />
  100. </TestWrapper>
  101. );
  102. // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
  103. await waitFor(() => {
  104. expect(screen.getByText('选择所在省份')).toBeInTheDocument();
  105. });
  106. expect(screen.getByText('省份')).toBeInTheDocument();
  107. expect(screen.getByText('城市')).toBeInTheDocument();
  108. expect(screen.getByText('区县')).toBeInTheDocument();
  109. });
  110. it('应该显示必填标记当 required=true 时', async () => {
  111. render(
  112. <TestWrapper>
  113. <AreaSelect required={true} />
  114. </TestWrapper>
  115. );
  116. // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
  117. await waitFor(() => {
  118. expect(screen.getByText('选择所在省份')).toBeInTheDocument();
  119. });
  120. // 检查省份标签是否包含星号
  121. const provinceLabel = screen.getByText('省份');
  122. expect(provinceLabel.innerHTML).toContain('*');
  123. // 城市和区县不应该显示星号(初始状态)
  124. const cityLabel = screen.getByText('城市');
  125. expect(cityLabel.innerHTML).not.toContain('*');
  126. const districtLabel = screen.getByText('区县');
  127. expect(districtLabel.innerHTML).not.toContain('*');
  128. });
  129. it('应该正确处理省份选择并加载城市数据', async () => {
  130. const user = userEvent.setup();
  131. const handleChange = vi.fn();
  132. render(
  133. <TestWrapper>
  134. <AreaSelect onChange={handleChange} />
  135. </TestWrapper>
  136. );
  137. // 使用test ID查找省份选择框
  138. const provinceSelect = screen.getByTestId('area-select-province');
  139. // 等待省份数据加载并启用
  140. await waitFor(() => {
  141. expect(provinceSelect).toBeInTheDocument();
  142. expect(provinceSelect).not.toHaveAttribute('disabled');
  143. });
  144. await user.click(provinceSelect);
  145. // 选择北京市 - 尝试直接设置隐藏的select元素的值
  146. // 查找隐藏的select元素
  147. const hiddenSelect = screen.getByTestId('area-select-province').parentElement?.querySelector('select[aria-hidden="true"]');
  148. if (hiddenSelect) {
  149. await user.selectOptions(hiddenSelect, '1');
  150. // 触发change事件
  151. fireEvent.change(hiddenSelect, { target: { value: '1' } });
  152. }
  153. // 验证onChange被调用
  154. expect(handleChange).toHaveBeenCalledWith({
  155. provinceId: 1,
  156. cityId: undefined,
  157. districtId: undefined
  158. });
  159. // 验证城市选择框应该被启用(因为选择了省份)
  160. const citySelect = screen.getByTestId('area-select-city');
  161. expect(citySelect).toBeInTheDocument();
  162. });
  163. it('应该正确处理城市选择并加载区县数据', async () => {
  164. const user = userEvent.setup();
  165. const handleChange = vi.fn();
  166. render(
  167. <TestWrapper>
  168. <AreaSelect onChange={handleChange} />
  169. </TestWrapper>
  170. );
  171. // 先选择省份(北京市)
  172. const provinceSelect = screen.getByTestId('area-select-province');
  173. // 等待省份数据加载并启用
  174. await waitFor(() => {
  175. expect(provinceSelect).toBeInTheDocument();
  176. expect(provinceSelect).not.toHaveAttribute('disabled');
  177. });
  178. await user.click(provinceSelect);
  179. await waitFor(() => {
  180. expect(screen.getByText('北京市')).toBeInTheDocument();
  181. });
  182. await user.click(screen.getByText('北京市'));
  183. // 等待城市数据加载并启用
  184. const citySelect = screen.getByTestId('area-select-city');
  185. await waitFor(() => {
  186. expect(citySelect).toBeInTheDocument();
  187. expect(citySelect).not.toHaveAttribute('disabled');
  188. }, { timeout: 2000 });
  189. // 点击城市选择框
  190. await user.click(citySelect);
  191. // 选择北京市(城市)- 尝试直接设置隐藏的select元素的值
  192. // 查找隐藏的select元素
  193. const cityHiddenSelect = screen.getByTestId('area-select-city').parentElement?.querySelector('select[aria-hidden="true"]');
  194. if (cityHiddenSelect) {
  195. await user.selectOptions(cityHiddenSelect, '3');
  196. }
  197. // 验证onChange被调用
  198. expect(handleChange).toHaveBeenCalledWith({
  199. provinceId: 1,
  200. cityId: 3,
  201. districtId: undefined
  202. });
  203. // 验证区县选择框应该被启用(因为选择了城市)
  204. const districtSelect = screen.getByTestId('area-select-district');
  205. expect(districtSelect).toBeInTheDocument();
  206. });
  207. it('应该正确处理区县选择', async () => {
  208. const user = userEvent.setup();
  209. const handleChange = vi.fn();
  210. render(
  211. <TestWrapper>
  212. <AreaSelect onChange={handleChange} />
  213. </TestWrapper>
  214. );
  215. // 先选择省份(北京市)
  216. const provinceSelect = screen.getByTestId('area-select-province');
  217. // 等待省份数据加载并启用
  218. await waitFor(() => {
  219. expect(provinceSelect).toBeInTheDocument();
  220. expect(provinceSelect).not.toHaveAttribute('disabled');
  221. });
  222. await user.click(provinceSelect);
  223. // 选择北京市 - 尝试直接设置隐藏的select元素的值
  224. // 查找隐藏的select元素
  225. const provinceHiddenSelect = screen.getByTestId('area-select-province').parentElement?.querySelector('select[aria-hidden="true"]');
  226. if (provinceHiddenSelect) {
  227. await user.selectOptions(provinceHiddenSelect, '1');
  228. }
  229. // 等待城市数据加载并启用
  230. const citySelect = screen.getByTestId('area-select-city');
  231. await waitFor(() => {
  232. expect(citySelect).toBeInTheDocument();
  233. expect(citySelect).not.toHaveAttribute('disabled');
  234. }, { timeout: 2000 });
  235. await user.click(citySelect);
  236. // 选择北京市(城市)- 尝试直接设置隐藏的select元素的值
  237. // 查找隐藏的select元素
  238. const cityHiddenSelect = screen.getByTestId('area-select-city').parentElement?.querySelector('select[aria-hidden="true"]');
  239. if (cityHiddenSelect) {
  240. await user.selectOptions(cityHiddenSelect, '3');
  241. }
  242. // 等待区县数据加载并启用
  243. const districtSelect = screen.getByTestId('area-select-district');
  244. await waitFor(() => {
  245. expect(districtSelect).toBeInTheDocument();
  246. expect(districtSelect).not.toHaveAttribute('disabled');
  247. }, { timeout: 2000 });
  248. // 点击区县选择框
  249. await user.click(districtSelect);
  250. // 选择东城区 - 尝试直接设置隐藏的select元素的值
  251. // 查找隐藏的select元素
  252. const districtHiddenSelect = screen.getByTestId('area-select-district').parentElement?.querySelector('select[aria-hidden="true"]');
  253. if (districtHiddenSelect) {
  254. await user.selectOptions(districtHiddenSelect, '5');
  255. }
  256. // 验证onChange被调用
  257. expect(handleChange).toHaveBeenCalledWith({
  258. provinceId: 1,
  259. cityId: 3,
  260. districtId: 5
  261. });
  262. });
  263. it('应该支持禁用状态', async () => {
  264. render(
  265. <TestWrapper>
  266. <AreaSelect disabled={true} />
  267. </TestWrapper>
  268. );
  269. // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
  270. await waitFor(() => {
  271. expect(screen.getByText('选择所在省份')).toBeInTheDocument();
  272. });
  273. // 检查组件渲染正常
  274. expect(screen.getByText('省份')).toBeInTheDocument();
  275. expect(screen.getByText('城市')).toBeInTheDocument();
  276. expect(screen.getByText('区县')).toBeInTheDocument();
  277. });
  278. it('应该正确处理初始值', async () => {
  279. const initialValue = {
  280. provinceId: 1,
  281. cityId: 3,
  282. districtId: 5
  283. };
  284. render(
  285. <TestWrapper>
  286. <AreaSelect value={initialValue} />
  287. </TestWrapper>
  288. );
  289. // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
  290. await waitFor(() => {
  291. expect(screen.getByText('选择所在省份')).toBeInTheDocument();
  292. });
  293. // 组件应该能正常渲染,初始值会在组件内部处理
  294. expect(screen.getByText('省份')).toBeInTheDocument();
  295. expect(screen.getByText('城市')).toBeInTheDocument();
  296. expect(screen.getByText('区县')).toBeInTheDocument();
  297. });
  298. it('当选择新省份时应该清空城市和区县选择', async () => {
  299. const user = userEvent.setup();
  300. const handleChange = vi.fn();
  301. render(
  302. <TestWrapper>
  303. <AreaSelect onChange={handleChange} />
  304. </TestWrapper>
  305. );
  306. // 先选择省份(北京市)
  307. const provinceSelect = screen.getByTestId('area-select-province');
  308. // 等待省份数据加载并启用
  309. await waitFor(() => {
  310. expect(provinceSelect).toBeInTheDocument();
  311. expect(provinceSelect).not.toHaveAttribute('disabled');
  312. });
  313. await user.click(provinceSelect);
  314. // 选择北京市 - 尝试直接设置隐藏的select元素的值
  315. // 查找隐藏的select元素
  316. const provinceHiddenSelect = screen.getByTestId('area-select-province').parentElement?.querySelector('select[aria-hidden="true"]');
  317. if (provinceHiddenSelect) {
  318. await user.selectOptions(provinceHiddenSelect, '1');
  319. }
  320. // 等待城市数据加载并启用
  321. const citySelect = screen.getByTestId('area-select-city');
  322. await waitFor(() => {
  323. expect(citySelect).toBeInTheDocument();
  324. expect(citySelect).not.toHaveAttribute('disabled');
  325. }, { timeout: 2000 });
  326. await user.click(citySelect);
  327. // 选择北京市(城市)- 尝试直接设置隐藏的select元素的值
  328. // 查找隐藏的select元素
  329. const cityHiddenSelect = screen.getByTestId('area-select-city').parentElement?.querySelector('select[aria-hidden="true"]');
  330. if (cityHiddenSelect) {
  331. await user.selectOptions(cityHiddenSelect, '3');
  332. }
  333. // 等待区县数据加载并启用
  334. const districtSelect = screen.getByTestId('area-select-district');
  335. await waitFor(() => {
  336. expect(districtSelect).toBeInTheDocument();
  337. expect(districtSelect).not.toHaveAttribute('disabled');
  338. }, { timeout: 2000 });
  339. await user.click(districtSelect);
  340. // 选择东城区 - 尝试直接设置隐藏的select元素的值
  341. // 查找隐藏的select元素
  342. const districtHiddenSelect = screen.getByTestId('area-select-district').parentElement?.querySelector('select[aria-hidden="true"]');
  343. if (districtHiddenSelect) {
  344. await user.selectOptions(districtHiddenSelect, '5');
  345. }
  346. // 验证已经选择了完整的省市区
  347. expect(handleChange).toHaveBeenLastCalledWith({
  348. provinceId: 1,
  349. cityId: 3,
  350. districtId: 5
  351. });
  352. // 现在选择新的省份(上海市)
  353. await user.click(screen.getByTestId('area-select-province')); // 重新打开省份选择
  354. await waitFor(() => {
  355. expect(screen.getByText('上海市')).toBeInTheDocument();
  356. });
  357. await user.click(screen.getByText('上海市'));
  358. // 验证onChange被调用,城市和区县被清空
  359. expect(handleChange).toHaveBeenLastCalledWith({
  360. provinceId: 2,
  361. cityId: undefined,
  362. districtId: undefined
  363. });
  364. // 验证城市选择框应该显示"选择城市"(被清空)
  365. expect(screen.getByTestId('area-select-city')).toBeInTheDocument();
  366. // 验证区县选择框应该显示"选择区县"(被清空)
  367. expect(screen.getByTestId('area-select-district')).toBeInTheDocument();
  368. });
  369. });