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:
@@ -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('成都');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user