| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- /**
- * 支付页面额度支付单元测试
- * 测试额度支付选项功能
- */
- import { render, screen, waitFor, fireEvent } from '@testing-library/react'
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
- import PaymentPage from '@/pages/payment/index'
- import { creditBalanceClient } from '@/api'
- import { mockUseRouter, mockRedirectTo, mockShowToast } from '~/__mocks__/taroMock'
- // @tarojs/taro 已经在 jest.config.js 中通过 moduleNameMapper 重定向到 mock 文件
- // 不需要额外 mock
- // Mock API客户端
- jest.mock('@/api', () => ({
- creditBalanceClient: {
- me: {
- $get: jest.fn(),
- },
- payment: {
- $post: jest.fn(),
- },
- },
- paymentClient: {
- payment: {
- $post: jest.fn(),
- },
- },
- }))
- // Mock 支付工具函数
- jest.mock('@/utils/payment', () => ({
- requestWechatPayment: jest.fn(),
- PaymentStatus: {
- PENDING: 'pending',
- PROCESSING: 'processing',
- SUCCESS: 'success',
- FAILED: 'failed',
- },
- PaymentStateManager: {
- getInstance: jest.fn(() => ({
- setPaymentState: jest.fn(),
- clearPaymentState: jest.fn(),
- })),
- },
- PaymentRateLimiter: {
- getInstance: jest.fn(() => ({
- isRateLimited: jest.fn(() => ({ limited: false })),
- recordAttempt: jest.fn(),
- clearAttempts: jest.fn(),
- })),
- },
- retryPayment: jest.fn(),
- }))
- // 创建测试QueryClient
- const createTestQueryClient = () => new QueryClient({
- defaultOptions: {
- queries: { retry: false },
- mutations: { retry: false },
- },
- })
- // 测试包装器
- const TestWrapper = ({ children }: { children: React.ReactNode }) => (
- <QueryClientProvider client={createTestQueryClient()}>
- {children}
- </QueryClientProvider>
- )
- // 测试数据工厂
- const createTestCreditBalance = (overrides = {}) => ({
- totalLimit: 1000,
- usedAmount: 200,
- availableAmount: 800,
- isEnabled: true,
- ...overrides,
- })
- const createTestPaymentData = () => ({
- timeStamp: '1234567890',
- nonceStr: 'test-nonce',
- package: 'prepay_id=test_prepay_id',
- signType: 'MD5',
- paySign: 'test-sign',
- })
- describe('支付页面额度支付功能测试', () => {
- beforeEach(() => {
- jest.clearAllMocks()
- jest.useFakeTimers()
- // 设置默认路由参数
- mockUseRouter.mockReturnValue({
- params: {
- orderId: '123',
- amount: '100',
- orderNo: 'ORD123456',
- },
- })
- })
- afterEach(() => {
- jest.useRealTimers()
- })
- test('应该正确渲染支付页面', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 验证页面标题
- await waitFor(() => {
- expect(screen.getByTestId('payment-page-title')).toBeInTheDocument()
- })
- // 验证订单信息显示
- expect(screen.getByTestId('order-info')).toBeInTheDocument()
- expect(screen.getByTestId('order-no')).toHaveTextContent('ORD123456')
- expect(screen.getByTestId('payment-amount')).toHaveTextContent('¥100.00')
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 验证支付方式选项
- expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
- expect(screen.getByTestId('credit-payment-option')).toBeInTheDocument()
- })
- test('应该显示额度支付选项(只在额度满足时)', async () => {
- // Mock 额度查询返回正常数据(额度足够)
- const mockCreditBalance = createTestCreditBalance({ availableAmount: 800 })
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 验证额度支付选项显示
- const creditPaymentOption = screen.getByTestId('credit-payment-option')
- expect(creditPaymentOption).toBeInTheDocument()
- expect(creditPaymentOption).not.toHaveClass('opacity-50')
- })
- test('额度为0时不应该显示额度支付选项', async () => {
- // Mock 额度查询返回额度为0的数据
- const mockCreditBalance = createTestCreditBalance({
- totalLimit: 0,
- usedAmount: 0,
- availableAmount: 0,
- isEnabled: false,
- })
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成 - 现在额度未启用时,额度支付选项根本不显示
- // 所以没有特定的文本需要等待
- await waitFor(() => {
- // 验证只有微信支付选项显示
- expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
- })
- // 验证额度支付选项不显示
- expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
- expect(screen.queryByTestId('credit-disabled-text')).not.toBeInTheDocument()
- })
- test('额度不足时不应该显示额度支付选项', async () => {
- // Mock 额度查询返回额度不足的数据
- const mockCreditBalance = createTestCreditBalance({
- totalLimit: 50,
- usedAmount: 40,
- availableAmount: 10, // 可用额度10元,支付金额100元
- isEnabled: true,
- })
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- // 额度不足时,额度支付选项不应该显示
- expect(screen.queryByTestId('credit-payment-option')).not.toBeInTheDocument()
- })
- // 验证只有微信支付选项显示
- expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
- })
- test('应该可以切换支付方式', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 初始应该是微信支付选中
- const wechatOption = screen.getByTestId('wechat-payment-option')
- expect(wechatOption).toHaveClass('border-blue-500')
- expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
- // 点击额度支付选项
- const creditOption = screen.getByTestId('credit-payment-option')
- fireEvent.click(creditOption)
- // 验证额度支付被选中
- await waitFor(() => {
- expect(creditOption).toHaveClass('border-blue-500')
- expect(screen.getByTestId('credit-selected')).toBeInTheDocument()
- })
- // 验证支付按钮文字变为额度支付
- expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
- })
- test('选择额度支付时应该显示额度详情(不显示可用额度)', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 点击额度支付选项
- const creditOption = screen.getByTestId('credit-payment-option')
- fireEvent.click(creditOption)
- // 验证显示额度详情(不包含可用额度)
- await waitFor(() => {
- // 使用data-testid查询额度详情容器
- const creditDetails = screen.getByTestId('credit-payment-details')
- expect(creditDetails).toBeInTheDocument()
- // 验证容器中包含额度信息(不包含可用额度)
- expect(creditDetails).toHaveTextContent(/使用信用额度支付,无需立即付款/)
- expect(creditDetails).toHaveTextContent(/总额度: ¥1000\.00/)
- expect(creditDetails).toHaveTextContent(/已用额度: ¥200\.00/)
- // 不应该包含可用额度
- expect(creditDetails).not.toHaveTextContent(/可用额度:/)
- })
- })
- test('额度支付成功应该跳转到成功页面', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- // Mock 额度支付成功
- const updatedBalance = createTestCreditBalance({ usedAmount: 300, availableAmount: 700 })
- ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(updatedBalance),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 点击额度支付选项
- const creditOption = screen.getByTestId('credit-payment-option')
- fireEvent.click(creditOption)
- // 点击支付按钮
- const payButton = screen.getByText('额度支付 ¥100.00')
- fireEvent.click(payButton)
- // 验证调用了额度支付API
- await waitFor(() => {
- expect(creditBalanceClient.payment.$post).toHaveBeenCalledWith({
- json: {
- referenceId: '123', // 现在传递订单ID而不是订单号
- remark: '订单支付 - ORD123456',
- },
- })
- })
- // 验证跳转到成功页面
- // 推进时间以触发setTimeout中的跳转
- jest.advanceTimersByTime(1600)
- await waitFor(() => {
- expect(mockRedirectTo).toHaveBeenCalledWith({
- url: '/pages/payment-success/index?orderId=123&amount=100&paymentMethod=credit',
- })
- })
- })
- test('额度支付失败应该显示错误信息', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- // Mock 额度支付失败
- ;(creditBalanceClient.payment.$post as jest.Mock).mockResolvedValue({
- status: 400,
- json: () => Promise.resolve({ message: '额度不足' }),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 点击额度支付选项
- const creditOption = screen.getByTestId('credit-payment-option')
- fireEvent.click(creditOption)
- // 点击支付按钮
- const payButton = screen.getByText('额度支付 ¥100.00')
- fireEvent.click(payButton)
- // 验证显示错误信息
- await waitFor(() => {
- expect(screen.getByText('额度不足')).toBeInTheDocument()
- })
- })
- test('应该与微信支付选项并行工作', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- // Mock 微信支付参数
- const { paymentClient } = require('@/api')
- ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(createTestPaymentData()),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 验证两个支付选项都存在
- expect(screen.getByTestId('wechat-payment-option')).toBeInTheDocument()
- expect(screen.getByTestId('credit-payment-option')).toBeInTheDocument()
- // 默认选中微信支付
- const wechatOption = screen.getByTestId('wechat-payment-option')
- expect(wechatOption).toHaveClass('border-blue-500')
- expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
- expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
- // 可以切换到额度支付
- const creditOption = screen.getByTestId('credit-payment-option')
- fireEvent.click(creditOption)
- await waitFor(() => {
- expect(creditOption).toHaveClass('border-blue-500')
- expect(screen.getByTestId('credit-selected')).toBeInTheDocument()
- expect(screen.getByTestId('pay-button')).toHaveTextContent('额度支付 ¥100.00')
- })
- // 可以切换回微信支付
- fireEvent.click(wechatOption)
- await waitFor(() => {
- expect(wechatOption).toHaveClass('border-blue-500')
- expect(screen.getByTestId('wechat-selected')).toBeInTheDocument()
- expect(screen.getByTestId('pay-button')).toHaveTextContent('微信支付 ¥100.00')
- })
- })
- test('页面加载时不应该自动调用微信支付API', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- // 获取paymentClient mock
- const { paymentClient } = require('@/api')
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 验证微信支付API没有被调用(页面加载时不应该自动调用)
- expect(paymentClient.payment.$post).not.toHaveBeenCalled()
- // 验证支付按钮可用
- const payButton = screen.getByTestId('pay-button')
- expect(payButton).not.toBeDisabled()
- expect(payButton).toHaveTextContent('微信支付 ¥100.00')
- })
- test('选择微信支付并点击支付按钮时才调用微信支付API', async () => {
- // Mock 额度查询返回正常数据
- const mockCreditBalance = createTestCreditBalance()
- ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(mockCreditBalance),
- })
- // Mock 微信支付参数
- const { paymentClient } = require('@/api')
- ;(paymentClient.payment.$post as jest.Mock).mockResolvedValue({
- status: 200,
- json: () => Promise.resolve(createTestPaymentData()),
- })
- render(
- <TestWrapper>
- <PaymentPage />
- </TestWrapper>
- )
- // 等待额度加载完成
- await waitFor(() => {
- expect(screen.getByTestId('available-amount-text')).toHaveTextContent('使用信用额度支付')
- })
- // 初始时微信支付API不应该被调用
- expect(paymentClient.payment.$post).not.toHaveBeenCalled()
- // 点击支付按钮(默认选中微信支付)
- const payButton = screen.getByTestId('pay-button')
- fireEvent.click(payButton)
- // 验证微信支付API被调用
- await waitFor(() => {
- expect(paymentClient.payment.$post).toHaveBeenCalledWith({
- json: {
- orderId: 123,
- totalAmount: 10000, // 100元转换为分
- description: '订单支付 - ORD123456',
- },
- })
- })
- })
- })
|