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) => (
),
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()
})
})
})