Scrum4Me/__tests__/lib/product-doc-slug.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

111 lines
3.2 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import {
nextAdrPrefix,
parseAdrNumber,
slugify,
suggestAdrSlug,
suggestSlug,
} from '@/lib/product-doc-slug'
describe('slugify', () => {
it('maakt simpele titels lowercase met koppeltekens', () => {
expect(slugify('Deploy stappen')).toBe('deploy-stappen')
expect(slugify('Hello, World!')).toBe('hello-world')
})
it('stript diakritieken', () => {
expect(slugify('Café écrasé')).toBe('cafe-ecrase')
expect(slugify('Ångström')).toBe('angstrom')
})
it('verwijdert leading/trailing dashes', () => {
expect(slugify(' --- hello --- ')).toBe('hello')
})
it('capt lengte op 80 tekens', () => {
const long = 'a'.repeat(100)
expect(slugify(long).length).toBe(80)
})
it('geeft lege string voor lege/whitespace-only input', () => {
expect(slugify('')).toBe('')
expect(slugify(' ')).toBe('')
expect(slugify('!@#$%')).toBe('')
})
})
describe('suggestSlug', () => {
it('returnt base-slug zonder collision', () => {
expect(suggestSlug('Deploy', [])).toBe('deploy')
})
it('voegt -2 suffix toe bij eerste collision', () => {
expect(suggestSlug('Deploy', ['deploy'])).toBe('deploy-2')
})
it('telt door bij meerdere collisions', () => {
expect(suggestSlug('Deploy', ['deploy', 'deploy-2', 'deploy-3'])).toBe('deploy-4')
})
it('geeft lege string voor lege titel', () => {
expect(suggestSlug('', ['x'])).toBe('')
})
it('respecteert max-len bij toevoegen suffix', () => {
const long80 = 'a'.repeat(80)
const result = suggestSlug(long80, [long80])
expect(result.length).toBeLessThanOrEqual(80)
expect(result.endsWith('-2')).toBe(true)
})
})
describe('nextAdrPrefix', () => {
it('geeft 0001 als er nog geen ADRs zijn', () => {
expect(nextAdrPrefix(null)).toBe('0001')
})
it('telt door op currentMax', () => {
expect(nextAdrPrefix(0)).toBe('0001')
expect(nextAdrPrefix(41)).toBe('0042')
expect(nextAdrPrefix(999)).toBe('1000')
})
it('pad altijd tot minimaal 4 cijfers', () => {
expect(nextAdrPrefix(null)).toMatch(/^\d{4}$/)
expect(nextAdrPrefix(8)).toBe('0009')
})
})
describe('parseAdrNumber', () => {
it('parseert geldig NNNN-prefix', () => {
expect(parseAdrNumber('0001-context')).toBe(1)
expect(parseAdrNumber('0042-some-slug')).toBe(42)
})
it('returns null voor slugs zonder geldig prefix', () => {
expect(parseAdrNumber('context')).toBeNull()
expect(parseAdrNumber('abc-context')).toBeNull()
expect(parseAdrNumber('1-context')).toBeNull()
expect(parseAdrNumber('12345-context')).toBeNull() // 5 cijfers
})
})
describe('suggestAdrSlug', () => {
it('bouwt NNNN-{slug} format', () => {
expect(suggestAdrSlug('Use base-ui not Radix', null)).toBe('0001-use-base-ui-not-radix')
expect(suggestAdrSlug('Use base-ui not Radix', 41)).toBe('0042-use-base-ui-not-radix')
})
it('geeft alleen prefix bij lege titel', () => {
expect(suggestAdrSlug('', null)).toBe('0001')
expect(suggestAdrSlug(' ', 5)).toBe('0006')
})
it('respecteert max-len van 80 tekens', () => {
const longTitle = 'x'.repeat(100)
const slug = suggestAdrSlug(longTitle, null)
expect(slug.length).toBeLessThanOrEqual(80)
expect(slug.startsWith('0001-')).toBe(true)
})
})