test(e2e,unit): add navigation edge cases and component coverage

- Add missing-paths E2E spec for desktop/mobile navigation and contrast
- Add local Playwright config for dev server testing
- Expand unit tests for CMS data server, products, contact and UI components
- Adjust jest config for new test patterns
This commit is contained in:
张翔
2026-07-08 14:47:07 +08:00
parent 7f36211342
commit a6ea2cc72c
11 changed files with 1693 additions and 34 deletions
+5 -8
View File
@@ -258,6 +258,7 @@ const mockProduct = {
specs: ['支持 10 万并发', '99.9% 可用性'],
tags: ['ERP', '企业管理'],
heroThemeId: 'erp',
bundle: 'enterprise' as const,
caseStudies: [
{ client: '某制造企业', industry: '制造业', challenge: '库存管理混乱', solution: '实施ERP系统', result: '库存周转率提升200%' },
],
@@ -403,8 +404,7 @@ describe('ProductValueSection', () => {
it('should render product title and overview', async () => {
const { ProductValueSection } = await import('./detail-product-value');
render(<ProductValueSection product={mockProduct} />);
expect(screen.getByText('核心能力')).toBeInTheDocument();
expect(screen.getByText(mockProduct.title)).toBeInTheDocument();
expect(screen.getByRole('heading', { name: /为企业打造的/ })).toHaveTextContent(mockProduct.title);
expect(screen.getByText(mockProduct.overview)).toBeInTheDocument();
});
@@ -434,7 +434,6 @@ describe('ProductValueSection', () => {
it('should render data proofs when available', async () => {
const { ProductValueSection } = await import('./detail-product-value');
render(<ProductValueSection product={mockProduct} />);
expect(screen.getByText('数据说话')).toBeInTheDocument();
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
});
@@ -442,7 +441,7 @@ describe('ProductValueSection', () => {
const { ProductValueSection } = await import('./detail-product-value');
const productWithoutData = { ...mockProduct, dataProofs: [] };
render(<ProductValueSection product={productWithoutData} />);
expect(screen.queryByText('数据说话')).not.toBeInTheDocument();
expect(screen.queryByText('用真实效果证明价值')).not.toBeInTheDocument();
});
it('should render ink glow cards for features', async () => {
@@ -468,7 +467,6 @@ describe('DetailTrustSection', () => {
dataProofs={mockProduct.dataProofs}
/>
);
expect(screen.getByText('数据说话')).toBeInTheDocument();
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
expect(screen.getByText(mockProduct.dataProofs[0]!.metric)).toBeInTheDocument();
});
@@ -480,7 +478,6 @@ describe('DetailTrustSection', () => {
caseStudies={mockProduct.caseStudies}
/>
);
expect(screen.getByText('客户案例')).toBeInTheDocument();
expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument();
expect(screen.getByTestId('case-study-card')).toBeInTheDocument();
expect(screen.getByTestId('case-client')).toHaveTextContent('某制造企业');
@@ -508,8 +505,8 @@ describe('DetailTrustSection', () => {
accentColor="#C41E3A"
/>
);
expect(screen.getByText('数据说话')).toBeInTheDocument();
expect(screen.getByText('客户案例')).toBeInTheDocument();
expect(screen.getByText('用真实效果证明价值')).toBeInTheDocument();
expect(screen.getByText('他们已经实现了转型突破')).toBeInTheDocument();
expect(screen.getByText('资质认证')).toBeInTheDocument();
});
});
+2 -4
View File
@@ -248,11 +248,10 @@ describe('ProductCard', () => {
});
describe('ServiceCard', () => {
it('should render number, title, description and link', async () => {
it('should render title, description and link', async () => {
const { ServiceCard } = await import('./service-card');
render(
<ServiceCard
number="01"
title="战略咨询"
description="业务诊断与战略规划"
href="/services/consulting"
@@ -268,7 +267,6 @@ describe('ServiceCard', () => {
const { ServiceCard } = await import('./service-card');
render(
<ServiceCard
number="02"
title="Data"
description="Analysis"
href="/services/data"
@@ -280,7 +278,7 @@ describe('ServiceCard', () => {
expect(target).toBeTruthy();
});
it('should apply custom color', async () => {
it('should accept optional number and color props for compatibility', async () => {
const { ServiceCard } = await import('./service-card');
render(
<ServiceCard
+10 -3
View File
@@ -10,7 +10,7 @@ jest.mock('@/hooks/use-reduced-motion', () => ({
describe('ScrollProgress', () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock document height for predictable progress calculation
// Mock document/scroll geometry for predictable progress calculation
Object.defineProperty(document.documentElement, 'scrollHeight', {
value: 1000,
configurable: true,
@@ -19,6 +19,11 @@ describe('ScrollProgress', () => {
value: 500,
configurable: true,
});
Object.defineProperty(window, 'scrollY', {
value: 0,
configurable: true,
writable: true,
});
});
it('should always render the progress container', () => {
@@ -44,8 +49,10 @@ describe('ScrollProgress', () => {
const { container } = render(<ScrollProgress />);
const innerBar = (container.firstChild as HTMLElement).firstChild as HTMLElement;
expect(innerBar).toBeInTheDocument();
// 默认 progress 为 0宽度为 0%
expect(innerBar.style.width).toBe('0%');
// 默认 progress 为 0使用 transform scaleX 实现宽度变化
expect(innerBar.style.width).toBe('100%');
expect(innerBar.style.transform).toBe('scaleX(0)');
expect(innerBar.style.transformOrigin).toBe('left');
});
it('should apply default height of 2px', () => {
+5 -2
View File
@@ -48,13 +48,16 @@ export function ScrollProgress({
height: `${height}px`,
}}
aria-hidden="true"
data-testid="scroll-progress"
>
<div
style={{
width: `${shouldReduceMotion ? 0 : progress}%`,
width: '100%',
height: '100%',
background: color,
transition: shouldReduceMotion ? 'none' : 'width 0.1s linear',
transformOrigin: 'left',
transform: `scaleX(${shouldReduceMotion ? 0 : progress / 100})`,
transition: shouldReduceMotion ? 'none' : 'transform 0.1s linear',
}}
/>
</div>