diff --git a/apps/blog/__tests__/content-types.test.ts b/apps/blog/__tests__/content-types.test.ts index 1e5daca..d4dbc7c 100644 --- a/apps/blog/__tests__/content-types.test.ts +++ b/apps/blog/__tests__/content-types.test.ts @@ -92,11 +92,33 @@ describe('Content Types', () => { expect(() => validateFrontmatter(rest)).toThrow('title is required'); }); - it('throws for missing description', () => { + it('throws for missing description on a non-draft', () => { const { description, ...rest } = validFrontmatter; expect(() => validateFrontmatter(rest)).toThrow('description is required'); }); + it('throws for empty-string description on a non-draft', () => { + expect(() => + validateFrontmatter({ ...validFrontmatter, description: '' }) + ).toThrow('description is required'); + }); + + it('allows missing description on a draft', () => { + const { description, ...rest } = validFrontmatter; + const result = validateFrontmatter({ ...rest, draft: true }); + expect(result.draft).toBe(true); + expect(result.description).toBe(''); + }); + + it('allows empty-string description on a draft', () => { + const result = validateFrontmatter({ + ...validFrontmatter, + description: '', + draft: true, + }); + expect(result.description).toBe(''); + }); + it('throws for missing date', () => { const { date, ...rest } = validFrontmatter; expect(() => validateFrontmatter(rest)).toThrow('date is required'); diff --git a/apps/blog/types/content.ts b/apps/blog/types/content.ts index d0ac72b..c91fd73 100644 --- a/apps/blog/types/content.ts +++ b/apps/blog/types/content.ts @@ -331,8 +331,15 @@ export function validateFrontmatter(data: Record): Frontmatter if (typeof data.title !== 'string' || !data.title) { errors.push('title is required and must be a string'); } - if (typeof data.description !== 'string' || !data.description) { - errors.push('description is required and must be a string'); + const isDraft = data.draft === true; + // Drafts are work-in-progress — description may not be filled yet. For + // published essays, description is required and must be non-empty. + if (!isDraft) { + if (typeof data.description !== 'string' || !data.description) { + errors.push('description is required and must be a string'); + } + } else if (data.description !== undefined && typeof data.description !== 'string') { + errors.push('description must be a string when provided'); } if (typeof data.date !== 'string' || !data.date) { errors.push('date is required and must be a string'); @@ -371,7 +378,7 @@ export function validateFrontmatter(data: Record): Frontmatter return { title: data.title as string, - description: data.description as string, + description: (data.description as string | undefined) ?? '', date: data.date as string, type: data.type as EssayType, topics: data.topics as Topic[], @@ -451,8 +458,13 @@ export function validateSeriesFrontmatter(data: Record): Series if (typeof data.title !== 'string' || !data.title) { errors.push('title is required and must be a string'); } - if (typeof data.description !== 'string' || !data.description) { - errors.push('description is required and must be a string'); + const isSeriesDraft = data.draft === true; + if (!isSeriesDraft) { + if (typeof data.description !== 'string' || !data.description) { + errors.push('description is required and must be a string'); + } + } else if (data.description !== undefined && typeof data.description !== 'string') { + errors.push('description must be a string when provided'); } if (typeof data.date !== 'string' || !data.date) { errors.push('date is required and must be a string'); @@ -490,7 +502,7 @@ export function validateSeriesFrontmatter(data: Record): Series return { title: data.title as string, - description: data.description as string, + description: (data.description as string | undefined) ?? '', date: data.date as string, updated: data.updated as string | undefined, category: data.category as SeriesCategory,