aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-single-issue.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-23 23:40:27 -0400
committerCraig Jennings <c@cjennings.net>2026-06-23 23:40:27 -0400
commit8869a083c3cc28c20efa63e8932347329b2865f4 (patch)
tree8959775eb2cae28a3418542ab8e2fc235fe465f2 /tests/test-pearl-single-issue.el
parent92ae9d8b56a9fabadf6ea8beb2163655c7a8ffb8 (diff)
downloadpearl-8869a083c3cc28c20efa63e8932347329b2865f4.tar.gz
pearl-8869a083c3cc28c20efa63e8932347329b2865f4.zip
feat(sources): single-issue source (favorites + open-issue-by-id)
I wired the issue kind into the same pipeline every other source uses, so a favorited issue renders in the buffer as its own subtree instead of the v1 browser punt. The new pearl-open-issue-by-id command does the same for an issue you type by identifier or id. It's handy when someone hands you ENG-123 and you'd rather read it in Pearl than a browser. Linear's issue(id:) accepts both the UUID and the human identifier, so there's no resolution step. I fetch by whatever was given and build the :type issue source from the returned node, so the stored id and identifier stay authoritative. Refresh re-fetches the one issue and merges by LINEAR-ID like the filter and view sources. Tests cover the source constructor, the header round-trip, and the fetch/render and command paths across Normal, Boundary, and Error.
Diffstat (limited to 'tests/test-pearl-single-issue.el')
-rw-r--r--tests/test-pearl-single-issue.el117
1 files changed, 117 insertions, 0 deletions
diff --git a/tests/test-pearl-single-issue.el b/tests/test-pearl-single-issue.el
new file mode 100644
index 0000000..a686ea5
--- /dev/null
+++ b/tests/test-pearl-single-issue.el
@@ -0,0 +1,117 @@
+;;; test-pearl-single-issue.el --- Tests for the single-issue source -*- 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 the single-issue source: `pearl--issue-source' (id/identifier ->
+;; source plist), the `#+LINEAR-SOURCE' round-trip for that plist,
+;; `pearl--fetch-and-render-issue' (fetch one issue and render it like every
+;; other source), and `pearl-open-issue-by-id' (the jump-by-identifier command).
+;; The async fetch and the render boundary are stubbed at their seams.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+
+;;; pearl--issue-source
+
+(ert-deftest test-pearl-issue-source-builds-plist ()
+ "An id + identifier builds a `:type issue' source named by the identifier."
+ (let ((s (pearl--issue-source "u1" "ENG-7")))
+ (should (eq 'issue (plist-get s :type)))
+ (should (string= "u1" (plist-get s :id)))
+ (should (string= "ENG-7" (plist-get s :identifier)))
+ (should (string= "ENG-7" (plist-get s :name)))))
+
+(ert-deftest test-pearl-issue-source-name-falls-back-to-id ()
+ "With no identifier the source name falls back to the id, never nil."
+ (let ((s (pearl--issue-source "u1" nil)))
+ (should (string= "u1" (plist-get s :name)))))
+
+(ert-deftest test-pearl-issue-source-round-trips-through-header ()
+ "The issue source survives `#+LINEAR-SOURCE' serialization and read-back."
+ (let* ((s (pearl--issue-source "u1" "ENG-7"))
+ (back (car (read-from-string (pearl--linear-source-string s)))))
+ (should (equal s back))))
+
+;;; pearl--fetch-and-render-issue
+
+(ert-deftest test-pearl-fetch-and-render-issue-renders-one ()
+ "A fetched node renders through the shared query-result boundary as one issue,
+tagged with an issue source built from the node's own id and identifier."
+ (let ((node '((id . "u1") (identifier . "ENG-7") (title . "A bug")))
+ captured)
+ (cl-letf (((symbol-function 'pearl--fetch-issue-async)
+ (lambda (_ref cb) (funcall cb node)))
+ ((symbol-function 'pearl--render-query-result)
+ (lambda (result source) (setq captured (list result source)))))
+ (pearl--fetch-and-render-issue "ENG-7")
+ (should captured)
+ (let ((result (nth 0 captured))
+ (source (nth 1 captured)))
+ (should (equal (list node) (pearl--query-result-issues result)))
+ (should (eq 'issue (plist-get source :type)))
+ (should (string= "u1" (plist-get source :id)))
+ (should (string= "ENG-7" (plist-get source :identifier)))))))
+
+(ert-deftest test-pearl-fetch-and-render-issue-missing-does-not-render ()
+ "A `:missing' result reports and renders nothing (no buffer rewrite)."
+ (let (rendered)
+ (cl-letf (((symbol-function 'pearl--fetch-issue-async)
+ (lambda (_ref cb) (funcall cb :missing)))
+ ((symbol-function 'pearl--render-query-result)
+ (lambda (&rest _) (setq rendered t)))
+ ((symbol-function 'message) (lambda (&rest _) nil)))
+ (pearl--fetch-and-render-issue "ENG-404")
+ (should-not rendered))))
+
+(ert-deftest test-pearl-fetch-and-render-issue-error-does-not-render ()
+ "An `:error' result reports and renders nothing."
+ (let (rendered)
+ (cl-letf (((symbol-function 'pearl--fetch-issue-async)
+ (lambda (_ref cb) (funcall cb :error)))
+ ((symbol-function 'pearl--render-query-result)
+ (lambda (&rest _) (setq rendered t)))
+ ((symbol-function 'message) (lambda (&rest _) nil)))
+ (pearl--fetch-and-render-issue "ENG-7")
+ (should-not rendered))))
+
+;;; pearl-open-issue-by-id
+
+(ert-deftest test-pearl-open-issue-by-id-forwards-trimmed-ref ()
+ "The command trims the typed ref and forwards it to the fetch/render path."
+ (let (got)
+ (cl-letf (((symbol-function 'pearl--require-account-context) #'ignore)
+ ((symbol-function 'pearl--progress) (lambda (&rest _) nil))
+ ((symbol-function 'pearl--fetch-and-render-issue)
+ (lambda (ref) (setq got ref))))
+ (pearl-open-issue-by-id " ENG-7 ")
+ (should (string= "ENG-7" got)))))
+
+(ert-deftest test-pearl-open-issue-by-id-rejects-empty ()
+ "An empty/whitespace identifier is a `user-error', not a fetch."
+ (let (fetched)
+ (cl-letf (((symbol-function 'pearl--require-account-context) #'ignore)
+ ((symbol-function 'pearl--fetch-and-render-issue)
+ (lambda (&rest _) (setq fetched t))))
+ (should-error (pearl-open-issue-by-id " ") :type 'user-error)
+ (should-not fetched))))
+
+(provide 'test-pearl-single-issue)
+;;; test-pearl-single-issue.el ends here