aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-agenda-config-category.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-13 15:25:52 -0500
committerCraig Jennings <c@cjennings.net>2026-05-13 15:25:52 -0500
commitc3514440eb3de3101576baa5c3c592c0d908f70b (patch)
tree6534504d1a90a9c014a09c446f2f384677e80e10 /tests/test-org-agenda-config-category.el
parente076815895ed452e021ae4c1986eca1f0e67aa41 (diff)
downloaddotemacs-c3514440eb3de3101576baa5c3c592c0d908f70b.tar.gz
dotemacs-c3514440eb3de3101576baa5c3c592c0d908f70b.zip
feat(org-agenda): use project name as todo.org category
The %c column on agenda blocks rendered every project's todo.org as "todo:" -- org defaults the buffer category to the filename without extension, so every entry looked alike. An org-mode-hook now overrides org-category with the parent directory's basename (stripping a single leading dot, so ~/.emacs.d/todo.org reads as "emacs.d") whenever a todo.org file opens and its category is still the filename default. Explicit #+CATEGORY: keywords still win. 14 tests in test-org-agenda-config-category.el cover the helper's normal/boundary/error paths and the hook's override + explicit-category-preserved cases.
Diffstat (limited to 'tests/test-org-agenda-config-category.el')
-rw-r--r--tests/test-org-agenda-config-category.el137
1 files changed, 137 insertions, 0 deletions
diff --git a/tests/test-org-agenda-config-category.el b/tests/test-org-agenda-config-category.el
new file mode 100644
index 00000000..6a54d9e6
--- /dev/null
+++ b/tests/test-org-agenda-config-category.el
@@ -0,0 +1,137 @@
+;;; test-org-agenda-config-category.el --- Tests for project-name category derivation -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the agenda-display category helpers in org-agenda-config.el:
+;; - cj/--org-todo-category-from-file (pure)
+;; - cj/--org-set-todo-category (org-mode-hook side effect)
+;;
+;; Goal: when a buffer visits a project-local todo.org, its `org-category`
+;; should be the parent directory name (the project slug) rather than the
+;; default "todo" -- so the agenda's %c column shows "emacs.d:" instead of
+;; "todo:" for every project's tasks.
+
+;;; Code:
+
+(require 'ert)
+(require 'org)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'org-agenda-config)
+
+;;; ---------- cj/--org-todo-category-from-file (pure helper) ----------
+
+;;; Normal Cases
+
+(ert-deftest test-org-agenda-config-category-normal-emacs-d-todo ()
+ "Normal: todo.org under .emacs.d returns \"emacs.d\"."
+ (should (equal "emacs.d"
+ (cj/--org-todo-category-from-file
+ "/home/cjennings/.emacs.d/todo.org"))))
+
+(ert-deftest test-org-agenda-config-category-normal-project-todo ()
+ "Normal: todo.org under a project dir returns the project basename."
+ (should (equal "dotemacs"
+ (cj/--org-todo-category-from-file
+ "/home/cjennings/code/dotemacs/todo.org"))))
+
+(ert-deftest test-org-agenda-config-category-normal-deep-project ()
+ "Normal: deeply nested todo.org returns only the immediate parent."
+ (should (equal "frontend"
+ (cj/--org-todo-category-from-file
+ "/home/cjennings/projects/work/myapp/frontend/todo.org"))))
+
+;;; Boundary Cases
+
+(ert-deftest test-org-agenda-config-category-boundary-non-todo-file ()
+ "Boundary: non-todo.org file returns nil so default category stays."
+ (should (null (cj/--org-todo-category-from-file
+ "/home/cjennings/sync/org/roam/inbox.org"))))
+
+(ert-deftest test-org-agenda-config-category-boundary-schedule-org ()
+ "Boundary: schedule.org returns nil; not a project todo file."
+ (should (null (cj/--org-todo-category-from-file
+ "/home/cjennings/sync/org/schedule.org"))))
+
+(ert-deftest test-org-agenda-config-category-boundary-todo-at-fs-root ()
+ "Boundary: /todo.org with no real parent directory returns nil."
+ (should (null (cj/--org-todo-category-from-file "/todo.org"))))
+
+(ert-deftest test-org-agenda-config-category-boundary-relative-path ()
+ "Boundary: bare relative \"todo.org\" with no directory returns nil."
+ (should (null (cj/--org-todo-category-from-file "todo.org"))))
+
+(ert-deftest test-org-agenda-config-category-boundary-todo-with-trailing-dir ()
+ "Boundary: tolerate a path that is already directory-form."
+ (should (equal "emacs.d"
+ (cj/--org-todo-category-from-file
+ "/home/cjennings/.emacs.d/todo.org"))))
+
+;;; Error Cases
+
+(ert-deftest test-org-agenda-config-category-error-nil-path ()
+ "Error: nil PATH returns nil, no signal."
+ (should (null (cj/--org-todo-category-from-file nil))))
+
+(ert-deftest test-org-agenda-config-category-error-empty-path ()
+ "Error: empty-string PATH returns nil."
+ (should (null (cj/--org-todo-category-from-file ""))))
+
+;;; ---------- cj/--org-set-todo-category (hook function) ----------
+
+(defmacro test-org-agenda-config-category--with-file (path body-form)
+ "Visit PATH in a temp buffer with org-mode active, evaluate BODY-FORM.
+Sets `buffer-file-name' so the hook's lookup sees the desired path.
+Suppresses other org-mode hooks to keep the test isolated."
+ (declare (indent 1))
+ `(with-temp-buffer
+ (let ((org-mode-hook nil)
+ (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))))
+ ,body-form))
+
+;;; Normal Cases
+
+(ert-deftest test-org-agenda-config-category-hook-normal-overrides-todo ()
+ "Normal: hook overrides default \"todo\" with the parent dir name."
+ (test-org-agenda-config-category--with-file "/home/cjennings/.emacs.d/todo.org"
+ (progn
+ (cj/--org-set-todo-category)
+ (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."
+ (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)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-org-agenda-config-category-hook-boundary-respects-explicit ()
+ "Boundary: explicit category (not the filename default) is preserved."
+ (test-org-agenda-config-category--with-file "/home/cjennings/.emacs.d/todo.org"
+ (progn
+ (setq-local org-category "Personal")
+ (cj/--org-set-todo-category)
+ (should (equal "Personal" org-category)))))
+
+(ert-deftest test-org-agenda-config-category-hook-boundary-nil-buffer-file-name ()
+ "Boundary: hook is safe in buffers with no `buffer-file-name'."
+ (with-temp-buffer
+ (let ((org-mode-hook nil)
+ (text-mode-hook nil))
+ (org-mode))
+ (setq buffer-file-name nil)
+ (cj/--org-set-todo-category)
+ ;; no error and no spurious mutation
+ (should t)))
+
+(provide 'test-org-agenda-config-category)
+;;; test-org-agenda-config-category.el ends here