aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-pearl-mapping.el28
-rw-r--r--tests/test-pearl-save.el75
-rw-r--r--tests/test-pearl-todo-keywords.el22
3 files changed, 104 insertions, 21 deletions
diff --git a/tests/test-pearl-mapping.el b/tests/test-pearl-mapping.el
index 2a96163..b5faa3d 100644
--- a/tests/test-pearl-mapping.el
+++ b/tests/test-pearl-mapping.el
@@ -37,27 +37,13 @@
(pearl--todo-states-pattern-source nil))
,@body))
-;;; pearl--map-linear-state-to-org
-
-(ert-deftest test-pearl-map-linear-state-to-org-mapped-returns-keyword ()
- "A Linear state present in the mapping returns its org keyword."
- (test-pearl--with-mapping '(("Todo" . "TODO") ("In Progress" . "DOING"))
- (should (string-equal "DOING" (pearl--map-linear-state-to-org "In Progress")))))
-
-(ert-deftest test-pearl-map-linear-state-to-org-unmapped-defaults-todo ()
- "An unmapped Linear state falls back to TODO."
- (test-pearl--with-mapping '(("Todo" . "TODO") ("Done" . "DONE"))
- (should (string-equal "TODO" (pearl--map-linear-state-to-org "Triage")))))
-
-(ert-deftest test-pearl-map-linear-state-to-org-nil-defaults-todo ()
- "A nil state falls back to TODO rather than erroring."
- (test-pearl--with-mapping '(("Todo" . "TODO"))
- (should (string-equal "TODO" (pearl--map-linear-state-to-org nil)))))
-
-;; pearl--map-org-state-to-linear (the keyword->state reverse map) was removed
-;; with the org-sync push path in save-model-v2; the keyword-cycle save will
-;; resolve through the team's states (the keyword-derivation task), not a flat
-;; reverse mapping.
+;; pearl--map-linear-state-to-org (the static name->keyword lookup) was removed
+;; in the keyword-cycle save: the keyword is now always the slug of the state
+;; name (`pearl--state-name-to-keyword'), matching the workspace-derived #+TODO
+;; header, and the keyword->state-id direction resolves through the team's
+;; states (`pearl--resolve-keyword-to-state'), not a flat mapping. The mapping
+;; alist itself survives only as the source for `pearl--get-todo-states-pattern'
+;; (tested below).
;;; pearl--get-todo-states-pattern
diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el
index 1f4f273..22df84e 100644
--- a/tests/test-pearl-save.el
+++ b/tests/test-pearl-save.el
@@ -153,6 +153,21 @@
(org-entry-put nil "LINEAR-STATE-ID" "s2")
(should (plist-get (pearl--issue-dirty-fields) :state))))
+(ert-deftest test-pearl-dirty-fields-state-keyword-cycle ()
+ "Cycling the TODO keyword away from the synced state-name slug marks state dirty,
+even when the explicit state id still matches its baseline."
+ (test-pearl-save--in-rendered (test-pearl-save--structured-issue)
+ (org-back-to-heading t)
+ (let ((org-after-todo-state-change-hook nil))
+ (org-todo "DONE")) ; TODO -> DONE, id untouched
+ (should (plist-get (pearl--issue-dirty-fields) :state))))
+
+(ert-deftest test-pearl-dirty-fields-state-fresh-keyword-not-dirty ()
+ "A freshly rendered state (keyword equals the state-name slug) is not state dirty."
+ (test-pearl-save--in-rendered (test-pearl-save--structured-issue)
+ (org-back-to-heading t)
+ (should-not (plist-get (pearl--issue-dirty-fields) :state))))
+
(ert-deftest test-pearl-dirty-fields-assignee-change ()
"An assignee id different from the synced baseline marks assignee dirty."
(test-pearl-save--in-rendered (test-pearl-save--structured-issue)
@@ -495,6 +510,66 @@ skipped (the remote push already landed), not signal."
(should (string= "s2" pushed))
(should (string= "s2" (org-entry-get marker "LINEAR-STATE-ID-SYNCED")))))))
+(ert-deftest test-pearl-save-state-keyword-cycle-resolves-and-pushes ()
+ "A keyword-cycled state (id unmoved) resolves the keyword to its team state id
+by slug match, pushes that id, and writes the resolved name/id/baseline."
+ (test-pearl-save--in-rendered
+ '(:id "u" :identifier "ENG-1" :title "t" :priority 2
+ :state (:id "s1" :name "Todo") :description "body")
+ (goto-char (point-min))
+ (insert "#+TODO: TODO IN-PROGRESS | DONE\n")
+ (org-mode) ; re-init so the keywords take
+ (goto-char (point-min))
+ (re-search-forward "ENG-1")
+ (org-back-to-heading t)
+ (org-entry-put nil "LINEAR-TEAM-ID" "team-1")
+ (progn
+ (let ((org-after-todo-state-change-hook nil))
+ (org-todo "IN-PROGRESS")) ; cycle keyword, leave id = s1
+ (let ((marker (point-marker)) outcome pushed)
+ (cl-letf (((symbol-function 'pearl--team-states)
+ (lambda (_tid)
+ '(((id . "s1") (name . "Todo") (type . "unstarted") (position . 1.0))
+ ((id . "s2") (name . "In Progress") (type . "started") (position . 2.0)))))
+ ((symbol-function 'pearl--fetch-issue-async)
+ (lambda (_id cb) (funcall cb '((state (id . "s1") (name . "Todo"))))))
+ ((symbol-function 'pearl--update-issue-async)
+ (lambda (_id input cb)
+ (setq pushed (cdr (assoc "stateId" input)))
+ (funcall cb '(:success t)))))
+ (pearl--save-state-field marker (lambda (o) (setq outcome o)))
+ (should (eq 'pushed (plist-get outcome :status)))
+ (should (string= "s2" pushed))
+ (should (string= "s2" (org-entry-get marker "LINEAR-STATE-ID")))
+ (should (string= "In Progress" (org-entry-get marker "LINEAR-STATE-NAME")))
+ (should (string= "s2" (org-entry-get marker "LINEAR-STATE-ID-SYNCED"))))))))
+
+(ert-deftest test-pearl-save-state-keyword-cycle-unknown-keyword-skips ()
+ "A keyword no team state slugifies to cannot be resolved, so the save is skipped
+with no push."
+ (test-pearl-save--in-rendered
+ '(:id "u" :identifier "ENG-1" :title "t" :priority 2
+ :state (:id "s1" :name "Todo") :description "body")
+ (goto-char (point-min))
+ (insert "#+TODO: TODO BLOCKED | DONE\n")
+ (org-mode)
+ (goto-char (point-min))
+ (re-search-forward "ENG-1")
+ (org-back-to-heading t)
+ (org-entry-put nil "LINEAR-TEAM-ID" "team-1")
+ (progn
+ (let ((org-after-todo-state-change-hook nil))
+ (org-todo "BLOCKED")) ; no team state slugifies to BLOCKED
+ (let ((marker (point-marker)) outcome (pushed :none))
+ (cl-letf (((symbol-function 'pearl--team-states)
+ (lambda (_tid)
+ '(((id . "s1") (name . "Todo") (type . "unstarted") (position . 1.0)))))
+ ((symbol-function 'pearl--update-issue-async)
+ (lambda (_id _input cb) (setq pushed :called) (funcall cb '(:success t)))))
+ (pearl--save-state-field marker (lambda (o) (setq outcome o)))
+ (should (eq 'skipped (plist-get outcome :status)))
+ (should (eq :none pushed)))))))
+
(ert-deftest test-pearl-save-labels-clean-push-advances-baseline ()
"A changed label set with an unmoved remote pushes labelIds and advances the baseline."
(test-pearl-save--in-rendered
diff --git a/tests/test-pearl-todo-keywords.el b/tests/test-pearl-todo-keywords.el
index 281beea..0555a4e 100644
--- a/tests/test-pearl-todo-keywords.el
+++ b/tests/test-pearl-todo-keywords.el
@@ -150,5 +150,27 @@
;; SHIPPED is an org done-keyword, so it lands on the done side
(should (re-search-forward "^#\\+TODO: *\\| SHIPPED$" nil t)))))
+;;; --resolve-keyword-to-state (keyword -> team state id, save-time)
+
+(ert-deftest test-pearl-resolve-keyword-to-state-collision-first-by-position ()
+ "When two state names slugify to the same keyword, the lower position wins."
+ (cl-letf (((symbol-function 'pearl--team-states)
+ (lambda (_tid)
+ '(((id . "s-hi") (name . "Dev Review") (type . "started") (position . 5.0))
+ ((id . "s-lo") (name . "Dev-Review") (type . "started") (position . 2.0))))))
+ (let ((r (pearl--resolve-keyword-to-state "DEV-REVIEW" "t1")))
+ (should (string= "s-lo" (plist-get r :id)))
+ (should (string= "Dev-Review" (plist-get r :name)))
+ (should (string= "started" (plist-get r :type))))))
+
+(ert-deftest test-pearl-resolve-keyword-to-state-no-match-or-empty-returns-nil ()
+ "A keyword no state slugifies to yields nil, as does an empty/failed fetch."
+ (cl-letf (((symbol-function 'pearl--team-states)
+ (lambda (_tid)
+ '(((id . "s1") (name . "Todo") (type . "unstarted") (position . 1.0))))))
+ (should-not (pearl--resolve-keyword-to-state "BLOCKED" "t1")))
+ (cl-letf (((symbol-function 'pearl--team-states) (lambda (_tid) nil)))
+ (should-not (pearl--resolve-keyword-to-state "TODO" "t1"))))
+
(provide 'test-pearl-todo-keywords)
;;; test-pearl-todo-keywords.el ends here