diff options
| -rw-r--r-- | modules/custom-whitespace.el | 133 | ||||
| -rw-r--r-- | tests/test-custom-whitespace-collapse.el | 150 | ||||
| -rw-r--r-- | tests/test-custom-whitespace-delete-blank-lines.el | 146 | ||||
| -rw-r--r-- | tests/test-custom-whitespace-hyphenate.el | 140 | ||||
| -rw-r--r-- | tests/test-custom-whitespace-remove-leading-trailing.el | 157 |
5 files changed, 682 insertions, 44 deletions
diff --git a/modules/custom-whitespace.el b/modules/custom-whitespace.el index a69d6138..f2a9d60a 100644 --- a/modules/custom-whitespace.el +++ b/modules/custom-whitespace.el @@ -17,14 +17,32 @@ ;;; Code: +(eval-when-compile (defvar cj/custom-keymap)) ;; cj/custom-keymap defined in keybindings.el ;;; ---------------------- Whitespace Operations And Keymap --------------------- +;; ------------------- Remove Leading/Trailing Whitespace --------------------- + +(defun cj/--remove-leading-trailing-whitespace (start end) + "Internal implementation: Remove leading and trailing whitespace. +START and END define the region to operate on. +Removes leading whitespace (^[ \\t]+) and trailing whitespace ([ \\t]+$). +Preserves interior whitespace." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (save-excursion + (save-restriction + (narrow-to-region start end) + (goto-char (point-min)) + (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) + (goto-char (point-min)) + (while (re-search-forward "[ \t]+$" nil t) (replace-match ""))))) + (defun cj/remove-leading-trailing-whitespace () "Remove leading and trailing whitespace in a region, line, or buffer. When called interactively: - If a region is active, operate on the region. -- If called with a \[universal-argument] prefix, operate on the entire buffer. +- If called with a \\[universal-argument] prefix, operate on the entire buffer. - Otherwise, operate on the current line." (interactive) (let ((start (cond (current-prefix-arg (point-min)) @@ -33,36 +51,57 @@ When called interactively: (end (cond (current-prefix-arg (point-max)) ((use-region-p) (region-end)) (t (line-end-position))))) - (save-excursion - (save-restriction - (narrow-to-region start end) - (goto-char (point-min)) - (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) - (goto-char (point-min)) - (while (re-search-forward "[ \t]+$" nil t) (replace-match "")))))) + (cj/--remove-leading-trailing-whitespace start end))) + +;; ----------------------- Collapse Whitespace --------------------------------- + +(defun cj/--collapse-whitespace (start end) + "Internal implementation: Collapse whitespace to single spaces. +START and END define the region to operate on. +Converts tabs to spaces, removes leading/trailing whitespace, +and collapses multiple spaces to single space. +Preserves newlines and line structure." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (save-excursion + (save-restriction + (narrow-to-region start end) + ;; Replace all tabs with space + (goto-char (point-min)) + (while (search-forward "\t" nil t) + (replace-match " " nil t)) + ;; Remove leading and trailing spaces (but not newlines) + (goto-char (point-min)) + (while (re-search-forward "^[ \t]+\\|[ \t]+$" nil t) + (replace-match "" nil nil)) + ;; Ensure only one space between words (but preserve newlines) + (goto-char (point-min)) + (while (re-search-forward "[ \t]\\{2,\\}" nil t) + (replace-match " " nil nil))))) (defun cj/collapse-whitespace-line-or-region () "Collapse whitespace to one space in the current line or active region. -Ensure there is exactly one space between words and remove leading and trailing whitespace." +Ensure there is exactly one space between words and remove leading and +trailing whitespace." (interactive) + (let* ((region-active (use-region-p)) + (beg (if region-active (region-beginning) (line-beginning-position))) + (end (if region-active (region-end) (line-end-position)))) + (cj/--collapse-whitespace beg end))) + +;; ------------------------ Delete Blank Lines --------------------------------- + +(defun cj/--delete-blank-lines (start end) + "Internal implementation: Delete blank lines between START and END. +Blank lines are lines containing only whitespace or nothing. +Uses the regexp ^[[:space:]]*$ to match blank lines." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) (save-excursion - (let* ((region-active (use-region-p)) - (beg (if region-active (region-beginning) (line-beginning-position))) - (end (if region-active (region-end) (line-end-position)))) - (save-restriction - (narrow-to-region beg end) - ;; Replace all tabs with space - (goto-char (point-min)) - (while (search-forward "\t" nil t) - (replace-match " " nil t)) - ;; Remove leading and trailing spaces - (goto-char (point-min)) - (while (re-search-forward "^\\s-+\\|\\s-+$" nil t) - (replace-match "" nil nil)) - ;; Ensure only one space between words/symbols - (goto-char (point-min)) - (while (re-search-forward "\\s-\\{2,\\}" nil t) - (replace-match " " nil nil)))))) + (save-restriction + (widen) + ;; Regexp "^[[:space:]]*$" matches lines of zero or more spaces/tabs/newlines. + (flush-lines "^[[:space:]]*$" start end)))) (defun cj/delete-blank-lines-region-or-buffer (start end) "Delete blank lines between START and END. @@ -73,32 +112,38 @@ Signal a user error and do nothing when the user declines. Restore point to its original position after deletion." (interactive (if (use-region-p) - ;; grab its boundaries if there's a region - (list (region-beginning) (region-end)) - ;; or ask if user intended operating on whole buffer - (if (yes-or-no-p "Delete blank lines in entire buffer? ") - (list (point-min) (point-max)) - (user-error "Aborted")))) - (save-excursion - (save-restriction - (widen) - ;; Regexp "^[[:space:]]*$" matches lines of zero or more spaces/tabs. - (flush-lines "^[[:space:]]*$" start end))) + ;; grab its boundaries if there's a region + (list (region-beginning) (region-end)) + ;; or ask if user intended operating on whole buffer + (if (yes-or-no-p "Delete blank lines in entire buffer? ") + (list (point-min) (point-max)) + (user-error "Aborted")))) + (cj/--delete-blank-lines start end) ;; Return nil (Emacs conventions). Point is already restored. nil) +;; ------------------------- Hyphenate Whitespace ------------------------------ + +(defun cj/--hyphenate-whitespace (start end) + "Internal implementation: Replace whitespace runs with hyphens. +START and END define the region to operate on. +Replaces all runs of spaces, tabs, newlines, and carriage returns with hyphens." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (save-excursion + (save-restriction + (narrow-to-region start end) + (goto-char (point-min)) + (while (re-search-forward "[ \t\n\r]+" nil t) + (replace-match "-"))))) + (defun cj/hyphenate-whitespace-in-region (start end) "Replace runs of whitespace between START and END with hyphens. Operate on the active region designated by START and END." (interactive "*r") (if (use-region-p) - (save-excursion - (save-restriction - (narrow-to-region start end) - (goto-char (point-min)) - (while (re-search-forward "[ \t\n\r]+" nil t) - (replace-match "-")))) - (message "No region; nothing to hyphenate."))) + (cj/--hyphenate-whitespace start end) + (message "No region; nothing to hyphenate."))) ;; Whitespace operations prefix and keymap diff --git a/tests/test-custom-whitespace-collapse.el b/tests/test-custom-whitespace-collapse.el new file mode 100644 index 00000000..40face95 --- /dev/null +++ b/tests/test-custom-whitespace-collapse.el @@ -0,0 +1,150 @@ +;;; test-custom-whitespace-collapse.el --- Tests for cj/--collapse-whitespace -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--collapse-whitespace function from custom-whitespace.el +;; +;; This function collapses whitespace in text by: +;; - Converting all tabs to spaces +;; - Removing leading and trailing whitespace +;; - Collapsing multiple consecutive spaces to single space +;; - Preserving newlines and text structure +;; +;; We test the NON-INTERACTIVE implementation (cj/--collapse-whitespace) +;; 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-whitespace) + +;;; Test Helpers + +(defun test-collapse-whitespace (input-text) + "Test cj/--collapse-whitespace on INPUT-TEXT. +Returns the buffer string after operation." + (with-temp-buffer + (insert input-text) + (cj/--collapse-whitespace (point-min) (point-max)) + (buffer-string))) + +;;; Normal Cases + +(ert-deftest test-collapse-whitespace-multiple-spaces () + "Should collapse multiple spaces to single space." + (let ((result (test-collapse-whitespace "hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-multiple-tabs () + "Should convert tabs to spaces and collapse." + (let ((result (test-collapse-whitespace "hello\t\t\tworld"))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-mixed-tabs-spaces () + "Should handle mixed tabs and spaces." + (let ((result (test-collapse-whitespace "hello \t \t world"))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-leading-trailing () + "Should remove leading and trailing whitespace." + (let ((result (test-collapse-whitespace " hello world "))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-tabs-leading-trailing () + "Should remove leading and trailing tabs." + (let ((result (test-collapse-whitespace "\t\thello world\t\t"))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-multiple-words () + "Should collapse spaces between multiple words." + (let ((result (test-collapse-whitespace "one two three four"))) + (should (string= result "one two three four")))) + +(ert-deftest test-collapse-whitespace-preserve-newlines () + "Should preserve newlines while collapsing spaces." + (let ((result (test-collapse-whitespace "hello world\nfoo bar"))) + (should (string= result "hello world\nfoo bar")))) + +(ert-deftest test-collapse-whitespace-multiple-lines () + "Should handle multiple lines with various whitespace." + (let ((result (test-collapse-whitespace " hello world \n\t\tfoo bar\t\t"))) + (should (string= result "hello world\nfoo bar")))) + +;;; Boundary Cases + +(ert-deftest test-collapse-whitespace-empty-string () + "Should handle empty string." + (let ((result (test-collapse-whitespace ""))) + (should (string= result "")))) + +(ert-deftest test-collapse-whitespace-single-char () + "Should handle single character with surrounding spaces." + (let ((result (test-collapse-whitespace " x "))) + (should (string= result "x")))) + +(ert-deftest test-collapse-whitespace-only-whitespace () + "Should handle text with only whitespace (becomes empty)." + (let ((result (test-collapse-whitespace " \t \t "))) + (should (string= result "")))) + +(ert-deftest test-collapse-whitespace-no-extra-whitespace () + "Should handle text with no extra whitespace (no-op)." + (let ((result (test-collapse-whitespace "hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-collapse-whitespace-single-space () + "Should handle text with already-collapsed spaces (no-op)." + (let ((result (test-collapse-whitespace "one two three"))) + (should (string= result "one two three")))) + +(ert-deftest test-collapse-whitespace-very-long-line () + "Should handle very long lines with many spaces." + (let ((result (test-collapse-whitespace "word word word word word"))) + (should (string= result "word word word word word")))) + +(ert-deftest test-collapse-whitespace-multiple-newlines () + "Should preserve multiple newlines while removing spaces." + (let ((result (test-collapse-whitespace "hello world\n\n\nfoo bar"))) + (should (string= result "hello world\n\n\nfoo bar")))) + +(ert-deftest test-collapse-whitespace-spaces-around-newlines () + "Should remove spaces before/after newlines." + (let ((result (test-collapse-whitespace "hello \n world"))) + (should (string= result "hello\nworld")))) + +(ert-deftest test-collapse-whitespace-empty-lines () + "Should handle empty lines (lines become empty after whitespace removal)." + (let ((result (test-collapse-whitespace "line1\n \nline2"))) + (should (string= result "line1\n\nline2")))) + +;;; Error Cases + +(ert-deftest test-collapse-whitespace-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--collapse-whitespace (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-collapse-whitespace-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--collapse-whitespace pos pos) + ;; Should complete without error and not change buffer + (should (string= (buffer-string) "hello world"))))) + +(provide 'test-custom-whitespace-collapse) +;;; test-custom-whitespace-collapse.el ends here diff --git a/tests/test-custom-whitespace-delete-blank-lines.el b/tests/test-custom-whitespace-delete-blank-lines.el new file mode 100644 index 00000000..2d250521 --- /dev/null +++ b/tests/test-custom-whitespace-delete-blank-lines.el @@ -0,0 +1,146 @@ +;;; test-custom-whitespace-delete-blank-lines.el --- Tests for cj/--delete-blank-lines -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--delete-blank-lines function from custom-whitespace.el +;; +;; This function deletes blank lines from text, where blank lines are defined +;; as lines containing only whitespace (spaces, tabs) or nothing at all. +;; Uses the regexp ^[[:space:]]*$ to match blank lines. +;; +;; We test the NON-INTERACTIVE implementation (cj/--delete-blank-lines) +;; to avoid mocking user prompts. 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-whitespace) + +;;; Test Helpers + +(defun test-delete-blank-lines (input-text) + "Test cj/--delete-blank-lines on INPUT-TEXT. +Returns the buffer string after operation." + (with-temp-buffer + (insert input-text) + (cj/--delete-blank-lines (point-min) (point-max)) + (buffer-string))) + +;;; Normal Cases + +(ert-deftest test-delete-blank-lines-single-blank () + "Should delete single blank line between text." + (let ((result (test-delete-blank-lines "line1\n\nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-multiple-consecutive () + "Should delete multiple consecutive blank lines." + (let ((result (test-delete-blank-lines "line1\n\n\n\nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-spaces-only () + "Should delete lines with spaces only." + (let ((result (test-delete-blank-lines "line1\n \nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-tabs-only () + "Should delete lines with tabs only." + (let ((result (test-delete-blank-lines "line1\n\t\t\nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-mixed-whitespace () + "Should delete lines with mixed whitespace." + (let ((result (test-delete-blank-lines "line1\n \t \t \nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-no-blank-lines () + "Should handle text with no blank lines (no-op)." + (let ((result (test-delete-blank-lines "line1\nline2\nline3"))) + (should (string= result "line1\nline2\nline3")))) + +(ert-deftest test-delete-blank-lines-at-start () + "Should delete blank lines at start of region." + (let ((result (test-delete-blank-lines "\n\nline1\nline2"))) + (should (string= result "line1\nline2")))) + +(ert-deftest test-delete-blank-lines-at-end () + "Should delete blank lines at end of region." + (let ((result (test-delete-blank-lines "line1\nline2\n\n"))) + (should (string= result "line1\nline2\n")))) + +(ert-deftest test-delete-blank-lines-scattered () + "Should delete blank lines scattered throughout text." + (let ((result (test-delete-blank-lines "line1\n\nline2\n \nline3\n\t\nline4"))) + (should (string= result "line1\nline2\nline3\nline4")))) + +;;; Boundary Cases + +(ert-deftest test-delete-blank-lines-empty-string () + "Should handle empty string." + (let ((result (test-delete-blank-lines ""))) + (should (string= result "")))) + +(ert-deftest test-delete-blank-lines-only-blank-lines () + "Should delete all lines if only blank lines exist." + (let ((result (test-delete-blank-lines "\n\n\n"))) + (should (string= result "")))) + +(ert-deftest test-delete-blank-lines-only-whitespace () + "Should delete lines containing only whitespace." + (let ((result (test-delete-blank-lines " \n\t\t\n \t "))) + (should (string= result "")))) + +(ert-deftest test-delete-blank-lines-single-line-content () + "Should handle single line with content (no-op)." + (let ((result (test-delete-blank-lines "hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-delete-blank-lines-single-blank-line () + "Should delete single blank line." + (let ((result (test-delete-blank-lines "\n"))) + (should (string= result "")))) + +(ert-deftest test-delete-blank-lines-very-long-region () + "Should handle very long region with many blank lines." + (let* ((lines (make-list 100 "content")) + (input (mapconcat #'identity lines "\n\n")) + (expected (mapconcat #'identity lines "\n")) + (result (test-delete-blank-lines input))) + (should (string= result expected)))) + +(ert-deftest test-delete-blank-lines-preserve-content-lines () + "Should preserve lines with any non-whitespace content." + (let ((result (test-delete-blank-lines "x\n\ny\n \nz"))) + (should (string= result "x\ny\nz")))) + +;;; Error Cases + +(ert-deftest test-delete-blank-lines-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "line1\n\nline2") + (cj/--delete-blank-lines (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-delete-blank-lines-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "line1\n\nline2") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--delete-blank-lines pos pos) + ;; Should complete without error + (should (string-match-p "line1" (buffer-string)))))) + +(provide 'test-custom-whitespace-delete-blank-lines) +;;; test-custom-whitespace-delete-blank-lines.el ends here diff --git a/tests/test-custom-whitespace-hyphenate.el b/tests/test-custom-whitespace-hyphenate.el new file mode 100644 index 00000000..03462fab --- /dev/null +++ b/tests/test-custom-whitespace-hyphenate.el @@ -0,0 +1,140 @@ +;;; test-custom-whitespace-hyphenate.el --- Tests for cj/--hyphenate-whitespace -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--hyphenate-whitespace function from custom-whitespace.el +;; +;; This function replaces all runs of whitespace (spaces, tabs, newlines, +;; carriage returns) with single hyphens. Useful for converting text with +;; whitespace into hyphenated identifiers or URLs. +;; +;; Uses the regexp [ \t\n\r]+ to match whitespace runs. +;; +;; We test the NON-INTERACTIVE implementation (cj/--hyphenate-whitespace) +;; 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-whitespace) + +;;; Test Helpers + +(defun test-hyphenate-whitespace (input-text) + "Test cj/--hyphenate-whitespace on INPUT-TEXT. +Returns the buffer string after operation." + (with-temp-buffer + (insert input-text) + (cj/--hyphenate-whitespace (point-min) (point-max)) + (buffer-string))) + +;;; Normal Cases + +(ert-deftest test-hyphenate-whitespace-single-space () + "Should replace single space with hyphen." + (let ((result (test-hyphenate-whitespace "hello world"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-multiple-spaces () + "Should replace multiple spaces with single hyphen." + (let ((result (test-hyphenate-whitespace "hello world"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-tabs () + "Should replace tabs with hyphen." + (let ((result (test-hyphenate-whitespace "hello\tworld"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-mixed-tabs-spaces () + "Should replace mixed tabs and spaces with single hyphen." + (let ((result (test-hyphenate-whitespace "hello \t world"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-newlines () + "Should replace newlines with hyphen (joining lines)." + (let ((result (test-hyphenate-whitespace "hello\nworld"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-multiple-newlines () + "Should replace multiple newlines with single hyphen." + (let ((result (test-hyphenate-whitespace "hello\n\n\nworld"))) + (should (string= result "hello-world")))) + +(ert-deftest test-hyphenate-whitespace-multiple-words () + "Should hyphenate multiple words with various whitespace." + (let ((result (test-hyphenate-whitespace "one two three\tfour\nfive"))) + (should (string= result "one-two-three-four-five")))) + +(ert-deftest test-hyphenate-whitespace-carriage-returns () + "Should handle carriage returns." + (let ((result (test-hyphenate-whitespace "hello\r\nworld"))) + (should (string= result "hello-world")))) + +;;; Boundary Cases + +(ert-deftest test-hyphenate-whitespace-empty-string () + "Should handle empty string." + (let ((result (test-hyphenate-whitespace ""))) + (should (string= result "")))) + +(ert-deftest test-hyphenate-whitespace-no-whitespace () + "Should handle text with no whitespace (no-op)." + (let ((result (test-hyphenate-whitespace "helloworld"))) + (should (string= result "helloworld")))) + +(ert-deftest test-hyphenate-whitespace-only-whitespace () + "Should convert text with only whitespace to single hyphen." + (let ((result (test-hyphenate-whitespace " \t \n "))) + (should (string= result "-")))) + +(ert-deftest test-hyphenate-whitespace-single-char () + "Should handle single character with surrounding spaces." + (let ((result (test-hyphenate-whitespace " x "))) + (should (string= result "-x-")))) + +(ert-deftest test-hyphenate-whitespace-very-long-text () + "Should handle very long text with many spaces." + (let ((result (test-hyphenate-whitespace "word word word word word word word word"))) + (should (string= result "word-word-word-word-word-word-word-word")))) + +(ert-deftest test-hyphenate-whitespace-leading-whitespace () + "Should replace leading whitespace with hyphen." + (let ((result (test-hyphenate-whitespace " hello world"))) + (should (string= result "-hello-world")))) + +(ert-deftest test-hyphenate-whitespace-trailing-whitespace () + "Should replace trailing whitespace with hyphen." + (let ((result (test-hyphenate-whitespace "hello world "))) + (should (string= result "hello-world-")))) + +;;; Error Cases + +(ert-deftest test-hyphenate-whitespace-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--hyphenate-whitespace (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-hyphenate-whitespace-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--hyphenate-whitespace pos pos) + ;; Should complete without error and not change buffer + (should (string= (buffer-string) "hello world"))))) + +(provide 'test-custom-whitespace-hyphenate) +;;; test-custom-whitespace-hyphenate.el ends here diff --git a/tests/test-custom-whitespace-remove-leading-trailing.el b/tests/test-custom-whitespace-remove-leading-trailing.el new file mode 100644 index 00000000..5a846e7f --- /dev/null +++ b/tests/test-custom-whitespace-remove-leading-trailing.el @@ -0,0 +1,157 @@ +;;; test-custom-whitespace-remove-leading-trailing.el --- Tests for cj/--remove-leading-trailing-whitespace -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--remove-leading-trailing-whitespace function from custom-whitespace.el +;; +;; This function removes leading and trailing whitespace (spaces and tabs) from text. +;; - Removes leading whitespace: ^[ \t]+ +;; - Removes trailing whitespace: [ \t]+$ +;; - Preserves interior whitespace +;; - Operates on any region defined by START and END +;; +;; We test the NON-INTERACTIVE implementation (cj/--remove-leading-trailing-whitespace) +;; to avoid mocking region selection and 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-whitespace) + +;;; Test Helpers + +(defun test-remove-leading-trailing (input-text) + "Test cj/--remove-leading-trailing-whitespace on INPUT-TEXT. +Returns the buffer string after operation." + (with-temp-buffer + (insert input-text) + (cj/--remove-leading-trailing-whitespace (point-min) (point-max)) + (buffer-string))) + +;;; Normal Cases + +(ert-deftest test-remove-leading-trailing-leading-spaces () + "Should remove leading spaces from single line." + (let ((result (test-remove-leading-trailing " hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-trailing-spaces () + "Should remove trailing spaces from single line." + (let ((result (test-remove-leading-trailing "hello world "))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-both-spaces () + "Should remove both leading and trailing spaces." + (let ((result (test-remove-leading-trailing " hello world "))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-leading-tabs () + "Should remove leading tabs from single line." + (let ((result (test-remove-leading-trailing "\t\thello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-trailing-tabs () + "Should remove trailing tabs from single line." + (let ((result (test-remove-leading-trailing "hello world\t\t"))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-mixed-tabs-spaces () + "Should remove mixed tabs and spaces." + (let ((result (test-remove-leading-trailing " \t hello world \t "))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-preserve-interior () + "Should preserve interior whitespace." + (let ((result (test-remove-leading-trailing " hello world \t"))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-multiple-lines () + "Should handle multiple lines with leading/trailing whitespace." + (let ((result (test-remove-leading-trailing " line1 \n\t\tline2\t\n line3 "))) + (should (string= result "line1\nline2\nline3")))) + +(ert-deftest test-remove-leading-trailing-multiline-preserve-interior () + "Should preserve interior whitespace on multiple lines." + (let ((result (test-remove-leading-trailing " hello world \n foo bar "))) + (should (string= result "hello world\nfoo bar")))) + +;;; Boundary Cases + +(ert-deftest test-remove-leading-trailing-empty-string () + "Should handle empty string." + (let ((result (test-remove-leading-trailing ""))) + (should (string= result "")))) + +(ert-deftest test-remove-leading-trailing-single-char () + "Should handle single character with surrounding spaces." + (let ((result (test-remove-leading-trailing " x "))) + (should (string= result "x")))) + +(ert-deftest test-remove-leading-trailing-only-whitespace () + "Should handle lines with only whitespace." + (let ((result (test-remove-leading-trailing " \t "))) + (should (string= result "")))) + +(ert-deftest test-remove-leading-trailing-no-whitespace () + "Should handle text with no leading/trailing whitespace (no-op)." + (let ((result (test-remove-leading-trailing "hello world"))) + (should (string= result "hello world")))) + +(ert-deftest test-remove-leading-trailing-very-long-line () + "Should handle very long lines with whitespace." + (let* ((long-text (make-string 500 ?x)) + (input (concat " " long-text " ")) + (result (test-remove-leading-trailing input))) + (should (string= result long-text)))) + +(ert-deftest test-remove-leading-trailing-whitespace-between-lines () + "Should handle lines that become empty after removal." + (let ((result (test-remove-leading-trailing "line1\n \nline2"))) + (should (string= result "line1\n\nline2")))) + +(ert-deftest test-remove-leading-trailing-newlines-only () + "Should preserve newlines while removing spaces." + (let ((result (test-remove-leading-trailing "\n\n\n"))) + (should (string= result "\n\n\n")))) + +(ert-deftest test-remove-leading-trailing-partial-region () + "Should work on partial buffer region." + (with-temp-buffer + (insert " hello \n world \n test ") + ;; Only operate on middle line + (let ((start (+ (point-min) 10)) ; Start of second line + (end (+ (point-min) 19))) ; End of second line + (cj/--remove-leading-trailing-whitespace start end) + (should (string= (buffer-string) " hello \nworld\n test "))))) + +;;; Error Cases + +(ert-deftest test-remove-leading-trailing-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--remove-leading-trailing-whitespace (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-remove-leading-trailing-empty-region () + "Should handle empty region (start == end) without error." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (cj/--remove-leading-trailing-whitespace pos pos) + ;; Should complete without error and not change buffer + (should (string= (buffer-string) "hello world"))))) + +(provide 'test-custom-whitespace-remove-leading-trailing) +;;; test-custom-whitespace-remove-leading-trailing.el ends here |
