import React from 'react' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import AddressEditPage from '@/pages/address-edit/index' // 导入Taro mock函数 import { mockUseRouter, mockNavigateBack, mockShowToast } from '~/__mocks__/taroMock' // Mock API client jest.mock('@/api', () => ({ deliveryAddressClient: { $post: jest.fn(), ':id': { $get: jest.fn(), $put: jest.fn(), }, }, })) // Mock auth hook jest.mock('@/utils/auth', () => ({ useAuth: () => ({ user: { id: 1 }, }), })) // Mock react-hook-form let mockFormData = { name: '', phone: '', province: undefined as number | undefined, city: undefined as number | undefined, district: undefined as number | undefined, town: undefined as number | undefined, address: '', isDefault: false } const mockHandleSubmit = jest.fn((onSubmit, onError) => (e: any) => { e?.preventDefault?.() // 模拟验证通过,调用onSubmit onSubmit(mockFormData) }) const mockReset = jest.fn((data: any) => { if (data) { mockFormData = { ...mockFormData, ...data } } }) const mockSetValue = jest.fn((name: string, value: any) => { mockFormData = { ...mockFormData, [name]: value } }) const mockWatch = jest.fn() jest.mock('react-hook-form', () => ({ useForm: () => ({ formState: { isValid: true, errors: {}, }, handleSubmit: mockHandleSubmit, reset: mockReset, setValue: mockSetValue, watch: mockWatch, getValues: () => mockFormData, }), })) // Mock components jest.mock('@/components/ui/navbar', () => ({ Navbar: ({ title, onClickLeft }: { title: string; onClickLeft: () => void }) => (
{title}
), })) jest.mock('@/components/ui/button', () => ({ Button: ({ children, onClick, disabled, className }: any) => ( ), })) jest.mock('@/components/ui/card', () => ({ Card: ({ children, className }: any) => (
{children}
), })) jest.mock('@/components/ui/form', () => ({ Form: ({ children, ...props }: any) => (
{children}
), FormField: ({ name, render }: any) => (
{render({ field: { value: '', onChange: jest.fn() } })}
), FormItem: ({ children }: any) =>
{children}
, FormLabel: ({ children }: any) => , FormControl: ({ children }: any) =>
{children}
, FormMessage: () =>
错误消息
, })) jest.mock('@/components/ui/input', () => ({ Input: ({ placeholder, ...props }: any) => ( ), })) jest.mock('@/components/ui/switch', () => ({ Switch: ({ checked, onChange, color }: any) => ( ), })) jest.mock('@/components/ui/city-selector', () => ({ CitySelector: ({ provinceValue, cityValue, districtValue, townValue, onProvinceChange, onCityChange, onDistrictChange, onTownChange, showLabels }: any) => (
省份: {provinceValue}
城市: {cityValue}
区县: {districtValue}
乡镇: {townValue}
), })) describe('AddressEditPage', () => { let queryClient: QueryClient beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) // Reset all mocks jest.clearAllMocks() // 重置表单数据 mockFormData = { name: '', phone: '', province: undefined, city: undefined, district: undefined, town: undefined, address: '', isDefault: false } // 默认设置无地址ID(添加模式) mockUseRouter.mockReturnValue({ params: {}, }) }) const renderWithProviders = (component: React.ReactElement) => { return render( {component} ) } const mockAddress = { id: 1, name: '张三', phone: '13812345678', receiverProvince: 440000, receiverCity: 440300, receiverDistrict: 440305, receiverTown: 440305001, address: '科技大厦A座', isDefault: 1, } it('渲染添加地址页面标题和布局', async () => { renderWithProviders() expect(screen.getByTestId('navbar')).toBeInTheDocument() expect(screen.getByText('添加地址')).toBeInTheDocument() expect(screen.getByTestId('form')).toBeInTheDocument() }) it('渲染编辑地址页面标题和布局', async () => { const { deliveryAddressClient } = await import('@/api') ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({ status: 200, json: async () => mockAddress, } as any) mockUseRouter.mockReturnValue({ params: { id: '1' }, }) renderWithProviders() await waitFor(() => { expect(screen.getByText('编辑地址')).toBeInTheDocument() expect(deliveryAddressClient[':id'].$get).toHaveBeenCalledWith({ param: { id: 1 } }) }) }) it('填充编辑模式下的表单数据', async () => { const { deliveryAddressClient } = await import('@/api') ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({ status: 200, json: async () => mockAddress, } as any) mockUseRouter.mockReturnValue({ params: { id: '1' }, }) renderWithProviders() await waitFor(() => { expect(deliveryAddressClient[':id'].$get).toHaveBeenCalledWith({ param: { id: 1 } }) }) }) it('提交新建地址表单', async () => { const { deliveryAddressClient } = await import('@/api') const mockResponse = { id: 2, ...mockAddress, isDefault: 0 } ;(deliveryAddressClient.$post as jest.Mock).mockResolvedValue({ status: 201, json: async () => mockResponse, } as any) renderWithProviders() // 找到保存按钮并点击 await waitFor(() => { const saveButton = screen.getByText('保存地址') expect(saveButton).toBeInTheDocument() fireEvent.click(saveButton) }) await waitFor(() => { expect(deliveryAddressClient.$post).toHaveBeenCalled() expect(mockShowToast).toHaveBeenCalledWith({ title: '添加成功', icon: 'success' }) expect(mockNavigateBack).toHaveBeenCalled() }) }) it('提交编辑地址表单', async () => { const { deliveryAddressClient } = await import('@/api') ;(deliveryAddressClient[':id'].$get as jest.Mock).mockResolvedValue({ status: 200, json: async () => mockAddress, } as any) const updatedAddress = { ...mockAddress, name: '李四' } ;(deliveryAddressClient[':id'].$put as jest.Mock).mockResolvedValue({ status: 200, json: async () => updatedAddress, } as any) mockUseRouter.mockReturnValue({ params: { id: '1' }, }) renderWithProviders() // 等待数据加载 await waitFor(() => { expect(deliveryAddressClient[':id'].$get).toHaveBeenCalled() }) // 确保表单已重置 await waitFor(() => { expect(mockReset).toHaveBeenCalled() }) // 找到保存按钮并点击 await waitFor(() => { const saveButton = screen.getByText('保存地址') expect(saveButton).toBeInTheDocument() fireEvent.click(saveButton) }) await waitFor(() => { expect(deliveryAddressClient[':id'].$put).toHaveBeenCalled() // 检查调用参数 const call = deliveryAddressClient[':id'].$put.mock.calls[0] expect(call[0]).toEqual(expect.objectContaining({ param: { id: 1 } })) expect(mockShowToast).toHaveBeenCalledWith({ title: '更新成功', icon: 'success' }) expect(mockNavigateBack).toHaveBeenCalled() }) }) it('显示API错误提示', async () => { const { deliveryAddressClient } = await import('@/api') ;(deliveryAddressClient.$post as jest.Mock).mockResolvedValue({ status: 400, json: async () => ({ message: '保存失败' }), } as any) renderWithProviders() // 找到保存按钮并点击 await waitFor(() => { const saveButton = screen.getByText('保存地址') fireEvent.click(saveButton) }) await waitFor(() => { expect(mockShowToast).toHaveBeenCalledWith({ title: '保存地址失败', icon: 'none' }) }) }) it('验证表单字段渲染', async () => { renderWithProviders() expect(screen.getByText('收货人姓名')).toBeInTheDocument() expect(screen.getByText('手机号码')).toBeInTheDocument() expect(screen.getByText('详细地址')).toBeInTheDocument() expect(screen.getByText('设为默认地址')).toBeInTheDocument() }) it('点击返回按钮', async () => { renderWithProviders() const navbar = screen.getByTestId('navbar') const backButton = navbar.querySelector('button') if (backButton) { fireEvent.click(backButton) expect(mockNavigateBack).toHaveBeenCalled() } }) it('显示保存中的加载状态', async () => { const { deliveryAddressClient } = await import('@/api') let resolvePromise: any const promise = new Promise(resolve => { resolvePromise = resolve }) ;(deliveryAddressClient.$post as jest.Mock).mockReturnValue(promise) renderWithProviders() // 点击保存按钮 await waitFor(() => { const saveButton = screen.getByText('保存地址') fireEvent.click(saveButton) }) // 应该显示加载状态 await waitFor(() => { expect(screen.getByText('保存中...')).toBeInTheDocument() }) // 解析promise resolvePromise({ status: 201, json: async () => ({ id: 1 }), }) await waitFor(() => { expect(screen.getByText('保存地址')).toBeInTheDocument() }) }) })