feat(analytics): enhance Google Analytics with privacy compliance and comprehensive tracking

- Add automatic route change tracking for SPA navigation
- Implement Cookie consent banner for GDPR compliance
- Add performance tracking (LCP, FID, CLS Web Vitals)
- Add outbound link click tracking
- Integrate contact form submission tracking with conversion events
- Add CTA button click tracking in hero section
- Integrate error tracking in ErrorBoundary component
- Extend analytics utility library with 15+ tracking functions
- Configure IP anonymization and privacy settings
- Remove unused test files and deployment scripts
- Update case studies to include only specified cases
- Fix mobile navigation active state issues
- Fix lint errors in test files and components

BREAKING CHANGE: Google Analytics now requires user consent before tracking
This commit is contained in:
张翔
2026-04-22 07:19:29 +08:00
parent b117372b03
commit 2f45818724
45 changed files with 652 additions and 2293 deletions
-171
View File
@@ -1,171 +0,0 @@
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
import { render, screen, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, className, ...props }: any) => (
<div className={className} {...props}>
{children}
</div>
),
section: ({ children, className, ...props }: any) => (
<section className={className} {...props}>
{children}
</section>
),
},
AnimatePresence: ({ children }: any) => <>{children}</>,
useInView: () => [null, true],
}));
jest.mock('next/link', () => {
return ({ children, href, ...props }: any) => (
<a href={href} {...props}>
{children}
</a>
);
});
jest.mock('lucide-react', () => ({
ArrowRight: () => <span data-testid="arrow-right-icon" />,
ArrowLeft: () => <span data-testid="arrow-left-icon" />,
Code: () => <span data-testid="code-icon" />,
Cloud: () => <span data-testid="cloud-icon" />,
BarChart3: () => <span data-testid="bar-chart-icon" />,
Shield: () => <span data-testid="shield-icon" />,
Search: () => <span data-testid="search-icon" />,
ChevronLeft: () => <span data-testid="chevron-left-icon" />,
ChevronRight: () => <span data-testid="chevron-right-icon" />,
Filter: () => <span data-testid="filter-icon" />,
}));
jest.mock('@/components/ui/button', () => ({
Button: ({ children, className, ...props }: any) => (
<button className={className} {...props}>
{children}
</button>
),
}));
jest.mock('@/components/ui/badge', () => ({
Badge: ({ children, className, ...props }: any) => (
<span className={className} {...props}>
{children}
</span>
),
}));
jest.mock('@/components/ui/input', () => ({
Input: ({ className, ...props }: any) => (
<input className={className} {...props} />
),
}));
jest.mock('@/components/ui/loading-skeleton', () => ({
ServiceCardSkeleton: () => <div data-testid="service-card-skeleton">Loading...</div>,
}));
jest.mock('@/components/ui/page-header', () => ({
PageHeader: ({ title, description }: any) => (
<header>
<h1>{title}</h1>
<p>{description}</p>
</header>
),
}));
const mockServices = [
{
id: 'software-dev',
title: '软件开发',
icon: 'Code',
description: '定制化软件开发服务',
features: ['需求分析', '架构设计', '开发测试', '运维支持'],
},
{
id: 'cloud-service',
title: '云服务',
icon: 'Cloud',
description: '企业云服务解决方案',
features: ['云迁移', '云原生', '云安全', '云运维'],
},
];
jest.mock('@/hooks/use-services', () => ({
useServices: () => ({
services: mockServices,
loading: false,
error: null,
}),
}));
import ServicesPage from './page';
describe('ServicesPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('Rendering', () => {
it('should render services page', async () => {
const { container } = render(<ServicesPage />);
await waitFor(() => {
const pageContainer = container.querySelector('.min-h-screen');
expect(pageContainer).toBeInTheDocument();
});
});
it('should render page header', async () => {
render(<ServicesPage />);
await waitFor(() => {
const title = screen.getByText(/核心业务/i);
expect(title).toBeInTheDocument();
});
});
it('should render back to home link', async () => {
render(<ServicesPage />);
await waitFor(() => {
const backLink = screen.getByText(/返回首页/i);
expect(backLink).toBeInTheDocument();
});
});
it('should render loading skeletons initially', async () => {
render(<ServicesPage />);
await waitFor(() => {
const pageContainer = screen.queryByText('加载中...');
expect(pageContainer).not.toBeInTheDocument();
});
});
it('should render CTA section', async () => {
render(<ServicesPage />);
await waitFor(() => {
const cta = screen.getByText(/准备开始您的数字化转型之旅/i);
expect(cta).toBeInTheDocument();
});
});
});
describe('Navigation', () => {
it('should have contact link', async () => {
render(<ServicesPage />);
await waitFor(() => {
const contactLink = screen.getByRole('link', { name: /立即咨询/i });
expect(contactLink).toHaveAttribute('href', '/contact');
});
});
});
describe('Accessibility', () => {
it('should have proper heading hierarchy', async () => {
render(<ServicesPage />);
await waitFor(() => {
const h1 = screen.getByRole('heading', { level: 1 });
expect(h1).toBeInTheDocument();
});
});
});
});