import { describe, it, expect, jest } from '@jest/globals'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; // ─── Mocks ─────────────────────────────────────────────────────────────── jest.mock('framer-motion', () => ({ motion: { div: ({ children, initial, animate, variants, whileInView, whileHover, whileTap, viewport, transition, className, onMouseMove, onMouseEnter, onMouseLeave, onMouseUp, style, ref, href, ...props }: any) => ( href ? ( {children} ) : (
{children}
) ), p: ({ children, className, ...props }: any) => (

{children}

), h1: ({ children, className, ...props }: any) => (

{children}

), h2: ({ children, className, ...props }: any) => (

{children}

), h3: ({ children, className, ...props }: any) => (

{children}

), span: ({ children, className, ...props }: any) => ( {children} ), button: ({ children, onClick, className, ...props }: any) => ( ), a: ({ children, href, className, ...props }: any) => ( {children} ), li: ({ children, className, ...props }: any) => (
  • {children}
  • ), circle: ({ className, cx, cy, r, fill, stroke, strokeWidth, strokeLinecap, strokeDasharray, ...props }: any) => ( ), }, useInView: jest.fn(() => true), useMotionValue: jest.fn(() => ({ get: () => 0.5, set: jest.fn() })), useTransform: jest.fn(() => ({ get: () => '0%' })), useSpring: jest.fn(() => ({ get: () => 0 })), AnimatePresence: ({ children }: any) => <>{children}, })); jest.mock('@/hooks/use-reduced-motion', () => ({ useReducedMotion: jest.fn(() => false), })); jest.mock('./micro-interactions', () => ({ TiltCard: ({ children, className }: any) => (
    {children}
    ), PressableButton: ({ children, className, variant, onClick }: any) => ( ), HoverLink: ({ children, href, className }: any) => ( {children} ), })); jest.mock('./detail-case-study', () => ({ CaseStudyCard: ({ study, index }: any) => (
    {study.client} {study.industry}
    ), })); jest.mock('./detail-certification', () => ({ CertificationList: ({ certifications }: any) => (
    {certifications.map((cert: any, i: number) => ( {cert.name} ))}
    ), })); jest.mock('lucide-react', () => { const icons: Record = {}; const names = [ 'ArrowRight', 'Sparkles', 'ChevronDown', 'Zap', 'Shield', 'CheckCircle2', 'MessageCircle', 'Download', 'Package', 'Lightbulb', 'Wrench', 'TrendingUp', 'Clock', 'Award', 'Users', 'Cpu', 'ExternalLink', ]; for (const name of names) { const Icon = (props: any) => ( ); Icon.displayName = name; icons[name] = Icon; } return icons; }); jest.mock('@/lib/constants/design-system', () => ({ DESIGN_SYSTEM: { animation: { duration: { fast: '0.2s', normal: '0.4s', slow: '0.6s', slower: '0.8s', countUp: '2s' }, easing: { smooth: 'cubic-bezier(0.4, 0, 0.2, 1)', bounce: 'cubic-bezier(0.34, 1.56, 0.64, 1)', easeOut: 'cubic-bezier(0, 0, 0.2, 1)', easeInOut: 'cubic-bezier(0.4, 0, 0.6, 1)' }, delay: { stagger: 0.08, section: 0.15 }, }, spacing: { section: { py: 'py-20 lg:py-28', pyCompact: 'py-16 lg:py-20' }, container: { default: 'max-w-7xl mx-auto px-4 sm:px-6 lg:px-8', narrow: 'max-w-5xl mx-auto px-4 sm:px-6 lg:px-8', wide: 'max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8' }, grid: { gap: 'gap-6 lg:gap-8', gapSmall: 'gap-4 lg:gap-6' }, }, typography: { hero: { title: 'text-4xl', subtitle: 'text-lg', description: 'text-base' }, section: { title: 'text-3xl', subtitle: 'text-lg', body: 'text-base' }, card: { title: 'text-lg', description: 'text-sm' }, }, effects: { inkGlow: { border: 'conic-gradient(...)', glow: 'radial-gradient(...)', speed: '3s' }, hover: { translateY: '-4px', shadow: 'shadow-xl', transition: 'all 0.3s' }, scroll: { fadeInUp: { initial: { opacity: 0, y: 24 }, animate: { opacity: 1, y: 0 }, viewport: { once: true, margin: '-80px' }, transition: { duration: 0.6, ease: [0.16, 1, 0.3, 1] }, }, staggerChildren: { delay: 0.08 }, }, }, colors: { brand: { primary: 'var(--color-brand)', light: 'var(--color-brand-bg)', lighter: 'rgba(var(--color-brand-rgb), 0.04)', gradient: 'linear-gradient(135deg, var(--color-brand) 0%, #99182d 100%)' }, neutral: { '50': 'var(--color-bg-primary)', '100': 'var(--color-bg-section)', '200': 'var(--color-border-primary)' }, ink: { light: 'rgba(0, 0, 0, 0.03)', medium: 'rgba(0, 0, 0, 0.06)' }, }, components: { card: { base: 'rounded-2xl', hover: 'hover:-translate-y-1', padding: 'p-6' }, badge: { base: 'inline-flex', variants: { primary: 'bg-brand-soft', secondary: 'bg-bg-tertiary' } }, button: { primary: 'bg-brand', secondary: 'bg-white', ghost: 'bg-transparent' }, metric: { container: 'text-center', value: 'text-3xl', label: 'text-sm', description: 'text-xs' }, }, }, })); // ─── Mock Data ─────────────────────────────────────────────────────────── const mockHeroTheme = { id: 'erp', name: 'ERP', gradientFrom: '#f8f6f3', gradientTo: '#f0ebe4', gradientAngle: 135, accentColor: '#1e3a5f', accentBg: 'rgba(30, 58, 95, 0.06)', texture: { type: 'grid' as const, opacity: 0.03 }, layout: 'left' as const, badge: '企业套装', }; const mockCenterTheme = { ...mockHeroTheme, id: 'solution', layout: 'center' as const, badge: undefined, }; const mockProduct = { id: 'erp', title: 'ERP 企业资源管理系统', description: '企业资源管理系统', image: '/images/erp.jpg', category: 'Enterprise Software', categoryId: 'enterprise' as const, status: '已发布' as const, overview: '覆盖企业全业务流程的数字化管理平台', features: ['财务管理:总账、应收应付、资产管理', '供应链管理:采购、库存、物流'], benefits: ['提升运营效率 300%', '降低人力成本 50%'], process: ['需求调研', '方案设计'], specs: ['支持 10 万并发', '99.9% 可用性'], tags: ['ERP', '企业管理'], heroThemeId: 'erp', bundle: 'enterprise' as const, caseStudies: [ { client: '某制造企业', industry: '制造业', challenge: '库存管理混乱', solution: '实施ERP系统', result: '库存周转率提升200%' }, ], dataProofs: [ { metric: '效率提升', value: '300%', description: '业务流程效率提升' }, ], certifications: [ { name: 'ISO 27001', issuer: '国际认证', link: 'https://example.com' }, ], }; // ─── Tests ─────────────────────────────────────────────────────────────── describe('DetailHero', () => { it('should render title and subtitle', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.getByText('ERP 系统')).toBeInTheDocument(); expect(screen.getByText('企业资源管理专家')).toBeInTheDocument(); }); it('should render badge from theme', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.getByText('企业套装')).toBeInTheDocument(); }); it('should render badge prop overriding theme badge', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.getByText('新版')).toBeInTheDocument(); }); it('should render description when provided', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.getByText('Detailed description here')).toBeInTheDocument(); }); it('should not render description when not provided', async () => { const { DetailHero } = await import('./detail-hero'); const { container } = render( ); // Should still render subtitle but no extra description paragraph expect(screen.getByText('Expert')).toBeInTheDocument(); // The description text should not be present expect(container.querySelector('section')?.children.length).toBeGreaterThan(0); }); it('should render primary and secondary actions', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.getByText('了解更多')).toBeInTheDocument(); expect(screen.getByText('预约演示')).toBeInTheDocument(); }); it('should not render actions when not provided', async () => { const { DetailHero } = await import('./detail-hero'); render( ); expect(screen.queryByText('了解更多')).not.toBeInTheDocument(); }); it('should render no badge when theme has no badge and no badge prop', async () => { const { DetailHero } = await import('./detail-hero'); render( ); // Center theme has no badge - verify sparkles (badge icon) is not in document expect(screen.queryByText('企业套装')).not.toBeInTheDocument(); }); it('should render with center layout', async () => { const { DetailHero } = await import('./detail-hero'); const { container } = render( ); // Center layout renders text-center class const section = container.querySelector('section'); expect(section).toBeInTheDocument(); }); it('should use theme-aware bg token (not hard-coded bg-white) for light variant', async () => { const { DetailHero } = await import('./detail-hero'); const { container } = render( ); // 暗黑模式:浅色底色必须走 CSS 变量 token,dark 下才能随主题反色 const section = container.querySelector('section'); expect(section?.className).toContain('bg-bg-primary'); expect(section?.className).not.toContain('bg-white'); }); }); describe('ProductValueSection', () => { it('should render product title and overview', async () => { const { ProductValueSection } = await import('./detail-product-value'); render(); const heading = screen.getByRole('heading', { name: new RegExp(mockProduct.title) }); expect(heading).toHaveTextContent('核心能力'); expect(screen.getByText(mockProduct.overview)).toBeInTheDocument(); }); it('should render feature items', async () => { const { ProductValueSection } = await import('./detail-product-value'); render(); expect(screen.getByText('产品功能')).toBeInTheDocument(); // Features are rendered via FeatureTags which splits on ":" expect(screen.getByText('财务管理')).toBeInTheDocument(); }); it('should render benefit items', async () => { const { ProductValueSection } = await import('./detail-product-value'); render(); expect(screen.getByText('核心优势')).toBeInTheDocument(); expect(screen.getByText('提升运营效率 300%')).toBeInTheDocument(); expect(screen.getByText('降低人力成本 50%')).toBeInTheDocument(); }); it('should render spec items', async () => { const { ProductValueSection } = await import('./detail-product-value'); render(); expect(screen.getByText('技术规格')).toBeInTheDocument(); expect(screen.getByText('支持 10 万并发')).toBeInTheDocument(); }); it('should render data proofs when available', async () => { const { ProductValueSection } = await import('./detail-product-value'); render(); expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument(); }); it('should not render data proofs section when empty', async () => { const { ProductValueSection } = await import('./detail-product-value'); const productWithoutData = { ...mockProduct, dataProofs: [] }; render(); expect(screen.queryByText('用真实效果证明价值')).not.toBeInTheDocument(); }); }); describe('DetailTrustSection', () => { it('should render nothing when all props are empty', async () => { const { DetailTrustSection } = await import('./detail-trust-section'); const { container } = render(); // Component returns null when no content expect(container.innerHTML).toBe(''); }); it('should render data proofs', async () => { const { DetailTrustSection } = await import('./detail-trust-section'); render( ); expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument(); expect(screen.getByText(mockProduct.dataProofs[0]!.metric)).toBeInTheDocument(); }); it('should render case studies', async () => { const { DetailTrustSection } = await import('./detail-trust-section'); render( ); expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument(); expect(screen.getByTestId('case-study-card')).toBeInTheDocument(); expect(screen.getByTestId('case-client')).toHaveTextContent('某制造企业'); }); it('should render certifications', async () => { const { DetailTrustSection } = await import('./detail-trust-section'); render( ); expect(screen.getByText('资质认证')).toBeInTheDocument(); expect(screen.getByTestId('certification-list')).toBeInTheDocument(); expect(screen.getByTestId('cert-name')).toHaveTextContent('ISO 27001'); }); it('should render all sections together', async () => { const { DetailTrustSection } = await import('./detail-trust-section'); render( ); expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument(); expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument(); expect(screen.getByText('资质认证')).toBeInTheDocument(); }); }); describe('DetailCTASection', () => { it('should render default title and description', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); expect(screen.getByText('准备好开始了吗?')).toBeInTheDocument(); expect(screen.getByText('联系我们,获取专属方案和报价')).toBeInTheDocument(); }); it('should render custom title and description', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); expect(screen.getByText('Custom CTA Title')).toBeInTheDocument(); expect(screen.getByText('Custom CTA description')).toBeInTheDocument(); }); it('should render primary action with link', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); const link = screen.getByText('免费试用').closest('a'); expect(link).toHaveAttribute('href', '/trial'); }); it('should render secondary action when provided', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); expect(screen.getByText('下载资料')).toBeInTheDocument(); const link = screen.getByText('下载资料').closest('a'); expect(link).toHaveAttribute('href', '/download'); }); it('should render free consultation badge', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); expect(screen.getByText('免费咨询')).toBeInTheDocument(); }); it('should render SLA text', async () => { const { DetailCTASection } = await import('./detail-cta-section'); render( ); expect(screen.getByText(/平均响应时间 < 2小时/)).toBeInTheDocument(); }); }); // 注:CrossRecommendGrid 相关测试已随组件删除(2026-08-31,组件生产 0 引用)