AtlatestRepositoryfolio
1
(import (sigil test)2
(sigil struct)3
(sigil string)4
(sigil fs)5
(sigil path)6
(folio store)7
(folio task)8
(folio id)9
(folio project)10
(folio frontmatter))12
;; ============================================================13
;; Helpers14
;; ============================================================16
(define sample-project17
(string-append18
"---\n"19
"status: active\n"20
"goal: Add contact management\n"21
"area: development\n"22
"tags:\n- sigil\n- fastmail\n"23
"last-updated: 2026-03-13\n"24
"---\n"25
"\n"26
"# Bureau v2\n"27
"\n"28
"## Tasks\n"29
"\n"30
"- [ ] Add contact lookup tool {id: t-a3f2, priority: high, due: 2026-03-20}\n"31
"- [ ] Improve search result ranking {id: t-b1c4, priority: medium}\n"32
"- [x] Fix reply identity selection {id: t-c2d5, completed: 2026-03-13}\n"33
"\n"34
"## Log\n"35
"\n"36
"### 2026-03-13\n"37
"\n"38
"Fixed identity selection for replies.\n"39
"\n"40
"### 2026-03-12\n"41
"\n"42
"Added batch trash tool.\n"))44
;; ============================================================45
;; read-project46
;; ============================================================48
(test-group "read-project"49
(test "parses project file"50
(call-with-temp-directory51
(lambda (dir)52
(let ((file (path-join dir "bureau-v2.md")))53
(write-file-string file sample-project)54
(let ((proj (read-project file)))55
(assert-equal "bureau-v2" (folio-project-name proj))56
(assert-equal "active" (project-status proj))57
(assert-equal "Add contact management" (project-goal proj))58
(assert-equal "development" (project-area proj)))))))60
(test "parses tasks"61
(call-with-temp-directory62
(lambda (dir)63
(let ((file (path-join dir "test.md")))64
(write-file-string file sample-project)65
(let* ((proj (read-project file))66
(tasks (folio-project-tasks proj)))67
(assert-equal 3 (length tasks))68
(assert-equal "Add contact lookup tool" (folio-task-text (car tasks)))69
(assert-false (folio-task-done? (car tasks)))70
(assert-true (folio-task-done? (caddr tasks))))))))72
(test "parses log entries"73
(call-with-temp-directory74
(lambda (dir)75
(let ((file (path-join dir "test.md")))76
(write-file-string file sample-project)77
(let* ((proj (read-project file))78
(entries (folio-project-log-entries proj)))79
(assert-equal 2 (length entries))80
(assert-equal "2026-03-13" (caar entries))81
(assert-true (string-contains? (cdar entries) "identity selection")))))))83
(test "task counts"84
(call-with-temp-directory85
(lambda (dir)86
(let ((file (path-join dir "test.md")))87
(write-file-string file sample-project)88
(let ((proj (read-project file)))89
(let-values (((open done) (project-task-counts proj)))90
(assert-equal 2 open)91
(assert-equal 1 done))))))))93
;; ============================================================94
;; project-create!95
;; ============================================================97
(test-group "project-create!"98
(test "creates project file with correct structure"99
(call-with-temp-directory100
(lambda (dir)101
(let* ((store (make-folio-store dir))102
(file (project-create! store "Bureau v2" "Add contact management" "development")))103
(assert-true (file-exists? file))104
(let ((proj (read-project file)))105
(assert-equal "active" (project-status proj))106
(assert-equal "Add contact management" (project-goal proj))107
(assert-equal "development" (project-area proj))108
(assert-equal 0 (length (folio-project-tasks proj)))109
(assert-equal 1 (length (folio-project-log-entries proj)))))))))111
;; ============================================================112
;; project-add-task!113
;; ============================================================115
(test-group "project-add-task!"116
(test "adds task to project"117
(call-with-temp-directory118
(lambda (dir)119
(let* ((store (make-folio-store dir))120
(file (project-create! store "Test" "Test goal" #f)))121
(project-add-task! file "New task" '((priority . "high")))122
(let* ((proj (read-project file))123
(tasks (folio-project-tasks proj)))124
(assert-equal 1 (length tasks))125
(assert-equal "New task" (folio-task-text (car tasks)))126
(assert-true (folio-task-id (car tasks)))))))))128
;; ============================================================129
;; project-add-log!130
;; ============================================================132
(test-group "project-add-log!"133
(test "adds log entry"134
(call-with-temp-directory135
(lambda (dir)136
(let* ((store (make-folio-store dir))137
(file (project-create! store "Test" "Goal" #f)))138
(project-add-log! file "Did some important work.")139
(let* ((proj (read-project file))140
(entries (folio-project-log-entries proj)))141
;; Should have the creation entry plus the new one142
(assert-true (>= (length entries) 2))143
(assert-true144
(string-contains? (cdar entries) "important work"))))))))146
;; ============================================================147
;; project-archive!148
;; ============================================================150
(test-group "project-archive!"151
(test "moves project to archive"152
(call-with-temp-directory153
(lambda (dir)154
(let* ((store (make-folio-store dir))155
(file (project-create! store "Old Project" "Done" #f)))156
(assert-true (project-archive! store "old-project"))157
(assert-false (file-exists? file))158
(assert-true (file-exists?159
(path-join (store-archive-project-dir store)160
"old-project.md"))))))))162
;; ============================================================163
;; project-clean!164
;; ============================================================166
(test-group "project-clean!"167
(test "moves completed tasks to log"168
(call-with-temp-directory169
(lambda (dir)170
(let* ((store (make-folio-store dir))171
(file (project-create! store "Test" "Goal" #f)))172
(project-add-task! file "Task A" '((priority . "high")))173
(project-add-task! file "Task B" '((priority . "low")))174
;; Complete task A175
(let ((id (folio-task-id (car (folio-project-tasks (read-project file))))))176
(backfill-ids! file)177
;; Re-read to get the id178
(let* ((proj (read-project file))179
(task-a-id (folio-task-id (car (folio-project-tasks proj)))))180
;; Manually complete it in the file181
(let* ((content (read-file-string file))182
(updated (string-replace content "- [ ] Task A" "- [x] Task A")))183
(write-file-string file updated))184
;; Clean185
(let ((count (project-clean! file)))186
(assert-equal 1 count)187
;; Verify task list only has Task B188
(let ((proj (read-project file)))189
(assert-equal 1 (length (folio-project-tasks proj)))190
(assert-equal "Task B" (folio-task-text (car (folio-project-tasks proj)))))191
;; Verify log has the completed task192
(let ((content (read-file-string file)))193
(assert-true (string-contains? content "Completed:"))194
(assert-true (string-contains? content "Task A"))))))))))196
(test "returns 0 when no completed tasks"197
(call-with-temp-directory198
(lambda (dir)199
(let* ((store (make-folio-store dir))200
(file (project-create! store "Test" "Goal" #f)))201
(project-add-task! file "Open task" '())202
(assert-equal 0 (project-clean! file)))))))