aboutsummaryrefslogtreecommitdiff
path: root/tests/test-org-capture-config-target-cache.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-11 17:17:53 -0500
committerCraig Jennings <c@cjennings.net>2026-05-11 17:17:53 -0500
commit96ee689709ca45866e985fff2179c0e6797ec697 (patch)
tree381d571336b9ad82c34e93d5d7390b85430cf1db /tests/test-org-capture-config-target-cache.el
parente958410cbc14d2bfa0f97890aafe38031e082aa7 (diff)
downloaddotemacs-96ee689709ca45866e985fff2179c0e6797ec697.tar.gz
dotemacs-96ee689709ca45866e985fff2179c0e6797ec697.zip
perf(org-capture): cache file+headline target markers
A task capture took 15-20 seconds: Org resolves a `(file+headline FILE "Headline")' target by opening the file, widening, and regex-scanning from the top for the headline, and the inbox template captures to `(file+headline inbox-file "Inbox")' over and over. An `:around' advice on `org-capture-set-target-location' caches a marker per resolved file/headline. On the next capture it validates the marker (still live, still in an Org buffer, still at a heading, headline text still matches) and jumps straight there. On any mismatch it falls back to the normal scan/create and refreshes the cache. `M-x cj/org-capture-clear-target-cache' resets it. Tests cover the cache hit, marker invalidation after the headline text changes, and creating a missing headline.
Diffstat (limited to 'tests/test-org-capture-config-target-cache.el')
-rw-r--r--tests/test-org-capture-config-target-cache.el89
1 files changed, 89 insertions, 0 deletions
diff --git a/tests/test-org-capture-config-target-cache.el b/tests/test-org-capture-config-target-cache.el
new file mode 100644
index 00000000..7b88975b
--- /dev/null
+++ b/tests/test-org-capture-config-target-cache.el
@@ -0,0 +1,89 @@
+;;; test-org-capture-config-target-cache.el --- Tests for capture target cache -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for cached file+headline org-capture target lookup.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+(require 'org-capture)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'org-capture-config)
+
+(defun test-org-capture-target-cache--reset ()
+ "Reset target cache between tests."
+ (clrhash cj/org-capture--file-headline-target-cache))
+
+(defmacro test-org-capture-target-cache--with-temp-org-file (contents &rest body)
+ "Create a temporary Org file with CONTENTS, then run BODY.
+The file-visiting buffer is killed after BODY returns."
+ (declare (indent 1))
+ `(let ((file (make-temp-file "org-capture-target-cache" nil ".org" ,contents)))
+ (unwind-protect
+ (progn ,@body)
+ (when-let ((buffer (find-buffer-visiting file)))
+ (kill-buffer buffer))
+ (when (file-exists-p file)
+ (delete-file file)))))
+
+(ert-deftest test-org-capture-target-cache-second-file-headline-lookup-reuses-marker ()
+ "Normal: repeated file+headline target resolution avoids a second headline scan."
+ (test-org-capture-target-cache--reset)
+ (unwind-protect
+ (test-org-capture-target-cache--with-temp-org-file
+ "* Inbox\n** Existing task\n"
+ (let ((scan-count 0)
+ (original-re-search-forward (symbol-function 're-search-forward)))
+ (cl-letf (((symbol-function 're-search-forward)
+ (lambda (regexp &optional bound noerror count)
+ (when (and (stringp regexp)
+ (string-match-p "Inbox" regexp))
+ (cl-incf scan-count))
+ (funcall original-re-search-forward
+ regexp bound noerror count))))
+ (let ((org-capture-plist `(:target (file+headline ,file "Inbox"))))
+ (org-capture-set-target-location)
+ (org-capture-set-target-location))
+ (should (= 1 scan-count)))))
+ (test-org-capture-target-cache--reset)))
+
+(ert-deftest test-org-capture-target-cache-validates-marker-headline ()
+ "Boundary: a cached marker is invalid when its heading no longer matches."
+ (test-org-capture-target-cache--reset)
+ (unwind-protect
+ (test-org-capture-target-cache--with-temp-org-file
+ "* Inbox\n"
+ (let ((org-capture-plist `(:target (file+headline ,file "Inbox"))))
+ (org-capture-set-target-location)
+ (let* ((key (cj/org-capture--file-headline-cache-key file "Inbox"))
+ (marker (gethash key cj/org-capture--file-headline-target-cache)))
+ (should (cj/org-capture--headline-marker-valid-p marker "Inbox"))
+ (with-current-buffer (marker-buffer marker)
+ (save-excursion
+ (goto-char marker)
+ (insert "Renamed ")))
+ (should-not
+ (cj/org-capture--headline-marker-valid-p marker "Inbox")))))
+ (test-org-capture-target-cache--reset)))
+
+(ert-deftest test-org-capture-target-cache-creates-missing-headline-and-caches-it ()
+ "Boundary: missing file+headline targets are created and cached."
+ (test-org-capture-target-cache--reset)
+ (unwind-protect
+ (test-org-capture-target-cache--with-temp-org-file
+ "#+title: Empty\n"
+ (let ((org-capture-plist `(:target (file+headline ,file "Inbox"))))
+ (org-capture-set-target-location)
+ (with-current-buffer (org-capture-get :buffer)
+ (goto-char (org-capture-get :pos))
+ (should (looking-at-p "\\* Inbox")))
+ (let* ((key (cj/org-capture--file-headline-cache-key file "Inbox"))
+ (marker (gethash key cj/org-capture--file-headline-target-cache)))
+ (should (cj/org-capture--headline-marker-valid-p marker "Inbox")))))
+ (test-org-capture-target-cache--reset)))
+
+(provide 'test-org-capture-config-target-cache)
+;;; test-org-capture-config-target-cache.el ends here