Scrum4Me/__tests__/lib/idea-plan-parser.test.ts
Janpeter Visser 79005dc777
Sprint: regril (#170)
* ST-cmowjelb1: Parser: bestand-relatieve regel + hint-detectie in YAMLParseError-tak

- Voeg `hint?: string` toe aan PlanParseError type
- Bereken bestand-relatief regelnummer (yamlLine + 1 voor de openings-`---`)
- Detecteer markdown-patronen (numbered/bullet lijst) op de offending regel
- Zet Nederlandstalige hint bij markdown-match
- Render hint als "Tip: …" onder het foutbericht in IdeaMdEditor

* ST-cmowjeq3q: UI: render hint apart onder error-message in IdeaMdEditor

Vervang <span block mt-0.5 text-status-blocked/80> door <div mt-1 text-foreground/80>
voor de Tip-hint per plan-spec (MD3-token, geen status-kleur).

* ST-cmowjewfg: Test: parser geeft hint bij markdown-in-frontmatter

Voeg twee Vitest-cases toe:
- hints when markdown sneaks into frontmatter: fixture met [unclosed op
  een genummerde markdown-regel triggert YAMLParseError op die regel
  (plain lijst zonder unclosed flow parset als geldig YAML)
- omits hint for non-markdown yaml errors: unclosed bracket zonder
  markdown-patroon geeft geen hint
2026-05-08 13:22:10 +02:00

138 lines
2.8 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('hints when markdown sneaks into frontmatter', () => {
// "1. **...**: [unclosed" triggers a YAMLParseError at the markdown line
// (plain-list-with-bold parses as valid YAML without an unclosed flow)
const broken = `---
pbi:
title: Test
priority: 2
stories:
1. **Toggle zichtbaar in productie**: [unclosed
---
body
`
const r = parsePlanMd(broken)
expect(r.ok).toBe(false)
if (!r.ok) {
expect(r.errors[0].hint).toMatch(/markdown/i)
expect(r.errors[0].line).toBeGreaterThan(1)
}
})
it('omits hint for non-markdown yaml errors', () => {
const broken = `---
pbi:
title: Test
priority: [unclosed
stories:
- foo
---
`
const r = parsePlanMd(broken)
expect(r.ok).toBe(false)
if (!r.ok) expect(r.errors[0].hint).toBeUndefined()
})
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)
})
})