Scrum4Me/__tests__/lib/schemas/product-doc.test.ts
Madhura68 55781e463a feat(PBI-96/T-1059): add Zod schemas, folder-mapping, slug helpers
- lib/schemas/product-doc.ts: PRODUCT_DOC_FOLDERS/STATUSES + create/update/
  toggle/frontmatter schemas (MAX_PRODUCT_DOC_CONTENT_LEN=100k)
- lib/product-doc-folder.ts: DB UPPER_SNAKE ↔ API lowercase mapper
  (spiegel van lib/task-status.ts)
- lib/product-doc-slug.ts: pure slugify + suggestSlug (dedupe-suffix) +
  ADR-sequence helpers (nextAdrPrefix, parseAdrNumber, suggestAdrSlug)
- lib/schemas/product-doc-frontmatter-defaults.ts: per-folder UI-templates
  voor "Nieuwe doc"-dialog (last_updated weggelaten — server normaliseert
  bij save, zie T-1060)
- __tests__: 37 tests groen (Zod-schemas + slug-helpers); de pre-existing
  worktree-env fail in idea-timeline-merge.test.ts blijft buiten scope

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 11:40:30 +02:00

160 lines
4.5 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
PRODUCT_DOC_FOLDERS,
PRODUCT_DOC_STATUSES,
productDocCreateSchema,
productDocFolderToggleSchema,
productDocFrontmatterSchema,
productDocSlugSchema,
productDocUpdateSchema,
} from '@/lib/schemas/product-doc'
const validProductId = 'cmohrysyj0000rd17clnjy4tc'
describe('productDocSlugSchema', () => {
it('accepteert geldige slugs', () => {
expect(productDocSlugSchema.safeParse('deploy').success).toBe(true)
expect(productDocSlugSchema.safeParse('0001-context-decision').success).toBe(true)
expect(productDocSlugSchema.safeParse('a').success).toBe(true)
})
it('weigert hoofdletters, spaties en speciale tekens', () => {
expect(productDocSlugSchema.safeParse('Deploy').success).toBe(false)
expect(productDocSlugSchema.safeParse('deploy stappen').success).toBe(false)
expect(productDocSlugSchema.safeParse('deploy/stappen').success).toBe(false)
})
it('weigert slug die met streepje begint', () => {
expect(productDocSlugSchema.safeParse('-deploy').success).toBe(false)
})
it('weigert slug > 80 tekens', () => {
expect(productDocSlugSchema.safeParse('a'.repeat(81)).success).toBe(false)
expect(productDocSlugSchema.safeParse('a'.repeat(80)).success).toBe(true)
})
})
describe('productDocFrontmatterSchema', () => {
it('accepteert minimaal valide frontmatter', () => {
const r = productDocFrontmatterSchema.safeParse({ title: 'Deploy', status: 'draft' })
expect(r.success).toBe(true)
})
it('weigert ontbrekende title of status', () => {
expect(
productDocFrontmatterSchema.safeParse({ status: 'draft' }).success,
).toBe(false)
expect(
productDocFrontmatterSchema.safeParse({ title: 'Deploy' }).success,
).toBe(false)
})
it('weigert status die niet in de enum zit', () => {
expect(
productDocFrontmatterSchema.safeParse({ title: 'D', status: 'wip' }).success,
).toBe(false)
})
it('accepteert audience als string of array', () => {
expect(
productDocFrontmatterSchema.safeParse({
title: 'D',
status: 'draft',
audience: 'maintainer',
}).success,
).toBe(true)
expect(
productDocFrontmatterSchema.safeParse({
title: 'D',
status: 'draft',
audience: ['maintainer', 'contributor'],
}).success,
).toBe(true)
})
it('weigert oversized title', () => {
expect(
productDocFrontmatterSchema.safeParse({
title: 'x'.repeat(201),
status: 'draft',
}).success,
).toBe(false)
})
})
describe('productDocCreateSchema', () => {
const base = {
product_id: validProductId,
folder: 'runbooks' as const,
slug: 'deploy',
content_md: '---\ntitle: "Deploy"\nstatus: draft\n---\n\nbody',
}
it('accepteert geldige input', () => {
expect(productDocCreateSchema.safeParse(base).success).toBe(true)
})
it('weigert ongeldige folder', () => {
expect(
productDocCreateSchema.safeParse({ ...base, folder: 'wiki' }).success,
).toBe(false)
})
it('weigert ongeldige product_id (geen cuid)', () => {
expect(
productDocCreateSchema.safeParse({ ...base, product_id: 'not-a-cuid' }).success,
).toBe(false)
})
it('weigert leeg of te lang content_md', () => {
expect(productDocCreateSchema.safeParse({ ...base, content_md: '' }).success).toBe(false)
expect(
productDocCreateSchema.safeParse({ ...base, content_md: 'x'.repeat(100_001) }).success,
).toBe(false)
})
})
describe('productDocUpdateSchema', () => {
it('accepteert valide content_md', () => {
expect(
productDocUpdateSchema.safeParse({ content_md: '---\ntitle: "x"\nstatus: draft\n---\n\nbody' })
.success,
).toBe(true)
})
it('weigert leeg content_md', () => {
expect(productDocUpdateSchema.safeParse({ content_md: '' }).success).toBe(false)
})
})
describe('productDocFolderToggleSchema', () => {
it('accepteert valide toggle-input', () => {
expect(
productDocFolderToggleSchema.safeParse({
product_id: validProductId,
folder: 'api',
enabled: false,
}).success,
).toBe(true)
})
it('weigert ontbrekende enabled-vlag', () => {
expect(
productDocFolderToggleSchema.safeParse({
product_id: validProductId,
folder: 'api',
}).success,
).toBe(false)
})
})
describe('PRODUCT_DOC_FOLDERS + STATUSES', () => {
it('bevat exact 8 folders', () => {
expect(PRODUCT_DOC_FOLDERS).toHaveLength(8)
})
it('bevat exact 4 statussen', () => {
expect(PRODUCT_DOC_STATUSES).toHaveLength(4)
})
})