* 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
138 lines
2.8 KiB
TypeScript
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)
|
|
})
|
|
})
|