/**
* SchemaGenerator Tests
*
* Tests for the SEO schema markup generation functionality including
* all supported Schema.org types, content extraction, and validation.
*
* @since 2.7.0
*/
import { describe, it, expect, beforeEach } from "vitest";
import { SchemaGenerator } from "../../../dist/tools/seo/generators/SchemaGenerator.js";
describe("SchemaGenerator", () => {
let generator;
beforeEach(() => {
generator = new SchemaGenerator();
});
describe("Basic functionality", () => {
it("should be instantiable", () => {
expect(generator).toBeDefined();
expect(generator).toBeInstanceOf(SchemaGenerator);
});
it("should have generateSchema method", () => {
expect(generator.generateSchema).toBeDefined();
expect(typeof generator.generateSchema).toBe("function");
});
it("should have validateSchema method", () => {
expect(generator.validateSchema).toBeDefined();
expect(typeof generator.validateSchema).toBe("function");
});
});
describe("Article Schema Generation", () => {
const samplePost = {
id: 1,
title: { rendered: "Complete Guide to WordPress SEO" },
content: {
rendered: `
<h1>Complete Guide to WordPress SEO</h1>
<p>WordPress SEO is essential for increasing your website's visibility in search engines.
This comprehensive guide covers all aspects of optimizing your WordPress site for better rankings.</p>
<img src="https://example.com/seo-guide.jpg" alt="SEO Guide">
<h2>Why SEO Matters</h2>
<p>Search Engine Optimization helps your content reach more people and drives organic traffic to your site.</p>
`,
},
excerpt: { rendered: "Learn comprehensive WordPress SEO techniques to improve your search rankings." },
link: "https://example.com/wordpress-seo-guide",
date: "2023-01-15T10:00:00Z",
modified: "2023-01-16T12:00:00Z",
status: "publish",
type: "post",
};
it("should generate Article schema markup", async () => {
const params = {
postId: 1,
schemaType: "Article",
site: "test",
};
const options = {
includeAuthor: true,
includeOrganization: true,
siteConfig: {
name: "SEO Blog",
url: "https://example.com",
logo: "https://example.com/logo.png",
},
};
const result = await generator.generateSchema(samplePost, params, options);
expect(result).toBeDefined();
expect(result["@context"]).toBe("https://schema.org");
expect(result["@type"]).toBe("Article");
expect(result.headline).toBe("Complete Guide to WordPress SEO");
expect(result.description).toContain("WordPress SEO techniques");
expect(result.datePublished).toBe("2023-01-15T10:00:00Z");
expect(result.dateModified).toBe("2023-01-16T12:00:00Z");
expect(result.author).toBeDefined();
expect(result.author["@type"]).toBe("Person");
expect(result.publisher).toBeDefined();
expect(result.publisher["@type"]).toBe("Organization");
expect(result.mainEntityOfPage).toBe("https://example.com/wordpress-seo-guide");
expect(result.wordCount).toBeGreaterThan(0);
expect(result.image).toBeDefined();
expect(Array.isArray(result.image)).toBe(true);
});
it("should generate Article schema markup with different content", async () => {
const params = {
postId: 1,
schemaType: "Article",
site: "test",
};
const result = await generator.generateSchema(samplePost, params);
expect(result["@type"]).toBe("Article");
expect(result.headline).toBeDefined();
expect(result.author).toBeDefined();
expect(result.publisher).toBeDefined();
});
it("should handle posts without excerpts", async () => {
const postWithoutExcerpt = {
...samplePost,
excerpt: { rendered: "" },
};
const params = {
postId: 1,
schemaType: "Article",
site: "test",
};
const result = await generator.generateSchema(postWithoutExcerpt, params);
expect(result.description).toBeDefined();
expect(result.description.length).toBeGreaterThan(0);
expect(result.description).toContain("WordPress SEO is essential");
});
it("should include custom keywords when provided", async () => {
const params = {
postId: 1,
schemaType: "Article",
site: "test",
};
const options = {
customProperties: {
keywords: ["WordPress", "SEO", "optimization", "guide"],
},
};
const result = await generator.generateSchema(samplePost, params, options);
expect(result.keywords).toBeDefined();
expect(Array.isArray(result.keywords)).toBe(true);
expect(result.keywords).toContain("WordPress");
expect(result.keywords).toContain("SEO");
});
});
describe("Product Schema Generation", () => {
const productPost = {
id: 2,
title: { rendered: "Premium WordPress Theme" },
content: {
rendered: `
<h1>Premium WordPress Theme</h1>
<p>A professional WordPress theme with advanced features and responsive design.</p>
<img src="https://example.com/theme-preview.jpg" alt="Theme Preview">
<p>Price: $99</p>
<p>Brand: ThemeCompany</p>
`,
},
excerpt: { rendered: "Professional WordPress theme with responsive design and advanced features." },
link: "https://example.com/premium-theme",
status: "publish",
type: "product",
};
it("should generate Product schema markup", async () => {
const params = {
postId: 2,
schemaType: "Product",
site: "test",
};
const result = await generator.generateSchema(productPost, params);
expect(result["@type"]).toBe("Product");
expect(result.name).toBe("Premium WordPress Theme");
expect(result.description).toContain("Professional WordPress theme");
expect(result.image).toBeDefined();
expect(Array.isArray(result.image)).toBe(true);
});
it("should handle products without specific product data", async () => {
const simpleProduct = {
...productPost,
content: { rendered: "<p>Simple product description without pricing.</p>" },
};
const params = {
postId: 2,
schemaType: "Product",
site: "test",
};
const result = await generator.generateSchema(simpleProduct, params);
expect(result.name).toBeDefined();
expect(result.description).toBeDefined();
// Should not have offers without price data
expect(result.offers).toBeUndefined();
});
});
describe("FAQ Schema Generation", () => {
const faqPost = {
id: 3,
title: { rendered: "WordPress SEO FAQ" },
content: {
rendered: `
<h1>WordPress SEO FAQ</h1>
<h2>What is WordPress SEO?</h2>
<p>WordPress SEO refers to the practice of optimizing your WordPress website to rank higher in search engines.</p>
<h2>How do I improve my WordPress SEO?</h2>
<p>You can improve WordPress SEO by optimizing content, using proper headings, adding meta descriptions, and using SEO plugins.</p>
<h3>Do I need an SEO plugin?</h3>
<p>While not strictly necessary, SEO plugins like Yoast or RankMath make optimization much easier.</p>
`,
},
excerpt: { rendered: "Frequently asked questions about WordPress SEO optimization." },
link: "https://example.com/wordpress-seo-faq",
status: "publish",
type: "page",
};
it("should generate FAQ schema markup", async () => {
const params = {
postId: 3,
schemaType: "FAQ",
site: "test",
};
const result = await generator.generateSchema(faqPost, params);
expect(result["@type"]).toBe("FAQ");
expect(result.mainEntity).toBeDefined();
expect(Array.isArray(result.mainEntity)).toBe(true);
expect(result.mainEntity.length).toBeGreaterThan(0);
const firstQuestion = result.mainEntity[0];
expect(firstQuestion["@type"]).toBe("Question");
expect(firstQuestion.name).toContain("What is WordPress SEO");
expect(firstQuestion.acceptedAnswer).toBeDefined();
expect(firstQuestion.acceptedAnswer["@type"]).toBe("Answer");
expect(firstQuestion.acceptedAnswer.text).toContain("optimizing your WordPress website");
});
it("should handle content without FAQ structure", async () => {
const nonFaqPost = {
...faqPost,
content: { rendered: "<p>This is regular content without FAQ structure.</p>" },
};
const params = {
postId: 3,
schemaType: "FAQ",
site: "test",
};
const result = await generator.generateSchema(nonFaqPost, params);
expect(result.mainEntity).toBeDefined();
expect(Array.isArray(result.mainEntity)).toBe(true);
expect(result.mainEntity.length).toBe(0);
});
});
describe("HowTo Schema Generation", () => {
const howToPost = {
id: 4,
title: { rendered: "How to Install WordPress" },
content: {
rendered: `
<h1>How to Install WordPress</h1>
<p>Learn how to install WordPress in just a few simple steps. This process takes about 5 minutes.</p>
<img src="https://example.com/wordpress-install.jpg" alt="WordPress Installation">
<h2>Step 1: Download WordPress</h2>
<p>Visit wordpress.org and download the latest version of WordPress.</p>
<h2>Step 2: Upload Files</h2>
<p>Upload the WordPress files to your web server using FTP or hosting control panel.</p>
<h2>Step 3: Create Database</h2>
<p>Create a MySQL database and user for your WordPress installation.</p>
`,
},
excerpt: { rendered: "Step-by-step guide to installing WordPress on your website." },
link: "https://example.com/install-wordpress",
status: "publish",
type: "post",
};
it("should generate HowTo schema markup", async () => {
const params = {
postId: 4,
schemaType: "HowTo",
site: "test",
};
const result = await generator.generateSchema(howToPost, params);
expect(result["@type"]).toBe("HowTo");
expect(result.name).toBe("How to Install WordPress");
expect(result.description).toContain("Step-by-step guide");
expect(result.image).toBeDefined();
expect(result.totalTime).toBe("PT5M"); // 5 minutes
expect(result.step).toBeDefined();
expect(Array.isArray(result.step)).toBe(true);
expect(result.step.length).toBeGreaterThan(0);
const firstStep = result.step[0];
expect(firstStep["@type"]).toBe("HowToStep");
expect(firstStep.position).toBe(1);
expect(firstStep.name).toContain("Download WordPress");
expect(firstStep.text).toContain("Visit wordpress.org");
});
it("should extract duration from content", async () => {
const timedPost = {
...howToPost,
content: {
rendered: "<p>This tutorial takes approximately 30 minutes to complete.</p>",
},
};
const params = {
postId: 4,
schemaType: "HowTo",
site: "test",
};
const result = await generator.generateSchema(timedPost, params);
expect(result.totalTime).toBe("PT30M");
});
});
describe("Organization Schema Generation", () => {
const organizationPost = {
id: 5,
title: { rendered: "About TechCorp" },
content: {
rendered: `
<h1>About TechCorp</h1>
<p>TechCorp is a leading technology company specializing in web development and digital marketing solutions.</p>
<img src="https://example.com/techcorp-logo.jpg" alt="TechCorp Logo">
`,
},
excerpt: { rendered: "Leading technology company specializing in web development solutions." },
link: "https://techcorp.com/about",
status: "publish",
type: "page",
};
it("should generate Organization schema markup", async () => {
const params = {
postId: 5,
schemaType: "Organization",
site: "test",
};
const options = {
siteConfig: {
name: "TechCorp",
url: "https://techcorp.com",
logo: "https://techcorp.com/logo.png",
description: "Leading technology company",
socialProfiles: ["https://twitter.com/techcorp", "https://linkedin.com/company/techcorp"],
contactInfo: {
telephone: "+1-555-0123",
email: "info@techcorp.com",
},
},
};
const result = await generator.generateSchema(organizationPost, params, options);
expect(result["@type"]).toBe("Organization");
expect(result.name).toBe("TechCorp");
expect(result.description).toContain("Leading technology company");
expect(result.url).toBe("https://techcorp.com");
expect(result.logo).toBeDefined();
expect(result.logo["@type"]).toBe("ImageObject");
expect(result.logo.url).toBe("https://techcorp.com/logo.png");
expect(result.sameAs).toBeDefined();
expect(Array.isArray(result.sameAs)).toBe(true);
expect(result.sameAs).toContain("https://twitter.com/techcorp");
expect(result.contactPoint).toBeDefined();
expect(result.contactPoint.telephone).toBe("+1-555-0123");
expect(result.contactPoint.email).toBe("info@techcorp.com");
});
});
describe("Website Schema Generation", () => {
const websitePost = {
id: 6,
title: { rendered: "TechCorp Homepage" },
content: { rendered: "<p>Welcome to TechCorp, your technology partner.</p>" },
excerpt: { rendered: "TechCorp homepage with company information and services." },
link: "https://techcorp.com",
status: "publish",
type: "page",
};
it("should generate Website schema markup", async () => {
const params = {
postId: 6,
schemaType: "Website",
site: "test",
};
const options = {
siteConfig: {
name: "TechCorp Website",
url: "https://techcorp.com",
description: "Technology solutions and services",
},
};
const result = await generator.generateSchema(websitePost, params, options);
expect(result["@type"]).toBe("Website");
expect(result.name).toBe("TechCorp Website");
expect(result.url).toBe("https://techcorp.com");
expect(result.potentialAction).toBeDefined();
expect(result.potentialAction["@type"]).toBe("SearchAction");
expect(result.potentialAction.target).toBeDefined();
expect(result.potentialAction["query-input"]).toBe("required name=search_term_string");
});
});
describe("BreadcrumbList Schema Generation", () => {
const breadcrumbPost = {
id: 7,
title: { rendered: "WordPress SEO Tips" },
content: { rendered: "<p>Advanced WordPress SEO tips and techniques.</p>" },
link: "https://example.com/blog/wordpress-seo-tips",
status: "publish",
type: "post",
};
it("should generate BreadcrumbList schema markup", async () => {
const params = {
postId: 7,
schemaType: "BreadcrumbList",
site: "test",
};
const result = await generator.generateSchema(breadcrumbPost, params);
expect(result["@type"]).toBe("BreadcrumbList");
expect(result.itemListElement).toBeDefined();
expect(Array.isArray(result.itemListElement)).toBe(true);
expect(result.itemListElement.length).toBeGreaterThan(0);
const firstItem = result.itemListElement[0];
expect(firstItem["@type"]).toBe("ListItem");
expect(firstItem.position).toBe(1);
expect(firstItem.name).toBe("Home");
expect(firstItem.item).toBeDefined();
const lastItem = result.itemListElement[result.itemListElement.length - 1];
expect(lastItem.name).toBe("WordPress SEO Tips");
});
});
describe("Schema Validation", () => {
it("should validate valid Article schema", () => {
const validSchema = {
"@context": "https://schema.org",
"@type": "Article",
headline: "Test Article",
author: {
"@type": "Person",
name: "Test Author",
},
};
const result = generator.validateSchema(validSchema);
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("should detect missing @context", () => {
const invalidSchema = {
"@type": "Article",
headline: "Test Article",
};
const result = generator.validateSchema(invalidSchema);
expect(result.valid).toBe(false);
expect(result.errors).toContain("Missing @context");
});
it("should detect missing @type", () => {
const invalidSchema = {
"@context": "https://schema.org",
headline: "Test Article",
};
const result = generator.validateSchema(invalidSchema);
expect(result.valid).toBe(false);
expect(result.errors).toContain("Missing @type");
});
it("should detect missing required Article properties", () => {
const invalidSchema = {
"@context": "https://schema.org",
"@type": "Article",
};
const result = generator.validateSchema(invalidSchema);
expect(result.valid).toBe(false);
expect(result.errors).toContain("Article schema missing required headline property");
});
it("should detect missing required Product properties", () => {
const invalidSchema = {
"@context": "https://schema.org",
"@type": "Product",
};
const result = generator.validateSchema(invalidSchema);
expect(result.valid).toBe(false);
expect(result.errors).toContain("Product schema missing required name property");
});
});
describe("Error Handling", () => {
const samplePost = {
id: 8,
title: { rendered: "Test Post" },
content: { rendered: "<p>Test content</p>" },
status: "publish",
};
it("should throw error for missing schema type", async () => {
const params = {
postId: 8,
site: "test",
};
await expect(generator.generateSchema(samplePost, params)).rejects.toThrow("Schema type is required");
});
it("should throw error for unsupported schema type", async () => {
const params = {
postId: 8,
schemaType: "UnsupportedType",
site: "test",
};
await expect(generator.generateSchema(samplePost, params)).rejects.toThrow(
"Unsupported schema type: UnsupportedType",
);
});
});
describe("Content Extraction", () => {
it("should extract images from HTML content", async () => {
const postWithImages = {
id: 9,
title: { rendered: "Image Gallery Post" },
content: {
rendered: `
<p>Check out these images:</p>
<img src="https://example.com/image1.jpg" alt="Image 1">
<img src="https://example.com/image2.png" alt="Image 2">
<img src="https://example.com/image3.gif" alt="Image 3">
`,
},
status: "publish",
};
const params = {
postId: 9,
schemaType: "Article",
site: "test",
};
const result = await generator.generateSchema(postWithImages, params);
expect(result.image).toBeDefined();
expect(Array.isArray(result.image)).toBe(true);
expect(result.image).toHaveLength(3);
expect(result.image).toContain("https://example.com/image1.jpg");
expect(result.image).toContain("https://example.com/image2.png");
expect(result.image).toContain("https://example.com/image3.gif");
});
it("should count words correctly", async () => {
const longContentPost = {
id: 10,
title: { rendered: "Long Article" },
content: {
rendered: `
<h1>Long Article</h1>
<p>This is a paragraph with exactly ten words in it.</p>
<p>This is another paragraph with more words than the previous one.</p>
`,
},
status: "publish",
};
const params = {
postId: 10,
schemaType: "Article",
site: "test",
};
const result = await generator.generateSchema(longContentPost, params);
expect(result.wordCount).toBeDefined();
expect(typeof result.wordCount).toBe("number");
expect(result.wordCount).toBeGreaterThan(0);
});
});
});