aboutsummaryrefslogtreecommitdiff
path: root/tests/testutil-events.el
blob: 19b3d55d1b38b95387df6fdc45a2d5f971d64838 (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
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
;;; testutil-events.el --- Event creation and gathering utilities for tests -*- lexical-binding: t; -*-

;; Copyright (C) 2024-2026 Craig Jennings

;; Author: Craig Jennings <c@cjennings.net>

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; Utilities for creating and gathering events in tests.
;; Reduces duplication across test files that work with org events.
;;
;; Key functions:
;; - test-create-org-event: Create org event content string
;; - test-gather-events-from-content: Create file, gather events, clean up
;; - test-make-event-data: Create event data structure programmatically

;;; Code:

(require 'testutil-general)
(require 'testutil-time)

;;; Event Content Creation

(defun test-create-org-event (title time &optional scheduled-p all-day-p)
  "Create org event content string with TITLE at TIME.
If SCHEDULED-P is non-nil, use SCHEDULED: keyword (default is plain timestamp).
If ALL-DAY-P is non-nil, create all-day event without time component.
Returns formatted org content string.

Examples:
  (test-create-org-event \"Meeting\" (test-time-now))
  => \"* Meeting\\n<2025-01-15 Wed 10:00>\\n\"

  (test-create-org-event \"Call\" (test-time-now) t)
  => \"* TODO Call\\nSCHEDULED: <2025-01-15 Wed 10:00>\\n\"

  (test-create-org-event \"Birthday\" (test-time-now) nil t)
  => \"* Birthday\\n<2025-01-15 Wed>\\n\""
  (let ((timestamp (test-timestamp-string time all-day-p))
        (todo-kw (if scheduled-p "TODO " "")))
    (if scheduled-p
        (format "* %s%s\nSCHEDULED: %s\n" todo-kw title timestamp)
      (format "* %s\n%s\n" title timestamp))))

(defun test-create-org-events (event-specs)
  "Create multiple org events from EVENT-SPECS list.
Each spec is (TITLE TIME &optional SCHEDULED-P ALL-DAY-P).
Returns concatenated org content string.

Example:
  (test-create-org-events
   '((\"Meeting\" ,(test-time-at 0 2 0) t)
     (\"Call\" ,(test-time-at 0 4 0) t)))
  => \"* TODO Meeting\\nSCHEDULED: ...\\n* TODO Call\\nSCHEDULED: ...\\n\""
  (mapconcat (lambda (spec)
               (apply #'test-create-org-event spec))
             event-specs
             "\n"))

;;; Event Gathering

(defun test-gather-events-from-content (content)
  "Create temp org file with CONTENT, gather events using chime--gather-info.
Returns list of event data structures.
Automatically creates and cleans up buffer.

Example:
  (let* ((content (test-create-org-event \"Meeting\" (test-time-now) t))
         (events (test-gather-events-from-content content)))
    (should (= 1 (length events)))
    (should (string= \"Meeting\" (cdr (assoc 'title (car events))))))"
  (let* ((test-file (chime-create-temp-test-file-with-content content))
         (test-buffer (find-file-noselect test-file))
         (events nil))
    (unwind-protect
        (with-current-buffer test-buffer
          (org-mode)
          (goto-char (point-min))
          (while (re-search-forward "^\\*+ " nil t)
            (beginning-of-line)
            (push (chime--gather-info (point-marker)) events)
            (forward-line 1))
          (nreverse events))
      (kill-buffer test-buffer))))

(defun test-gather-single-event-from-content (content)
  "Like test-gather-events-from-content but returns single event (not list).
Signals error if content contains multiple events.
Useful when test expects exactly one event.

Example:
  (let* ((content (test-create-org-event \"Call\" (test-time-now) t))
         (event (test-gather-single-event-from-content content)))
    (should (string= \"Call\" (cdr (assoc 'title event)))))"
  (let ((events (test-gather-events-from-content content)))
    (unless (= 1 (length events))
      (error "Expected exactly 1 event, found %d" (length events)))
    (car events)))

;;; Event Data Structure Creation

(defun test-make-event-data (title time-alist &optional intervals)
  "Create event data structure programmatically.
TITLE is the event title string.
TIME-ALIST is list of (TIMESTAMP-STR . TIME-OBJECT) cons cells.
INTERVALS is optional list of (MINUTES . SEVERITY) cons cells.

This is useful for creating events without going through org-mode parsing.

Example:
  (let* ((time (test-time-now))
         (ts-str (test-timestamp-string time))
         (event (test-make-event-data
                 \"Meeting\"
                 (list (cons ts-str time))
                 '((10 . medium)))))
    (should (string= \"Meeting\" (cdr (assoc 'title event)))))"
  `((times . ,time-alist)
    (title . ,title)
    (intervals . ,(or intervals '((10 . medium))))))

(defun test-make-simple-event (title time &optional interval-minutes severity)
  "Create simple event data structure with single time and interval.
TITLE is event title.
TIME is the event time (Emacs time object).
INTERVAL-MINUTES defaults to 10.
SEVERITY defaults to 'medium.

Convenience wrapper around test-make-event-data for common case.

Example:
  (let ((event (test-make-simple-event \"Call\" (test-time-now) 5 'high)))
    (should (string= \"Call\" (cdr (assoc 'title event)))))"
  (let* ((ts-str (test-timestamp-string time))
         (interval (or interval-minutes 10))
         (sev (or severity 'medium)))
    (test-make-event-data title
                          (list (cons ts-str time))
                          (list (cons interval sev)))))

;;; Macros for Common Test Patterns

(defmacro with-test-event-file (content &rest body)
  "Create temp org file with CONTENT, execute BODY, clean up.
Binds `test-file' and `test-buffer' in BODY.

Example:
  (with-test-event-file (test-create-org-event \"Meeting\" (test-time-now))
    (with-current-buffer test-buffer
      (goto-char (point-min))
      (should (search-forward \"Meeting\" nil t))))"
  (declare (indent 1))
  `(let* ((test-file (chime-create-temp-test-file-with-content ,content))
          (test-buffer (find-file-noselect test-file)))
     (unwind-protect
         (progn ,@body)
       (kill-buffer test-buffer))))

(defmacro with-gathered-events (content events-var &rest body)
  "Create temp file with CONTENT, gather events into EVENTS-VAR, execute BODY.
Automatically creates file, gathers events, and cleans up.

Example:
  (with-gathered-events (test-create-org-event \"Call\" (test-time-now))
                        events
    (should (= 1 (length events)))
    (should (string= \"Call\" (cdr (assoc 'title (car events))))))"
  (declare (indent 2))
  `(let ((,events-var (test-gather-events-from-content ,content)))
     ,@body))

;;; Setup/Teardown Helpers

(defun test-standard-setup ()
  "Standard setup for tests: create test base dir.
Most tests can use this instead of custom setup function."
  (chime-create-test-base-dir))

(defun test-standard-teardown ()
  "Standard teardown for tests: delete test base dir.
Most tests can use this instead of custom teardown function."
  (chime-delete-test-base-dir))

(defmacro with-test-setup (&rest body)
  "Execute BODY with standard test setup/teardown.
Ensures test base dir is created before and cleaned up after.

Example:
  (ert-deftest test-something ()
    (with-test-setup
      (let ((file (chime-create-temp-test-file)))
        (should (file-exists-p file)))))"
  (declare (indent 0))
  `(progn
     (test-standard-setup)
     (unwind-protect
         (progn ,@body)
       (test-standard-teardown))))

(provide 'testutil-events)
;;; testutil-events.el ends here