UNPKG

@bernierllc/content-type-blog-post

Version:

Blog post content type with rich TipTap editor, SEO metadata, database storage, and web publishing

172 lines (157 loc) 5.54 kB
/* Copyright (c) 2025 Bernier LLC This file is licensed to the client under a limited-use license. The client may use and modify this code *only within the scope of the project it was delivered for*. Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC. */ import { validateSEOCompleteness, checkSEOLimits } from '../src/seo'; import { createSampleBlogPost } from './test-utils'; describe('seo', () => { describe('validateSEOCompleteness', () => { it('should pass validation for complete SEO', () => { const post = createSampleBlogPost(); const result = validateSEOCompleteness(post); expect(result.success).toBe(true); expect(result.error).toBeUndefined(); }); it('should fail if meta title is missing', () => { const post = createSampleBlogPost({ seo: { metaTitle: '', metaDescription: 'Description', keywords: ['test'], ogType: 'article', }, }); const result = validateSEOCompleteness(post); expect(result.success).toBe(false); expect(result.error).toContain('Meta title is required'); }); it('should fail if meta description is missing', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'Title', metaDescription: '', keywords: ['test'], ogType: 'article', }, }); const result = validateSEOCompleteness(post); expect(result.success).toBe(false); expect(result.error).toContain('Meta description is required'); }); it('should fail if keywords are missing', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'Title', metaDescription: 'Description', keywords: [], ogType: 'article', }, }); const result = validateSEOCompleteness(post); expect(result.success).toBe(false); expect(result.error).toContain('At least one keyword is required'); }); it('should report multiple validation errors', () => { const post = createSampleBlogPost({ seo: { metaTitle: '', metaDescription: '', keywords: [], ogType: 'article', }, }); const result = validateSEOCompleteness(post); expect(result.success).toBe(false); expect(result.error).toContain('Meta title is required'); expect(result.error).toContain('Meta description is required'); expect(result.error).toContain('At least one keyword is required'); }); it('should accept whitespace-only fields as invalid', () => { const post = createSampleBlogPost({ seo: { metaTitle: ' ', metaDescription: ' ', keywords: ['test'], ogType: 'article', }, }); const result = validateSEOCompleteness(post); expect(result.success).toBe(false); }); }); describe('checkSEOLimits', () => { it('should return no warnings for SEO within limits', () => { const post = createSampleBlogPost(); const warnings = checkSEOLimits(post); expect(warnings).toEqual([]); }); it('should warn if meta title exceeds 60 characters', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'A'.repeat(70), metaDescription: 'Description', keywords: ['test'], ogType: 'article', }, }); const warnings = checkSEOLimits(post); expect(warnings).toHaveLength(1); expect(warnings[0]).toContain('Meta title is 70 characters'); expect(warnings[0]).toContain('recommended: 60 or less'); }); it('should warn if meta description exceeds 160 characters', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'Title', metaDescription: 'A'.repeat(200), keywords: ['test'], ogType: 'article', }, }); const warnings = checkSEOLimits(post); expect(warnings).toHaveLength(1); expect(warnings[0]).toContain('Meta description is 200 characters'); expect(warnings[0]).toContain('recommended: 160 or less'); }); it('should warn if keywords exceed 10', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'Title', metaDescription: 'Description', keywords: Array(15).fill('keyword'), ogType: 'article', }, }); const warnings = checkSEOLimits(post); expect(warnings).toHaveLength(1); expect(warnings[0]).toContain('15 keywords provided'); expect(warnings[0]).toContain('recommended: 10 or less'); }); it('should return multiple warnings when multiple limits exceeded', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'A'.repeat(70), metaDescription: 'B'.repeat(200), keywords: Array(15).fill('keyword'), ogType: 'article', }, }); const warnings = checkSEOLimits(post); expect(warnings).toHaveLength(3); }); it('should not warn at exact limits', () => { const post = createSampleBlogPost({ seo: { metaTitle: 'A'.repeat(60), metaDescription: 'B'.repeat(160), keywords: Array(10).fill('keyword'), ogType: 'article', }, }); const warnings = checkSEOLimits(post); expect(warnings).toEqual([]); }); }); });