- lib/idea-code.ts: pure formatIdeaCode helper (client-safe, no prisma) - lib/idea-code-server.ts: atomic nextIdeaCode via Prisma row-lock, accepts optional TransactionClient for combining with idea.create - lib/idea-plan-parser.ts: parsePlanMd extracts ---yaml---/body, runs yaml.parse + ideaPlanMdFrontmatterSchema, returns line-info on failure; CRLF-tolerant - adds yaml@^2.8.4 dependency - 8 unit tests (parser happy/missing/yaml-error/zod-error/empty-stories/CRLF; formatIdeaCode pad-3 + overflow) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
103 lines
2.1 KiB
TypeScript
103 lines
2.1 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
|
|
import { parsePlanMd } from '@/lib/idea-plan-parser'
|
|
|
|
const VALID = `---
|
|
pbi:
|
|
title: Test PBI
|
|
priority: 2
|
|
stories:
|
|
- title: Eerste flow
|
|
priority: 2
|
|
tasks:
|
|
- title: Setup
|
|
priority: 2
|
|
implementation_plan: |
|
|
1. Doe X
|
|
2. Doe Y
|
|
---
|
|
|
|
# Overwegingen
|
|
|
|
Dit is de body, niet geparsed.
|
|
`
|
|
|
|
describe('parsePlanMd', () => {
|
|
it('parses a valid plan', () => {
|
|
const r = parsePlanMd(VALID)
|
|
expect(r.ok).toBe(true)
|
|
if (r.ok) {
|
|
expect(r.plan.pbi.title).toBe('Test PBI')
|
|
expect(r.plan.stories).toHaveLength(1)
|
|
expect(r.plan.stories[0].tasks).toHaveLength(1)
|
|
expect(r.plan.stories[0].tasks[0].implementation_plan).toContain('Doe X')
|
|
expect(r.body).toContain('# Overwegingen')
|
|
}
|
|
})
|
|
|
|
it('rejects when frontmatter is missing', () => {
|
|
const r = parsePlanMd('# Just markdown\n\nNo frontmatter here.')
|
|
expect(r.ok).toBe(false)
|
|
if (!r.ok) {
|
|
expect(r.errors[0].line).toBe(1)
|
|
expect(r.errors[0].message).toMatch(/frontmatter/i)
|
|
}
|
|
})
|
|
|
|
it('reports yaml syntax error with line info', () => {
|
|
const broken = `---
|
|
pbi:
|
|
title: Test
|
|
priority: [unclosed
|
|
stories:
|
|
- foo
|
|
---
|
|
|
|
body
|
|
`
|
|
const r = parsePlanMd(broken)
|
|
expect(r.ok).toBe(false)
|
|
if (!r.ok) {
|
|
expect(r.errors[0].message.length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
it('reports schema-validation error when pbi-section missing', () => {
|
|
const noPbi = `---
|
|
stories:
|
|
- title: x
|
|
priority: 2
|
|
tasks:
|
|
- title: y
|
|
priority: 2
|
|
---
|
|
|
|
body
|
|
`
|
|
const r = parsePlanMd(noPbi)
|
|
expect(r.ok).toBe(false)
|
|
if (!r.ok) {
|
|
expect(r.errors.some((e) => e.message.includes('pbi'))).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('rejects empty stories array', () => {
|
|
const noStories = `---
|
|
pbi:
|
|
title: x
|
|
priority: 2
|
|
stories: []
|
|
---
|
|
|
|
body
|
|
`
|
|
const r = parsePlanMd(noStories)
|
|
expect(r.ok).toBe(false)
|
|
})
|
|
|
|
it('handles CRLF line endings', () => {
|
|
const crlf = VALID.replace(/\n/g, '\r\n')
|
|
const r = parsePlanMd(crlf)
|
|
expect(r.ok).toBe(true)
|
|
})
|
|
})
|