diff options
| -rw-r--r-- | tests/test-custom-line-paragraph-duplicate-line-or-region.el | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/tests/test-custom-line-paragraph-duplicate-line-or-region.el b/tests/test-custom-line-paragraph-duplicate-line-or-region.el new file mode 100644 index 00000000..22f19c16 --- /dev/null +++ b/tests/test-custom-line-paragraph-duplicate-line-or-region.el @@ -0,0 +1,463 @@ +;;; test-custom-line-paragraph-duplicate-line-or-region.el --- Tests for cj/duplicate-line-or-region -*- lexical-binding: t; -*- + +;;; Commentary: +;; Tests for the cj/duplicate-line-or-region function from custom-line-paragraph.el +;; +;; This function duplicates the current line or active region below the original. +;; When called with a prefix argument, the duplicated text is commented out. +;; +;; IMPORTANT NOTE ON REGION ACTIVATION IN BATCH MODE: +;; When testing functions that use (region-active-p) in batch mode, you must +;; explicitly activate the region. Unlike interactive Emacs, batch mode does +;; not automatically activate regions when you set mark and point. +;; +;; To properly test region-based behavior in batch mode: +;; 1. Enable transient-mark-mode: (transient-mark-mode 1) +;; 2. Set mark and point as needed +;; 3. Explicitly activate the mark: (activate-mark) + +;;; 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.") + +;; Stub expand-region package +(provide 'expand-region) + +;; Now load the actual production module +(require 'custom-line-paragraph) + +;;; Setup and Teardown + +(defun test-duplicate-line-or-region-setup () + "Setup for duplicate-line-or-region tests." + (cj/create-test-base-dir)) + +(defun test-duplicate-line-or-region-teardown () + "Teardown for duplicate-line-or-region tests." + (cj/delete-test-base-dir)) + +;;; Normal Cases + +(ert-deftest test-duplicate-line-or-region-single-line-without-comment () + "Should duplicate single line below original without commenting." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "line one\nline one" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-single-line-with-comment () + "Should duplicate single line and comment the duplicate." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (emacs-lisp-mode) ; Enable comment syntax + (insert "line one") + (goto-char (point-min)) + (cj/duplicate-line-or-region t) ; Pass comment argument + (should (string-match-p "line one\n;; line one" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-multi-line-region-without-comment () + "Should duplicate entire region below without commenting." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one\nline two\nline three") + (goto-char (point-min)) + (set-mark (point)) + (goto-char (point-max)) + (transient-mark-mode 1) + (activate-mark) + (cj/duplicate-line-or-region) + (should (string-match-p "line one\nline two\nline three\nline one\nline two\nline three" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-multi-line-region-with-comment () + "Should duplicate region and comment all duplicated lines." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (emacs-lisp-mode) + (insert "line one\nline two\nline three") + (goto-char (point-min)) + (set-mark (point)) + (goto-char (point-max)) + (transient-mark-mode 1) + (activate-mark) + (cj/duplicate-line-or-region t) + ;; All duplicated lines should be commented + (should (string-match-p ";; line one\n;; line two\n;; line three" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-cursor-position-unchanged () + "Should keep cursor at original position (save-excursion)." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one\nline two") + (goto-char (point-min)) + (forward-char 5) ; Position in middle of first line + (let ((original-pos (point))) + (cj/duplicate-line-or-region) + (should (= (point) original-pos)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-original-content-preserved () + "Should preserve original text unchanged." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "original line") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + ;; Original should still be there + (goto-char (point-min)) + (should (looking-at "original line"))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-preserves-text-content () + "Should exactly duplicate text content." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "exact text") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + ;; Count occurrences of "exact text" + (should (= 2 (how-many "exact text" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +;;; Boundary Cases + +(ert-deftest test-duplicate-line-or-region-at-buffer-start () + "Should handle duplication from beginning of buffer." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "first line\nsecond line") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "first line\nfirst line\nsecond line" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-at-buffer-end () + "Should handle duplication at end of buffer." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "first line\nlast line") + (goto-char (point-max)) + (cj/duplicate-line-or-region) + (should (string-match-p "last line\nlast line$" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-empty-line () + "Should duplicate empty line." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "") + (cj/duplicate-line-or-region) + ;; Should have duplicated the empty content + (should (string= "\n" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-only-whitespace () + "Should preserve whitespace in duplicate." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert " ") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string= " \n " (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-very-long-line () + "Should handle very long lines (5000+ chars)." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (let ((long-line (make-string 5000 ?x))) + (insert long-line) + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (= 2 (how-many long-line (point-min) (point-max)))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-region-with-empty-lines () + "Should duplicate empty lines within region." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one\n\nline three") + (goto-char (point-min)) + (set-mark (point)) + (goto-char (point-max)) + (transient-mark-mode 1) + (activate-mark) + (cj/duplicate-line-or-region) + ;; Should have empty line duplicated + (should (string-match-p "line one\n\nline three\nline one\n\nline three" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-single-character () + "Should handle minimal single character content." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "x") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string= "x\nx" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-unicode-emoji () + "Should handle Unicode and emoji characters." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "hello 👋 café") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "hello 👋 café\nhello 👋 café" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-with-tabs () + "Should preserve tab characters." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line\twith\ttabs") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "line\twith\ttabs\nline\twith\ttabs" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-mixed-whitespace () + "Should preserve exact whitespace." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert " line \t text ") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string= " line \t text \n line \t text " (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-narrowed-buffer () + "Should respect buffer narrowing." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "before\ntarget\nafter") + (goto-char (point-min)) + (forward-line 1) + (let ((beg (point))) + (forward-line 1) + (narrow-to-region beg (point)) + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (widen) + ;; Should still have before and after + (should (string-match-p "before" (buffer-string))) + (should (string-match-p "after" (buffer-string))) + ;; Target should be duplicated + (should (= 2 (how-many "target" (point-min) (point-max)))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-backwards-region () + "Should handle backwards region (mark after point)." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one\nline two") + (goto-char (point-max)) + (set-mark (point)) + (goto-char (point-min)) + (transient-mark-mode 1) + (activate-mark) + (cj/duplicate-line-or-region) + (should (= 2 (how-many "line one" (point-min) (point-max)))) + (should (= 2 (how-many "line two" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-entire-buffer () + "Should handle entire buffer selected as region." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "one\ntwo\nthree") + (transient-mark-mode 1) + (mark-whole-buffer) + (activate-mark) + (cj/duplicate-line-or-region) + (should (= 2 (how-many "one" (point-min) (point-max)))) + (should (= 2 (how-many "two" (point-min) (point-max)))) + (should (= 2 (how-many "three" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-ending-mid-line () + "Should handle region ending mid-line." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line one\nline two\nline three") + (goto-char (point-min)) + (forward-char 5) ; Middle of first line + (set-mark (point)) + (forward-line 2) + (forward-char 5) ; Middle of third line + (transient-mark-mode 1) + (activate-mark) + (cj/duplicate-line-or-region) + ;; Should duplicate the selected portion + (should (> (length (buffer-string)) (length "line one\nline two\nline three")))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-trailing-whitespace () + "Should preserve trailing whitespace." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line with trailing ") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string= "line with trailing \nline with trailing " (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-rtl-text () + "Should handle RTL text." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "Ù…Ø±ØØ¨Ø§") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (= 2 (how-many "Ù…Ø±ØØ¨Ø§" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-combining-characters () + "Should handle Unicode combining characters." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "cafe\u0301") ; e with combining acute + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "cafe\u0301\ncafe\u0301" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-at-point-min () + "Should handle duplication at point-min edge case." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "first") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (= 2 (how-many "first" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-at-point-max () + "Should handle duplication at point-max edge case." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "last") + (goto-char (point-max)) + (cj/duplicate-line-or-region) + (should (= 2 (how-many "last" (point-min) (point-max))))) + (test-duplicate-line-or-region-teardown))) + +;;; Error Cases + +(ert-deftest test-duplicate-line-or-region-read-only-buffer () + "Should error when attempting to modify read-only buffer." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "read only line") + (goto-char (point-min)) + (read-only-mode 1) + (should-error (cj/duplicate-line-or-region))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-buffer-modified-flag () + "Should set buffer modified flag." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line") + (set-buffer-modified-p nil) + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (buffer-modified-p))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-undo-behavior () + "Should support undo after duplication." + (test-duplicate-line-or-region-setup) + (unwind-protect + (let* ((temp-file (expand-file-name "test-undo-dup.txt" cj/test-base-dir)) + (original-content "line one")) + ;; Create file with initial content + (with-temp-file temp-file + (insert original-content)) + ;; Open file and test undo + (find-file temp-file) + (buffer-enable-undo) + ;; Establish undo history + (goto-char (point-min)) + (insert " ") + (delete-char -1) + (undo-boundary) + (goto-char (point-min)) + (let ((before-dup (buffer-string))) + (cj/duplicate-line-or-region) + (undo-boundary) + (let ((after-dup (buffer-string))) + (should-not (string= before-dup after-dup)) + (undo) + (should (string= before-dup (buffer-string))))) + (kill-buffer (current-buffer))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-comment-without-syntax () + "Should error when comment requested but no comment syntax defined." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + ;; Fundamental mode has no comment syntax + (fundamental-mode) + (insert "line") + (goto-char (point-min)) + ;; Should error when trying to comment without syntax + (should-error (cj/duplicate-line-or-region t))) + (test-duplicate-line-or-region-teardown))) + +(ert-deftest test-duplicate-line-or-region-special-characters () + "Should handle control characters." + (test-duplicate-line-or-region-setup) + (unwind-protect + (with-temp-buffer + (insert "line\u000Cwith\u000Dcontrol") + (goto-char (point-min)) + (cj/duplicate-line-or-region) + (should (string-match-p "line\u000Cwith\u000Dcontrol\nline\u000Cwith\u000Dcontrol" (buffer-string)))) + (test-duplicate-line-or-region-teardown))) + +(provide 'test-custom-line-paragraph-duplicate-line-or-region) +;;; test-custom-line-paragraph-duplicate-line-or-region.el ends here |
