diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-31 05:54:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-31 05:55:26 -0500 |
| commit | c506f4a197f4018938369506d2ba9a779d096feb (patch) | |
| tree | 78986bd7bfa5a96e20a06c2fe358514bee75e8a9 /tests | |
| parent | f5e927f38247b77afbc401d086e9e4888e8e1263 (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.el | 171 | ||||
| -rw-r--r-- | tests/test-custom-misc-cj-count-characters-buffer-or-region.el | 231 |
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 |
