summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-31 05:54:26 -0500
committerCraig Jennings <c@cjennings.net>2025-10-31 05:55:26 -0500
commitc506f4a197f4018938369506d2ba9a779d096feb (patch)
tree78986bd7bfa5a96e20a06c2fe358514bee75e8a9 /tests
parentf5e927f38247b77afbc401d086e9e4888e8e1263 (diff)
feat:count-characters: Add character counting functionality
Introduce functions to count characters in a buffer or region. Bind new character counting function to "C-; C" in custom keymap.
Diffstat (limited to 'tests')
-rw-r--r--tests/test-custom-misc-cj--count-characters.el171
-rw-r--r--tests/test-custom-misc-cj-count-characters-buffer-or-region.el231
2 files changed, 402 insertions, 0 deletions
diff --git a/tests/test-custom-misc-cj--count-characters.el b/tests/test-custom-misc-cj--count-characters.el
new file mode 100644
index 00000000..1834b5c4
--- /dev/null
+++ b/tests/test-custom-misc-cj--count-characters.el
@@ -0,0 +1,171 @@
+;;; test-custom-misc-cj--count-characters.el --- Tests for cj/--count-characters -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/--count-characters internal implementation function from custom-misc.el
+;;
+;; This internal function counts characters between START and END positions.
+;; It validates that START is not greater than END and returns the character count.
+
+;;; Code:
+
+(require 'ert)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Now load the actual production module
+(require 'custom-misc)
+
+;;; Setup and Teardown
+
+(defun test-count-characters-setup ()
+ "Set up test environment."
+ ;; No setup needed for this function
+ nil)
+
+(defun test-count-characters-teardown ()
+ "Clean up test environment."
+ ;; No teardown needed for this function
+ nil)
+
+;;; Normal Cases
+
+(ert-deftest test-custom-misc-cj--count-characters-normal-simple-text-returns-count ()
+ "Should count characters in simple text region."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ (let ((result (cj/--count-characters 1 14)))
+ (should (= result 13))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-normal-partial-region-returns-count ()
+ "Should count characters in partial region."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ (let ((result (cj/--count-characters 1 6)))
+ (should (= result 5))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-normal-multiline-returns-count ()
+ "Should count characters including newlines."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ ;; 6 + 1 + 6 + 1 + 6 = 20 characters
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ (should (= result 20))))
+ (test-count-characters-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-empty-region-returns-zero ()
+ "Should return 0 for empty region (start equals end)."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello")
+ (let ((result (cj/--count-characters 3 3)))
+ (should (= result 0))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-single-character-returns-one ()
+ "Should return 1 for single character region."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello")
+ (let ((result (cj/--count-characters 1 2)))
+ (should (= result 1))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-large-region-returns-count ()
+ "Should handle very large region."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (let ((large-content (make-string 100000 ?x)))
+ (insert large-content)
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ (should (= result 100000)))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-unicode-returns-count ()
+ "Should count unicode characters (emoji, RTL text, combining characters)."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ ;; "Hello 👋 مرحبا" contains emoji and Arabic text
+ (insert "Hello 👋 مرحبا")
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ ;; Count the actual characters in the buffer
+ (should (= result (- (point-max) (point-min))))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-whitespace-only-returns-count ()
+ "Should count whitespace characters."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert " \t\n ")
+ ;; 3 spaces + 1 tab + 1 newline + 2 spaces = 7 characters
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ (should (= result 7))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-newlines-at-boundaries-returns-count ()
+ "Should count newlines at start and end."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "\n\nHello\n\n")
+ ;; 2 newlines + 5 chars + 2 newlines = 9 characters
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ (should (= result 9))))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-boundary-binary-content-returns-count ()
+ "Should handle binary content."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert (string 0 1 2 255))
+ (let ((result (cj/--count-characters (point-min) (point-max))))
+ (should (= result 4))))
+ (test-count-characters-teardown)))
+
+;;; Error Cases
+
+(ert-deftest test-custom-misc-cj--count-characters-error-start-greater-than-end-signals-error ()
+ "Should signal error when start is greater than end."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ (should-error (cj/--count-characters 10 5)
+ :type 'error))
+ (test-count-characters-teardown)))
+
+(ert-deftest test-custom-misc-cj--count-characters-error-positions-out-of-bounds-handled ()
+ "Should handle positions beyond buffer bounds (Emacs handles this)."
+ (test-count-characters-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello")
+ ;; Emacs will error if positions are truly out of bounds,
+ ;; but this tests that our function doesn't add additional errors
+ ;; Buffer has 6 positions (1-6), testing valid bounds
+ (let ((result (cj/--count-characters 1 6)))
+ (should (= result 5))))
+ (test-count-characters-teardown)))
+
+(provide 'test-custom-misc-cj--count-characters)
+;;; test-custom-misc-cj--count-characters.el ends here
diff --git a/tests/test-custom-misc-cj-count-characters-buffer-or-region.el b/tests/test-custom-misc-cj-count-characters-buffer-or-region.el
new file mode 100644
index 00000000..dbbda00d
--- /dev/null
+++ b/tests/test-custom-misc-cj-count-characters-buffer-or-region.el
@@ -0,0 +1,231 @@
+;;; test-custom-misc-cj-count-characters-buffer-or-region.el --- Tests for cj/count-characters-buffer-or-region -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/count-characters-buffer-or-region function from custom-misc.el
+;;
+;; This function counts characters in the active region or the entire buffer
+;; if no region is active. It displays the count in the minibuffer.
+
+;;; Code:
+
+(require 'ert)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Now load the actual production module
+(require 'custom-misc)
+
+;;; Setup and Teardown
+
+(defun test-count-characters-buffer-or-region-setup ()
+ "Set up test environment."
+ ;; No setup needed
+ nil)
+
+(defun test-count-characters-buffer-or-region-teardown ()
+ "Clean up test environment."
+ ;; Clear any active region
+ (when (use-region-p)
+ (deactivate-mark)))
+
+;;; Normal Cases
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-whole-buffer-counts-all ()
+ "Should count all characters in buffer when no region is active."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ ;; Ensure no region is active
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p "13 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-active-region-counts-region ()
+ "Should count characters in active region."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ ;; Select "Hello" (positions 1-6)
+ (goto-char 1)
+ (push-mark 1)
+ (goto-char 6)
+ (activate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p "5 characters.*region" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-multiline-buffer-counts-all ()
+ "Should count characters including newlines in buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ ;; 6 + 1 + 6 + 1 + 6 = 20 characters
+ (should (string-match-p "20 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-multiline-region-counts-region ()
+ "Should count characters including newlines in region."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ ;; Select first two lines including newlines
+ (goto-char 1)
+ (push-mark 1)
+ (goto-char 14) ; After "Line 1\nLine 2"
+ (activate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ ;; "Line 1\nLine 2" = 6 + 1 + 6 = 13 characters
+ (should (string-match-p "13 characters.*region" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-empty-buffer-returns-zero ()
+ "Should return 0 for empty buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p "0 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-empty-region-counts-buffer ()
+ "Should count whole buffer when region is empty (point equals mark).
+When mark and point are at the same position, use-region-p returns nil,
+so the function correctly falls back to counting the entire buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello, world!")
+ ;; Create empty region (point equals mark)
+ ;; Even with activate-mark, use-region-p returns nil when mark == point
+ (goto-char 5)
+ (push-mark 5)
+ (activate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ ;; Should count the whole buffer (13 characters) not the empty region
+ (should (string-match-p "13 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-large-buffer-counts-all ()
+ "Should handle very large buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (let ((large-content (make-string 100000 ?x)))
+ (insert large-content)
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p "100000 characters.*buffer" message-output))))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-unicode-counts-correctly ()
+ "Should count unicode characters (emoji, RTL text) correctly."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello 👋 مرحبا")
+ (deactivate-mark)
+ (let ((message-output nil)
+ (expected-count (- (point-max) (point-min))))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p (format "%d characters.*buffer" expected-count)
+ message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-whitespace-only-counts-whitespace ()
+ "Should count whitespace characters."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert " \t\n ")
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ ;; 3 spaces + 1 tab + 1 newline + 2 spaces = 7 characters
+ (should (string-match-p "7 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-single-character-returns-one ()
+ "Should return 1 for single character buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "x")
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ (should (string-match-p "1 character.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-narrowed-buffer-counts-visible ()
+ "Should count only visible characters in narrowed buffer."
+ (test-count-characters-buffer-or-region-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3\n")
+ (goto-char (point-min))
+ (forward-line 1)
+ (narrow-to-region (point) (progn (forward-line 1) (point)))
+ (deactivate-mark)
+ (let ((message-output nil))
+ (cl-letf (((symbol-function 'message)
+ (lambda (format-string &rest args)
+ (setq message-output (apply #'format format-string args)))))
+ (cj/count-characters-buffer-or-region)
+ ;; "Line 2\n" = 7 characters
+ (should (string-match-p "7 characters.*buffer" message-output)))))
+ (test-count-characters-buffer-or-region-teardown)))
+
+(provide 'test-custom-misc-cj-count-characters-buffer-or-region)
+;;; test-custom-misc-cj-count-characters-buffer-or-region.el ends here