aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-saved-query-sync.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-01 17:33:09 -0500
committerCraig Jennings <c@cjennings.net>2026-06-01 17:33:09 -0500
commitc2ba1d8f25463bf6342ac635850500a9738b31e9 (patch)
tree69518c846bc2e9b11559dc637b6ee81d9d3511c0 /tests/test-pearl-saved-query-sync.el
parent4eab7a048ca18eeef31e4f8d0823182fc6e2368f (diff)
downloadpearl-c2ba1d8f25463bf6342ac635850500a9738b31e9.tar.gz
pearl-c2ba1d8f25463bf6342ac635850500a9738b31e9.zip
refactor: rename saved-query surface to local-view vocabulary
I renamed Pearl's "saved query" surface to the local/Linear view vocabulary, with no obsolete aliases, since Pearl has no users to protect. The defcustom pearl-saved-queries is now pearl-local-views. The commands pearl-run-saved-query, pearl-delete-saved-query, pearl-sync-saved-query-to-linear, pearl-run-view, and pearl-publish-current-source become pearl-run-local-view, pearl-delete-local-view, pearl-publish-local-view, pearl-run-linear-view, and pearl-publish-current-view. User-facing prompts, messages, and docstrings drop "saved query" for "local view", and the publish command reads "publish" instead of "sync". Internal GraphQL and helper names keep their "query" terms, which a user never sees. A naming-regression test asserts the new symbols exist, the old ones are gone rather than aliased, and no public command exposes "query". Phase 1 of docs/local-and-linear-views-spec.org. No behavior change. Suite, compile, and lint are green.
Diffstat (limited to 'tests/test-pearl-saved-query-sync.el')
-rw-r--r--tests/test-pearl-saved-query-sync.el110
1 files changed, 55 insertions, 55 deletions
diff --git a/tests/test-pearl-saved-query-sync.el b/tests/test-pearl-saved-query-sync.el
index e237108..534993e 100644
--- a/tests/test-pearl-saved-query-sync.el
+++ b/tests/test-pearl-saved-query-sync.el
@@ -1,4 +1,4 @@
-;;; test-pearl-saved-query-sync.el --- Tests for syncing saved queries to Linear views -*- lexical-binding: t; -*-
+;;; test-pearl-saved-query-sync.el --- Tests for syncing local views to Linear views -*- lexical-binding: t; -*-
;; Copyright (C) 2026 Craig Jennings
@@ -19,7 +19,7 @@
;;; Commentary:
-;; Tests for `pearl-sync-saved-query-to-linear' and its helpers
+;; Tests for `pearl-publish-local-view' and its helpers
;; (`pearl--sync-scope-candidates', `pearl--find-view-by-name-in-scope',
;; `pearl--customview-create-async', `pearl--customview-update-async',
;; `pearl--save-query-mark-synced', `pearl--sync-record-or-orphan-error').
@@ -254,11 +254,11 @@ label, plist team-id nil + team-name \"Personal\" + shared nil."
(ert-deftest test-pearl-save-query-mark-synced-adds-the-four-keys ()
"After mark-synced, the entry carries `:linear-view-id', `-team-id', `-shared', `-synced-at'."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t))))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced "Q" "team-uuid" t "view-uuid"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (equal "view-uuid" (plist-get spec :linear-view-id)))
(should (equal "team-uuid" (plist-get spec :linear-view-team-id)))
(should (eq t (plist-get spec :linear-view-shared)))
@@ -268,39 +268,39 @@ label, plist team-id nil + team-name \"Personal\" + shared nil."
(ert-deftest test-pearl-save-query-mark-synced-preserves-sort-and-order ()
"Mark-synced doesn't drop `:sort' or `:order' from the entry."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t) :sort updated :order desc)))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced "Q" nil nil "view-uuid"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (eq 'updated (plist-get spec :sort)))
(should (eq 'desc (plist-get spec :order))))))
(ert-deftest test-pearl-save-query-mark-synced-personal-nil-team ()
"Personal scope persists `:linear-view-team-id' as nil and `:linear-view-shared' nil."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t))))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced "Q" nil nil "view-uuid"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (null (plist-get spec :linear-view-team-id)))
(should (null (plist-get spec :linear-view-shared))))))
(ert-deftest test-pearl-save-query-mark-synced-unknown-name-signals-user-error ()
"Mark-synced on a name that doesn't exist signals a `user-error'."
- (let ((pearl-saved-queries '()))
+ (let ((pearl-local-views '()))
(should-error
(pearl--save-query-mark-synced "Q" nil nil "view-uuid")
:type 'user-error)))
(ert-deftest test-pearl-save-query-mark-synced-stores-view-url-when-given ()
"The optional VIEW-URL arg lands in the entry as :linear-view-url."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t))))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced
"Q" nil nil "view-uuid" "https://linear.app/ws/view/abc"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (equal "https://linear.app/ws/view/abc"
(plist-get spec :linear-view-url))))))
@@ -308,7 +308,7 @@ label, plist team-id nil + team-name \"Personal\" + shared nil."
"Omitting VIEW-URL on a re-sync preserves any previously stored URL.
Re-sync paths whose API response doesn't carry a URL shouldn't erase a
URL the create path already wrote."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "old-id"
:linear-view-team-id nil
@@ -317,7 +317,7 @@ URL the create path already wrote."
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
;; No VIEW-URL passed -- the prior URL should survive.
(pearl--save-query-mark-synced "Q" nil nil "new-id"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (equal "https://linear.app/ws/view/old"
(plist-get spec :linear-view-url))))))
@@ -327,7 +327,7 @@ The earlier implementation rebuilt the spec from a fixed key set and
silently dropped anything outside it. Copy-then-plist-put preserves
arbitrary keys so a future schema field or user annotation isn't
erased on every re-sync."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:sort updated
:order desc
@@ -335,7 +335,7 @@ erased on every re-sync."
:user-tag "experimental")))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced "Q" "team-uuid" t "view-uuid" "https://x"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
;; Original keys survive.
(should (equal '(:open t) (plist-get spec :filter)))
(should (eq 'updated (plist-get spec :sort)))
@@ -351,7 +351,7 @@ erased on every re-sync."
(ert-deftest test-pearl-save-query-mark-synced-overwrites-prior-sync-metadata ()
"Re-syncing an already-synced entry overwrites the four `:linear-view-*' keys."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "OLD-id"
:linear-view-team-id "OLD-team"
@@ -359,7 +359,7 @@ erased on every re-sync."
:linear-view-synced-at "1970-01-01T00:00:00Z")))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (_ _) nil)))
(pearl--save-query-mark-synced "Q" "NEW-team" t "NEW-id"))
- (let ((spec (cdr (assoc "Q" pearl-saved-queries))))
+ (let ((spec (cdr (assoc "Q" pearl-local-views))))
(should (equal "NEW-id" (plist-get spec :linear-view-id)))
(should (equal "NEW-team" (plist-get spec :linear-view-team-id)))
(should (eq t (plist-get spec :linear-view-shared)))
@@ -445,7 +445,7 @@ erased on every re-sync."
"Re-sync of an entry with `:linear-view-shared :json-false' must NOT flip it shared.
`:json-false' is truthy in Elisp, so a stored `:json-false' would slip past
`(if shared t :json-false)' in the encoder and silently make the view team-shared."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "view-id"
:linear-view-team-id nil
@@ -465,18 +465,18 @@ erased on every re-sync."
(funcall cb (list :success t
:view '((id . "view-id")
(shared . :json-false)))))))
- (pearl-sync-saved-query-to-linear "Q")
+ (pearl-publish-local-view "Q")
(let ((shared-value (cdr (assoc "shared" captured-input))))
(should (eq :json-false shared-value))))))
-;;; pearl-sync-saved-query-to-linear — empty filter refusal
+;;; pearl-publish-local-view — empty filter refusal
(ert-deftest test-pearl-sync-empty-filter-refuses-before-scope-prompt ()
- "A first-time-sync saved query whose filter compiles to nil signals a clear
+ "A first-time-sync local view whose filter compiles to nil signals a clear
user-error before any scope prompt fires. Without the guard, the sync would
pass filterData=null to Linear, which rejects with an opaque error after the
user has already clicked through the scope picker."
- (let ((pearl-saved-queries (copy-tree '(("EmptyFilter" :filter nil))))
+ (let ((pearl-local-views (copy-tree '(("EmptyFilter" :filter nil))))
(scope-prompt-fired nil)
(api-called nil))
(cl-letf (((symbol-function 'pearl--sync-saved-query-pick-scope)
@@ -488,7 +488,7 @@ user has already clicked through the scope picker."
(list :team-id nil :team-name "Personal" :shared nil))))
((symbol-function 'pearl--customview-create-async)
(lambda (&rest _) (setq api-called t))))
- (should-error (pearl-sync-saved-query-to-linear "EmptyFilter")
+ (should-error (pearl-publish-local-view "EmptyFilter")
:type 'user-error)
(should-not scope-prompt-fired)
(should-not api-called))))
@@ -498,7 +498,7 @@ user has already clicked through the scope picker."
edited down to nil takes the same empty-filter guard, refusing without
calling customViewUpdate. Guards against a future refactor accidentally
hoisting the guard into the create-only branch."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("EmptyFilter" :filter nil
:linear-view-id "existing-view-uuid"
:linear-view-team-id nil
@@ -506,11 +506,11 @@ hoisting the guard into the create-only branch."
(update-called nil))
(cl-letf (((symbol-function 'pearl--customview-update-async)
(lambda (&rest _) (setq update-called t))))
- (should-error (pearl-sync-saved-query-to-linear "EmptyFilter")
+ (should-error (pearl-publish-local-view "EmptyFilter")
:type 'user-error)
(should-not update-called))))
-;;; pearl-publish-current-source
+;;; pearl-publish-current-view
(defmacro test-pearl-publish--in-buffer (source-form &rest body)
"Run BODY in an org-mode temp buffer carrying SOURCE-FORM as #+LINEAR-SOURCE.
@@ -526,50 +526,50 @@ SOURCE-FORM is the source plist (or nil for no source header)."
(ert-deftest test-pearl-publish-current-source-no-source-header-errors ()
"A buffer with no #+LINEAR-SOURCE signals a clear user-error."
(test-pearl-publish--in-buffer nil
- (should-error (pearl-publish-current-source) :type 'user-error)))
+ (should-error (pearl-publish-current-view) :type 'user-error)))
(ert-deftest test-pearl-publish-current-source-view-source-errors ()
"A view source signals user-error -- views are already on Linear."
(test-pearl-publish--in-buffer '(:type view :name "Eng dashboard" :id "v1")
- (should-error (pearl-publish-current-source) :type 'user-error)))
+ (should-error (pearl-publish-current-view) :type 'user-error)))
(ert-deftest test-pearl-publish-current-source-transient-filter-no-name-errors ()
"A :type filter with no :name (transient ad-hoc) signals user-error."
(test-pearl-publish--in-buffer '(:type filter :filter (:open t))
- (should-error (pearl-publish-current-source) :type 'user-error)))
+ (should-error (pearl-publish-current-view) :type 'user-error)))
(ert-deftest test-pearl-publish-current-source-no-matching-saved-query-errors ()
- "A :type filter source whose name doesn't match any pearl-saved-queries entry errors."
- (let ((pearl-saved-queries '(("Other" :filter (:open t)))))
+ "A :type filter source whose name doesn't match any pearl-local-views entry errors."
+ (let ((pearl-local-views '(("Other" :filter (:open t)))))
(test-pearl-publish--in-buffer
'(:type filter :name "Gone" :filter (:open t))
- (should-error (pearl-publish-current-source) :type 'user-error))))
+ (should-error (pearl-publish-current-view) :type 'user-error))))
(ert-deftest test-pearl-publish-current-source-dispatches-to-sync-with-name ()
- "A :type filter source whose name matches dispatches to pearl-sync-saved-query-to-linear."
- (let ((pearl-saved-queries '(("Mine" :filter (:open t))))
+ "A :type filter source whose name matches dispatches to pearl-publish-local-view."
+ (let ((pearl-local-views '(("Mine" :filter (:open t))))
(sync-called-with nil))
- (cl-letf (((symbol-function 'pearl-sync-saved-query-to-linear)
+ (cl-letf (((symbol-function 'pearl-publish-local-view)
(lambda (n) (setq sync-called-with n))))
(test-pearl-publish--in-buffer
'(:type filter :name "Mine" :filter (:open t))
- (pearl-publish-current-source))
+ (pearl-publish-current-view))
(should (equal "Mine" sync-called-with)))))
(ert-deftest test-pearl-publish-current-source-empty-name-errors ()
"A :type filter source with an empty-string :name (degenerate) errors cleanly."
(test-pearl-publish--in-buffer
'(:type filter :name "" :filter (:open t))
- (should-error (pearl-publish-current-source) :type 'user-error)))
+ (should-error (pearl-publish-current-view) :type 'user-error)))
(ert-deftest test-pearl-publish-current-source-whitespace-name-errors ()
"A :type filter source with a whitespace-only :name takes the no-name branch
rather than falling through to the no-matching-saved-query branch with a
visibly-blank name in the error message."
- (let ((pearl-saved-queries '()))
+ (let ((pearl-local-views '()))
(test-pearl-publish--in-buffer
'(:type filter :name " " :filter (:open t))
- (let ((err (should-error (pearl-publish-current-source)
+ (let ((err (should-error (pearl-publish-current-view)
:type 'user-error)))
;; The message should name the source-has-no-name reason, not the
;; no-matching-query reason.
@@ -609,7 +609,7 @@ team-key lookups."
(pearl--customview-delete-async "view-uuid" (lambda (r) (setq result r)))
(should-not (plist-get result :success)))))
-;;; pearl-delete-saved-query — synced-entry second-stage prompt
+;;; pearl-delete-local-view — synced-entry second-stage prompt
(defun test-pearl--make-yes-no-stub (answers)
"Return a `yes-or-no-p' replacement that returns each ANSWERS in order.
@@ -623,7 +623,7 @@ more prompts fire than answers were prepared for."
(ert-deftest test-pearl-delete-saved-query-synced-yes-yes-calls-customview-delete ()
"Synced entry, both prompts yes: customViewDelete fires and the local entry is removed."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "view-uuid"
:linear-view-team-id nil
@@ -638,13 +638,13 @@ more prompts fire than answers were prepared for."
(lambda (vid cb)
(setq delete-called-with vid)
(funcall cb (list :success t)))))
- (pearl-delete-saved-query "Q")
+ (pearl-delete-local-view "Q")
(should (equal "view-uuid" delete-called-with))
- (should-not (assoc "Q" pearl-saved-queries)))))
+ (should-not (assoc "Q" pearl-local-views)))))
(ert-deftest test-pearl-delete-saved-query-synced-yes-no-unlinks-only ()
"Synced entry, first prompt yes second prompt no: local entry removed, customViewDelete not called."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "view-uuid"))))
(delete-called nil))
@@ -655,13 +655,13 @@ more prompts fire than answers were prepared for."
((symbol-function 'message) (lambda (&rest _) nil))
((symbol-function 'pearl--customview-delete-async)
(lambda (_ _) (setq delete-called t))))
- (pearl-delete-saved-query "Q")
+ (pearl-delete-local-view "Q")
(should-not delete-called)
- (should-not (assoc "Q" pearl-saved-queries)))))
+ (should-not (assoc "Q" pearl-local-views)))))
(ert-deftest test-pearl-delete-saved-query-synced-api-failure-then-delete-anyway ()
"Synced entry, customViewDelete fails, user accepts delete-anyway prompt: local removed, message names the orphan id."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "ORPHAN-UUID"))))
(last-message nil))
@@ -675,8 +675,8 @@ more prompts fire than answers were prepared for."
((symbol-function 'pearl--customview-delete-async)
(lambda (_ cb)
(funcall cb (list :success nil :error "permission denied")))))
- (pearl-delete-saved-query "Q")
- (should-not (assoc "Q" pearl-saved-queries))
+ (pearl-delete-local-view "Q")
+ (should-not (assoc "Q" pearl-local-views))
(should (string-match-p "ORPHAN-UUID" last-message))
(should (string-match-p "orphan" last-message)))))
@@ -685,7 +685,7 @@ more prompts fire than answers were prepared for."
Verifies the API call actually fires (so the keep branch only runs when the
delete-anyway prompt explicitly declines), guarding against a future refactor
that silently routes to the unlink branch and skips the API call."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)
:linear-view-id "view-uuid"))))
(delete-called-with nil))
@@ -698,13 +698,13 @@ that silently routes to the unlink branch and skips the API call."
(lambda (vid cb)
(setq delete-called-with vid)
(funcall cb (list :success nil :error "permission denied")))))
- (pearl-delete-saved-query "Q")
+ (pearl-delete-local-view "Q")
(should (equal "view-uuid" delete-called-with))
- (should (assoc "Q" pearl-saved-queries)))))
+ (should (assoc "Q" pearl-local-views)))))
(ert-deftest test-pearl-delete-saved-query-local-only-entry-unchanged-behavior ()
"Local-only entry (no `:linear-view-id'): one yes-or-no-p, no second prompt, no API call."
- (let ((pearl-saved-queries
+ (let ((pearl-local-views
(copy-tree '(("Q" :filter (:open t)))))
(delete-called nil)
(prompt-count 0))
@@ -714,10 +714,10 @@ that silently routes to the unlink branch and skips the API call."
((symbol-function 'message) (lambda (&rest _) nil))
((symbol-function 'pearl--customview-delete-async)
(lambda (_ _) (setq delete-called t))))
- (pearl-delete-saved-query "Q")
+ (pearl-delete-local-view "Q")
(should (= 1 prompt-count))
(should-not delete-called)
- (should-not (assoc "Q" pearl-saved-queries)))))
+ (should-not (assoc "Q" pearl-local-views)))))
(provide 'test-pearl-saved-query-sync)
;;; test-pearl-saved-query-sync.el ends here