diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-26 21:40:29 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-26 21:40:29 -0500 |
| commit | 026323489e758cb1f21ab9fffb1ddd107ea739f3 (patch) | |
| tree | 94187e029f9948493817cf3d2811a32ee6c47284 | |
| parent | 03719a065c21bf683a9b90c83ab26ee0dc069cc7 (diff) | |
feat+test:ordering: add prefix/suffix to arrayify/convenience funcs
Enhanced cj/--arrayify with optional prefix/suffix parameters for flexible formatting:
- prefix: string to prepend (e.g., "[" or "(")
- suffix: string to append (e.g., "]" or ")")
Added convenience functions:
- cj/listify: unquoted list (a, b, c)
- cj/arrayify-json: JSON array ["a", "b", "c"]
- cj/arrayify-python: Python list ["a", "b", "c"]
Updated keybindings:
- C-; o l → cj/listify
- C-; o j → cj/arrayify-json
- C-; o p → cj/arrayify-python
- C-; o L → cj/comma-separated-text-to-lines (moved from 'l')
| -rw-r--r-- | modules/custom-ordering.el | 134 | ||||
| -rw-r--r-- | tests/test-custom-ordering-alphabetize.el | 176 | ||||
| -rw-r--r-- | tests/test-custom-ordering-arrayify.el | 215 | ||||
| -rw-r--r-- | tests/test-custom-ordering-comma-to-lines.el | 159 | ||||
| -rw-r--r-- | tests/test-custom-ordering-unarrayify.el | 159 |
5 files changed, 809 insertions, 34 deletions
diff --git a/modules/custom-ordering.el b/modules/custom-ordering.el index 5d308604..034216d6 100644 --- a/modules/custom-ordering.el +++ b/modules/custom-ordering.el @@ -2,16 +2,15 @@ ;;; Commentary: -;; This module provides functions for converting text between different formats and sorting operations. -;; These utilities are useful for reformatting data structures and organizing text. - -;; Functions include: - -;; - converting lines to quoted comma-separated arrays (arrayify) -;; - converting arrays back to separate lines (unarrayify) -;; - alphabetically sorting words in a region -;; - splitting comma-separated text into individual lines - +;; Text transformation and sorting utilities for reformatting data structures. +;; +;; Main functions: +;; - arrayify/listify - convert lines to comma-separated format (with/without quotes, brackets) +;; - unarrayify - convert arrays back to separate lines +;; - alphabetize-region - sort words alphabetically +;; - comma-separated-text-to-lines - split CSV text into lines +;; +;; Convenience functions: listify, arrayify-json, arrayify-python ;; Bound to keymap prefix C-; o ;;; Code: @@ -20,29 +19,89 @@ (eval-when-compile (defvar cj/custom-keymap)) (defvar cj/ordering-map) +(defun cj/--arrayify (start end quote &optional prefix suffix) + "Internal implementation: Convert lines to quoted, comma-separated format. +START and END define the region to operate on. +QUOTE specifies the quotation characters to surround each element. + Use \"\" for no quotes, \"\\\"\" for double quotes, \"'\" for single quotes. +PREFIX is an optional string to prepend to the result (e.g., \"[\" or \"(\"). +SUFFIX is an optional string to append to the result (e.g., \"]\" or \")\"). +Returns the transformed string without modifying the buffer." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (let ((result (mapconcat + (lambda (x) (format "%s%s%s" quote x quote)) + (split-string (buffer-substring start end)) ", "))) + (concat (or prefix "") result (or suffix "")))) + (defun cj/arrayify (start end quote) "Convert lines between START and END into quoted, comma-separated strings. START and END identify the active region. QUOTE specifies the quotation characters to surround each element." (interactive "r\nMQuotation character to use for array element: ") - (let ((insertion - (mapconcat - (lambda (x) (format "%s%s%s" quote x quote)) - (split-string (buffer-substring start end)) ", "))) + (let ((insertion (cj/--arrayify start end quote))) (delete-region start end) (insert insertion))) +(defun cj/listify (start end) + "Convert lines between START and END into an unquoted, comma-separated list. +START and END identify the active region. +Example: `apple banana cherry' becomes `apple, banana, cherry'." + (interactive "r") + (let ((insertion (cj/--arrayify start end ""))) + (delete-region start end) + (insert insertion))) + +(defun cj/arrayify-json (start end) + "Convert lines between START and END into a JSON-style array. +START and END identify the active region. +Example: `apple banana cherry' becomes `[\"apple\", \"banana\", \"cherry\"]'." + (interactive "r") + (let ((insertion (cj/--arrayify start end "\"" "[" "]"))) + (delete-region start end) + (insert insertion))) + +(defun cj/arrayify-python (start end) + "Convert lines between START and END into a Python-style list. +START and END identify the active region. +Example: `apple banana cherry' becomes `[\"apple\", \"banana\", \"cherry\"]'." + (interactive "r") + (let ((insertion (cj/--arrayify start end "\"" "[" "]"))) + (delete-region start end) + (insert insertion))) + +(defun cj/--unarrayify (start end) + "Internal implementation: Convert comma-separated array to lines. +START and END define the region to operate on. +Removes quotes (both single and double) and splits by ', '. +Returns the transformed string without modifying the buffer." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (mapconcat + (lambda (x) (replace-regexp-in-string "[\"']" "" x)) + (split-string (buffer-substring start end) ", ") "\n")) + (defun cj/unarrayify (start end) "Convert quoted comma-separated strings between START and END to separate lines. START and END identify the active region." (interactive "r") - (let ((insertion - (mapconcat - (lambda (x) (replace-regexp-in-string "[\"']" "" x)) - (split-string (buffer-substring start end) ", ") "\n"))) + (let ((insertion (cj/--unarrayify start end))) (delete-region start end) (insert insertion))) +(defun cj/--alphabetize-region (start end) + "Internal implementation: Alphabetize words in region. +START and END define the region to operate on. +Splits by whitespace and commas, sorts alphabetically, joins with ', '. +Returns the transformed string without modifying the buffer." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (let ((string (buffer-substring-no-properties start end))) + (mapconcat #'identity + (sort (split-string string "[[:space:],]+" t) + #'string-lessp) + ", "))) + (defun cj/alphabetize-region () "Alphabetize words in the active region and replace the original text. Produce a comma-separated list as the result." @@ -51,14 +110,26 @@ Produce a comma-separated list as the result." (user-error "No region selected")) (let ((start (region-beginning)) (end (region-end)) - (string (buffer-substring-no-properties (region-beginning) (region-end)))) + (insertion (cj/--alphabetize-region (region-beginning) (region-end)))) (delete-region start end) (goto-char start) - (insert - (mapconcat #'identity - (sort (split-string string "[[:space:],]+" t) - #'string-lessp) - ", ")))) + (insert insertion))) + +(defun cj/--comma-separated-text-to-lines (start end) + "Internal implementation: Convert comma-separated text to lines. +START and END define the region to operate on. +Replaces commas with newlines and removes trailing whitespace from each line. +Returns the transformed string without modifying the buffer." + (when (> start end) + (error "Invalid region: start (%d) is greater than end (%d)" start end)) + (let ((text (buffer-substring-no-properties start end))) + (with-temp-buffer + (insert text) + (goto-char (point-min)) + (while (search-forward "," nil t) + (replace-match "\n" nil t)) + (delete-trailing-whitespace) + (buffer-string)))) (defun cj/comma-separated-text-to-lines () "Break up comma-separated text in active region so each item is on own line." @@ -68,15 +139,7 @@ Produce a comma-separated list as the result." (let ((beg (region-beginning)) (end (region-end)) - (text (buffer-substring-no-properties (region-beginning) (region-end)))) - (with-temp-buffer - (insert text) - (goto-char (point-min)) - (while (search-forward "," nil t) - (replace-match "\n" nil t)) - (delete-trailing-whitespace) - (setq text (buffer-string))) - + (text (cj/--comma-separated-text-to-lines (region-beginning) (region-end)))) (delete-region beg end) (goto-char beg) (insert text))) @@ -88,8 +151,11 @@ Produce a comma-separated list as the result." :doc "Keymap for text ordering and sorting operations" "a" #'cj/arrayify "u" #'cj/unarrayify + "l" #'cj/listify + "j" #'cj/arrayify-json + "p" #'cj/arrayify-python "A" #'cj/alphabetize-region - "l" #'cj/comma-separated-text-to-lines) + "L" #'cj/comma-separated-text-to-lines) (keymap-set cj/custom-keymap "o" cj/ordering-map) (with-eval-after-load 'which-key diff --git a/tests/test-custom-ordering-alphabetize.el b/tests/test-custom-ordering-alphabetize.el new file mode 100644 index 00000000..c609e324 --- /dev/null +++ b/tests/test-custom-ordering-alphabetize.el @@ -0,0 +1,176 @@ +;;; test-custom-ordering-alphabetize.el --- Tests for cj/--alphabetize-region -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--alphabetize-region function from custom-ordering.el +;; +;; This function alphabetically sorts words in a region. +;; It splits by whitespace and commas, sorts alphabetically, and joins with ", ". +;; +;; Examples: +;; Input: "zebra apple banana" +;; Output: "apple, banana, zebra" +;; +;; Input: "dog, cat, bird" +;; Output: "bird, cat, dog" +;; +;; We test the NON-INTERACTIVE implementation (cj/--alphabetize-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-ordering) + +;;; Test Helpers + +(defun test-alphabetize (input-text) + "Test cj/--alphabetize-region on INPUT-TEXT. +Returns the sorted, comma-separated string." + (with-temp-buffer + (insert input-text) + (cj/--alphabetize-region (point-min) (point-max)))) + +;;; Normal Cases - Simple Words + +(ert-deftest test-alphabetize-simple-words () + "Should alphabetize simple words." + (let ((result (test-alphabetize "zebra apple banana"))) + (should (string= result "apple, banana, zebra")))) + +(ert-deftest test-alphabetize-already-sorted () + "Should handle already sorted words." + (let ((result (test-alphabetize "apple banana cherry"))) + (should (string= result "apple, banana, cherry")))) + +(ert-deftest test-alphabetize-reverse-order () + "Should alphabetize reverse-ordered words." + (let ((result (test-alphabetize "zebra yankee xray"))) + (should (string= result "xray, yankee, zebra")))) + +(ert-deftest test-alphabetize-two-words () + "Should alphabetize two words." + (let ((result (test-alphabetize "world hello"))) + (should (string= result "hello, world")))) + +;;; Normal Cases - With Commas + +(ert-deftest test-alphabetize-comma-separated () + "Should alphabetize comma-separated words." + (let ((result (test-alphabetize "dog, cat, bird"))) + (should (string= result "bird, cat, dog")))) + +(ert-deftest test-alphabetize-comma-separated-with-spaces () + "Should handle comma-separated with various spacing." + (let ((result (test-alphabetize "dog,cat,bird"))) + (should (string= result "bird, cat, dog")))) + +;;; Normal Cases - With Newlines + +(ert-deftest test-alphabetize-multiline () + "Should alphabetize words across multiple lines." + (let ((result (test-alphabetize "zebra\napple\nbanana"))) + (should (string= result "apple, banana, zebra")))) + +(ert-deftest test-alphabetize-mixed-separators () + "Should alphabetize with mixed separators (spaces, commas, newlines)." + (let ((result (test-alphabetize "zebra, apple\nbanana cherry"))) + (should (string= result "apple, banana, cherry, zebra")))) + +;;; Normal Cases - Case Sensitivity + +(ert-deftest test-alphabetize-case-sensitive () + "Should sort case-sensitively (uppercase before lowercase)." + (let ((result (test-alphabetize "zebra Apple banana"))) + ;; string-lessp sorts uppercase before lowercase + (should (string= result "Apple, banana, zebra")))) + +(ert-deftest test-alphabetize-mixed-case () + "Should handle mixed case words." + (let ((result (test-alphabetize "ZEBRA apple BANANA"))) + (should (string= result "BANANA, ZEBRA, apple")))) + +;;; Normal Cases - Numbers and Special Characters + +(ert-deftest test-alphabetize-with-numbers () + "Should alphabetize numbers as strings." + (let ((result (test-alphabetize "10 2 1 20"))) + ;; Alphabetic sort: "1", "10", "2", "20" + (should (string= result "1, 10, 2, 20")))) + +(ert-deftest test-alphabetize-mixed-alphanumeric () + "Should alphabetize mixed alphanumeric content." + (let ((result (test-alphabetize "item2 item1 item10"))) + (should (string= result "item1, item10, item2")))) + +(ert-deftest test-alphabetize-with-punctuation () + "Should alphabetize words with punctuation." + (let ((result (test-alphabetize "world! hello? test."))) + (should (string= result "hello?, test., world!")))) + +;;; Boundary Cases + +(ert-deftest test-alphabetize-empty-string () + "Should handle empty string." + (let ((result (test-alphabetize ""))) + (should (string= result "")))) + +(ert-deftest test-alphabetize-single-word () + "Should handle single word." + (let ((result (test-alphabetize "hello"))) + (should (string= result "hello")))) + +(ert-deftest test-alphabetize-only-whitespace () + "Should handle whitespace-only text." + (let ((result (test-alphabetize " \n\n\t\t "))) + (should (string= result "")))) + +(ert-deftest test-alphabetize-duplicates () + "Should handle duplicate words." + (let ((result (test-alphabetize "apple banana apple cherry"))) + (should (string= result "apple, apple, banana, cherry")))) + +(ert-deftest test-alphabetize-many-commas () + "Should handle multiple consecutive commas." + (let ((result (test-alphabetize "apple,,,banana,,,cherry"))) + (should (string= result "apple, banana, cherry")))) + +(ert-deftest test-alphabetize-very-long-list () + "Should handle very long list." + (let* ((words (mapcar (lambda (i) (format "word%03d" i)) (number-sequence 100 1 -1))) + (input (mapconcat #'identity words " ")) + (result (test-alphabetize input)) + (sorted-words (split-string result ", "))) + (should (= 100 (length sorted-words))) + (should (string= "word001" (car sorted-words))) + (should (string= "word100" (car (last sorted-words)))))) + +;;; Error Cases + +(ert-deftest test-alphabetize-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--alphabetize-region (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-alphabetize-empty-region () + "Should handle empty region (start == end)." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (string= "" (cj/--alphabetize-region pos pos)))))) + +(provide 'test-custom-ordering-alphabetize) +;;; test-custom-ordering-alphabetize.el ends here diff --git a/tests/test-custom-ordering-arrayify.el b/tests/test-custom-ordering-arrayify.el new file mode 100644 index 00000000..9aedbc46 --- /dev/null +++ b/tests/test-custom-ordering-arrayify.el @@ -0,0 +1,215 @@ +;;; test-custom-ordering-arrayify.el --- Tests for cj/--arrayify -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--arrayify function from custom-ordering.el +;; +;; This function converts lines of text into a quoted, comma-separated array format. +;; It splits input by whitespace, wraps each element in quotes, and joins with ", ". +;; +;; Examples: +;; Input: "apple\nbanana\ncherry" +;; Output: "\"apple\", \"banana\", \"cherry\"" +;; +;; Input: "one two three" (with single quotes) +;; Output: "'one', 'two', 'three'" +;; +;; We test the NON-INTERACTIVE implementation (cj/--arrayify) to avoid +;; mocking user input for quote characters. 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-ordering) + +;;; Test Helpers + +(defun test-arrayify (input-text quote) + "Test cj/--arrayify on INPUT-TEXT with QUOTE character. +Returns the transformed string." + (with-temp-buffer + (insert input-text) + (cj/--arrayify (point-min) (point-max) quote))) + +(defun test-arrayify-with-prefix-suffix (input-text quote prefix suffix) + "Test cj/--arrayify with PREFIX and SUFFIX on INPUT-TEXT. +Returns the transformed string." + (with-temp-buffer + (insert input-text) + (cj/--arrayify (point-min) (point-max) quote prefix suffix))) + +;;; Normal Cases - Double Quotes + +(ert-deftest test-arrayify-single-line-double-quotes () + "Should arrayify single line with double quotes." + (let ((result (test-arrayify "apple banana cherry" "\""))) + (should (string= result "\"apple\", \"banana\", \"cherry\"")))) + +(ert-deftest test-arrayify-multiple-lines-double-quotes () + "Should arrayify multiple lines with double quotes." + (let ((result (test-arrayify "apple\nbanana\ncherry" "\""))) + (should (string= result "\"apple\", \"banana\", \"cherry\"")))) + +(ert-deftest test-arrayify-mixed-whitespace-double-quotes () + "Should arrayify text with mixed whitespace using double quotes." + (let ((result (test-arrayify "apple \n\n banana\t\tcherry" "\""))) + (should (string= result "\"apple\", \"banana\", \"cherry\"")))) + +;;; Normal Cases - Single Quotes + +(ert-deftest test-arrayify-single-line-single-quotes () + "Should arrayify single line with single quotes." + (let ((result (test-arrayify "one two three" "'"))) + (should (string= result "'one', 'two', 'three'")))) + +(ert-deftest test-arrayify-multiple-lines-single-quotes () + "Should arrayify multiple lines with single quotes." + (let ((result (test-arrayify "one\ntwo\nthree" "'"))) + (should (string= result "'one', 'two', 'three'")))) + +;;; Normal Cases - Various Quote Types + +(ert-deftest test-arrayify-backticks () + "Should arrayify with backticks." + (let ((result (test-arrayify "foo bar baz" "`"))) + (should (string= result "`foo`, `bar`, `baz`")))) + +(ert-deftest test-arrayify-no-quotes () + "Should arrayify with empty quote string." + (let ((result (test-arrayify "alpha beta gamma" ""))) + (should (string= result "alpha, beta, gamma")))) + +(ert-deftest test-arrayify-square-brackets () + "Should arrayify with square brackets as quotes." + (let ((result (test-arrayify "x y z" "[]"))) + (should (string= result "[]x[], []y[], []z[]")))) + +;;; Normal Cases - Various Content + +(ert-deftest test-arrayify-with-numbers () + "Should arrayify numbers." + (let ((result (test-arrayify "1 2 3 4 5" "\""))) + (should (string= result "\"1\", \"2\", \"3\", \"4\", \"5\"")))) + +(ert-deftest test-arrayify-with-punctuation () + "Should arrayify words with punctuation." + (let ((result (test-arrayify "hello! world? test." "\""))) + (should (string= result "\"hello!\", \"world?\", \"test.\"")))) + +(ert-deftest test-arrayify-mixed-content () + "Should arrayify mixed alphanumeric content." + (let ((result (test-arrayify "item1 item2 item3" "\""))) + (should (string= result "\"item1\", \"item2\", \"item3\"")))) + +;;; Boundary Cases + +(ert-deftest test-arrayify-empty-string () + "Should handle empty string." + (let ((result (test-arrayify "" "\""))) + (should (string= result "")))) + +(ert-deftest test-arrayify-single-word () + "Should arrayify single word." + (let ((result (test-arrayify "hello" "\""))) + (should (string= result "\"hello\"")))) + +(ert-deftest test-arrayify-only-whitespace () + "Should handle whitespace-only text." + (let ((result (test-arrayify " \n\n\t\t " "\""))) + (should (string= result "")))) + +(ert-deftest test-arrayify-leading-trailing-whitespace () + "Should ignore leading and trailing whitespace." + (let ((result (test-arrayify " apple banana " "\""))) + (should (string= result "\"apple\", \"banana\"")))) + +(ert-deftest test-arrayify-very-long-list () + "Should handle very long list." + (let* ((words (make-list 100 "word")) + (input (mapconcat #'identity words " ")) + (result (test-arrayify input "\""))) + (should (= 100 (length (split-string result ", ")))))) + +(ert-deftest test-arrayify-two-words () + "Should arrayify two words." + (let ((result (test-arrayify "hello world" "\""))) + (should (string= result "\"hello\", \"world\"")))) + +;;; Normal Cases - Prefix/Suffix + +(ert-deftest test-arrayify-with-square-brackets () + "Should arrayify with square brackets prefix/suffix." + (let ((result (test-arrayify-with-prefix-suffix "apple banana cherry" "\"" "[" "]"))) + (should (string= result "[\"apple\", \"banana\", \"cherry\"]")))) + +(ert-deftest test-arrayify-with-parens () + "Should arrayify with parentheses prefix/suffix." + (let ((result (test-arrayify-with-prefix-suffix "one two three" "\"" "(" ")"))) + (should (string= result "(\"one\", \"two\", \"three\")")))) + +(ert-deftest test-arrayify-unquoted-with-brackets () + "Should create unquoted list with brackets." + (let ((result (test-arrayify-with-prefix-suffix "a b c" "" "[" "]"))) + (should (string= result "[a, b, c]")))) + +(ert-deftest test-arrayify-single-quotes-with-brackets () + "Should create single-quoted array with brackets." + (let ((result (test-arrayify-with-prefix-suffix "x y z" "'" "[" "]"))) + (should (string= result "['x', 'y', 'z']")))) + +(ert-deftest test-arrayify-only-prefix () + "Should handle only prefix, no suffix." + (let ((result (test-arrayify-with-prefix-suffix "foo bar" "\"" "[" nil))) + (should (string= result "[\"foo\", \"bar\"")))) + +(ert-deftest test-arrayify-only-suffix () + "Should handle only suffix, no prefix." + (let ((result (test-arrayify-with-prefix-suffix "foo bar" "\"" nil "]"))) + (should (string= result "\"foo\", \"bar\"]")))) + +(ert-deftest test-arrayify-multichar-prefix-suffix () + "Should handle multi-character prefix/suffix." + (let ((result (test-arrayify-with-prefix-suffix "a b" "\"" "Array(" ")"))) + (should (string= result "Array(\"a\", \"b\")")))) + +(ert-deftest test-arrayify-json-style () + "Should create JSON-style array." + (let ((result (test-arrayify-with-prefix-suffix "apple banana" "\"" "[" "]"))) + (should (string= result "[\"apple\", \"banana\"]")))) + +;;; Error Cases + +(ert-deftest test-arrayify-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "hello world") + (cj/--arrayify (point-max) (point-min) "\"")) + :type 'error)) + +(ert-deftest test-arrayify-empty-region () + "Should handle empty region (start == end)." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (string= "" (cj/--arrayify pos pos "\"")))))) + +(ert-deftest test-arrayify-empty-region-with-brackets () + "Should handle empty region with brackets." + (with-temp-buffer + (insert "hello world") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (string= "[]" (cj/--arrayify pos pos "\"" "[" "]")))))) + +(provide 'test-custom-ordering-arrayify) +;;; test-custom-ordering-arrayify.el ends here diff --git a/tests/test-custom-ordering-comma-to-lines.el b/tests/test-custom-ordering-comma-to-lines.el new file mode 100644 index 00000000..93e37ec6 --- /dev/null +++ b/tests/test-custom-ordering-comma-to-lines.el @@ -0,0 +1,159 @@ +;;; test-custom-ordering-comma-to-lines.el --- Tests for cj/--comma-separated-text-to-lines -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--comma-separated-text-to-lines function from custom-ordering.el +;; +;; This function converts comma-separated text to separate lines. +;; It replaces commas with newlines and removes trailing whitespace from each line. +;; +;; Examples: +;; Input: "apple, banana, cherry" +;; Output: "apple\nbanana\ncherry" +;; +;; Input: "one,two,three" +;; Output: "one\ntwo\nthree" +;; +;; We test the NON-INTERACTIVE implementation (cj/--comma-separated-text-to-lines) +;; 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-ordering) + +;;; Test Helpers + +(defun test-comma-to-lines (input-text) + "Test cj/--comma-separated-text-to-lines on INPUT-TEXT. +Returns the transformed string." + (with-temp-buffer + (insert input-text) + (cj/--comma-separated-text-to-lines (point-min) (point-max)))) + +;;; Normal Cases - Simple Comma-Separated + +(ert-deftest test-comma-to-lines-simple () + "Should convert simple comma-separated text to lines." + (let ((result (test-comma-to-lines "apple, banana, cherry"))) + (should (string= result "apple\n banana\n cherry")))) + +(ert-deftest test-comma-to-lines-no-spaces () + "Should convert comma-separated text without spaces." + (let ((result (test-comma-to-lines "one,two,three"))) + (should (string= result "one\ntwo\nthree")))) + +(ert-deftest test-comma-to-lines-two-elements () + "Should convert two comma-separated elements." + (let ((result (test-comma-to-lines "hello,world"))) + (should (string= result "hello\nworld")))) + +(ert-deftest test-comma-to-lines-with-varied-spacing () + "Should preserve leading spaces after commas." + (let ((result (test-comma-to-lines "alpha, beta, gamma"))) + (should (string= result "alpha\n beta\n gamma")))) + +;;; Normal Cases - Trailing Whitespace + +(ert-deftest test-comma-to-lines-trailing-spaces () + "Should remove trailing spaces but preserve leading spaces." + (let ((result (test-comma-to-lines "apple , banana , cherry "))) + (should (string= result "apple\n banana\n cherry")))) + +(ert-deftest test-comma-to-lines-trailing-tabs () + "Should remove trailing tabs after conversion." + (let ((result (test-comma-to-lines "apple\t,banana\t,cherry\t"))) + (should (string= result "apple\nbanana\ncherry")))) + +;;; Boundary Cases + +(ert-deftest test-comma-to-lines-empty-string () + "Should handle empty string." + (let ((result (test-comma-to-lines ""))) + (should (string= result "")))) + +(ert-deftest test-comma-to-lines-single-element () + "Should handle single element with no comma." + (let ((result (test-comma-to-lines "hello"))) + (should (string= result "hello")))) + +(ert-deftest test-comma-to-lines-single-element-with-trailing-comma () + "Should handle single element with trailing comma." + (let ((result (test-comma-to-lines "hello,"))) + (should (string= result "hello\n")))) + +(ert-deftest test-comma-to-lines-leading-comma () + "Should handle leading comma." + (let ((result (test-comma-to-lines ",apple,banana"))) + (should (string= result "\napple\nbanana")))) + +(ert-deftest test-comma-to-lines-consecutive-commas () + "Should handle consecutive commas." + (let ((result (test-comma-to-lines "apple,,banana"))) + (should (string= result "apple\n\nbanana")))) + +(ert-deftest test-comma-to-lines-many-consecutive-commas () + "Should handle many consecutive commas." + (let ((result (test-comma-to-lines "apple,,,banana"))) + (should (string= result "apple\n\n\nbanana")))) + +(ert-deftest test-comma-to-lines-only-commas () + "Should handle string with only commas (trailing blank lines removed)." + (let ((result (test-comma-to-lines ",,,"))) + ;; delete-trailing-whitespace removes trailing blank lines + (should (string= result "\n")))) + +;;; Normal Cases - With Spaces Around Elements + +(ert-deftest test-comma-to-lines-leading-spaces () + "Should preserve leading spaces within elements." + (let ((result (test-comma-to-lines " apple, banana, cherry"))) + (should (string= result " apple\n banana\n cherry")))) + +(ert-deftest test-comma-to-lines-mixed-content () + "Should handle mixed alphanumeric content." + (let ((result (test-comma-to-lines "item1,item2,item3"))) + (should (string= result "item1\nitem2\nitem3")))) + +(ert-deftest test-comma-to-lines-with-numbers () + "Should handle numbers." + (let ((result (test-comma-to-lines "1,2,3,4,5"))) + (should (string= result "1\n2\n3\n4\n5")))) + +(ert-deftest test-comma-to-lines-very-long-list () + "Should handle very long list." + (let* ((elements (mapcar #'number-to-string (number-sequence 1 100))) + (input (mapconcat #'identity elements ",")) + (result (test-comma-to-lines input)) + (lines (split-string result "\n"))) + (should (= 100 (length lines))))) + +;;; Error Cases + +(ert-deftest test-comma-to-lines-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "a,b,c") + (cj/--comma-separated-text-to-lines (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-comma-to-lines-empty-region () + "Should handle empty region (start == end)." + (with-temp-buffer + (insert "a,b,c") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (string= "" (cj/--comma-separated-text-to-lines pos pos)))))) + +(provide 'test-custom-ordering-comma-to-lines) +;;; test-custom-ordering-comma-to-lines.el ends here diff --git a/tests/test-custom-ordering-unarrayify.el b/tests/test-custom-ordering-unarrayify.el new file mode 100644 index 00000000..a778f419 --- /dev/null +++ b/tests/test-custom-ordering-unarrayify.el @@ -0,0 +1,159 @@ +;;; test-custom-ordering-unarrayify.el --- Tests for cj/--unarrayify -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/--unarrayify function from custom-ordering.el +;; +;; This function converts comma-separated array format back to separate lines. +;; It splits by ", " (comma-space), removes quotes (both " and '), and joins with newlines. +;; +;; Examples: +;; Input: "\"apple\", \"banana\", \"cherry\"" +;; Output: "apple\nbanana\ncherry" +;; +;; Input: "'one', 'two', 'three'" +;; Output: "one\ntwo\nthree" +;; +;; We test the NON-INTERACTIVE implementation (cj/--unarrayify) 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-ordering) + +;;; Test Helpers + +(defun test-unarrayify (input-text) + "Test cj/--unarrayify on INPUT-TEXT. +Returns the transformed string." + (with-temp-buffer + (insert input-text) + (cj/--unarrayify (point-min) (point-max)))) + +;;; Normal Cases - Double Quotes + +(ert-deftest test-unarrayify-double-quotes-simple () + "Should unarrayify double-quoted elements." + (let ((result (test-unarrayify "\"apple\", \"banana\", \"cherry\""))) + (should (string= result "apple\nbanana\ncherry")))) + +(ert-deftest test-unarrayify-double-quotes-single-element () + "Should unarrayify single double-quoted element." + (let ((result (test-unarrayify "\"hello\""))) + (should (string= result "hello")))) + +(ert-deftest test-unarrayify-double-quotes-two-elements () + "Should unarrayify two double-quoted elements." + (let ((result (test-unarrayify "\"one\", \"two\""))) + (should (string= result "one\ntwo")))) + +;;; Normal Cases - Single Quotes + +(ert-deftest test-unarrayify-single-quotes-simple () + "Should unarrayify single-quoted elements." + (let ((result (test-unarrayify "'alpha', 'beta', 'gamma'"))) + (should (string= result "alpha\nbeta\ngamma")))) + +(ert-deftest test-unarrayify-single-quotes-single-element () + "Should unarrayify single single-quoted element." + (let ((result (test-unarrayify "'hello'"))) + (should (string= result "hello")))) + +;;; Normal Cases - Mixed Quotes + +(ert-deftest test-unarrayify-mixed-quotes () + "Should unarrayify mixed quote types." + (let ((result (test-unarrayify "\"apple\", 'banana', \"cherry\""))) + (should (string= result "apple\nbanana\ncherry")))) + +;;; Normal Cases - No Quotes + +(ert-deftest test-unarrayify-no-quotes () + "Should unarrayify unquoted elements." + (let ((result (test-unarrayify "foo, bar, baz"))) + (should (string= result "foo\nbar\nbaz")))) + +;;; Normal Cases - Various Content + +(ert-deftest test-unarrayify-with-numbers () + "Should unarrayify numbers." + (let ((result (test-unarrayify "\"1\", \"2\", \"3\""))) + (should (string= result "1\n2\n3")))) + +(ert-deftest test-unarrayify-with-spaces-in-elements () + "Should preserve spaces within elements." + (let ((result (test-unarrayify "\"hello world\", \"foo bar\""))) + (should (string= result "hello world\nfoo bar")))) + +(ert-deftest test-unarrayify-mixed-content () + "Should unarrayify mixed alphanumeric content." + (let ((result (test-unarrayify "\"item1\", \"item2\", \"item3\""))) + (should (string= result "item1\nitem2\nitem3")))) + +;;; Boundary Cases + +(ert-deftest test-unarrayify-empty-string () + "Should handle empty string." + (let ((result (test-unarrayify ""))) + (should (string= result "")))) + +(ert-deftest test-unarrayify-only-quotes () + "Should remove quotes from quote-only string." + (let ((result (test-unarrayify "\"\""))) + (should (string= result "")))) + +(ert-deftest test-unarrayify-very-long-list () + "Should handle very long list." + (let* ((elements (mapcar (lambda (i) (format "\"%d\"" i)) (number-sequence 1 100))) + (input (mapconcat #'identity elements ", ")) + (result (test-unarrayify input)) + (lines (split-string result "\n"))) + (should (= 100 (length lines))))) + +(ert-deftest test-unarrayify-with-empty-elements () + "Should handle empty quoted elements." + (let ((result (test-unarrayify "\"\", \"test\", \"\""))) + (should (string= result "\ntest\n")))) + +;;; Edge Cases - Nested or Mismatched Quotes + +(ert-deftest test-unarrayify-double-quotes-in-single () + "Should handle double quotes inside single-quoted strings." + (let ((result (test-unarrayify "'he said \"hello\"', 'world'"))) + (should (string= result "he said hello\nworld")))) + +(ert-deftest test-unarrayify-only-opening-quotes () + "Should remove all quote characters even if mismatched." + (let ((result (test-unarrayify "\"apple, \"banana, \"cherry"))) + (should (string= result "apple\nbanana\ncherry")))) + +;;; Error Cases + +(ert-deftest test-unarrayify-start-greater-than-end () + "Should error when start > end." + (should-error + (with-temp-buffer + (insert "\"a\", \"b\"") + (cj/--unarrayify (point-max) (point-min))) + :type 'error)) + +(ert-deftest test-unarrayify-empty-region () + "Should handle empty region (start == end)." + (with-temp-buffer + (insert "\"a\", \"b\"") + (let ((pos (/ (+ (point-min) (point-max)) 2))) + (should (string= "" (cj/--unarrayify pos pos)))))) + +(provide 'test-custom-ordering-unarrayify) +;;; test-custom-ordering-unarrayify.el ends here |
