aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 22:30:49 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 22:30:49 -0500
commit504133e600a197b50f05d3916264433ee9e4cb12 (patch)
treeaf78b8d1eae61ba82b71aec53269c7fa93fb0c3b /tests
parent768c9c0122130d44e9243f22f7681677623b713a (diff)
downloadpearl-504133e600a197b50f05d3916264433ee9e4cb12.tar.gz
pearl-504133e600a197b50f05d3916264433ee9e4cb12.zip
feat(render): derive the #+TODO line and keywords from team workflow states
The renderer wrote a fixed #+TODO line and mapped state names through a static defcustom, so a workspace's real states (Dev Review, PM Acceptance, ...) never showed up. Now the displayed issues' teams drive both. pearl--gather-header-states collects each team's full state set (cached via pearl--team-states, teams in first-seen order, states by position) plus every displayed issue's own state, so a failed team fetch still leaves its issues' states declared. pearl--build-org-content takes that list and derives the #+TODO via pearl--derive-todo-line. pearl--format-issue-as-org-entry renders each keyword with pearl--state-name-to-keyword instead of the static map -- slugify reproduces the old defaults, so standard states are unchanged -- and writes a :LINEAR-STATE-TYPE: drawer so a later merge scan can classify a retained heading. The team-states query gains type and position. The integration test stubs pearl--team-states so the render stays network-free. The merge-refresh header rebuild is the next commit. Until then a same-source refresh keeps the previous header.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-integration-acceptance.el4
-rw-r--r--tests/test-pearl-todo-keywords.el38
2 files changed, 42 insertions, 0 deletions
diff --git a/tests/test-integration-acceptance.el b/tests/test-integration-acceptance.el
index de5f0e7..11b88b2 100644
--- a/tests/test-integration-acceptance.el
+++ b/tests/test-integration-acceptance.el
@@ -109,6 +109,10 @@ because the description-update mutation contains both `IssueDescription' and
(when (find-buffer-visiting tmp) (kill-buffer (find-buffer-visiting tmp)))
(cl-letf (((symbol-function 'pearl--graphql-request-async)
#'test-integration--dispatch)
+ ;; the header-state gather fetches team states synchronously;
+ ;; stub it so the render stays network-free (the derived #+TODO
+ ;; then comes from the displayed issues' own states)
+ ((symbol-function 'pearl--team-states) (lambda (_team-id) nil))
;; no windows in batch; keep the surface step a no-op
((symbol-function 'pearl--surface-buffer) (lambda (b) b)))
(unwind-protect
diff --git a/tests/test-pearl-todo-keywords.el b/tests/test-pearl-todo-keywords.el
index a167157..b941b26 100644
--- a/tests/test-pearl-todo-keywords.el
+++ b/tests/test-pearl-todo-keywords.el
@@ -83,5 +83,43 @@
(should (string= "TODO IN-PROGRESS IN-REVIEW BACKLOG BLOCKED | DONE"
(pearl--derive-todo-line '()))))
+;;; --gather-header-states (teams first-seen, states by position)
+
+(ert-deftest test-pearl-gather-header-states-orders-by-team-then-position ()
+ "Teams come in first-seen order; within a team, states sort by position."
+ (cl-letf (((symbol-function 'pearl--team-states)
+ (lambda (tid)
+ (pcase tid
+ ("t1" '(((id . "s2") (name . "In Progress") (type . "started") (position . 2.0))
+ ((id . "s1") (name . "Todo") (type . "unstarted") (position . 1.0))))
+ ("t2" '(((id . "s3") (name . "Done") (type . "completed") (position . 9.0))))))))
+ (let* ((issues (list (list :team '(:id "t1") :state '(:name "Todo" :type "unstarted"))
+ (list :team '(:id "t2") :state '(:name "Done" :type "completed"))))
+ (line (pearl--derive-todo-line (pearl--gather-header-states issues))))
+ (should (string= "TODO IN-PROGRESS | DONE" line)))))
+
+(ert-deftest test-pearl-gather-header-states-covers-failed-team ()
+ "A team whose fetch returns nil is skipped, but its issues' own states still appear."
+ (cl-letf (((symbol-function 'pearl--team-states) (lambda (_tid) nil)))
+ (let* ((issues (list (list :team '(:id "t9") :state '(:name "Grooming" :type "backlog"))))
+ (line (pearl--derive-todo-line (pearl--gather-header-states issues))))
+ (should (string-match-p "GROOMING" line)))))
+
+;;; --build-org-content with a derived #+TODO
+
+(ert-deftest test-pearl-build-org-content-derives-todo-from-states ()
+ "With a states list, the #+TODO line is derived (not the hardcoded default)."
+ (let ((out (pearl--build-org-content
+ '() '(:type filter :name "X" :filter nil) nil
+ '((:name "Todo" :type "unstarted")
+ (:name "Dev Review" :type "started")
+ (:name "Done" :type "completed")))))
+ (should (string-match-p "^#\\+TODO: TODO DEV-REVIEW \\| DONE$" out))))
+
+(ert-deftest test-pearl-build-org-content-no-states-keeps-fallback ()
+ "With no states (nil), the #+TODO line stays the hardcoded default."
+ (let ((out (pearl--build-org-content '() '(:type filter :name "X" :filter nil) nil nil)))
+ (should (string-match-p "^#\\+TODO: TODO IN-PROGRESS IN-REVIEW BACKLOG BLOCKED \\| DONE$" out))))
+
(provide 'test-pearl-todo-keywords)
;;; test-pearl-todo-keywords.el ends here