aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-states.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-24 13:44:34 -0500
committerCraig Jennings <c@cjennings.net>2026-05-24 13:44:34 -0500
commitb081d62276378b3168c92c06153fd59db0589535 (patch)
tree9be7f7d22e0c9b4a73432fe744c09bb456c671a9 /tests/test-pearl-states.el
downloadpearl-b081d62276378b3168c92c06153fd59db0589535.tar.gz
pearl-b081d62276378b3168c92c06153fd59db0589535.zip
feat: pearl — manage Linear issues from org-mode
Pearl fetches Linear issues into an org file and syncs edits back. It covers list / custom views / saved queries, per-issue and bulk rendering with comments inline, conflict-aware sync of descriptions, titles, and comments, field commands for priority / state / assignee / labels, and a transient dispatch menu. The render folds to a scannable outline and nests issues under a sortable parent. Based on and inspired by Gael Blanchemain's linear-emacs.
Diffstat (limited to 'tests/test-pearl-states.el')
-rw-r--r--tests/test-pearl-states.el134
1 files changed, 134 insertions, 0 deletions
diff --git a/tests/test-pearl-states.el b/tests/test-pearl-states.el
new file mode 100644
index 0000000..713cb78
--- /dev/null
+++ b/tests/test-pearl-states.el
@@ -0,0 +1,134 @@
+;;; test-pearl-states.el --- Tests for pearl state management -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for workflow-state fetching, state name -> id resolution, and the
+;; two state-update entry points. Covers the guard that skips the mutation
+;; when the state name doesn't resolve, so no request fires with a null id.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+(require 'testutil-request (expand-file-name "testutil-request.el"))
+(require 'cl-lib)
+
+;;; pearl-get-states-async
+
+(ert-deftest test-pearl-get-states-async-parses-nodes ()
+ "Workflow states are unwrapped and passed to the callback."
+ (let ((got nil))
+ (testutil-linear-with-response
+ '((data (team (states (nodes . (((id . "s1") (name . "Todo"))
+ ((id . "s2") (name . "Done"))))))))
+ (pearl-get-states-async "team-1" (lambda (states) (setq got states))))
+ (should (= 2 (length got)))
+ (should (string-equal "s1" (cdr (assoc 'id (car got)))))))
+
+;;; pearl--get-state-id-by-name
+
+(ert-deftest test-pearl-get-state-id-by-name-found ()
+ "A state whose name matches (case-insensitively) resolves to its id."
+ (let ((pearl--cache-states nil))
+ (testutil-linear-with-response
+ '((data (team (states (nodes . (((id . "s1") (name . "Todo"))
+ ((id . "s2") (name . "Done"))))))))
+ (should (string-equal "s2" (pearl--get-state-id-by-name "done" "team-1"))))))
+
+(ert-deftest test-pearl-get-state-id-by-name-not-found ()
+ "A state name absent from the team resolves to nil."
+ (let ((pearl--cache-states nil))
+ (testutil-linear-with-response
+ '((data (team (states (nodes . (((id . "s1") (name . "Todo"))))))))
+ (should (null (pearl--get-state-id-by-name "Archived" "team-1"))))))
+
+(ert-deftest test-pearl-state-lookup-caches-per-team ()
+ "A second lookup for the same team is served from cache, no new request."
+ (let ((pearl-api-key "test-key")
+ (pearl--cache-states nil)
+ (calls 0))
+ (cl-letf (((symbol-function 'request)
+ (lambda (_url &rest args)
+ (setq calls (1+ calls))
+ (funcall (plist-get args :success)
+ :data '((data (team (states (nodes . (((id . "s1") (name . "Todo"))
+ ((id . "s2") (name . "Done"))))))))))))
+ (should (string-equal "s2" (pearl--get-state-id-by-name "Done" "team-1")))
+ (should (string-equal "s1" (pearl--get-state-id-by-name "Todo" "team-1")))
+ (should (= 1 calls)))))
+
+;;; pearl-update-issue-state (sync) -- nil-state-id guard
+
+(ert-deftest test-pearl-update-issue-state-nil-state-id-skips-request ()
+ "When the state name doesn't resolve, no mutation request is fired."
+ (let ((requested nil)
+ (pearl-api-key "test-key"))
+ (cl-letf (((symbol-function 'pearl--get-state-id-by-name) (lambda (_s _t) nil))
+ ((symbol-function 'request) (lambda (&rest _) (setq requested t))))
+ (pearl-update-issue-state "i-1" "Nonexistent" "team-1")
+ (should-not requested))))
+
+(ert-deftest test-pearl-update-issue-state-resolved-fires-request ()
+ "When the state resolves, the mutation request is fired."
+ (let ((requested nil)
+ (pearl-api-key "test-key"))
+ (cl-letf (((symbol-function 'pearl--get-state-id-by-name) (lambda (_s _t) "s2"))
+ ((symbol-function 'request)
+ (lambda (_url &rest args)
+ (setq requested t)
+ (let ((cb (plist-get args :success)))
+ (when cb (funcall cb :data '((data (issueUpdate (success . t))))))))))
+ (pearl-update-issue-state "i-1" "Done" "team-1")
+ (should requested))))
+
+;;; pearl--update-issue-state-async -- nil-state-id guard
+
+(ert-deftest test-pearl-update-issue-state-async-nil-state-id-skips-request ()
+ "The async update also short-circuits when the state name doesn't resolve."
+ (let ((requested nil)
+ (pearl-api-key "test-key"))
+ (cl-letf (((symbol-function 'pearl--get-state-id-by-name) (lambda (_s _t) nil))
+ ((symbol-function 'request) (lambda (&rest _) (setq requested t))))
+ (pearl--update-issue-state-async "i-1" "Nonexistent" "team-1")
+ (should-not requested))))
+
+(ert-deftest test-pearl-update-issue-state-async-success-runs ()
+ "The async success handler runs on a successful update."
+ (let ((pearl-api-key "test-key"))
+ (cl-letf (((symbol-function 'pearl--get-state-id-by-name) (lambda (_s _t) "s2"))
+ ((symbol-function 'request)
+ (lambda (_url &rest args)
+ (funcall (plist-get args :success)
+ :data '((data (issueUpdate (success . t))))))))
+ (should (progn (pearl--update-issue-state-async "i-1" "Done" "team-1") t)))))
+
+(ert-deftest test-pearl-update-issue-state-async-error-runs ()
+ "The async error handler runs on a transport error."
+ (let ((pearl-api-key "test-key"))
+ (cl-letf (((symbol-function 'pearl--get-state-id-by-name) (lambda (_s _t) "s2"))
+ ((symbol-function 'request)
+ (lambda (_url &rest args)
+ (funcall (plist-get args :error)
+ :error-thrown "boom"
+ :response (make-request-response :status-code 500)
+ :data nil))))
+ (should (progn (pearl--update-issue-state-async "i-1" "Done" "team-1") t)))))
+
+(provide 'test-pearl-states)
+;;; test-pearl-states.el ends here