mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
346 lines
10 KiB
TypeScript
346 lines
10 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
|
|
/**
|
|
* Test the task parsing logic by reimplementing the parsing functions
|
|
* These mirror the logic in auto-mode-service.ts parseTasksFromSpec and parseTaskLine
|
|
*/
|
|
|
|
interface ParsedTask {
|
|
id: string;
|
|
description: string;
|
|
filePath?: string;
|
|
phase?: string;
|
|
status: 'pending' | 'in_progress' | 'completed';
|
|
}
|
|
|
|
function parseTaskLine(line: string, currentPhase?: string): ParsedTask | null {
|
|
// Match pattern: - [ ] T###: Description | File: path
|
|
const taskMatch = line.match(/- \[ \] (T\d{3}):\s*([^|]+)(?:\|\s*File:\s*(.+))?$/);
|
|
if (!taskMatch) {
|
|
// Try simpler pattern without file
|
|
const simpleMatch = line.match(/- \[ \] (T\d{3}):\s*(.+)$/);
|
|
if (simpleMatch) {
|
|
return {
|
|
id: simpleMatch[1],
|
|
description: simpleMatch[2].trim(),
|
|
phase: currentPhase,
|
|
status: 'pending',
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: taskMatch[1],
|
|
description: taskMatch[2].trim(),
|
|
filePath: taskMatch[3]?.trim(),
|
|
phase: currentPhase,
|
|
status: 'pending',
|
|
};
|
|
}
|
|
|
|
function parseTasksFromSpec(specContent: string): ParsedTask[] {
|
|
const tasks: ParsedTask[] = [];
|
|
|
|
// Extract content within ```tasks ... ``` block
|
|
const tasksBlockMatch = specContent.match(/```tasks\s*([\s\S]*?)```/);
|
|
if (!tasksBlockMatch) {
|
|
// Try fallback: look for task lines anywhere in content
|
|
const taskLines = specContent.match(/- \[ \] T\d{3}:.*$/gm);
|
|
if (!taskLines) {
|
|
return tasks;
|
|
}
|
|
// Parse fallback task lines
|
|
let currentPhase: string | undefined;
|
|
for (const line of taskLines) {
|
|
const parsed = parseTaskLine(line, currentPhase);
|
|
if (parsed) {
|
|
tasks.push(parsed);
|
|
}
|
|
}
|
|
return tasks;
|
|
}
|
|
|
|
const tasksContent = tasksBlockMatch[1];
|
|
const lines = tasksContent.split('\n');
|
|
|
|
let currentPhase: string | undefined;
|
|
|
|
for (const line of lines) {
|
|
const trimmedLine = line.trim();
|
|
|
|
// Check for phase header (e.g., "## Phase 1: Foundation")
|
|
const phaseMatch = trimmedLine.match(/^##\s*(.+)$/);
|
|
if (phaseMatch) {
|
|
currentPhase = phaseMatch[1].trim();
|
|
continue;
|
|
}
|
|
|
|
// Check for task line
|
|
if (trimmedLine.startsWith('- [ ]')) {
|
|
const parsed = parseTaskLine(trimmedLine, currentPhase);
|
|
if (parsed) {
|
|
tasks.push(parsed);
|
|
}
|
|
}
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
describe("Task Parsing", () => {
|
|
describe("parseTaskLine", () => {
|
|
it("should parse task with file path", () => {
|
|
const line = "- [ ] T001: Create user model | File: src/models/user.ts";
|
|
const result = parseTaskLine(line);
|
|
expect(result).toEqual({
|
|
id: "T001",
|
|
description: "Create user model",
|
|
filePath: "src/models/user.ts",
|
|
phase: undefined,
|
|
status: "pending",
|
|
});
|
|
});
|
|
|
|
it("should parse task without file path", () => {
|
|
const line = "- [ ] T002: Setup database connection";
|
|
const result = parseTaskLine(line);
|
|
expect(result).toEqual({
|
|
id: "T002",
|
|
description: "Setup database connection",
|
|
phase: undefined,
|
|
status: "pending",
|
|
});
|
|
});
|
|
|
|
it("should include phase when provided", () => {
|
|
const line = "- [ ] T003: Write tests | File: tests/user.test.ts";
|
|
const result = parseTaskLine(line, "Phase 1: Foundation");
|
|
expect(result?.phase).toBe("Phase 1: Foundation");
|
|
});
|
|
|
|
it("should return null for invalid line", () => {
|
|
expect(parseTaskLine("- [ ] Invalid format")).toBeNull();
|
|
expect(parseTaskLine("Not a task line")).toBeNull();
|
|
expect(parseTaskLine("")).toBeNull();
|
|
});
|
|
|
|
it("should handle multi-word descriptions", () => {
|
|
const line = "- [ ] T004: Implement user authentication with JWT tokens | File: src/auth.ts";
|
|
const result = parseTaskLine(line);
|
|
expect(result?.description).toBe("Implement user authentication with JWT tokens");
|
|
});
|
|
|
|
it("should trim whitespace from description and file path", () => {
|
|
const line = "- [ ] T005: Create API endpoint | File: src/routes/api.ts ";
|
|
const result = parseTaskLine(line);
|
|
expect(result?.description).toBe("Create API endpoint");
|
|
expect(result?.filePath).toBe("src/routes/api.ts");
|
|
});
|
|
});
|
|
|
|
describe("parseTasksFromSpec", () => {
|
|
it("should parse tasks from a tasks code block", () => {
|
|
const specContent = `
|
|
## Specification
|
|
|
|
Some description here.
|
|
|
|
\`\`\`tasks
|
|
- [ ] T001: Create user model | File: src/models/user.ts
|
|
- [ ] T002: Add API endpoint | File: src/routes/users.ts
|
|
- [ ] T003: Write unit tests | File: tests/user.test.ts
|
|
\`\`\`
|
|
|
|
## Notes
|
|
Some notes here.
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toHaveLength(3);
|
|
expect(tasks[0].id).toBe("T001");
|
|
expect(tasks[1].id).toBe("T002");
|
|
expect(tasks[2].id).toBe("T003");
|
|
});
|
|
|
|
it("should parse tasks with phases", () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
## Phase 1: Foundation
|
|
- [ ] T001: Initialize project | File: package.json
|
|
- [ ] T002: Configure TypeScript | File: tsconfig.json
|
|
|
|
## Phase 2: Implementation
|
|
- [ ] T003: Create main module | File: src/index.ts
|
|
- [ ] T004: Add utility functions | File: src/utils.ts
|
|
|
|
## Phase 3: Testing
|
|
- [ ] T005: Write tests | File: tests/index.test.ts
|
|
\`\`\`
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toHaveLength(5);
|
|
expect(tasks[0].phase).toBe("Phase 1: Foundation");
|
|
expect(tasks[1].phase).toBe("Phase 1: Foundation");
|
|
expect(tasks[2].phase).toBe("Phase 2: Implementation");
|
|
expect(tasks[3].phase).toBe("Phase 2: Implementation");
|
|
expect(tasks[4].phase).toBe("Phase 3: Testing");
|
|
});
|
|
|
|
it("should return empty array for content without tasks", () => {
|
|
const specContent = "Just some text without any tasks";
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toEqual([]);
|
|
});
|
|
|
|
it("should fallback to finding task lines outside code block", () => {
|
|
const specContent = `
|
|
## Implementation Plan
|
|
|
|
- [ ] T001: First task | File: src/first.ts
|
|
- [ ] T002: Second task | File: src/second.ts
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toHaveLength(2);
|
|
expect(tasks[0].id).toBe("T001");
|
|
expect(tasks[1].id).toBe("T002");
|
|
});
|
|
|
|
it("should handle empty tasks block", () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
\`\`\`
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toEqual([]);
|
|
});
|
|
|
|
it("should handle mixed valid and invalid lines", () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
- [ ] T001: Valid task | File: src/valid.ts
|
|
- Invalid line
|
|
Some other text
|
|
- [ ] T002: Another valid task
|
|
\`\`\`
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toHaveLength(2);
|
|
});
|
|
|
|
it("should preserve task order", () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
- [ ] T003: Third
|
|
- [ ] T001: First
|
|
- [ ] T002: Second
|
|
\`\`\`
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks[0].id).toBe("T003");
|
|
expect(tasks[1].id).toBe("T001");
|
|
expect(tasks[2].id).toBe("T002");
|
|
});
|
|
|
|
it("should handle task IDs with different numbers", () => {
|
|
const specContent = `
|
|
\`\`\`tasks
|
|
- [ ] T001: First
|
|
- [ ] T010: Tenth
|
|
- [ ] T100: Hundredth
|
|
\`\`\`
|
|
`;
|
|
const tasks = parseTasksFromSpec(specContent);
|
|
expect(tasks).toHaveLength(3);
|
|
expect(tasks[0].id).toBe("T001");
|
|
expect(tasks[1].id).toBe("T010");
|
|
expect(tasks[2].id).toBe("T100");
|
|
});
|
|
});
|
|
|
|
describe("spec content generation patterns", () => {
|
|
it("should match the expected lite mode output format", () => {
|
|
const liteModeOutput = `
|
|
1. **Goal**: Implement user registration
|
|
2. **Approach**: Create form component, add validation, connect to API
|
|
3. **Files to Touch**: src/components/Register.tsx, src/api/auth.ts
|
|
4. **Tasks**:
|
|
1. Create registration form
|
|
2. Add form validation
|
|
3. Connect to backend API
|
|
5. **Risks**: Form state management complexity
|
|
|
|
[PLAN_GENERATED] Planning outline complete.
|
|
`;
|
|
expect(liteModeOutput).toContain("[PLAN_GENERATED]");
|
|
expect(liteModeOutput).toContain("Goal");
|
|
expect(liteModeOutput).toContain("Approach");
|
|
});
|
|
|
|
it("should match the expected spec mode output format", () => {
|
|
const specModeOutput = `
|
|
1. **Problem**: Users cannot register for accounts
|
|
|
|
2. **Solution**: Implement registration form with email/password validation
|
|
|
|
3. **Acceptance Criteria**:
|
|
- GIVEN a new user, WHEN they fill in valid details, THEN account is created
|
|
|
|
4. **Files to Modify**:
|
|
| File | Purpose | Action |
|
|
|------|---------|--------|
|
|
| src/Register.tsx | Registration form | create |
|
|
|
|
5. **Implementation Tasks**:
|
|
\`\`\`tasks
|
|
- [ ] T001: Create registration component | File: src/Register.tsx
|
|
- [ ] T002: Add form validation | File: src/Register.tsx
|
|
\`\`\`
|
|
|
|
6. **Verification**: Manual testing of registration flow
|
|
|
|
[SPEC_GENERATED] Please review the specification above.
|
|
`;
|
|
expect(specModeOutput).toContain("[SPEC_GENERATED]");
|
|
expect(specModeOutput).toContain("```tasks");
|
|
expect(specModeOutput).toContain("T001");
|
|
});
|
|
|
|
it("should match the expected full mode output format", () => {
|
|
const fullModeOutput = `
|
|
1. **Problem Statement**: Users need ability to create accounts
|
|
|
|
2. **User Story**: As a new user, I want to register, so that I can access the app
|
|
|
|
3. **Acceptance Criteria**:
|
|
- **Happy Path**: GIVEN valid email, WHEN registering, THEN account created
|
|
- **Edge Cases**: GIVEN existing email, WHEN registering, THEN error shown
|
|
|
|
4. **Technical Context**:
|
|
| Aspect | Value |
|
|
|--------|-------|
|
|
| Affected Files | src/Register.tsx |
|
|
|
|
5. **Non-Goals**: Social login, password recovery
|
|
|
|
6. **Implementation Tasks**:
|
|
\`\`\`tasks
|
|
## Phase 1: Foundation
|
|
- [ ] T001: Setup component structure | File: src/Register.tsx
|
|
|
|
## Phase 2: Core Implementation
|
|
- [ ] T002: Add form logic | File: src/Register.tsx
|
|
|
|
## Phase 3: Integration & Testing
|
|
- [ ] T003: Connect to API | File: src/api/auth.ts
|
|
\`\`\`
|
|
|
|
[SPEC_GENERATED] Please review the comprehensive specification above.
|
|
`;
|
|
expect(fullModeOutput).toContain("Phase 1");
|
|
expect(fullModeOutput).toContain("Phase 2");
|
|
expect(fullModeOutput).toContain("Phase 3");
|
|
expect(fullModeOutput).toContain("[SPEC_GENERATED]");
|
|
});
|
|
});
|
|
});
|