;;; test-pearl-default-view.el --- Tests for the configurable default view -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Craig Jennings ;; Author: Craig Jennings ;; 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 . ;;; Commentary: ;; Unit tests for the default-view resolver and the `pearl-open-default-view' ;; dispatcher (phase 1 of docs/default-view-spec.org). The resolver returns ;; the active scope's default -- `pearl-default-view' in legacy mode, the ;; active account's `:default-view' in accounts mode. The open command ;; dispatches nil -> my open issues, a known local-view name -> that view, and ;; a stale name -> my open issues with a message (never an error). The two ;; fetch commands are stubbed so no network is touched. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el")) ;;; pearl--resolve-default-view -- scope resolution (ert-deftest test-pearl-resolve-default-view-legacy-nil () "Legacy mode, no default set: the resolver returns nil." (let ((pearl-accounts nil) (pearl-default-view nil)) (should-not (pearl--resolve-default-view)))) (ert-deftest test-pearl-resolve-default-view-legacy-name () "Legacy mode with a default set: the resolver returns the local-view name." (let ((pearl-accounts nil) (pearl-default-view "My active work")) (should (string-equal "My active work" (pearl--resolve-default-view))))) (ert-deftest test-pearl-resolve-default-view-accounts-reads-active () "Accounts mode: the resolver returns the *active* account's :default-view." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org" :default-view "Work board") ("home" :api-key-source (:literal "kh") :org-file "/tmp/h.org" :default-view "Home triage"))) (pearl-default-account nil) (pearl-active-account "work")) (should (string-equal "Work board" (pearl--resolve-default-view))))) (ert-deftest test-pearl-resolve-default-view-accounts-switch () "Accounts mode: switching the active account switches the resolved default." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org" :default-view "Work board") ("home" :api-key-source (:literal "kh") :org-file "/tmp/h.org" :default-view "Home triage"))) (pearl-default-account nil) (pearl-active-account "home")) (should (string-equal "Home triage" (pearl--resolve-default-view))))) (ert-deftest test-pearl-resolve-default-view-accounts-unset () "Accounts mode, active account has no :default-view: the resolver returns nil. The legacy `pearl-default-view' does not leak in when accounts are configured." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org"))) (pearl-default-account nil) (pearl-active-account "work") (pearl-default-view "Legacy default")) (should-not (pearl--resolve-default-view)))) (ert-deftest test-pearl-resolve-account-carries-default-view () "`pearl--resolve-account' surfaces the account's :default-view in its context." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org" :default-view "Work board")))) (should (string-equal "Work board" (plist-get (pearl--resolve-account "work") :default-view))))) ;;; pearl-open-default-view -- dispatch (ert-deftest test-pearl-open-default-view-nil-runs-my-open-issues () "An unset default dispatches to `pearl-list-issues' (my open issues)." (let ((pearl-accounts nil) (pearl-default-view nil) (calls nil)) (cl-letf (((symbol-function 'pearl-list-issues) (lambda (&rest args) (push (cons 'list args) calls))) ((symbol-function 'pearl-run-local-view) (lambda (name) (push (cons 'view name) calls)))) (pearl-open-default-view) (should (equal calls '((list))))))) (ert-deftest test-pearl-open-default-view-name-runs-local-view () "A default naming an existing local view dispatches to `pearl-run-local-view'." (let ((pearl-accounts nil) (pearl-default-view "My active work") (pearl-local-views '(("My active work" :filter (:assignee :me :open t)))) (calls nil)) (cl-letf (((symbol-function 'pearl-list-issues) (lambda (&rest args) (push (cons 'list args) calls))) ((symbol-function 'pearl-run-local-view) (lambda (name) (push (cons 'view name) calls)))) (pearl-open-default-view) (should (equal calls '((view . "My active work"))))))) (ert-deftest test-pearl-open-default-view-stale-name-falls-back () "A default naming a deleted local view falls back to my open issues, no error. It dispatches to `pearl-list-issues' and never calls `pearl-run-local-view'." (let ((pearl-accounts nil) (pearl-default-view "Deleted view") (pearl-local-views '(("Some other view" :filter (:open t)))) (calls nil) (messaged nil)) (cl-letf (((symbol-function 'pearl-list-issues) (lambda (&rest args) (push (cons 'list args) calls))) ((symbol-function 'pearl-run-local-view) (lambda (name) (push (cons 'view name) calls))) ((symbol-function 'message) (lambda (&rest args) (setq messaged (or messaged args))))) (pearl-open-default-view) (should (equal calls '((list)))) (should messaged)))) ;;; pearl--persist-default-view -- scope targeting + metadata preservation (ert-deftest test-pearl-persist-default-view-legacy () "Legacy mode: persisting writes `pearl-default-view' and saves it." (let ((pearl-accounts nil) (pearl-default-view nil) (saved nil)) (cl-letf (((symbol-function 'customize-save-variable) (lambda (sym val) (setq saved (cons sym val))))) (pearl--persist-default-view "My active work") (should (string-equal "My active work" pearl-default-view)) (should (equal saved '(pearl-default-view . "My active work")))))) (ert-deftest test-pearl-persist-default-view-legacy-clear () "Legacy mode: persisting nil clears the default back to my open issues." (let ((pearl-accounts nil) (pearl-default-view "Something") (saved nil)) (cl-letf (((symbol-function 'customize-save-variable) (lambda (sym val) (setq saved (cons sym val))))) (pearl--persist-default-view nil) (should-not pearl-default-view) (should (equal saved '(pearl-default-view)))))) (ert-deftest test-pearl-persist-default-view-accounts-targets-active () "Accounts mode: persisting updates only the active account's :default-view." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org" :default-team-id "team_w") ("home" :api-key-source (:literal "kh") :org-file "/tmp/h.org"))) (pearl-default-account nil) (pearl-active-account "work") (saved nil)) (cl-letf (((symbol-function 'customize-save-variable) (lambda (sym val) (setq saved (cons sym val))))) (pearl--persist-default-view "Work board") ;; active account got the key (should (string-equal "Work board" (plist-get (cdr (assoc "work" pearl-accounts)) :default-view))) ;; the other account is untouched (should-not (plist-get (cdr (assoc "home" pearl-accounts)) :default-view)) ;; pearl-accounts (not the legacy global) was the saved variable (should (eq 'pearl-accounts (car saved)))))) (ert-deftest test-pearl-persist-default-view-accounts-preserves-keys () "Accounts mode: the rewrite preserves the active account's other plist keys." (let ((pearl-accounts '(("work" :api-key-source (:literal "kw") :org-file "/tmp/w.org" :default-team-id "team_w" :url "https://example/api"))) (pearl-default-account nil) (pearl-active-account "work")) (cl-letf (((symbol-function 'customize-save-variable) (lambda (&rest _) nil))) (pearl--persist-default-view "Work board") (let ((spec (cdr (assoc "work" pearl-accounts)))) (should (equal '(:literal "kw") (plist-get spec :api-key-source))) (should (string-equal "/tmp/w.org" (plist-get spec :org-file))) (should (string-equal "team_w" (plist-get spec :default-team-id))) (should (string-equal "https://example/api" (plist-get spec :url))) (should (string-equal "Work board" (plist-get spec :default-view))))))) ;;; pearl-set-default-view -- value determination paths (ert-deftest test-pearl-set-default-view-from-buffer-offer () "From a local-view buffer, accepting the offer persists that view's name." (let ((pearl-accounts nil) (pearl-default-view nil) (pearl-local-views '(("Sprint board" :filter (:open t)))) (persisted 'untouched)) (cl-letf (((symbol-function 'pearl--read-active-source) (lambda () '(:type filter :name "Sprint board" :filter (:open t)))) ((symbol-function 'pearl--read-yes-no) (lambda (&rest _) t)) ((symbol-function 'pearl--persist-default-view) (lambda (v) (setq persisted v)))) (pearl-set-default-view) (should (string-equal "Sprint board" persisted))))) (ert-deftest test-pearl-set-default-view-prompt-pick-name () "Not on a local-view buffer: the prompt's chosen name is persisted." (let ((pearl-accounts nil) (pearl-default-view nil) (pearl-local-views '(("Sprint board" :filter (:open t)))) (persisted 'untouched)) (cl-letf (((symbol-function 'pearl--read-active-source) (lambda () nil)) ((symbol-function 'completing-read) (lambda (&rest _) "Sprint board")) ((symbol-function 'pearl--persist-default-view) (lambda (v) (setq persisted v)))) (pearl-set-default-view) (should (string-equal "Sprint board" persisted))))) (ert-deftest test-pearl-set-default-view-clear-sentinel-sets-nil () "Choosing the my-open-issues sentinel persists nil (clears the default)." (let ((pearl-accounts nil) (pearl-default-view "Sprint board") (pearl-local-views '(("Sprint board" :filter (:open t)))) (persisted 'untouched)) (cl-letf (((symbol-function 'pearl--read-active-source) (lambda () nil)) ((symbol-function 'completing-read) (lambda (&rest _) pearl--default-view-clear)) ((symbol-function 'pearl--persist-default-view) (lambda (v) (setq persisted v)))) (pearl-set-default-view) (should (null persisted))))) (ert-deftest test-pearl-set-default-view-cancel-leaves-unchanged () "An empty / quit choice persists nothing -- the default is left as-is." (let ((pearl-accounts nil) (pearl-default-view "Sprint board") (pearl-local-views '(("Sprint board" :filter (:open t)))) (persisted 'untouched)) (cl-letf (((symbol-function 'pearl--read-active-source) (lambda () nil)) ((symbol-function 'completing-read) (lambda (&rest _) "")) ((symbol-function 'pearl--persist-default-view) (lambda (v) (setq persisted v)))) (pearl-set-default-view) (should (eq 'untouched persisted))))) (provide 'test-pearl-default-view) ;;; test-pearl-default-view.el ends here