aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-25 19:54:32 -0500
committerCraig Jennings <c@cjennings.net>2026-05-25 19:54:32 -0500
commit8fa0febbc3a3afe78d1a5abbbb944adf6dd3612a (patch)
tree892acf92c1e1fffcedfeab18cb980ba7e9698c91 /tests
parent648893bd6ff5f4c403733d03258dbf3e91f2394b (diff)
downloadpearl-8fa0febbc3a3afe78d1a5abbbb944adf6dd3612a.tar.gz
pearl-8fa0febbc3a3afe78d1a5abbbb944adf6dd3612a.zip
feat(save): add an atomic conflict gate for enum and relation fields
The text conflict gate (pearl--resolve-conflict) is built for mergeable text — it can open an smerge buffer, stash to the kill ring, and advance a SHA hash. None of that fits a state, priority, assignee, or label conflict, where the value is a single id or scalar that can't be combined. pearl--resolve-atomic-conflict is the sibling for those fields: it prompts use-local / use-remote / cancel (no merge option), and delegates the actual push and the baseline advance to the caller's two callbacks, so it stays free of any field-specific mechanics. It maps to the same outcome contract as the text gate — cancel to conflict/cancelled, use-local to pushed or failed/push-failed, use-remote to resolved-remote. It's not wired into a saver yet — the per-field savers in the next commit call it.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-pearl-conflict.el56
1 files changed, 56 insertions, 0 deletions
diff --git a/tests/test-pearl-conflict.el b/tests/test-pearl-conflict.el
index 138e622..56d3c84 100644
--- a/tests/test-pearl-conflict.el
+++ b/tests/test-pearl-conflict.el
@@ -224,6 +224,62 @@
(should (string= (secure-hash 'sha256 "merged text")
(org-entry-get marker "LINEAR-DESC-SHA256"))))))))
+;;; --resolve-atomic-conflict (enum/relation fields, no merge)
+
+(ert-deftest test-pearl-resolve-atomic-conflict-cancel ()
+ "Cancel pushes nothing, takes nothing, and reports conflict/cancelled."
+ (let (pushed took outcome)
+ (cl-letf (((symbol-function 'pearl--read-atomic-conflict-resolution)
+ (lambda (_) 'cancel)))
+ (pearl--resolve-atomic-conflict
+ "ENG-1 state" "In Progress" "Done"
+ (lambda (cb) (setq pushed t) (funcall cb t))
+ (lambda () (setq took t))
+ (lambda (status reason) (setq outcome (list status reason))))
+ (should-not pushed)
+ (should-not took)
+ (should (equal '(conflict cancelled) outcome)))))
+
+(ert-deftest test-pearl-resolve-atomic-conflict-use-local-pushes ()
+ "Use-local runs the push; on success reports pushed."
+ (let (pushed took outcome)
+ (cl-letf (((symbol-function 'pearl--read-atomic-conflict-resolution)
+ (lambda (_) 'use-local)))
+ (pearl--resolve-atomic-conflict
+ "ENG-1 state" "In Progress" "Done"
+ (lambda (cb) (setq pushed t) (funcall cb t))
+ (lambda () (setq took t))
+ (lambda (status reason) (setq outcome (list status reason))))
+ (should pushed)
+ (should-not took)
+ (should (equal '(pushed nil) outcome)))))
+
+(ert-deftest test-pearl-resolve-atomic-conflict-use-local-push-failure ()
+ "A failed local push reports failed/push-failed."
+ (let (outcome)
+ (cl-letf (((symbol-function 'pearl--read-atomic-conflict-resolution)
+ (lambda (_) 'use-local)))
+ (pearl--resolve-atomic-conflict
+ "ENG-1 state" "In Progress" "Done"
+ (lambda (cb) (funcall cb nil))
+ (lambda () nil)
+ (lambda (status reason) (setq outcome (list status reason))))
+ (should (equal '(failed push-failed) outcome)))))
+
+(ert-deftest test-pearl-resolve-atomic-conflict-use-remote-takes-no-push ()
+ "Use-remote adopts Linear's value, never pushes, and reports resolved-remote."
+ (let (pushed took outcome)
+ (cl-letf (((symbol-function 'pearl--read-atomic-conflict-resolution)
+ (lambda (_) 'use-remote)))
+ (pearl--resolve-atomic-conflict
+ "ENG-1 state" "In Progress" "Done"
+ (lambda (cb) (setq pushed t) (funcall cb t))
+ (lambda () (setq took t))
+ (lambda (status reason) (setq outcome (list status reason))))
+ (should took)
+ (should-not pushed)
+ (should (equal '(resolved-remote nil) outcome)))))
+
;;; --conflict-has-markers-p
(ert-deftest test-pearl-conflict-has-markers-p ()