aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-20 12:06:37 -0400
committerCraig Jennings <c@cjennings.net>2026-06-20 12:06:37 -0400
commit2b257d426f5e98219cdd6ac191d259b74a31d87e (patch)
tree6a8c62ec5231a679d4faa10fc187795d5f00f547
parentd96a162db3be25cb812d08a10c831baa2790c492 (diff)
downloaddotemacs-2b257d426f5e98219cdd6ac191d259b74a31d87e.tar.gz
dotemacs-2b257d426f5e98219cdd6ac191d259b74a31d87e.zip
refactor(mail-config): build the account-nav keymaps from one template
The cmail/dmail/gmail navigation maps were three near-identical defvar-keymap blocks differing only by maildir prefix, with the unread/flagged/large query clauses repeated in each. Add cj/--mail-account-search-queries (account -> the four search strings) and cj/--mail-make-account-map (builds the keymap), wrapped in eval-and-compile so org-msg's :preface can call the builder during byte-compilation. The three maps become one-line builder calls. Adds direct coverage of the query strings and the per-account closures.
-rw-r--r--modules/mail-config.el52
-rw-r--r--tests/test-mail-config--account-search-queries.el53
2 files changed, 87 insertions, 18 deletions
diff --git a/modules/mail-config.el b/modules/mail-config.el
index 1ec41f213..08f50b12f 100644
--- a/modules/mail-config.el
+++ b/modules/mail-config.el
@@ -417,6 +417,34 @@ Prompts user for the action when executing."
(cj/activate-mu4e-org-contacts-integration)) ;; end use-package mu4e
+;; ----------------------- Account Navigation Keymaps --------------------------
+;; The C-; e c/d/g submaps jump to each account's inbox views. Built from one
+;; template so the maildir prefix is the only per-account difference.
+
+;; eval-and-compile so the builder is defined when org-msg's :preface (below)
+;; calls it during byte-compilation, not only at load.
+(eval-and-compile
+ (defun cj/--mail-account-search-queries (account)
+ "Return an alist of (KEY . QUERY) mu4e searches for ACCOUNT's inbox.
+ACCOUNT is the maildir account name (\"cmail\", \"dmail\", \"gmail\"). The four
+entries scope inbox / unread / flagged / large searches to that account's
+INBOX maildir."
+ (let ((base (format "maildir:/%s/INBOX" account)))
+ (list (cons "i" base)
+ (cons "u" (concat base " AND flag:unread AND NOT flag:trashed"))
+ (cons "s" (concat base " AND flag:flagged"))
+ (cons "l" (concat base " AND size:5M..999M")))))
+
+ (defun cj/--mail-make-account-map (account)
+ "Build a mu4e navigation keymap for ACCOUNT (a maildir account name).
+Keys i/u/s/l run the inbox/unread/flagged/large searches from
+`cj/--mail-account-search-queries', each scoped to ACCOUNT."
+ (let ((map (make-sparse-keymap)))
+ (dolist (entry (cj/--mail-account-search-queries account) map)
+ (let ((query (cdr entry)))
+ (keymap-set map (car entry)
+ (lambda () (interactive) (mu4e-search query))))))))
+
;; ---------------------------------- Org-Msg ----------------------------------
;; user composes org mode; recipient receives html
@@ -425,24 +453,12 @@ Prompts user for the action when executing."
:defer 1
:after (org mu4e)
:preface
- (defvar-keymap cj/mail-cmail-map
- :doc "cmail account navigation"
- "i" (lambda () (interactive) (mu4e-search "maildir:/cmail/INBOX"))
- "u" (lambda () (interactive) (mu4e-search "maildir:/cmail/INBOX AND flag:unread AND NOT flag:trashed"))
- "s" (lambda () (interactive) (mu4e-search "maildir:/cmail/INBOX AND flag:flagged"))
- "l" (lambda () (interactive) (mu4e-search "maildir:/cmail/INBOX AND size:5M..999M")))
- (defvar-keymap cj/mail-dmail-map
- :doc "deepsat account navigation"
- "i" (lambda () (interactive) (mu4e-search "maildir:/dmail/INBOX"))
- "u" (lambda () (interactive) (mu4e-search "maildir:/dmail/INBOX AND flag:unread AND NOT flag:trashed"))
- "s" (lambda () (interactive) (mu4e-search "maildir:/dmail/INBOX AND flag:flagged"))
- "l" (lambda () (interactive) (mu4e-search "maildir:/dmail/INBOX AND size:5M..999M")))
- (defvar-keymap cj/mail-gmail-map
- :doc "gmail account navigation"
- "i" (lambda () (interactive) (mu4e-search "maildir:/gmail/INBOX"))
- "u" (lambda () (interactive) (mu4e-search "maildir:/gmail/INBOX AND flag:unread AND NOT flag:trashed"))
- "s" (lambda () (interactive) (mu4e-search "maildir:/gmail/INBOX AND flag:flagged"))
- "l" (lambda () (interactive) (mu4e-search "maildir:/gmail/INBOX AND size:5M..999M")))
+ (defvar cj/mail-cmail-map (cj/--mail-make-account-map "cmail")
+ "cmail account navigation.")
+ (defvar cj/mail-dmail-map (cj/--mail-make-account-map "dmail")
+ "deepsat account navigation.")
+ (defvar cj/mail-gmail-map (cj/--mail-make-account-map "gmail")
+ "gmail account navigation.")
(defvar-keymap cj/email-map
:doc "Email operations and account navigation"
"A" #'org-msg-attach-attach
diff --git a/tests/test-mail-config--account-search-queries.el b/tests/test-mail-config--account-search-queries.el
new file mode 100644
index 000000000..9f1b6b3e6
--- /dev/null
+++ b/tests/test-mail-config--account-search-queries.el
@@ -0,0 +1,53 @@
+;;; test-mail-config--account-search-queries.el --- Tests for the mail account-nav helpers -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; cj/--mail-account-search-queries (pure: account name -> the four mu4e search
+;; strings) and cj/--mail-make-account-map (builds the per-account nav keymap)
+;; replace three near-identical defvar-keymap blocks that differed only by
+;; maildir prefix. The map test invokes each binding with mu4e-search mocked,
+;; which also verifies each loop-built closure captured its own query.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'mail-config)
+
+(ert-deftest test-mail-account-search-queries-cmail ()
+ "Normal: the four searches are scoped to the account's INBOX maildir."
+ (should (equal (cj/--mail-account-search-queries "cmail")
+ '(("i" . "maildir:/cmail/INBOX")
+ ("u" . "maildir:/cmail/INBOX AND flag:unread AND NOT flag:trashed")
+ ("s" . "maildir:/cmail/INBOX AND flag:flagged")
+ ("l" . "maildir:/cmail/INBOX AND size:5M..999M")))))
+
+(ert-deftest test-mail-account-search-queries-prefix-varies ()
+ "Boundary: only the maildir prefix changes between accounts."
+ (should (equal (cdr (assoc "i" (cj/--mail-account-search-queries "dmail")))
+ "maildir:/dmail/INBOX"))
+ (should (equal (cdr (assoc "i" (cj/--mail-account-search-queries "gmail")))
+ "maildir:/gmail/INBOX")))
+
+(ert-deftest test-mail-make-account-map-binds-four-keys ()
+ "Normal: the built keymap binds i/u/s/l to commands."
+ (let ((map (cj/--mail-make-account-map "cmail")))
+ (dolist (key '("i" "u" "s" "l"))
+ (should (commandp (keymap-lookup map key))))))
+
+(ert-deftest test-mail-make-account-map-closures-capture-distinct-queries ()
+ "Normal: each binding runs its own account-scoped search (no closure leak).
+mu4e-search is mocked to capture the query each command passes."
+ (let ((searched '()))
+ (cl-letf (((symbol-function 'mu4e-search)
+ (lambda (q) (push q searched))))
+ (let ((map (cj/--mail-make-account-map "dmail")))
+ (funcall (keymap-lookup map "i"))
+ (funcall (keymap-lookup map "u"))))
+ (should (member "maildir:/dmail/INBOX" searched))
+ (should (member "maildir:/dmail/INBOX AND flag:unread AND NOT flag:trashed"
+ searched))))
+
+(provide 'test-mail-config--account-search-queries)
+;;; test-mail-config--account-search-queries.el ends here