aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-06 14:09:31 -0500
committerCraig Jennings <c@cjennings.net>2026-06-06 14:09:31 -0500
commit480a59f36a7e3f406847a4157f0b2c62d114af7c (patch)
tree720592b845158276c4ae9cd3739f833c55f8e5e4 /tests
parent651f4c878a2bc6dda634b4cfc40e965a062cdffc (diff)
downloaddotemacs-480a59f36a7e3f406847a4157f0b2c62d114af7c.tar.gz
dotemacs-480a59f36a7e3f406847a4157f0b2c62d114af7c.zip
feat(capture): project-aware Task and Bug capture targets
C-c c t (Task) and a new C-c c b (Bug) now file into the current projectile project's todo.org under its "... Open Work" heading instead of always landing in the global inbox. Bug stamps the entry [#C]. Task stays a plain TODO. Both share one function target, cj/--org-capture-project-location. It matches an existing top-level "... Open Work" heading when there is one, so a directory like .emacs.d resolves to the existing "Emacs Open Work" rather than a name derived from the basename. It only creates "<Project> Open Work" when the file has none. Outside a project, or in a project whose root has no todo.org, it falls back to the global inbox under "Inbox". In the no-todo.org case it also warns, naming the project. It never creates a project's todo.org. I split the logic into pure helpers (project name, target decision, find-or-create heading) so they test directly, with the impure buffer-positioning left thin. 15 ERT tests cover the helpers and the wiring. I confirmed a real capture lands the entry under Open Work at the right level in the running daemon.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-org-capture-config-project-target.el174
1 files changed, 174 insertions, 0 deletions
diff --git a/tests/test-org-capture-config-project-target.el b/tests/test-org-capture-config-project-target.el
new file mode 100644
index 00000000..c9091c91
--- /dev/null
+++ b/tests/test-org-capture-config-project-target.el
@@ -0,0 +1,174 @@
+;;; test-org-capture-config-project-target.el --- Project-aware capture tests -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the project-aware capture target shared by C-c c t (Task) and
+;; C-c c b (Bug): the pure project-name and target-decision helpers, the
+;; find-or-create "Open Work" / "Inbox" heading helpers, the function-target
+;; wiring, and the two template registrations.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+(require 'org-capture)
+(require 'user-constants)
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'org-capture-config)
+
+;;; cj/--org-capture-project-name
+
+(ert-deftest test-org-capture-project-name-normal ()
+ "Normal: basename, first letter upcased; trailing slash ignored."
+ (should (equal (cj/--org-capture-project-name "/home/cj/code/duet/") "Duet"))
+ (should (equal (cj/--org-capture-project-name "/home/cj/code/duet") "Duet")))
+
+(ert-deftest test-org-capture-project-name-strips-leading-dot ()
+ "Boundary: a single leading dot is stripped before upcasing."
+ (should (equal (cj/--org-capture-project-name "/home/cj/.emacs.d/") "Emacs.d")))
+
+(ert-deftest test-org-capture-project-name-nil-and-empty ()
+ "Error: nil or empty root yields nil."
+ (should-not (cj/--org-capture-project-name nil))
+ (should-not (cj/--org-capture-project-name "")))
+
+;;; cj/--org-capture-project-target
+
+(ert-deftest test-org-capture-target-project-with-todo ()
+ "Normal: a projectile root whose todo.org exists targets that file's Open Work."
+ (let ((root (make-temp-file "captest-" t)))
+ (unwind-protect
+ (progn
+ (with-temp-file (expand-file-name "todo.org" root)
+ (insert "* X Open Work\n"))
+ (let ((plan (cj/--org-capture-project-target root "/tmp/inbox.org")))
+ (should (string= (plist-get plan :file)
+ (expand-file-name "todo.org" root)))
+ (should (plist-get plan :open-work))
+ (should-not (plist-get plan :warn))))
+ (delete-directory root t))))
+
+(ert-deftest test-org-capture-target-project-without-todo ()
+ "Boundary: a projectile root with no todo.org falls back to inbox and warns."
+ (let ((root (make-temp-file "captest-" t)))
+ (unwind-protect
+ (let ((plan (cj/--org-capture-project-target root "/tmp/inbox.org")))
+ (should (string= (plist-get plan :file) "/tmp/inbox.org"))
+ (should-not (plist-get plan :open-work))
+ (should (stringp (plist-get plan :warn)))
+ (should (string-match-p (regexp-quote (cj/--org-capture-project-name root))
+ (plist-get plan :warn))))
+ (delete-directory root t))))
+
+(ert-deftest test-org-capture-target-no-project ()
+ "Boundary: nil root targets the inbox with no warning."
+ (let ((plan (cj/--org-capture-project-target nil "/tmp/inbox.org")))
+ (should (string= (plist-get plan :file) "/tmp/inbox.org"))
+ (should-not (plist-get plan :open-work))
+ (should-not (plist-get plan :warn))))
+
+;;; cj/--org-capture-goto-open-work
+
+(ert-deftest test-org-capture-goto-open-work-finds-existing ()
+ "Normal: an existing top-level \"... Open Work\" heading is reused, not duplicated."
+ (with-temp-buffer
+ (org-mode)
+ (insert "* Emacs Open Work\n** TODO a\n* Emacs Resolved\n")
+ (cj/--org-capture-goto-open-work "Ignored")
+ (should (string= (org-get-heading t t t t) "Emacs Open Work"))
+ (should-not (string-match-p "Ignored" (buffer-string)))))
+
+(ert-deftest test-org-capture-goto-open-work-matches-tagged-heading ()
+ "Boundary: a tagged \"... Open Work\" heading still matches and is not duplicated."
+ (with-temp-buffer
+ (org-mode)
+ (insert "* Foo Open Work :stuff:\n")
+ (cj/--org-capture-goto-open-work "Bar")
+ (should (string-match-p "Open Work" (org-get-heading t t t t)))
+ (should-not (string-match-p "Bar Open Work" (buffer-string)))))
+
+(ert-deftest test-org-capture-goto-open-work-creates-when-absent ()
+ "Boundary: with no Open Work heading, create \"* NAME Open Work\" at end."
+ (with-temp-buffer
+ (org-mode)
+ (insert "* Something Else\n")
+ (cj/--org-capture-goto-open-work "Duet")
+ (should (string-match-p "^\\* Duet Open Work$" (buffer-string)))
+ (should (string= (org-get-heading t t t t) "Duet Open Work"))))
+
+;;; cj/--org-capture-goto-exact-headline
+
+(ert-deftest test-org-capture-goto-exact-headline-finds ()
+ "Normal: an existing Inbox heading is found."
+ (with-temp-buffer
+ (org-mode)
+ (insert "* Inbox\n** TODO x\n")
+ (cj/--org-capture-goto-exact-headline "Inbox")
+ (should (string= (org-get-heading t t t t) "Inbox"))))
+
+(ert-deftest test-org-capture-goto-exact-headline-creates ()
+ "Boundary: a missing Inbox heading is created at end of buffer."
+ (with-temp-buffer
+ (org-mode)
+ (insert "* Other\n")
+ (cj/--org-capture-goto-exact-headline "Inbox")
+ (should (string-match-p "^\\* Inbox$" (buffer-string)))))
+
+;;; cj/--org-capture-project-location (function-target wiring)
+
+(ert-deftest test-org-capture-location-files-into-project-open-work ()
+ "Integration: in a project with a todo.org, the location function visits that
+file and lands point on its Open Work heading."
+ (let* ((root (make-temp-file "captest-" t))
+ (todo (expand-file-name "todo.org" root))
+ (org-capture-plist nil)
+ visited)
+ (unwind-protect
+ (progn
+ (with-temp-file todo (insert "* Captest Open Work\n** TODO old\n"))
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda (&optional _d) root)))
+ (cj/--org-capture-project-location)
+ (setq visited (current-buffer))
+ (should (string= (buffer-file-name) todo))
+ (should (string-match-p "Open Work" (org-get-heading t t t t)))))
+ (when (buffer-live-p visited) (kill-buffer visited))
+ (delete-directory root t))))
+
+(ert-deftest test-org-capture-location-falls-back-to-inbox-without-project ()
+ "Integration: with no project, the location function visits the inbox file
+under its Inbox heading."
+ (let* ((inbox (make-temp-file "captest-inbox-" nil ".org" "* Inbox\n"))
+ (inbox-file inbox)
+ (org-capture-plist nil)
+ visited)
+ (unwind-protect
+ (cl-letf (((symbol-function 'projectile-project-root)
+ (lambda (&optional _d) nil)))
+ (cj/--org-capture-project-location)
+ (setq visited (current-buffer))
+ (should (string= (buffer-file-name) inbox))
+ (should (string= (org-get-heading t t t t) "Inbox")))
+ (when (buffer-live-p visited) (kill-buffer visited))
+ (delete-file inbox))))
+
+;;; templates
+
+(ert-deftest test-org-capture-task-template-is-project-aware ()
+ "Normal: the Task template (t) targets the project-aware function."
+ (let ((entry (assoc "t" org-capture-templates)))
+ (should entry)
+ (should (equal (nth 3 entry)
+ '(function cj/--org-capture-project-location)))))
+
+(ert-deftest test-org-capture-bug-template-registered ()
+ "Normal: the Bug template (b) exists, targets the project-aware function, and
+defaults to the [#C] priority."
+ (let ((entry (assoc "b" org-capture-templates)))
+ (should entry)
+ (should (equal (nth 3 entry)
+ '(function cj/--org-capture-project-location)))
+ (should (string-match-p "\\[#C\\]" (nth 4 entry)))))
+
+(provide 'test-org-capture-config-project-target)
+;;; test-org-capture-config-project-target.el ends here