Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|