summaryrefslogtreecommitdiff
path: root/tests/test-keyboard-macros.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-keyboard-macros.el')
-rw-r--r--tests/test-keyboard-macros.el356
1 files changed, 356 insertions, 0 deletions
diff --git a/tests/test-keyboard-macros.el b/tests/test-keyboard-macros.el
new file mode 100644
index 00000000..3a1ae523
--- /dev/null
+++ b/tests/test-keyboard-macros.el
@@ -0,0 +1,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