feat(ui): 抽离 CTAButton 为 src/components/ui/cta-button.tsx - 4 variant 覆盖全站 CTA 场景: * primary:品牌红填充 pill(hero 主行动 / 底部大 CTA) * secondary:描边墨色 pill(浅色 section 次行动) * dark:描边浅色 pill(深色区块 bg-dark-bg 内次行动,如 brand/team 底部 CTA) * inline:透明 + 小箭头(卡片内次行动) - 品牌箭头签名化:内置 ArrowRight + hover translate-x 微交互 - 200-300ms transition,对齐项目动效四原则 - data-testid 透传避免破坏既有测试 style(cta): P5 全站 CTA 签名切换(17 页面 27 处) - home V15 / news×3 / products×4(含 erp)/ cases / services×2 / solutions×2 - brand / methodology / team(hero 2 + L4 2 = 4 处,含 dark variant) - error / not-found(保留 onClick 功能按钮与卡片式导航 StaticLink) - 清理:StaticLink / Button / ArrowRight / ChevronRight unused import test(marketing): services/solutions lucide-react mock 补 ArrowRight - CTAButton 内部用 ArrowRight,原 mock 漏导导致渲染崩(4 测试失败已修) - 对齐 home-content-v15.test.tsx 既有 mock 范式
150 lines
4.9 KiB
TypeScript
150 lines
4.9 KiB
TypeScript
import { describe, it, expect, jest } from '@jest/globals';
|
|
import { render, screen } from '@testing-library/react';
|
|
import '@testing-library/jest-dom';
|
|
import SolutionsContentV3 from './solutions-content-v3';
|
|
import type { Solution } from '@/lib/constants/solutions';
|
|
import type { Product } from '@/lib/constants/products';
|
|
|
|
jest.mock('framer-motion', () => ({
|
|
motion: {
|
|
div: ({ children, initial, animate, whileInView, viewport, className, ...props }: any) => (
|
|
<div
|
|
data-testid="motion-div"
|
|
data-initial={JSON.stringify(initial)}
|
|
data-animate={JSON.stringify(animate)}
|
|
data-while-inview={JSON.stringify(whileInView)}
|
|
data-viewport={JSON.stringify(viewport)}
|
|
className={className}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</div>
|
|
),
|
|
h1: ({ children, className, ...props }: any) => (
|
|
<h1 data-testid="motion-h1" className={className} {...props}>{children}</h1>
|
|
),
|
|
p: ({ children, className, ...props }: any) => (
|
|
<p data-testid="motion-p" className={className} {...props}>{children}</p>
|
|
),
|
|
span: ({ children, className, ...props }: any) => (
|
|
<span data-testid="motion-span" className={className} {...props}>{children}</span>
|
|
),
|
|
a: ({ children, href, className, ...props }: any) => (
|
|
<a href={href} className={className} data-testid="motion-a" {...props}>{children}</a>
|
|
),
|
|
},
|
|
useInView: jest.fn(() => true),
|
|
AnimatePresence: ({ children }: any) => <>{children}</>,
|
|
}));
|
|
|
|
jest.mock('@/components/ui/static-link', () => ({
|
|
StaticLink: ({ children, href, className, ...props }: any) => (
|
|
<a href={href} className={className} data-testid="static-link" {...props}>{children}</a>
|
|
),
|
|
}));
|
|
|
|
jest.mock('lucide-react', () => {
|
|
const mockIcon = (name: string) => {
|
|
const Icon = (props: any) => <svg data-testid={`icon-${name.toLowerCase()}`} className={props.className} />;
|
|
Icon.displayName = name;
|
|
return Icon;
|
|
};
|
|
return {
|
|
ArrowRight: mockIcon('arrow-right'),
|
|
ArrowUpRight: mockIcon('arrow-up-right'),
|
|
Factory: mockIcon('factory'),
|
|
ShoppingCart: mockIcon('shopping-cart'),
|
|
Heart: mockIcon('heart'),
|
|
GraduationCap: mockIcon('graduation-cap'),
|
|
Shield: mockIcon('shield'),
|
|
Truck: mockIcon('truck'),
|
|
};
|
|
});
|
|
|
|
const mockSolutions: Solution[] = [
|
|
{
|
|
id: 'manufacturing',
|
|
industry: '制造业',
|
|
title: '智能制造数字化转型方案',
|
|
subtitle: '端到端制造数字化',
|
|
description: '面向制造企业的整体转型方案',
|
|
challenges: ['数据孤岛', '库存积压'],
|
|
solutions: ['ERP 集成', 'BI 分析'],
|
|
relatedProducts: ['erp', 'bi'],
|
|
valueProposition: {
|
|
headline: '价值主张',
|
|
points: [{ icon: 'Factory', title: '降本增效', description: '描述' }],
|
|
},
|
|
suiteCombination: {
|
|
primaryProducts: ['erp'],
|
|
complementaryServices: ['consulting'],
|
|
rationale: '理由',
|
|
},
|
|
painPoints: ['数据孤岛'],
|
|
outcomes: [{ value: '30%', label: '效率提升' }],
|
|
recommendedProducts: ['erp', 'bi'],
|
|
},
|
|
{
|
|
id: 'retail',
|
|
industry: '贸易零售',
|
|
title: '零售数字化解决方案',
|
|
subtitle: '全渠道零售数字化',
|
|
description: '面向零售企业的数字化方案',
|
|
challenges: ['渠道割裂'],
|
|
solutions: ['CRM 统一'],
|
|
relatedProducts: ['crm', 'bi'],
|
|
valueProposition: {
|
|
headline: '价值主张',
|
|
points: [{ icon: 'ShoppingCart', title: '提升转化', description: '描述' }],
|
|
},
|
|
suiteCombination: {
|
|
primaryProducts: ['crm'],
|
|
complementaryServices: ['consulting'],
|
|
rationale: '理由',
|
|
},
|
|
painPoints: ['渠道割裂'],
|
|
outcomes: [{ value: '3x', label: '转化率提升' }],
|
|
recommendedProducts: ['crm', 'bi'],
|
|
},
|
|
];
|
|
|
|
const mockProducts: Product[] = [
|
|
{
|
|
id: 'erp',
|
|
title: '睿新ERP管理系统',
|
|
description: 'ERP',
|
|
image: '/erp.png',
|
|
category: '企业旗舰系列',
|
|
categoryId: 'enterprise',
|
|
status: '已发布',
|
|
overview: '概述',
|
|
features: [],
|
|
benefits: [],
|
|
process: [],
|
|
specs: [],
|
|
tags: [],
|
|
heroThemeId: 'erp',
|
|
caseStudies: [],
|
|
dataProofs: [],
|
|
certifications: [],
|
|
bundle: 'enterprise',
|
|
},
|
|
];
|
|
|
|
describe('SolutionsContentV3 - 非对称方案卡片', () => {
|
|
it('makes the first solution a featured full-width card', () => {
|
|
render(<SolutionsContentV3 solutions={mockSolutions} products={mockProducts} />);
|
|
|
|
expect(screen.getByTestId('solution-card-manufacturing')).toHaveAttribute('data-featured', 'true');
|
|
expect(screen.getByTestId('solution-card-manufacturing').className).toContain('md:col-span-2');
|
|
expect(screen.getByTestId('solution-card-retail')).toHaveAttribute('data-featured', 'false');
|
|
});
|
|
|
|
it('renders solution industries', () => {
|
|
render(<SolutionsContentV3 solutions={mockSolutions} products={mockProducts} />);
|
|
|
|
expect(screen.getByText('制造业')).toBeInTheDocument();
|
|
expect(screen.getByText('贸易零售')).toBeInTheDocument();
|
|
});
|
|
});
|