aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test-pearl-adhoc.el14
-rw-r--r--tests/test-pearl-local-views.el171
2 files changed, 178 insertions, 7 deletions
diff --git a/tests/test-pearl-adhoc.el b/tests/test-pearl-adhoc.el
index 4959840..68a920c 100644
--- a/tests/test-pearl-adhoc.el
+++ b/tests/test-pearl-adhoc.el
@@ -155,22 +155,22 @@ candidate list, so completion still works the way the framework expects."
(should (string= "user-1" (plist-get f :assignee-id)))
(should-not (plist-member f :assignee))))
-;;; --save-query
+;;; --save-local-view
-(ert-deftest test-pearl-save-query-adds-entry ()
- "Saving a query adds it to the saved-queries variable."
+(ert-deftest test-pearl-save-local-view-adds-entry ()
+ "Saving a local view adds it to `pearl-local-views'."
(let ((pearl-local-views nil))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (&rest _) nil)))
- (pearl--save-query "My filter" '(:open t :labels ("bug")))
+ (pearl--save-local-view "My filter" '(:open t :labels ("bug")))
(let ((entry (assoc "My filter" pearl-local-views)))
(should entry)
(should (equal '(:open t :labels ("bug")) (plist-get (cdr entry) :filter)))))))
-(ert-deftest test-pearl-save-query-replaces-same-name ()
- "Saving under an existing name replaces that entry rather than duplicating."
+(ert-deftest test-pearl-save-local-view-replaces-same-name ()
+ "Saving under an existing name updates that entry rather than duplicating."
(let ((pearl-local-views '(("Dup" :filter (:open t)))))
(cl-letf (((symbol-function 'customize-save-variable) (lambda (&rest _) nil)))
- (pearl--save-query "Dup" '(:priority 1))
+ (pearl--save-local-view "Dup" '(:priority 1))
(should (= 1 (cl-count "Dup" pearl-local-views
:key #'car :test #'string=)))
(should (equal '(:priority 1)
diff --git a/tests/test-pearl-local-views.el b/tests/test-pearl-local-views.el
new file mode 100644
index 0000000..71fb173
--- /dev/null
+++ b/tests/test-pearl-local-views.el
@@ -0,0 +1,171 @@
+;;; test-pearl-local-views.el --- Tests for local-view create/edit/persistence -*- 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:
+
+;; Phase 2 of the local/Linear views spec: the metadata-preserving writer
+;; `pearl--save-local-view', the shared account guard
+;; `pearl--require-local-view-account', and the collision resolver
+;; `pearl--resolve-local-view-name'. The load-bearing guarantee is that
+;; editing a published local view never drops its `:linear-view-*' tracking
+;; link.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+
+(defmacro test-pearl-lv--with-store (initial &rest body)
+ "Run BODY with `pearl-local-views' set to INITIAL and Customize persistence stubbed."
+ (declare (indent 1))
+ `(let ((pearl-local-views ,initial))
+ (cl-letf (((symbol-function 'customize-save-variable) (lambda (&rest _) nil)))
+ ,@body)))
+
+;;; pearl--save-local-view -- metadata preservation
+
+(ert-deftest test-pearl-save-local-view-creates-new-entry ()
+ "A new name is saved with its filter and no metadata stamped in legacy mode."
+ (test-pearl-lv--with-store nil
+ (let ((pearl-accounts nil))
+ (pearl--save-local-view "bugs" '(:labels ("bug") :open t))
+ (let ((spec (cdr (assoc "bugs" pearl-local-views))))
+ (should (equal (plist-get spec :filter) '(:labels ("bug") :open t)))
+ (should-not (plist-member spec :account))))))
+
+(ert-deftest test-pearl-save-local-view-preserves-tracking-metadata ()
+ "Re-saving a published local view keeps its :linear-view-* and provenance keys."
+ (test-pearl-lv--with-store
+ '(("team open"
+ :filter (:team "ENG" :open t)
+ :linear-view-id "view-123"
+ :linear-view-team-id "team-1"
+ :linear-view-shared t
+ :linear-view-url "https://linear.app/x/view/view-123"
+ :linear-view-synced-at "2026-06-01"
+ :account "acct-a"
+ :copied-from-view-id "src-9"))
+ (pearl--save-local-view "team open" '(:team "ENG" :state "In Progress"))
+ (let ((spec (cdr (assoc "team open" pearl-local-views))))
+ ;; the edited field changed
+ (should (equal (plist-get spec :filter) '(:team "ENG" :state "In Progress")))
+ ;; every other key survived
+ (should (equal (plist-get spec :linear-view-id) "view-123"))
+ (should (equal (plist-get spec :linear-view-team-id) "team-1"))
+ (should (eq (plist-get spec :linear-view-shared) t))
+ (should (equal (plist-get spec :linear-view-url)
+ "https://linear.app/x/view/view-123"))
+ (should (equal (plist-get spec :linear-view-synced-at) "2026-06-01"))
+ (should (equal (plist-get spec :account) "acct-a"))
+ (should (equal (plist-get spec :copied-from-view-id) "src-9")))))
+
+(ert-deftest test-pearl-save-local-view-updates-sort-order-keeping-rest ()
+ "Sort/order update in place; an unrelated key is preserved."
+ (test-pearl-lv--with-store
+ '(("q" :filter (:open t) :sort updated :order desc :copied-from-view-id "s"))
+ (pearl--save-local-view "q" '(:open t) 'priority 'asc)
+ (let ((spec (cdr (assoc "q" pearl-local-views))))
+ (should (eq (plist-get spec :sort) 'priority))
+ (should (eq (plist-get spec :order) 'asc))
+ (should (equal (plist-get spec :copied-from-view-id) "s")))))
+
+(ert-deftest test-pearl-save-local-view-stamps-account-when-accounts-active ()
+ "A new entry created under `pearl-accounts' is stamped with the active account."
+ (test-pearl-lv--with-store nil
+ (let ((pearl-accounts '(("acct-a" . ignored))))
+ (cl-letf (((symbol-function 'pearl--current-account-context)
+ (lambda () '(:name "acct-a"))))
+ (pearl--save-local-view "mine" '(:assignee :me :open t))
+ (should (equal (plist-get (cdr (assoc "mine" pearl-local-views)) :account)
+ "acct-a"))))))
+
+;;; pearl--require-local-view-account
+
+(ert-deftest test-pearl-require-account-legacy-is-inert ()
+ "With no `pearl-accounts', the guard returns nil and never errors."
+ (let ((pearl-accounts nil))
+ (should-not (pearl--require-local-view-account "q" '(:filter (:open t))))))
+
+(ert-deftest test-pearl-require-account-matching-passes ()
+ "A view tagged to the active account passes and returns the active name."
+ (let ((pearl-accounts '(("acct-a" . x))))
+ (cl-letf (((symbol-function 'pearl--current-account-context)
+ (lambda () '(:name "acct-a"))))
+ (should (equal (pearl--require-local-view-account
+ "q" '(:account "acct-a" :filter (:open t)))
+ "acct-a")))))
+
+(ert-deftest test-pearl-require-account-mismatch-refuses ()
+ "A view tagged to another account signals a user-error."
+ (let ((pearl-accounts '(("acct-a" . x))))
+ (cl-letf (((symbol-function 'pearl--current-account-context)
+ (lambda () '(:name "acct-a"))))
+ (should-error (pearl--require-local-view-account
+ "q" '(:account "acct-b" :filter (:open t)))
+ :type 'user-error))))
+
+;;; pearl--resolve-local-view-name -- collision policy
+
+(ert-deftest test-pearl-resolve-name-no-collision-returns-name ()
+ (test-pearl-lv--with-store '(("other" :filter (:open t)))
+ (should (equal (pearl--resolve-local-view-name "fresh") "fresh"))))
+
+(ert-deftest test-pearl-resolve-name-same-as-current-is-not-collision ()
+ (test-pearl-lv--with-store '(("q" :filter (:open t)))
+ (should (equal (pearl--resolve-local-view-name "q" "q") "q"))))
+
+(ert-deftest test-pearl-resolve-name-collision-replace ()
+ (test-pearl-lv--with-store '(("q" :filter (:open t)))
+ (cl-letf (((symbol-function 'completing-read)
+ (lambda (&rest _) "Replace")))
+ (should (equal (pearl--resolve-local-view-name "q") "q")))))
+
+(ert-deftest test-pearl-resolve-name-collision-cancel ()
+ (test-pearl-lv--with-store '(("q" :filter (:open t)))
+ (cl-letf (((symbol-function 'completing-read)
+ (lambda (&rest _) "Cancel")))
+ (should-not (pearl--resolve-local-view-name "q")))))
+
+(ert-deftest test-pearl-resolve-name-collision-rename ()
+ "Rename prompts a new name; a free name then resolves."
+ (test-pearl-lv--with-store '(("q" :filter (:open t)))
+ (cl-letf (((symbol-function 'completing-read)
+ (lambda (&rest _) "Rename"))
+ ((symbol-function 'read-string)
+ (lambda (&rest _) "q2")))
+ (should (equal (pearl--resolve-local-view-name "q") "q2")))))
+
+;;; pearl-edit-local-view -- rename carries metadata, drops the old name
+
+(ert-deftest test-pearl-edit-local-view-rename-preserves-metadata ()
+ "Renaming a published local view moves it under the new name with its link intact."
+ (test-pearl-lv--with-store
+ '(("old" :filter (:open t) :linear-view-id "v-1" :account nil))
+ (let ((pearl-accounts nil))
+ (cl-letf (((symbol-function 'pearl--read-yes-no) (lambda (&rest _) nil))
+ ((symbol-function 'completing-read) (lambda (&rest _) "keep"))
+ ((symbol-function 'read-string) (lambda (&rest _) "new")))
+ (pearl-edit-local-view "old")
+ (should-not (assoc "old" pearl-local-views))
+ (let ((spec (cdr (assoc "new" pearl-local-views))))
+ (should spec)
+ (should (equal (plist-get spec :filter) '(:open t)))
+ (should (equal (plist-get spec :linear-view-id) "v-1")))))))
+
+(provide 'test-pearl-local-views)
+;;; test-pearl-local-views.el ends here