diff options
Diffstat (limited to 'tests/test-keyboard-macros.el')
| -rw-r--r-- | tests/test-keyboard-macros.el | 356 |
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 |
