From 37d53dacf0636ae299b092b54dde78c0658a51c1 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 29 Jun 2026 04:41:40 -0400 Subject: refactor: split custom-misc.el into focused modules custom-misc.el was an incoherent grab-bag, so anything small defaulted to landing there. I split its eight commands by concern. Three moved into new modules: custom-format (region/buffer reformat), custom-counts (word and character counts), and custom-text-transform (fraction glyphs). The other three went to existing homes: the previous-buffer toggle to custom-buffer-file, the delimiter jump to custom-line-paragraph, and the align-regexp space advice with its alignment and fill bindings to custom-whitespace. The C-; bindings, which-key labels, and the six test files moved with their functions, and custom-misc.el is deleted. No behavior change: every command keeps its name and its C-; key. --- tests/test-custom-counts--count-characters.el | 171 +++++++++++++++ ...tom-counts-count-characters-buffer-or-region.el | 231 +++++++++++++++++++++ tests/test-custom-counts-count-words.el | 148 +++++++++++++ tests/test-custom-format-format-region.el | 161 ++++++++++++++ ...custom-line-paragraph-jump-to-matching-paren.el | 197 ++++++++++++++++++ tests/test-custom-misc-cj--count-characters.el | 171 --------------- ...om-misc-cj-count-characters-buffer-or-region.el | 231 --------------------- tests/test-custom-misc-count-words.el | 148 ------------- tests/test-custom-misc-format-region.el | 161 -------------- tests/test-custom-misc-jump-to-matching-paren.el | 197 ------------------ tests/test-custom-misc-replace-fraction-glyphs.el | 185 ----------------- ...ustom-text-transform-replace-fraction-glyphs.el | 185 +++++++++++++++++ tests/test-init-module-headers.el | 4 +- 13 files changed, 1096 insertions(+), 1094 deletions(-) create mode 100644 tests/test-custom-counts--count-characters.el create mode 100644 tests/test-custom-counts-count-characters-buffer-or-region.el create mode 100644 tests/test-custom-counts-count-words.el create mode 100644 tests/test-custom-format-format-region.el create mode 100644 tests/test-custom-line-paragraph-jump-to-matching-paren.el delete mode 100644 tests/test-custom-misc-cj--count-characters.el delete mode 100644 tests/test-custom-misc-cj-count-characters-buffer-or-region.el delete mode 100644 tests/test-custom-misc-count-words.el delete mode 100644 tests/test-custom-misc-format-region.el delete mode 100644 tests/test-custom-misc-jump-to-matching-paren.el delete mode 100644 tests/test-custom-misc-replace-fraction-glyphs.el create mode 100644 tests/test-custom-text-transform-replace-fraction-glyphs.el (limited to 'tests') diff --git a/tests/test-custom-counts--count-characters.el b/tests/test-custom-counts--count-characters.el new file mode 100644 index 000000000..8abd759f9 --- /dev/null +++ b/tests/test-custom-counts--count-characters.el @@ -0,0 +1,171 @@ +;;; test-custom-counts--count-characters.el --- Tests for cj/--count-characters -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--count-characters internal implementation function from custom-counts.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-counts) + +;;; 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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--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-counts--count-characters) +;;; test-custom-counts--count-characters.el ends here diff --git a/tests/test-custom-counts-count-characters-buffer-or-region.el b/tests/test-custom-counts-count-characters-buffer-or-region.el new file mode 100644 index 000000000..adeb812a8 --- /dev/null +++ b/tests/test-custom-counts-count-characters-buffer-or-region.el @@ -0,0 +1,231 @@ +;;; test-custom-counts-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-counts.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-counts) + +;;; 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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-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-counts-count-characters-buffer-or-region) +;;; test-custom-counts-count-characters-buffer-or-region.el ends here diff --git a/tests/test-custom-counts-count-words.el b/tests/test-custom-counts-count-words.el new file mode 100644 index 000000000..642a5a411 --- /dev/null +++ b/tests/test-custom-counts-count-words.el @@ -0,0 +1,148 @@ +;;; test-custom-counts-count-words.el --- Tests for cj/--count-words -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--count-words function from custom-counts.el +;; +;; This function counts words in a region using Emacs's built-in count-words. +;; A word is defined by Emacs's word boundaries, which generally means +;; sequences of word-constituent characters separated by whitespace or punctuation. +;; +;; We test the NON-INTERACTIVE implementation (cj/--count-words) to avoid +;; mocking region selection. This follows our testing best practice of +;; separating business logic from UI interaction. + +;;; Code: + +(require 'ert) +(require 'testutil-general) + +;; 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-counts) + +;;; Test Helpers + +(defun test-count-words (input-text) + "Test cj/--count-words on INPUT-TEXT. +Returns the word count." + (with-temp-buffer + (insert input-text) + (cj/--count-words (point-min) (point-max)))) + +;;; Normal Cases + +(ert-deftest test-count-words-multiple-words () + "Should count multiple words." + (should (= 5 (test-count-words "The quick brown fox jumps")))) + +(ert-deftest test-count-words-single-word () + "Should count single word." + (should (= 1 (test-count-words "hello")))) + +(ert-deftest test-count-words-with-punctuation () + "Should count words with punctuation." + (should (= 5 (test-count-words "Hello, world! How are you?")))) + +(ert-deftest test-count-words-multiple-spaces () + "Should count words separated by multiple spaces." + (should (= 3 (test-count-words "hello world test")))) + +(ert-deftest test-count-words-with-newlines () + "Should count words across newlines." + (should (= 6 (test-count-words "line one\nline two\nline three")))) + +(ert-deftest test-count-words-with-tabs () + "Should count words separated by tabs." + (should (= 3 (test-count-words "hello\tworld\ttest")))) + +(ert-deftest test-count-words-mixed-whitespace () + "Should count words with mixed whitespace." + (should (= 4 (test-count-words "hello \t world\n\ntest end")))) + +(ert-deftest test-count-words-hyphenated () + "Should count hyphenated words." + ;; Emacs treats hyphens as word separators in count-words + (should (= 7 (test-count-words "This is state-of-the-art technology")))) + +(ert-deftest test-count-words-contractions () + "Should count contractions." + ;; Emacs treats apostrophes as word separators in count-words + (should (= 6 (test-count-words "don't can't won't")))) + +(ert-deftest test-count-words-numbers () + "Should count numbers as words." + (should (= 6 (test-count-words "The year 2024 has 365 days")))) + +;;; Boundary Cases + +(ert-deftest test-count-words-empty-string () + "Should return 0 for empty string." + (should (= 0 (test-count-words "")))) + +(ert-deftest test-count-words-only-whitespace () + "Should return 0 for whitespace-only text." + (should (= 0 (test-count-words " \t\n\n ")))) + +(ert-deftest test-count-words-only-punctuation () + "Should count punctuation-only text." + ;; Emacs may count consecutive punctuation as a word + (should (= 1 (test-count-words "!@#$%^&*()")))) + +(ert-deftest test-count-words-leading-trailing-spaces () + "Should count words ignoring leading/trailing spaces." + (should (= 3 (test-count-words " hello world test ")))) + +(ert-deftest test-count-words-unicode () + "Should count Unicode words." + (should (= 3 (test-count-words "café résumé naïve")))) + +(ert-deftest test-count-words-very-long-text () + "Should handle very long text." + (let ((long-text (mapconcat (lambda (_) "word") (make-list 1000 nil) " "))) + (should (= 1000 (test-count-words long-text))))) + +(ert-deftest test-count-words-multiline-paragraph () + "Should count words in multi-line paragraph." + (let ((text "This is a paragraph +that spans multiple +lines with various +words in it.")) + (should (= 13 (test-count-words text))))) + +;;; Error Cases + +(ert-deftest test-count-words-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--count-words (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-count-words-empty-region () + "Should return 0 for empty region (start == end)." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (= 0 (cj/--count-words pos pos)))))) + +(ert-deftest test-count-words-partial-region () + "Should count words only in specified region." + (with-temp-buffer + (insert "one two three four five") + ;; Count only "two three four" (positions roughly in middle) + (goto-char (point-min)) + (search-forward "two") + (let ((start (match-beginning 0))) + (search-forward "four") + (let ((end (match-end 0))) + (should (= 3 (cj/--count-words start end))))))) + +(provide 'test-custom-counts-count-words) +;;; test-custom-counts-count-words.el ends here diff --git a/tests/test-custom-format-format-region.el b/tests/test-custom-format-format-region.el new file mode 100644 index 000000000..27f1c6b99 --- /dev/null +++ b/tests/test-custom-format-format-region.el @@ -0,0 +1,161 @@ +;;; test-custom-format-format-region.el --- Tests for cj/--format-region -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--format-region function from custom-format.el +;; +;; This function reformats text by applying three operations: +;; 1. untabify - converts tabs to spaces +;; 2. indent-region - reindents according to major mode +;; 3. delete-trailing-whitespace - removes trailing whitespace +;; +;; Note: indent-region behavior is major-mode dependent. We test in +;; emacs-lisp-mode and fundamental-mode for predictable results. +;; +;; We test the NON-INTERACTIVE implementation (cj/--format-region) +;; to avoid mocking region selection. This follows our testing best practice +;; of separating business logic from UI interaction. + +;;; Code: + +(require 'ert) +(require 'testutil-general) + +;; 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-format) + +;;; Test Helpers + +(defun test-format-region (input-text &optional mode) + "Test cj/--format-region on INPUT-TEXT. +MODE is the major mode to use (defaults to fundamental-mode). +Returns the buffer string after operation." + (with-temp-buffer + (funcall (or mode #'fundamental-mode)) + (insert input-text) + (cj/--format-region (point-min) (point-max)) + (buffer-string))) + +;;; Normal Cases - Tab Conversion + +(ert-deftest test-format-region-converts-tabs () + "Should convert tabs to spaces." + (let ((result (test-format-region "hello\tworld"))) + (should-not (string-match-p "\t" result)) + (should (string-match-p " " result)))) + +(ert-deftest test-format-region-multiple-tabs () + "Should convert multiple tabs." + (let ((result (test-format-region "\t\thello\t\tworld\t\t"))) + (should-not (string-match-p "\t" result)))) + +;;; Normal Cases - Trailing Whitespace + +(ert-deftest test-format-region-removes-trailing-spaces () + "Should remove trailing spaces." + (let ((result (test-format-region "hello world "))) + (should (string= result "hello world")))) + +(ert-deftest test-format-region-removes-trailing-tabs () + "Should remove trailing tabs." + (let ((result (test-format-region "hello world\t\t"))) + (should (string= result "hello world")))) + +(ert-deftest test-format-region-removes-trailing-mixed () + "Should remove trailing mixed whitespace." + (let ((result (test-format-region "hello world \t \t "))) + (should (string= result "hello world")))) + +(ert-deftest test-format-region-multiline-trailing () + "Should remove trailing whitespace from multiple lines." + (let ((result (test-format-region "line1 \nline2\t\t\nline3 \t "))) + (should (string= result "line1\nline2\nline3")))) + +;;; Normal Cases - Combined Operations + +(ert-deftest test-format-region-tabs-and-trailing () + "Should handle both tabs and trailing whitespace." + (let ((result (test-format-region "\thello\tworld\t\t"))) + (should-not (string-match-p "\t" result)) + ;; Should not end with whitespace + (should-not (string-match-p "[ \t]+$" result)))) + +(ert-deftest test-format-region-preserves-interior-spaces () + "Should preserve interior spaces while fixing edges." + (let ((result (test-format-region "\thello world\t"))) + (should (string-match-p "hello world" result)) + (should-not (string-match-p "\t" result)))) + +;;; Normal Cases - Indentation (Mode-Specific) + +(ert-deftest test-format-region-elisp-indentation () + "Should reindent Elisp code." + (let* ((input "(defun foo ()\n(+ 1 2))") + (result (test-format-region input #'emacs-lisp-mode)) + (lines (split-string result "\n"))) + ;; The inner form should be indented - second line should start with 2 spaces + (should (= 2 (length lines))) + (should (string-prefix-p "(defun foo ()" (car lines))) + (should (string-prefix-p " " (cadr lines))))) + +;;; Boundary Cases + +(ert-deftest test-format-region-empty-string () + "Should handle empty string." + (let ((result (test-format-region ""))) + (should (string= result "")))) + +(ert-deftest test-format-region-no-issues () + "Should handle text with no formatting issues (no-op)." + (let ((result (test-format-region "hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-format-region-only-whitespace () + "Should handle text with only whitespace." + (let ((result (test-format-region "\t \t "))) + ;; Should become empty or just spaces, no tabs + (should-not (string-match-p "\t" result)))) + +(ert-deftest test-format-region-single-line () + "Should handle single line." + (let ((result (test-format-region "\thello\t"))) + (should-not (string-match-p "\t" result)))) + +(ert-deftest test-format-region-very-long-text () + "Should handle very long text." + (let* ((long-text (mapconcat (lambda (_) "\thello\t") (make-list 100 nil) "\n")) + (result (test-format-region long-text))) + (should-not (string-match-p "\t" result)))) + +(ert-deftest test-format-region-newlines-preserved () + "Should preserve newlines while fixing formatting." + (let ((result (test-format-region "line1\t \nline2\t \nline3\t "))) + (should (= 2 (cl-count ?\n result))))) + +;;; Error Cases + +(ert-deftest test-format-region-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--format-region (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-format-region-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--format-region pos pos) + ;; Should complete without error + (should (string= (buffer-string) "hello world"))))) + +(provide 'test-custom-format-format-region) +;;; test-custom-format-format-region.el ends here diff --git a/tests/test-custom-line-paragraph-jump-to-matching-paren.el b/tests/test-custom-line-paragraph-jump-to-matching-paren.el new file mode 100644 index 000000000..31853da67 --- /dev/null +++ b/tests/test-custom-line-paragraph-jump-to-matching-paren.el @@ -0,0 +1,197 @@ +;;; test-custom-line-paragraph-jump-to-matching-paren.el --- Tests for cj/jump-to-matching-paren -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/jump-to-matching-paren function from custom-line-paragraph.el +;; +;; This function jumps to matching delimiters using Emacs's sexp navigation. +;; It works with any delimiter that has matching syntax according to the +;; current syntax table (parentheses, brackets, braces, etc.). +;; +;; Unlike other functions in this test suite, this is an INTERACTIVE function +;; that moves point and displays messages. We test it as an integration test +;; by setting up buffers, positioning point, calling the function, and +;; verifying where point ends up. +;; +;; Key behaviors: +;; - When on opening delimiter: jump forward to matching closing delimiter +;; - When on closing delimiter: jump backward to matching opening delimiter +;; - When just after closing delimiter: jump backward to matching opening +;; - When not on delimiter: display message, don't move +;; - When no matching delimiter: display error message, don't move + +;;; Code: + +(require 'ert) +(require 'testutil-general) + +;; 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-line-paragraph) + +;;; Test Helpers + +(defun test-jump-to-matching-paren (text point-position) + "Test cj/jump-to-matching-paren with TEXT and point at POINT-POSITION. +Returns the new point position after calling the function. +POINT-POSITION is 1-indexed (1 = first character)." + (with-temp-buffer + (emacs-lisp-mode) ; Use elisp mode for proper syntax table + (insert text) + (goto-char point-position) + (cj/jump-to-matching-paren) + (point))) + +;;; Normal Cases - Forward Jump (Opening to Closing) + +(ert-deftest test-jump-paren-forward-simple () + "Should jump forward from opening paren to closing paren." + ;; Text: "(hello)" + ;; Start at position 1 (on opening paren) + ;; Should end at position 8 (after closing paren) + (should (= 8 (test-jump-to-matching-paren "(hello)" 1)))) + +(ert-deftest test-jump-paren-forward-nested () + "Should jump forward over nested parens." + ;; Text: "(foo (bar))" + ;; Start at position 1 (on outer opening paren) + ;; Should end at position 12 (after outer closing paren) + (should (= 12 (test-jump-to-matching-paren "(foo (bar))" 1)))) + +(ert-deftest test-jump-paren-forward-inner-nested () + "Should jump forward from inner opening paren." + ;; Text: "(foo (bar))" + ;; Start at position 6 (on inner opening paren) + ;; Should end at position 11 (after inner closing paren) + (should (= 11 (test-jump-to-matching-paren "(foo (bar))" 6)))) + +(ert-deftest test-jump-bracket-forward () + "Should jump forward from opening bracket." + ;; Text: "[1 2 3]" + ;; Start at position 1 + ;; Should end at position 8 + (should (= 8 (test-jump-to-matching-paren "[1 2 3]" 1)))) + +;; Note: Braces are not treated as matching delimiters in emacs-lisp-mode +;; so we don't test them here + +;;; Normal Cases - Backward Jump (Closing to Opening) + +(ert-deftest test-jump-paren-backward-simple () + "Should jump backward from closing paren to opening paren." + ;; Text: "(hello)" + ;; Start at position 7 (on closing paren) + ;; Should end at position 2 (after opening paren) + (should (= 2 (test-jump-to-matching-paren "(hello)" 7)))) + +(ert-deftest test-jump-paren-backward-nested () + "Should jump backward over nested parens from after outer closing." + ;; Text: "(foo (bar))" + ;; Start at position 12 (after outer closing paren) + ;; backward-sexp goes back to before opening paren + (should (= 1 (test-jump-to-matching-paren "(foo (bar))" 12)))) + +(ert-deftest test-jump-paren-backward-inner-nested () + "Should jump backward from inner closing paren." + ;; Text: "(foo (bar))" + ;; Start at position 10 (on inner closing paren) + ;; Should end at position 7 (after inner opening paren) + (should (= 7 (test-jump-to-matching-paren "(foo (bar))" 10)))) + +(ert-deftest test-jump-bracket-backward () + "Should jump backward from after closing bracket." + ;; Text: "[1 2 3]" + ;; Start at position 8 (after ]) + ;; backward-sexp goes back one sexp + (should (= 1 (test-jump-to-matching-paren "[1 2 3]" 8)))) + +;;; Normal Cases - Jump from After Closing Delimiter + +(ert-deftest test-jump-paren-after-closing () + "Should jump backward when just after closing paren." + ;; Text: "(hello)" + ;; Start at position 8 (after closing paren) + ;; backward-sexp goes back one sexp, ending before the opening paren + (should (= 1 (test-jump-to-matching-paren "(hello)" 8)))) + +;;; Boundary Cases - No Movement + +(ert-deftest test-jump-paren-not-on-delimiter () + "Should not move when not on delimiter." + ;; Text: "(hello world)" + ;; Start at position 3 (on 'e' in hello) + ;; Should stay at position 3 + (should (= 3 (test-jump-to-matching-paren "(hello world)" 3)))) + +(ert-deftest test-jump-paren-on-whitespace () + "Should not move when on whitespace." + ;; Text: "(hello world)" + ;; Start at position 7 (on space) + ;; Should stay at position 7 + (should (= 7 (test-jump-to-matching-paren "(hello world)" 7)))) + +;;; Boundary Cases - Unmatched Delimiters + +(ert-deftest test-jump-paren-unmatched-opening () + "Should not move from unmatched opening paren." + ;; Text: "(hello" + ;; Start at position 1 (on opening paren with no closing) + ;; Should stay at position 1 + (should (= 1 (test-jump-to-matching-paren "(hello" 1)))) + +(ert-deftest test-jump-paren-unmatched-closing () + "Should move to beginning from unmatched closing paren." + ;; Text: "hello)" + ;; Start at position 6 (on closing paren with no opening) + ;; backward-sexp with unmatched closing paren goes to beginning + (should (= 1 (test-jump-to-matching-paren "hello)" 6)))) + +;;; Boundary Cases - Empty Delimiters + +(ert-deftest test-jump-paren-empty () + "Should jump over empty parens." + ;; Text: "()" + ;; Start at position 1 + ;; Should end at position 3 + (should (= 3 (test-jump-to-matching-paren "()" 1)))) + +(ert-deftest test-jump-paren-empty-backward () + "Should stay put when on closing paren of empty parens." + ;; Text: "()" + ;; Start at position 2 (on closing paren) + ;; backward-sexp from closing of empty parens gives an error, so stays at 2 + (should (= 2 (test-jump-to-matching-paren "()" 2)))) + +;;; Boundary Cases - Multiple Delimiter Types + +(ert-deftest test-jump-paren-mixed-delimiters () + "Should jump over mixed delimiter types." + ;; Text: "(foo [bar {baz}])" + ;; Start at position 1 (on opening paren) + ;; Should end at position 18 (after closing paren) + (should (= 18 (test-jump-to-matching-paren "(foo [bar {baz}])" 1)))) + +(ert-deftest test-jump-bracket-in-parens () + "Should jump from bracket inside parens." + ;; Text: "(foo [bar])" + ;; Start at position 6 (on opening bracket) + ;; Should end at position 11 (after closing bracket) + (should (= 11 (test-jump-to-matching-paren "(foo [bar])" 6)))) + +;;; Complex Cases - Strings and Comments + +(ert-deftest test-jump-paren-over-string () + "Should jump over parens containing strings." + ;; Text: "(\"hello (world)\")" + ;; Start at position 1 (on opening paren) + ;; Should end at position 18 (after closing paren) + ;; The parens in the string should be ignored + (should (= 18 (test-jump-to-matching-paren "(\"hello (world)\")" 1)))) + +(provide 'test-custom-line-paragraph-jump-to-matching-paren) +;;; test-custom-line-paragraph-jump-to-matching-paren.el ends here diff --git a/tests/test-custom-misc-cj--count-characters.el b/tests/test-custom-misc-cj--count-characters.el deleted file mode 100644 index 1834b5c4f..000000000 --- a/tests/test-custom-misc-cj--count-characters.el +++ /dev/null @@ -1,171 +0,0 @@ -;;; 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 deleted file mode 100644 index dbbda00d8..000000000 --- a/tests/test-custom-misc-cj-count-characters-buffer-or-region.el +++ /dev/null @@ -1,231 +0,0 @@ -;;; 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 diff --git a/tests/test-custom-misc-count-words.el b/tests/test-custom-misc-count-words.el deleted file mode 100644 index f2bf793f4..000000000 --- a/tests/test-custom-misc-count-words.el +++ /dev/null @@ -1,148 +0,0 @@ -;;; test-custom-misc-count-words.el --- Tests for cj/--count-words -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for the cj/--count-words function from custom-misc.el -;; -;; This function counts words in a region using Emacs's built-in count-words. -;; A word is defined by Emacs's word boundaries, which generally means -;; sequences of word-constituent characters separated by whitespace or punctuation. -;; -;; We test the NON-INTERACTIVE implementation (cj/--count-words) to avoid -;; mocking region selection. This follows our testing best practice of -;; separating business logic from UI interaction. - -;;; Code: - -(require 'ert) -(require 'testutil-general) - -;; 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) - -;;; Test Helpers - -(defun test-count-words (input-text) - "Test cj/--count-words on INPUT-TEXT. -Returns the word count." - (with-temp-buffer - (insert input-text) - (cj/--count-words (point-min) (point-max)))) - -;;; Normal Cases - -(ert-deftest test-count-words-multiple-words () - "Should count multiple words." - (should (= 5 (test-count-words "The quick brown fox jumps")))) - -(ert-deftest test-count-words-single-word () - "Should count single word." - (should (= 1 (test-count-words "hello")))) - -(ert-deftest test-count-words-with-punctuation () - "Should count words with punctuation." - (should (= 5 (test-count-words "Hello, world! How are you?")))) - -(ert-deftest test-count-words-multiple-spaces () - "Should count words separated by multiple spaces." - (should (= 3 (test-count-words "hello world test")))) - -(ert-deftest test-count-words-with-newlines () - "Should count words across newlines." - (should (= 6 (test-count-words "line one\nline two\nline three")))) - -(ert-deftest test-count-words-with-tabs () - "Should count words separated by tabs." - (should (= 3 (test-count-words "hello\tworld\ttest")))) - -(ert-deftest test-count-words-mixed-whitespace () - "Should count words with mixed whitespace." - (should (= 4 (test-count-words "hello \t world\n\ntest end")))) - -(ert-deftest test-count-words-hyphenated () - "Should count hyphenated words." - ;; Emacs treats hyphens as word separators in count-words - (should (= 7 (test-count-words "This is state-of-the-art technology")))) - -(ert-deftest test-count-words-contractions () - "Should count contractions." - ;; Emacs treats apostrophes as word separators in count-words - (should (= 6 (test-count-words "don't can't won't")))) - -(ert-deftest test-count-words-numbers () - "Should count numbers as words." - (should (= 6 (test-count-words "The year 2024 has 365 days")))) - -;;; Boundary Cases - -(ert-deftest test-count-words-empty-string () - "Should return 0 for empty string." - (should (= 0 (test-count-words "")))) - -(ert-deftest test-count-words-only-whitespace () - "Should return 0 for whitespace-only text." - (should (= 0 (test-count-words " \t\n\n ")))) - -(ert-deftest test-count-words-only-punctuation () - "Should count punctuation-only text." - ;; Emacs may count consecutive punctuation as a word - (should (= 1 (test-count-words "!@#$%^&*()")))) - -(ert-deftest test-count-words-leading-trailing-spaces () - "Should count words ignoring leading/trailing spaces." - (should (= 3 (test-count-words " hello world test ")))) - -(ert-deftest test-count-words-unicode () - "Should count Unicode words." - (should (= 3 (test-count-words "café résumé naïve")))) - -(ert-deftest test-count-words-very-long-text () - "Should handle very long text." - (let ((long-text (mapconcat (lambda (_) "word") (make-list 1000 nil) " "))) - (should (= 1000 (test-count-words long-text))))) - -(ert-deftest test-count-words-multiline-paragraph () - "Should count words in multi-line paragraph." - (let ((text "This is a paragraph -that spans multiple -lines with various -words in it.")) - (should (= 13 (test-count-words text))))) - -;;; Error Cases - -(ert-deftest test-count-words-start-greater-than-end () - "Should error when start > end." - (should-error - (with-temp-buffer - (insert "hello world") - (cj/--count-words (point-max) (point-min))) - :type 'error)) - -(ert-deftest test-count-words-empty-region () - "Should return 0 for empty region (start == end)." - (with-temp-buffer - (insert "hello world") - (let ((pos (/ (+ (point-min) (point-max)) 2))) - (should (= 0 (cj/--count-words pos pos)))))) - -(ert-deftest test-count-words-partial-region () - "Should count words only in specified region." - (with-temp-buffer - (insert "one two three four five") - ;; Count only "two three four" (positions roughly in middle) - (goto-char (point-min)) - (search-forward "two") - (let ((start (match-beginning 0))) - (search-forward "four") - (let ((end (match-end 0))) - (should (= 3 (cj/--count-words start end))))))) - -(provide 'test-custom-misc-count-words) -;;; test-custom-misc-count-words.el ends here diff --git a/tests/test-custom-misc-format-region.el b/tests/test-custom-misc-format-region.el deleted file mode 100644 index c40a8898e..000000000 --- a/tests/test-custom-misc-format-region.el +++ /dev/null @@ -1,161 +0,0 @@ -;;; test-custom-misc-format-region.el --- Tests for cj/--format-region -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for the cj/--format-region function from custom-misc.el -;; -;; This function reformats text by applying three operations: -;; 1. untabify - converts tabs to spaces -;; 2. indent-region - reindents according to major mode -;; 3. delete-trailing-whitespace - removes trailing whitespace -;; -;; Note: indent-region behavior is major-mode dependent. We test in -;; emacs-lisp-mode and fundamental-mode for predictable results. -;; -;; We test the NON-INTERACTIVE implementation (cj/--format-region) -;; to avoid mocking region selection. This follows our testing best practice -;; of separating business logic from UI interaction. - -;;; Code: - -(require 'ert) -(require 'testutil-general) - -;; 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) - -;;; Test Helpers - -(defun test-format-region (input-text &optional mode) - "Test cj/--format-region on INPUT-TEXT. -MODE is the major mode to use (defaults to fundamental-mode). -Returns the buffer string after operation." - (with-temp-buffer - (funcall (or mode #'fundamental-mode)) - (insert input-text) - (cj/--format-region (point-min) (point-max)) - (buffer-string))) - -;;; Normal Cases - Tab Conversion - -(ert-deftest test-format-region-converts-tabs () - "Should convert tabs to spaces." - (let ((result (test-format-region "hello\tworld"))) - (should-not (string-match-p "\t" result)) - (should (string-match-p " " result)))) - -(ert-deftest test-format-region-multiple-tabs () - "Should convert multiple tabs." - (let ((result (test-format-region "\t\thello\t\tworld\t\t"))) - (should-not (string-match-p "\t" result)))) - -;;; Normal Cases - Trailing Whitespace - -(ert-deftest test-format-region-removes-trailing-spaces () - "Should remove trailing spaces." - (let ((result (test-format-region "hello world "))) - (should (string= result "hello world")))) - -(ert-deftest test-format-region-removes-trailing-tabs () - "Should remove trailing tabs." - (let ((result (test-format-region "hello world\t\t"))) - (should (string= result "hello world")))) - -(ert-deftest test-format-region-removes-trailing-mixed () - "Should remove trailing mixed whitespace." - (let ((result (test-format-region "hello world \t \t "))) - (should (string= result "hello world")))) - -(ert-deftest test-format-region-multiline-trailing () - "Should remove trailing whitespace from multiple lines." - (let ((result (test-format-region "line1 \nline2\t\t\nline3 \t "))) - (should (string= result "line1\nline2\nline3")))) - -;;; Normal Cases - Combined Operations - -(ert-deftest test-format-region-tabs-and-trailing () - "Should handle both tabs and trailing whitespace." - (let ((result (test-format-region "\thello\tworld\t\t"))) - (should-not (string-match-p "\t" result)) - ;; Should not end with whitespace - (should-not (string-match-p "[ \t]+$" result)))) - -(ert-deftest test-format-region-preserves-interior-spaces () - "Should preserve interior spaces while fixing edges." - (let ((result (test-format-region "\thello world\t"))) - (should (string-match-p "hello world" result)) - (should-not (string-match-p "\t" result)))) - -;;; Normal Cases - Indentation (Mode-Specific) - -(ert-deftest test-format-region-elisp-indentation () - "Should reindent Elisp code." - (let* ((input "(defun foo ()\n(+ 1 2))") - (result (test-format-region input #'emacs-lisp-mode)) - (lines (split-string result "\n"))) - ;; The inner form should be indented - second line should start with 2 spaces - (should (= 2 (length lines))) - (should (string-prefix-p "(defun foo ()" (car lines))) - (should (string-prefix-p " " (cadr lines))))) - -;;; Boundary Cases - -(ert-deftest test-format-region-empty-string () - "Should handle empty string." - (let ((result (test-format-region ""))) - (should (string= result "")))) - -(ert-deftest test-format-region-no-issues () - "Should handle text with no formatting issues (no-op)." - (let ((result (test-format-region "hello world"))) - (should (string= result "hello world")))) - -(ert-deftest test-format-region-only-whitespace () - "Should handle text with only whitespace." - (let ((result (test-format-region "\t \t "))) - ;; Should become empty or just spaces, no tabs - (should-not (string-match-p "\t" result)))) - -(ert-deftest test-format-region-single-line () - "Should handle single line." - (let ((result (test-format-region "\thello\t"))) - (should-not (string-match-p "\t" result)))) - -(ert-deftest test-format-region-very-long-text () - "Should handle very long text." - (let* ((long-text (mapconcat (lambda (_) "\thello\t") (make-list 100 nil) "\n")) - (result (test-format-region long-text))) - (should-not (string-match-p "\t" result)))) - -(ert-deftest test-format-region-newlines-preserved () - "Should preserve newlines while fixing formatting." - (let ((result (test-format-region "line1\t \nline2\t \nline3\t "))) - (should (= 2 (cl-count ?\n result))))) - -;;; Error Cases - -(ert-deftest test-format-region-start-greater-than-end () - "Should error when start > end." - (should-error - (with-temp-buffer - (insert "hello world") - (cj/--format-region (point-max) (point-min))) - :type 'error)) - -(ert-deftest test-format-region-empty-region () - "Should handle empty region (start == end) without error." - (with-temp-buffer - (insert "hello world") - (let ((pos (/ (+ (point-min) (point-max)) 2))) - (cj/--format-region pos pos) - ;; Should complete without error - (should (string= (buffer-string) "hello world"))))) - -(provide 'test-custom-misc-format-region) -;;; test-custom-misc-format-region.el ends here diff --git a/tests/test-custom-misc-jump-to-matching-paren.el b/tests/test-custom-misc-jump-to-matching-paren.el deleted file mode 100644 index 973b6dfa9..000000000 --- a/tests/test-custom-misc-jump-to-matching-paren.el +++ /dev/null @@ -1,197 +0,0 @@ -;;; test-custom-misc-jump-to-matching-paren.el --- Tests for cj/jump-to-matching-paren -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for the cj/jump-to-matching-paren function from custom-misc.el -;; -;; This function jumps to matching delimiters using Emacs's sexp navigation. -;; It works with any delimiter that has matching syntax according to the -;; current syntax table (parentheses, brackets, braces, etc.). -;; -;; Unlike other functions in this test suite, this is an INTERACTIVE function -;; that moves point and displays messages. We test it as an integration test -;; by setting up buffers, positioning point, calling the function, and -;; verifying where point ends up. -;; -;; Key behaviors: -;; - When on opening delimiter: jump forward to matching closing delimiter -;; - When on closing delimiter: jump backward to matching opening delimiter -;; - When just after closing delimiter: jump backward to matching opening -;; - When not on delimiter: display message, don't move -;; - When no matching delimiter: display error message, don't move - -;;; Code: - -(require 'ert) -(require 'testutil-general) - -;; 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) - -;;; Test Helpers - -(defun test-jump-to-matching-paren (text point-position) - "Test cj/jump-to-matching-paren with TEXT and point at POINT-POSITION. -Returns the new point position after calling the function. -POINT-POSITION is 1-indexed (1 = first character)." - (with-temp-buffer - (emacs-lisp-mode) ; Use elisp mode for proper syntax table - (insert text) - (goto-char point-position) - (cj/jump-to-matching-paren) - (point))) - -;;; Normal Cases - Forward Jump (Opening to Closing) - -(ert-deftest test-jump-paren-forward-simple () - "Should jump forward from opening paren to closing paren." - ;; Text: "(hello)" - ;; Start at position 1 (on opening paren) - ;; Should end at position 8 (after closing paren) - (should (= 8 (test-jump-to-matching-paren "(hello)" 1)))) - -(ert-deftest test-jump-paren-forward-nested () - "Should jump forward over nested parens." - ;; Text: "(foo (bar))" - ;; Start at position 1 (on outer opening paren) - ;; Should end at position 12 (after outer closing paren) - (should (= 12 (test-jump-to-matching-paren "(foo (bar))" 1)))) - -(ert-deftest test-jump-paren-forward-inner-nested () - "Should jump forward from inner opening paren." - ;; Text: "(foo (bar))" - ;; Start at position 6 (on inner opening paren) - ;; Should end at position 11 (after inner closing paren) - (should (= 11 (test-jump-to-matching-paren "(foo (bar))" 6)))) - -(ert-deftest test-jump-bracket-forward () - "Should jump forward from opening bracket." - ;; Text: "[1 2 3]" - ;; Start at position 1 - ;; Should end at position 8 - (should (= 8 (test-jump-to-matching-paren "[1 2 3]" 1)))) - -;; Note: Braces are not treated as matching delimiters in emacs-lisp-mode -;; so we don't test them here - -;;; Normal Cases - Backward Jump (Closing to Opening) - -(ert-deftest test-jump-paren-backward-simple () - "Should jump backward from closing paren to opening paren." - ;; Text: "(hello)" - ;; Start at position 7 (on closing paren) - ;; Should end at position 2 (after opening paren) - (should (= 2 (test-jump-to-matching-paren "(hello)" 7)))) - -(ert-deftest test-jump-paren-backward-nested () - "Should jump backward over nested parens from after outer closing." - ;; Text: "(foo (bar))" - ;; Start at position 12 (after outer closing paren) - ;; backward-sexp goes back to before opening paren - (should (= 1 (test-jump-to-matching-paren "(foo (bar))" 12)))) - -(ert-deftest test-jump-paren-backward-inner-nested () - "Should jump backward from inner closing paren." - ;; Text: "(foo (bar))" - ;; Start at position 10 (on inner closing paren) - ;; Should end at position 7 (after inner opening paren) - (should (= 7 (test-jump-to-matching-paren "(foo (bar))" 10)))) - -(ert-deftest test-jump-bracket-backward () - "Should jump backward from after closing bracket." - ;; Text: "[1 2 3]" - ;; Start at position 8 (after ]) - ;; backward-sexp goes back one sexp - (should (= 1 (test-jump-to-matching-paren "[1 2 3]" 8)))) - -;;; Normal Cases - Jump from After Closing Delimiter - -(ert-deftest test-jump-paren-after-closing () - "Should jump backward when just after closing paren." - ;; Text: "(hello)" - ;; Start at position 8 (after closing paren) - ;; backward-sexp goes back one sexp, ending before the opening paren - (should (= 1 (test-jump-to-matching-paren "(hello)" 8)))) - -;;; Boundary Cases - No Movement - -(ert-deftest test-jump-paren-not-on-delimiter () - "Should not move when not on delimiter." - ;; Text: "(hello world)" - ;; Start at position 3 (on 'e' in hello) - ;; Should stay at position 3 - (should (= 3 (test-jump-to-matching-paren "(hello world)" 3)))) - -(ert-deftest test-jump-paren-on-whitespace () - "Should not move when on whitespace." - ;; Text: "(hello world)" - ;; Start at position 7 (on space) - ;; Should stay at position 7 - (should (= 7 (test-jump-to-matching-paren "(hello world)" 7)))) - -;;; Boundary Cases - Unmatched Delimiters - -(ert-deftest test-jump-paren-unmatched-opening () - "Should not move from unmatched opening paren." - ;; Text: "(hello" - ;; Start at position 1 (on opening paren with no closing) - ;; Should stay at position 1 - (should (= 1 (test-jump-to-matching-paren "(hello" 1)))) - -(ert-deftest test-jump-paren-unmatched-closing () - "Should move to beginning from unmatched closing paren." - ;; Text: "hello)" - ;; Start at position 6 (on closing paren with no opening) - ;; backward-sexp with unmatched closing paren goes to beginning - (should (= 1 (test-jump-to-matching-paren "hello)" 6)))) - -;;; Boundary Cases - Empty Delimiters - -(ert-deftest test-jump-paren-empty () - "Should jump over empty parens." - ;; Text: "()" - ;; Start at position 1 - ;; Should end at position 3 - (should (= 3 (test-jump-to-matching-paren "()" 1)))) - -(ert-deftest test-jump-paren-empty-backward () - "Should stay put when on closing paren of empty parens." - ;; Text: "()" - ;; Start at position 2 (on closing paren) - ;; backward-sexp from closing of empty parens gives an error, so stays at 2 - (should (= 2 (test-jump-to-matching-paren "()" 2)))) - -;;; Boundary Cases - Multiple Delimiter Types - -(ert-deftest test-jump-paren-mixed-delimiters () - "Should jump over mixed delimiter types." - ;; Text: "(foo [bar {baz}])" - ;; Start at position 1 (on opening paren) - ;; Should end at position 18 (after closing paren) - (should (= 18 (test-jump-to-matching-paren "(foo [bar {baz}])" 1)))) - -(ert-deftest test-jump-bracket-in-parens () - "Should jump from bracket inside parens." - ;; Text: "(foo [bar])" - ;; Start at position 6 (on opening bracket) - ;; Should end at position 11 (after closing bracket) - (should (= 11 (test-jump-to-matching-paren "(foo [bar])" 6)))) - -;;; Complex Cases - Strings and Comments - -(ert-deftest test-jump-paren-over-string () - "Should jump over parens containing strings." - ;; Text: "(\"hello (world)\")" - ;; Start at position 1 (on opening paren) - ;; Should end at position 18 (after closing paren) - ;; The parens in the string should be ignored - (should (= 18 (test-jump-to-matching-paren "(\"hello (world)\")" 1)))) - -(provide 'test-custom-misc-jump-to-matching-paren) -;;; test-custom-misc-jump-to-matching-paren.el ends here diff --git a/tests/test-custom-misc-replace-fraction-glyphs.el b/tests/test-custom-misc-replace-fraction-glyphs.el deleted file mode 100644 index 81d1546e1..000000000 --- a/tests/test-custom-misc-replace-fraction-glyphs.el +++ /dev/null @@ -1,185 +0,0 @@ -;;; test-custom-misc-replace-fraction-glyphs.el --- Tests for cj/--replace-fraction-glyphs -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for the cj/--replace-fraction-glyphs function from custom-misc.el -;; -;; This function bidirectionally converts between text fractions (1/4) and -;; Unicode fraction glyphs (¼). It supports 5 common fractions: -;; - 1/4 ↔ ¼ -;; - 1/2 ↔ ½ -;; - 3/4 ↔ ¾ -;; - 1/3 ↔ ⅓ -;; - 2/3 ↔ ⅔ -;; -;; We test the NON-INTERACTIVE implementation (cj/--replace-fraction-glyphs) -;; to avoid mocking prefix arguments. This follows our testing best practice -;; of separating business logic from UI interaction. - -;;; Code: - -(require 'ert) -(require 'testutil-general) - -;; 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) - -;;; Test Helpers - -(defun test-replace-fraction-glyphs (input-text to-glyphs) - "Test cj/--replace-fraction-glyphs on INPUT-TEXT. -TO-GLYPHS determines conversion direction. -Returns the buffer string after operation." - (with-temp-buffer - (insert input-text) - (cj/--replace-fraction-glyphs (point-min) (point-max) to-glyphs) - (buffer-string))) - -;;; Normal Cases - Text to Glyphs - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-quarter () - "Should convert 1/4 to ¼." - (let ((result (test-replace-fraction-glyphs "1/4" t))) - (should (string= result "¼")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-half () - "Should convert 1/2 to ½." - (let ((result (test-replace-fraction-glyphs "1/2" t))) - (should (string= result "½")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-three-quarters () - "Should convert 3/4 to ¾." - (let ((result (test-replace-fraction-glyphs "3/4" t))) - (should (string= result "¾")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-third () - "Should convert 1/3 to ⅓." - (let ((result (test-replace-fraction-glyphs "1/3" t))) - (should (string= result "⅓")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-two-thirds () - "Should convert 2/3 to ⅔." - (let ((result (test-replace-fraction-glyphs "2/3" t))) - (should (string= result "⅔")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-multiple () - "Should convert multiple fractions in text." - (let ((result (test-replace-fraction-glyphs "Use 1/4 cup and 1/2 teaspoon" t))) - (should (string= result "Use ¼ cup and ½ teaspoon")))) - -(ert-deftest test-replace-fraction-glyphs-text-to-glyph-all-types () - "Should convert all fraction types." - (let ((result (test-replace-fraction-glyphs "1/4 1/2 3/4 1/3 2/3" t))) - (should (string= result "¼ ½ ¾ ⅓ ⅔")))) - -;;; Normal Cases - Glyphs to Text - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-quarter () - "Should convert ¼ to 1/4." - (let ((result (test-replace-fraction-glyphs "¼" nil))) - (should (string= result "1/4")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-half () - "Should convert ½ to 1/2." - (let ((result (test-replace-fraction-glyphs "½" nil))) - (should (string= result "1/2")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-three-quarters () - "Should convert ¾ to 3/4." - (let ((result (test-replace-fraction-glyphs "¾" nil))) - (should (string= result "3/4")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-third () - "Should convert ⅓ to 1/3." - (let ((result (test-replace-fraction-glyphs "⅓" nil))) - (should (string= result "1/3")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-two-thirds () - "Should convert ⅔ to 2/3." - (let ((result (test-replace-fraction-glyphs "⅔" nil))) - (should (string= result "2/3")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-multiple () - "Should convert multiple glyphs in text." - (let ((result (test-replace-fraction-glyphs "Use ¼ cup and ½ teaspoon" nil))) - (should (string= result "Use 1/4 cup and 1/2 teaspoon")))) - -(ert-deftest test-replace-fraction-glyphs-glyph-to-text-all-types () - "Should convert all glyph types." - (let ((result (test-replace-fraction-glyphs "¼ ½ ¾ ⅓ ⅔" nil))) - (should (string= result "1/4 1/2 3/4 1/3 2/3")))) - -;;; Boundary Cases - -(ert-deftest test-replace-fraction-glyphs-empty-string () - "Should handle empty string." - (let ((result (test-replace-fraction-glyphs "" t))) - (should (string= result "")))) - -(ert-deftest test-replace-fraction-glyphs-no-fractions-to-glyphs () - "Should handle text with no fractions (no-op) when converting to glyphs." - (let ((result (test-replace-fraction-glyphs "hello world" t))) - (should (string= result "hello world")))) - -(ert-deftest test-replace-fraction-glyphs-no-fractions-to-text () - "Should handle text with no glyphs (no-op) when converting to text." - (let ((result (test-replace-fraction-glyphs "hello world" nil))) - (should (string= result "hello world")))) - -(ert-deftest test-replace-fraction-glyphs-at-start () - "Should handle fraction at start of text." - (let ((result (test-replace-fraction-glyphs "1/2 of the total" t))) - (should (string= result "½ of the total")))) - -(ert-deftest test-replace-fraction-glyphs-at-end () - "Should handle fraction at end of text." - (let ((result (test-replace-fraction-glyphs "Reduce by 1/4" t))) - (should (string= result "Reduce by ¼")))) - -(ert-deftest test-replace-fraction-glyphs-repeated () - "Should handle repeated fractions." - (let ((result (test-replace-fraction-glyphs "1/4 and 1/4 and 1/4" t))) - (should (string= result "¼ and ¼ and ¼")))) - -(ert-deftest test-replace-fraction-glyphs-very-long-text () - "Should handle very long text with many fractions." - (let* ((long-text (mapconcat (lambda (_) "1/4") (make-list 50 nil) " ")) - (result (test-replace-fraction-glyphs long-text t))) - (should (string-match-p "¼" result)) - (should-not (string-match-p "1/4" result)))) - -(ert-deftest test-replace-fraction-glyphs-bidirectional () - "Should correctly convert back and forth." - (let* ((original "Use 1/4 cup") - (to-glyph (test-replace-fraction-glyphs original t)) - (back-to-text (test-replace-fraction-glyphs to-glyph nil))) - (should (string= to-glyph "Use ¼ cup")) - (should (string= back-to-text original)))) - -;;; Error Cases - -(ert-deftest test-replace-fraction-glyphs-start-greater-than-end () - "Should error when start > end." - (should-error - (with-temp-buffer - (insert "1/4") - (cj/--replace-fraction-glyphs (point-max) (point-min) t)) - :type 'error)) - -(ert-deftest test-replace-fraction-glyphs-empty-region () - "Should handle empty region (start == end) without error." - (with-temp-buffer - (insert "1/4") - (let ((pos (/ (+ (point-min) (point-max)) 2))) - (cj/--replace-fraction-glyphs pos pos t) - ;; Should complete without error - (should (string= (buffer-string) "1/4"))))) - -(provide 'test-custom-misc-replace-fraction-glyphs) -;;; test-custom-misc-replace-fraction-glyphs.el ends here diff --git a/tests/test-custom-text-transform-replace-fraction-glyphs.el b/tests/test-custom-text-transform-replace-fraction-glyphs.el new file mode 100644 index 000000000..ed961c63e --- /dev/null +++ b/tests/test-custom-text-transform-replace-fraction-glyphs.el @@ -0,0 +1,185 @@ +;;; test-custom-text-transform-replace-fraction-glyphs.el --- Tests for cj/--replace-fraction-glyphs -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--replace-fraction-glyphs function from custom-text-transform.el +;; +;; This function bidirectionally converts between text fractions (1/4) and +;; Unicode fraction glyphs (¼). It supports 5 common fractions: +;; - 1/4 ↔ ¼ +;; - 1/2 ↔ ½ +;; - 3/4 ↔ ¾ +;; - 1/3 ↔ ⅓ +;; - 2/3 ↔ ⅔ +;; +;; We test the NON-INTERACTIVE implementation (cj/--replace-fraction-glyphs) +;; to avoid mocking prefix arguments. This follows our testing best practice +;; of separating business logic from UI interaction. + +;;; Code: + +(require 'ert) +(require 'testutil-general) + +;; 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-text-transform) + +;;; Test Helpers + +(defun test-replace-fraction-glyphs (input-text to-glyphs) + "Test cj/--replace-fraction-glyphs on INPUT-TEXT. +TO-GLYPHS determines conversion direction. +Returns the buffer string after operation." + (with-temp-buffer + (insert input-text) + (cj/--replace-fraction-glyphs (point-min) (point-max) to-glyphs) + (buffer-string))) + +;;; Normal Cases - Text to Glyphs + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-quarter () + "Should convert 1/4 to ¼." + (let ((result (test-replace-fraction-glyphs "1/4" t))) + (should (string= result "¼")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-half () + "Should convert 1/2 to ½." + (let ((result (test-replace-fraction-glyphs "1/2" t))) + (should (string= result "½")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-three-quarters () + "Should convert 3/4 to ¾." + (let ((result (test-replace-fraction-glyphs "3/4" t))) + (should (string= result "¾")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-third () + "Should convert 1/3 to ⅓." + (let ((result (test-replace-fraction-glyphs "1/3" t))) + (should (string= result "⅓")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-two-thirds () + "Should convert 2/3 to ⅔." + (let ((result (test-replace-fraction-glyphs "2/3" t))) + (should (string= result "⅔")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-multiple () + "Should convert multiple fractions in text." + (let ((result (test-replace-fraction-glyphs "Use 1/4 cup and 1/2 teaspoon" t))) + (should (string= result "Use ¼ cup and ½ teaspoon")))) + +(ert-deftest test-replace-fraction-glyphs-text-to-glyph-all-types () + "Should convert all fraction types." + (let ((result (test-replace-fraction-glyphs "1/4 1/2 3/4 1/3 2/3" t))) + (should (string= result "¼ ½ ¾ ⅓ ⅔")))) + +;;; Normal Cases - Glyphs to Text + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-quarter () + "Should convert ¼ to 1/4." + (let ((result (test-replace-fraction-glyphs "¼" nil))) + (should (string= result "1/4")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-half () + "Should convert ½ to 1/2." + (let ((result (test-replace-fraction-glyphs "½" nil))) + (should (string= result "1/2")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-three-quarters () + "Should convert ¾ to 3/4." + (let ((result (test-replace-fraction-glyphs "¾" nil))) + (should (string= result "3/4")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-third () + "Should convert ⅓ to 1/3." + (let ((result (test-replace-fraction-glyphs "⅓" nil))) + (should (string= result "1/3")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-two-thirds () + "Should convert ⅔ to 2/3." + (let ((result (test-replace-fraction-glyphs "⅔" nil))) + (should (string= result "2/3")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-multiple () + "Should convert multiple glyphs in text." + (let ((result (test-replace-fraction-glyphs "Use ¼ cup and ½ teaspoon" nil))) + (should (string= result "Use 1/4 cup and 1/2 teaspoon")))) + +(ert-deftest test-replace-fraction-glyphs-glyph-to-text-all-types () + "Should convert all glyph types." + (let ((result (test-replace-fraction-glyphs "¼ ½ ¾ ⅓ ⅔" nil))) + (should (string= result "1/4 1/2 3/4 1/3 2/3")))) + +;;; Boundary Cases + +(ert-deftest test-replace-fraction-glyphs-empty-string () + "Should handle empty string." + (let ((result (test-replace-fraction-glyphs "" t))) + (should (string= result "")))) + +(ert-deftest test-replace-fraction-glyphs-no-fractions-to-glyphs () + "Should handle text with no fractions (no-op) when converting to glyphs." + (let ((result (test-replace-fraction-glyphs "hello world" t))) + (should (string= result "hello world")))) + +(ert-deftest test-replace-fraction-glyphs-no-fractions-to-text () + "Should handle text with no glyphs (no-op) when converting to text." + (let ((result (test-replace-fraction-glyphs "hello world" nil))) + (should (string= result "hello world")))) + +(ert-deftest test-replace-fraction-glyphs-at-start () + "Should handle fraction at start of text." + (let ((result (test-replace-fraction-glyphs "1/2 of the total" t))) + (should (string= result "½ of the total")))) + +(ert-deftest test-replace-fraction-glyphs-at-end () + "Should handle fraction at end of text." + (let ((result (test-replace-fraction-glyphs "Reduce by 1/4" t))) + (should (string= result "Reduce by ¼")))) + +(ert-deftest test-replace-fraction-glyphs-repeated () + "Should handle repeated fractions." + (let ((result (test-replace-fraction-glyphs "1/4 and 1/4 and 1/4" t))) + (should (string= result "¼ and ¼ and ¼")))) + +(ert-deftest test-replace-fraction-glyphs-very-long-text () + "Should handle very long text with many fractions." + (let* ((long-text (mapconcat (lambda (_) "1/4") (make-list 50 nil) " ")) + (result (test-replace-fraction-glyphs long-text t))) + (should (string-match-p "¼" result)) + (should-not (string-match-p "1/4" result)))) + +(ert-deftest test-replace-fraction-glyphs-bidirectional () + "Should correctly convert back and forth." + (let* ((original "Use 1/4 cup") + (to-glyph (test-replace-fraction-glyphs original t)) + (back-to-text (test-replace-fraction-glyphs to-glyph nil))) + (should (string= to-glyph "Use ¼ cup")) + (should (string= back-to-text original)))) + +;;; Error Cases + +(ert-deftest test-replace-fraction-glyphs-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "1/4") + (cj/--replace-fraction-glyphs (point-max) (point-min) t)) + :type 'error)) + +(ert-deftest test-replace-fraction-glyphs-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "1/4") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--replace-fraction-glyphs pos pos t) + ;; Should complete without error + (should (string= (buffer-string) "1/4"))))) + +(provide 'test-custom-text-transform-replace-fraction-glyphs) +;;; test-custom-text-transform-replace-fraction-glyphs.el ends here diff --git a/tests/test-init-module-headers.el b/tests/test-init-module-headers.el index 22dec1d5f..c01f19368 100644 --- a/tests/test-init-module-headers.el +++ b/tests/test-init-module-headers.el @@ -35,7 +35,9 @@ "custom-datetime" "custom-buffer-file" "custom-line-paragraph" - "custom-misc" + "custom-counts" + "custom-format" + "custom-text-transform" "custom-ordering" "custom-text-enclose" "custom-whitespace" -- cgit v1.2.3