Files
novalon-website/src/app/(marketing)/home-content-v15.test.tsx
T
zhangxiang 95dc07c3f7 refactor(home): 移除 Hero「数据驱动」产品视觉整个板块(非仅眉标)
- 删除 hero-product-visual 组件及其测试(该板块仅用于 Hero 右侧,移除后成死代码)。
- home-content-v15:移除 2 列网格的右侧列,Hero 收为单列文本 + 循环粒子层;
  同步删除 HeroProductVisual 导入与过时 L3 注释("入场静止"→"持续循环漂移")。
- 删除 home-content-v15 中对已移除板块的测试断言。

注意(视觉回归):home 快照将因右侧板块消失而大幅变化,需在
CI/本地(npx playwright install chromium + npm run dev 后 npm run test)
跑 --update-snapshots 并 review 新基线。

验证:type-check ✅ / lint ✅(0 error) / 单测 ✅(129 套 1628 passed)。
视觉回归像素门禁:本沙箱未装 Playwright 浏览器,未实跑;且因板块移除快照
必然变更,需更新基线。
2026-09-01 09:11:35 +08:00

266 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, jest } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import HomeContentV15 from './home-content-v15';
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, initial, animate, className, style, ...props }: any) => (
<div
data-testid="motion-div"
data-initial={JSON.stringify(initial)}
data-animate={JSON.stringify(animate)}
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>
),
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('next/image', () => ({
__esModule: true,
default: ({ src, alt, className }: any) => (
// eslint-disable-next-line @next/next/no-img-element
<img src={src} alt={alt} className={className} data-testid="mock-next-image" />
),
}));
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/badge', () => ({
Badge: ({ children, className }: any) => (
<span data-testid="mock-badge" className={className}>{children}</span>
),
}));
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'),
ArrowDownRight: mockIcon('arrow-down-right'),
Lightbulb: mockIcon('lightbulb'),
Database: mockIcon('database'),
Layers: mockIcon('layers'),
Code: mockIcon('code'),
Puzzle: mockIcon('puzzle'),
Server: mockIcon('server'),
Quote: mockIcon('quote'),
Newspaper: mockIcon('newspaper'),
Calendar: mockIcon('calendar'),
LayoutDashboard: mockIcon('layout-dashboard'),
BarChart3: mockIcon('bar-chart3'),
Package: mockIcon('package'),
Users: mockIcon('users'),
Settings: mockIcon('settings'),
Search: mockIcon('search'),
Bell: mockIcon('bell'),
TrendingUp: mockIcon('trending-up'),
AlertCircle: mockIcon('alert-circle'),
};
});
describe('HomeContentV15 - 浅色 Hero(暗黑模式 token 友好)', () => {
it('does not repeat the brand logo inside the hero (dedup with header)', () => {
render(<HomeContentV15 />);
const hero = screen.getByTestId('hero-section');
expect(hero).toBeInTheDocument();
// hero 内不应再有 Logo(导航栏已展示,避免重复);slogan badge 保留
expect(hero.querySelector('img')).toBeNull();
});
it('mounts the brand-red dynamic particle layer (continuous drift loop)', () => {
render(<HomeContentV15 />);
const hero = screen.getByTestId('hero-section');
const particles = screen.getByTestId('hero-particle-field');
expect(particles.tagName).toBe('CANVAS');
expect(particles).toHaveAttribute('aria-hidden', 'true');
expect(hero.contains(particles)).toBe(true);
});
it('keeps a single primary CTA and demotes the secondary action to a text link', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('hero-primary-cta')).toBeInTheDocument();
expect(screen.getByTestId('hero-primary-cta')).toHaveAttribute('href', '/contact');
expect(screen.getByTestId('hero-secondary-link')).toBeInTheDocument();
});
it('keeps hero declaration-first: stats sink out of hero into the results band', () => {
render(<HomeContentV15 stats={[{ value: '100%', label: '私有化部署' }]} />);
const hero = screen.getByTestId('hero-section');
// 声明先行:hero 内不再内联数据条(一屏只负责一句话声明 + 一个行动)
expect(hero.querySelector('[data-testid="hero-stats"]')).toBeNull();
// 数据下沉为独立成果带,仍渲染(Glance 层滚动触发)
expect(screen.getByTestId('hero-stats')).toBeInTheDocument();
});
});
describe('HomeContentV15 - 信任层与章节式叙事', () => {
it('renders verifiable trust signals with a source annotation', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('trust-section')).toBeInTheDocument();
expect(screen.getByText('100%')).toBeInTheDocument();
expect(screen.getByText('成都')).toBeInTheDocument();
// 可验证来源标注存在(每个数据卡一个)
expect(screen.getAllByText(/数据来源:/i).length).toBeGreaterThanOrEqual(4);
});
it('renders an early-access co-creation label instead of fabricated testimonials', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('early-access-banner')).toBeInTheDocument();
expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1);
});
it('renders the problem-method-result narrative with chapter numbers', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('narrative-section')).toBeInTheDocument();
expect(screen.getAllByText('01').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('02').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('03').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText(/从问题到结果/).length).toBeGreaterThan(0);
});
});
describe('HomeContentV15 - 行业洞察区(对标 Accenture Insights)', () => {
it('renders the insights pillar with typed tags and verifiable methodology content', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('insights-section')).toBeInTheDocument();
// 类型标签(对标 Research Report / Perspective / Case Study)
expect(screen.getAllByText('方法论').length).toBeGreaterThanOrEqual(2);
expect(screen.getByText('观点')).toBeInTheDocument();
// 全部链接真实页面,不虚构研究报告
expect(screen.getByText('数字化转型诊断:从哪里开始,如何衡量')).toBeInTheDocument();
});
});
describe('HomeContentV15 - 创始人观点区(对标 Accenture CEO Quote)', () => {
it('renders the founder quote with attribution (no fabricated persona)', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('founder-quote-section')).toBeInTheDocument();
expect(screen.getByText(/数字化不是一次性项目/)).toBeInTheDocument();
expect(screen.getByText('Novalon 创始团队')).toBeInTheDocument();
});
});
describe('HomeContentV15 - 新闻动态区(对标 Accenture News)', () => {
it('renders the news section with latest items and a view-all entry', () => {
render(<HomeContentV15 />);
expect(screen.getByTestId('news-section')).toBeInTheDocument();
expect(screen.getByText('四川睿新致远科技有限公司正式成立')).toBeInTheDocument();
expect(screen.getByText('查看全部新闻')).toBeInTheDocument();
});
it('renders CMS-provided news when available', () => {
render(<HomeContentV15 news={[{
id: 'cms-news-1',
title: 'CMS 新闻条目',
excerpt: 'CMS 提供的新闻摘要',
date: '2026-08-30',
category: '研发动态',
image: '',
content: '',
}]} />);
expect(screen.getByText('CMS 新闻条目')).toBeInTheDocument();
});
});
describe('HomeContentV15 - 案例区 Client Spotlight(对标 Accenture Client Spotlight)', () => {
it('renders a co-creation spotlight card (verifiable, no fabricated clients) when no cases exist', () => {
render(<HomeContentV15 />);
// 空态 spotlight:共创流程 3 步 + 深色卡 CTA
expect(screen.getAllByText('首批客户共创中').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('业务诊断').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('方案共创')).toBeInTheDocument();
expect(screen.getByText('验证发布')).toBeInTheDocument();
expect(screen.getAllByText('01').length).toBeGreaterThanOrEqual(1);
expect(screen.getByTestId('cases-co-creation-cta')).toHaveAttribute('href', '/contact');
});
it('renders a dark spotlight card with metrics for the featured case when data exists', () => {
const mockCases = [
{
slug: 'spotlight-demo',
industry: '制造业',
title: 'Spotlight 案例标题',
href: '/cases/spotlight-demo',
verified: true,
testimonial: {
quote: '这是一段客户引言,用于验证 spotlight 渲染。',
author: '客户 CIO',
role: '某制造企业',
},
metrics: [
{ value: '38%', label: '交付效率提升' },
{ value: '2x', label: '数据查询提速' },
{ value: '3周', label: '上线周期' },
],
},
{
slug: 'secondary-case',
industry: '贸易零售',
title: '次要案例标题',
href: '/cases/secondary-case',
metrics: [
{ value: '15%', label: '库存周转改善' },
{ value: '4h', label: '对账耗时' },
{ value: '9成', label: '预测准确率' },
],
},
];
render(<HomeContentV15 cases={mockCases as any} />);
// spotlight 大卡:深色画布 + 引言 + 授权 badge + 量化指标
expect(screen.getByText('Spotlight 案例标题')).toBeInTheDocument();
expect(screen.getByText('这是一段客户引言,用于验证 spotlight 渲染。')).toBeInTheDocument();
expect(screen.getByText('已获客户授权')).toBeInTheDocument();
expect(screen.getByText('38%')).toBeInTheDocument();
expect(screen.getByText('查看完整案例')).toBeInTheDocument();
// 次要案例保持轻量卡片
expect(screen.getByText('次要案例标题')).toBeInTheDocument();
expect(screen.getAllByText('了解详情').length).toBeGreaterThanOrEqual(1);
});
});