import { describe, it, expect } from 'vitest'; import { parseTasksFile, writeTasksFile } from '../tasks-file.js'; import type { MissionTask } from '../types.js'; const SAMPLE_TASKS_MD = `# Tasks — MVP > Single-writer: orchestrator only. Workers read but never modify. | id | status | milestone | description | pr | notes | | ------ | ----------- | --------- | ------------------------------------------ | --- | ----- | | P0-001 | done | Phase 0 | Scaffold monorepo | #60 | #1 | | P0-002 | done | Phase 0 | @mosaic/types — migrate and extend | #65 | #2 | | P1-001 | in-progress | Phase 1 | apps/gateway scaffold | #61 | #10 | | P2-001 | not-started | Phase 2 | @mosaic/agent — Pi SDK integration | — | #19 | | P2-002 | blocked | Phase 2 | Multi-provider support | — | #20 | `; describe('parseTasksFile', () => { it('parses a valid TASKS.md into MissionTask[]', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); expect(tasks).toHaveLength(5); }); it('extracts task IDs correctly', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); expect(tasks.map((t) => t.id)).toEqual(['P0-001', 'P0-002', 'P1-001', 'P2-001', 'P2-002']); }); it('extracts statuses correctly', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); expect(tasks.map((t) => t.status)).toEqual([ 'done', 'done', 'in-progress', 'not-started', 'blocked', ]); }); it('extracts milestones correctly', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); expect(tasks[0]!.milestone).toBe('Phase 0'); expect(tasks[2]!.milestone).toBe('Phase 1'); expect(tasks[3]!.milestone).toBe('Phase 2'); }); it('extracts PR references', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); expect(tasks[0]!.pr).toBe('#60'); expect(tasks[3]!.pr).toBe('—'); }); it('returns empty array for empty content', () => { expect(parseTasksFile('')).toEqual([]); expect(parseTasksFile('# No table here')).toEqual([]); }); it('handles legacy status values', () => { const content = `| id | status | description | |----|--------|-------------| | T1 | completed | Task one | | T2 | pending | Task two | | T3 | failed | Task three | `; const tasks = parseTasksFile(content); expect(tasks[0]!.status).toBe('done'); expect(tasks[1]!.status).toBe('not-started'); expect(tasks[2]!.status).toBe('blocked'); }); }); describe('writeTasksFile', () => { it('generates valid markdown table', () => { const tasks: MissionTask[] = [ { id: 'T-001', title: 'Test task', status: 'done', dependencies: [], milestone: 'Phase 1', pr: '#42', notes: '#1', }, ]; const output = writeTasksFile(tasks); expect(output).toContain('| T-001 | done | Phase 1 | Test task | #42 | #1 |'); expect(output).toContain('# Tasks'); }); it('roundtrips parse/write', () => { const tasks = parseTasksFile(SAMPLE_TASKS_MD); const output = writeTasksFile(tasks); const reparsed = parseTasksFile(output); expect(reparsed).toHaveLength(tasks.length); for (let i = 0; i < tasks.length; i++) { expect(reparsed[i]!.id).toBe(tasks[i]!.id); expect(reparsed[i]!.status).toBe(tasks[i]!.status); expect(reparsed[i]!.title).toBe(tasks[i]!.title); } }); });