blob: f127bfbca47e87d8ada73483f498e2e6c37dcbb6 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
;;; 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)))
(ert-deftest test-org-capture-target-cache-recovers-after-buffer-killed ()
"Boundary: killing the marker's buffer invalidates the cache entry.
The next capture rescans (and refreshes), without crashing on the stale marker."
(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)))
(kill-buffer (marker-buffer marker))
(should-not (cj/org-capture--headline-marker-valid-p marker "Inbox"))
;; A second resolution must succeed without erroring on the stale marker.
(org-capture-set-target-location)
(let ((fresh (gethash key cj/org-capture--file-headline-target-cache)))
(should (cj/org-capture--headline-marker-valid-p fresh "Inbox"))))))
(test-org-capture-target-cache--reset)))
(ert-deftest test-org-capture-target-cache-clear-empties-the-hash ()
"Normal: `cj/org-capture-clear-target-cache' actually empties the cache."
(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)
(should (= 1 (hash-table-count cj/org-capture--file-headline-target-cache)))
;; Suppress the user-facing message during the test.
(cl-letf (((symbol-function 'message) (lambda (&rest _) nil)))
(cj/org-capture-clear-target-cache))
(should (= 0 (hash-table-count cj/org-capture--file-headline-target-cache)))))
(test-org-capture-target-cache--reset)))
(ert-deftest test-org-capture-target-cache-falls-through-for-non-file+headline ()
"Boundary: targets that aren't `file+headline' (e.g. plain `file', `file+olp',
`file+function') fall through to the original `org-capture-set-target-location'
unchanged."
(test-org-capture-target-cache--reset)
(dolist (target '((file "/tmp/fall-through.org")
(file+olp "/tmp/fall-through.org" "Tasks" "Inbox")
(file+function "/tmp/fall-through.org" ignore)))
(let ((called-with nil))
(cj/org-capture--set-target-location-advice
(lambda (&optional received) (setq called-with (list :ran received)))
target)
(should (equal called-with (list :ran target))))))
(provide 'test-org-capture-config-target-cache)
;;; test-org-capture-config-target-cache.el ends here
|