aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-24 16:48:45 -0500
committerCraig Jennings <c@cjennings.net>2026-07-24 16:48:45 -0500
commit01e1793831ee2a80a20e266ac3b0b950b71df240 (patch)
tree0afacf5c58cc943d007189eac8204560a2af1e91 /tests
parent6a88b2618835937fa4a508f55e2454ac16d1f408 (diff)
downloaddotemacs-01e1793831ee2a80a20e266ac3b0b950b71df240.tar.gz
dotemacs-01e1793831ee2a80a20e266ac3b0b950b71df240.zip
fix(agenda-frame): keep the allowlist when org-agenda loads first
- The read-only shadow walk keeps a key only when the frame map binds it commandp. - It ran from with-eval-after-load partway up the file. - The day/week/redo handlers are defined further down. - With org-agenda already loaded, the walk fired before those defuns existed. - So it read them as non-commands and denied d/w/g/r — the keys the allowlist grants. - That is the normal startup order and every module reload. - Moved the walk to the end of the file, after all the handlers are defined. - A subprocess test loads org-agenda first and pins d/w/g/r surviving, t denied. - Retitled the t-test to assert the deny outcome, not the pre-walk unbound state.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-integration-org-agenda-frame-load-order.el80
-rw-r--r--tests/test-org-agenda-frame.el13
2 files changed, 89 insertions, 4 deletions
diff --git a/tests/test-integration-org-agenda-frame-load-order.el b/tests/test-integration-org-agenda-frame-load-order.el
new file mode 100644
index 00000000..6541d250
--- /dev/null
+++ b/tests/test-integration-org-agenda-frame-load-order.el
@@ -0,0 +1,80 @@
+;;; test-integration-org-agenda-frame-load-order.el --- Frame allowlist survives load order -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Regression test for a load-order bug in the Full Agenda frame's read-only
+;; shadow.
+;;
+;; Components integrated:
+;; - org-agenda-frame (real, loaded in a subprocess)
+;; - org-agenda (real, loaded BEFORE the frame module to reproduce the bug)
+;;
+;; The bug: cj/--agenda-frame-shadow-mutations walks org-agenda-mode-map and
+;; keeps a key only when the frame map already binds it to a `commandp' value.
+;; The view/redo handlers (day-view, week-view, safe-redo) were defined LOWER in
+;; the file than the `with-eval-after-load' that ran the walk. When org-agenda
+;; was already loaded at frame-load time -- the normal startup order and every
+;; reload -- the walk fired before those defuns existed, read them as not-yet
+;; commands, and denied d/w/g/r, the very keys the allowlist grants. Moving the
+;; walk to the end of the file (after the defuns) fixed it.
+;;
+;; This test can't reproduce the ordering in-process (the module is already
+;; loaded), so it drives a fresh Emacs that requires org-agenda first, then the
+;; frame module, and inspects the resulting keymap.
+;;
+;; Validates:
+;; - d/w/g/r keep their allowlist commands in the org-first load order
+;; - a real mutation key (t) is still denied (the read-only guarantee holds)
+;;
+;;; Code:
+
+(require 'ert)
+
+(defconst test-oaf--repo-root
+ (file-name-directory (directory-file-name
+ (file-name-directory (or load-file-name buffer-file-name))))
+ "Repo root, one level up from tests/.")
+
+(defun test-oaf--lookup-in-subprocess (keys)
+ "Load org-agenda then org-agenda-frame in a fresh Emacs, return KEYS' bindings.
+Returns an alist of (KEY . BINDING-SYMBOL-NAME-OR-nil)."
+ (let* ((root test-oaf--repo-root)
+ (form
+ (prin1-to-string
+ `(progn
+ (setq load-prefer-newer t)
+ (package-initialize)
+ (require 'org-agenda) ; the bad order: org first
+ (require 'org-agenda-frame)
+ (princ (prin1-to-string
+ (mapcar
+ (lambda (k)
+ (cons k (let ((b (lookup-key cj/agenda-frame-mode-map (kbd k))))
+ (and (symbolp b) (symbol-name b)))))
+ ',keys))))))
+ (out (with-output-to-string
+ (with-current-buffer standard-output
+ (call-process
+ (expand-file-name invocation-name invocation-directory)
+ nil t nil
+ "--batch" "--no-site-file" "--no-site-lisp"
+ "-L" root
+ "-L" (expand-file-name "modules" root)
+ "-L" (expand-file-name "themes" root)
+ "--eval" form)))))
+ (car (read-from-string out))))
+
+(ert-deftest test-integration-org-agenda-frame-allowlist-survives-org-first-load ()
+ "Integration: with org-agenda loaded before the frame module, the allowlisted
+view/redo keys keep their commands and a mutation key stays denied."
+ (skip-unless (file-exists-p (expand-file-name "modules/org-agenda-frame.el"
+ test-oaf--repo-root)))
+ (let ((got (test-oaf--lookup-in-subprocess '("d" "w" "g" "r" "t"))))
+ (should (equal "cj/--agenda-frame-day-view" (cdr (assoc "d" got))))
+ (should (equal "cj/--agenda-frame-week-view" (cdr (assoc "w" got))))
+ (should (equal "cj/--agenda-frame-safe-redo" (cdr (assoc "g" got))))
+ (should (equal "cj/--agenda-frame-safe-redo" (cdr (assoc "r" got))))
+ ;; t is a real org mutation key; it must be denied, not allowlisted.
+ (should (equal "cj/--agenda-frame-denied-readonly" (cdr (assoc "t" got))))))
+
+(provide 'test-integration-org-agenda-frame-load-order)
+;;; test-integration-org-agenda-frame-load-order.el ends here
diff --git a/tests/test-org-agenda-frame.el b/tests/test-org-agenda-frame.el
index fd8bd839..3c56d361 100644
--- a/tests/test-org-agenda-frame.el
+++ b/tests/test-org-agenda-frame.el
@@ -418,10 +418,15 @@ removed after a later success -- the failure banner would stick forever."
(should (= 0 (seq-count (lambda (o) (overlay-get o 'before-string))
(overlays-in (point-min) (point-max)))))))
-(ert-deftest test-org-agenda-frame-map-mutation-keys-not-explicitly-bound ()
- "Boundary: a mutation key (t = org-agenda-todo) is not explicitly bound, so the
-[t] catch-all denies it as read-only."
- (should (null (lookup-key cj/agenda-frame-mode-map (kbd "t")))))
+(ert-deftest test-org-agenda-frame-map-mutation-keys-denied ()
+ "Boundary: a mutation key (t = org-agenda-todo) is denied, never allowlisted.
+It is denied two ways depending on whether the shadow walk has run: the `[t]'
+catch-all handles it (lookup returns nil) before the walk, and the walk binds
+it explicitly to the deny handler once `org-agenda-mode-map' is present. Both
+are a read-only denial; the test asserts the outcome, not which path produced
+it, so it holds whether or not org-agenda is loaded in the test process."
+ (let ((b (lookup-key cj/agenda-frame-mode-map (kbd "t"))))
+ (should (or (null b) (eq b 'cj/--agenda-frame-denied-readonly)))))
;;; Default-deny policy — the minor mode + finalize re-enable