feat(cases): 新增智慧农业案例并优化政府案例数据
- 新增农业种植灌溉信息化建设咨询项目案例 - 更新政府案例:添加关键成果数据、优化客户评价 - 同步更新测试用例和页面组件
This commit is contained in:
@@ -2,6 +2,23 @@ import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { CaseDetailClient } from './client';
|
||||
|
||||
interface TestCaseItem {
|
||||
id: string;
|
||||
title: string;
|
||||
excerpt: string;
|
||||
content: string;
|
||||
category: string;
|
||||
slug: string;
|
||||
date: string;
|
||||
image?: string;
|
||||
challenge: string;
|
||||
solution: string;
|
||||
keyMoments: { title: string; description: string }[];
|
||||
results: { label: string; value: string }[];
|
||||
testimonial: { quote: string; author: string; role: string };
|
||||
duration: string;
|
||||
}
|
||||
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: jest.fn(),
|
||||
@@ -11,12 +28,14 @@ jest.mock('next/navigation', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('next/link', () => {
|
||||
return ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
const MockLink = ({ children, href }: { children: React.ReactNode; href: string }) => {
|
||||
return <a href={href}>{children}</a>;
|
||||
};
|
||||
MockLink.displayName = 'MockLink';
|
||||
return MockLink;
|
||||
});
|
||||
|
||||
const mockCaseItem = {
|
||||
const mockCaseItem: TestCaseItem = {
|
||||
id: 'test-case',
|
||||
title: '测试案例标题',
|
||||
excerpt: '这是一个测试案例的描述',
|
||||
@@ -24,6 +43,23 @@ const mockCaseItem = {
|
||||
category: '制造业',
|
||||
slug: 'test-case',
|
||||
date: '2026-03-27',
|
||||
challenge: '这是客户面临的挑战描述',
|
||||
solution: '这是我们的解决方案描述',
|
||||
keyMoments: [
|
||||
{ title: '关键时刻一', description: '关键时刻一的详细描述' },
|
||||
{ title: '关键时刻二', description: '关键时刻二的详细描述' },
|
||||
],
|
||||
results: [
|
||||
{ label: '运营成本', value: '降低25%' },
|
||||
{ label: '设备故障响应', value: '缩短85%' },
|
||||
{ label: '排产周期', value: '从1周缩至半天' },
|
||||
],
|
||||
testimonial: {
|
||||
quote: '这是客户证言内容',
|
||||
author: '测试客户',
|
||||
role: 'CTO',
|
||||
},
|
||||
duration: '2年',
|
||||
};
|
||||
|
||||
describe('CaseDetailClient', () => {
|
||||
@@ -33,90 +69,124 @@ describe('CaseDetailClient', () => {
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render case detail page', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const main = screen.getByRole('main');
|
||||
expect(main).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render case title', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const title = screen.getByRole('heading', { level: 1 });
|
||||
expect(title).toBeInTheDocument();
|
||||
expect(title).toHaveTextContent('测试案例标题');
|
||||
});
|
||||
|
||||
it('should render case client name', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
it('should render case excerpt', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const excerpts = screen.getAllByText('这是一个测试案例的描述');
|
||||
expect(excerpts.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render case industry badge', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const categories = screen.getAllByText('制造业');
|
||||
expect(categories.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render case description', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
const excerpts = screen.getAllByText('这是一个测试案例的描述');
|
||||
expect(excerpts.length).toBeGreaterThan(0);
|
||||
it('should render challenge content', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
expect(screen.getByText('这是客户面临的挑战描述')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render case results', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
const excerpts = screen.getAllByText('这是一个测试案例的描述');
|
||||
expect(excerpts.length).toBeGreaterThan(0);
|
||||
it('should render solution content', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
expect(screen.getByText('这是我们的解决方案描述')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render case tags', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
const categories = screen.getAllByText('制造业');
|
||||
expect(categories.length).toBeGreaterThan(0);
|
||||
it('should render results data', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
expect(screen.getByText('降低25%')).toBeInTheDocument();
|
||||
expect(screen.getByText('缩短85%')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render testimonial', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
expect(screen.getByText('这是客户证言内容')).toBeInTheDocument();
|
||||
const authors = screen.getAllByText('测试客户');
|
||||
expect(authors.length).toBeGreaterThan(0);
|
||||
const roles = screen.getAllByText('CTO');
|
||||
expect(roles.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should render contact button', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const contactButton = screen.getByRole('link', { name: /联系我们/i });
|
||||
expect(contactButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render duration in sidebar', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
expect(screen.getByText('2年')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sections', () => {
|
||||
it('should render customer challenges section', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const section = screen.getByText('客户遇到的成长瓶颈');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render solution section', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const section = screen.getByText('我们如何智连未来');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render growth story section', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
it('should render growth story section with key moments', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const section = screen.getByText('共同成长的故事');
|
||||
expect(section).toBeInTheDocument();
|
||||
expect(screen.getByText('关键时刻一')).toBeInTheDocument();
|
||||
expect(screen.getByText('关键时刻二')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render achievements section', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
it('should render achievements section with results', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const section = screen.getByText('今天,他们走到了哪里');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render testimonial section', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const section = screen.getByText('客户证言精选');
|
||||
expect(section).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Conditional Rendering', () => {
|
||||
it('should not render key moments section when empty', () => {
|
||||
const caseWithoutMoments = { ...mockCaseItem, keyMoments: [] };
|
||||
render(<CaseDetailClient caseItem={caseWithoutMoments} />);
|
||||
expect(screen.queryByText('共同成长的故事')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render results section when empty', () => {
|
||||
const caseWithoutResults = { ...mockCaseItem, results: [] };
|
||||
render(<CaseDetailClient caseItem={caseWithoutResults} />);
|
||||
expect(screen.queryByText('今天,他们走到了哪里')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render testimonial section when absent', () => {
|
||||
const caseWithoutTestimonial = { ...mockCaseItem, testimonial: undefined };
|
||||
render(<CaseDetailClient caseItem={caseWithoutTestimonial} />);
|
||||
expect(screen.queryByText('客户证言精选')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Navigation', () => {
|
||||
it('should have back button', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const backButton = screen.getByRole('button', { name: /返回/i });
|
||||
expect(backButton).toBeInTheDocument();
|
||||
});
|
||||
@@ -124,16 +194,16 @@ describe('CaseDetailClient', () => {
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have main landmark', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const main = screen.getByRole('main');
|
||||
expect(main).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have proper heading hierarchy', () => {
|
||||
render(<CaseDetailClient caseItem={mockCaseItem as any} />);
|
||||
render(<CaseDetailClient caseItem={mockCaseItem} />);
|
||||
const h1 = screen.getByRole('heading', { level: 1 });
|
||||
expect(h1).toBeInTheDocument();
|
||||
|
||||
|
||||
const h2s = screen.getAllByRole('heading', { level: 2 });
|
||||
expect(h2s.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -37,10 +37,16 @@ export default async function CaseDetailPage({ params }: { params: Promise<{ id:
|
||||
id: caseItem.id,
|
||||
title: caseItem.title,
|
||||
excerpt: caseItem.description,
|
||||
content: caseItem.content || '',
|
||||
content: caseItem.solution || '',
|
||||
category: caseItem.industry,
|
||||
slug: caseItem.id,
|
||||
date: '2026-01-15',
|
||||
date: caseItem.date,
|
||||
image: caseItem.image,
|
||||
challenge: caseItem.challenge,
|
||||
solution: caseItem.solution,
|
||||
keyMoments: caseItem.keyMoments,
|
||||
results: caseItem.results,
|
||||
testimonial: caseItem.testimonial,
|
||||
duration: caseItem.duration,
|
||||
}} />;
|
||||
}
|
||||
|
||||
@@ -145,12 +145,14 @@ export default function CasesPage() {
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
<Badge variant="secondary" className="flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
3年合作
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="flex items-center gap-1">
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
数字化转型
|
||||
{caseItem.duration}合作
|
||||
</Badge>
|
||||
{caseItem.tags.slice(0, 1).map((tag) => (
|
||||
<Badge key={tag} variant="secondary" className="flex items-center gap-1">
|
||||
<TrendingUp className="w-3 h-3" />
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-[#5C5C5C] text-sm line-clamp-2 mb-4">
|
||||
|
||||
Reference in New Issue
Block a user