fix: support dash prefix in parseMarkdownChecklist for todo lists (#8055)

- Updated regex pattern to support optional dash prefix (e.g., "- [ ] Task")
- Added comprehensive test coverage for both formats
- Fixes issue where todo lists with dash prefixes were not being parsed correctly

Fixes #8054

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
roomote[bot] 2025-09-19 22:45:46 -04:00 • committed by GitHub
parent b75ef1dd83
commit f7a6589719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 245 additions and 1 deletions

View file

@ -0,0 +1,243 @@
import { describe, it, expect, beforeEach, vi } from "vitest"
import { parseMarkdownChecklist } from "../updateTodoListTool"
import { TodoItem } from "@roo-code/types"
describe("parseMarkdownChecklist", () => {
describe("standard checkbox format (without dash prefix)", () => {
it("should parse pending tasks", () => {
const md = `[ ] Task 1
[ ] Task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Task 1")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Task 2")
expect(result[1].status).toBe("pending")
})
it("should parse completed tasks with lowercase x", () => {
const md = `[x] Completed task 1
[x] Completed task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Completed task 1")
expect(result[0].status).toBe("completed")
expect(result[1].content).toBe("Completed task 2")
expect(result[1].status).toBe("completed")
})
it("should parse completed tasks with uppercase X", () => {
const md = `[X] Completed task 1
[X] Completed task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Completed task 1")
expect(result[0].status).toBe("completed")
expect(result[1].content).toBe("Completed task 2")
expect(result[1].status).toBe("completed")
})
it("should parse in-progress tasks with dash", () => {
const md = `[-] In progress task 1
[-] In progress task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("In progress task 1")
expect(result[0].status).toBe("in_progress")
expect(result[1].content).toBe("In progress task 2")
expect(result[1].status).toBe("in_progress")
})
it("should parse in-progress tasks with tilde", () => {
const md = `[~] In progress task 1
[~] In progress task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("In progress task 1")
expect(result[0].status).toBe("in_progress")
expect(result[1].content).toBe("In progress task 2")
expect(result[1].status).toBe("in_progress")
})
})
describe("dash-prefixed checkbox format", () => {
it("should parse pending tasks with dash prefix", () => {
const md = `- [ ] Task 1
- [ ] Task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Task 1")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Task 2")
expect(result[1].status).toBe("pending")
})
it("should parse completed tasks with dash prefix and lowercase x", () => {
const md = `- [x] Completed task 1
- [x] Completed task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Completed task 1")
expect(result[0].status).toBe("completed")
expect(result[1].content).toBe("Completed task 2")
expect(result[1].status).toBe("completed")
})
it("should parse completed tasks with dash prefix and uppercase X", () => {
const md = `- [X] Completed task 1
- [X] Completed task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Completed task 1")
expect(result[0].status).toBe("completed")
expect(result[1].content).toBe("Completed task 2")
expect(result[1].status).toBe("completed")
})
it("should parse in-progress tasks with dash prefix and dash marker", () => {
const md = `- [-] In progress task 1
- [-] In progress task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("In progress task 1")
expect(result[0].status).toBe("in_progress")
expect(result[1].content).toBe("In progress task 2")
expect(result[1].status).toBe("in_progress")
})
it("should parse in-progress tasks with dash prefix and tilde marker", () => {
const md = `- [~] In progress task 1
- [~] In progress task 2`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("In progress task 1")
expect(result[0].status).toBe("in_progress")
expect(result[1].content).toBe("In progress task 2")
expect(result[1].status).toBe("in_progress")
})
})
describe("mixed formats", () => {
it("should parse mixed formats correctly", () => {
const md = `[ ] Task without dash
- [ ] Task with dash
[x] Completed without dash
- [X] Completed with dash
[-] In progress without dash
- [~] In progress with dash`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(6)
expect(result[0].content).toBe("Task without dash")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Task with dash")
expect(result[1].status).toBe("pending")
expect(result[2].content).toBe("Completed without dash")
expect(result[2].status).toBe("completed")
expect(result[3].content).toBe("Completed with dash")
expect(result[3].status).toBe("completed")
expect(result[4].content).toBe("In progress without dash")
expect(result[4].status).toBe("in_progress")
expect(result[5].content).toBe("In progress with dash")
expect(result[5].status).toBe("in_progress")
})
})
describe("edge cases", () => {
it("should handle empty strings", () => {
const result = parseMarkdownChecklist("")
expect(result).toEqual([])
})
it("should handle non-string input", () => {
const result = parseMarkdownChecklist(null as any)
expect(result).toEqual([])
})
it("should handle undefined input", () => {
const result = parseMarkdownChecklist(undefined as any)
expect(result).toEqual([])
})
it("should ignore non-checklist lines", () => {
const md = `This is not a checklist
[ ] Valid task
Just some text
- Not a checklist item
- [x] Valid completed task
[not valid] Invalid format`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(2)
expect(result[0].content).toBe("Valid task")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Valid completed task")
expect(result[1].status).toBe("completed")
})
it("should handle extra spaces", () => {
const md = ` [ ] Task with spaces
- [ ] Task with dash and spaces
[x] Completed with spaces
- [X] Completed with dash and spaces`
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(4)
expect(result[0].content).toBe("Task with spaces")
expect(result[1].content).toBe("Task with dash and spaces")
expect(result[2].content).toBe("Completed with spaces")
expect(result[3].content).toBe("Completed with dash and spaces")
})
it("should handle Windows line endings", () => {
const md = "[ ] Task 1\r\n- [x] Task 2\r\n[-] Task 3"
const result = parseMarkdownChecklist(md)
expect(result).toHaveLength(3)
expect(result[0].content).toBe("Task 1")
expect(result[0].status).toBe("pending")
expect(result[1].content).toBe("Task 2")
expect(result[1].status).toBe("completed")
expect(result[2].content).toBe("Task 3")
expect(result[2].status).toBe("in_progress")
})
})
describe("ID generation", () => {
it("should generate consistent IDs for the same content and status", () => {
const md1 = `[ ] Task 1
[x] Task 2`
const md2 = `[ ] Task 1
[x] Task 2`
const result1 = parseMarkdownChecklist(md1)
const result2 = parseMarkdownChecklist(md2)
expect(result1[0].id).toBe(result2[0].id)
expect(result1[1].id).toBe(result2[1].id)
})
it("should generate different IDs for different content", () => {
const md = `[ ] Task 1
[ ] Task 2`
const result = parseMarkdownChecklist(md)
expect(result[0].id).not.toBe(result[1].id)
})
it("should generate different IDs for same content but different status", () => {
const md = `[ ] Task 1
[x] Task 1`
const result = parseMarkdownChecklist(md)
expect(result[0].id).not.toBe(result[1].id)
})
it("should generate same IDs regardless of dash prefix", () => {
const md1 = `[ ] Task 1`
const md2 = `- [ ] Task 1`
const result1 = parseMarkdownChecklist(md1)
const result2 = parseMarkdownChecklist(md2)
expect(result1[0].id).toBe(result2[0].id)
})
})
})

View file

@ -108,7 +108,8 @@ export function parseMarkdownChecklist(md: string): TodoItem[] {
.filter(Boolean)
const todos: TodoItem[] = []
for (const line of lines) {
const match = line.match(/^\[\s*([ xX\-~])\s*\]\s+(.+)$/)
// Support both "[ ] Task" and "- [ ] Task" formats
const match = line.match(/^(?:-\s*)?\[\s*([ xX\-~])\s*\]\s+(.+)$/)
if (!match) continue
let status: TodoStatus = "pending"
if (match[1] === "x" || match[1] === "X") status = "completed"