AtlatestRepositoryfolio
1
;;; Task ids survive a move, in every direction.2
;;;3
;;; Ids are globally unique random hex (`generate-task-id`) with no per-project4
;;; id space, so nothing ever required a new one on move. The old behaviour was5
;;; an oversight in two halves: `project-add-task!` called `generate-task-id`6
;;; unconditionally and ignored any id handed to it, and `move-task-in-store!`7
;;; stripped the id out of the metadata on the way in.8
;;;9
;;; The asymmetry is what marked it as an oversight rather than a decision:10
;;; project -> inbox passed metadata through untouched, and only11
;;; inbox -> project destroyed the id. But project -> inbox was not clean12
;;; either: `inbox-add!` prepended a fresh id and left the old one behind it in13
;;; the alist, so the line ended up carrying TWO `id` entries.15
(import (sigil test)16
(sigil struct)17
(sigil string)18
(sigil dict)19
(sigil fs)20
(folio store)21
(folio task)22
(folio id)23
(folio inbox)24
(folio project)25
(folio tools))27
;; How many times `key` appears in a task's metadata alist.28
(define (meta-key-count task key)29
(length (filter (lambda (p) (eq? (car p) key))30
(folio-task-metadata task))))32
(test-group "task-move preserves ids"33
(test "positive control: the move lands the task in the target project"34
(call-with-temp-directory35
(lambda (dir)36
(let* ((store (make-folio-store dir))37
(file (project-create! store "Target" "Goal" #f))38
(added (inbox-add! store "Route me" '())))39
(let ((result (move-task-in-store! store (folio-task-id added) "target")))40
(assert-true (string-contains? result "Moved")))41
(assert-equal 0 (length (read-inbox store)))42
(assert-equal 1 (length (folio-project-tasks (read-project file))))))))44
(test "inbox -> project keeps the id"45
(call-with-temp-directory46
(lambda (dir)47
(let* ((store (make-folio-store dir))48
(file (project-create! store "Target" "Goal" #f))49
(added (inbox-add! store "Route me" '((priority . "high"))))50
(id (folio-task-id added)))51
(move-task-in-store! store id "target")52
(let ((moved (car (folio-project-tasks (read-project file)))))53
(assert-equal id (folio-task-id moved))54
(assert-equal "high" (task-priority moved))55
(assert-equal 1 (meta-key-count moved 'id)))))))57
(test "project -> inbox keeps the id and emits exactly one"58
(call-with-temp-directory59
(lambda (dir)60
(let* ((store (make-folio-store dir))61
(file (project-create! store "Source" "Goal" #f))62
(added (project-add-task! file "Send me back" '((priority . "low"))))63
(id (folio-task-id added)))64
(move-task-in-store! store id #f)65
(let ((moved (car (read-inbox store))))66
(assert-equal id (folio-task-id moved))67
(assert-equal "low" (task-priority moved))68
(assert-equal 1 (meta-key-count moved 'id)))))))70
(test "project -> project keeps the id"71
(call-with-temp-directory72
(lambda (dir)73
(let* ((store (make-folio-store dir))74
(src (project-create! store "Source" "Goal" #f))75
(dst (project-create! store "Dest" "Goal" #f))76
(added (project-add-task! src "Relocate me" '()))77
(id (folio-task-id added)))78
(move-task-in-store! store id "dest")79
(let ((moved (car (folio-project-tasks (read-project dst)))))80
(assert-equal id (folio-task-id moved)))))))82
(test "a round trip out and back returns the same id"83
(call-with-temp-directory84
(lambda (dir)85
(let* ((store (make-folio-store dir))86
(file (project-create! store "Target" "Goal" #f))87
(added (inbox-add! store "Out and back" '()))88
(id (folio-task-id added)))89
(move-task-in-store! store id "target")90
(move-task-in-store! store id #f)91
(let ((back (car (read-inbox store))))92
(assert-equal id (folio-task-id back))93
(assert-equal 1 (meta-key-count back 'id)))))))95
(test "inbox-triage keeps the id"96
(call-with-temp-directory97
(lambda (dir)98
(let* ((store (make-folio-store dir))99
(file (project-create! store "Target" "Goal" #f))100
(added (inbox-add! store "Triage me" '()))101
(id (folio-task-id added))102
(handler (make-tool-inbox-triage store)))103
(handler (dict id: id project: "target"))104
(let ((moved (car (folio-project-tasks (read-project file)))))105
(assert-equal id (folio-task-id moved))106
(assert-equal 1 (meta-key-count moved 'id)))))))108
;; The whole point of stable ids: a held reference stays valid.109
(test "task-update on the pre-move id still resolves after the move"110
(call-with-temp-directory111
(lambda (dir)112
(let* ((store (make-folio-store dir))113
(file (project-create! store "Target" "Goal" #f))114
(added (inbox-add! store "Chase me" '()))115
(id (folio-task-id added)))116
(move-task-in-store! store id "target")117
(let ((result (update-task-in-store! store id (dict priority: "high"))))118
(assert-true (string-contains? result "Updated")))119
(assert-equal "high"120
(task-priority121
(car (folio-project-tasks (read-project file))))))))))123
(test-group "project-add-task! id handling"124
(test "honours a supplied id"125
(call-with-temp-directory126
(lambda (dir)127
(let* ((store (make-folio-store dir))128
(file (project-create! store "Target" "Goal" #f))129
(task (project-add-task! file "Preassigned"130
'((id . "t-abcd") (priority . "high")))))131
(assert-equal "t-abcd" (folio-task-id task))132
(assert-equal 1 (meta-key-count task 'id))133
(let ((read-back (car (folio-project-tasks (read-project file)))))134
(assert-equal "t-abcd" (folio-task-id read-back))135
(assert-equal "high" (task-priority read-back)))))))137
(test "still generates an id when none is supplied"138
(call-with-temp-directory139
(lambda (dir)140
(let* ((store (make-folio-store dir))141
(file (project-create! store "Target" "Goal" #f))142
(task (project-add-task! file "Fresh" '())))143
(assert-true (string-starts-with? (folio-task-id task) "t-"))144
(assert-equal 1 (meta-key-count task 'id)))))))146
(test-group "inbox-add! id handling"147
(test "honours a supplied id"148
(call-with-temp-directory149
(lambda (dir)150
(let* ((store (make-folio-store dir))151
(task (inbox-add! store "Preassigned" '((id . "t-beef")))))152
(assert-equal "t-beef" (folio-task-id task))153
(assert-equal 1 (meta-key-count task 'id))154
(assert-equal "t-beef" (folio-task-id (car (read-inbox store))))))))156
(test "still generates an id when none is supplied"157
(call-with-temp-directory158
(lambda (dir)159
(let* ((store (make-folio-store dir))160
(task (inbox-add! store "Fresh" '())))161
(assert-true (string-starts-with? (folio-task-id task) "t-"))162
(assert-equal 1 (meta-key-count task 'id)))))))