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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
;;; test-org-noter-config-commands.el --- Tests for org-noter-config command + helper functions -*- lexical-binding: t; -*-
;;; Commentary:
;; Sibling tests cover the preferred-split helper, the title-to-slug
;; helper, the notes-template generator, and the in-document /
;; in-notes-file predicates. This file fills in:
;;
;; cj/org-noter--get-document-path
;; cj/org-noter--extract-document-title
;; cj/org-noter--find-notes-file
;; cj/org-noter--create-notes-file
;; cj/org-noter--session-active-p
;; cj/org-noter--toggle-notes-window
;; cj/org-noter-start
;; cj/org-noter-insert-note-dwim
;;
;; org-noter / pdf-view / nov primitives are stubbed.
;;; Code:
(require 'ert)
(require 'cl-lib)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'org-noter-config)
;; Top-level dynamic vars some of the helpers reference.
(defvar org-noter--session nil
"Stub for `org-noter--session' (org-noter not loaded here).")
(defvar nov-file-name nil
"Stub for `nov-file-name' (nov.el not loaded here).")
(defvar pdf-view-display-size nil
"Stub for `pdf-view-display-size'.")
;;; cj/org-noter--get-document-path
(ert-deftest test-org-noter-get-document-path-pdf-mode-uses-buffer-file ()
"Normal: in pdf-view-mode the buffer-file-name is the doc path."
(cl-letf (((symbol-function 'derived-mode-p)
(lambda (&rest modes) (memq 'pdf-view-mode modes))))
(with-temp-buffer
(setq buffer-file-name "/tmp/whitepaper.pdf")
(should (equal (cj/org-noter--get-document-path) "/tmp/whitepaper.pdf"))
(setq buffer-file-name nil))))
(ert-deftest test-org-noter-get-document-path-nov-mode-uses-nov-file-name ()
"Normal: in nov-mode the variable `nov-file-name' is the doc path."
(cl-letf (((symbol-function 'derived-mode-p)
(lambda (&rest modes) (memq 'nov-mode modes))))
(let ((nov-file-name "/tmp/book.epub"))
(should (equal (cj/org-noter--get-document-path) "/tmp/book.epub")))))
(ert-deftest test-org-noter-get-document-path-unknown-mode-returns-nil ()
"Boundary: an unrelated major mode returns nil."
(cl-letf (((symbol-function 'derived-mode-p) (lambda (&rest _) nil)))
(should-not (cj/org-noter--get-document-path))))
;;; cj/org-noter--extract-document-title
(ert-deftest test-org-noter-extract-document-title-strips-extension ()
"Normal: the title is the basename without extension."
(cl-letf (((symbol-function 'cj/org-noter--get-document-path)
(lambda () "/tmp/papers/quantum-2024.pdf")))
(should (equal (cj/org-noter--extract-document-title) "quantum-2024"))))
;;; cj/org-noter--find-notes-file
(ert-deftest test-org-noter-find-notes-file-returns-matching-file ()
"Normal: a notes file whose body mentions the doc-path is returned."
(let* ((dir (make-temp-file "test-org-noter-find-" t))
(notes (expand-file-name "notes-on-foo.org" dir))
(doc "/tmp/papers/foo.pdf")
(cj/org-noter-notes-directory dir))
(unwind-protect
(progn
(with-temp-file notes
(insert ":PROPERTIES:\n:NOTER_DOCUMENT: " doc "\n:END:\n"))
(cl-letf (((symbol-function 'cj/org-noter--get-document-path)
(lambda () doc)))
(should (equal (cj/org-noter--find-notes-file) notes))))
(delete-directory dir t))))
(ert-deftest test-org-noter-find-notes-file-nil-when-no-doc-path ()
"Boundary: with no current document, find returns nil."
(cl-letf (((symbol-function 'cj/org-noter--get-document-path)
(lambda () nil)))
(should-not (cj/org-noter--find-notes-file))))
(ert-deftest test-org-noter-find-notes-file-nil-when-no-match ()
"Boundary: a directory with notes files that don't match returns nil."
(let* ((dir (make-temp-file "test-org-noter-find-" t))
(cj/org-noter-notes-directory dir))
(unwind-protect
(progn
(with-temp-file (expand-file-name "unrelated.org" dir)
(insert "* Notes about something else\n"))
(cl-letf (((symbol-function 'cj/org-noter--get-document-path)
(lambda () "/tmp/different.pdf")))
(should-not (cj/org-noter--find-notes-file))))
(delete-directory dir t))))
;;; cj/org-noter--create-notes-file
(ert-deftest test-org-noter-create-notes-file-writes-template-when-absent ()
"Normal: a new doc gets a new notes file with the template content."
(let* ((dir (make-temp-file "test-org-noter-create-" t))
(cj/org-noter-notes-directory dir)
(doc "/tmp/papers/great-book.pdf"))
(unwind-protect
(cl-letf (((symbol-function 'cj/org-noter--get-document-path)
(lambda () doc))
((symbol-function 'read-string)
(lambda (&rest _) "great-book"))
;; The template uses `org-id-uuid' (org-id not loaded here).
((symbol-function 'org-id-uuid)
(lambda () "00000000-0000-0000-0000-000000000000"))
((symbol-function 'find-file-noselect)
(lambda (f) (get-buffer-create (concat "*test-" f "*")))))
(let ((path (cj/org-noter--create-notes-file)))
(should (file-exists-p path))
(with-temp-buffer
(insert-file-contents path)
(should (string-match-p (regexp-quote doc) (buffer-string))))))
(delete-directory dir t))))
;;; cj/org-noter--session-active-p
(ert-deftest test-org-noter-session-active-p-nil-when-unbound ()
"Boundary: with `org-noter--session' nil, predicate returns nil."
(let ((org-noter--session nil))
(should-not (cj/org-noter--session-active-p))))
(ert-deftest test-org-noter-session-active-p-non-nil-when-set ()
"Normal: with `org-noter--session' set, predicate returns non-nil."
(let ((org-noter--session 'active-session))
(should (cj/org-noter--session-active-p))))
;;; cj/org-noter--toggle-notes-window
(ert-deftest test-org-noter-toggle-notes-window-closes-when-visible ()
"Normal: a visible notes window gets deleted."
(let ((deleted nil))
(cl-letf (((symbol-function 'org-noter--get-notes-window)
(lambda (&rest _) 'fake-window))
((symbol-function 'delete-window)
(lambda (w) (setq deleted w)))
((symbol-function 'derived-mode-p) (lambda (&rest _) nil)))
(cj/org-noter--toggle-notes-window))
(should (eq deleted 'fake-window))))
(ert-deftest test-org-noter-toggle-notes-window-opens-when-hidden ()
"Normal: when no notes window exists, `start' is requested."
(let ((requested-mode nil))
(cl-letf (((symbol-function 'org-noter--get-notes-window)
(lambda (&optional flag)
(if flag (setq requested-mode flag) nil)))
((symbol-function 'derived-mode-p) (lambda (&rest _) nil)))
(cj/org-noter--toggle-notes-window))
(should (eq requested-mode 'start))))
;;; cj/org-noter-start
(ert-deftest test-org-noter-start-in-document-with-session-toggles-notes ()
"Normal: in a document with an active session, start toggles the notes window."
(let ((toggled nil))
(cl-letf (((symbol-function 'cj/org-noter--in-document-p)
(lambda () t))
((symbol-function 'cj/org-noter--in-notes-file-p)
(lambda () nil))
((symbol-function 'cj/org-noter--session-active-p)
(lambda () t))
((symbol-function 'cj/org-noter--toggle-notes-window)
(lambda () (setq toggled t))))
(cj/org-noter-start))
(should toggled)))
(ert-deftest test-org-noter-start-in-notes-file-with-session-switches-window ()
"Normal: in a notes file with active session, switch to doc window."
(let ((selected nil))
(cl-letf (((symbol-function 'cj/org-noter--in-document-p)
(lambda () nil))
((symbol-function 'cj/org-noter--in-notes-file-p)
(lambda () t))
((symbol-function 'cj/org-noter--session-active-p)
(lambda () t))
((symbol-function 'org-noter--get-doc-window)
(lambda () 'doc-win))
((symbol-function 'select-window)
(lambda (w) (setq selected w))))
(cj/org-noter-start))
(should (eq selected 'doc-win))))
(ert-deftest test-org-noter-start-elsewhere-messages ()
"Boundary: outside any noter context, the function messages."
(let ((msg nil))
(cl-letf (((symbol-function 'cj/org-noter--in-document-p)
(lambda () nil))
((symbol-function 'cj/org-noter--in-notes-file-p)
(lambda () nil))
((symbol-function 'message)
(lambda (fmt &rest args)
(setq msg (apply #'format fmt args)))))
(cj/org-noter-start))
(should (string-match-p "Not in a document" msg))))
;;; cj/org-noter-insert-note-dwim
(ert-deftest test-org-noter-insert-note-dwim-inserts-when-session-active ()
"Normal: with an active session, insert-note is called directly."
(let ((inserted nil)
(started nil))
(cl-letf (((symbol-function 'cj/org-noter--session-active-p)
(lambda () t))
((symbol-function 'cj/org-noter-start)
(lambda () (setq started t)))
((symbol-function 'org-noter-insert-note)
(lambda () (setq inserted t))))
(cj/org-noter-insert-note-dwim))
(should inserted)
;; Doesn't try to start a session when one's already active.
(should-not started)))
(ert-deftest test-org-noter-insert-note-dwim-starts-then-inserts-when-no-session ()
"Normal: without a session, the function starts one and then inserts."
(let ((session-state nil) ; flipped to t when start "succeeds"
(inserted nil)
(selected nil))
(cl-letf (((symbol-function 'cj/org-noter--session-active-p)
(lambda () session-state))
((symbol-function 'cj/org-noter-start)
(lambda () (setq session-state t)))
((symbol-function 'org-noter--get-doc-window)
(lambda () 'doc-win))
((symbol-function 'select-window)
(lambda (w) (setq selected w)))
((symbol-function 'org-noter-insert-note)
(lambda () (setq inserted t))))
(cj/org-noter-insert-note-dwim))
(should (eq selected 'doc-win))
(should inserted)))
(provide 'test-org-noter-config-commands)
;;; test-org-noter-config-commands.el ends here
|