summaryrefslogtreecommitdiff
path: root/tests/test-keyboard-macros.el
blob: 3a1ae523b1ba9922f335f4ad550ee86f6132ae28 (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
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
;;; test-keyboard-macros.el --- ERT tests for keyboard-macros -*- lexical-binding: t; -*-

;; Author: Claude Code and cjennings
;; Keywords: tests, keyboard-macros

;;; Commentary:
;; ERT tests for keyboard-macros.el functions.
;; Tests are organized into normal, boundary, and error cases.

;;; Code:

(require 'ert)
(require 'keyboard-macros)
(require 'testutil-general)

;;; Setup and Teardown

(defun test-keyboard-macros-setup ()
  "Set up test environment for keyboard-macros tests."
  (cj/create-test-base-dir)
  ;; Bind macros-file to test location
  (setq macros-file (expand-file-name "test-macros.el" cj/test-base-dir))
  ;; Reset state flags
  (setq cj/macros-loaded nil)
  (setq cj/macros-loading nil)
  ;; Clear any existing macro
  (setq last-kbd-macro nil))

(defun test-keyboard-macros-teardown ()
  "Clean up test environment after keyboard-macros tests."
  ;; Kill any buffers visiting the test macros file
  (when-let ((buf (get-file-buffer macros-file)))
    (kill-buffer buf))
  ;; Clean up test directory
  (cj/delete-test-base-dir)
  ;; Reset state
  (setq cj/macros-loaded nil)
  (setq cj/macros-loading nil)
  (setq last-kbd-macro nil))

;;; Normal Cases

(ert-deftest test-keyboard-macros-ensure-macros-loaded-first-time-normal ()
  "Normal: macros file is loaded on first call when file exists."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create a macros file with a simple macro definition
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n")
          (insert "(fset 'test-macro [?h ?e ?l ?l ?o])\n"))
        ;; Verify initial state
        (should (not cj/macros-loaded))
        ;; Load macros
        (cj/ensure-macros-loaded)
        ;; Verify loaded
        (should cj/macros-loaded)
        (should (fboundp 'test-macro)))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-ensure-macros-loaded-idempotent-normal ()
  "Normal: subsequent calls don't reload when flag is already true."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create a macros file
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n"))
        ;; First load
        (cj/ensure-macros-loaded)
        (should cj/macros-loaded)
        ;; Modify the file after loading
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n")
          (insert "(fset 'new-macro [?n ?e ?w])\n"))
        ;; Second call should not reload
        (cj/ensure-macros-loaded)
        ;; new-macro should not be defined because file wasn't reloaded
        (should (not (fboundp 'new-macro))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-ensure-macros-file-creates-new-normal ()
  "Normal: ensure-macros-file creates new file with lexical-binding header."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (should (not (file-exists-p macros-file)))
        (ensure-macros-file macros-file)
        (should (file-exists-p macros-file))
        (with-temp-buffer
          (insert-file-contents macros-file)
          (should (string-match-p "lexical-binding: t" (buffer-string)))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-ensure-macros-file-exists-normal ()
  "Normal: ensure-macros-file leaves existing file untouched."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n")
          (insert "(fset 'existing-macro [?t ?e ?s ?t])\n"))
        (let ((original-content (with-temp-buffer
                                  (insert-file-contents macros-file)
                                  (buffer-string))))
          (ensure-macros-file macros-file)
          (should (string= original-content
                          (with-temp-buffer
                            (insert-file-contents macros-file)
                            (buffer-string))))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-start-or-end-toggle-normal ()
  "Normal: starting and stopping macro recording toggles defining-kbd-macro."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create empty macros file
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n"))
        ;; Start recording
        (should (not defining-kbd-macro))
        (cj/kbd-macro-start-or-end)
        (should defining-kbd-macro)
        ;; Stop recording
        (cj/kbd-macro-start-or-end)
        (should (not defining-kbd-macro)))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-valid-name-normal ()
  "Normal: saving a macro with valid name writes to file and returns name."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Set up a macro
        (setq last-kbd-macro [?t ?e ?s ?t])
        ;; Save it
        (let ((result (cj/save-maybe-edit-macro "test-macro")))
          (should (string= result "test-macro"))
          (should (file-exists-p macros-file))
          ;; Verify macro was written to file
          (with-temp-buffer
            (insert-file-contents macros-file)
            (should (string-match-p "test-macro" (buffer-string))))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-without-prefix-arg-normal ()
  "Normal: without prefix arg, returns to original buffer."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (let ((original-buffer (current-buffer))
              (current-prefix-arg nil))
          (cj/save-maybe-edit-macro "test-macro")
          ;; Should return to original buffer (or stay if it was the macros file)
          (should (or (eq (current-buffer) original-buffer)
                     (not (eq (current-buffer) (get-file-buffer macros-file)))))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-with-prefix-arg-normal ()
  "Normal: with prefix arg, opens macros file for editing."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (let ((current-prefix-arg t))
          (cj/save-maybe-edit-macro "test-macro")
          ;; Should be in the macros file buffer
          (should (eq (current-buffer) (get-file-buffer macros-file)))))
    (test-keyboard-macros-teardown)))

;;; Boundary Cases

(ert-deftest test-keyboard-macros-name-single-character-boundary ()
  "Boundary: macro name with single letter (minimum valid length)."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (let ((result (cj/save-maybe-edit-macro "a")))
          (should (string= result "a"))
          (should (file-exists-p macros-file))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-name-with-numbers-boundary ()
  "Boundary: macro name containing letters, numbers, and hyphens."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (let ((result (cj/save-maybe-edit-macro "macro-123-test")))
          (should (string= result "macro-123-test"))
          (should (file-exists-p macros-file))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-name-all-caps-boundary ()
  "Boundary: macro name with uppercase letters."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (let ((result (cj/save-maybe-edit-macro "TESTMACRO")))
          (should (string= result "TESTMACRO"))
          (should (file-exists-p macros-file))))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-empty-macro-file-boundary ()
  "Boundary: loading behavior when macros file exists but is empty."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create empty file
        (with-temp-file macros-file
          (insert ""))
        (should (not cj/macros-loaded))
        ;; Should handle empty file gracefully
        (cj/ensure-macros-loaded)
        ;; Loading an empty file should still set the flag
        (should cj/macros-loaded))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-whitespace-only-name-boundary ()
  "Boundary: whitespace-only name (spaces, tabs) is rejected."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (should-error (cj/save-maybe-edit-macro "   \t  ")))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-concurrent-load-attempts-boundary ()
  "Boundary: cj/macros-loading lock prevents race conditions."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n"))
        ;; Simulate concurrent load by setting the lock
        (setq cj/macros-loading t)
        (cj/ensure-macros-loaded)
        ;; Should not load because lock is set
        (should (not cj/macros-loaded))
        ;; Release lock and try again
        (setq cj/macros-loading nil)
        (cj/ensure-macros-loaded)
        (should cj/macros-loaded))
    (test-keyboard-macros-teardown)))

;;; Error Cases

(ert-deftest test-keyboard-macros-save-empty-name-error ()
  "Error: empty string name triggers user-error."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (should-error (cj/save-maybe-edit-macro "") :type 'user-error))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-invalid-name-special-chars-error ()
  "Error: names with special characters trigger user-error."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (should-error (cj/save-maybe-edit-macro "test@macro") :type 'user-error)
        (should-error (cj/save-maybe-edit-macro "test!macro") :type 'user-error)
        (should-error (cj/save-maybe-edit-macro "test#macro") :type 'user-error))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-invalid-name-starts-with-number-error ()
  "Error: name starting with number triggers user-error."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (should-error (cj/save-maybe-edit-macro "123macro") :type 'user-error))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-invalid-name-has-spaces-error ()
  "Error: name with spaces triggers user-error."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        (should-error (cj/save-maybe-edit-macro "test macro") :type 'user-error))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-no-macro-defined-error ()
  "Error: saving when last-kbd-macro is nil triggers user-error."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro nil)
        (should-error (cj/save-maybe-edit-macro "test-macro") :type 'user-error))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-load-malformed-file-error ()
  "Error: error handling when macros file has syntax errors."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create a malformed macros file
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n")
          (insert "(fset 'broken-macro [incomplete"))
        (should (not cj/macros-loaded))
        ;; Should handle error gracefully (prints message but doesn't crash)
        (cj/ensure-macros-loaded)
        ;; Should not be marked as loaded due to error
        (should (not cj/macros-loaded)))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-save-file-write-error-error ()
  "Error: error handling when unable to write to macros file."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        (setq last-kbd-macro [?t ?e ?s ?t])
        ;; Create the file and make it read-only
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n"))
        (set-file-modes macros-file #o444)
        ;; Should error when trying to save
        (condition-case err
            (progn
              (cj/save-maybe-edit-macro "test-macro")
              (should nil)) ;; Should not reach here
          (error
           ;; Expected to error
           (should t)))
        ;; Clean up permissions for teardown
        (set-file-modes macros-file #o644))
    (test-keyboard-macros-teardown)))

(ert-deftest test-keyboard-macros-load-file-read-error-error ()
  "Error: error handling when unable to read macros file."
  (test-keyboard-macros-setup)
  (unwind-protect
      (progn
        ;; Create file and remove read permissions
        (with-temp-file macros-file
          (insert ";;; -*- lexical-binding: t -*-\n"))
        (set-file-modes macros-file #o000)
        (should (not cj/macros-loaded))
        ;; Should handle error gracefully
        (cj/ensure-macros-loaded)
        ;; Should not be marked as loaded
        (should (not cj/macros-loaded))
        ;; Clean up permissions for teardown
        (set-file-modes macros-file #o644))
    (test-keyboard-macros-teardown)))

(provide 'test-keyboard-macros)
;;; test-keyboard-macros.el ends here