test(seo): expand structured data tests to cover all 7 schema components

- Add 24 new tests for ServiceSchema, ProductSchema, FAQSchema,
  BreadcrumbSchema, and LocalBusinessSchema (previously uncovered)
- Raise SEO component coverage from 40.89% → 100% (all metrics)
- Update jest.config.js coverage thresholds to reflect Phase 7实测 values
- Sync test-strategy-plan.md to v1.8 with latest coverage data
- Update README.md progress with 1509 tests / 73.59% stmts / 82.38% branches
This commit is contained in:
2026-08-02 13:36:28 +08:00
parent d5d04aa96d
commit a022a612c5
4 changed files with 471 additions and 215 deletions
+214 -1
View File
@@ -1,6 +1,6 @@
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { OrganizationSchema, WebsiteSchema } from './structured-data';
import { OrganizationSchema, WebsiteSchema, ServiceSchema, ProductSchema, FAQSchema, BreadcrumbSchema, LocalBusinessSchema } from './structured-data';
import { COMPANY_INFO } from '@/lib/constants';
describe('StructuredData', () => {
@@ -57,5 +57,218 @@ describe('StructuredData', () => {
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.name).toBe(COMPANY_INFO.name);
});
it('should contain SearchAction potentialAction', () => {
const { container } = render(<WebsiteSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.potentialAction).toBeDefined();
expect(schema.potentialAction['@type']).toBe('SearchAction');
});
});
describe('ServiceSchema', () => {
it('should render with default props', () => {
const { container } = render(<ServiceSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema['@type']).toBe('Service');
expect(schema.name).toBe('企业数字化转型服务');
expect(schema.provider.name).toBe(COMPANY_INFO.name);
});
it('should render with custom name and description', () => {
const { container } = render(<ServiceSchema name="技术咨询" description="专业的技术咨询服务" />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.name).toBe('技术咨询');
expect(schema.description).toBe('专业的技术咨询服务');
});
it('should render with custom areaServed', () => {
const { container } = render(<ServiceSchema areaServed="US" />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.areaServed.name).toBe('US');
});
it('should include offer catalog', () => {
const { container } = render(<ServiceSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.hasOfferCatalog).toBeDefined();
expect(schema.hasOfferCatalog.itemListElement).toHaveLength(6);
});
});
describe('ProductSchema', () => {
const requiredProps = { name: 'Test Product', description: 'A test product description' };
it('should render with required props', () => {
const { container } = render(<ProductSchema {...requiredProps} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema['@type']).toBe('SoftwareApplication');
expect(schema.name).toBe('Test Product');
expect(schema.description).toBe('A test product description');
});
it('should use default category when not provided', () => {
const { container } = render(<ProductSchema {...requiredProps} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.applicationCategory).toBe('BusinessApplication');
});
it('should use custom category when provided', () => {
const { container } = render(<ProductSchema {...requiredProps} category="GameApplication" />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.applicationCategory).toBe('GameApplication');
});
it('should include image when provided', () => {
const { container } = render(<ProductSchema {...requiredProps} image="/images/product.png" />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.image).toBe('https://www.novalon.cn/images/product.png');
});
it('should not include image when not provided', () => {
const { container } = render(<ProductSchema {...requiredProps} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.image).toBeUndefined();
});
it('should include offers and provider', () => {
const { container } = render(<ProductSchema {...requiredProps} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.offers).toBeDefined();
expect(schema.offers.priceCurrency).toBe('CNY');
expect(schema.provider.name).toBe(COMPANY_INFO.name);
});
});
describe('FAQSchema', () => {
const items = [
{ question: 'Q1?', answer: 'A1' },
{ question: 'Q2?', answer: 'A2' },
];
it('should render FAQ schema', () => {
const { container } = render(<FAQSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema['@type']).toBe('FAQPage');
});
it('should contain questions and answers', () => {
const { container } = render(<FAQSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.mainEntity).toHaveLength(2);
expect(schema.mainEntity[0].name).toBe('Q1?');
expect(schema.mainEntity[0].acceptedAnswer.text).toBe('A1');
expect(schema.mainEntity[1].name).toBe('Q2?');
expect(schema.mainEntity[1].acceptedAnswer.text).toBe('A2');
});
it('should render with empty items array', () => {
const { container } = render(<FAQSchema items={[]} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.mainEntity).toHaveLength(0);
});
});
describe('BreadcrumbSchema', () => {
const items = [
{ name: 'Home', href: '/' },
{ name: 'Products', href: '/products' },
{ name: 'ERP', href: '/products/erp' },
];
it('should render breadcrumb schema', () => {
const { container } = render(<BreadcrumbSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema['@type']).toBe('BreadcrumbList');
});
it('should contain correct number of items', () => {
const { container } = render(<BreadcrumbSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.itemListElement).toHaveLength(3);
});
it('should have correct position values', () => {
const { container } = render(<BreadcrumbSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.itemListElement[0].position).toBe(1);
expect(schema.itemListElement[1].position).toBe(2);
expect(schema.itemListElement[2].position).toBe(3);
});
it('should have correct item URLs', () => {
const { container } = render(<BreadcrumbSchema items={items} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.itemListElement[0].item).toBe('https://www.novalon.cn/');
expect(schema.itemListElement[1].item).toBe('https://www.novalon.cn/products');
});
it('should render with single item', () => {
const { container } = render(<BreadcrumbSchema items={[{ name: 'Home', href: '/' }]} />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.itemListElement).toHaveLength(1);
expect(schema.itemListElement[0].name).toBe('Home');
});
});
describe('LocalBusinessSchema', () => {
it('should render local business schema', () => {
const { container } = render(<LocalBusinessSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema['@type']).toBe('LocalBusiness');
});
it('should contain business name', () => {
const { container } = render(<LocalBusinessSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.name).toBe(COMPANY_INFO.name);
});
it('should contain address with geo coordinates', () => {
const { container } = render(<LocalBusinessSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.address).toBeDefined();
expect(schema.address.addressLocality).toBe('成都市');
expect(schema.geo).toBeDefined();
expect(schema.geo.latitude).toBe(30.5728);
});
it('should contain opening hours', () => {
const { container } = render(<LocalBusinessSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.openingHoursSpecification).toHaveLength(1);
expect(schema.openingHoursSpecification[0].dayOfWeek).toContain('Monday');
});
it('should contain area served', () => {
const { container } = render(<LocalBusinessSchema />);
const script = container.querySelector('script[type="application/ld+json"]');
const schema = JSON.parse(script?.textContent || '{}');
expect(schema.areaServed).toHaveLength(3);
expect(schema.areaServed[0].name).toBe('成都');
});
});
});