Skip to main content
Glama
ContentAnalyzer.test.js9.97 kB
/** * Tests for SEO Content Analyzer * * @group unit * @group seo */ import { describe, it, expect, beforeEach } from "vitest"; import { ContentAnalyzer } from "../../../dist/tools/seo/analyzers/ContentAnalyzer.js"; describe("SEO Content Analyzer", () => { let analyzer; beforeEach(() => { analyzer = new ContentAnalyzer(); }); describe("Basic functionality", () => { it("should be instantiable", () => { expect(analyzer).toBeDefined(); expect(analyzer).toBeInstanceOf(ContentAnalyzer); }); it("should have analyzePost method", () => { expect(analyzer.analyzePost).toBeDefined(); expect(typeof analyzer.analyzePost).toBe("function"); }); }); describe("Content analysis", () => { const samplePost = { id: 1, title: { rendered: "Sample SEO Test Post" }, content: { rendered: ` <h1>Main Heading</h1> <p>This is a test paragraph with some content for SEO analysis. It contains multiple sentences to test readability scoring.</p> <h2>Subheading</h2> <p>Another paragraph with more content. This paragraph also contains important information about SEO optimization.</p> <img src="test.jpg" alt="Test image"> <p>A paragraph with <a href="/internal-link">internal link</a> and <a href="https://external.com">external link</a>.</p> `, }, excerpt: { rendered: "Test excerpt for SEO analysis" }, }; const analysisParams = { site: "test", postId: 1, analysisType: "full", focusKeywords: ["SEO"], }; it("should analyze post content and return results", async () => { const result = await analyzer.analyzePost(samplePost, analysisParams); expect(result).toBeDefined(); expect(result).toHaveProperty("score"); expect(result).toHaveProperty("status"); expect(result).toHaveProperty("metrics"); expect(result).toHaveProperty("recommendations"); expect(result).toHaveProperty("analyzedAt"); expect(typeof result.score).toBe("number"); expect(result.score).toBeGreaterThanOrEqual(0); expect(result.score).toBeLessThanOrEqual(100); expect(["poor", "needs-improvement", "good", "excellent"]).toContain(result.status); }); it("should calculate basic metrics correctly", async () => { const result = await analyzer.analyzePost(samplePost, analysisParams); expect(result.metrics).toHaveProperty("wordCount"); expect(result.metrics).toHaveProperty("fleschReadingEase"); expect(result.metrics).toHaveProperty("fleschKincaidGrade"); expect(result.metrics).toHaveProperty("headingCount"); expect(result.metrics).toHaveProperty("imageCount"); expect(result.metrics).toHaveProperty("internalLinkCount"); expect(result.metrics).toHaveProperty("externalLinkCount"); expect(result.metrics.wordCount).toBeGreaterThan(0); expect(result.metrics.headingCount).toBeGreaterThan(0); expect(result.metrics.imageCount).toBe(1); expect(result.metrics.internalLinkCount).toBeGreaterThanOrEqual(1); expect(result.metrics.externalLinkCount).toBeGreaterThanOrEqual(1); }); it("should generate appropriate recommendations", async () => { const result = await analyzer.analyzePost(samplePost, analysisParams); expect(Array.isArray(result.recommendations)).toBe(true); if (result.recommendations.length > 0) { const firstRecommendation = result.recommendations[0]; expect(firstRecommendation).toHaveProperty("type"); expect(firstRecommendation).toHaveProperty("priority"); expect(firstRecommendation).toHaveProperty("message"); expect(firstRecommendation).toHaveProperty("impact"); expect(firstRecommendation).toHaveProperty("autoFixAvailable"); expect(["title", "meta", "content", "structure", "keyword", "technical", "performance"]).toContain( firstRecommendation.type, ); expect(["low", "medium", "high", "critical"]).toContain(firstRecommendation.priority); expect(typeof firstRecommendation.message).toBe("string"); expect(firstRecommendation.impact).toBeGreaterThan(0); expect(firstRecommendation.impact).toBeLessThanOrEqual(100); } }); it("should analyze keywords when provided", async () => { const result = await analyzer.analyzePost(samplePost, analysisParams); expect(result.keywordAnalysis).toBeDefined(); expect(result.keywordAnalysis).toHaveProperty("primaryKeyword"); expect(result.keywordAnalysis).toHaveProperty("keywordFound"); expect(result.keywordAnalysis).toHaveProperty("occurrences"); expect(result.keywordAnalysis).toHaveProperty("density"); expect(result.keywordAnalysis).toHaveProperty("semanticKeywords"); expect(result.keywordAnalysis.primaryKeyword).toBe("SEO"); expect(typeof result.keywordAnalysis.keywordFound).toBe("boolean"); expect(typeof result.keywordAnalysis.occurrences).toBe("number"); expect(typeof result.keywordAnalysis.density).toBe("number"); expect(Array.isArray(result.keywordAnalysis.semanticKeywords)).toBe(true); }); it("should analyze content structure", async () => { const result = await analyzer.analyzePost(samplePost, analysisParams); expect(result.structure).toBeDefined(); expect(result.structure).toHaveProperty("hasH1"); expect(result.structure).toHaveProperty("h1Text"); expect(result.structure).toHaveProperty("headingHierarchy"); expect(result.structure).toHaveProperty("paragraphCount"); expect(result.structure).toHaveProperty("avgParagraphLength"); expect(result.structure.hasH1).toBe(true); expect(result.structure.h1Text).toBe("Main Heading"); expect(typeof result.structure.headingHierarchy).toBe("boolean"); expect(result.structure.paragraphCount).toBeGreaterThan(0); }); it("should handle posts without focus keywords", async () => { const paramsWithoutKeywords = { site: "test", postId: 1, analysisType: "full", }; const result = await analyzer.analyzePost(samplePost, paramsWithoutKeywords); expect(result.keywordAnalysis).toBeUndefined(); expect(result.metrics.keywordDensity).toBe(0); }); it("should handle empty or minimal content", async () => { const minimalPost = { id: 2, title: { rendered: "Short" }, content: { rendered: "<p>Very short content.</p>" }, excerpt: { rendered: "" }, }; const result = await analyzer.analyzePost(minimalPost, analysisParams); expect(result).toBeDefined(); expect(result.metrics.wordCount).toBeLessThan(10); expect(result.recommendations.length).toBeGreaterThan(0); // Should recommend longer content const contentRecommendation = result.recommendations.find( (r) => r.type === "content" && r.message.includes("too short"), ); expect(contentRecommendation).toBeDefined(); }); }); describe("Edge cases", () => { it("should handle posts with no HTML content", async () => { const plainTextPost = { id: 3, title: "Plain Title", content: "Plain text content without HTML tags. This should still be analyzed correctly.", excerpt: "", }; const params = { site: "test", postId: 3, analysisType: "full", focusKeywords: ["SEO"], }; const result = await analyzer.analyzePost(plainTextPost, params); expect(result).toBeDefined(); expect(result.metrics.wordCount).toBeGreaterThan(0); expect(result.metrics.headingCount).toBe(0); }); it("should handle posts with malformed HTML", async () => { const malformedPost = { id: 4, title: { rendered: "Malformed HTML Test" }, content: { rendered: "<h1>Unclosed heading<p>Paragraph without closing<img src='test.jpg'>" }, excerpt: { rendered: "" }, }; const params = { site: "test", postId: 4, analysisType: "full", focusKeywords: ["SEO"], }; const result = await analyzer.analyzePost(malformedPost, params); expect(result).toBeDefined(); expect(result.score).toBeGreaterThanOrEqual(0); expect(result.score).toBeLessThanOrEqual(100); }); it("should handle posts with special characters and unicode", async () => { const unicodePost = { id: 5, title: { rendered: "Unicode Test: Café & Résumé 中文" }, content: { rendered: "<p>Content with émojis 🚀 and spëcial characters: àáâãäå</p>", }, excerpt: { rendered: "" }, }; const params = { site: "test", postId: 5, analysisType: "full", focusKeywords: ["SEO"], }; const result = await analyzer.analyzePost(unicodePost, params); expect(result).toBeDefined(); expect(result.metrics.wordCount).toBeGreaterThan(0); }); }); describe("Performance", () => { it("should complete analysis within reasonable time", async () => { const largePost = { id: 6, title: { rendered: "Performance Test Post" }, content: { rendered: "<p>" + "This is a performance test paragraph. ".repeat(100) + "</p>".repeat(50), }, excerpt: { rendered: "Performance test excerpt" }, }; const params = { site: "test", postId: 6, analysisType: "full", focusKeywords: ["SEO"], }; const startTime = Date.now(); const result = await analyzer.analyzePost(largePost, params); const endTime = Date.now(); expect(result).toBeDefined(); expect(endTime - startTime).toBeLessThan(5000); // Should complete within 5 seconds }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docdyhr/mcp-wordpress'

If you have feedback or need assistance with the MCP directory API, please join our Discord server