flnxi-Wu/components/ui/__tests__/ActivationModal.test.tsx
2026-05-25 14:52:24 +08:00

296 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ActivationModal 组件单元测试
*
* 测试激活码输入弹窗的功能和交互行为
*
* @jest-environment jsdom
*/
import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import ActivationModal from '../ActivationModal'
// Mock cn function
jest.mock('@/lib/ui-styles', () => ({
cn: (...classes: (string | boolean | undefined)[]) =>
classes.filter(Boolean).join(' '),
}))
// Mock fetch
const mockFetch = jest.fn()
global.fetch = mockFetch
// Mock window.open
const mockOpen = jest.fn()
Object.defineProperty(window, 'open', {
value: mockOpen,
writable: true,
})
describe('ActivationModal 组件', () => {
const mockOnClose = jest.fn()
const mockOnSuccess = jest.fn()
beforeEach(() => {
mockOnClose.mockClear()
mockOnSuccess.mockClear()
mockFetch.mockClear()
mockOpen.mockClear()
})
describe('基础渲染', () => {
it('当 isOpen 为 true 时应该渲染弹窗', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
expect(screen.getByText('激活许可证')).toBeInTheDocument()
})
it('当 isOpen 为 false 时不应该渲染弹窗', () => {
render(
<ActivationModal
isOpen={false}
onClose={mockOnClose}
/>
)
expect(screen.queryByText('激活许可证')).not.toBeInTheDocument()
})
it('应该显示激活码输入框', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
expect(screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')).toBeInTheDocument()
})
it('应该显示格式提示', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
expect(screen.getByText('激活码格式:FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')).toBeInTheDocument()
})
it('应该显示立即激活按钮', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
expect(screen.getByText('立即激活')).toBeInTheDocument()
})
it('应该显示购买链接', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
expect(screen.getByText('还没有激活码?')).toBeInTheDocument()
expect(screen.getByText('前往购买 →')).toBeInTheDocument()
})
})
describe('输入交互', () => {
it('输入应该自动转换为大写', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX') as HTMLInputElement
fireEvent.change(input, { target: { value: 'flnxi-pro-abcd-1234-efgh-5678' } })
expect(input.value).toBe('FLNXI-PRO-ABCD-1234-EFGH-5678')
})
})
describe('表单验证', () => {
it('空激活码应该显示错误', async () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
fireEvent.click(screen.getByText('立即激活'))
expect(await screen.findByText('请输入激活码')).toBeInTheDocument()
})
it('格式不正确的激活码应该显示错误', async () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'INVALID-CODE' } })
fireEvent.click(screen.getByText('立即激活'))
expect(await screen.findByText('激活码格式不正确')).toBeInTheDocument()
})
})
describe('API 调用', () => {
it('有效激活码应该调用 API', async () => {
mockFetch.mockResolvedValueOnce({
json: () => Promise.resolve({ success: true }),
})
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
onSuccess={mockOnSuccess}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'FLNXI-PRO-ABCD-1234-EFGH-5678' } })
fireEvent.click(screen.getByText('立即激活'))
await waitFor(() => {
expect(mockFetch).toHaveBeenCalledWith('/api/license/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ activationCode: 'FLNXI-PRO-ABCD-1234-EFGH-5678' }),
})
})
})
it('激活成功应该调用 onSuccess 和 onClose', async () => {
mockFetch.mockResolvedValueOnce({
json: () => Promise.resolve({ success: true }),
})
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
onSuccess={mockOnSuccess}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'FLNXI-PRO-ABCD-1234-EFGH-5678' } })
fireEvent.click(screen.getByText('立即激活'))
await waitFor(() => {
expect(mockOnSuccess).toHaveBeenCalled()
expect(mockOnClose).toHaveBeenCalled()
})
})
it('激活失败应该显示错误信息', async () => {
mockFetch.mockResolvedValueOnce({
json: () => Promise.resolve({ success: false, error: '激活码无效' }),
})
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'FLNXI-PRO-ABCD-1234-EFGH-5678' } })
fireEvent.click(screen.getByText('立即激活'))
expect(await screen.findByText('激活码无效')).toBeInTheDocument()
})
it('网络错误应该显示错误信息', async () => {
mockFetch.mockRejectedValueOnce(new Error('Network error'))
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'FLNXI-PRO-ABCD-1234-EFGH-5678' } })
fireEvent.click(screen.getByText('立即激活'))
expect(await screen.findByText('网络错误,请稍后重试')).toBeInTheDocument()
})
})
describe('加载状态', () => {
it('提交时应该显示加载状态', async () => {
mockFetch.mockImplementation(() => new Promise(() => {})) // 永不 resolve
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
const input = screen.getByPlaceholderText('FLNXI-PRO-XXXX-XXXX-XXXX-XXXX')
fireEvent.change(input, { target: { value: 'FLNXI-PRO-ABCD-1234-EFGH-5678' } })
fireEvent.click(screen.getByText('立即激活'))
expect(await screen.findByText('激活中...')).toBeInTheDocument()
})
})
describe('关闭交互', () => {
it('点击关闭按钮应该调用 onClose', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
// 找到关闭按钮(SVG 的父元素)
const closeButton = screen.getByText('激活许可证').parentElement?.querySelector('button')
if (closeButton) {
fireEvent.click(closeButton)
expect(mockOnClose).toHaveBeenCalled()
}
})
it('点击背景遮罩应该调用 onClose', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
// 点击背景遮罩
const overlay = document.querySelector('.backdrop-blur-sm')
if (overlay) {
fireEvent.click(overlay)
expect(mockOnClose).toHaveBeenCalled()
}
})
})
describe('购买链接', () => {
it('点击购买链接应该打开新窗口', () => {
render(
<ActivationModal
isOpen={true}
onClose={mockOnClose}
/>
)
fireEvent.click(screen.getByText('前往购买 →'))
expect(mockOpen).toHaveBeenCalledWith('https://www.flnxi.com/pricing', '_blank')
})
})
})