- 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>
111 lines
3.2 KiB
TypeScript
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)
|
|
})
|
|
})
|