feat(methodology): add CMS-driven methodology page skeleton
- 新增 methodology 内容模型(content-types + seed),内容由 CMS 后台可编辑 - 新建 /methodology 页面:Hero / 四阶段 Cards / 空态占位 / 轻量 CTA - 首页「了解我们的方法论」CTA 改指向 /methodology;sitemap 收录 - 单元测试 4/4 + 视觉基线通过,type-check / lint 0 errors
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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, className, style, ...props }: any) => (
|
||||
<div data-testid="motion-div" className={className} style={style} {...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>
|
||||
),
|
||||
},
|
||||
useInView: jest.fn(() => true),
|
||||
AnimatePresence: ({ children }: any) => <>{children}</>,
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/scroll-reveal', () => ({
|
||||
ScrollReveal: ({ children, className }: any) => (
|
||||
<div data-testid="scroll-reveal" className={className}>{children}</div>
|
||||
),
|
||||
StaggerReveal: ({ children, className }: any) => (
|
||||
<div data-testid="stagger-reveal" className={className}>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/button', () => ({
|
||||
Button: ({ children, className, ...props }: any) => (
|
||||
<button className={className} data-testid="mock-button" {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('@/components/ui/page-decoration', () => ({
|
||||
EASE_OUT: [0.22, 1, 0.36, 1],
|
||||
}));
|
||||
|
||||
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'),
|
||||
CheckCircle2: mockIcon('check-circle2'),
|
||||
};
|
||||
});
|
||||
|
||||
// ─── Mock Data ────────────────────────────────────────────────────────────
|
||||
|
||||
const mockData = {
|
||||
heroSubtitle: '方法论',
|
||||
heroTitle: '四阶段推进数字化转型',
|
||||
heroDescription: '从诊断到优化,步步可衡量。',
|
||||
phases: [
|
||||
{
|
||||
number: 1,
|
||||
title: '诊断评估',
|
||||
subtitle: '了解现状,找准痛点',
|
||||
description: '深入调研企业现状,识别关键痛点。',
|
||||
activities: ['业务流程调研', 'IT基础设施评估'],
|
||||
deliverables: ['成熟度评估报告', '关键痛点清单'],
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: '战略规划',
|
||||
subtitle: '明确方向,制定路线',
|
||||
description: '制定匹配企业战略的转型路线图。',
|
||||
activities: ['IT战略对齐'],
|
||||
deliverables: ['战略规划书'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('MethodologyContent - 方法论页面', () => {
|
||||
it('should render hero section', async () => {
|
||||
const { MethodologyContent } = await import('./methodology-content');
|
||||
render(<MethodologyContent data={mockData} />);
|
||||
|
||||
expect(screen.getByText('方法论')).toBeInTheDocument();
|
||||
expect(screen.getByText('四阶段推进数字化转型')).toBeInTheDocument();
|
||||
expect(screen.getByText('从诊断到优化,步步可衡量。')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render all methodology phases with activities and deliverables', async () => {
|
||||
const { MethodologyContent } = await import('./methodology-content');
|
||||
render(<MethodologyContent data={mockData} />);
|
||||
|
||||
expect(screen.getByText('诊断评估')).toBeInTheDocument();
|
||||
expect(screen.getByText('战略规划')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('核心活动').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText('交付物').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('业务流程调研')).toBeInTheDocument();
|
||||
expect(screen.getByText('成熟度评估报告')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render empty state when phases are missing', async () => {
|
||||
const { MethodologyContent } = await import('./methodology-content');
|
||||
render(<MethodologyContent data={{ heroTitle: '方法论' }} />);
|
||||
|
||||
expect(screen.getByText('方法论内容即将发布')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render CTA links to services and contact', async () => {
|
||||
const { MethodologyContent } = await import('./methodology-content');
|
||||
render(<MethodologyContent data={mockData} />);
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
expect(links.some((l) => l.getAttribute('href') === '/services')).toBe(true);
|
||||
expect(links.some((l) => l.getAttribute('href') === '/contact')).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user