[
{"name": "no todos is just the header line, still ending CRLF", "args": [[]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\n"},
{"name": "a todo with no optional fields: empty fields for nulls, false, order 0", "args": [[{"id": "t1", "title": "Buy milk", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "every field filled: done, due, two tags joined by a space, a monthly repeat", "args": [[{"id": "t2", "title": "Pay rent", "notes": "Standing order", "done": true, "priority": "high", "due": "2026-10-01", "tags": ["home", "bills"], "recurrence": {"frequency": "monthly", "interval": 1, "anchor": "2026-10-01"}, "createdAt": "2026-09-20T08:00:00Z", "completedAt": "2026-09-28T10:00:00.123Z", "order": 3}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt2,Pay rent,Standing order,true,high,2026-10-01,home bills,monthly,1,2026-10-01,2026-09-20T08:00:00Z,2026-09-28T10:00:00.123Z,3\r\n"},
{"name": "a comma in the title makes it quoted", "args": [[{"id": "t1", "title": "Eggs, milk", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,\"Eggs, milk\",,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "double quotes are doubled inside a quoted field", "args": [[{"id": "t1", "title": "Read \"Dune\" again", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,\"Read \"\"Dune\"\" again\",,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "line breaks in notes (LF, CRLF and a lone CR) are kept inside quotes, as they are", "args": [[{"id": "t1", "title": "Buy milk", "notes": "line 1\nline 2\r\nline 3\rend", "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,\"line 1\nline 2\r\nline 3\rend\",false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "a title starting with = is written as is: no spreadsheet formula escaping", "args": [[{"id": "t1", "title": "=1+1", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,=1+1,,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "unicode, semicolons, tabs and spaces need no quotes", "args": [[{"id": "t1", "title": "Café ☕ 😀; tab\there", "notes": " padded ", "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Café ☕ 😀; tab\there, padded ,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "todos stay in the order given, not sorted by order or id", "args": [[{"id": "b", "title": "Second", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 5}, {"id": "a", "title": "First", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 1}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nb,Second,,false,none,,,,,,2026-09-28T09:30:00Z,,5\r\na,First,,false,none,,,,,,2026-09-28T09:30:00Z,,1\r\n"},
{"name": "an interval above 9 and a weekly repeat", "args": [[{"id": "t1", "title": "Buy milk", "notes": null, "done": false, "priority": "none", "due": "2026-10-05", "tags": [], "recurrence": {"frequency": "weekly", "interval": 12, "anchor": "2026-09-28"}, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 42}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,,false,none,2026-10-05,,weekly,12,2026-09-28,2026-09-28T09:30:00Z,,42\r\n"},
{"name": "empty notes are written like null notes: an empty field", "args": [[{"id": "t1", "title": "Buy milk", "notes": "", "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,,false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "a field that is only a double quote", "args": [[{"id": "t1", "title": "Buy milk", "notes": "\"", "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": 0}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,\"\"\"\",false,none,,,,,,2026-09-28T09:30:00Z,,0\r\n"},
{"name": "export does not validate: a negative order is written as it is", "args": [[{"id": "t1", "title": "Buy milk", "notes": null, "done": false, "priority": "none", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-28T09:30:00Z", "completedAt": null, "order": -1}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,Buy milk,,false,none,,,,,,2026-09-28T09:30:00Z,,-1\r\n"},
{"name": "round trip: quotes, commas, line breaks, unicode, a repeat and a done todo (the text importCsv reads back)", "args": [[{"id": "t1", "title": "Read \"Dune\", then lend it", "notes": "Chapter 1\r\nChapter 2\nsee \"notes\", p. 3", "done": false, "priority": "medium", "due": "2026-10-05", "tags": ["books", "home-office"], "recurrence": {"frequency": "weekly", "interval": 2, "anchor": "2026-09-28"}, "createdAt": "2026-09-28T09:30:00.5Z", "completedAt": null, "order": 0}, {"id": "t2", "title": "Café ☕ 😀 =1+1", "notes": null, "done": true, "priority": "high", "due": null, "tags": [], "recurrence": null, "createdAt": "2026-09-27T23:59:59Z", "completedAt": "2026-09-28T00:00:00Z", "order": 1}]], "expect": "id,title,notes,done,priority,due,tags,recurrenceFrequency,recurrenceInterval,recurrenceAnchor,createdAt,completedAt,order\r\nt1,\"Read \"\"Dune\"\", then lend it\",\"Chapter 1\r\nChapter 2\nsee \"\"notes\"\", p. 3\",false,medium,2026-10-05,books home-office,weekly,2,2026-09-28,2026-09-28T09:30:00.5Z,,0\r\nt2,Café ☕ 😀 =1+1,,true,high,,,,,,2026-09-27T23:59:59Z,2026-09-28T00:00:00Z,1\r\n"}
]