255 lines
9.0 KiB
TypeScript
255 lines
9.0 KiB
TypeScript
/**
|
|
* RestrictedToggle 组件单元测试
|
|
*
|
|
* 测试 RestrictedToggle 组件的点击拦截和禁用状态
|
|
*
|
|
* **Feature: free-version-ui-restrictions**
|
|
* **Property 1: 免费版点击拦截**
|
|
* **Property 6: 禁用开关视觉状态**
|
|
* **Validates: Requirements 1.1, 5.1, 5.2, 5.4**
|
|
*
|
|
* @jest-environment jsdom
|
|
*/
|
|
|
|
import React from 'react'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
import '@testing-library/jest-dom'
|
|
import RestrictedToggle from '../RestrictedToggle'
|
|
import type { FeatureKey } from '@/lib/license/types'
|
|
|
|
// Mock useTheme hook
|
|
jest.mock('@/lib/ui-styles', () => ({
|
|
useTheme: jest.fn(() => 'light'),
|
|
cn: (...classes: (string | boolean | undefined)[]) =>
|
|
classes.filter(Boolean).join(' '),
|
|
}))
|
|
|
|
// Mock useThemeStyles for UpgradeModal
|
|
jest.mock('@/lib/ui-styles', () => ({
|
|
useTheme: jest.fn(() => 'light'),
|
|
useThemeStyles: jest.fn(() => ({
|
|
buttonStyles: {
|
|
secondary: 'mock-secondary-button',
|
|
},
|
|
modalStyles: {
|
|
overlay: 'mock-overlay',
|
|
container: 'mock-container',
|
|
content: 'mock-content',
|
|
header: 'mock-header',
|
|
title: 'mock-title',
|
|
closeButton: 'mock-close-button',
|
|
body: 'mock-body',
|
|
footer: 'mock-footer',
|
|
sizes: {
|
|
small: 'mock-small',
|
|
},
|
|
},
|
|
})),
|
|
cn: (...classes: (string | boolean | undefined)[]) =>
|
|
classes.filter(Boolean).join(' '),
|
|
}))
|
|
|
|
// Mock createPortal
|
|
jest.mock('react-dom', () => ({
|
|
...jest.requireActual('react-dom'),
|
|
createPortal: (node: React.ReactNode) => node,
|
|
}))
|
|
|
|
describe('RestrictedToggle 组件', () => {
|
|
const mockOnChange = jest.fn()
|
|
const defaultProps = {
|
|
label: '测试功能',
|
|
featureKey: 'customTemplates' as FeatureKey,
|
|
value: false,
|
|
onChange: mockOnChange,
|
|
isRestricted: false,
|
|
}
|
|
|
|
beforeEach(() => {
|
|
mockOnChange.mockClear()
|
|
})
|
|
|
|
describe('基础渲染', () => {
|
|
it('应该正确渲染标签', () => {
|
|
render(<RestrictedToggle {...defaultProps} />)
|
|
expect(screen.getByText('测试功能')).toBeInTheDocument()
|
|
})
|
|
|
|
it('应该正确渲染描述', () => {
|
|
render(<RestrictedToggle {...defaultProps} description="这是功能描述" />)
|
|
expect(screen.getByText('这是功能描述')).toBeInTheDocument()
|
|
})
|
|
|
|
it('应该有正确的 data-testid', () => {
|
|
render(<RestrictedToggle {...defaultProps} />)
|
|
expect(screen.getByTestId('restricted-toggle')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Property 1: 免费版点击拦截
|
|
* *For any* 受限功能开关和 FREE 许可证类型,点击开关必须显示升级弹窗且不改变开关值
|
|
* **Feature: free-version-ui-restrictions, Property 1: Click Interception for FREE License**
|
|
* **Validates: Requirements 1.1, 5.2, 5.4**
|
|
*/
|
|
describe('Property 1: 免费版点击拦截', () => {
|
|
it('受限时点击应该显示升级弹窗', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// 应该显示升级弹窗
|
|
expect(screen.getByTestId('upgrade-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('受限时点击不应该调用 onChange', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// 不应该调用 onChange
|
|
expect(mockOnChange).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('受限时开关值不应该改变', () => {
|
|
const { rerender } = render(
|
|
<RestrictedToggle {...defaultProps} isRestricted={true} value={false} />
|
|
)
|
|
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// 值应该保持不变
|
|
expect(mockOnChange).not.toHaveBeenCalled()
|
|
|
|
// 重新渲染确认值没变
|
|
rerender(<RestrictedToggle {...defaultProps} isRestricted={true} value={false} />)
|
|
expect(toggleButton).toHaveAttribute('aria-checked', 'false')
|
|
})
|
|
|
|
it('不受限时点击应该调用 onChange', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// 应该调用 onChange
|
|
expect(mockOnChange).toHaveBeenCalledWith(true)
|
|
})
|
|
|
|
it('不受限时点击不应该显示升级弹窗', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// 不应该显示升级弹窗
|
|
expect(screen.queryByTestId('upgrade-modal')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
/**
|
|
* Property 6: 禁用开关视觉状态
|
|
* *For any* 受限功能和 FREE 许可证,开关必须以禁用视觉状态显示,具有降低的不透明度或灰色外观
|
|
* **Feature: free-version-ui-restrictions, Property 6: Disabled Toggle Visual State**
|
|
* **Validates: Requirements 5.1**
|
|
*/
|
|
describe('Property 6: 禁用开关视觉状态', () => {
|
|
it('受限时应该显示 PRO 徽章', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
expect(screen.getByTestId('pro-badge')).toBeInTheDocument()
|
|
})
|
|
|
|
it('不受限时不应该显示 PRO 徽章', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
expect(screen.queryByTestId('pro-badge')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('受限时开关应该有禁用样式', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton.className).toContain('opacity-50')
|
|
expect(toggleButton.className).toContain('cursor-not-allowed')
|
|
})
|
|
|
|
it('不受限时开关应该有正常样式', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton.className).toContain('cursor-pointer')
|
|
expect(toggleButton.className).not.toContain('opacity-50')
|
|
})
|
|
|
|
it('受限时应该显示升级提示文字', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
expect(screen.getByTestId('restricted-hint')).toBeInTheDocument()
|
|
expect(screen.getByText('升级到付费版解锁此功能')).toBeInTheDocument()
|
|
})
|
|
|
|
it('不受限时不应该显示升级提示文字', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
expect(screen.queryByTestId('restricted-hint')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('受限时标签应该有降低的不透明度', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
const label = screen.getByText('测试功能')
|
|
expect(label.className).toContain('opacity-60')
|
|
})
|
|
|
|
it('受限时 data-restricted 属性应该为 true', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
const toggle = screen.getByTestId('restricted-toggle')
|
|
expect(toggle).toHaveAttribute('data-restricted', 'true')
|
|
})
|
|
|
|
it('不受限时 data-restricted 属性应该为 false', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
const toggle = screen.getByTestId('restricted-toggle')
|
|
expect(toggle).toHaveAttribute('data-restricted', 'false')
|
|
})
|
|
})
|
|
|
|
describe('开关状态', () => {
|
|
it('value 为 true 时开关应该在右侧', () => {
|
|
render(<RestrictedToggle {...defaultProps} value={true} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton).toHaveAttribute('aria-checked', 'true')
|
|
})
|
|
|
|
it('value 为 false 时开关应该在左侧', () => {
|
|
render(<RestrictedToggle {...defaultProps} value={false} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton).toHaveAttribute('aria-checked', 'false')
|
|
})
|
|
|
|
it('受限时 aria-disabled 应该为 true', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton).toHaveAttribute('aria-disabled', 'true')
|
|
})
|
|
|
|
it('不受限时 aria-disabled 应该为 false', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={false} />)
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
expect(toggleButton).toHaveAttribute('aria-disabled', 'false')
|
|
})
|
|
})
|
|
|
|
describe('升级弹窗交互', () => {
|
|
it('关闭升级弹窗后应该隐藏弹窗', () => {
|
|
render(<RestrictedToggle {...defaultProps} isRestricted={true} />)
|
|
|
|
// 打开弹窗
|
|
const toggleButton = screen.getByTestId('toggle-button')
|
|
fireEvent.click(toggleButton)
|
|
expect(screen.getByTestId('upgrade-modal')).toBeInTheDocument()
|
|
|
|
// 关闭弹窗
|
|
fireEvent.click(screen.getByText('关闭'))
|
|
expect(screen.queryByTestId('upgrade-modal')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|