diff --git a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
index d2b34c5..eb6a528 100644
--- a/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
+++ b/src/app/(marketing)/news/[slug]/NewsDetailClient.tsx
@@ -52,9 +52,19 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
className="max-w-4xl"
>
-
- 📰
-
+ {news.image ? (
+
+

+
+ ) : (
+
+ 📰
+
+ )}
{news.excerpt}
@@ -74,8 +84,18 @@ export function NewsDetailClient({ news }: NewsDetailClientProps) {
{relatedNews.map((related) => (
-
-
📰
+
+ {related.image ? (
+

+ ) : (
+
+ 📰
+
+ )}
{related.category}
diff --git a/src/app/(marketing)/news/page.test.tsx b/src/app/(marketing)/news/page.test.tsx
deleted file mode 100644
index 43b766c..0000000
--- a/src/app/(marketing)/news/page.test.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-import { describe, it, expect, jest, beforeAll, beforeEach } from '@jest/globals';
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import '@testing-library/jest-dom';
-
-jest.mock('framer-motion', () => ({
- motion: {
- div: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- section: ({ children, className, ...props }: any) => (
-
- ),
- },
- AnimatePresence: ({ children }: any) => <>{children}>,
- useInView: () => [null, true],
-}));
-
-jest.mock('next/link', () => {
- return ({ children, href, ...props }: any) => (
-
- {children}
-
- );
-});
-
-jest.mock('lucide-react', () => ({
- Search: () => ,
- Calendar: () => ,
- ArrowRight: () => ,
- ArrowLeft: () => ,
- Filter: () => ,
- ChevronLeft: () => ,
- ChevronRight: () => ,
-}));
-
-jest.mock('@/components/ui/button', () => ({
- Button: ({ children, className, onClick, ...props }: any) => (
-
- ),
-}));
-
-jest.mock('@/components/ui/badge', () => ({
- Badge: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
-}));
-
-jest.mock('@/components/ui/card', () => ({
- Card: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- CardContent: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
-}));
-
-jest.mock('@/components/ui/input', () => ({
- Input: ({ className, ...props }: any) => (
-
- ),
-}));
-
-jest.mock('@/components/ui/page-header', () => ({
- PageHeader: ({ title, description }: any) => (
-
- {title}
- {description}
-
- ),
-}));
-
-const mockNews = [
- {
- id: 'news-1',
- title: '公司成立新闻',
- category: '公司新闻',
- date: '2026-01-15',
- excerpt: '公司正式成立,开启数字化转型之旅',
- content: '详细内容',
- slug: 'company-founded',
- },
- {
- id: 'news-2',
- title: '产品发布新闻',
- category: '产品发布',
- date: '2026-02-01',
- excerpt: '新产品正式发布',
- content: '详细内容',
- slug: 'product-released',
- },
-];
-
-jest.mock('@/hooks/use-news', () => ({
- useNews: () => ({
- news: mockNews,
- loading: false,
- error: null,
- }),
-}));
-
-import NewsListPage from './page';
-
-describe('NewsListPage', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- describe('Rendering', () => {
- it('should render news page', () => {
- const { container } = render();
- const pageContainer = container.querySelector('.min-h-screen');
- expect(pageContainer).toBeInTheDocument();
- });
-
- it('should render page header', async () => {
- render();
- await waitFor(() => {
- const title = screen.getByText(/新闻动态/i);
- expect(title).toBeInTheDocument();
- });
- });
-
- it('should render back to home link', async () => {
- render();
- await waitFor(() => {
- const backLink = screen.getByText(/返回首页/i);
- expect(backLink).toBeInTheDocument();
- });
- });
-
- it('should render news cards', async () => {
- render();
- await waitFor(() => {
- const headings = screen.getAllByRole('heading');
- const newsCards = headings.filter(h => h.tagName === 'H3');
- expect(newsCards.length).toBeGreaterThan(0);
- });
- });
-
- it('should render category filter', async () => {
- render();
- await waitFor(() => {
- const allButton = screen.getByRole('button', { name: '全部' });
- expect(allButton).toBeInTheDocument();
- });
- });
-
- it('should render search input', async () => {
- render();
- await waitFor(() => {
- const searchInput = screen.getByPlaceholderText(/搜索新闻/i);
- expect(searchInput).toBeInTheDocument();
- });
- });
- });
-
- describe('Filtering', () => {
- it('should filter news by category', async () => {
- render();
- await waitFor(() => {
- const companyNewsButton = screen.getByRole('button', { name: '公司新闻' });
- fireEvent.click(companyNewsButton);
- });
-
- await waitFor(() => {
- const headings = screen.getAllByRole('heading');
- const newsCards = headings.filter(h => h.tagName === 'H3');
- expect(newsCards.length).toBe(1);
- });
- });
-
- it('should filter news by search query', async () => {
- render();
- await waitFor(() => {
- const searchInput = screen.getByPlaceholderText(/搜索新闻/i);
- fireEvent.change(searchInput, { target: { value: '成立' } });
- });
-
- await waitFor(() => {
- const headings = screen.getAllByRole('heading');
- const newsCards = headings.filter(h => h.tagName === 'H3');
- expect(newsCards.length).toBe(1);
- });
- });
- });
-
- describe('Navigation', () => {
- it('should have news detail links', async () => {
- render();
- await waitFor(() => {
- const links = screen.getAllByRole('link');
- const newsLinks = links.filter(link => link.getAttribute('href')?.startsWith('/news/'));
- expect(newsLinks.length).toBeGreaterThan(0);
- });
- });
- });
-
- describe('Accessibility', () => {
- it('should have proper heading hierarchy', async () => {
- render();
- await waitFor(() => {
- const h1 = screen.getByRole('heading', { level: 1 });
- expect(h1).toBeInTheDocument();
- });
- });
- });
-});
diff --git a/src/app/(marketing)/news/page.tsx b/src/app/(marketing)/news/page.tsx
index 0ccbbf0..d431525 100644
--- a/src/app/(marketing)/news/page.tsx
+++ b/src/app/(marketing)/news/page.tsx
@@ -12,7 +12,7 @@ import { Search, Calendar, Filter, ChevronLeft, ChevronRight, ArrowRight } from
import { StaticLink } from '@/components/ui/static-link';
import { motion } from 'framer-motion';
-const categories = ['全部', '公司新闻', '产品发布', '合作动态', '行业资讯'];
+const categories = ['全部', '公司新闻', '产品发布'];
const ITEMS_PER_PAGE = 9;
export default function NewsListPage() {
@@ -98,6 +98,7 @@ export default function NewsListPage() {
value={searchQuery}
onChange={handleSearchChange}
className="pl-10"
+ aria-label="搜索新闻"
/>
diff --git a/src/app/(marketing)/page.test.tsx b/src/app/(marketing)/page.test.tsx
deleted file mode 100644
index f2481ce..0000000
--- a/src/app/(marketing)/page.test.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { describe, it, expect, jest, beforeAll } from '@jest/globals';
-import { render, screen } from '@testing-library/react';
-import '@testing-library/jest-dom';
-
-jest.mock('next/navigation', () => ({
- useSearchParams: () => ({
- get: jest.fn(),
- }),
-}));
-
-jest.mock('framer-motion', () => ({
- motion: {
- div: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- section: ({ children, className, ...props }: any) => (
-
- ),
- span: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- h1: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- },
- AnimatePresence: ({ children }: any) => <>{children}>,
- useInView: () => [null, true],
-}));
-
-jest.mock('next/link', () => {
- return ({ children, href, ...props }: any) => (
-
- {children}
-
- );
-});
-
-jest.mock('@/db', () => ({
- db: {
- select: jest.fn().mockReturnValue({
- from: jest.fn().mockResolvedValue([]),
- }),
- },
-}));
-
-jest.mock('@/components/sections/hero-section', () => ({
- HeroSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/sections/services-section', () => ({
- ServicesSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/sections/products-section', () => ({
- ProductsSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/sections/cases-section', () => ({
- CasesSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/sections/about-section', () => ({
- AboutSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/sections/news-section', () => ({
- NewsSection: () => (
-
- ),
-}));
-
-jest.mock('@/components/ui/loading-skeleton', () => ({
- SectionSkeleton: () =>
Loading...
,
-}));
-
-jest.mock('next/dynamic', () => ({
- __esModule: true,
- default: (importFn: any) => {
- const mockComponents: Record
= {
- '@/components/sections/services-section': () => (
-
- ),
- '@/components/sections/products-section': () => (
-
- ),
- '@/components/sections/cases-section': () => (
-
- ),
- '@/components/sections/about-section': () => (
-
- ),
- '@/components/sections/news-section': () => (
-
- ),
- };
-
- const importString = importFn.toString();
- for (const [key, component] of Object.entries(mockComponents)) {
- if (importString.includes(key.replace('@/components/sections/', ''))) {
- return component;
- }
- }
-
- return () => Mocked Dynamic Component
;
- },
-}));
-
-import { HomeContent } from './home-content';
-
-describe('HomePage', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- describe('Rendering', () => {
- it('should render home page', () => {
- render();
- const main = screen.getByRole('main');
- expect(main).toBeInTheDocument();
- });
-
- it('should render hero section', () => {
- render();
- const heroSection = document.querySelector('#home');
- expect(heroSection).toBeInTheDocument();
- });
-
- it('should render services section', () => {
- render();
- const servicesSection = document.querySelector('#services');
- expect(servicesSection).toBeInTheDocument();
- });
-
- it('should render products section', () => {
- render();
- const productsSection = document.querySelector('#products');
- expect(productsSection).toBeInTheDocument();
- });
-
- it('should render cases section', () => {
- render();
- const casesSection = document.querySelector('#cases');
- expect(casesSection).toBeInTheDocument();
- });
-
- it('should render about section', () => {
- render();
- const aboutSection = document.querySelector('#about');
- expect(aboutSection).toBeInTheDocument();
- });
-
- it('should render news section', () => {
- render();
- const newsSection = document.querySelector('#news');
- expect(newsSection).toBeInTheDocument();
- });
- });
-
- describe('Accessibility', () => {
- it('should have main landmark', () => {
- render();
- const main = screen.getByRole('main');
- expect(main).toBeInTheDocument();
- });
-
- it('should have proper heading hierarchy', () => {
- render();
- const h1 = screen.getByRole('heading', { level: 1 });
- expect(h1).toBeInTheDocument();
- });
- });
-});
diff --git a/src/app/(marketing)/products/page.test.tsx b/src/app/(marketing)/products/page.test.tsx
deleted file mode 100644
index b678437..0000000
--- a/src/app/(marketing)/products/page.test.tsx
+++ /dev/null
@@ -1,212 +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) => (
-
- {children}
-
- ),
- section: ({ children, className, ...props }: any) => (
-
- ),
- },
- AnimatePresence: ({ children }: any) => <>{children}>,
- useInView: () => [null, true],
-}));
-
-jest.mock('next/link', () => {
- return ({ children, href, ...props }: any) => (
-
- {children}
-
- );
-});
-
-jest.mock('lucide-react', () => ({
- ArrowRight: () => ,
- ArrowLeft: () => ,
- Check: () => ,
- TrendingUp: () => ,
- Search: () => ,
- ChevronLeft: () => ,
- ChevronRight: () => ,
- Filter: () => ,
-}));
-
-jest.mock('@/components/ui/button', () => ({
- Button: ({ children, className, ...props }: any) => (
-
- ),
-}));
-
-jest.mock('@/components/ui/badge', () => ({
- Badge: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
-}));
-
-jest.mock('@/components/ui/input', () => ({
- Input: ({ className, ...props }: any) => (
-
- ),
-}));
-
-jest.mock('@/components/ui/card', () => ({
- Card: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- CardContent: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- CardHeader: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- CardTitle: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
- CardDescription: ({ children, className, ...props }: any) => (
-
- {children}
-
- ),
-}));
-
-jest.mock('@/components/ui/page-header', () => ({
- PageHeader: ({ title, description }: any) => (
-
- {title}
- {description}
-
- ),
-}));
-
-const mockProducts = [
- {
- id: 'erp',
- title: 'ERP企业资源计划',
- category: '软件产品',
- description: '一站式企业资源管理解决方案',
- features: ['财务管理', '供应链管理', '生产管理', '人力资源'],
- benefits: ['提高运营效率', '降低管理成本'],
- },
- {
- id: 'crm',
- title: 'CRM客户关系管理',
- category: '软件产品',
- description: '智能化客户关系管理平台',
- features: ['客户管理', '销售管理', '营销自动化', '数据分析'],
- benefits: ['提升客户满意度', '增加销售收入'],
- },
-];
-
-jest.mock('@/hooks/use-products', () => ({
- useProducts: () => ({
- products: mockProducts,
- loading: false,
- error: null,
- }),
-}));
-
-import ProductsPage from './page';
-
-describe('ProductsPage', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- describe('Rendering', () => {
- it('should render products page', async () => {
- const { container } = render();
- await waitFor(() => {
- const pageContainer = container.querySelector('.min-h-screen');
- expect(pageContainer).toBeInTheDocument();
- });
- });
-
- it('should render page header', async () => {
- render();
- await waitFor(() => {
- const title = screen.getByText(/产品服务/i);
- expect(title).toBeInTheDocument();
- });
- });
-
- it('should render back to home link', async () => {
- render();
- await waitFor(() => {
- const backLink = screen.getByText(/返回首页/i);
- expect(backLink).toBeInTheDocument();
- });
- });
-
- it('should render product cards', async () => {
- render();
- await waitFor(() => {
- const productTitles = screen.getAllByRole('heading', { level: 3 });
- expect(productTitles.length).toBeGreaterThan(0);
- });
- });
-
- it('should render product categories', async () => {
- render();
- await waitFor(() => {
- const categories = screen.getAllByText(/软件产品/i);
- expect(categories.length).toBeGreaterThan(0);
- });
- });
-
- it('should render CTA section', async () => {
- render();
- await waitFor(() => {
- const cta = screen.getByText(/需要定制化解决方案/i);
- expect(cta).toBeInTheDocument();
- });
- });
- });
-
- describe('Navigation', () => {
- it('should have product detail links', async () => {
- render();
- await waitFor(() => {
- const links = screen.getAllByRole('link');
- const productLinks = links.filter(link => link.getAttribute('href')?.startsWith('/products/'));
- expect(productLinks.length).toBeGreaterThan(0);
- });
- });
-
- it('should have contact link', async () => {
- render();
- await waitFor(() => {
- const contactLink = screen.getByRole('link', { name: /联系我们/i });
- expect(contactLink).toHaveAttribute('href', '/contact');
- });
- });
- });
-
- describe('Accessibility', () => {
- it('should have proper heading hierarchy', async () => {
- render();
- await waitFor(() => {
- const h1 = screen.getByRole('heading', { level: 1 });
- expect(h1).toBeInTheDocument();
- });
- });
- });
-});
diff --git a/src/app/(marketing)/products/page.tsx b/src/app/(marketing)/products/page.tsx
index 038c80e..ef12b01 100644
--- a/src/app/(marketing)/products/page.tsx
+++ b/src/app/(marketing)/products/page.tsx
@@ -99,6 +99,7 @@ export default function ProductsPage() {
value={searchQuery}
onChange={handleSearchChange}
className="pl-10"
+ aria-label="搜索产品"
/>
diff --git a/src/app/(marketing)/services/[id]/client.tsx b/src/app/(marketing)/services/[id]/client.tsx
index 4b86acd..55266d5 100644
--- a/src/app/(marketing)/services/[id]/client.tsx
+++ b/src/app/(marketing)/services/[id]/client.tsx
@@ -103,7 +103,7 @@ export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
const Icon = iconMap[service.icon];
return (
-
+
@@ -272,6 +272,6 @@ export function ServiceDetailClient({ service }: ServiceDetailClientProps) {
-
+