aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-30 09:09:15 -0500
committerCraig Jennings <c@cjennings.net>2026-07-30 09:09:15 -0500
commitce223dc63c4901603e66aab0ebc8934791260c89 (patch)
tree84b7d2f2a2583f7cfc4e9090629b77e29d4f00cd /tests
parent204bc6091b76c30e68a14cd2c3b948179b27c602 (diff)
downloaddotemacs-ce223dc63c4901603e66aab0ebc8934791260c89.tar.gz
dotemacs-ce223dc63c4901603e66aab0ebc8934791260c89.zip
fix(agenda): make the project-name category actually apply
- The override never fired once since it shipped. - Its guard wanted org-category to already equal "todo". - Org leaves that nil and derives the name later. - So the condition was false in every real buffer. The guard now tests for nil, and the hook runs at depth -100. That depth is load-bearing. org-get-category resolves a deferred value the element cache then holds, so any hook reading it first freezes "todo" and leaves the override inert. add-hook prepends, and three hooks already run ahead. The old tests set org-category by hand to a value org never produces, so they passed while the feature did nothing. They now visit real files and assert on what the agenda reads. The two todo.org agenda files show their project name. Files with distinctive names keep theirs, and an explicit category still wins.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-org-agenda-config-category.el111
1 files changed, 103 insertions, 8 deletions
diff --git a/tests/test-org-agenda-config-category.el b/tests/test-org-agenda-config-category.el
index 6a54d9e6..918d342a 100644
--- a/tests/test-org-agenda-config-category.el
+++ b/tests/test-org-agenda-config-category.el
@@ -88,12 +88,11 @@ Suppresses other org-mode hooks to keep the test isolated."
(text-mode-hook nil))
(org-mode))
(setq buffer-file-name ,path)
- ;; mimic org's default category (filename-sans-extension) so the
- ;; hook's "only override the default" guard is exercised.
- (setq-local org-category
- (and ,path
- (file-name-sans-extension
- (file-name-nondirectory ,path))))
+ ;; `org-category' is deliberately left nil, which is what org actually
+ ;; does with no `#+CATEGORY:'. An earlier version of this fixture set it
+ ;; to the filename base to "mimic org's default"; org never does that, so
+ ;; these tests passed against a state that cannot occur while the feature
+ ;; did nothing in practice.
,body-form))
;;; Normal Cases
@@ -106,11 +105,14 @@ Suppresses other org-mode hooks to keep the test isolated."
(should (equal "emacs.d" org-category)))))
(ert-deftest test-org-agenda-config-category-hook-normal-leaves-inbox-alone ()
- "Normal: hook leaves inbox.org's category at its filename default."
+ "Normal: hook declines on a non-todo file, leaving `org-category' unset.
+Leaving it nil is the correct outcome, not a gap: `org-get-category' then
+derives \"inbox\" from the filename, which is already a useful label. The
+end-to-end test below checks that derivation on a real file."
(test-org-agenda-config-category--with-file "/home/cjennings/sync/org/roam/inbox.org"
(progn
(cj/--org-set-todo-category)
- (should (equal "inbox" org-category)))))
+ (should (null org-category)))))
;;; Boundary Cases
@@ -133,5 +135,98 @@ Suppresses other org-mode hooks to keep the test isolated."
;; no error and no spurious mutation
(should t)))
+;;; ---------- End-to-end: a real file through the real hook ----------
+;; The fixture above sets `org-category' by hand to the filename base, to
+;; "mimic org's default". Org does not do that: with no `#+CATEGORY:' it
+;; leaves `org-category' nil and derives the fallback inside
+;; `org-get-category' at read time. So those tests exercise a precondition
+;; that never occurs, and passed while the feature did nothing in practice.
+;;
+;; These drive a real file on disk through `find-file-noselect' (which runs
+;; `org-mode-hook' for real) and assert on `org-get-category', which is what
+;; the agenda's %c column actually reads.
+
+(defmacro test-org-agenda-config-category--with-real-file (spec &rest body)
+ "Create FILE under a temp project dir and visit it, then run BODY.
+SPEC is (VAR DIRNAME FILENAME CONTENT). VAR is bound to the live buffer."
+ (declare (indent 1))
+ (let ((var (nth 0 spec)) (dirname (nth 1 spec))
+ (filename (nth 2 spec)) (content (nth 3 spec)))
+ ;; The outer `unwind-protect' covers `find-file-noselect' itself, so a
+ ;; signal there still removes the temp tree rather than leaking it.
+ `(let* ((root (make-temp-file "cj-cat-" t))
+ (project (expand-file-name ,dirname root))
+ (path (expand-file-name ,filename project))
+ (,var nil))
+ (unwind-protect
+ (progn
+ (make-directory project t)
+ (with-temp-file path (insert ,content))
+ (setq ,var (find-file-noselect path))
+ ,@body)
+ (when (buffer-live-p ,var)
+ (with-current-buffer ,var (set-buffer-modified-p nil))
+ (kill-buffer ,var))
+ (delete-directory root t)))))
+
+(ert-deftest test-org-agenda-config-category-endtoend-todo-shows-project ()
+ "Normal: visiting a project's todo.org makes the agenda show the project.
+This is the whole point of the feature, and it is what the hand-built
+fixture above could not check."
+ (test-org-agenda-config-category--with-real-file
+ (buffer "myproject" "todo.org" "* TODO a task\n")
+ (with-current-buffer buffer
+ (should (equal "myproject" (org-get-category (point-min)))))))
+
+(ert-deftest test-org-agenda-config-category-endtoend-explicit-category-wins ()
+ "Boundary: an explicit `#+CATEGORY:' still beats the derived name.
+
+Note what this does and does not prove. It confirms the user-visible contract,
+but not that the hook's guard is what enforces it: `org-element--get-category'
+searches the buffer for `#+CATEGORY:' itself and returns that before consulting
+`org-category', so the directive survives even with the guard deleted. The
+guard's own job is covered by
+`test-org-agenda-config-category-hook-boundary-respects-explicit'."
+ (test-org-agenda-config-category--with-real-file
+ (buffer "myproject" "todo.org" "#+CATEGORY: Personal\n* TODO a task\n")
+ (with-current-buffer buffer
+ (should (equal "Personal" (org-get-category (point-min)))))))
+
+(ert-deftest test-org-agenda-config-category-endtoend-survives-an-earlier-hook ()
+ "Error: the override still lands when another hook reads the category first.
+
+`org-get-category' resolves a deferred `:CATEGORY' that the org-element cache
+then holds. A hook running BEFORE ours that reads it freezes \"todo\" in that
+cache, and our later `setq-local' becomes inert -- `org-category' reads correct
+while the agenda still shows \"todo\".
+
+This is not hypothetical. `add-hook' prepends by default, so every hook added
+after ours runs before it, and the live config already has three ahead of it.
+The fix is the explicit depth on our `add-hook'; this test is what holds it
+there."
+ (test-org-agenda-config-category--with-real-file
+ (buffer "projX" "todo.org" "* TODO a task\n")
+ (ignore buffer))
+ ;; Re-visit with a nosy reader installed ahead of ours.
+ (let ((reader (lambda () (ignore (org-get-category (point-min))))))
+ (unwind-protect
+ (progn
+ (add-hook 'org-mode-hook reader)
+ (test-org-agenda-config-category--with-real-file
+ (buffer "projX" "todo.org" "* TODO a task\n")
+ (with-current-buffer buffer
+ (should (equal "projX" (org-get-category (point-min)))))))
+ (remove-hook 'org-mode-hook reader))))
+
+(ert-deftest test-org-agenda-config-category-endtoend-other-filename-untouched ()
+ "Boundary: a distinctively-named file keeps its filename category.
+Only todo.org is generic enough to be worth replacing. Files like
+schedule.org or gcal.org already say something useful, and deriving from
+the directory would collapse several of them onto one name."
+ (test-org-agenda-config-category--with-real-file
+ (buffer "data" "gcal.org" "* TODO an event\n")
+ (with-current-buffer buffer
+ (should (equal "gcal" (org-get-category (point-min)))))))
+
(provide 'test-org-agenda-config-category)
;;; test-org-agenda-config-category.el ends here