| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- import { render, fireEvent, waitFor } from '@testing-library/react'
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
- import { mockShowModal, mockShowToast, mockGetNetworkType, mockGetEnv } from '~/__mocks__/taroMock'
- import OrderButtonBar from '@/components/order/OrderButtonBar'
- // Mock API client
- jest.mock('@/api', () => ({
- orderClient: {
- cancelOrder: {
- $post: jest.fn()
- }
- }
- }))
- // Mock CancelReasonDialog 组件
- jest.mock('@/components/common/CancelReasonDialog', () => {
- const React = require('react')
- const MockCancelReasonDialog = ({ open, onOpenChange, onConfirm, loading }: any) => {
- const [reason, setReason] = React.useState('')
- const [error, setError] = React.useState('')
- if (!open) return null
- const handleConfirm = () => {
- const trimmedReason = reason.trim()
- if (!trimmedReason) {
- setError('请输入取消原因')
- return
- }
- if (trimmedReason.length < 5) {
- setError('取消原因至少需要5个字符')
- return
- }
- if (trimmedReason.length > 200) {
- setError('取消原因不能超过200个字符')
- return
- }
- setError('')
- onConfirm(trimmedReason)
- }
- // 预定义原因选项
- const CANCEL_REASONS = [
- '我不想买了',
- '信息填写错误,重新下单',
- '商家缺货',
- '价格不合适',
- '其他原因'
- ]
- return React.createElement('div', { 'data-testid': 'cancel-reason-dialog' }, [
- React.createElement('div', { key: 'title' }, '取消订单'),
- React.createElement('div', { key: 'description' }, '请选择或填写取消原因,这将帮助我们改进服务'),
- // 预定义原因选项
- React.createElement('div', { key: 'reasons' },
- CANCEL_REASONS.map((reasonText, index) =>
- React.createElement('div', {
- key: index,
- onClick: () => {
- setReason(reasonText)
- if (error) setError('')
- }
- }, reasonText)
- )
- ),
- React.createElement('input', {
- key: 'reason-input',
- placeholder: '请输入其他取消原因...',
- value: reason,
- onChange: (e: any) => {
- setReason(e.target.value)
- if (error) setError('')
- }
- }),
- error && React.createElement('div', { key: 'error', 'data-testid': 'error-message' }, error),
- React.createElement('button', {
- key: 'confirm',
- onClick: handleConfirm,
- disabled: loading
- }, loading ? '提交中...' : '确认取消'),
- React.createElement('button', {
- key: 'cancel',
- onClick: () => onOpenChange(false)
- }, '取消')
- ])
- }
- MockCancelReasonDialog.displayName = 'MockCancelReasonDialog'
- return MockCancelReasonDialog
- })
- const mockOrder = {
- id: 1,
- orderNo: 'ORDER001',
- payState: 0, // 未支付
- state: 0, // 未发货
- amount: 100,
- payAmount: 100,
- freightAmount: 0,
- discountAmount: 0,
- goodsDetail: JSON.stringify([
- { id: 1, name: '商品1', price: 50, num: 2, image: '', spec: '默认规格' }
- ]),
- recevierName: '张三',
- receiverMobile: '13800138000',
- address: '北京市朝阳区',
- createdAt: '2025-01-01T00:00:00Z'
- }
- const createTestQueryClient = () => new QueryClient({
- defaultOptions: {
- queries: { retry: false },
- mutations: { retry: false }
- }
- })
- const TestWrapper = ({ children }: { children: React.ReactNode }) => (
- <QueryClientProvider client={createTestQueryClient()}>
- {children}
- </QueryClientProvider>
- )
- describe('OrderButtonBar', () => {
- beforeEach(() => {
- jest.clearAllMocks()
- // 模拟网络检查成功回调
- mockGetNetworkType.mockImplementation((options) => {
- if (options?.success) {
- options.success({ networkType: 'wifi' })
- }
- return Promise.resolve()
- })
- // 模拟环境检查
- mockGetEnv.mockReturnValue('WEB')
- })
- it('should render cancel button for unpaid order', () => {
- const { getByText } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- expect(getByText('取消订单')).toBeTruthy()
- expect(getByText('去支付')).toBeTruthy()
- expect(getByText('查看详情')).toBeTruthy()
- })
- it('should show cancel reason dialog when cancel button is clicked', async () => {
- const { getByText, getByTestId } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(getByTestId('cancel-reason-dialog')).toBeTruthy()
- // 检查对话框内容
- expect(getByText('请选择或填写取消原因,这将帮助我们改进服务')).toBeTruthy()
- })
- })
- it('should call API when cancel order is confirmed', async () => {
- const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
- mockShowModal.mockResolvedValueOnce({ confirm: true }) // 确认取消
- mockApiCall.mockResolvedValue({ status: 200, json: () => Promise.resolve({ success: true, message: '取消成功' }) })
- const { getByText, getByPlaceholderText, getByTestId } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- // 打开取消对话框
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(getByTestId('cancel-reason-dialog')).toBeTruthy()
- })
- // 输入取消原因
- const reasonInput = getByPlaceholderText('请输入其他取消原因...')
- fireEvent.change(reasonInput, { target: { value: '测试取消原因' } })
- // 点击确认取消按钮
- fireEvent.click(getByText('确认取消'))
- await waitFor(() => {
- expect(mockShowModal).toHaveBeenCalledWith({
- title: '确认取消',
- content: '确定要取消订单吗?\n取消原因:测试取消原因',
- success: expect.any(Function)
- })
- })
- // 模拟确认对话框确认
- const modalCall = mockShowModal.mock.calls[0][0]
- if (modalCall.success) {
- modalCall.success({ confirm: true })
- }
- await waitFor(() => {
- expect(mockApiCall).toHaveBeenCalledWith({
- json: {
- orderId: 1,
- reason: '测试取消原因'
- }
- })
- })
- })
- it('should show error when cancel reason is empty', async () => {
- const { getByText, getByTestId } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- // 打开取消对话框
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(getByTestId('cancel-reason-dialog')).toBeTruthy()
- })
- // 直接点击确认取消按钮(不输入原因)
- fireEvent.click(getByText('确认取消'))
- await waitFor(() => {
- expect(getByTestId('error-message')).toBeTruthy()
- expect(getByText('请输入取消原因')).toBeTruthy()
- })
- })
- it('should handle network error gracefully', async () => {
- const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
- mockShowModal.mockResolvedValueOnce({ confirm: true })
- mockApiCall.mockRejectedValue(new Error('网络连接失败'))
- const { getByText, getByPlaceholderText, getByTestId } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- // 打开取消对话框
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(getByTestId('cancel-reason-dialog')).toBeTruthy()
- })
- // 输入取消原因
- const reasonInput = getByPlaceholderText('请输入其他取消原因...')
- fireEvent.change(reasonInput, { target: { value: '测试取消原因' } })
- // 点击确认取消按钮
- fireEvent.click(getByText('确认取消'))
- // 模拟确认对话框确认
- await waitFor(() => {
- expect(mockShowModal).toHaveBeenCalledWith({
- title: '确认取消',
- content: '确定要取消订单吗?\n取消原因:测试取消原因',
- success: expect.any(Function)
- })
- })
- const modalCall = mockShowModal.mock.calls[0][0]
- if (modalCall.success) {
- modalCall.success({ confirm: true })
- }
- await waitFor(() => {
- expect(mockShowToast).toHaveBeenCalledWith({
- title: '网络连接失败,请检查网络后重试',
- icon: 'error',
- duration: 3000
- })
- })
- })
- it('should disable cancel button during mutation', async () => {
- // 模拟mutation正在进行中
- const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
- mockApiCall.mockImplementation(() => new Promise(() => {})) // 永不resolve的promise
- const { getByText, getByPlaceholderText, getByTestId } = render(
- <TestWrapper>
- <OrderButtonBar order={mockOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- // 打开取消对话框
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(getByTestId('cancel-reason-dialog')).toBeTruthy()
- })
- // 输入取消原因
- const reasonInput = getByPlaceholderText('请输入其他取消原因...')
- fireEvent.change(reasonInput, { target: { value: '测试取消原因' } })
- // 点击确认取消按钮
- fireEvent.click(getByText('确认取消'))
- // 模拟确认对话框确认
- await waitFor(() => {
- expect(mockShowModal).toHaveBeenCalledWith({
- title: '确认取消',
- content: '确定要取消订单吗?\n取消原因:测试取消原因',
- success: expect.any(Function)
- })
- })
- const modalCall = mockShowModal.mock.calls[0][0]
- if (modalCall.success) {
- modalCall.success({ confirm: true })
- }
- // 检查按钮状态
- await waitFor(() => {
- expect(getByText('取消中...')).toBeTruthy()
- })
- })
- it('should not show cancel button for shipped order', () => {
- const shippedOrder = {
- ...mockOrder,
- payState: 2, // 已支付
- state: 1 // 已发货
- }
- const { queryByText } = render(
- <TestWrapper>
- <OrderButtonBar order={shippedOrder} onViewDetail={jest.fn()} />
- </TestWrapper>
- )
- expect(queryByText('取消订单')).toBeNull()
- expect(queryByText('确认收货')).toBeTruthy()
- })
- it('should use external cancel handler when provided', async () => {
- const mockOnCancelOrder = jest.fn()
- const { getByText } = render(
- <TestWrapper>
- <OrderButtonBar
- order={mockOrder}
- onViewDetail={jest.fn()}
- onCancelOrder={mockOnCancelOrder}
- />
- </TestWrapper>
- )
- fireEvent.click(getByText('取消订单'))
- await waitFor(() => {
- expect(mockOnCancelOrder).toHaveBeenCalled()
- })
- })
- })
|