summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-26 20:25:59 -0500
committerCraig Jennings <c@cjennings.net>2025-10-26 20:25:59 -0500
commit0c6cd8584da8861727cbdb170fd654a8ac738671 (patch)
treeee9abcc2ea68cc05f121c93b73b4581b5bbec220
parent87d2586bcd99f3ceb811d4e1e287ac7ca8f5c65b (diff)
feat+test:comments: add 7 comment generation functions and tests
Implement 5 new comment styles (simple-divider, padded-divider, heavy-box, unicode-box, block-banner) and refactor 2 existing functions (inline-border, comment-box) to use interactive/non-interactive pattern for testability. Add 178 tests across 7 test files with validation for length constraints and cross-language support. Fix linter warnings for parameter shadowing and docstring formatting.
-rw-r--r--modules/custom-comments.el664
-rw-r--r--tests/test-custom-comments-comment-block-banner.el228
-rw-r--r--tests/test-custom-comments-comment-box.el241
-rw-r--r--tests/test-custom-comments-comment-heavy-box.el251
-rw-r--r--tests/test-custom-comments-comment-inline-border.el235
-rw-r--r--tests/test-custom-comments-comment-padded-divider.el250
-rw-r--r--tests/test-custom-comments-comment-simple-divider.el246
-rw-r--r--tests/test-custom-comments-comment-unicode-box.el264
8 files changed, 2266 insertions, 113 deletions
diff --git a/modules/custom-comments.el b/modules/custom-comments.el
index 4cfd9776..b4e51b2c 100644
--- a/modules/custom-comments.el
+++ b/modules/custom-comments.el
@@ -13,11 +13,50 @@
;; These utilities help create consistent, well-formatted code comments and section headers.
;; Bound to keymap prefix: C-; C
;;
+;; Comment Style Patterns:
+;;
+;; inline-border:
+;; ========== inline-border ==========
+;;
+;; simple-divider:
+;; ====================================
+;; simple-divider
+;; ====================================
+;;
+;; padded-divider:
+;; ====================================
+;; padded-divider
+;; ====================================
+;;
+;; box:
+;; ************************************
+;; * box *
+;; ************************************
+;;
+;; heavy-box:
+;; ************************************
+;; * *
+;; * heavy-box *
+;; * *
+;; ************************************
+;;
+;; unicode-box:
+;; ┌──────────────────────────────────┐
+;; │ unicode-box │
+;; └──────────────────────────────────┘
+;;
+;; block-banner:
+;; /************************************
+;; * block-banner
+;; ************************************/
+;;
;;; Code:
(eval-when-compile (defvar cj/custom-keymap)) ;; cj/custom-keymap defined in keybindings.el
(autoload 'cj/join-line-or-region "custom-line-paragraph" nil t)
+;; ======================== Comment Manipulation Functions =====================
+
;; --------------------------- Delete Buffer Comments --------------------------
(defun cj/delete-buffer-comments ()
@@ -44,140 +83,539 @@
;; if no region
(message "No region was selected. Select the comment lines to reformat."))
-;; ------------------------------ Comment Centered -----------------------------
+;; ======================== Comment Generation Functions =======================
+
+;; ----------------------------- Inline Border ---------------------------------
+
+(defun cj/--comment-inline-border (cmt-start cmt-end decoration-char text length)
+ "Internal implementation: Generate single-line centered comment with decoration.
+CMT-START and CMT-END are the comment syntax strings.
+DECORATION-CHAR is the character to use for borders (string).
+TEXT is the comment text (will be centered).
+LENGTH is the total width of the line."
+ (let* ((current-column-pos (current-column))
+ (text-length (length text))
+ (comment-start-len (+ (length cmt-start)
+ (if (equal cmt-start ";") 1 0))) ; doubled semicolon
+ ;; Calculate available space for decoration + text + spaces
+ (available-width (- length current-column-pos
+ comment-start-len
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end)))
+ 1)) ; space after comment-start
+ ;; Space for decoration on each side (excluding text and its surrounding spaces)
+ (space-on-each-side (/ (- available-width
+ text-length
+ (if (> text-length 0) 2 0)) ; spaces around text
+ 2))
+ (min-space 2))
+ ;; Validate we have enough space
+ (when (< space-on-each-side min-space)
+ (error "Length %d is too small for text '%s' (need at least %d more chars)"
+ length text (- min-space space-on-each-side)))
+ ;; Generate the line
+ (insert cmt-start)
+ (when (equal cmt-start ";")
+ (insert cmt-start))
+ (insert " ")
+ ;; Left decoration
+ (dotimes (_ space-on-each-side)
+ (insert decoration-char))
+ ;; Text with spaces
+ (when (> text-length 0)
+ (insert " " text " "))
+ ;; Right decoration (handle odd-length text)
+ (dotimes (_ (if (= (% text-length 2) 0)
+ (- space-on-each-side 1)
+ space-on-each-side))
+ (insert decoration-char))
+ ;; Comment end
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)))
+
+(defun cj/comment-inline-border (&optional decoration-char)
+ "Insert single-line comment with TEXT centered around DECORATION-CHAR borders.
+DECORATION-CHAR defaults to \"#\" if not provided.
+Uses the lesser of `fill-column\\=' or 80 for line length."
+ (interactive)
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (decoration-char (or decoration-char "#"))
+ (text (capitalize (string-trim (read-from-minibuffer "Comment: "))))
+ (length (min fill-column 80)))
+ (cj/--comment-inline-border comment-start comment-end decoration-char text length)))
+
+;; ---------------------------- Simple Divider ---------------------------------
+
+(defun cj/--comment-simple-divider (cmt-start cmt-end decoration-char text length)
+ "Internal implementation: Generate a simple divider comment.
+CMT-START and CMT-END are the comment syntax strings.
+DECORATION-CHAR is the character to use for the divider lines.
+TEXT is the comment text.
+LENGTH is the total width of each line."
+ (let* ((current-column-pos (current-column))
+ (min-length (+ current-column-pos
+ (length cmt-start)
+ (if (equal cmt-start ";") 1 0) ; doubled semicolon
+ 1 ; space after comment-start
+ 3 ; minimum decoration chars
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end))))))
+ (when (< length min-length)
+ (error "Length %d is too small to generate comment (minimum %d)" length min-length))
+ (let* ((available-width (- length current-column-pos
+ (length cmt-start)
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end)))))
+ (line (make-string available-width (string-to-char decoration-char))))
+ ;; Top line
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " ")
+ (insert line)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Text line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " " text)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Bottom line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " ")
+ (insert line)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline))))
+
+(defun cj/comment-simple-divider ()
+ "Insert a simple divider comment banner.
+Prompts for decoration character, text, and length option."
+ (interactive)
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (decoration-char (read-string "Decoration character (default =): " nil nil "="))
+ (text (read-string "Comment text: "))
+ (length-option (completing-read "Length: "
+ '("fill-column" "half-column" "match-text")
+ nil t nil nil "fill-column"))
+ (length (cond
+ ((string= length-option "fill-column") fill-column)
+ ((string= length-option "half-column") (/ fill-column 2))
+ ((string= length-option "match-text")
+ (+ (length comment-start)
+ (if (equal comment-start ";") 1 0)
+ 1 ; space after comment-start
+ (length text)
+ (if (string-empty-p comment-end) 0 (1+ (length comment-end))))))))
+ (cj/--comment-simple-divider comment-start comment-end decoration-char text length)))
+
+;; ---------------------------- Padded Divider ---------------------------------
+
+(defun cj/--comment-padded-divider (cmt-start cmt-end decoration-char text length padding)
+ "Internal implementation: Generate a padded divider comment.
+CMT-START and CMT-END are the comment syntax strings.
+DECORATION-CHAR is the character to use for the divider lines.
+TEXT is the comment text.
+LENGTH is the total width of each line.
+PADDING is the number of spaces before the text."
+ (when (< padding 0)
+ (error "Padding %d cannot be negative" padding))
+ (let* ((current-column-pos (current-column))
+ (min-length (+ current-column-pos
+ (length cmt-start)
+ (if (equal cmt-start ";") 1 0) ; doubled semicolon
+ 1 ; space after comment-start
+ 3 ; minimum decoration chars
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end))))))
+ (when (< length min-length)
+ (error "Length %d is too small to generate comment (minimum %d)" length min-length))
+ (let* ((available-width (- length current-column-pos
+ (length cmt-start)
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end)))))
+ (line (make-string available-width (string-to-char decoration-char))))
+ ;; Top line
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " ")
+ (insert line)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Text line with padding
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " ")
+ (dotimes (_ padding) (insert " "))
+ (insert text)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Bottom line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert cmt-start)
+ (when (equal cmt-start ";") (insert cmt-start))
+ (insert " ")
+ (insert line)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline))))
-(defun cj/comment-centered (&optional comment-char)
- "Insert comment text centered around the COMMENT-CHAR character.
-Default to the hash character when COMMENT-CHAR is nil.
-Use the lesser of `fill-column' or 80 to calculate the comment length.
-Begin and end line with the appropriate comment symbols for the current mode."
+(defun cj/comment-padded-divider ()
+ "Insert a padded divider comment banner.
+Prompts for decoration character, text, padding, and length option."
(interactive)
- (if (not (char-or-string-p comment-char))
- (setq comment-char "#"))
- (let* ((comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
- (fill-column (min fill-column 80))
- (comment-length (length comment))
- ;; (comment-start-length (length comment-start))
- ;; (comment-end-length (length comment-end))
- (current-column-pos (current-column))
- (space-on-each-side (/ (- fill-column
- current-column-pos
- comment-length
- (length comment-start)
- (length comment-end)
- ;; Single space on each side of comment
- (if (> comment-length 0) 2 0)
- ;; Single space after comment syntax sting
- 1)
- 2)))
- (if (< space-on-each-side 2)
- (message "Comment string is too big to fit in one line")
- (progn
- (insert comment-start)
- (when (equal comment-start ";") ;; emacs-lisp line comments are ';;'
- (insert comment-start)) ;; so insert comment-char again
- (insert " ")
- (dotimes (_ space-on-each-side) (insert comment-char))
- (when (> comment-length 0) (insert " "))
- (insert comment)
- (when (> comment-length 0) (insert " "))
- (dotimes (_ (if (= (% comment-length 2) 0)
- (- space-on-each-side 1)
- space-on-each-side))
- (insert comment-char))
- ;; Only insert trailing space and comment-end if comment-end is not empty
- (when (not (string-empty-p comment-end))
- (insert " ")
- (insert comment-end))))))
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (decoration-char (read-string "Decoration character (default =): " nil nil "="))
+ (text (read-string "Comment text: "))
+ (padding (string-to-number (read-string "Padding spaces (default 2): " nil nil "2")))
+ (length-option (completing-read "Length: "
+ '("fill-column" "half-column" "match-text")
+ nil t nil nil "fill-column"))
+ (length (cond
+ ((string= length-option "fill-column") fill-column)
+ ((string= length-option "half-column") (/ fill-column 2))
+ ((string= length-option "match-text")
+ (+ (length comment-start)
+ (if (equal comment-start ";") 1 0)
+ 1 ; space after comment-start
+ padding
+ (length text)
+ (if (string-empty-p comment-end) 0 (1+ (length comment-end))))))))
+ (cj/--comment-padded-divider comment-start comment-end decoration-char text length padding)))
;; -------------------------------- Comment Box --------------------------------
+(defun cj/--comment-box (cmt-start cmt-end decoration-char text length)
+ "Internal implementation: Generate a 3-line box comment with centered text.
+CMT-START and CMT-END are the comment syntax strings.
+DECORATION-CHAR is the character to use for borders.
+TEXT is the comment text (centered).
+LENGTH is the total width of each line."
+ (let* ((current-column-pos (current-column))
+ (comment-char (if (equal cmt-start ";") ";;" cmt-start))
+ (comment-end-char (if (string-empty-p cmt-end) comment-char cmt-end))
+ (min-length (+ current-column-pos
+ (length comment-char)
+ 2 ; spaces around content
+ (length comment-end-char)
+ 6))) ; minimum: 3 border chars + text space + 3 border chars
+ (when (< length min-length)
+ (error "Length %d is too small to generate comment (minimum %d)" length min-length))
+ (let* ((available-width (- length current-column-pos
+ (length comment-char)
+ (length comment-end-char)
+ 2)) ; spaces around content
+ (border-line (make-string available-width (string-to-char decoration-char)))
+ (text-length (length text))
+ ;; For text line: need space for decoration + space + text + space + decoration
+ (text-available (- available-width 4)) ; 2 for side decorations, 2 for spaces
+ (padding-each-side (max 1 (/ (- text-available text-length) 2)))
+ (right-padding (if (= (% (- text-available text-length) 2) 0)
+ padding-each-side
+ (1+ padding-each-side))))
+ ;; Top border
+ (insert comment-char " " border-line " " comment-end-char)
+ (newline)
+
+ ;; Centered text line with side borders
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert comment-char " " decoration-char " ")
+ (dotimes (_ padding-each-side) (insert " "))
+ (insert text)
+ (dotimes (_ right-padding) (insert " "))
+ (insert " " decoration-char " " comment-end-char)
+ (newline)
+
+ ;; Bottom border
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert comment-char " " border-line " " comment-end-char)
+ (newline))))
+
(defun cj/comment-box ()
- "Insert a comment box around text that the user inputs.
-The box extends to the fill column, centers the text, and uses the current
-mode's comment syntax at both the beginning and end of each line. The box
-respects the current indentation level and avoids trailing whitespace."
+ "Insert a 3-line comment box with centered text.
+Prompts for decoration character, text, and uses `fill-column' for length."
+ (interactive)
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (decoration-char (read-string "Decoration character (default -): " nil nil "-"))
+ (text (capitalize (string-trim (read-from-minibuffer "Comment: "))))
+ (length (min fill-column 80)))
+ (cj/--comment-box comment-start comment-end decoration-char text length)))
+
+;; ------------------------------ Heavy Box ------------------------------------
+
+(defun cj/--comment-heavy-box (cmt-start cmt-end decoration-char text length)
+ "Internal implementation: Generate a heavy box comment with blank lines.
+CMT-START and CMT-END are the comment syntax strings.
+DECORATION-CHAR is the character to use for borders.
+TEXT is the comment text (centered).
+LENGTH is the total width of each line."
+ (let* ((current-column-pos (current-column))
+ (comment-char (if (equal cmt-start ";") ";;" cmt-start))
+ (comment-end-char (if (string-empty-p cmt-end) comment-char cmt-end))
+ (available-width (- length current-column-pos
+ (length comment-char)
+ (length comment-end-char)
+ 2)) ; spaces around content
+ (border-line (make-string available-width (string-to-char decoration-char)))
+ (text-length (length text))
+ (padding-each-side (max 1 (/ (- available-width text-length) 2)))
+ (right-padding (if (= (% (- available-width text-length) 2) 0)
+ padding-each-side
+ (1+ padding-each-side))))
+ ;; Top border
+ (insert comment-char " " border-line " " comment-end-char)
+ (newline)
+
+ ;; Empty line with side borders
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert decoration-char)
+ (dotimes (_ available-width) (insert " "))
+ (insert " " decoration-char)
+ (newline)
+
+ ;; Centered text line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert decoration-char " ")
+ (dotimes (_ padding-each-side) (insert " "))
+ (insert text)
+ (dotimes (_ right-padding) (insert " "))
+ (insert " " decoration-char)
+ (newline)
+
+ ;; Empty line with side borders
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert decoration-char)
+ (dotimes (_ available-width) (insert " "))
+ (insert " " decoration-char)
+ (newline)
+
+ ;; Bottom border
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert comment-char " " border-line " " comment-end-char)
+ (newline)))
+
+(defun cj/comment-heavy-box ()
+ "Insert a heavy box comment with blank lines around centered text.
+Prompts for decoration character, text, and length option."
+ (interactive)
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (decoration-char (read-string "Decoration character (default *): " nil nil "*"))
+ (text (read-string "Comment text: "))
+ (length-option (completing-read "Length: "
+ '("fill-column" "half-column" "padded-text")
+ nil t nil nil "fill-column"))
+ (length (cond
+ ((string= length-option "fill-column") fill-column)
+ ((string= length-option "half-column") (/ fill-column 2))
+ ((string= length-option "padded-text")
+ (+ (current-column)
+ (length (if (equal comment-start ";") ";;" comment-start))
+ 2 ; decoration char + space
+ 4 ; minimum padding (2 on each side)
+ (length text)
+ (if (string-empty-p comment-end)
+ 1 ; just the side decoration
+ (1+ (length comment-end))))))))
+ (cj/--comment-heavy-box comment-start comment-end decoration-char text length)))
+
+;; ---------------------------- Unicode Box ------------------------------------
+
+(defun cj/--comment-unicode-box (cmt-start cmt-end text length box-style)
+ "Internal implementation: Generate a unicode box comment.
+CMT-START and CMT-END are the comment syntax strings.
+TEXT is the comment text.
+LENGTH is the total width of each line.
+BOX-STYLE is either \\='single or \\='double for line style."
+ (let* ((current-column-pos (current-column))
+ (comment-char (if (equal cmt-start ";") ";;" cmt-start))
+ (min-length (+ current-column-pos
+ (length comment-char)
+ 1 ; space after comment-char
+ 5 ; minimum: corner + corner + padding
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end))))))
+ (when (< length min-length)
+ (error "Length %d is too small to generate comment (minimum %d)" length min-length))
+ (let* ((available-width (- length current-column-pos
+ (length comment-char)
+ (if (string-empty-p cmt-end) 0 (1+ (length cmt-end)))
+ 3)) ; box corners and padding
+ (top-left (if (eq box-style 'double) "╔" "┌"))
+ (top-right (if (eq box-style 'double) "╗" "┐"))
+ (bottom-left (if (eq box-style 'double) "╚" "└"))
+ (bottom-right (if (eq box-style 'double) "╝" "┘"))
+ (horizontal (if (eq box-style 'double) "═" "─"))
+ (vertical (if (eq box-style 'double) "║" "│"))
+ (text-padding (- available-width (length text) 2)))
+ ;; Top line
+ (insert comment-char " " top-left)
+ (dotimes (_ available-width) (insert horizontal))
+ (insert top-right)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Text line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert comment-char " " vertical " " text)
+ (dotimes (_ text-padding) (insert " "))
+ (insert " " vertical)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline)
+
+ ;; Bottom line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert comment-char " " bottom-left)
+ (dotimes (_ available-width) (insert horizontal))
+ (insert bottom-right)
+ (when (not (string-empty-p cmt-end))
+ (insert " " cmt-end))
+ (newline))))
+
+(defun cj/comment-unicode-box ()
+ "Insert a unicode box comment.
+Prompts for text, box style, and length option."
+ (interactive)
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start)
+ comment-start
+ (read-string "Comment start character(s): ")))
+ (comment-end (if (and (boundp 'comment-end) comment-end)
+ comment-end
+ ""))
+ (text (read-string "Comment text: "))
+ (box-style (intern (completing-read "Box style: "
+ '("single" "double")
+ nil t nil nil "single")))
+ (length-option (completing-read "Length: "
+ '("fill-column" "half-column" "padded-text")
+ nil t nil nil "fill-column"))
+ (length (cond
+ ((string= length-option "fill-column") fill-column)
+ ((string= length-option "half-column") (/ fill-column 2))
+ ((string= length-option "padded-text")
+ (+ (current-column)
+ (length (if (equal comment-start ";") ";;" comment-start))
+ 5 ; box chars and spaces
+ (length text)
+ (if (string-empty-p comment-end) 0 (1+ (length comment-end))))))))
+ (cj/--comment-unicode-box comment-start comment-end text length box-style)))
+
+;; ---------------------------- Block Banner -----------------------------------
+
+(defun cj/--comment-block-banner (cmt-start cmt-end decoration-char text length)
+ "Internal implementation: Generate a block banner comment (JSDoc/Doxygen style).
+CMT-START should be the block comment start (e.g., '/*').
+CMT-END should be the block comment end (e.g., '*/').
+DECORATION-CHAR is the character to use for the border line.
+TEXT is the comment text.
+LENGTH is the total width of each line."
+ (let* ((current-column-pos (current-column))
+ (min-length (+ current-column-pos
+ (length cmt-start)
+ 3))) ; minimum: 3 decoration chars
+ (when (< length min-length)
+ (error "Length %d is too small to generate comment (minimum %d)" length min-length))
+ (let* ((available-width (- length current-column-pos (length cmt-start) 1))
+ (border-line (make-string available-width (string-to-char decoration-char))))
+ ;; Top line
+ (insert cmt-start border-line)
+ (newline)
+
+ ;; Text line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert " " decoration-char " " text)
+ (newline)
+
+ ;; Bottom line
+ (dotimes (_ current-column-pos) (insert " "))
+ (insert " ")
+ (dotimes (_ (- available-width (length cmt-end)))
+ (insert decoration-char))
+ (insert cmt-end)
+ (newline))))
+
+(defun cj/comment-block-banner ()
+ "Insert a block banner comment (JSDoc/Doxygen style).
+Prompts for decoration character, text, and length option."
(interactive)
- (let* ((comment-char (if (equal comment-start ";") ";;"
- (string-trim comment-start)))
- (comment-end-char (if (string-empty-p comment-end)
- comment-char
- (string-trim comment-end)))
- (line-char (if (equal comment-char ";;") "-" "#"))
- (comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
- (comment-length (length comment))
- (current-column-pos (current-column))
- (max-width (min fill-column 80))
- ;; Calculate available width between comment markers
- (available-width (- max-width
- current-column-pos
- (length comment-char)
- (length comment-end-char)))
- ;; Inner width is the width without the spaces after comment start and before comment end
- (inner-width (- available-width 2))
- ;; Calculate padding for each side of the centered text
- (padding-each-side (max 1 (/ (- inner-width comment-length) 2)))
- ;; Adjust for odd-length comments
- (right-padding (if (= (% (- inner-width comment-length) 2) 0)
- padding-each-side
- (1+ padding-each-side))))
-
- ;; Check if we have enough space
- (if (< inner-width (+ comment-length 4)) ; minimum sensible width
- (message "Comment string is too big to fit in one line")
- (progn
- ;; Top line - fill entirely with line characters except for space after comment start
- (insert comment-char)
- (insert " ")
- (insert (make-string inner-width (string-to-char line-char)))
- (insert " ")
- (insert comment-end-char)
- (newline)
-
- ;; Add indentation on the new line to match current column
- (dotimes (_ current-column-pos) (insert " "))
-
- ;; Middle line with centered text
- (insert comment-char)
- (insert " ")
- ;; Left padding
- (dotimes (_ padding-each-side) (insert " "))
- ;; The comment text
- (insert comment)
- ;; Right padding
- (dotimes (_ right-padding) (insert " "))
- (insert " ")
- (insert comment-end-char)
- (newline)
-
- ;; Add indentation on the new line to match current column
- (dotimes (_ current-column-pos) (insert " "))
-
- ;; Bottom line - same as top line
- (insert comment-char)
- (insert " ")
- (dotimes (_ inner-width) (insert line-char))
- (insert " ")
- (insert comment-end-char)
- (newline)))))
+ (let* ((comment-start (if (and (boundp 'comment-start) comment-start
+ (string-match-p "/\\*" comment-start))
+ comment-start
+ (read-string "Block comment start (e.g., /*): " nil nil "/*")))
+ (comment-end (if (and (boundp 'comment-end) comment-end
+ (not (string-empty-p comment-end)))
+ comment-end
+ (read-string "Block comment end (e.g., */): " nil nil "*/")))
+ (decoration-char (read-string "Decoration character (default *): " nil nil "*"))
+ (text (read-string "Comment text: "))
+ (length-option (completing-read "Length: "
+ '("fill-column" "half-column" "match-text")
+ nil t nil nil "fill-column"))
+ (length (cond
+ ((string= length-option "fill-column") fill-column)
+ ((string= length-option "half-column") (/ fill-column 2))
+ ((string= length-option "match-text")
+ (+ (current-column)
+ (length comment-start)
+ 2 ; space + decoration
+ (length text))))))
+ (cj/--comment-block-banner comment-start comment-end decoration-char text length)))
;; ------------------------------- Comment Hyphen ------------------------------
(defun cj/comment-hyphen()
"Insert a centered comment with `-' (hyphens) on each side.
-Leverages cj/comment-centered."
+Leverages cj/comment-inline-border."
(interactive)
- (cj/comment-centered "-"))
+ (cj/comment-inline-border "-"))
;; ------------------------------- Comment Keymap ------------------------------
(defvar-keymap cj/comment-map
:doc "Keymap for code comment operations"
"r" #'cj/comment-reformat
- "c" #'cj/comment-centered
+ "d" #'cj/delete-buffer-comments
+ "c" #'cj/comment-inline-border
"-" #'cj/comment-hyphen
+ "s" #'cj/comment-simple-divider
+ "p" #'cj/comment-padded-divider
"b" #'cj/comment-box
- "D" #'cj/delete-buffer-comments)
+ "h" #'cj/comment-heavy-box
+ "u" #'cj/comment-unicode-box
+ "n" #'cj/comment-block-banner)
(keymap-set cj/custom-keymap "C" cj/comment-map)
(with-eval-after-load 'which-key
diff --git a/tests/test-custom-comments-comment-block-banner.el b/tests/test-custom-comments-comment-block-banner.el
new file mode 100644
index 00000000..6561ebfa
--- /dev/null
+++ b/tests/test-custom-comments-comment-block-banner.el
@@ -0,0 +1,228 @@
+;;; test-custom-comments-comment-block-banner.el --- Tests for cj/comment-block-banner -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-block-banner function from custom-comments.el
+;;
+;; This function generates a 3-line block banner comment (JSDoc/Doxygen style):
+;; - Top line: comment-start (e.g., /*) + decoration chars
+;; - Text line: space + decoration char + space + text
+;; - Bottom line: space + decoration chars + comment-end (e.g., */)
+;;
+;; This style is common in C, JavaScript, Java, and other languages that use
+;; block comments.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-block-banner)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in C (the primary language for this style)
+;; - Representative testing in JavaScript/Java (similar block comment syntax)
+;; - This style is specifically designed for block comments, so we focus
+;; testing on languages that use /* */ syntax
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-block-banner-at-column (column-pos comment-start comment-end decoration-char text length)
+ "Test cj/--comment-block-banner at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-block-banner with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, and LENGTH.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-block-banner comment-start comment-end decoration-char text length)
+ (buffer-string)))
+
+;;; C/JavaScript/Java Tests (Block Comment Languages - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-block-banner-c-basic ()
+ "Should generate 3-line block banner in C style."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Section Header" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; First line should start with /*
+ (should (string-match-p "^/\\*\\*" result))
+ ;; Middle line should contain text
+ (should (string-match-p "\\* Section Header" result))
+ ;; Last line should end with */
+ (should (string-match-p "\\*/$" result))))
+
+(ert-deftest test-block-banner-c-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "#" "Header" 70)))
+ (should (string-match-p "/\\*#" result))
+ (should (string-match-p " # Header" result))))
+
+(ert-deftest test-block-banner-c-custom-text ()
+ "Should include custom text in banner."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Custom Text Here" 70)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-block-banner-c-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "" 70)))
+ ;; Should still generate 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should have comment delimiters
+ (should (string-match-p "/\\*" result))
+ (should (string-match-p "\\*/$" result))))
+
+(ert-deftest test-block-banner-c-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Header" 70)))
+ ;; First character should be /
+ (should (string-prefix-p "/*" result))))
+
+(ert-deftest test-block-banner-c-indented ()
+ "Should work when indented."
+ (let ((result (test-block-banner-at-column 4 "/*" "*/" "*" "Header" 70)))
+ ;; First line should start with spaces
+ (should (string-prefix-p " /*" result))
+ ;; Other lines should be indented
+ (let ((lines (split-string result "\n" t)))
+ (should (string-prefix-p " " (nth 1 lines))) ; text line has extra space
+ (should (string-prefix-p " " (nth 2 lines)))))) ; bottom line has extra space
+
+(ert-deftest test-block-banner-c-short-text ()
+ "Should handle short text properly."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "X" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-block-banner-c-long-text ()
+ "Should handle longer text."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "This is a longer header text" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "This is a longer header text" result))))
+
+(ert-deftest test-block-banner-c-custom-length ()
+ "Should respect custom length."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Header" 50)))
+ ;; Top line should be approximately 50 chars
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (<= (length first-line) 51))
+ (should (>= (length first-line) 48)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-block-banner-c-minimum-length ()
+ "Should work with minimum viable length."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "X" 10)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-block-banner-c-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Header" 200)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Top line should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-block-banner-c-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "✦" "Header" 70)))
+ (should (string-match-p "✦" result))))
+
+(ert-deftest test-block-banner-c-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-block-banner-at-column 0 "/*" "*/" "*" "Hello 👋 مرحبا café" 70)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-block-banner-c-very-long-text ()
+ "Should handle very long text."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-block-banner-at-column 0 "/*" "*/" "*" long-text 70)))
+ ;; Should still generate output
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-block-banner-c-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-block-banner-at-column 60 "/*" "*/" "*" "Header" 100)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; First line should start with 60 spaces
+ (should (string-prefix-p (make-string 60 ?\s) result))))
+
+;;; Error Cases
+
+(ert-deftest test-block-banner-c-length-too-small ()
+ "Should error when length is too small."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" "*" "Header" 3)
+ :type 'error))
+
+(ert-deftest test-block-banner-c-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" "*" "Header" -10)
+ :type 'error))
+
+(ert-deftest test-block-banner-c-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" "*" "Header" 0)
+ :type 'error))
+
+(ert-deftest test-block-banner-c-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" nil "Header" 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-block-banner-c-nil-text ()
+ "Should error when text is nil."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" "*" nil 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-block-banner-c-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-block-banner-at-column 0 "/*" "*/" "*" "Header" "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Alternative Block Comment Styles
+
+(ert-deftest test-block-banner-java-style ()
+ "Should work with Java-style block comments."
+ (let ((result (test-block-banner-at-column 0 "/**" "*/" "*" "JavaDoc Comment" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\*\\*\\*" result))
+ (should (string-match-p "JavaDoc Comment" result))))
+
+(ert-deftest test-block-banner-js-style ()
+ "Should work with JavaScript-style block comments."
+ (let ((result (test-block-banner-at-column 2 "/*" "*/" "*" "Function Documentation" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-prefix-p " /*" result))
+ (should (string-match-p "Function Documentation" result))))
+
+(provide 'test-custom-comments-comment-block-banner)
+;;; test-custom-comments-comment-block-banner.el ends here
diff --git a/tests/test-custom-comments-comment-box.el b/tests/test-custom-comments-comment-box.el
new file mode 100644
index 00000000..10b1a67d
--- /dev/null
+++ b/tests/test-custom-comments-comment-box.el
@@ -0,0 +1,241 @@
+;;; test-custom-comments-comment-box.el --- Tests for cj/comment-box -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-box function from custom-comments.el
+;;
+;; This function generates a 3-line box comment:
+;; - Top border: comment-start + full decoration line
+;; - Text line: comment-start + decoration + spaces + text + spaces + decoration
+;; - Bottom border: comment-start + full decoration line
+;;
+;; The text is centered within the box with decoration characters on the sides.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-box)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-comment-box-at-column (column-pos comment-start comment-end decoration-char text length)
+ "Test cj/--comment-box at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-box with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, and LENGTH.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-box comment-start comment-end decoration-char text length)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-comment-box-elisp-basic ()
+ "Should generate 3-line box in emacs-lisp style."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Section Header" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; First line should start with ;; and have decoration
+ (should (string-match-p "^;; -" result))
+ ;; Middle line should contain text with side borders
+ (should (string-match-p ";; - .* Section Header .* - ;;" result))
+ ;; Should have top and bottom borders
+ (should (string-match-p "^;; -" result))))
+
+(ert-deftest test-comment-box-elisp-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "*" "Header" 70)))
+ (should (string-match-p ";; \\*" result))
+ (should-not (string-match-p "-" result))))
+
+(ert-deftest test-comment-box-elisp-custom-text ()
+ "Should include custom text centered in box."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Custom Text Here" 70)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-comment-box-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "" 70)))
+ ;; Should still generate 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should have side borders
+ (should (string-match-p "- .*-" result))))
+
+(ert-deftest test-comment-box-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Header" 70)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-comment-box-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-comment-box-at-column 4 ";;" "" "-" "Header" 70)))
+ ;; First line should start with spaces
+ (should (string-prefix-p " ;;" result))
+ ;; Other lines should be indented
+ (let ((lines (split-string result "\n" t)))
+ (should (string-prefix-p " " (nth 1 lines)))
+ (should (string-prefix-p " " (nth 2 lines))))))
+
+(ert-deftest test-comment-box-elisp-short-text ()
+ "Should center short text properly."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "X" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present and centered
+ (should (string-match-p "- .* X .* -" result))))
+
+(ert-deftest test-comment-box-elisp-long-text ()
+ "Should handle longer text."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "This is a longer header text" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "This is a longer header text" result))))
+
+;;; Boundary Cases
+
+(ert-deftest test-comment-box-elisp-minimum-length ()
+ "Should work with minimum viable length."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "X" 15)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-comment-box-elisp-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Header" 200)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Border lines should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-comment-box-elisp-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "═" "Header" 70)))
+ (should (string-match-p "═" result))))
+
+(ert-deftest test-comment-box-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Hello 👋 مرحبا café" 70)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-comment-box-elisp-very-long-text ()
+ "Should handle very long text."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-comment-box-at-column 0 ";;" "" "-" long-text 70)))
+ ;; Should still generate output
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-comment-box-elisp-comment-end-symmetric ()
+ "Should use symmetric comment syntax when comment-end is empty."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "Header" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should use ;; on both sides for symmetry
+ (should (string-match-p ";;.*;;$" result))))
+
+(ert-deftest test-comment-box-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-comment-box-at-column 60 ";;" "" "-" "Header" 100)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; First line should start with 60 spaces
+ (should (string-prefix-p (make-string 60 ?\s) result))))
+
+(ert-deftest test-comment-box-elisp-text-centering-even ()
+ "Should center text properly with even length."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "EVEN" 70)))
+ ;; Text should be centered (roughly equal padding on both sides)
+ (should (string-match-p "- .* EVEN .* -" result))))
+
+(ert-deftest test-comment-box-elisp-text-centering-odd ()
+ "Should center text properly with odd length."
+ (let ((result (test-comment-box-at-column 0 ";;" "" "-" "ODD" 70)))
+ ;; Text should be centered (roughly equal padding on both sides)
+ (should (string-match-p "- .* ODD .* -" result))))
+
+;;; Error Cases
+
+(ert-deftest test-comment-box-elisp-length-too-small ()
+ "Should error when length is too small."
+ (should-error
+ (test-comment-box-at-column 0 ";;" "" "-" "Header" 5)
+ :type 'error))
+
+(ert-deftest test-comment-box-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-comment-box-at-column 0 ";;" "" "-" "Header" -10)
+ :type 'error))
+
+(ert-deftest test-comment-box-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-comment-box-at-column 0 ";;" "" "-" "Header" 0)
+ :type 'error))
+
+(ert-deftest test-comment-box-elisp-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-comment-box-at-column 0 ";;" "" nil "Header" 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-comment-box-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-comment-box-at-column 0 ";;" "" "-" "Header" "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-comment-box-python-basic ()
+ "Should generate box with Python comment syntax."
+ (let ((result (test-comment-box-at-column 0 "#" "" "-" "Section" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^# -" result))
+ (should (string-match-p "Section" result))))
+
+(ert-deftest test-comment-box-python-indented ()
+ "Should handle indented Python comments."
+ (let ((result (test-comment-box-at-column 4 "#" "" "#" "Function Section" 70)))
+ (should (string-prefix-p " #" result))
+ (should (string-match-p "Function Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-comment-box-c-block-comments ()
+ "Should generate box with C block comment syntax."
+ (let ((result (test-comment-box-at-column 0 "/*" "*/" "-" "Section" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* -" result))
+ (should (string-match-p "Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(provide 'test-custom-comments-comment-box)
+;;; test-custom-comments-comment-box.el ends here
diff --git a/tests/test-custom-comments-comment-heavy-box.el b/tests/test-custom-comments-comment-heavy-box.el
new file mode 100644
index 00000000..30289625
--- /dev/null
+++ b/tests/test-custom-comments-comment-heavy-box.el
@@ -0,0 +1,251 @@
+;;; test-custom-comments-comment-heavy-box.el --- Tests for cj/comment-heavy-box -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-heavy-box function from custom-comments.el
+;;
+;; This function generates a 5-line heavy box comment:
+;; - Top border: comment-start + full decoration line
+;; - Empty line: decoration char + spaces + decoration char
+;; - Centered text: decoration char + spaces + text + spaces + decoration char
+;; - Empty line: decoration char + spaces + decoration char
+;; - Bottom border: comment-start + full decoration line
+;;
+;; The text is centered within the box with padding on both sides.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-heavy-box)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-heavy-box-at-column (column-pos comment-start comment-end decoration-char text length)
+ "Test cj/--comment-heavy-box at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-heavy-box with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, and LENGTH.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-heavy-box comment-start comment-end decoration-char text length)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-heavy-box-elisp-basic ()
+ "Should generate 5-line heavy box in emacs-lisp style."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Section Header" 70)))
+ ;; Should have 5 lines
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; First line should start with ;; and have decoration
+ (should (string-match-p "^;; \\*" result))
+ ;; Middle line should contain centered text
+ (should (string-match-p "Section Header" result))
+ ;; Should have side borders
+ (should (string-match-p "^\\*.*\\*$" result))))
+
+(ert-deftest test-heavy-box-elisp-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "#" "Header" 70)))
+ (should (string-match-p ";; #" result))
+ (should-not (string-match-p "\\*" result))))
+
+(ert-deftest test-heavy-box-elisp-custom-text ()
+ "Should include custom text centered in box."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Custom Text Here" 70)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-heavy-box-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "" 70)))
+ ;; Should still generate 5 lines
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; Middle line should just have side borders and spaces
+ (should (string-match-p "^\\*.*\\*$" result))))
+
+(ert-deftest test-heavy-box-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Header" 70)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-heavy-box-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-heavy-box-at-column 4 ";;" "" "*" "Header" 70)))
+ ;; First line should start with spaces
+ (should (string-prefix-p " ;;" result))
+ ;; Other lines should be indented
+ (let ((lines (split-string result "\n" t)))
+ (should (string-prefix-p " " (nth 1 lines)))
+ (should (string-prefix-p " " (nth 2 lines))))))
+
+(ert-deftest test-heavy-box-elisp-short-text ()
+ "Should center short text properly."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "X" 70)))
+ ;; Should have 5 lines
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; Text should be present and centered
+ (should (string-match-p "\\* .* X .* \\*" result))))
+
+(ert-deftest test-heavy-box-elisp-long-text ()
+ "Should handle longer text."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "This is a longer header text" 70)))
+ ;; Should have 5 lines
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "This is a longer header text" result))))
+
+;;; Boundary Cases
+
+(ert-deftest test-heavy-box-elisp-minimum-length ()
+ "Should work with minimum viable length."
+ ;; Minimum for a box: comment + spaces + borders + minimal content
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "X" 15)))
+ (should (= 5 (length (split-string result "\n" t))))
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-heavy-box-elisp-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Header" 200)))
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; Border lines should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-heavy-box-elisp-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "═" "Header" 70)))
+ (should (string-match-p "═" result))))
+
+(ert-deftest test-heavy-box-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Hello 👋 مرحبا café" 70)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-heavy-box-elisp-very-long-text ()
+ "Should handle very long text."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-heavy-box-at-column 0 ";;" "" "*" long-text 70)))
+ ;; Should still generate output
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-heavy-box-elisp-comment-end-empty ()
+ "Should handle empty comment-end by using symmetric comment syntax."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "Header" 70)))
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; When comment-end is empty, function uses comment-char for symmetry
+ ;; So border lines will have ";; ... ;;" for visual balance
+ (should (string-match-p ";;.*;;$" result))))
+
+(ert-deftest test-heavy-box-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-heavy-box-at-column 60 ";;" "" "*" "Header" 100)))
+ (should (= 5 (length (split-string result "\n" t))))
+ ;; First line should start with 60 spaces
+ (should (string-prefix-p (make-string 60 ?\s) result))))
+
+(ert-deftest test-heavy-box-elisp-text-centering-even ()
+ "Should center text properly with even length."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "EVEN" 70)))
+ ;; Text should be centered (roughly equal padding on both sides)
+ (should (string-match-p "\\* .* EVEN .* \\*" result))))
+
+(ert-deftest test-heavy-box-elisp-text-centering-odd ()
+ "Should center text properly with odd length."
+ (let ((result (test-heavy-box-at-column 0 ";;" "" "*" "ODD" 70)))
+ ;; Text should be centered (roughly equal padding on both sides)
+ (should (string-match-p "\\* .* ODD .* \\*" result))))
+
+;;; Error Cases
+
+(ert-deftest test-heavy-box-elisp-length-too-small ()
+ "Should error when length is too small."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" "*" "Header" 5)
+ :type 'error))
+
+(ert-deftest test-heavy-box-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" "*" "Header" -10)
+ :type 'error))
+
+(ert-deftest test-heavy-box-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" "*" "Header" 0)
+ :type 'error))
+
+(ert-deftest test-heavy-box-elisp-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" nil "Header" 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-heavy-box-elisp-nil-text ()
+ "Should error when text is nil."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" "*" nil 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-heavy-box-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-heavy-box-at-column 0 ";;" "" "*" "Header" "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-heavy-box-python-basic ()
+ "Should generate heavy box with Python comment syntax."
+ (let ((result (test-heavy-box-at-column 0 "#" "" "*" "Section" 70)))
+ (should (= 5 (length (split-string result "\n" t))))
+ (should (string-match-p "^# \\*" result))
+ (should (string-match-p "Section" result))))
+
+(ert-deftest test-heavy-box-python-indented ()
+ "Should handle indented Python comments."
+ (let ((result (test-heavy-box-at-column 4 "#" "" "#" "Function Section" 70)))
+ (should (string-prefix-p " #" result))
+ (should (string-match-p "Function Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-heavy-box-c-block-comments ()
+ "Should generate heavy box with C block comment syntax."
+ (let ((result (test-heavy-box-at-column 0 "/*" "*/" "*" "Section" 70)))
+ (should (= 5 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* \\*" result))
+ (should (string-match-p "Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(provide 'test-custom-comments-comment-heavy-box)
+;;; test-custom-comments-comment-heavy-box.el ends here
diff --git a/tests/test-custom-comments-comment-inline-border.el b/tests/test-custom-comments-comment-inline-border.el
new file mode 100644
index 00000000..ca2bef06
--- /dev/null
+++ b/tests/test-custom-comments-comment-inline-border.el
@@ -0,0 +1,235 @@
+;;; test-custom-comments-comment-inline-border.el --- Tests for cj/comment-inline-border -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-inline-border function from custom-comments.el
+;;
+;; This function generates a single-line centered comment with decoration borders:
+;; Format: comment-start + decoration + space + text + space + decoration + comment-end
+;; Example: ";; ======= Section Header ======="
+;;
+;; The text is centered with decoration characters on both sides. When text has
+;; odd length, the right side gets one less decoration character.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-inline-border)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-inline-border-at-column (column-pos comment-start comment-end decoration-char text length)
+ "Test cj/--comment-inline-border at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-inline-border with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, and LENGTH.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-inline-border comment-start comment-end decoration-char text length)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-inline-border-elisp-basic ()
+ "Should generate single-line centered comment in emacs-lisp style."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Section Header" 70)))
+ ;; Should be single line
+ (should (= 1 (length (split-string result "\n" t))))
+ ;; Should start with ;;
+ (should (string-match-p "^;; =" result))
+ ;; Should contain text
+ (should (string-match-p "Section Header" result))
+ ;; Should have decoration on both sides
+ (should (string-match-p "= Section Header =" result))))
+
+(ert-deftest test-inline-border-elisp-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "#" "Header" 70)))
+ (should (string-match-p ";; #" result))
+ (should (string-match-p "# Header #" result))
+ (should-not (string-match-p "=" result))))
+
+(ert-deftest test-inline-border-elisp-custom-text ()
+ "Should include custom text centered."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Custom Text Here" 70)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-inline-border-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "" 70)))
+ ;; Should still generate output with decoration
+ (should (string-match-p ";; =" result))
+ ;; Should not have extra spaces where text would be
+ (should-not (string-match-p " " result))))
+
+(ert-deftest test-inline-border-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Header" 70)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-inline-border-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-inline-border-at-column 4 ";;" "" "=" "Header" 70)))
+ ;; Result should start with spaces
+ (should (string-prefix-p " ;;" result))))
+
+(ert-deftest test-inline-border-elisp-short-text ()
+ "Should center short text properly."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "X" 70)))
+ (should (string-match-p "X" result))
+ ;; Should have decoration on both sides
+ (should (string-match-p "= X =" result))))
+
+(ert-deftest test-inline-border-elisp-custom-length ()
+ "Should respect custom length."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Header" 50)))
+ ;; Line should be approximately 50 chars
+ (let ((line (car (split-string result "\n" t))))
+ (should (<= (length line) 51))
+ (should (>= (length line) 48)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-inline-border-elisp-minimum-length ()
+ "Should work with minimum viable length."
+ ;; Minimum: 2 (;;) + 1 (space) + 1 (space) + 2 (min decoration each side) = 6
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "" 10)))
+ (should (string-match-p ";" result))))
+
+(ert-deftest test-inline-border-elisp-text-centering-even ()
+ "Should center text properly with even length."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "EVEN" 70)))
+ ;; Text should be centered with roughly equal decoration
+ (should (string-match-p "= EVEN =" result))))
+
+(ert-deftest test-inline-border-elisp-text-centering-odd ()
+ "Should center text properly with odd length."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "ODD" 70)))
+ ;; Text should be centered (right side has one less due to odd length)
+ (should (string-match-p "= ODD =" result))))
+
+(ert-deftest test-inline-border-elisp-very-long-text ()
+ "Should handle text that fills most of the line."
+ (let* ((long-text (make-string 50 ?x))
+ (result (test-inline-border-at-column 0 ";;" "" "=" long-text 70)))
+ ;; Should still have decoration
+ (should (string-match-p "=" result))
+ ;; Text should be present
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-inline-border-elisp-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "─" "Header" 70)))
+ (should (string-match-p "─" result))))
+
+(ert-deftest test-inline-border-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Hello 👋 café" 70)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-inline-border-elisp-comment-end-empty ()
+ "Should handle empty comment-end correctly."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Header" 70)))
+ ;; Line should not have trailing comment-end
+ (should-not (string-match-p ";;$" result))))
+
+(ert-deftest test-inline-border-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-inline-border-at-column 60 ";;" "" "=" "H" 100)))
+ (should (string-prefix-p (make-string 60 ?\s) result))))
+
+(ert-deftest test-inline-border-elisp-minimum-decoration-each-side ()
+ "Should have at least 2 decoration chars on each side."
+ (let ((result (test-inline-border-at-column 0 ";;" "" "=" "Test" 20)))
+ ;; Should have at least == on each side
+ (should (string-match-p "== Test ==" result))))
+
+;;; Error Cases
+
+(ert-deftest test-inline-border-elisp-length-too-small ()
+ "Should error when length is too small for text."
+ (should-error
+ (test-inline-border-at-column 0 ";;" "" "=" "Very Long Header Text" 20)
+ :type 'error))
+
+(ert-deftest test-inline-border-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-inline-border-at-column 0 ";;" "" "=" "Header" -10)
+ :type 'error))
+
+(ert-deftest test-inline-border-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-inline-border-at-column 0 ";;" "" "=" "Header" 0)
+ :type 'error))
+
+(ert-deftest test-inline-border-elisp-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-inline-border-at-column 0 ";;" "" nil "Header" 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-inline-border-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-inline-border-at-column 0 ";;" "" "=" "Header" "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-inline-border-python-basic ()
+ "Should generate inline border with Python comment syntax."
+ (let ((result (test-inline-border-at-column 0 "#" "" "=" "Section" 70)))
+ (should (string-match-p "^# =" result))
+ (should (string-match-p "Section" result))))
+
+(ert-deftest test-inline-border-python-indented ()
+ "Should handle indented Python comments."
+ (let ((result (test-inline-border-at-column 4 "#" "" "-" "Function Section" 70)))
+ (should (string-prefix-p " #" result))
+ (should (string-match-p "Function Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-inline-border-c-block-comments ()
+ "Should generate inline border with C block comment syntax."
+ (let ((result (test-inline-border-at-column 0 "/*" "*/" "=" "Section" 70)))
+ (should (string-match-p "^/\\* =" result))
+ (should (string-match-p "Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/$" result))))
+
+(ert-deftest test-inline-border-c-line-comments ()
+ "Should generate inline border with C line comment syntax."
+ (let ((result (test-inline-border-at-column 0 "//" "" "-" "Header" 70)))
+ (should (string-match-p "^// -" result))
+ (should (string-match-p "Header" result))))
+
+(provide 'test-custom-comments-comment-inline-border)
+;;; test-custom-comments-comment-inline-border.el ends here
diff --git a/tests/test-custom-comments-comment-padded-divider.el b/tests/test-custom-comments-comment-padded-divider.el
new file mode 100644
index 00000000..702a4c67
--- /dev/null
+++ b/tests/test-custom-comments-comment-padded-divider.el
@@ -0,0 +1,250 @@
+;;; test-custom-comments-comment-padded-divider.el --- Tests for cj/comment-padded-divider -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-padded-divider function from custom-comments.el
+;;
+;; This function generates a padded 3-line comment divider banner:
+;; - Top line: comment-start + decoration chars
+;; - Middle line: comment-start + padding spaces + text
+;; - Bottom line: comment-start + decoration chars
+;;
+;; The key difference from simple-divider is the PADDING parameter which
+;; adds spaces before the text to create visual indentation.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-padded-divider)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-padded-divider-at-column (column-pos comment-start comment-end decoration-char text length padding)
+ "Test cj/--comment-padded-divider at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-padded-divider with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, LENGTH, and PADDING.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-padded-divider comment-start comment-end decoration-char text length padding)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-padded-divider-elisp-basic ()
+ "Should generate padded 3-line divider in emacs-lisp style."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Section Header" 70 2)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; First line should start with ;; and have decoration
+ (should (string-match-p "^;; =" result))
+ ;; Middle line should contain text with padding
+ (should (string-match-p ";; Section Header" result))))
+
+(ert-deftest test-padded-divider-elisp-custom-padding ()
+ "Should respect custom padding value."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Header" 70 4)))
+ ;; Middle line should have 4 spaces before text
+ (should (string-match-p ";; Header" result))))
+
+(ert-deftest test-padded-divider-elisp-zero-padding ()
+ "Should work with zero padding."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "-" "Header" 70 0)))
+ ;; Middle line should have text immediately after comment-start + space
+ (should (string-match-p "^;; Header$" result))))
+
+(ert-deftest test-padded-divider-elisp-large-padding ()
+ "Should work with large padding value."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Text" 70 10)))
+ ;; Middle line should have 10 spaces before text
+ (should (string-match-p ";; Text" result))))
+
+(ert-deftest test-padded-divider-elisp-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "*" "Header" 70 2)))
+ (should (string-match-p ";; \\*" result))
+ (should-not (string-match-p ";; =" result))))
+
+(ert-deftest test-padded-divider-elisp-custom-text ()
+ "Should include custom text in middle line."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Custom Text Here" 70 2)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-padded-divider-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "-" "" 70 2)))
+ ;; Should still generate 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should just be comment-start + padding
+ (should (string-match-p "^;; *\n" result))))
+
+(ert-deftest test-padded-divider-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Header" 70 2)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-padded-divider-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-padded-divider-at-column 4 ";;" "" "=" "Header" 70 2)))
+ ;; Result should start with spaces
+ (should (string-prefix-p " ;;" result))
+ ;; All lines should be indented
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p " ;;" line)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-padded-divider-elisp-minimum-length ()
+ "Should work with minimum viable length at column 0."
+ ;; Minimum: 2 (;;) + 1 (space) + 1 (space) + 3 (dashes) = 7
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "-" "" 7 0)))
+ (should (= 3 (length (split-string result "\n" t))))))
+
+(ert-deftest test-padded-divider-elisp-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Header" 200 2)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Decoration lines should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-padded-divider-elisp-padding-larger-than-length ()
+ "Should handle padding that exceeds reasonable bounds."
+ ;; This tests behavior when padding is very large relative to length
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "X" 70 50)))
+ ;; Should still generate output (text may extend beyond decoration)
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-padded-divider-elisp-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "─" "Header" 70 2)))
+ (should (string-match-p "─" result))))
+
+(ert-deftest test-padded-divider-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Hello 👋 مرحبا café" 70 2)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-padded-divider-elisp-very-long-text ()
+ "Should handle very long text."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-padded-divider-at-column 0 ";;" "" "=" long-text 70 2)))
+ ;; Should still generate output
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-padded-divider-elisp-comment-end-empty ()
+ "Should handle empty comment-end correctly."
+ (let ((result (test-padded-divider-at-column 0 ";;" "" "=" "Header" 70 2)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Lines should not have trailing comment-end
+ (should-not (string-match-p ";;.*;;$" result))))
+
+(ert-deftest test-padded-divider-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-padded-divider-at-column 60 ";;" "" "=" "Header" 100 2)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; All lines should start with 60 spaces
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p (make-string 60 ?\s) line)))))
+
+;;; Error Cases
+
+(ert-deftest test-padded-divider-elisp-negative-padding ()
+ "Should error with negative padding."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" "Header" 70 -5)
+ :type 'error))
+
+(ert-deftest test-padded-divider-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" "Header" -10 2)
+ :type 'error))
+
+(ert-deftest test-padded-divider-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" "Header" 0 2)
+ :type 'error))
+
+(ert-deftest test-padded-divider-elisp-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" nil "Header" 70 2)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-padded-divider-elisp-nil-text ()
+ "Should error when text is nil."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" nil 70 2)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-padded-divider-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" "Header" "not-a-number" 2)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-padded-divider-elisp-non-integer-padding ()
+ "Should error when padding is not an integer."
+ (should-error
+ (test-padded-divider-at-column 0 ";;" "" "=" "Header" 70 "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-padded-divider-python-basic ()
+ "Should generate padded divider with Python comment syntax."
+ (let ((result (test-padded-divider-at-column 0 "#" "" "=" "Section" 70 2)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^# =" result))
+ (should (string-match-p "# Section" result))))
+
+(ert-deftest test-padded-divider-python-indented ()
+ "Should handle indented Python comments with padding."
+ (let ((result (test-padded-divider-at-column 4 "#" "" "-" "Function Section" 70 4)))
+ (should (string-prefix-p " #" result))
+ (should (string-match-p "Function Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-padded-divider-c-block-comments ()
+ "Should generate padded divider with C block comment syntax."
+ (let ((result (test-padded-divider-at-column 0 "/*" "*/" "=" "Section" 70 2)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* =" result))
+ (should (string-match-p "/\\* Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(provide 'test-custom-comments-comment-padded-divider)
+;;; test-custom-comments-comment-padded-divider.el ends here
diff --git a/tests/test-custom-comments-comment-simple-divider.el b/tests/test-custom-comments-comment-simple-divider.el
new file mode 100644
index 00000000..a61e6b4c
--- /dev/null
+++ b/tests/test-custom-comments-comment-simple-divider.el
@@ -0,0 +1,246 @@
+;;; test-custom-comments-comment-simple-divider.el --- Tests for cj/comment-simple-divider -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-simple-divider function from custom-comments.el
+;;
+;; This function generates a simple 3-line comment divider banner:
+;; - Top line: comment-start + decoration chars
+;; - Middle line: comment-start + text
+;; - Bottom line: comment-start + decoration chars
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-simple-divider)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-simple-divider-at-column (column-pos comment-start comment-end decoration-char text length)
+ "Test cj/--comment-simple-divider at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-simple-divider with
+COMMENT-START, COMMENT-END, DECORATION-CHAR, TEXT, and LENGTH.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-simple-divider comment-start comment-end decoration-char text length)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases
+
+(ert-deftest test-simple-divider-elisp-basic ()
+ "Should generate simple 3-line divider in emacs-lisp style."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-" "Section Header" 70)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Each line should start with ;;
+ (should (string-match-p "^;; -" result))
+ ;; Middle line should contain text
+ (should (string-match-p ";; Section Header" result))))
+
+(ert-deftest test-simple-divider-elisp-custom-decoration ()
+ "Should use custom decoration character."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "=" "Header" 70)))
+ (should (string-match-p ";; =" result))
+ (should-not (string-match-p ";; -" result))))
+
+(ert-deftest test-simple-divider-elisp-custom-text ()
+ "Should include custom text in middle line."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-" "Custom Text Here" 70)))
+ (should (string-match-p ";; Custom Text Here" result))))
+
+(ert-deftest test-simple-divider-elisp-custom-length ()
+ "Should respect custom length."
+ (let* ((result (test-simple-divider-at-column 0 ";;" "" "-" "Header" 50))
+ (lines (split-string result "\n" t)))
+ ;; Should have 3 lines
+ (should (= 3 (length lines)))
+ ;; First and last lines (decoration) should be approximately 50 chars
+ (should (<= (length (car lines)) 51))
+ (should (>= (length (car lines)) 48))
+ (should (<= (length (car (last lines))) 51))
+ (should (>= (length (car (last lines))) 48))))
+
+(ert-deftest test-simple-divider-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-" "" 70)))
+ ;; Should still generate 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should just be comment-start
+ (should (string-match-p "^;; *\n" result))))
+
+(ert-deftest test-simple-divider-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-""Header" 70)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-simple-divider-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-simple-divider-at-column 4 ";;" "" "-""Header" 70)))
+ ;; Result should start with spaces
+ (should (string-prefix-p " ;;" result))
+ ;; All lines should be indented
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p " ;;" line)))))
+
+;;; Boundary Cases
+
+(ert-deftest test-simple-divider-elisp-minimum-length ()
+ "Should work with minimum viable length at column 0."
+ ;; Minimum length at column 0: 2 (;;) + 1 (space) + 1 (space) + 3 (dashes) = 7
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-""" 7)))
+ (should (= 3 (length (split-string result "\n" t))))))
+
+(ert-deftest test-simple-divider-elisp-minimum-length-indented ()
+ "Should work with minimum viable length when indented."
+ ;; At column 4, minimum is 4 + 2 + 1 + 1 + 3 = 11
+ (let ((result (test-simple-divider-at-column 4 ";;" "" "-""" 11)))
+ (should (= 3 (length (split-string result "\n" t))))))
+
+(ert-deftest test-simple-divider-elisp-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-""Header" 200)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Decoration lines should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-simple-divider-elisp-unicode-decoration ()
+ "Should handle unicode decoration character."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "─""Header" 70)))
+ (should (string-match-p "─" result))))
+
+(ert-deftest test-simple-divider-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-""Hello 👋 مرحبا café" 70)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-simple-divider-elisp-very-long-text ()
+ "Should handle very long text (may wrap or truncate)."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-simple-divider-at-column 0 ";;" "" "-"long-text 70)))
+ ;; Should still generate output (behavior may vary)
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-simple-divider-elisp-comment-end-empty ()
+ "Should handle empty comment-end correctly."
+ (let ((result (test-simple-divider-at-column 0 ";;" "" "-""Header" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Lines should not have trailing comment-end
+ (should-not (string-match-p ";;.*;;$" result))))
+
+(ert-deftest test-simple-divider-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-simple-divider-at-column 60 ";;" "" "-""Header" 100)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; All lines should start with 60 spaces
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p (make-string 60 ?\s) line)))))
+
+;;; Error Cases
+
+(ert-deftest test-simple-divider-elisp-length-too-small-column-0 ()
+ "Should error when length is too small at column 0."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" "-" "Header" 5)
+ :type 'error))
+
+(ert-deftest test-simple-divider-elisp-length-too-small-indented ()
+ "Should error when length is too small for indentation level."
+ (should-error
+ (test-simple-divider-at-column 10 ";;" "" "-" "Header" 15)
+ :type 'error))
+
+(ert-deftest test-simple-divider-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" "-" "Header" -10)
+ :type 'error))
+
+(ert-deftest test-simple-divider-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" "-" "Header" 0)
+ :type 'error))
+
+(ert-deftest test-simple-divider-elisp-nil-decoration ()
+ "Should error when decoration-char is nil."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" nil "Header" 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-simple-divider-elisp-nil-text ()
+ "Should error when text is nil."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" "-" nil 70)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-simple-divider-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-simple-divider-at-column 0 ";;" "" "-""Header" "not-a-number")
+ :type 'wrong-type-argument))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-simple-divider-python-basic ()
+ "Should generate simple divider with Python comment syntax."
+ (let ((result (test-simple-divider-at-column 0 "#" "" "-""Section" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^# -" result))
+ (should (string-match-p "# Section" result))))
+
+(ert-deftest test-simple-divider-python-indented ()
+ "Should handle indented Python comments."
+ (let ((result (test-simple-divider-at-column 4 "#" "" "=""Function Section" 70)))
+ (should (string-prefix-p " #" result))
+ (should (string-match-p "Function Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-simple-divider-c-block-comments ()
+ "Should generate simple divider with C block comment syntax."
+ (let ((result (test-simple-divider-at-column 0 "/*" "*/" "-""Section" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* -" result))
+ (should (string-match-p "/\\* Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(ert-deftest test-simple-divider-c-line-comments ()
+ "Should generate simple divider with C line comment syntax."
+ (let ((result (test-simple-divider-at-column 0 "//" "" "=""Header" 70)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^// =" result))
+ (should (string-match-p "// Header" result))))
+
+(provide 'test-custom-comments-comment-simple-divider)
+;;; test-custom-comments-comment-simple-divider.el ends here
diff --git a/tests/test-custom-comments-comment-unicode-box.el b/tests/test-custom-comments-comment-unicode-box.el
new file mode 100644
index 00000000..f34329c8
--- /dev/null
+++ b/tests/test-custom-comments-comment-unicode-box.el
@@ -0,0 +1,264 @@
+;;; test-custom-comments-comment-unicode-box.el --- Tests for cj/comment-unicode-box -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/comment-unicode-box function from custom-comments.el
+;;
+;; This function generates a 3-line unicode box comment:
+;; - Top line: comment-start + top-left corner + horizontal lines + top-right corner
+;; - Text line: comment-start + vertical bar + text + vertical bar
+;; - Bottom line: comment-start + bottom-left corner + horizontal lines + bottom-right corner
+;;
+;; Supports both 'single and 'double box styles with different unicode characters.
+;;
+;; We test the NON-INTERACTIVE implementation (cj/--comment-unicode-box)
+;; to avoid mocking user prompts. This follows our testing best practice
+;; of separating business logic from UI interaction.
+;;
+;; Cross-Language Testing Strategy:
+;; - Comprehensive testing in Emacs Lisp (our primary language)
+;; - Representative testing in Python and C (hash-based and C-style comments)
+;; - Function handles comment syntax generically, so testing 3 syntaxes
+;; proves cross-language compatibility
+;; - See test-custom-comments-delete-buffer-comments.el for detailed rationale
+
+;;; 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-comments)
+
+;;; Test Helpers
+
+(defun test-unicode-box-at-column (column-pos comment-start comment-end text length box-style)
+ "Test cj/--comment-unicode-box at COLUMN-POS indentation.
+Insert spaces to reach COLUMN-POS, then call cj/--comment-unicode-box with
+COMMENT-START, COMMENT-END, TEXT, LENGTH, and BOX-STYLE.
+Returns the buffer string for assertions."
+ (with-temp-buffer
+ (when (> column-pos 0)
+ (insert (make-string column-pos ?\s)))
+ (cj/--comment-unicode-box comment-start comment-end text length box-style)
+ (buffer-string)))
+
+;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
+
+;;; Normal Cases - Single Box Style
+
+(ert-deftest test-unicode-box-elisp-single-basic ()
+ "Should generate 3-line single-line unicode box in emacs-lisp style."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Section Header" 70 'single)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should have single-line box characters
+ (should (string-match-p "┌" result))
+ (should (string-match-p "┐" result))
+ (should (string-match-p "└" result))
+ (should (string-match-p "┘" result))
+ (should (string-match-p "─" result))
+ (should (string-match-p "│" result))
+ ;; Should contain text
+ (should (string-match-p "Section Header" result))))
+
+(ert-deftest test-unicode-box-elisp-double-basic ()
+ "Should generate 3-line double-line unicode box in emacs-lisp style."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Section Header" 70 'double)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should have double-line box characters
+ (should (string-match-p "╔" result))
+ (should (string-match-p "╗" result))
+ (should (string-match-p "╚" result))
+ (should (string-match-p "╝" result))
+ (should (string-match-p "═" result))
+ (should (string-match-p "║" result))
+ ;; Should contain text
+ (should (string-match-p "Section Header" result))))
+
+(ert-deftest test-unicode-box-elisp-single-vs-double ()
+ "Should use different characters for single vs double."
+ (let ((single-result (test-unicode-box-at-column 0 ";;" "" "Header" 70 'single))
+ (double-result (test-unicode-box-at-column 0 ";;" "" "Header" 70 'double)))
+ ;; Single should have single-line chars but not double
+ (should (string-match-p "─" single-result))
+ (should-not (string-match-p "═" single-result))
+ ;; Double should have double-line chars but not single
+ (should (string-match-p "═" double-result))
+ (should-not (string-match-p "─" double-result))))
+
+(ert-deftest test-unicode-box-elisp-custom-text ()
+ "Should include custom text in box."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Custom Text Here" 70 'single)))
+ (should (string-match-p "Custom Text Here" result))))
+
+(ert-deftest test-unicode-box-elisp-empty-text ()
+ "Should handle empty text string."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "" 70 'single)))
+ ;; Should still generate 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Should have box characters
+ (should (string-match-p "┌" result))))
+
+(ert-deftest test-unicode-box-elisp-at-column-0 ()
+ "Should work at column 0."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Header" 70 'single)))
+ ;; First character should be semicolon
+ (should (string-prefix-p ";;" result))))
+
+(ert-deftest test-unicode-box-elisp-indented ()
+ "Should work when indented."
+ (let ((result (test-unicode-box-at-column 4 ";;" "" "Header" 70 'single)))
+ ;; Result should start with spaces
+ (should (string-prefix-p " ;;" result))
+ ;; All lines should be indented
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p " ;;" line)))))
+
+(ert-deftest test-unicode-box-elisp-short-text ()
+ "Should handle short text properly."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "X" 70 'single)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-unicode-box-elisp-long-text ()
+ "Should handle longer text."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "This is a longer header text" 70 'single)))
+ ;; Should have 3 lines
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Text should be present
+ (should (string-match-p "This is a longer header text" result))))
+
+;;; Boundary Cases
+
+(ert-deftest test-unicode-box-elisp-minimum-length ()
+ "Should work with minimum viable length."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "X" 15 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "X" result))))
+
+(ert-deftest test-unicode-box-elisp-very-long-length ()
+ "Should handle very long length."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Header" 200 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Border lines should be very long
+ (let ((first-line (car (split-string result "\n" t))))
+ (should (> (length first-line) 100)))))
+
+(ert-deftest test-unicode-box-elisp-unicode-text ()
+ "Should handle unicode in text."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Hello 👋 مرحبا café" 70 'single)))
+ (should (string-match-p "👋" result))
+ (should (string-match-p "مرحبا" result))
+ (should (string-match-p "café" result))))
+
+(ert-deftest test-unicode-box-elisp-very-long-text ()
+ "Should handle very long text."
+ (let* ((long-text (make-string 100 ?x))
+ (result (test-unicode-box-at-column 0 ";;" "" long-text 70 'single)))
+ ;; Should still generate output
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Middle line should contain some of the text
+ (should (string-match-p "xxx" result))))
+
+(ert-deftest test-unicode-box-elisp-comment-end-empty ()
+ "Should handle empty comment-end correctly."
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Header" 70 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; Lines should not have trailing comment-end
+ (should-not (string-match-p ";;.*;;$" result))))
+
+(ert-deftest test-unicode-box-elisp-max-indentation ()
+ "Should handle maximum practical indentation."
+ (let ((result (test-unicode-box-at-column 60 ";;" "" "Header" 100 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ ;; All lines should start with 60 spaces
+ (dolist (line (split-string result "\n" t))
+ (should (string-prefix-p (make-string 60 ?\s) line)))))
+
+;;; Error Cases
+
+(ert-deftest test-unicode-box-elisp-length-too-small ()
+ "Should error when length is too small."
+ (should-error
+ (test-unicode-box-at-column 0 ";;" "" "Header" 5 'single)
+ :type 'error))
+
+(ert-deftest test-unicode-box-elisp-negative-length ()
+ "Should error with negative length."
+ (should-error
+ (test-unicode-box-at-column 0 ";;" "" "Header" -10 'single)
+ :type 'error))
+
+(ert-deftest test-unicode-box-elisp-zero-length ()
+ "Should error with zero length."
+ (should-error
+ (test-unicode-box-at-column 0 ";;" "" "Header" 0 'single)
+ :type 'error))
+
+(ert-deftest test-unicode-box-elisp-nil-text ()
+ "Should error when text is nil."
+ (should-error
+ (test-unicode-box-at-column 0 ";;" "" nil 70 'single)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-unicode-box-elisp-non-integer-length ()
+ "Should error when length is not an integer."
+ (should-error
+ (test-unicode-box-at-column 0 ";;" "" "Header" "not-a-number" 'single)
+ :type 'wrong-type-argument))
+
+(ert-deftest test-unicode-box-elisp-invalid-box-style ()
+ "Should handle invalid box-style gracefully."
+ ;; Function may use a default or error - either is acceptable
+ (let ((result (test-unicode-box-at-column 0 ";;" "" "Header" 70 'invalid)))
+ ;; Should still generate some output
+ (should (stringp result))))
+
+;;; Python Tests (Hash-based comments)
+
+(ert-deftest test-unicode-box-python-single ()
+ "Should generate unicode box with Python comment syntax."
+ (let ((result (test-unicode-box-at-column 0 "#" "" "Section" 70 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^# ┌" result))
+ (should (string-match-p "Section" result))))
+
+(ert-deftest test-unicode-box-python-double ()
+ "Should generate double-line unicode box with Python comment syntax."
+ (let ((result (test-unicode-box-at-column 0 "#" "" "Section" 70 'double)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^# ╔" result))
+ (should (string-match-p "Section" result))))
+
+;;; C Tests (C-style comments)
+
+(ert-deftest test-unicode-box-c-block-comments-single ()
+ "Should generate unicode box with C block comment syntax."
+ (let ((result (test-unicode-box-at-column 0 "/*" "*/" "Section" 70 'single)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* ┌" result))
+ (should (string-match-p "Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(ert-deftest test-unicode-box-c-block-comments-double ()
+ "Should generate double-line unicode box with C block comment syntax."
+ (let ((result (test-unicode-box-at-column 0 "/*" "*/" "Section" 70 'double)))
+ (should (= 3 (length (split-string result "\n" t))))
+ (should (string-match-p "^/\\* ╔" result))
+ (should (string-match-p "Section" result))
+ ;; Should include comment-end
+ (should (string-match-p "\\*/" result))))
+
+(provide 'test-custom-comments-comment-unicode-box)
+;;; test-custom-comments-comment-unicode-box.el ends here