main
1;;; org-batch-functions-test.el --- Tests for org-batch-functions -*- lexical-binding: t -*-
2
3;; Copyright (C) 2026 Vincent Demeester
4
5;;; Commentary:
6
7;; Comprehensive tests for org-batch-functions.el v2.0 (org-ql based)
8;; Run with: emacs --batch -l org-batch-functions-test.el -f org-batch-run-all-tests
9
10;;; Code:
11
12(require 'org-batch-functions)
13(require 'ert)
14
15(defvar org-batch-test-file "/home/vincent/desktop/org/todos.org"
16 "File to use for testing.")
17
18(defvar org-batch-test-results '()
19 "Accumulator for test results.")
20
21(defun org-batch-test-report (name passed &optional message)
22 "Report test NAME result (PASSED t/nil) with optional MESSAGE."
23 (push (list name passed message) org-batch-test-results)
24 (princ (format "%s %s%s\n"
25 (if passed "✓" "✗")
26 name
27 (if message (format " - %s" message) ""))))
28
29(defun org-batch-test-assert (name condition &optional message)
30 "Assert CONDITION is true for test NAME."
31 (org-batch-test-report name condition message))
32
33;;; Read Operations Tests
34
35(defun org-batch-test-list-todos ()
36 "Test org-batch-list-todos function."
37 (let ((results (org-batch-list-todos org-batch-test-file)))
38 (org-batch-test-assert
39 "list-todos: returns list"
40 (listp results)
41 (format "got %d items" (length results)))
42 (org-batch-test-assert
43 "list-todos: items have heading"
44 (and (car results) (alist-get 'heading (car results)))
45 (format "first: %s" (alist-get 'heading (car results))))))
46
47(defun org-batch-test-list-todos-filter-state ()
48 "Test state filtering."
49 (let ((next-items (org-batch-list-todos org-batch-test-file "NEXT"))
50 (todo-items (org-batch-list-todos org-batch-test-file "TODO")))
51 (org-batch-test-assert
52 "list-todos-filter-state: NEXT filter works"
53 (and (listp next-items)
54 (or (null next-items)
55 (string= "NEXT" (alist-get 'todo (car next-items)))))
56 (format "%d NEXT items" (length next-items)))
57 (org-batch-test-assert
58 "list-todos-filter-state: TODO filter works"
59 (and (listp todo-items)
60 (or (null todo-items)
61 (string= "TODO" (alist-get 'todo (car todo-items)))))
62 (format "%d TODO items" (length todo-items)))))
63
64(defun org-batch-test-list-todos-comma-separated ()
65 "Test comma-separated state filtering."
66 (let ((results (org-batch-list-todos org-batch-test-file "NEXT,STRT")))
67 (org-batch-test-assert
68 "list-todos-comma-separated: works"
69 (and (listp results)
70 (or (null results)
71 (member (alist-get 'todo (car results)) '("NEXT" "STRT"))))
72 (format "%d items" (length results)))))
73
74(defun org-batch-test-scheduled-today ()
75 "Test org-batch-scheduled-today function."
76 (let ((results (org-batch-scheduled-today org-batch-test-file)))
77 (org-batch-test-assert
78 "scheduled-today: returns list"
79 (listp results)
80 (format "%d scheduled today" (length results)))))
81
82(defun org-batch-test-by-section ()
83 "Test org-batch-by-section function."
84 (let ((results (org-batch-by-section org-batch-test-file "Systems")))
85 (org-batch-test-assert
86 "by-section: finds Systems section"
87 (and (listp results) (> (length results) 0))
88 (format "%d items in Systems" (length results)))))
89
90(defun org-batch-test-count-by-state ()
91 "Test org-batch-count-by-state function."
92 (let ((counts (org-batch-count-by-state org-batch-test-file)))
93 (org-batch-test-assert
94 "count-by-state: returns alist"
95 (and (listp counts) (assoc 'total counts))
96 (format "total: %d" (alist-get 'total counts)))
97 (org-batch-test-assert
98 "count-by-state: has TODO count"
99 (assoc 'TODO counts)
100 (format "TODO: %d" (alist-get 'TODO counts)))))
101
102(defun org-batch-test-search ()
103 "Test org-batch-search function."
104 (let ((results (org-batch-search org-batch-test-file "tekton")))
105 (org-batch-test-assert
106 "search: finds tekton"
107 (and (listp results) (> (length results) 0))
108 (format "%d matches for 'tekton'" (length results)))))
109
110(defun org-batch-test-get-sections ()
111 "Test org-batch-get-sections function."
112 (let ((sections (org-batch-get-sections org-batch-test-file)))
113 (org-batch-test-assert
114 "get-sections: returns list"
115 (and (listp sections) (> (length sections) 0))
116 (format "sections: %s" (string-join (seq-take sections 3) ", ")))))
117
118(defun org-batch-test-get-overdue ()
119 "Test org-batch-get-overdue function."
120 (let ((results (org-batch-get-overdue org-batch-test-file)))
121 (org-batch-test-assert
122 "get-overdue: returns list"
123 (listp results)
124 (format "%d overdue items" (length results)))))
125
126(defun org-batch-test-get-upcoming ()
127 "Test org-batch-get-upcoming function."
128 (let ((results (org-batch-get-upcoming org-batch-test-file 7)))
129 (org-batch-test-assert
130 "get-upcoming: returns list"
131 (listp results)
132 (format "%d items in next 7 days" (length results)))))
133
134(defun org-batch-test-get-recurring-tasks ()
135 "Test org-batch-get-recurring-tasks function."
136 (let ((results (org-batch-get-recurring-tasks org-batch-test-file)))
137 (org-batch-test-assert
138 "get-recurring-tasks: returns list"
139 (listp results)
140 (format "%d recurring tasks" (length results)))
141 (org-batch-test-assert
142 "get-recurring-tasks: has repeater field"
143 (or (null results) (assoc 'repeater (car results)))
144 "repeater field present")))
145
146(defun org-batch-test-get-blocked-tasks ()
147 "Test org-batch-get-blocked-tasks function."
148 (let ((results (org-batch-get-blocked-tasks org-batch-test-file)))
149 (org-batch-test-assert
150 "get-blocked-tasks: returns list"
151 (listp results)
152 (format "%d blocked tasks" (length results)))))
153
154;;; New org-ql Capabilities Tests
155
156(defun org-batch-test-clocked-today ()
157 "Test org-batch-clocked-today function (NEW)."
158 (let ((results (org-batch-clocked-today org-batch-test-file)))
159 (org-batch-test-assert
160 "clocked-today (NEW): returns list"
161 (listp results)
162 (format "%d clocked today" (length results)))))
163
164(defun org-batch-test-habits ()
165 "Test org-batch-habits function (NEW)."
166 (let ((results (org-batch-habits org-batch-test-file)))
167 (org-batch-test-assert
168 "habits (NEW): returns list"
169 (listp results)
170 (format "%d habits" (length results)))))
171
172(defun org-batch-test-priority-items ()
173 "Test org-batch-priority-items function (NEW)."
174 (let ((results (org-batch-priority-items org-batch-test-file 1 2)))
175 (org-batch-test-assert
176 "priority-items (NEW): returns list"
177 (listp results)
178 (format "%d priority 1-2 items" (length results)))))
179
180(defun org-batch-test-with-property ()
181 "Test org-batch-with-property function (NEW)."
182 (let ((results (org-batch-with-property org-batch-test-file "CREATED")))
183 (org-batch-test-assert
184 "with-property (NEW): returns list"
185 (listp results)
186 (format "%d items with CREATED property" (length results)))))
187
188;;; Statistics Tests
189
190(defun org-batch-test-get-statistics ()
191 "Test org-batch-get-statistics function."
192 (let ((stats (org-batch-get-statistics org-batch-test-file)))
193 (org-batch-test-assert
194 "get-statistics: returns alist"
195 (and (listp stats) (assoc 'total stats))
196 (format "total: %d" (alist-get 'total stats)))
197 (org-batch-test-assert
198 "get-statistics: has by_state"
199 (assoc 'by_state stats)
200 "by_state present")
201 (org-batch-test-assert
202 "get-statistics: has overdue_count"
203 (assoc 'overdue_count stats)
204 (format "overdue: %d" (alist-get 'overdue_count stats)))))
205
206(defun org-batch-test-get-priority-distribution ()
207 "Test org-batch-get-priority-distribution function."
208 (let ((dist (org-batch-get-priority-distribution org-batch-test-file)))
209 (org-batch-test-assert
210 "get-priority-distribution: returns alist"
211 (and (listp dist) (= 5 (length dist)))
212 (format "priorities 1-5 present"))))
213
214(defun org-batch-test-get-tag-statistics ()
215 "Test org-batch-get-tag-statistics function."
216 (let ((stats (org-batch-get-tag-statistics org-batch-test-file)))
217 (org-batch-test-assert
218 "get-tag-statistics: returns list"
219 (listp stats)
220 (format "%d unique tags" (length stats)))))
221
222(defun org-batch-test-list-all-tags ()
223 "Test org-batch-list-all-tags function."
224 (let ((tags (org-batch-list-all-tags org-batch-test-file)))
225 (org-batch-test-assert
226 "list-all-tags: returns sorted list"
227 (and (listp tags)
228 (or (null tags)
229 (null (cdr tags)) ; Only 1 tag - nothing to compare
230 (string< (car tags) (cadr tags))))
231 (format "%d tags" (length tags)))))
232
233;;; Output Format Tests
234
235(defun org-batch-test-json-output ()
236 "Test JSON output functions."
237 (let ((json-output (with-output-to-string
238 (org-batch-output-json t '((test . "value"))))))
239 (org-batch-test-assert
240 "json-output: valid JSON"
241 (string-match-p "success" json-output)
242 "contains success field")))
243
244;;; Integration Tests
245
246(defun org-batch-test-children ()
247 "Test org-batch-get-children with real heading."
248 ;; First find a heading with children
249 (let* ((sections (org-batch-get-sections org-batch-test-file))
250 (results (when sections
251 (org-batch-get-children org-batch-test-file (car sections)))))
252 (org-batch-test-assert
253 "get-children: works with real data"
254 (listp results)
255 (format "%d children of '%s'" (length results) (car sections)))))
256
257(defun org-batch-test-get-todo-content ()
258 "Test org-batch-get-todo-content with real heading."
259 (let* ((todos (org-batch-list-todos org-batch-test-file "NEXT"))
260 (heading (when todos (alist-get 'heading (car todos))))
261 (content (when heading (org-batch-get-todo-content org-batch-test-file heading))))
262 (org-batch-test-assert
263 "get-todo-content: returns content"
264 (or (null heading) (listp content))
265 (if content "got content" "no NEXT items to test"))))
266
267;;; Run All Tests
268
269(defun org-batch-run-all-tests ()
270 "Run all tests and print summary."
271 (setq org-batch-test-results '())
272
273 (princ "\n=== org-batch-functions.el v2.0 Test Suite ===\n\n")
274 (princ "--- Read Operations ---\n")
275 (org-batch-test-list-todos)
276 (org-batch-test-list-todos-filter-state)
277 (org-batch-test-list-todos-comma-separated)
278 (org-batch-test-scheduled-today)
279 (org-batch-test-by-section)
280 (org-batch-test-count-by-state)
281 (org-batch-test-search)
282 (org-batch-test-get-sections)
283 (org-batch-test-get-overdue)
284 (org-batch-test-get-upcoming)
285 (org-batch-test-get-recurring-tasks)
286 (org-batch-test-get-blocked-tasks)
287
288 (princ "\n--- New org-ql Capabilities ---\n")
289 (org-batch-test-clocked-today)
290 (org-batch-test-habits)
291 (org-batch-test-priority-items)
292 (org-batch-test-with-property)
293
294 (princ "\n--- Statistics ---\n")
295 (org-batch-test-get-statistics)
296 (org-batch-test-get-priority-distribution)
297 (org-batch-test-get-tag-statistics)
298 (org-batch-test-list-all-tags)
299
300 (princ "\n--- Output Format ---\n")
301 (org-batch-test-json-output)
302
303 (princ "\n--- Integration ---\n")
304 (org-batch-test-children)
305 (org-batch-test-get-todo-content)
306
307 ;; Summary
308 (let* ((total (length org-batch-test-results))
309 (passed (length (seq-filter #'cadr org-batch-test-results)))
310 (failed (- total passed)))
311 (princ (format "\n=== Summary: %d/%d passed" passed total))
312 (when (> failed 0)
313 (princ (format ", %d failed" failed)))
314 (princ " ===\n")
315
316 ;; Return exit code
317 (kill-emacs (if (= failed 0) 0 1))))
318
319(provide 'org-batch-functions-test)
320;;; org-batch-functions-test.el ends here