summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/fixtures/grammar-correct.txt5
-rw-r--r--tests/fixtures/grammar-errors-basic.txt7
-rw-r--r--tests/fixtures/grammar-errors-punctuation.txt5
-rw-r--r--tests/fixtures/pactl-output-empty.txt0
-rw-r--r--tests/fixtures/pactl-output-inputs-only.txt3
-rw-r--r--tests/fixtures/pactl-output-malformed.txt4
-rw-r--r--tests/fixtures/pactl-output-monitors-only.txt3
-rw-r--r--tests/fixtures/pactl-output-normal.txt6
-rw-r--r--tests/fixtures/pactl-output-single.txt1
-rw-r--r--tests/test-all-comp-errors.el254
-rw-r--r--tests/test-browser-config.el277
-rw-r--r--tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el163
-rw-r--r--tests/test-custom-buffer-file-clear-to-top-of-buffer.el162
-rw-r--r--tests/test-custom-buffer-file-copy-link-to-buffer-file.el209
-rw-r--r--tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el205
-rw-r--r--tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el187
-rw-r--r--tests/test-custom-buffer-file-copy-to-top-of-buffer.el186
-rw-r--r--tests/test-custom-buffer-file-copy-whole-buffer.el194
-rw-r--r--tests/test-custom-buffer-file-delete-buffer-and-file.el671
-rw-r--r--tests/test-custom-buffer-file-move-buffer-and-file.el936
-rw-r--r--tests/test-custom-buffer-file-rename-buffer-and-file.el939
-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-reformat.el191
-rw-r--r--tests/test-custom-comments-comment-simple-divider.el246
-rw-r--r--tests/test-custom-comments-comment-unicode-box.el264
-rw-r--r--tests/test-custom-comments-delete-buffer-comments.el224
-rw-r--r--tests/test-custom-functions-join-line-or-region.el.disabled84
-rw-r--r--tests/test-custom-line-paragraph-duplicate-line-or-region.el451
-rw-r--r--tests/test-custom-line-paragraph-join-line-or-region.el618
-rw-r--r--tests/test-custom-line-paragraph-join-paragraph.el360
-rw-r--r--tests/test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el471
-rw-r--r--tests/test-custom-line-paragraph-remove-lines-containing.el456
-rw-r--r--tests/test-custom-line-paragraph-underscore-line.el397
-rw-r--r--tests/test-custom-misc-cj--count-characters.el171
-rw-r--r--tests/test-custom-misc-cj-count-characters-buffer-or-region.el231
-rw-r--r--tests/test-custom-misc-count-words.el148
-rw-r--r--tests/test-custom-misc-format-region.el161
-rw-r--r--tests/test-custom-misc-jump-to-matching-paren.el197
-rw-r--r--tests/test-custom-misc-replace-fraction-glyphs.el185
-rw-r--r--tests/test-custom-ordering-alphabetize.el176
-rw-r--r--tests/test-custom-ordering-arrayify.el215
-rw-r--r--tests/test-custom-ordering-comma-to-lines.el159
-rw-r--r--tests/test-custom-ordering-number-lines.el181
-rw-r--r--tests/test-custom-ordering-reverse-lines.el131
-rw-r--r--tests/test-custom-ordering-toggle-quotes.el155
-rw-r--r--tests/test-custom-ordering-unarrayify.el159
-rw-r--r--tests/test-custom-text-enclose-append.el190
-rw-r--r--tests/test-custom-text-enclose-indent.el241
-rw-r--r--tests/test-custom-text-enclose-prepend.el207
-rw-r--r--tests/test-custom-text-enclose-surround.el200
-rw-r--r--tests/test-custom-text-enclose-unwrap.el266
-rw-r--r--tests/test-custom-text-enclose-wrap.el240
-rw-r--r--tests/test-custom-whitespace-collapse.el150
-rw-r--r--tests/test-custom-whitespace-delete-all.el150
-rw-r--r--tests/test-custom-whitespace-delete-blank-lines.el146
-rw-r--r--tests/test-custom-whitespace-ensure-single-blank.el146
-rw-r--r--tests/test-custom-whitespace-hyphenate.el140
-rw-r--r--tests/test-custom-whitespace-remove-leading-trailing.el157
-rw-r--r--tests/test-flycheck-languagetool-setup.el71
-rw-r--r--tests/test-integration-buffer-diff.el300
-rw-r--r--tests/test-integration-grammar-checking.el190
-rw-r--r--tests/test-integration-recording-device-workflow.el232
-rw-r--r--tests/test-integration-recording-modeline-sync.el384
-rw-r--r--tests/test-integration-recording-toggle-workflow.el347
-rw-r--r--tests/test-integration-transcription.el150
-rw-r--r--tests/test-jumper.el352
-rw-r--r--tests/test-keyboard-macros.el356
-rw-r--r--tests/test-lorem-optimum-benchmark.el223
-rw-r--r--tests/test-lorem-optimum.el242
-rw-r--r--tests/test-music-config--append-track-to-m3u-file.el187
-rw-r--r--tests/test-music-config--collect-entries-recursive.el245
-rw-r--r--tests/test-music-config--completion-table.el134
-rw-r--r--tests/test-music-config--get-m3u-basenames.el121
-rw-r--r--tests/test-music-config--get-m3u-files.el150
-rw-r--r--tests/test-music-config--m3u-file-tracks.el193
-rw-r--r--tests/test-music-config--safe-filename.el97
-rw-r--r--tests/test-music-config--valid-directory-p.el139
-rw-r--r--tests/test-music-config--valid-file-p.el99
-rw-r--r--tests/test-org-agenda-build-list.el294
-rw-r--r--tests/test-org-contacts-capture-finalize.el178
-rw-r--r--tests/test-org-contacts-parse-email.el219
-rw-r--r--tests/test-org-drill-first-function.el135
-rw-r--r--tests/test-org-drill-font-switching.el175
-rw-r--r--tests/test-org-refile-build-targets.el305
-rw-r--r--tests/test-org-roam-config-copy-todo-to-today.el182
-rw-r--r--tests/test-org-roam-config-demote.el183
-rw-r--r--tests/test-org-roam-config-format.el151
-rw-r--r--tests/test-org-roam-config-link-description.el188
-rw-r--r--tests/test-org-roam-config-slug.el223
-rw-r--r--tests/test-org-sort-by-todo-and-priority.el283
-rw-r--r--tests/test-org-webclipper-process.el210
-rw-r--r--tests/test-system-lib-executable-exists-p.el73
-rw-r--r--tests/test-test-runner.el359
-rw-r--r--tests/test-transcription-audio-file.el88
-rw-r--r--tests/test-transcription-config--transcription-script-path.el106
-rw-r--r--tests/test-transcription-counter.el103
-rw-r--r--tests/test-transcription-duration.el63
-rw-r--r--tests/test-transcription-log-cleanup.el49
-rw-r--r--tests/test-transcription-paths.el85
-rw-r--r--tests/test-undead-buffers-kill-all-other-buffers-and-windows.el159
-rw-r--r--tests/test-undead-buffers-kill-buffer-and-window.el112
-rw-r--r--tests/test-undead-buffers-kill-buffer-or-bury-alive.el138
-rw-r--r--tests/test-undead-buffers-kill-other-window.el123
-rw-r--r--tests/test-undead-buffers-make-buffer-undead.el134
-rw-r--r--tests/test-undead-buffers-undead-buffer-p.el106
-rw-r--r--tests/test-undead-buffers.el117
-rw-r--r--tests/test-video-audio-recording-check-ffmpeg.el46
-rw-r--r--tests/test-video-audio-recording-ffmpeg-functions.el361
-rw-r--r--tests/test-video-audio-recording-friendly-state.el65
-rw-r--r--tests/test-video-audio-recording-get-devices.el190
-rw-r--r--tests/test-video-audio-recording-group-devices-by-hardware.el194
-rw-r--r--tests/test-video-audio-recording-modeline-indicator.el134
-rw-r--r--tests/test-video-audio-recording-parse-pactl-output.el157
-rw-r--r--tests/test-video-audio-recording-parse-sources.el98
-rw-r--r--tests/test-video-audio-recording-process-sentinel.el190
-rw-r--r--tests/test-video-audio-recording-quick-setup-for-calls.el144
-rw-r--r--tests/test-video-audio-recording-select-device.el165
-rw-r--r--tests/test-video-audio-recording-test-mic.el147
-rw-r--r--tests/test-video-audio-recording-test-monitor.el148
-rw-r--r--tests/test-video-audio-recording-toggle-functions.el185
124 files changed, 0 insertions, 24863 deletions
diff --git a/tests/fixtures/grammar-correct.txt b/tests/fixtures/grammar-correct.txt
deleted file mode 100644
index bea335e8..00000000
--- a/tests/fixtures/grammar-correct.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a well-written sentence with no grammar errors.
-
-The quick brown fox jumps over the lazy dog.
-
-Everything here follows standard English grammar rules.
diff --git a/tests/fixtures/grammar-errors-basic.txt b/tests/fixtures/grammar-errors-basic.txt
deleted file mode 100644
index c2f72c12..00000000
--- a/tests/fixtures/grammar-errors-basic.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This are a test of basic grammar errors.
-
-I could of done better with this sentence.
-
-Their going to the store to buy there groceries.
-
-The dog wagged it's tail happily.
diff --git a/tests/fixtures/grammar-errors-punctuation.txt b/tests/fixtures/grammar-errors-punctuation.txt
deleted file mode 100644
index 37de646a..00000000
--- a/tests/fixtures/grammar-errors-punctuation.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This sentence is missing punctuation at the end
-
-Multiple spaces between words should be detected.
-
-A sentence with,incorrect comma,placement and usage.
diff --git a/tests/fixtures/pactl-output-empty.txt b/tests/fixtures/pactl-output-empty.txt
deleted file mode 100644
index e69de29b..00000000
--- a/tests/fixtures/pactl-output-empty.txt
+++ /dev/null
diff --git a/tests/fixtures/pactl-output-inputs-only.txt b/tests/fixtures/pactl-output-inputs-only.txt
deleted file mode 100644
index 1840b37c..00000000
--- a/tests/fixtures/pactl-output-inputs-only.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-50 alsa_input.pci-0000_00_1f.3.analog-stereo PipeWire s32le 2ch 48000Hz SUSPENDED
-79 bluez_input.00:1B:66:C0:91:6D PipeWire float32le 1ch 48000Hz SUSPENDED
-100 alsa_input.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.mono-fallback PipeWire s16le 1ch 16000Hz SUSPENDED
diff --git a/tests/fixtures/pactl-output-malformed.txt b/tests/fixtures/pactl-output-malformed.txt
deleted file mode 100644
index a37b8dd6..00000000
--- a/tests/fixtures/pactl-output-malformed.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is not valid pactl output
-Some random text
-50 incomplete-line-missing-fields
-Another bad line with only two tabs
diff --git a/tests/fixtures/pactl-output-monitors-only.txt b/tests/fixtures/pactl-output-monitors-only.txt
deleted file mode 100644
index be29ebe8..00000000
--- a/tests/fixtures/pactl-output-monitors-only.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-49 alsa_output.pci-0000_00_1f.3.analog-stereo.monitor PipeWire s32le 2ch 48000Hz SUSPENDED
-81 bluez_output.00_1B_66_C0_91_6D.1.monitor PipeWire s24le 2ch 48000Hz RUNNING
-99 alsa_output.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.analog-stereo.monitor PipeWire s16le 2ch 48000Hz SUSPENDED
diff --git a/tests/fixtures/pactl-output-normal.txt b/tests/fixtures/pactl-output-normal.txt
deleted file mode 100644
index 6d8d955b..00000000
--- a/tests/fixtures/pactl-output-normal.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-49 alsa_output.pci-0000_00_1f.3.analog-stereo.monitor PipeWire s32le 2ch 48000Hz SUSPENDED
-50 alsa_input.pci-0000_00_1f.3.analog-stereo PipeWire s32le 2ch 48000Hz SUSPENDED
-79 bluez_input.00:1B:66:C0:91:6D PipeWire float32le 1ch 48000Hz SUSPENDED
-81 bluez_output.00_1B_66_C0_91_6D.1.monitor PipeWire s24le 2ch 48000Hz SUSPENDED
-99 alsa_output.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.analog-stereo.monitor PipeWire s16le 2ch 48000Hz SUSPENDED
-100 alsa_input.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.mono-fallback PipeWire s16le 1ch 16000Hz SUSPENDED
diff --git a/tests/fixtures/pactl-output-single.txt b/tests/fixtures/pactl-output-single.txt
deleted file mode 100644
index d1d1c254..00000000
--- a/tests/fixtures/pactl-output-single.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 alsa_input.pci-0000_00_1f.3.analog-stereo PipeWire s32le 2ch 48000Hz SUSPENDED
diff --git a/tests/test-all-comp-errors.el b/tests/test-all-comp-errors.el
deleted file mode 100644
index 81614858..00000000
--- a/tests/test-all-comp-errors.el
+++ /dev/null
@@ -1,254 +0,0 @@
-;;; test-all-comp-errors.el --- ERT tests for compilation errors -*- lexical-binding: t; -*-
-
-;; Author: Claude Code and cjennings
-;; Keywords: tests, compilation
-
-;;; Commentary:
-;; ERT tests to check all .el files in modules/ and custom/ directories
-;; for byte-compilation and native-compilation errors.
-;;
-;; These tests help ensure code quality by catching compilation warnings
-;; and errors across the entire configuration.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-(require 'bytecomp)
-
-;;; Configuration
-
-(defvar test-comp-errors-directories '("modules" "custom")
- "List of directories to check for compilation errors.
-Each directory path should be relative to the Emacs configuration root.
-Example: '(\"modules\" \"custom\" \"libs\")")
-
-(defvar test-comp-errors-single-file nil
- "If non-nil, test only this single file instead of all files.
-Should be a relative path to a .el file (e.g., \"modules/ui-config.el\").
-Useful for debugging specific file compilation issues.")
-
-(defvar test-comp-errors-core-dependencies
- '("modules/user-constants.el"
- "modules/host-environment.el"
- "modules/system-defaults.el"
- "modules/keybindings.el")
- "List of core dependency files to pre-load before compilation.
-These files are loaded before compilation starts to reduce recursion depth.
-Should be files that many other files depend on.")
-
-(defvar test-comp-errors-byte-compile-report-file
- (expand-file-name "~/.emacs-tests-byte-compile-errors.txt")
- "File path where byte-compilation error reports are written.
-Only created when byte-compilation errors are detected.")
-
-(defvar test-comp-errors-native-compile-report-file
- (expand-file-name "~/.emacs-tests-native-compile-errors.txt")
- "File path where native-compilation error reports are written.
-Only created when native-compilation errors are detected.")
-
-;;; Setup and Teardown
-
-(defun test-comp-errors--preload-core-dependencies ()
- "Pre-load core dependency files to reduce recursion during compilation.
-Loads files specified in 'test-comp-errors-core-dependencies'."
- ;; Ensure load-path includes modules, custom, and assets directories
- (let ((user-emacs-directory (expand-file-name default-directory)))
- (add-to-list 'load-path (concat user-emacs-directory "assets/"))
- (add-to-list 'load-path (concat user-emacs-directory "custom/"))
- (add-to-list 'load-path (concat user-emacs-directory "modules/")))
-
- (let ((max-lisp-eval-depth 3000)) ; Allow depth for loading core files
- (dolist (file test-comp-errors-core-dependencies)
- (let ((full-path (expand-file-name file)))
- (if (file-exists-p full-path)
- (condition-case err
- (progn
- (message "Pre-loading core dependency: %s" full-path)
- (load full-path nil t))
- (error
- (message "Warning: Could not pre-load core dependency %s: %s"
- full-path (error-message-string err))))
- (message "Warning: Core dependency file not found: %s" full-path))))))
-
-(defun test-comp-errors-setup (compile-type)
- "Set up test environment for compilation tests.
-COMPILE-TYPE should be either 'byte or 'native."
- (cj/create-test-base-dir)
- (let ((subdir (format "compile-tests/%s/" (symbol-name compile-type))))
- (cj/create-test-subdirectory subdir))
- ;; Pre-load core dependencies to reduce recursion depth during compilation
- (test-comp-errors--preload-core-dependencies))
-
-(defun test-comp-errors-teardown ()
- "Clean up test environment after compilation tests."
- (cj/delete-test-base-dir))
-
-;;; Helper Functions
-
-(defun test-comp-errors--get-compile-dir (compile-type)
- "Get the compilation output directory for COMPILE-TYPE ('byte or 'native)."
- (expand-file-name
- (format "compile-tests/%s/" (symbol-name compile-type))
- cj/test-base-dir))
-
-(defun test-comp-errors--get-source-files ()
- "Return list of all .el files to test.
-If 'test-comp-errors-single-file' is set, return only that file.
-Otherwise, return all files in directories specified by 'test-comp-errors-directories'."
- (if test-comp-errors-single-file
- (if (file-exists-p test-comp-errors-single-file)
- (list test-comp-errors-single-file)
- (error "Single file does not exist: %s" test-comp-errors-single-file))
- (let ((all-files '()))
- (dolist (dir test-comp-errors-directories)
- (when (file-directory-p dir)
- (setq all-files
- (append all-files
- (directory-files-recursively dir "\\.el$")))))
- all-files)))
-
-(defun test-comp-errors--byte-compile-file (source-file output-dir)
- "Byte-compile SOURCE-FILE to OUTPUT-DIR.
-Returns a list of (FILE . ERROR-MESSAGES) if errors occurred, nil otherwise."
- (let* ((max-lisp-eval-depth 3000) ; Increase to handle deep dependency chains
- (byte-compile-dest-file-function
- (lambda (source)
- (expand-file-name
- (file-name-nondirectory (byte-compile-dest-file source))
- output-dir)))
- (byte-compile-log-buffer (get-buffer-create "*Byte-Compile-Test-Log*"))
- (errors nil))
- (with-current-buffer byte-compile-log-buffer
- (erase-buffer))
- ;; Attempt compilation
- (condition-case err
- (progn
- (byte-compile-file source-file)
- ;; Check log for warnings/errors
- (with-current-buffer byte-compile-log-buffer
- (goto-char (point-min))
- (let ((log-content (buffer-substring-no-properties (point-min) (point-max))))
- (when (or (string-match-p "Warning:" log-content)
- (string-match-p "Error:" log-content))
- (setq errors (cons source-file log-content))))))
- (error
- (setq errors (cons source-file (error-message-string err)))))
- errors))
-
-(defun test-comp-errors--native-compile-file (source-file output-dir)
- "Native-compile SOURCE-FILE to OUTPUT-DIR.
-Returns a list of (FILE . ERROR-MESSAGES) if errors occurred, nil otherwise."
- (if (not (and (fboundp 'native-comp-available-p)
- (native-comp-available-p)))
- nil ; Skip if native compilation not available
- (let* ((max-lisp-eval-depth 3000) ; Increase to handle deep dependency chains
- (errors nil))
- ;; Set native-compile-target-directory dynamically
- ;; This variable must be dynamically bound, not lexically
- (setq native-compile-target-directory output-dir)
- (condition-case err
- (progn
- (native-compile source-file)
- ;; Native compile warnings go to *Warnings* buffer
- (when-let ((warnings-buf (get-buffer "*Warnings*")))
- (with-current-buffer warnings-buf
- (let ((log-content (buffer-substring-no-properties (point-min) (point-max))))
- (when (and (> (length log-content) 0)
- (string-match-p (regexp-quote (file-name-nondirectory source-file))
- log-content))
- (setq errors (cons source-file log-content)))))))
- (error
- (setq errors (cons source-file (error-message-string err)))))
- errors)))
-
-(defun test-comp-errors--format-error-report (errors)
- "Format ERRORS list into a readable report string.
-ERRORS is a list of (FILE . ERROR-MESSAGES) cons cells."
- (if (null errors)
- ""
- (let ((report (format "\n\nCompilation errors found in %d file%s:\n\n"
- (length errors)
- (if (= (length errors) 1) "" "s")))
- (files-only ""))
- ;; First, show just the list of all affected files
- (setq files-only (concat "\nAffected files (" (number-to-string (length errors)) " total):\n"))
- (dolist (error-entry errors)
- (setq files-only (concat files-only " - " (car error-entry) "\n")))
- (setq report (concat report files-only "\n"))
-
- ;; Then show detailed error messages for each file
- (setq report (concat report "Detailed error messages:\n\n"))
- (dolist (error-entry errors)
- (let ((file (car error-entry))
- (messages (cdr error-entry)))
- (setq report
- (concat report
- (format "%s:\n" file)
- (if (stringp messages)
- (mapconcat (lambda (line)
- (concat " " line))
- (split-string messages "\n" t)
- "\n")
- messages)
- "\n\n"))))
- report)))
-
-;;; Tests
-
-(ert-deftest test-byte-compile-all-files ()
- "Check all .el files in configured directories for byte-compilation errors.
-Directories are specified by 'test-comp-errors-directories'."
- (test-comp-errors-setup 'byte)
- (unwind-protect
- (let* ((output-dir (test-comp-errors--get-compile-dir 'byte))
- (source-files (test-comp-errors--get-source-files))
- (errors '()))
- ;; Compile each file and collect errors
- (dolist (file source-files)
- (when-let ((error (test-comp-errors--byte-compile-file file output-dir)))
- (push error errors)))
- ;; Kill the compile log buffer
- (when-let ((buf (get-buffer "*Byte-Compile-Test-Log*")))
- (kill-buffer buf))
- ;; Write detailed error report to file for analysis (before teardown)
- (when errors
- (with-temp-file test-comp-errors-byte-compile-report-file
- (insert (test-comp-errors--format-error-report (nreverse errors))))
- (message "Full byte-compile error report written to: %s"
- test-comp-errors-byte-compile-report-file))
- ;; Assert no errors
- (should (null errors)))
- (test-comp-errors-teardown)))
-
-(ert-deftest test-native-compile-all-files ()
- "Check all .el files in configured directories for native-compilation errors.
-Directories are specified by 'test-comp-errors-directories'."
- (unless (and (fboundp 'native-comp-available-p)
- (native-comp-available-p))
- (ert-skip "Native compilation not available"))
- (test-comp-errors-setup 'native)
- (unwind-protect
- (let* ((output-dir (test-comp-errors--get-compile-dir 'native))
- (source-files (test-comp-errors--get-source-files))
- (errors '()))
- ;; Clear warnings buffer
- (when-let ((buf (get-buffer "*Warnings*")))
- (with-current-buffer buf
- (erase-buffer)))
- ;; Compile each file and collect errors
- (dolist (file source-files)
- (when-let ((error (test-comp-errors--native-compile-file file output-dir)))
- (push error errors)))
- ;; Write detailed error report to file for analysis (before teardown)
- (when errors
- (with-temp-file test-comp-errors-native-compile-report-file
- (insert (test-comp-errors--format-error-report (nreverse errors))))
- (message "Full native-compile error report written to: %s"
- test-comp-errors-native-compile-report-file))
- ;; Assert no errors
- (should (null errors)))
- (test-comp-errors-teardown)))
-
-(provide 'test-all-comp-errors)
-;;; test-all-comp-errors.el ends here
diff --git a/tests/test-browser-config.el b/tests/test-browser-config.el
deleted file mode 100644
index 6ab756dd..00000000
--- a/tests/test-browser-config.el
+++ /dev/null
@@ -1,277 +0,0 @@
-;;; test-browser-config.el --- Tests for browser-config.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for browser-config.el - browser selection and configuration.
-;;
-;; Testing approach:
-;; - Tests focus on internal `cj/--do-*` functions (pure business logic)
-;; - File I/O tests use temp files
-;; - executable-find is stubbed to control available browsers
-;; - Each test is isolated with setup/teardown
-;; - Tests verify return values, not user messages
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load the module with temp file to avoid polluting real config
-(defvar test-browser--temp-choice-file nil
- "Temporary file for browser choice during tests.")
-
-(defun test-browser-setup ()
- "Setup test environment before each test."
- (setq test-browser--temp-choice-file (make-temp-file "browser-choice-test" nil ".el"))
- (setq cj/browser-choice-file test-browser--temp-choice-file))
-
-(defun test-browser-teardown ()
- "Clean up test environment after each test."
- (when (and test-browser--temp-choice-file
- (file-exists-p test-browser--temp-choice-file))
- (delete-file test-browser--temp-choice-file))
- (setq test-browser--temp-choice-file nil))
-
-;; Now require the module
-(require 'browser-config)
-
-;;; Helper Functions
-
-(defun test-browser-make-plist (name &optional executable path)
- "Create a test browser plist with NAME, EXECUTABLE, and PATH."
- (list :function 'eww-browse-url
- :name name
- :executable executable
- :path path
- :program-var nil))
-
-;;; Normal Cases - Discover Browsers
-
-(ert-deftest test-browser-discover-finds-eww ()
- "Should always find built-in EWW browser."
- (test-browser-setup)
- (let ((browsers (cj/discover-browsers)))
- (should (cl-find-if (lambda (b) (string= (plist-get b :name) "EWW (Emacs Browser)"))
- browsers)))
- (test-browser-teardown))
-
-(ert-deftest test-browser-discover-deduplicates-names ()
- "Should not return duplicate browser names."
- (test-browser-setup)
- (let ((browsers (cj/discover-browsers))
- (names (mapcar (lambda (b) (plist-get b :name)) (cj/discover-browsers))))
- (should (= (length names) (length (cl-remove-duplicates names :test 'string=)))))
- (test-browser-teardown))
-
-;;; Normal Cases - Apply Browser Choice
-
-(ert-deftest test-browser-apply-valid-browser ()
- "Should successfully apply a valid browser configuration."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "Test Browser")))
- (let ((result (cj/--do-apply-browser-choice browser)))
- (should (eq result 'success))
- (should (eq browse-url-browser-function 'eww-browse-url))))
- (test-browser-teardown))
-
-(ert-deftest test-browser-apply-sets-program-var ()
- "Should set browser program variable if specified."
- (test-browser-setup)
- (let ((browser (list :function 'browse-url-chrome
- :name "Chrome"
- :executable "chrome"
- :path "/usr/bin/chrome"
- :program-var 'browse-url-chrome-program)))
- (cj/--do-apply-browser-choice browser)
- (should (string= browse-url-chrome-program "/usr/bin/chrome")))
- (test-browser-teardown))
-
-;;; Normal Cases - Save and Load
-
-(ert-deftest test-browser-save-and-load-choice ()
- "Should save and load browser choice correctly."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "Saved Browser" "firefox" "/usr/bin/firefox")))
- (cj/save-browser-choice browser)
- (let ((loaded (cj/load-browser-choice)))
- (should loaded)
- (should (string= (plist-get loaded :name) "Saved Browser"))
- (should (string= (plist-get loaded :executable) "firefox"))))
- (test-browser-teardown))
-
-;;; Normal Cases - Choose Browser
-
-(ert-deftest test-browser-choose-saves-and-applies ()
- "Should save and apply browser choice."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "Test")))
- (let ((result (cj/--do-choose-browser browser)))
- (should (eq result 'success))
- ;; Verify it was saved
- (let ((loaded (cj/load-browser-choice)))
- (should (string= (plist-get loaded :name) "Test")))))
- (test-browser-teardown))
-
-;;; Normal Cases - Initialize Browser
-
-(ert-deftest test-browser-initialize-with-saved-choice ()
- "Should load and use saved browser choice."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "Saved")))
- (cj/save-browser-choice browser)
- (let ((result (cj/--do-initialize-browser)))
- (should (eq (car result) 'loaded))
- (should (plist-get (cdr result) :name))
- (should (string= (plist-get (cdr result) :name) "Saved"))))
- (test-browser-teardown))
-
-(ert-deftest test-browser-initialize-without-saved-choice ()
- "Should use first available browser when no saved choice."
- (test-browser-setup)
- ;; Delete any saved choice
- (when (file-exists-p cj/browser-choice-file)
- (delete-file cj/browser-choice-file))
- (let ((result (cj/--do-initialize-browser)))
- (should (eq (car result) 'first-available))
- (should (plist-get (cdr result) :name)))
- (test-browser-teardown))
-
-;;; Boundary Cases - Apply Browser
-
-(ert-deftest test-browser-apply-nil-plist ()
- "Should return 'invalid-plist for nil browser."
- (test-browser-setup)
- (let ((result (cj/--do-apply-browser-choice nil)))
- (should (eq result 'invalid-plist)))
- (test-browser-teardown))
-
-(ert-deftest test-browser-apply-missing-function ()
- "Should return 'invalid-plist when :function is missing."
- (test-browser-setup)
- (let ((browser (list :name "Bad Browser" :function nil)))
- (let ((result (cj/--do-apply-browser-choice browser)))
- (should (eq result 'invalid-plist))))
- (test-browser-teardown))
-
-(ert-deftest test-browser-apply-with-nil-path ()
- "Should handle nil path for built-in browser."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "EWW" nil nil)))
- (let ((result (cj/--do-apply-browser-choice browser)))
- (should (eq result 'success))))
- (test-browser-teardown))
-
-;;; Boundary Cases - Save and Load
-
-(ert-deftest test-browser-load-nonexistent-file ()
- "Should return nil when loading from nonexistent file."
- (test-browser-setup)
- (when (file-exists-p cj/browser-choice-file)
- (delete-file cj/browser-choice-file))
- (let ((result (cj/load-browser-choice)))
- (should (null result)))
- (test-browser-teardown))
-
-(ert-deftest test-browser-load-corrupt-file ()
- "Should return nil when loading corrupt file."
- (test-browser-setup)
- (with-temp-file cj/browser-choice-file
- (insert "this is not valid elisp {{{"))
- (let ((result (cj/load-browser-choice)))
- (should (null result)))
- (test-browser-teardown))
-
-(ert-deftest test-browser-load-file-without-variable ()
- "Should return nil when file doesn't define expected variable."
- (test-browser-setup)
- (with-temp-file cj/browser-choice-file
- (insert "(setq some-other-variable 'foo)"))
- ;; Unset any previously loaded variable
- (makunbound 'cj/saved-browser-choice)
- (let ((result (cj/load-browser-choice)))
- (should (null result)))
- (test-browser-teardown))
-
-;;; Boundary Cases - Choose Browser
-
-(ert-deftest test-browser-choose-empty-plist ()
- "Should handle empty plist gracefully."
- (test-browser-setup)
- (let ((result (cj/--do-choose-browser nil)))
- (should (eq result 'invalid-plist)))
- (test-browser-teardown))
-
-;;; Error Cases - File Operations
-
-(ert-deftest test-browser-save-to-readonly-location ()
- "Should return 'save-failed when cannot write file."
- (test-browser-setup)
- ;; Make file read-only
- (with-temp-file cj/browser-choice-file
- (insert ";; test"))
- (set-file-modes cj/browser-choice-file #o444)
- (let ((browser (test-browser-make-plist "Test"))
- (result nil))
- (setq result (cj/--do-choose-browser browser))
- ;; Restore permissions before teardown
- (set-file-modes cj/browser-choice-file #o644)
- (should (eq result 'save-failed)))
- (test-browser-teardown))
-
-;;; Browser Discovery Tests
-
-(ert-deftest test-browser-discover-returns-plists ()
- "Should return properly formatted browser plists."
- (test-browser-setup)
- (let ((browsers (cj/discover-browsers)))
- (should (> (length browsers) 0))
- (dolist (browser browsers)
- (should (plist-member browser :function))
- (should (plist-member browser :name))
- (should (plist-member browser :executable))
- (should (plist-member browser :path))))
- (test-browser-teardown))
-
-(ert-deftest test-browser-format-location-keys ()
- "Should have all required keys in browser plist."
- (test-browser-setup)
- (let ((browsers (cj/discover-browsers)))
- (when browsers
- (let ((browser (car browsers)))
- (should (plist-get browser :function))
- (should (plist-get browser :name)))))
- (test-browser-teardown))
-
-;;; Integration Tests
-
-(ert-deftest test-browser-full-cycle ()
- "Should handle full save-load-apply cycle."
- (test-browser-setup)
- (let ((browser (test-browser-make-plist "Cycle Test" "test-browser" "/usr/bin/test")))
- ;; Choose (save and apply)
- (should (eq (cj/--do-choose-browser browser) 'success))
- ;; Verify it was saved
- (let ((loaded (cj/load-browser-choice)))
- (should loaded)
- (should (string= (plist-get loaded :name) "Cycle Test")))
- ;; Initialize should load the saved choice
- (let ((result (cj/--do-initialize-browser)))
- (should (eq (car result) 'loaded))
- (should (string= (plist-get (cdr result) :name) "Cycle Test"))))
- (test-browser-teardown))
-
-(ert-deftest test-browser-overwrite-choice ()
- "Should overwrite previous browser choice."
- (test-browser-setup)
- (let ((browser1 (test-browser-make-plist "First"))
- (browser2 (test-browser-make-plist "Second")))
- (cj/--do-choose-browser browser1)
- (cj/--do-choose-browser browser2)
- (let ((loaded (cj/load-browser-choice)))
- (should (string= (plist-get loaded :name) "Second"))))
- (test-browser-teardown))
-
-(provide 'test-browser-config)
-;;; test-browser-config.el ends here
diff --git a/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el b/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el
deleted file mode 100644
index bd309880..00000000
--- a/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el
+++ /dev/null
@@ -1,163 +0,0 @@
-;;; test-custom-buffer-file-clear-to-bottom-of-buffer.el --- Tests for cj/clear-to-bottom-of-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/clear-to-bottom-of-buffer function from custom-buffer-file.el
-;;
-;; This function deletes all text from point to the end of the current buffer.
-;; It does not save the deleted text in the kill ring.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-clear-to-bottom-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-clear-to-bottom-teardown ()
- "Clean up test environment."
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-clear-to-bottom-point-in-middle ()
- "Should delete from point to end when point in middle."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (forward-line 1) ; Point at start of "Line 2"
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 1\n")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-empty-buffer ()
- "Should do nothing in empty buffer."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "")))
- (test-clear-to-bottom-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-clear-to-bottom-point-at-beginning ()
- "Should delete entire buffer when point at beginning."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-point-at-end ()
- "Should delete nothing when point at end."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-point-second-to-last-char ()
- "Should delete last character when point at second-to-last."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (goto-char (1- (point-max))) ; Before 'o'
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "Hell")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-unicode-content ()
- "Should handle unicode content."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋\nمرحبا\nWorld")
- (goto-char (point-min))
- (forward-line 1)
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "Hello 👋\n")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-narrowed-buffer ()
- "Should respect narrowing."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\nLine 4")
- (goto-char (point-min))
- (forward-line 1)
- (let ((start (point)))
- (forward-line 2)
- (narrow-to-region start (point))
- (goto-char (point-min))
- (forward-line 1) ; Point at "Line 3"
- (cj/clear-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 2\n"))))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-multiple-windows ()
- "Should update all windows showing buffer."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (forward-line 1)
- (cj/clear-to-bottom-of-buffer)
- ;; Just verify content changed
- (should (equal (buffer-string) "Line 1\n")))
- (test-clear-to-bottom-teardown)))
-
-(ert-deftest test-clear-to-bottom-does-not-affect-kill-ring ()
- "Should not add deleted text to kill ring."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (setq kill-ring nil)
- (cj/clear-to-bottom-of-buffer)
- (should (null kill-ring)))
- (test-clear-to-bottom-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-clear-to-bottom-read-only-buffer ()
- "Should signal error in read-only buffer."
- (test-clear-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read-only content")
- (read-only-mode 1)
- (goto-char (point-min))
- (should-error (cj/clear-to-bottom-of-buffer)))
- (test-clear-to-bottom-teardown)))
-
-(provide 'test-custom-buffer-file-clear-to-bottom-of-buffer)
-;;; test-custom-buffer-file-clear-to-bottom-of-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-clear-to-top-of-buffer.el b/tests/test-custom-buffer-file-clear-to-top-of-buffer.el
deleted file mode 100644
index 2bf79b27..00000000
--- a/tests/test-custom-buffer-file-clear-to-top-of-buffer.el
+++ /dev/null
@@ -1,162 +0,0 @@
-;;; test-custom-buffer-file-clear-to-top-of-buffer.el --- Tests for cj/clear-to-top-of-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/clear-to-top-of-buffer function from custom-buffer-file.el
-;;
-;; This function deletes all text from point to the beginning of the current buffer.
-;; It does not save the deleted text in the kill ring.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-clear-to-top-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-clear-to-top-teardown ()
- "Clean up test environment."
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-clear-to-top-point-in-middle ()
- "Should delete from beginning to point when point in middle."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (forward-line 2) ; Point at start of "Line 3"
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "Line 3")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-empty-buffer ()
- "Should do nothing in empty buffer."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "")))
- (test-clear-to-top-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-clear-to-top-point-at-beginning ()
- "Should delete nothing when point at beginning."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-point-at-end ()
- "Should delete entire buffer when point at end."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-point-at-second-char ()
- "Should delete first character when point at second."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (goto-char (1+ (point-min))) ; After 'H'
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "ello")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-unicode-content ()
- "Should handle unicode content."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋\nمرحبا\nWorld")
- (goto-char (point-min))
- (forward-line 2)
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "World")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-narrowed-buffer ()
- "Should respect narrowing."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\nLine 4")
- (goto-char (point-min))
- (forward-line 1)
- (let ((start (point)))
- (forward-line 2)
- (narrow-to-region start (point))
- (goto-char (point-min))
- (forward-line 1) ; Point at "Line 3"
- (cj/clear-to-top-of-buffer)
- (should (equal (buffer-string) "Line 3\n"))))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-multiple-windows ()
- "Should update all windows showing buffer."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (cj/clear-to-top-of-buffer)
- ;; Just verify content changed
- (should (equal (buffer-string) "")))
- (test-clear-to-top-teardown)))
-
-(ert-deftest test-clear-to-top-does-not-affect-kill-ring ()
- "Should not add deleted text to kill ring."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (setq kill-ring nil)
- (cj/clear-to-top-of-buffer)
- (should (null kill-ring)))
- (test-clear-to-top-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-clear-to-top-read-only-buffer ()
- "Should signal error in read-only buffer."
- (test-clear-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read-only content")
- (read-only-mode 1)
- (goto-char (point-max))
- (should-error (cj/clear-to-top-of-buffer)))
- (test-clear-to-top-teardown)))
-
-(provide 'test-custom-buffer-file-clear-to-top-of-buffer)
-;;; test-custom-buffer-file-clear-to-top-of-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-copy-link-to-buffer-file.el b/tests/test-custom-buffer-file-copy-link-to-buffer-file.el
deleted file mode 100644
index 262968d6..00000000
--- a/tests/test-custom-buffer-file-copy-link-to-buffer-file.el
+++ /dev/null
@@ -1,209 +0,0 @@
-;;; test-custom-buffer-file-copy-link-to-buffer-file.el --- Tests for cj/copy-link-to-buffer-file -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/copy-link-to-buffer-file function from custom-buffer-file.el
-;;
-;; This function copies the full file:// path of the current buffer's file to
-;; the kill ring. For non-file buffers, it does nothing (no error).
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-copy-link-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-copy-link-teardown ()
- "Clean up test environment."
- ;; Kill all buffers visiting files in the test directory
- (dolist (buf (buffer-list))
- (when (buffer-file-name buf)
- (when (string-prefix-p cj/test-base-dir (buffer-file-name buf))
- (with-current-buffer buf
- (set-buffer-modified-p nil)
- (kill-buffer buf)))))
- (cj/delete-test-base-dir)
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-copy-link-simple-file ()
- "Should copy file:// link for simple file buffer."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-non-file-buffer ()
- "Should do nothing for non-file buffer without error."
- (test-copy-link-setup)
- (unwind-protect
- (with-temp-buffer
- (setq kill-ring nil)
- (cj/copy-link-to-buffer-file)
- (should (null kill-ring)))
- (test-copy-link-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-copy-link-unicode-filename ()
- "Should handle unicode in filename."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "café.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-spaces-in-filename ()
- "Should handle spaces in filename."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "my file.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-special-chars-filename ()
- "Should handle special characters in filename."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "[test]-(1).txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-very-long-path ()
- "Should handle very long path."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (long-name (make-string 200 ?x))
- (test-file (expand-file-name (concat long-name ".txt") test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-hidden-file ()
- "Should handle hidden file."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name ".hidden" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-no-extension ()
- "Should handle file with no extension."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "README" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-symlink-file ()
- "Should use buffer's filename for symlink."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (target-file (expand-file-name "target.txt" test-dir))
- (link-file (expand-file-name "link.txt" test-dir)))
- (with-temp-file target-file
- (insert "content"))
- (make-symbolic-link target-file link-file)
- (with-current-buffer (find-file link-file)
- (cj/copy-link-to-buffer-file)
- ;; Should use the link name (what buffer-file-name returns)
- (should (equal (car kill-ring) (concat "file://" (buffer-file-name))))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-kill-ring-has-content ()
- "Should add to kill ring when it already has content."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (kill-new "existing content")
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))
- (should (equal (cadr kill-ring) "existing content"))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-empty-kill-ring ()
- "Should populate empty kill ring."
- (test-copy-link-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (setq kill-ring nil)
- (with-current-buffer (find-file test-file)
- (cj/copy-link-to-buffer-file)
- (should (equal (car kill-ring) (concat "file://" test-file)))
- (should (= (length kill-ring) 1))))
- (test-copy-link-teardown)))
-
-(ert-deftest test-copy-link-scratch-buffer ()
- "Should do nothing for *scratch* buffer."
- (test-copy-link-setup)
- (unwind-protect
- (progn
- (setq kill-ring nil)
- (with-current-buffer "*scratch*"
- (cj/copy-link-to-buffer-file)
- (should (null kill-ring))))
- (test-copy-link-teardown)))
-
-(provide 'test-custom-buffer-file-copy-link-to-buffer-file)
-;;; test-custom-buffer-file-copy-link-to-buffer-file.el ends here
diff --git a/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el b/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el
deleted file mode 100644
index 08959a85..00000000
--- a/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el
+++ /dev/null
@@ -1,205 +0,0 @@
-;;; test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el --- Tests for cj/copy-path-to-buffer-file-as-kill -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/copy-path-to-buffer-file-as-kill function from custom-buffer-file.el
-;;
-;; This function copies the full path of the current buffer's file to the kill ring
-;; and returns the path. It signals an error if the buffer is not visiting a file.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-copy-path-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-copy-path-teardown ()
- "Clean up test environment."
- ;; Kill all buffers visiting files in the test directory
- (dolist (buf (buffer-list))
- (when (buffer-file-name buf)
- (when (string-prefix-p cj/test-base-dir (buffer-file-name buf))
- (with-current-buffer buf
- (set-buffer-modified-p nil)
- (kill-buffer buf)))))
- (cj/delete-test-base-dir)
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-copy-path-simple-file ()
- "Should copy absolute path for simple file buffer."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (let ((result (cj/copy-path-to-buffer-file-as-kill)))
- (should (equal result test-file))
- (should (equal (car kill-ring) test-file)))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-returns-path ()
- "Should return the path value."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (let ((result (cj/copy-path-to-buffer-file-as-kill)))
- (should (stringp result))
- (should (equal result test-file)))))
- (test-copy-path-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-copy-path-unicode-filename ()
- "Should handle unicode in filename."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "café.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-spaces-in-filename ()
- "Should handle spaces in filename."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "my file.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-special-chars-filename ()
- "Should handle special characters in filename."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "[test]-(1).txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-very-long-path ()
- "Should handle very long path."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (long-name (make-string 200 ?x))
- (test-file (expand-file-name (concat long-name ".txt") test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-hidden-file ()
- "Should handle hidden file."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name ".hidden" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-no-extension ()
- "Should handle file with no extension."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "README" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-symlink-file ()
- "Should use buffer's filename for symlink."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (target-file (expand-file-name "target.txt" test-dir))
- (link-file (expand-file-name "link.txt" test-dir)))
- (with-temp-file target-file
- (insert "content"))
- (make-symbolic-link target-file link-file)
- (with-current-buffer (find-file link-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) (buffer-file-name)))))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-kill-ring-has-content ()
- "Should add to kill ring when it already has content."
- (test-copy-path-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (kill-new "existing content")
- (with-current-buffer (find-file test-file)
- (cj/copy-path-to-buffer-file-as-kill)
- (should (equal (car kill-ring) test-file))
- (should (equal (cadr kill-ring) "existing content"))))
- (test-copy-path-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-copy-path-non-file-buffer ()
- "Should signal user-error for non-file buffer."
- (test-copy-path-setup)
- (unwind-protect
- (with-temp-buffer
- (should-error (cj/copy-path-to-buffer-file-as-kill) :type 'user-error))
- (test-copy-path-teardown)))
-
-(ert-deftest test-copy-path-scratch-buffer ()
- "Should signal user-error for *scratch* buffer."
- (test-copy-path-setup)
- (unwind-protect
- (with-current-buffer "*scratch*"
- (should-error (cj/copy-path-to-buffer-file-as-kill) :type 'user-error))
- (test-copy-path-teardown)))
-
-(provide 'test-custom-buffer-file-copy-path-to-buffer-file-as-kill)
-;;; test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el ends here
diff --git a/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el b/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el
deleted file mode 100644
index 0c41761e..00000000
--- a/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el
+++ /dev/null
@@ -1,187 +0,0 @@
-;;; test-custom-buffer-file-copy-to-bottom-of-buffer.el --- Tests for cj/copy-to-bottom-of-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/copy-to-bottom-of-buffer function from custom-buffer-file.el
-;;
-;; This function copies all text from point to the end of the current buffer
-;; to the kill ring without modifying the buffer.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-copy-to-bottom-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-copy-to-bottom-teardown ()
- "Clean up test environment."
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-normal-point-in-middle-copies-to-end ()
- "Should copy from point to end when point in middle."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (forward-line 1) ; Point at start of "Line 2"
- (let ((original-content (buffer-string)))
- (cj/copy-to-bottom-of-buffer)
- ;; Buffer should be unchanged
- (should (equal (buffer-string) original-content))
- ;; Kill ring should contain from point to end
- (should (equal (car kill-ring) "Line 2\nLine 3"))))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-normal-single-line-copies-partial ()
- "Should copy partial line content from middle of line."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello World")
- (goto-char (point-min))
- (forward-char 6) ; Point after "Hello "
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Hello World"))
- (should (equal (car kill-ring) "World")))
- (test-copy-to-bottom-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-at-beginning-copies-all ()
- "Should copy entire buffer when point at beginning."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
- (should (equal (car kill-ring) "Line 1\nLine 2\nLine 3")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-at-end-copies-empty ()
- "Should copy empty string when point at end."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
- (should (equal (car kill-ring) "")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-empty-buffer-copies-empty ()
- "Should copy empty string in empty buffer."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) ""))
- (should (equal (car kill-ring) "")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-second-to-last-char-copies-one ()
- "Should copy last character when point at second-to-last."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (goto-char (1- (point-max))) ; Before 'o'
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Hello"))
- (should (equal (car kill-ring) "o")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-unicode-content-copies-correctly ()
- "Should handle unicode content correctly."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋\nمرحبا\nWorld")
- (goto-char (point-min))
- (forward-line 1)
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Hello 👋\nمرحبا\nWorld"))
- (should (equal (car kill-ring) "مرحبا\nWorld")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-narrowed-buffer-respects-narrowing ()
- "Should respect narrowing and only copy within narrowed region."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\nLine 4")
- (goto-char (point-min))
- (forward-line 1)
- (let ((start (point)))
- (forward-line 2)
- (narrow-to-region start (point))
- (goto-char (point-min))
- (forward-line 1) ; Point at "Line 3"
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "Line 2\nLine 3\n"))
- (should (equal (car kill-ring) "Line 3\n"))))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-whitespace-only-copies-whitespace ()
- "Should copy whitespace-only content."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \n\t\t\n ")
- (goto-char (point-min))
- (forward-char 4) ; After first newline
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) " \n\t\t\n "))
- (should (equal (car kill-ring) "\t\t\n ")))
- (test-copy-to-bottom-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-single-character-copies-char ()
- "Should copy single character buffer."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "x")
- (goto-char (point-min))
- (cj/copy-to-bottom-of-buffer)
- (should (equal (buffer-string) "x"))
- (should (equal (car kill-ring) "x")))
- (test-copy-to-bottom-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-error-read-only-buffer-succeeds ()
- "Should work in read-only buffer since it doesn't modify content."
- (test-copy-to-bottom-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read-only content")
- (read-only-mode 1)
- (goto-char (point-min))
- (cj/copy-to-bottom-of-buffer)
- (should (equal (car kill-ring) "Read-only content")))
- (test-copy-to-bottom-teardown)))
-
-(provide 'test-custom-buffer-file-copy-to-bottom-of-buffer)
-;;; test-custom-buffer-file-copy-to-bottom-of-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-copy-to-top-of-buffer.el b/tests/test-custom-buffer-file-copy-to-top-of-buffer.el
deleted file mode 100644
index 0f09f26d..00000000
--- a/tests/test-custom-buffer-file-copy-to-top-of-buffer.el
+++ /dev/null
@@ -1,186 +0,0 @@
-;;; test-custom-buffer-file-copy-to-top-of-buffer.el --- Tests for cj/copy-to-top-of-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/copy-to-top-of-buffer function from custom-buffer-file.el
-;;
-;; This function copies all text from the beginning of the buffer to point
-;; to the kill ring without modifying the buffer.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-copy-to-top-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-copy-to-top-teardown ()
- "Clean up test environment."
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-normal-point-in-middle-copies-from-beginning ()
- "Should copy from beginning to point when point in middle."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (forward-line 2) ; Point at start of "Line 3"
- (let ((original-content (buffer-string)))
- (cj/copy-to-top-of-buffer)
- ;; Buffer should be unchanged
- (should (equal (buffer-string) original-content))
- ;; Kill ring should contain from beginning to point
- (should (equal (car kill-ring) "Line 1\nLine 2\n"))))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-normal-single-line-copies-partial ()
- "Should copy partial line content from beginning to middle of line."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello World")
- (goto-char (point-min))
- (forward-char 5) ; Point after "Hello"
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Hello World"))
- (should (equal (car kill-ring) "Hello")))
- (test-copy-to-top-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-end-copies-all ()
- "Should copy entire buffer when point at end."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-max))
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
- (should (equal (car kill-ring) "Line 1\nLine 2\nLine 3")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-beginning-copies-empty ()
- "Should copy empty string when point at beginning."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (goto-char (point-min))
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
- (should (equal (car kill-ring) "")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-empty-buffer-copies-empty ()
- "Should copy empty string in empty buffer."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) ""))
- (should (equal (car kill-ring) "")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-second-char-copies-one ()
- "Should copy first character when point at second character."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (goto-char (1+ (point-min))) ; After 'H'
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Hello"))
- (should (equal (car kill-ring) "H")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-unicode-content-copies-correctly ()
- "Should handle unicode content correctly."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋\nمرحبا\nWorld")
- (goto-char (point-min))
- (forward-line 2) ; Point at start of "World"
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Hello 👋\nمرحبا\nWorld"))
- (should (equal (car kill-ring) "Hello 👋\nمرحبا\n")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-narrowed-buffer-respects-narrowing ()
- "Should respect narrowing and only copy within narrowed region."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\nLine 4")
- (goto-char (point-min))
- (forward-line 1)
- (let ((start (point)))
- (forward-line 2)
- (narrow-to-region start (point))
- (goto-char (point-max)) ; Point at end of narrowed region
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "Line 2\nLine 3\n"))
- (should (equal (car kill-ring) "Line 2\nLine 3\n"))))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-whitespace-only-copies-whitespace ()
- "Should copy whitespace-only content."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \n\t\t\n ")
- (goto-char (point-min))
- (forward-char 7) ; After second newline
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) " \n\t\t\n "))
- (should (equal (car kill-ring) " \n\t\t\n")))
- (test-copy-to-top-teardown)))
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-single-character-copies-char ()
- "Should copy single character buffer."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "x")
- (goto-char (point-max))
- (cj/copy-to-top-of-buffer)
- (should (equal (buffer-string) "x"))
- (should (equal (car kill-ring) "x")))
- (test-copy-to-top-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-error-read-only-buffer-succeeds ()
- "Should work in read-only buffer since it doesn't modify content."
- (test-copy-to-top-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read-only content")
- (goto-char (point-max))
- (read-only-mode 1)
- (cj/copy-to-top-of-buffer)
- (should (equal (car kill-ring) "Read-only content")))
- (test-copy-to-top-teardown)))
-
-(provide 'test-custom-buffer-file-copy-to-top-of-buffer)
-;;; test-custom-buffer-file-copy-to-top-of-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-copy-whole-buffer.el b/tests/test-custom-buffer-file-copy-whole-buffer.el
deleted file mode 100644
index 181c491a..00000000
--- a/tests/test-custom-buffer-file-copy-whole-buffer.el
+++ /dev/null
@@ -1,194 +0,0 @@
-;;; test-custom-buffer-file-copy-whole-buffer.el --- Tests for cj/copy-whole-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/copy-whole-buffer function from custom-buffer-file.el
-;;
-;; This function copies the entire contents of the current buffer to the kill ring.
-;; Point and mark are left exactly where they were. No transient region is created.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-copy-whole-buffer-setup ()
- "Set up test environment."
- (setq kill-ring nil))
-
-(defun test-copy-whole-buffer-teardown ()
- "Clean up test environment."
- (setq kill-ring nil))
-
-;;; Normal Cases
-
-(ert-deftest test-copy-whole-buffer-simple-text ()
- "Should copy simple text content to kill ring."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "Hello, world!")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-preserves-point ()
- "Should preserve point position."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (goto-char 7) ; Position in middle
- (cj/copy-whole-buffer)
- (should (= (point) 7)))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-preserves-mark ()
- "Should preserve mark position."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (push-mark 5)
- (goto-char 10)
- (cj/copy-whole-buffer)
- (should (= (mark) 5))
- (should (= (point) 10)))
- (test-copy-whole-buffer-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-copy-whole-buffer-empty ()
- "Should handle empty buffer."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-large ()
- "Should handle very large buffer."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((large-content (make-string 100000 ?x)))
- (insert large-content)
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) large-content))))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-unicode ()
- "Should handle unicode content (emoji, RTL text)."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋 مرحبا")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "Hello 👋 مرحبا")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-binary ()
- "Should handle binary content."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert (string 0 1 2 255))
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) (string 0 1 2 255))))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-only-whitespace ()
- "Should handle buffer with only whitespace."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \t\n ")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) " \t\n ")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-newlines-at-boundaries ()
- "Should handle newlines at start/end."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "\n\nHello\n\n")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "\n\nHello\n\n")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-narrowed ()
- "Should copy only visible region in narrowed buffer."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\n")
- (goto-char (point-min))
- (forward-line 1)
- (narrow-to-region (point) (progn (forward-line 1) (point)))
- (cj/copy-whole-buffer)
- ;; Should copy only the narrowed region
- (should (equal (car kill-ring) "Line 2\n")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-read-only ()
- "Should work in read-only buffer."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read-only content")
- (read-only-mode 1)
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "Read-only content")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-kill-ring-has-content ()
- "Should add to kill ring when it already has content."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "New content")
- (kill-new "existing content")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "New content"))
- (should (equal (cadr kill-ring) "existing content")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-multiline ()
- "Should preserve multiline content."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "Line 1\nLine 2\nLine 3")))
- (test-copy-whole-buffer-teardown)))
-
-(ert-deftest test-copy-whole-buffer-no-properties ()
- "Should strip text properties."
- (test-copy-whole-buffer-setup)
- (unwind-protect
- (with-temp-buffer
- (insert (propertize "Hello" 'face 'bold))
- (cj/copy-whole-buffer)
- (should (equal (car kill-ring) "Hello"))
- (should (null (text-properties-at 0 (car kill-ring)))))
- (test-copy-whole-buffer-teardown)))
-
-(provide 'test-custom-buffer-file-copy-whole-buffer)
-;;; test-custom-buffer-file-copy-whole-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-delete-buffer-and-file.el b/tests/test-custom-buffer-file-delete-buffer-and-file.el
deleted file mode 100644
index 4af8d2a7..00000000
--- a/tests/test-custom-buffer-file-delete-buffer-and-file.el
+++ /dev/null
@@ -1,671 +0,0 @@
-;;; test-custom-buffer-file-delete-buffer-and-file.el --- Tests for cj/delete-buffer-and-file -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/delete-buffer-and-file function from custom-buffer-file.el
-;;
-;; This function deletes both the current buffer and the file it visits.
-;; It uses vc-delete-file for version-controlled files and delete-file
-;; for non-version-controlled files.
-;;
-;; Testing Strategy:
-;; - We test OUR code's behavior, not the underlying delete-file/vc-delete-file
-;; implementations
-;; - We verify our code correctly:
-;; 1. Detects VC vs non-VC files (via vc-backend)
-;; 2. Calls the appropriate deletion function (vc-delete-file or delete-file)
-;; 3. Passes the trash flag (t) to delete-file
-;; 4. Propagates errors from the deletion functions
-;;
-;; Why We Mock delete-file Errors:
-;; - Tests like "already deleted file" and "no delete permission" are testing
-;; system/environment behavior, not our code
-;; - The trash system handles these cases in environment-specific ways:
-;; - Missing files may not error (trash handles gracefully)
-;; - File permissions may not matter (directory permissions for moving to trash)
-;; - To make tests deterministic and portable, we mock delete-file to throw
-;; specific errors, then verify our code propagates them correctly
-;; - This tests our contract: "when delete-file fails, we let the error through"
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-delete-buffer-and-file-setup ()
- "Setup for delete-buffer-and-file tests."
- (cj/create-test-base-dir))
-
-(defun test-delete-buffer-and-file-teardown ()
- "Teardown for delete-buffer-and-file tests."
- ;; Kill all buffers visiting files in test directory
- (dolist (buf (buffer-list))
- (when (buffer-file-name buf)
- (when (string-prefix-p cj/test-base-dir (buffer-file-name buf))
- (with-current-buffer buf
- (set-buffer-modified-p nil))
- (kill-buffer buf))))
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-delete-buffer-and-file-simple-delete ()
- "Should delete file and kill buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (let ((buf (current-buffer)))
- ;; Mock vc-backend to return nil (non-VC file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf)))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-removes-file-from-disk ()
- "Should remove file from disk."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-kills-buffer ()
- "Should kill the buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (buffer-live-p buf)))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-calls-delete-file-with-trash-flag ()
- "Should call delete-file with trash flag set to t."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (delete-file-args nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil))
- ((symbol-function 'delete-file)
- (lambda (file trash)
- (setq delete-file-args (list file trash)))))
- (cj/delete-buffer-and-file)
- (should (equal delete-file-args (list test-file t)))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-shows-message ()
- "Should display message for non-VC deletes."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (message-output nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil))
- ((symbol-function 'message)
- (lambda (fmt &rest args)
- (setq message-output (apply #'format fmt args)))))
- (cj/delete-buffer-and-file)
- (should (string-match-p "Deleted file.*test.txt" message-output))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-vc-file-uses-vc-delete ()
- "Should call vc-delete-file for VC files."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (vc-delete-called nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) 'Git))
- ((symbol-function 'vc-delete-file)
- (lambda (file)
- (setq vc-delete-called file)
- ;; Simulate vc-delete-file killing the buffer
- (when (get-file-buffer file)
- (kill-buffer (get-file-buffer file)))
- ;; Actually delete the file for test cleanup
- (delete-file file t))))
- (cj/delete-buffer-and-file)
- (should (string= vc-delete-called test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-non-vc-file-uses-delete-file ()
- "Should call delete-file for non-VC files."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (delete-file-called nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil))
- ((symbol-function 'delete-file)
- (lambda (file trash)
- (setq delete-file-called file))))
- (cj/delete-buffer-and-file)
- (should (string= delete-file-called test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-returns-implicitly ()
- "Should return result of last expression."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (let ((result (cj/delete-buffer-and-file)))
- ;; kill-buffer returns t, so result should be t
- (should (eq result t)))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Boundary Cases - File Content
-
-(ert-deftest test-delete-buffer-and-file-empty-file ()
- "Should delete empty file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "empty.txt" test-dir)))
- (with-temp-file test-file)
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-large-file ()
- "Should delete large file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "large.txt" test-dir))
- (large-content (make-string 100000 ?x)))
- (with-temp-file test-file
- (insert large-content))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-binary-file ()
- "Should delete binary file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "binary.dat" test-dir))
- (binary-content (string 0 1 2 3 255 254 253)))
- (with-temp-file test-file
- (set-buffer-multibyte nil)
- (insert binary-content))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-with-unicode-content ()
- "Should delete file with Unicode content."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "unicode.txt" test-dir))
- (content "Hello 世界 مرحبا Привет"))
- (with-temp-file test-file
- (insert content))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Boundary Cases - File Naming
-
-(ert-deftest test-delete-buffer-and-file-unicode-filename ()
- "Should delete file with Unicode filename."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "café.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-spaces-in-filename ()
- "Should delete file with spaces in name."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "my file.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-special-chars-filename ()
- "Should delete file with special characters."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "[test]-(1).txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-hidden-file ()
- "Should delete hidden file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name ".hidden" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-no-extension ()
- "Should delete file without extension."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "README" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-very-long-filename ()
- "Should delete file with very long name."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (long-name (concat (make-string 200 ?x) ".txt"))
- (test-file (expand-file-name long-name test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Buffer State
-
-(ert-deftest test-delete-buffer-and-file-with-unsaved-changes ()
- "Should handle buffer with unsaved changes."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "original"))
- (find-file test-file)
- (insert " modified")
- (should (buffer-modified-p))
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf)))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-read-only-buffer ()
- "Should handle read-only buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (read-only-mode 1)
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf)))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-multiple-windows ()
- "Should work when buffer displayed in multiple windows."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (delete-other-windows)
- (split-window)
- (other-window 1)
- (switch-to-buffer (get-file-buffer test-file))
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf))))
- (delete-other-windows))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-buffer-not-current ()
- "Should only operate on current buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (file1 (expand-file-name "file1.txt" test-dir))
- (file2 (expand-file-name "file2.txt" test-dir)))
- (with-temp-file file1
- (insert "content1"))
- (with-temp-file file2
- (insert "content2"))
- (find-file file1)
- (find-file file2)
- ;; Current buffer is file2
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- ;; file2 should be deleted, file1 should still exist
- (should-not (file-exists-p file2))
- (should (file-exists-p file1)))
- (kill-buffer (get-file-buffer file1)))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-narrowed-buffer ()
- "Should work with narrowed buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "Line 1\nLine 2\nLine 3"))
- (find-file test-file)
- (goto-char (point-min))
- (forward-line 1)
- (narrow-to-region (point) (line-end-position))
- (let ((buf (current-buffer)))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf)))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Error Cases - Buffer Issues
-
-(ert-deftest test-delete-buffer-and-file-non-file-buffer-does-nothing ()
- "Should do nothing if buffer not visiting file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (with-temp-buffer
- (rename-buffer "non-file-buffer" t)
- (let ((buf (current-buffer)))
- (cj/delete-buffer-and-file)
- ;; Buffer should still be alive
- (should (buffer-live-p buf))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-scratch-buffer-does-nothing ()
- "Should do nothing for scratch buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (with-current-buffer "*scratch*"
- (cj/delete-buffer-and-file)
- ;; Scratch buffer should still exist
- (should (get-buffer "*scratch*")))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-already-killed-buffer ()
- "Should error when operating on killed buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (buf nil))
- (with-temp-file test-file
- (insert "content"))
- (setq buf (find-file test-file))
- (kill-buffer buf)
- (should-error
- (with-current-buffer buf
- (cj/delete-buffer-and-file))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Error Cases - File Issues
-
-(ert-deftest test-delete-buffer-and-file-already-deleted-file ()
- "Should propagate error when delete-file fails on missing file."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil))
- ((symbol-function 'delete-file)
- (lambda (file &optional _trash)
- (signal 'file-missing (list "Removing old name" "No such file or directory" file)))))
- ;; Should propagate error from delete-file
- (should-error (cj/delete-buffer-and-file) :type 'file-missing)))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-no-delete-permission ()
- "Should propagate error when delete-file fails due to permissions."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil))
- ((symbol-function 'delete-file)
- (lambda (file &optional _trash)
- (signal 'file-error (list "Removing old name" "Permission denied" file)))))
- ;; Should propagate error from delete-file
- (should-error (cj/delete-buffer-and-file) :type 'file-error)))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-no-write-permission-directory ()
- "Should error if directory not writable."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (set-file-modes test-dir #o555)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (should-error (cj/delete-buffer-and-file))
- (set-file-modes test-dir #o755)))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-open-in-other-buffer ()
- "Should handle file open in another buffer."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (let ((buf1 (current-buffer)))
- (find-file test-file)
- (let ((buf2 (current-buffer)))
- ;; Both buffers visiting same file
- (should (eq buf1 buf2))
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))
- (should-not (buffer-live-p buf1))))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-symlink-file ()
- "Should handle symlink files."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (real-file (expand-file-name "real.txt" test-dir))
- (symlink (expand-file-name "link.txt" test-dir)))
- (with-temp-file real-file
- (insert "content"))
- (make-symbolic-link real-file symlink)
- (find-file symlink)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- ;; Symlink should be deleted, real file should remain
- (should-not (file-exists-p symlink))
- (should (file-exists-p real-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-symlink-directory ()
- "Should handle files in symlinked directories."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((real-dir (cj/create-test-subdirectory "real"))
- (link-dir (expand-file-name "link" cj/test-base-dir))
- (test-file (expand-file-name "test.txt" real-dir)))
- (with-temp-file test-file
- (insert "content"))
- (make-symbolic-link real-dir link-dir)
- (let ((file-via-link (expand-file-name "test.txt" link-dir)))
- (find-file file-via-link)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- ;; File should be deleted
- (should-not (file-exists-p test-file)))))
- (test-delete-buffer-and-file-teardown)))
-
-;;; Edge Cases - Version Control
-
-(ert-deftest test-delete-buffer-and-file-git-tracked-file ()
- "Should use vc-delete-file for git files."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (vc-delete-called nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) 'Git))
- ((symbol-function 'vc-delete-file)
- (lambda (file)
- (setq vc-delete-called t)
- (when (get-file-buffer file)
- (kill-buffer (get-file-buffer file)))
- (delete-file file t))))
- (cj/delete-buffer-and-file)
- (should vc-delete-called)))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-untracked-in-vc-repo ()
- "Should use delete-file for untracked files in VC repo."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "untracked.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- ;; vc-backend returns nil for untracked files
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) nil)))
- (cj/delete-buffer-and-file)
- (should-not (file-exists-p test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-vc-backend-detection ()
- "Should correctly detect VC backend."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir))
- (backend-checked nil))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend)
- (lambda (file)
- (setq backend-checked file)
- nil)))
- (cj/delete-buffer-and-file)
- (should (string= backend-checked test-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(ert-deftest test-delete-buffer-and-file-vc-delete-fails ()
- "Should propagate vc-delete-file errors."
- (test-delete-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (test-file (expand-file-name "test.txt" test-dir)))
- (with-temp-file test-file
- (insert "content"))
- (find-file test-file)
- (cl-letf (((symbol-function 'vc-backend) (lambda (&rest _) 'Git))
- ((symbol-function 'vc-delete-file)
- (lambda (file)
- (error "VC operation failed"))))
- (should-error (cj/delete-buffer-and-file))))
- (test-delete-buffer-and-file-teardown)))
-
-(provide 'test-custom-buffer-file-delete-buffer-and-file)
-;;; test-custom-buffer-file-delete-buffer-and-file.el ends here
diff --git a/tests/test-custom-buffer-file-move-buffer-and-file.el b/tests/test-custom-buffer-file-move-buffer-and-file.el
deleted file mode 100644
index e8f4563d..00000000
--- a/tests/test-custom-buffer-file-move-buffer-and-file.el
+++ /dev/null
@@ -1,936 +0,0 @@
-;;; test-custom-buffer-file-move-buffer-and-file.el --- Tests for cj/move-buffer-and-file -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--move-buffer-and-file function from custom-buffer-file.el
-;;
-;; This is the internal (non-interactive) implementation that moves both the
-;; current buffer and its visited file to a new directory. It handles trailing
-;; slashes, preserves file content, updates the visited-file-name, and clears
-;; the modified flag. The interactive wrapper cj/move-buffer-and-file handles
-;; user prompting and delegates to this implementation.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-move-buffer-and-file-setup ()
- "Setup for move-buffer-and-file tests."
- (cj/create-test-base-dir))
-
-(defun test-move-buffer-and-file-teardown ()
- "Teardown for move-buffer-and-file tests."
- ;; Kill all buffers visiting files in test directory
- (dolist (buf (buffer-list))
- (when (buffer-file-name buf)
- (when (string-prefix-p cj/test-base-dir (buffer-file-name buf))
- (with-current-buffer buf
- (set-buffer-modified-p nil))
- (kill-buffer buf))))
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-move-buffer-and-file-simple-move-should-succeed ()
- "Should move file and buffer to new directory successfully."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir))
- (content "Test content"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-updates-buffer-file-name ()
- "Should update buffer-file-name to new location."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-file-name) target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-content ()
- "Should preserve file content after move."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (content "Original content\nWith multiple lines\n"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-buffer-name ()
- "Should preserve buffer name after move."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "myfile.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should (string= (buffer-name) "myfile.txt"))
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-name) "myfile.txt"))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-clears-modified-flag ()
- "Should clear buffer modified flag after move."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (insert "modification")
- (should (buffer-modified-p))
- (cj/--move-buffer-and-file target-dir)
- (should-not (buffer-modified-p))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-returns-t-on-success ()
- "Should return t on successful move."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should (eq t (cj/--move-buffer-and-file target-dir)))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-deletes-source-file ()
- "Should delete source file after move."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-creates-target-file ()
- "Should create file in target directory."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Path Handling
-
-(ert-deftest test-move-buffer-and-file-trailing-slash-should-strip ()
- "Should handle directory with trailing slash."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file (concat target-dir "/"))
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-trailing-backslash-should-strip ()
- "Should handle directory with trailing backslash."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file (concat target-dir "\\"))
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-no-trailing-slash-should-work ()
- "Should work with directory without trailing slash."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-deeply-nested-target ()
- "Should move to deeply nested target directory."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "a/b/c/d/target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-relative-path-should-work ()
- "Should resolve relative paths relative to file's directory."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- ;; Use "../target" to go up from source/ to target/
- (cj/--move-buffer-and-file "../target")
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Character Encoding
-
-(ert-deftest test-move-buffer-and-file-unicode-filename ()
- "Should handle Unicode characters in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test-café.txt" source-dir))
- (target-file (expand-file-name "test-café.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-unicode-directory ()
- "Should handle Unicode characters in directory name."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target-ñoño"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-emoji-in-filename ()
- "Should handle emoji in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test-🎉-file.txt" source-dir))
- (target-file (expand-file-name "test-🎉-file.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-rtl-characters ()
- "Should handle RTL text in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test-مرحبا.txt" source-dir))
- (target-file (expand-file-name "test-مرحبا.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-spaces-in-filename ()
- "Should handle spaces in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test file with spaces.txt" source-dir))
- (target-file (expand-file-name "test file with spaces.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-special-chars-in-filename ()
- "Should handle special characters in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test[file]-(1).txt" source-dir))
- (target-file (expand-file-name "test[file]-(1).txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Boundary Cases - File Naming
-
-(ert-deftest test-move-buffer-and-file-hidden-file ()
- "Should handle hidden files (starting with dot)."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name ".hidden" source-dir))
- (target-file (expand-file-name ".hidden" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-no-extension ()
- "Should handle files without extensions."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "README" source-dir))
- (target-file (expand-file-name "README" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-multiple-dots-in-name ()
- "Should handle multiple dots in filename."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "my.file.name.test.txt" source-dir))
- (target-file (expand-file-name "my.file.name.test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-single-char-filename ()
- "Should handle single character filenames."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "x" source-dir))
- (target-file (expand-file-name "x" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-very-long-filename ()
- "Should handle very long filenames."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (long-name (concat (make-string 200 ?x) ".txt"))
- (source-file (expand-file-name long-name source-dir))
- (target-file (expand-file-name long-name target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-very-long-path ()
- "Should handle very long paths."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((long-dir (make-string 100 ?x))
- (source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory long-dir))
- (long-filename (concat (make-string 100 ?y) ".txt"))
- (source-file (expand-file-name long-filename source-dir))
- (target-file (expand-file-name long-filename target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Boundary Cases - File Content
-
-(ert-deftest test-move-buffer-and-file-empty-file ()
- "Should move empty file successfully."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "empty.txt" source-dir))
- (target-file (expand-file-name "empty.txt" target-dir)))
- (with-temp-file source-file)
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (should (= 0 (buffer-size)))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-large-file ()
- "Should move large file successfully."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "large.txt" source-dir))
- (large-content (make-string 100000 ?x)))
- (with-temp-file source-file
- (insert large-content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-string) large-content))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-binary-file ()
- "Should move binary-like content successfully."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "binary.dat" source-dir))
- (target-file (expand-file-name "binary.dat" target-dir))
- (binary-content (string 0 1 2 3 255 254 253)))
- (with-temp-file source-file
- (set-buffer-multibyte nil)
- (insert binary-content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-newlines ()
- "Should preserve different newline types."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "newlines.txt" source-dir))
- (content "Line 1\nLine 2\n\nLine 4\n"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-encoding ()
- "Should preserve UTF-8 encoded content."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "utf8.txt" source-dir))
- (content "Hello 世界 مرحبا Привет"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir)
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Buffer State
-
-(ert-deftest test-move-buffer-and-file-with-unsaved-changes ()
- "Should handle buffer with unsaved changes."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir))
- (original "original"))
- (with-temp-file source-file
- (insert original))
- (find-file source-file)
- (insert " modified")
- (should (buffer-modified-p))
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (buffer-modified-p))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-with-multiple-windows ()
- "Should work when buffer is displayed in multiple windows."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (delete-other-windows)
- (split-window)
- (other-window 1)
- (switch-to-buffer (get-file-buffer source-file))
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer))
- (delete-other-windows))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-point-position ()
- "Should preserve point position in buffer."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (content "Line 1\nLine 2\nLine 3\n"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (goto-char (point-min))
- (forward-line 1)
- (let ((original-point (point)))
- (cj/--move-buffer-and-file target-dir)
- (should (= (point) original-point)))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-preserves-mark ()
- "Should preserve mark in buffer."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (content "Line 1\nLine 2\nLine 3\n"))
- (with-temp-file source-file
- (insert content))
- (find-file source-file)
- (goto-char (point-min))
- (set-mark (point))
- (forward-line 2)
- (let ((original-mark (mark)))
- (cj/--move-buffer-and-file target-dir)
- (should (= (mark) original-mark)))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Error Cases - Buffer Issues
-
-(ert-deftest test-move-buffer-and-file-non-file-buffer-returns-nil ()
- "Should return nil when buffer not visiting a file."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let ((target-dir (cj/create-test-subdirectory "target")))
- (with-temp-buffer
- (rename-buffer "non-file-buffer" t)
- (let ((result (cj/--move-buffer-and-file target-dir)))
- (should-not result))))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-scratch-buffer-returns-nil ()
- "Should return nil for scratch buffer."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let ((target-dir (cj/create-test-subdirectory "target")))
- (with-current-buffer "*scratch*"
- (let ((result (cj/--move-buffer-and-file target-dir)))
- (should-not result))))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-killed-buffer-should-error ()
- "Should error when operating on killed buffer."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (buf nil))
- (with-temp-file source-file
- (insert "content"))
- (setq buf (find-file source-file))
- (kill-buffer buf)
- (should-error
- (with-current-buffer buf
- (cj/--move-buffer-and-file target-dir))))
- (test-move-buffer-and-file-teardown)))
-
-;;; Error Cases - Directory Issues
-
-(ert-deftest test-move-buffer-and-file-nonexistent-target-should-error ()
- "Should error when target directory doesn't exist."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (source-file (expand-file-name "test.txt" source-dir))
- (nonexistent-dir (expand-file-name "nonexistent" cj/test-base-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file nonexistent-dir))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-target-is-file-not-dir-should-error ()
- "Should error when target is a file, not directory."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "notadir.txt" cj/test-base-dir)))
- (with-temp-file target-file
- (insert "I'm a file"))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-nil-directory-should-error ()
- "Should error when directory is nil."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file nil))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-empty-string-directory-should-error ()
- "Should error when directory is empty string."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file ""))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Error Cases - Permission Issues
-
-(ert-deftest test-move-buffer-and-file-no-read-permission-source-should-error ()
- "Should error when source file is not readable."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (set-file-modes source-file #o000)
- (should-error (cj/--move-buffer-and-file target-dir))
- (set-file-modes source-file #o644)
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-no-write-permission-target-should-error ()
- "Should error when target directory is not writable."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (set-file-modes target-dir #o555)
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file target-dir))
- (set-file-modes target-dir #o755)
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-no-delete-permission-source-should-error ()
- "Should error when source directory doesn't allow deletion."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (set-file-modes source-dir #o555)
- (should-error (cj/--move-buffer-and-file target-dir))
- (set-file-modes source-dir #o755)
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Error Cases - File Conflicts
-
-(ert-deftest test-move-buffer-and-file-target-exists-should-overwrite ()
- "Should overwrite existing file when ok-if-exists is t."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir))
- (new-content "New content")
- (old-content "Old content"))
- (with-temp-file target-file
- (insert old-content))
- (with-temp-file source-file
- (insert new-content))
- (find-file source-file)
- (cj/--move-buffer-and-file target-dir t)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (revert-buffer t t)
- (should (string= (buffer-string) new-content))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-target-exists-should-error-if-not-ok ()
- "Should error when target exists and ok-if-exists is nil."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file target-file
- (insert "existing"))
- (with-temp-file source-file
- (insert "new"))
- (find-file source-file)
- (should-error (cj/--move-buffer-and-file target-dir nil))
- ;; Source should still exist since move failed
- (should (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-interactive-prompts-if-target-exists ()
- "Should prompt user when called interactively and target exists."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir))
- (prompted nil))
- (with-temp-file target-file
- (insert "existing"))
- (with-temp-file source-file
- (insert "new"))
- (find-file source-file)
- ;; Mock yes-or-no-p to capture that it was called
- (cl-letf (((symbol-function 'yes-or-no-p)
- (lambda (prompt)
- (setq prompted t)
- t))
- ((symbol-function 'read-directory-name)
- (lambda (&rest _) target-dir)))
- (call-interactively #'cj/move-buffer-and-file)
- (should prompted))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-interactive-no-prompt-if-target-missing ()
- "Should not prompt when called interactively if target doesn't exist."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (prompted nil))
- (with-temp-file source-file
- (insert "new"))
- (find-file source-file)
- ;; Mock yes-or-no-p to capture if it was called
- (cl-letf (((symbol-function 'yes-or-no-p)
- (lambda (prompt)
- (setq prompted t)
- t))
- ((symbol-function 'read-directory-name)
- (lambda (&rest _) target-dir)))
- (call-interactively #'cj/move-buffer-and-file)
- (should-not prompted))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-source-deleted-during-operation-should-error ()
- "Should error if source file is deleted during operation."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (delete-file source-file)
- (should-error (cj/--move-buffer-and-file target-dir))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-;;; Error Cases - Edge Cases
-
-(ert-deftest test-move-buffer-and-file-symlink-source-should-handle ()
- "Should handle symbolic link as source."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (real-file (expand-file-name "real.txt" source-dir))
- (symlink (expand-file-name "link.txt" source-dir))
- (target-file (expand-file-name "link.txt" target-dir)))
- (with-temp-file real-file
- (insert "content"))
- (make-symbolic-link real-file symlink)
- (find-file symlink)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(ert-deftest test-move-buffer-and-file-read-only-buffer-should-still-work ()
- "Should work even if buffer is read-only."
- (test-move-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (source-file (expand-file-name "test.txt" source-dir))
- (target-file (expand-file-name "test.txt" target-dir)))
- (with-temp-file source-file
- (insert "content"))
- (find-file source-file)
- (read-only-mode 1)
- (cj/--move-buffer-and-file target-dir)
- (should (file-exists-p target-file))
- (should-not (file-exists-p source-file))
- (kill-buffer (current-buffer)))
- (test-move-buffer-and-file-teardown)))
-
-(provide 'test-custom-buffer-file-move-buffer-and-file)
-;;; test-custom-buffer-file-move-buffer-and-file.el ends here
diff --git a/tests/test-custom-buffer-file-rename-buffer-and-file.el b/tests/test-custom-buffer-file-rename-buffer-and-file.el
deleted file mode 100644
index 1eb61f1b..00000000
--- a/tests/test-custom-buffer-file-rename-buffer-and-file.el
+++ /dev/null
@@ -1,939 +0,0 @@
-;;; test-custom-buffer-file-rename-buffer-and-file.el --- Tests for cj/--rename-buffer-and-file -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--rename-buffer-and-file function from custom-buffer-file.el
-;;
-;; This is the internal (non-interactive) implementation that renames both the
-;; current buffer and its visited file. The interactive wrapper
-;; cj/rename-buffer-and-file handles user prompting and delegates to this
-;; implementation.
-
-;;; 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 ps-print package
-(provide 'ps-print)
-
-;; Now load the actual production module
-(require 'custom-buffer-file)
-
-;;; Setup and Teardown
-
-(defun test-rename-buffer-and-file-setup ()
- "Setup for rename-buffer-and-file tests."
- (cj/create-test-base-dir))
-
-(defun test-rename-buffer-and-file-teardown ()
- "Teardown for rename-buffer-and-file tests."
- ;; Kill all buffers visiting files in test directory
- (dolist (buf (buffer-list))
- (when (buffer-file-name buf)
- (when (string-prefix-p cj/test-base-dir (buffer-file-name buf))
- (with-current-buffer buf
- (set-buffer-modified-p nil))
- (kill-buffer buf))))
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-rename-buffer-and-file-simple-rename ()
- "Should rename file in same directory."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (file-exists-p new-file))
- (should-not (file-exists-p old-file))
- (should (string= (buffer-name) "new.txt"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-different-directory ()
- "Should rename to absolute path in different directory."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "file.txt" source-dir))
- (new-file (expand-file-name "renamed.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file new-file)
- (should (file-exists-p new-file))
- (should-not (file-exists-p old-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-different-extension ()
- "Should change file extension."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "file.txt" test-dir))
- (new-file (expand-file-name "file.md" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "file.md")
- (should (file-exists-p new-file))
- (should-not (file-exists-p old-file))
- (should (string= (buffer-name) "file.md"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-preserves-content ()
- "Should preserve file content after rename."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (content "Important content\nWith multiple lines"))
- (with-temp-file old-file
- (insert content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-updates-buffer-name ()
- "Should update buffer name to match new filename."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should (string= (buffer-name) "old.txt"))
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-name) "new.txt"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-updates-buffer-file-name ()
- "Should update buffer-file-name correctly."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-file-name) new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-clears-modified-flag ()
- "Should clear modified flag after rename."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (insert "modification")
- (should (buffer-modified-p))
- (cj/--rename-buffer-and-file "new.txt")
- (should-not (buffer-modified-p))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-returns-t-on-success ()
- "Should return t when successful."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should (eq t (cj/--rename-buffer-and-file "new.txt")))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Naming
-
-(ert-deftest test-rename-buffer-and-file-unicode-in-name ()
- "Should handle Unicode characters in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "café.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "café.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-emoji-in-name ()
- "Should handle emoji characters in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "test-🎉.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "test-🎉.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-rtl-text-in-name ()
- "Should handle RTL text in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "مرحبا.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "مرحبا.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-spaces-in-name ()
- "Should handle spaces in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "my new file.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "my new file.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-special-chars-in-name ()
- "Should handle special characters in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "[test]-(1).txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "[test]-(1).txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-very-long-name ()
- "Should handle very long filename."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (long-name (concat (make-string 200 ?x) ".txt"))
- (new-file (expand-file-name long-name test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file long-name)
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-single-char-name ()
- "Should handle single character name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "x" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "x")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-multiple-dots ()
- "Should handle multiple dots in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "my.file.name.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "my.file.name.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-no-extension ()
- "Should handle files without extension."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "README" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "README")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-hidden-file ()
- "Should handle hidden files (starting with dot)."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name ".hidden" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file ".hidden")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-trailing-whitespace ()
- "Should handle trailing/leading spaces in name."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name " spaced " test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file " spaced ")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-only-changes-case ()
- "Should handle case-only rename on case-sensitive filesystems."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "test.txt" test-dir))
- (new-file (expand-file-name "TEST.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- ;; On case-insensitive systems, need ok-if-exists
- (cj/--rename-buffer-and-file "TEST.txt" t)
- (should (string= (buffer-name) "TEST.txt"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-adds-extension ()
- "Should handle adding extension to file."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "file" test-dir))
- (new-file (expand-file-name "file.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "file.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-removes-extension ()
- "Should handle removing extension from file."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "file.txt" test-dir))
- (new-file (expand-file-name "file" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "file")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-just-extension ()
- "Should handle name that is just extension."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name ".gitignore" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file ".gitignore")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Path Handling
-
-(ert-deftest test-rename-buffer-and-file-relative-path ()
- "Should handle relative path."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "file.txt" source-dir))
- (new-file (expand-file-name "renamed.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "../target/renamed.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-absolute-path ()
- "Should handle absolute path."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "file.txt" source-dir))
- (new-file (expand-file-name "renamed.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file new-file)
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-parent-directory ()
- "Should handle parent directory reference."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((parent-dir (cj/create-test-subdirectory "parent"))
- (source-dir (cj/create-test-subdirectory "parent/source"))
- (old-file (expand-file-name "file.txt" source-dir))
- (new-file (expand-file-name "renamed.txt" parent-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "../renamed.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-deeply-nested-target ()
- "Should handle deeply nested target directory."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "a/b/c/d/target"))
- (old-file (expand-file-name "file.txt" source-dir))
- (new-file (expand-file-name "renamed.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file new-file)
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-same-directory-basename-only ()
- "Should rename in same directory using just basename."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (file-exists-p new-file))
- (should-not (file-exists-p old-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-expand-tilde ()
- "Should expand tilde in path."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- ;; Use a path relative to home that we can create
- (home-test-dir (expand-file-name "temp-test-rename" "~"))
- (new-file (expand-file-name "renamed.txt" home-test-dir)))
- (make-directory home-test-dir t)
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (cj/--rename-buffer-and-file (concat "~/temp-test-rename/renamed.txt"))
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer))
- (delete-directory home-test-dir t))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Boundary Cases - File Content
-
-(ert-deftest test-rename-buffer-and-file-empty-file ()
- "Should handle empty file."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file)
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (file-exists-p new-file))
- (should (= 0 (buffer-size)))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-large-file ()
- "Should handle large file."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (large-content (make-string 100000 ?x)))
- (with-temp-file old-file
- (insert large-content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-string) large-content))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-binary-content ()
- "Should handle binary content."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.dat" test-dir))
- (new-file (expand-file-name "new.dat" test-dir))
- (binary-content (string 0 1 2 3 255 254 253)))
- (with-temp-file old-file
- (set-buffer-multibyte nil)
- (insert binary-content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.dat")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-preserves-newlines ()
- "Should preserve different newline types."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (content "Line 1\nLine 2\n\nLine 4\n"))
- (with-temp-file old-file
- (insert content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-preserves-encoding ()
- "Should preserve UTF-8 encoded content."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (content "Hello 世界 مرحبا Привет"))
- (with-temp-file old-file
- (insert content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-string) content))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Boundary Cases - Buffer State
-
-(ert-deftest test-rename-buffer-and-file-with-unsaved-changes ()
- "Should handle buffer with unsaved changes."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "original"))
- (find-file old-file)
- (insert " modified")
- (should (buffer-modified-p))
- (cj/--rename-buffer-and-file "new.txt")
- (should-not (buffer-modified-p))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-multiple-windows ()
- "Should work when buffer displayed in multiple windows."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (delete-other-windows)
- (split-window)
- (other-window 1)
- (switch-to-buffer (get-file-buffer old-file))
- (cj/--rename-buffer-and-file "new.txt")
- (should (string= (buffer-name) "new.txt"))
- (kill-buffer (current-buffer))
- (delete-other-windows))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-preserves-point ()
- "Should preserve point position."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (content "Line 1\nLine 2\nLine 3\n"))
- (with-temp-file old-file
- (insert content))
- (find-file old-file)
- (goto-char (point-min))
- (forward-line 1)
- (let ((original-point (point)))
- (cj/--rename-buffer-and-file "new.txt")
- (should (= (point) original-point)))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-preserves-mark ()
- "Should preserve mark."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (content "Line 1\nLine 2\nLine 3\n"))
- (with-temp-file old-file
- (insert content))
- (find-file old-file)
- (goto-char (point-min))
- (set-mark (point))
- (forward-line 2)
- (let ((original-mark (mark)))
- (cj/--rename-buffer-and-file "new.txt")
- (should (= (mark) original-mark)))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-read-only-buffer ()
- "Should work even with read-only buffer."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (read-only-mode 1)
- (cj/--rename-buffer-and-file "new.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Error Cases - Buffer Issues
-
-(ert-deftest test-rename-buffer-and-file-non-file-buffer-returns-nil ()
- "Should return nil when buffer not visiting file."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (with-temp-buffer
- (rename-buffer "non-file-buffer" t)
- (let ((result (cj/--rename-buffer-and-file "new.txt")))
- (should-not result)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-scratch-buffer-returns-nil ()
- "Should return nil for scratch buffer."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (with-current-buffer "*scratch*"
- (let ((result (cj/--rename-buffer-and-file "new.txt")))
- (should-not result)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-buffer-name-exists-should-error ()
- "Should error when buffer with new name exists."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (file1 (expand-file-name "file1.txt" test-dir))
- (file2 (expand-file-name "file2.txt" test-dir)))
- (with-temp-file file1
- (insert "content1"))
- (with-temp-file file2
- (insert "content2"))
- (find-file file1)
- (let ((buf1 (current-buffer)))
- (find-file file2)
- ;; Try to rename file2 to file1.txt (buffer exists)
- (should-error (cj/--rename-buffer-and-file "file1.txt"))
- (kill-buffer (current-buffer))
- (kill-buffer buf1)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-killed-buffer-should-error ()
- "Should error when operating on killed buffer."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (buf nil))
- (with-temp-file old-file
- (insert "content"))
- (setq buf (find-file old-file))
- (kill-buffer buf)
- (should-error
- (with-current-buffer buf
- (cj/--rename-buffer-and-file "new.txt"))))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Error Cases - File Conflicts
-
-(ert-deftest test-rename-buffer-and-file-target-exists-should-error-if-not-ok ()
- "Should error when target exists and ok-if-exists is nil."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir)))
- (with-temp-file old-file
- (insert "old content"))
- (with-temp-file new-file
- (insert "existing content"))
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file "new.txt" nil))
- ;; Old file should still exist since rename failed
- (should (file-exists-p old-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-target-exists-should-overwrite-if-ok ()
- "Should overwrite when target exists and ok-if-exists is t."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir))
- (old-content "old content")
- (new-content "existing content"))
- (with-temp-file old-file
- (insert old-content))
- (with-temp-file new-file
- (insert new-content))
- (find-file old-file)
- (cj/--rename-buffer-and-file "new.txt" t)
- (should (file-exists-p new-file))
- (should-not (file-exists-p old-file))
- ;; Content should be from old file
- (revert-buffer t t)
- (should (string= (buffer-string) old-content))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-source-deleted-should-error ()
- "Should error if source file deleted during operation."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (delete-file old-file)
- (should-error (cj/--rename-buffer-and-file "new.txt"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-same-name-is-noop ()
- "Should handle rename to same name as no-op."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "file.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- ;; Rename to same name with ok-if-exists
- (cj/--rename-buffer-and-file "file.txt" t)
- (should (file-exists-p old-file))
- (should (string= (buffer-name) "file.txt"))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Error Cases - Path Issues
-
-(ert-deftest test-rename-buffer-and-file-nil-name-should-error ()
- "Should error when new-name is nil."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file nil))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-empty-name-should-error ()
- "Should error when new-name is empty string."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file ""))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-nonexistent-target-dir-should-error ()
- "Should error when target directory doesn't exist."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (nonexistent-path (expand-file-name "nonexistent/new.txt" cj/test-base-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file nonexistent-path))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-target-is-directory-should-error ()
- "Should error when new-name is existing directory."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "old.txt" test-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file target-dir))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Error Cases - Permissions
-
-(ert-deftest test-rename-buffer-and-file-no-write-permission-target ()
- "Should error when target directory not writable."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "old.txt" source-dir))
- (new-file (expand-file-name "new.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (set-file-modes target-dir #o555)
- (find-file old-file)
- (should-error (cj/--rename-buffer-and-file new-file))
- (set-file-modes target-dir #o755)
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-no-delete-permission-source-dir ()
- "Should error when source directory doesn't allow deletion."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((source-dir (cj/create-test-subdirectory "source"))
- (target-dir (cj/create-test-subdirectory "target"))
- (old-file (expand-file-name "old.txt" source-dir))
- (new-file (expand-file-name "new.txt" target-dir)))
- (with-temp-file old-file
- (insert "content"))
- (find-file old-file)
- (set-file-modes source-dir #o555)
- (should-error (cj/--rename-buffer-and-file new-file))
- (set-file-modes source-dir #o755)
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-;;; Error Cases - Edge Cases
-
-(ert-deftest test-rename-buffer-and-file-symlink-source ()
- "Should handle symbolic link as source."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (real-file (expand-file-name "real.txt" test-dir))
- (symlink (expand-file-name "link.txt" test-dir))
- (new-file (expand-file-name "renamed.txt" test-dir)))
- (with-temp-file real-file
- (insert "content"))
- (make-symbolic-link real-file symlink)
- (find-file symlink)
- (cj/--rename-buffer-and-file "renamed.txt")
- (should (file-exists-p new-file))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(ert-deftest test-rename-buffer-and-file-interactive-prompts-on-conflict ()
- "Should prompt user when called interactively and file exists."
- (test-rename-buffer-and-file-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "test"))
- (old-file (expand-file-name "old.txt" test-dir))
- (new-file (expand-file-name "new.txt" test-dir))
- (prompted nil))
- (with-temp-file old-file
- (insert "old"))
- (with-temp-file new-file
- (insert "existing"))
- (find-file old-file)
- ;; Mock yes-or-no-p to capture that it was called
- (cl-letf (((symbol-function 'yes-or-no-p)
- (lambda (prompt)
- (setq prompted t)
- t))
- ((symbol-function 'read-string)
- (lambda (&rest _) "new.txt")))
- (call-interactively #'cj/rename-buffer-and-file)
- (should prompted))
- (kill-buffer (current-buffer)))
- (test-rename-buffer-and-file-teardown)))
-
-(provide 'test-custom-buffer-file-rename-buffer-and-file)
-;;; test-custom-buffer-file-rename-buffer-and-file.el ends here
diff --git a/tests/test-custom-comments-comment-block-banner.el b/tests/test-custom-comments-comment-block-banner.el
deleted file mode 100644
index 6561ebfa..00000000
--- a/tests/test-custom-comments-comment-block-banner.el
+++ /dev/null
@@ -1,228 +0,0 @@
-;;; 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
deleted file mode 100644
index 10b1a67d..00000000
--- a/tests/test-custom-comments-comment-box.el
+++ /dev/null
@@ -1,241 +0,0 @@
-;;; 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
deleted file mode 100644
index 30289625..00000000
--- a/tests/test-custom-comments-comment-heavy-box.el
+++ /dev/null
@@ -1,251 +0,0 @@
-;;; 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
deleted file mode 100644
index ca2bef06..00000000
--- a/tests/test-custom-comments-comment-inline-border.el
+++ /dev/null
@@ -1,235 +0,0 @@
-;;; 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
deleted file mode 100644
index 702a4c67..00000000
--- a/tests/test-custom-comments-comment-padded-divider.el
+++ /dev/null
@@ -1,250 +0,0 @@
-;;; 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-reformat.el b/tests/test-custom-comments-comment-reformat.el
deleted file mode 100644
index 83248aee..00000000
--- a/tests/test-custom-comments-comment-reformat.el
+++ /dev/null
@@ -1,191 +0,0 @@
-;;; test-custom-comments-comment-reformat.el --- Tests for cj/comment-reformat -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/comment-reformat function from custom-comments.el
-;;
-;; This function reformats multi-line comments into a single paragraph by:
-;; 1. Uncommenting the selected region
-;; 2. Joining lines together (via cj/join-line-or-region)
-;; 3. Re-commenting the result
-;; 4. Temporarily reducing fill-column by 3 during the join operation
-;;
-;; Dependencies:
-;; - Requires cj/join-line-or-region from custom-line-paragraph.el
-;; - We load the REAL module to test actual integration behavior
-;; - This follows our "test production code" guideline
-;; - If join-line-or-region has bugs, our tests will catch integration issues
-;;
-;; Cross-Language Testing Strategy:
-;; - Comprehensive testing in Emacs Lisp (12 tests)
-;; - Representative testing in Python and C (1 test each)
-;; - Function delegates to uncomment-region/comment-region, so we test OUR logic
-;; - 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.")
-
-;; Load the real custom-line-paragraph module (for cj/join-line-or-region)
-(require 'custom-line-paragraph)
-
-;; Now load the actual production module
-(require 'custom-comments)
-
-;;; Test Helpers
-
-(defun test-comment-reformat-in-mode (mode content-before expected-after)
- "Test comment reformatting in MODE.
-Insert CONTENT-BEFORE, select all, run cj/comment-reformat, verify EXPECTED-AFTER."
- (with-temp-buffer
- (transient-mark-mode 1) ; Enable transient-mark-mode for batch testing
- (funcall mode)
- (insert content-before)
- (mark-whole-buffer)
- (activate-mark) ; Explicitly activate the mark
- (cj/comment-reformat)
- (should (equal (string-trim (buffer-string)) (string-trim expected-after)))))
-
-;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
-
-(ert-deftest test-comment-reformat-elisp-simple-multiline ()
- "Should join multiple commented lines into one."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; Line one\n;; Line two\n;; Line three"
- ";; Line one Line two Line three"))
-
-(ert-deftest test-comment-reformat-elisp-preserves-content ()
- "Should preserve text content after reformat."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; Hello world\n;; from Emacs"
- ";; Hello world from Emacs"))
-
-(ert-deftest test-comment-reformat-elisp-restores-fill-column ()
- "Should restore fill-column after operation."
- (with-temp-buffer
- (transient-mark-mode 1)
- (emacs-lisp-mode)
- (let ((original-fill-column fill-column))
- (insert ";; Line one\n;; Line two")
- (mark-whole-buffer)
- (activate-mark)
- (cj/comment-reformat)
- (should (= fill-column original-fill-column)))))
-
-(ert-deftest test-comment-reformat-elisp-single-line ()
- "Should handle single commented line."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; Single line comment"
- ";; Single line comment"))
-
-(ert-deftest test-comment-reformat-elisp-empty-region ()
- "Should error when trying to comment empty buffer."
- (with-temp-buffer
- (transient-mark-mode 1)
- (emacs-lisp-mode)
- (mark-whole-buffer)
- (activate-mark)
- (should-error (cj/comment-reformat))))
-
-(ert-deftest test-comment-reformat-elisp-whitespace-in-comments ()
- "Should handle comments with only whitespace."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; \n;; \n;; text"
- ";; text"))
-
-(ert-deftest test-comment-reformat-elisp-unicode ()
- "Should handle unicode in comments."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; Hello 👋\n;; مرحبا café"
- ";; Hello 👋 مرحبا café"))
-
-(ert-deftest test-comment-reformat-elisp-long-text ()
- "Should handle many lines of comments."
- (test-comment-reformat-in-mode
- 'emacs-lisp-mode
- ";; Line 1\n;; Line 2\n;; Line 3\n;; Line 4\n;; Line 5"
- ";; Line 1 Line 2 Line 3 Line 4 Line 5"))
-
-(ert-deftest test-comment-reformat-elisp-indented-comments ()
- "Should handle indented comments."
- (with-temp-buffer
- (transient-mark-mode 1)
- (emacs-lisp-mode)
- (insert " ;; Indented line 1\n ;; Indented line 2")
- (mark-whole-buffer)
- (activate-mark)
- (cj/comment-reformat)
- ;; After reformatting, should still be commented
- (should (string-match-p ";;" (buffer-string)))
- ;; Content should be joined
- (should (string-match-p "line 1.*line 2" (buffer-string)))))
-
-(ert-deftest test-comment-reformat-elisp-region-at-buffer-start ()
- "Should handle region at buffer start."
- (with-temp-buffer
- (transient-mark-mode 1)
- (emacs-lisp-mode)
- (insert ";; Start line 1\n;; Start line 2\n(setq x 1)")
- (goto-char (point-min))
- (set-mark (point))
- (forward-line 2)
- (activate-mark)
- (cj/comment-reformat)
- (should (string-match-p ";; Start line 1.*Start line 2" (buffer-string)))))
-
-(ert-deftest test-comment-reformat-elisp-no-region-active ()
- "Should show message when no region selected."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Comment line")
- (deactivate-mark)
- (let ((message-log-max nil)
- (messages '()))
- ;; Capture messages
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (push (apply #'format format-string args) messages))))
- (cj/comment-reformat)
- (should (string-match-p "No region was selected" (car messages)))))))
-
-(ert-deftest test-comment-reformat-elisp-read-only-buffer ()
- "Should signal error in read-only buffer."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Line 1\n;; Line 2")
- (mark-whole-buffer)
- (read-only-mode 1)
- (should-error (cj/comment-reformat))))
-
-;;; Python Tests (Hash-based comments)
-
-(ert-deftest test-comment-reformat-python-simple ()
- "Should join Python hash comments."
- (test-comment-reformat-in-mode
- 'python-mode
- "# Line one\n# Line two"
- "# Line one Line two"))
-
-;;; C Tests (C-style comments)
-
-(ert-deftest test-comment-reformat-c-line-comments ()
- "Should join C line comments (C-mode converts to block comments)."
- (test-comment-reformat-in-mode
- 'c-mode
- "// Line one\n// Line two"
- "/* Line one Line two */"))
-
-(provide 'test-custom-comments-comment-reformat)
-;;; test-custom-comments-comment-reformat.el ends here
diff --git a/tests/test-custom-comments-comment-simple-divider.el b/tests/test-custom-comments-comment-simple-divider.el
deleted file mode 100644
index a61e6b4c..00000000
--- a/tests/test-custom-comments-comment-simple-divider.el
+++ /dev/null
@@ -1,246 +0,0 @@
-;;; 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
deleted file mode 100644
index f34329c8..00000000
--- a/tests/test-custom-comments-comment-unicode-box.el
+++ /dev/null
@@ -1,264 +0,0 @@
-;;; 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
diff --git a/tests/test-custom-comments-delete-buffer-comments.el b/tests/test-custom-comments-delete-buffer-comments.el
deleted file mode 100644
index a21386f9..00000000
--- a/tests/test-custom-comments-delete-buffer-comments.el
+++ /dev/null
@@ -1,224 +0,0 @@
-;;; test-custom-comments-delete-buffer-comments.el --- Tests for cj/delete-buffer-comments -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/delete-buffer-comments function from custom-comments.el
-;;
-;; This function deletes all comments in the current buffer by delegating to
-;; Emacs' built-in `comment-kill` function.
-;;
-;; Cross-Language Testing Strategy:
-;; --------------------------------
-;; This function works across multiple programming languages/major modes because
-;; it delegates to `comment-kill`, which respects each mode's comment syntax
-;; (comment-start, comment-end).
-;;
-;; Rather than testing exhaustively in every language (8+ languages = 100+ tests),
-;; we test strategically:
-;;
-;; 1. EXTENSIVE testing in Emacs Lisp (our primary language):
-;; - ~15 tests covering all normal/boundary/error cases
-;; - Tests edge cases: empty buffers, inline comments, unicode, etc.
-;;
-;; 2. REPRESENTATIVE testing in Python and C:
-;; - ~3 tests each proving different comment syntaxes work
-;; - Python: hash-based comments (#)
-;; - C: C-style line (//) and block (/* */) comments
-;;
-;; Why this approach?
-;; - OUR code is simple: (goto-char (point-min)) + (comment-kill ...)
-;; - We're testing OUR integration logic, not Emacs' comment-kill implementation
-;; - After proving 3 different syntaxes work, additional languages have
-;; diminishing returns (testing Emacs internals, not our code)
-;; - Avoids test suite bloat (21 tests vs 100+) while maintaining confidence
-;; - Groups languages by similarity: C-style covers C/Java/Go/JavaScript/Rust
-;;
-;; See ai-prompts/quality-engineer.org: "Testing Framework/Library Integration"
-
-;;; 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 Helper
-
-(defun test-delete-comments-in-mode (mode content-before expected-after)
- "Test comment deletion in MODE.
-Insert CONTENT-BEFORE, run cj/delete-buffer-comments, verify EXPECTED-AFTER."
- (with-temp-buffer
- (funcall mode)
- (insert content-before)
- (cj/delete-buffer-comments)
- (should (equal (string-trim (buffer-string)) (string-trim expected-after)))))
-
-;;; Emacs Lisp Tests (Primary Language - Comprehensive Coverage)
-
-(ert-deftest test-delete-comments-elisp-simple-line-comments ()
- "Should delete simple line comments in emacs-lisp-mode."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; This is a comment\n(defun foo () nil)"
- "(defun foo () nil)"))
-
-(ert-deftest test-delete-comments-elisp-inline-comments ()
- "Should delete inline/end-of-line comments."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- "(setq x 10) ;; set x to 10"
- "(setq x 10)"))
-
-(ert-deftest test-delete-comments-elisp-only-comments ()
- "Buffer with only comments should become empty."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; Comment 1\n;; Comment 2\n;; Comment 3"
- ""))
-
-(ert-deftest test-delete-comments-elisp-mixed-code-and-comments ()
- "Should preserve code and delete all comments."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; Header comment\n(defun foo ()\n ;; body comment\n (+ 1 2)) ;; inline"
- "(defun foo ()\n\n (+ 1 2))"))
-
-(ert-deftest test-delete-comments-elisp-empty-buffer ()
- "Should do nothing in empty buffer."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ""
- ""))
-
-(ert-deftest test-delete-comments-elisp-no-comments ()
- "Should preserve all content when no comments exist."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- "(defun foo ()\n (+ 1 2))"
- "(defun foo ()\n (+ 1 2))"))
-
-(ert-deftest test-delete-comments-elisp-whitespace-only-comments ()
- "Should delete comments containing only whitespace."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; \n;; \t\n(setq x 1)"
- "(setq x 1)"))
-
-(ert-deftest test-delete-comments-elisp-unicode-in-comments ()
- "Should handle unicode characters in comments."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; Hello 👋 مرحبا café\n(setq x 1)"
- "(setq x 1)"))
-
-(ert-deftest test-delete-comments-elisp-indented-comments ()
- "Should delete comments at various indentation levels."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- "(defun foo ()\n ;; indented comment\n ;; more indented\n (+ 1 2))"
- "(defun foo ()\n\n\n (+ 1 2))"))
-
-(ert-deftest test-delete-comments-elisp-special-chars-in-comments ()
- "Should handle special characters in comments."
- (test-delete-comments-in-mode
- 'emacs-lisp-mode
- ";; Special: !@#$%^&*()[]{}|\\/<>?\n(setq x 1)"
- "(setq x 1)"))
-
-(ert-deftest test-delete-comments-elisp-point-not-at-beginning ()
- "Should work regardless of initial point position."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Comment 1\n(setq x 1)\n;; Comment 2")
- (goto-char (point-max)) ; Point at end
- (cj/delete-buffer-comments)
- (should (equal (string-trim (buffer-string)) "(setq x 1)"))))
-
-(ert-deftest test-delete-comments-elisp-does-not-affect-kill-ring ()
- "Should not add deleted comments to kill-ring."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Comment\n(setq x 1)")
- (setq kill-ring nil)
- (cj/delete-buffer-comments)
- (should (null kill-ring))))
-
-(ert-deftest test-delete-comments-elisp-read-only-buffer ()
- "Should signal error in read-only buffer."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Comment\n(setq x 1)")
- (read-only-mode 1)
- (should-error (cj/delete-buffer-comments))))
-
-(ert-deftest test-delete-comments-elisp-narrowed-buffer ()
- "Should only affect visible region when narrowed."
- (with-temp-buffer
- (emacs-lisp-mode)
- (insert ";; Comment 1\n(setq x 1)\n;; Comment 2\n(setq y 2)")
- (goto-char (point-min))
- (forward-line 2)
- (narrow-to-region (point) (point-max))
- (cj/delete-buffer-comments)
- (widen)
- ;; First comment should remain (was outside narrowed region)
- ;; Second comment should be deleted
- (should (string-match-p "Comment 1" (buffer-string)))
- (should-not (string-match-p "Comment 2" (buffer-string)))))
-
-
-;;; Python Tests (Hash-based comments)
-
-(ert-deftest test-delete-comments-python-simple ()
- "Should delete Python hash comments."
- (test-delete-comments-in-mode
- 'python-mode
- "# This is a comment\ndef foo():\n return 42"
- "def foo():\n return 42"))
-
-(ert-deftest test-delete-comments-python-inline ()
- "Should delete inline Python comments."
- (test-delete-comments-in-mode
- 'python-mode
- "x = 10 # set x to 10\ny = 20"
- "x = 10\ny = 20"))
-
-(ert-deftest test-delete-comments-python-mixed ()
- "Should preserve code and delete Python comments."
- (test-delete-comments-in-mode
- 'python-mode
- "# Header\ndef foo():\n # body\n return 42 # inline"
- "def foo():\n\n return 42"))
-
-;;; C Tests (C-style line and block comments)
-
-(ert-deftest test-delete-comments-c-line-comments ()
- "Should delete C line comments (//)."
- (test-delete-comments-in-mode
- 'c-mode
- "// This is a comment\nint main() {\n return 0;\n}"
- "int main() {\n return 0;\n}"))
-
-(ert-deftest test-delete-comments-c-block-comments ()
- "Should delete C block comments (/* */)."
- (test-delete-comments-in-mode
- 'c-mode
- "/* Block comment */\nint x = 10;"
- "int x = 10;"))
-
-(ert-deftest test-delete-comments-c-mixed ()
- "Should delete both line and block comments in C."
- (test-delete-comments-in-mode
- 'c-mode
- "// Line comment\n/* Block comment */\nint x = 10; // inline"
- "int x = 10;"))
-
-(provide 'test-custom-comments-delete-buffer-comments)
-;;; test-custom-comments-delete-buffer-comments.el ends here
diff --git a/tests/test-custom-functions-join-line-or-region.el.disabled b/tests/test-custom-functions-join-line-or-region.el.disabled
deleted file mode 100644
index d694e407..00000000
--- a/tests/test-custom-functions-join-line-or-region.el.disabled
+++ /dev/null
@@ -1,84 +0,0 @@
-;;; test-custom-functions-join-line-or-region.el --- Test cj/join-line-or-region -*- lexical-binding: t; -*-
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Tests for the cj/join-line-or-region function in custom-functions.el
-
-;;; Code:
-
-(add-to-list 'load-path (concat user-emacs-directory "modules"))
-(require 'ert)
-(require 'custom-functions)
-
-
-(ert-deftest test-cj/join-line-or-region-normal-case ()
- (let* ((given "Line1\nLine2\nLine3\n")
- (expected "Line1 Line2 Line3\n")) ; Note: join-line adds newline.
- (with-temp-buffer
- (insert given)
-
- ;; Properly set and activate the region
- (push-mark (point-min) t t) ; Set mark, no message, activate
- (goto-char (point-max)) ; This creates active region from min to max
-
- ;; Call the function being tested
- (cj/join-line-or-region)
-
- ;; Perform assertions to check the expected result
- (should (equal (buffer-substring-no-properties (point-min) (point-max))
- expected)))))
-
-(ert-deftest test-cj/join-line-or-region-multiple-spaces ()
- (let* ((given "Line1\n\n\n\n\nLine2\nLine3\n")
- (expected "Line1 Line2 Line3\n")) ; Note: join-line adds newline.
- (with-temp-buffer
- (insert given)
-
- ;; Properly set and activate the region
- (push-mark (point-min) t t)
- (goto-char (point-max))
-
- ;; Call the function being tested
- (cj/join-line-or-region)
-
- ;; Perform assertions to check the expected result
- (should (equal (buffer-substring-no-properties (point-min) (point-max))
- expected)))))
-
-
-(ert-deftest test-cj/join-line-or-region-single-line ()
- (let* ((given "Line1\n")
- (expected "Line1\n")) ; Note: join-line adds newline.
- (with-temp-buffer
- (insert given)
-
- ;; push the mark mid-way on the line
- (goto-char (/ (point-max) 2))
-
- ;; Call the function being tested
- (cj/join-line-or-region)
-
- ;; Perform assertions to check the expected result
- (should (equal (buffer-substring-no-properties (point-min) (point-max))
- expected)))))
-
-(ert-deftest test-cj/join-line-or-region-nothing ()
- (let* ((given "")
- (expected "\n")) ; Note: join-line adds newline.
- (with-temp-buffer
- (insert given)
-
- ;; Properly set and activate the region
- (push-mark (point-min) t t)
- (goto-char (point-max))
-
- ;; Call the function being tested
- (cj/join-line-or-region)
-
- ;; Perform assertions to check the expected result
- (should (equal (buffer-substring-no-properties (point-min) (point-max))
- expected)))))
-
-
-(provide 'test-custom-functions.el-join-line-or-region)
-;;; test-custom-functions-join-line-or-region.el ends here.
diff --git a/tests/test-custom-line-paragraph-duplicate-line-or-region.el b/tests/test-custom-line-paragraph-duplicate-line-or-region.el
deleted file mode 100644
index bd82e00f..00000000
--- a/tests/test-custom-line-paragraph-duplicate-line-or-region.el
+++ /dev/null
@@ -1,451 +0,0 @@
-;;; 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-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
diff --git a/tests/test-custom-line-paragraph-join-line-or-region.el b/tests/test-custom-line-paragraph-join-line-or-region.el
deleted file mode 100644
index 0d28ab6c..00000000
--- a/tests/test-custom-line-paragraph-join-line-or-region.el
+++ /dev/null
@@ -1,618 +0,0 @@
-;;; test-custom-line-paragraph-join-line-or-region.el --- Tests for cj/join-line-or-region -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/join-line-or-region function from custom-line-paragraph.el
-;;
-;; IMPORTANT NOTE ON REGION ACTIVATION IN BATCH MODE:
-;; When testing functions that use (use-region-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)
-;;
-;; Without these steps, (use-region-p) will return nil even when mark and
-;; point are set, causing the function to take the no-region code path.
-;; This is a common pitfall that junior developers may miss when writing
-;; ERT tests for region-aware commands.
-
-;;; 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-join-line-or-region-setup ()
- "Setup for join-line-or-region tests."
- (cj/create-test-base-dir))
-
-(defun test-join-line-or-region-teardown ()
- "Teardown for join-line-or-region tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-join-line-or-region-no-region-joins-with-previous-line ()
- "Without region, should join current line with previous line."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "line one line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-no-region-adds-newline-after-join ()
- "Without region, should add newline after joining."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-suffix-p "\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-with-region-joins-all-lines ()
- "With region, should join all lines in region."
- (test-join-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))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "line one line two line three" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-with-region-adds-newline-at-end ()
- "With region, should add newline at end."
- (test-join-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))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-suffix-p "\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-preserves-text-content ()
- "Should preserve all text content when joining."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "hello\nworld")
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "hello.*world" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-removes-line-breaks-between-words ()
- "Should remove line breaks and add spaces between words."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "hello\nworld")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "hello world" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-multiple-lines-in-region ()
- "Should handle multiple lines in region correctly."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "one\ntwo\nthree\nfour\nfive")
- (goto-char (point-min))
- (forward-line 1)
- (set-mark (point))
- (forward-line 3)
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "two three four" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-join-line-or-region-on-first-line-no-region-does-nothing-except-newline ()
- "On first line without region, should only add newline."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line")
- (goto-char (point-min))
- (cj/join-line-or-region)
- (should (string= "only line\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-empty-lines-in-region ()
- "Should handle empty lines in region."
- (test-join-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))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "line one.*line three" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-single-line-region ()
- "Should handle single-line region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line")
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string= "only line\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-region-with-only-whitespace-lines ()
- "Should handle region with only whitespace lines."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \n \n ")
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (stringp (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-lines-with-leading-whitespace ()
- "Should handle lines with leading whitespace."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\n line two")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "line one.*line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-lines-with-trailing-whitespace ()
- "Should handle lines with trailing whitespace."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one \nline two")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "line one.*line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-lines-with-tabs ()
- "Should handle lines with tab characters."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line\tone\nline\ttwo")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "line.*one.*line.*two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-lines-with-mixed-whitespace ()
- "Should handle lines with mixed whitespace."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " line \t one \n\t line two\t")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "line.*one.*line.*two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-very-long-lines ()
- "Should handle very long lines."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((long-line (make-string 5000 ?x)))
- (insert long-line "\n" long-line)
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (= (length (buffer-string)) (+ (* 2 5000) 1 1)))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-unicode-characters ()
- "Should handle unicode characters."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "café\nnaïve")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "café.*naïve" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-emoji-content ()
- "Should handle emoji content."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "hello 👋\nworld 🌍")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "hello 👋.*world 🌍" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-rtl-text ()
- "Should handle RTL text."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "مرحبا\nعالم")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "مرحبا.*عالم" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-region-at-buffer-start ()
- "Should handle region starting at buffer beginning."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (goto-char (point-min))
- (set-mark (point))
- (forward-line 2)
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "line one line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-region-at-buffer-end ()
- "Should handle region ending at buffer end."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (goto-char (point-min))
- (forward-line 1)
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "line two line three" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-entire-buffer-as-region ()
- "Should handle entire buffer selected as region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "one\ntwo\nthree")
- (transient-mark-mode 1)
- (mark-whole-buffer)
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "one two three" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-single-character-lines ()
- "Should handle single character lines."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "a\nb\nc")
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "a b c" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-cursor-position-after-no-region ()
- "Cursor should be at end after joining without region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (= (point) (point-max))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-cursor-position-after-region ()
- "Cursor should be at region end marker after joining region."
- (test-join-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))
- (let ((end-pos (point)))
- (activate-mark)
- (cj/join-line-or-region)
- ;; Point should be near the original end position
- (should (>= (point) (- end-pos 10)))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-marker-validity-after-operation ()
- "Marker should remain valid after operation."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (let ((marker (set-marker (make-marker) (point-min))))
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (should (marker-position marker))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-backwards-region ()
- "Should handle backwards region (mark after point)."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (goto-char (point-max))
- (set-mark (point))
- (goto-char (point-min))
- (activate-mark)
- (cj/join-line-or-region)
- (should (string-match-p "line one line two line three" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-partial-line-selection ()
- "Should handle region starting mid-line and ending mid-line."
- (test-join-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 "line one"
- (set-mark (point))
- (forward-line 2)
- (forward-char 5) ; Middle of "line three"
- (activate-mark)
- (cj/join-line-or-region)
- ;; Should join lines regardless of partial selection
- (should (string-match-p "line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-stress-test-many-lines ()
- "Should handle many lines (1000+) without hanging."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (dotimes (i 1000)
- (insert (format "line %d\n" i)))
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (let ((start-time (current-time)))
- (cj/join-line-or-region)
- (let ((elapsed (float-time (time-subtract (current-time) start-time))))
- ;; Should complete in reasonable time (< 5 seconds)
- (should (< elapsed 5.0))))
- ;; Verify all lines joined
- (goto-char (point-min))
- (should (string-match-p "line 0.*line 999" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-combining-characters ()
- "Should handle Unicode combining characters."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- ;; e with combining acute accent (é)
- (insert "cafe\u0301\nnaive\u0308")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string-match-p "cafe\u0301.*naive\u0308" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-control-characters ()
- "Should handle control characters."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line\u000Cone\nline\u000Ctwo")
- (goto-char (point-max))
- (cj/join-line-or-region)
- ;; Should preserve control characters
- (should (string-match-p "line.*one.*line.*two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-narrowed-buffer ()
- "Should respect buffer narrowing."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before\nline one\nline two\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (let ((beg (point)))
- (forward-line 2)
- (narrow-to-region beg (point))
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (cj/join-line-or-region)
- (widen)
- ;; Should only affect narrowed region
- (should (string-match-p "before" (buffer-string)))
- (should (string-match-p "after" (buffer-string)))
- (should (string-match-p "line one.*line two" (buffer-string)))))
- (test-join-line-or-region-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-join-line-or-region-empty-buffer-no-region ()
- "Should handle empty buffer gracefully without region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/join-line-or-region)
- (should (string= "\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-empty-buffer-with-region ()
- "Should handle empty buffer gracefully with region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (mark-whole-buffer)
- (cj/join-line-or-region)
- (should (string= "\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-single-line-buffer-no-region ()
- "Should handle single line buffer without region."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line")
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (string= "only line\n" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-read-only-buffer-should-error ()
- "Should error when attempting to modify read-only buffer."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two")
- (goto-char (point-max))
- (read-only-mode 1)
- (should-error (cj/join-line-or-region)))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-undo-behavior ()
- "Should properly support undo after joining lines."
- (test-join-line-or-region-setup)
- (unwind-protect
- (let* ((temp-file (expand-file-name "test-undo.txt" cj/test-base-dir))
- (original-content "line one\nline two"))
- ;; 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) ; Ensure undo is enabled
- ;; Make a small change to establish undo history
- (goto-char (point-min))
- (insert " ")
- (delete-char -1)
- (undo-boundary) ; Create explicit boundary
- (goto-char (point-max))
- (let ((before-join (buffer-string)))
- (cj/join-line-or-region)
- (undo-boundary) ; Create boundary after operation
- (let ((after-join (buffer-string)))
- (should-not (string= before-join after-join))
- ;; Undo should work now
- (undo)
- (should (string= before-join (buffer-string)))))
- (kill-buffer (current-buffer)))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-buffer-modified-flag ()
- "Should set buffer modified flag after joining lines."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two")
- (set-buffer-modified-p nil)
- (goto-char (point-max))
- (cj/join-line-or-region)
- (should (buffer-modified-p)))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-region-deactivation ()
- "Should deactivate region after operation."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (transient-mark-mode 1)
- (goto-char (point-min))
- (set-mark (point))
- (goto-char (point-max))
- (activate-mark)
- (should (use-region-p))
- (cj/join-line-or-region)
- ;; Region should be deactivated after operation
- (should-not (use-region-p)))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-dos-line-endings ()
- "Should handle DOS-style line endings (CRLF) and preserve them."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\r\nline two\r\n")
- ;; Go to line two so we can join with line one
- (goto-char (point-min))
- (forward-line 1)
- (cj/join-line-or-region)
- ;; Should join lines (join-line handles the line endings)
- (should (string-match-p "line one.*line two" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(ert-deftest test-join-line-or-region-consecutive-operations ()
- "Should handle consecutive join operations correctly."
- (test-join-line-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three\nline four")
- ;; First operation: on line two, joins with line one
- (goto-char (point-min))
- (forward-line 1)
- (cj/join-line-or-region)
- (should (string-match-p "line one line two" (buffer-string)))
- ;; Second operation: on line four, joins with line three
- (goto-char (point-min))
- (search-forward "line four")
- (cj/join-line-or-region)
- (should (string-match-p "line three line four" (buffer-string)))
- ;; Both operations should have worked (note: each operation adds a newline)
- (should (string-match-p "line one line two\n+line three line four" (buffer-string))))
- (test-join-line-or-region-teardown)))
-
-(provide 'test-custom-line-paragraph-join-line-or-region)
-;;; test-custom-line-paragraph-join-line-or-region.el ends here
diff --git a/tests/test-custom-line-paragraph-join-paragraph.el b/tests/test-custom-line-paragraph-join-paragraph.el
deleted file mode 100644
index a84adc6c..00000000
--- a/tests/test-custom-line-paragraph-join-paragraph.el
+++ /dev/null
@@ -1,360 +0,0 @@
-;;; test-custom-line-paragraph-join-paragraph.el --- Tests for cj/join-paragraph -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/join-paragraph function from custom-line-paragraph.el
-;;
-;; IMPORTANT NOTE ON REGION ACTIVATION IN BATCH MODE:
-;; When testing functions that use (use-region-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)
-;;
-;; Without these steps, (use-region-p) will return nil even when mark and
-;; point are set, causing the function to take the no-region code path.
-;; This is a common pitfall that junior developers may miss when writing
-;; ERT tests for region-aware commands.
-;;
-;; The cj/join-paragraph function uses er/mark-paragraph which sets a region,
-;; so we need to ensure transient-mark-mode is enabled in our tests.
-
-;;; Code:
-
-;; Add tests directory to load path for testutil-general
-(add-to-list 'load-path (expand-file-name "tests" user-emacs-directory))
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Initialize package system to load expand-region
-(require 'package)
-(setq package-user-dir (expand-file-name "elpa" user-emacs-directory))
-(package-initialize)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Add expand-region to load path explicitly
-(add-to-list 'load-path (expand-file-name "elpa/expand-region-1.0.0" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Load expand-region for real (needed by cj/join-paragraph)
-(require 'expand-region)
-(require 'the-org-mode-expansions)
-
-;; Now load the actual production module
-(require 'custom-line-paragraph)
-
-;; -------------------------------- Test Fixtures ------------------------------
-
-(defun test-join-paragraph-setup ()
- "Set up test environment."
- (cj/create-test-base-dir))
-
-(defun test-join-paragraph-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;; ---------------------------- Normal Cases -----------------------------------
-
-(ert-deftest test-join-paragraph-simple-multiline-cursor-at-start ()
- "Join a simple 3-line paragraph with cursor at start."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "line one\nline two\nline three")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "line one line two line three\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-simple-multiline-cursor-in-middle ()
- "Join a simple 3-line paragraph with cursor in middle line."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "line one\nline two\nline three")
- (goto-char (point-min))
- (forward-line 1)
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "line one line two line three\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-simple-multiline-cursor-at-end ()
- "Join a simple 3-line paragraph with cursor at end."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "line one\nline two\nline three")
- (goto-char (point-max))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "line one line two line three\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-surrounded-by-other-paragraphs ()
- "Join only the current paragraph when surrounded by others."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "para one line one\npara one line two\n\n")
- (insert "para two line one\npara two line two\n\n")
- (insert "para three line one\npara three line two")
- ;; Position in middle paragraph
- (goto-char (point-min))
- (forward-line 3)
- (cj/join-paragraph)
- (should (string-match-p "para one line one\npara one line two"
- (buffer-string)))
- (should (string-match-p "para two line one para two line two"
- (buffer-string)))
- (should (string-match-p "para three line one\npara three line two"
- (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-with-leading-whitespace ()
- "Join paragraph with indented lines, preserving appropriate spacing."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert " indented line one\n indented line two\n indented line three")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- " indented line one indented line two indented line three\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-cursor-position-after ()
- "Verify cursor moves forward one line after joining."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "line one\nline two\nline three\n")
- (goto-char (point-min))
- (let ((initial-line (line-number-at-pos)))
- (cj/join-paragraph)
- (should (= (line-number-at-pos) (1+ initial-line)))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-multiple-paragraphs-first ()
- "Join only first paragraph when three paragraphs exist."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "first one\nfirst two\n\nsecond one\nsecond two\n\nthird one\nthird two")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string-match-p "^first one first two\n" (buffer-string)))
- (should (string-match-p "second one\nsecond two" (buffer-string)))
- (should (string-match-p "third one\nthird two" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-multiple-paragraphs-middle ()
- "Join only middle paragraph when three paragraphs exist."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "first one\nfirst two\n\nsecond one\nsecond two\n\nthird one\nthird two")
- (goto-char (point-min))
- (forward-line 3)
- (cj/join-paragraph)
- (should (string-match-p "first one\nfirst two" (buffer-string)))
- (should (string-match-p "second one second two" (buffer-string)))
- (should (string-match-p "third one\nthird two" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-multiple-paragraphs-last ()
- "Join only last paragraph when three paragraphs exist."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "first one\nfirst two\n\nsecond one\nsecond two\n\nthird one\nthird two")
- (goto-char (point-max))
- (cj/join-paragraph)
- (should (string-match-p "first one\nfirst two" (buffer-string)))
- (should (string-match-p "second one\nsecond two" (buffer-string)))
- (should (string-match-p "third one third two" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-;; ---------------------------- Boundary Cases ---------------------------------
-
-(ert-deftest test-join-paragraph-single-line-paragraph ()
- "Handle paragraph with only one line gracefully."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "single line paragraph")
- (goto-char (point-min))
- (cj/join-paragraph)
- ;; Should still work, even if nothing to join
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "single line paragraph\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-at-buffer-start ()
- "Join paragraph at very beginning of buffer."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "first line\nsecond line\nthird line\n\nother paragraph")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string-match-p "^first line second line third line\n" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-at-buffer-end ()
- "Join paragraph at very end of buffer."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "other paragraph\n\nfirst line\nsecond line\nthird line")
- (goto-char (point-max))
- (cj/join-paragraph)
- (should (string-match-p "first line second line third line\n$" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-very-long ()
- "Join paragraph with many lines (20+ lines)."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (dotimes (i 25)
- (insert (format "line %d\n" (1+ i))))
- (goto-char (point-min))
- (cj/join-paragraph)
- ;; Should have all 25 "line X" strings joined with spaces
- (should (string-match-p "line 1 line 2 line 3.*line 24 line 25" (buffer-string)))
- ;; Should not have multiple newlines in sequence
- (should-not (string-match-p "\n.*\n.*\n" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-with-blank-lines-within ()
- "Test behavior when expand-region might see internal structure."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- ;; This tests how er/mark-paragraph handles the content
- (insert "line one\n\nline two\n\nother para")
- (goto-char (point-min))
- (cj/join-paragraph)
- ;; er/mark-paragraph should mark just the first line in this case
- (should (string-match-p "^line one\n" (buffer-string))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-cursor-on-blank-line ()
- "Handle cursor positioned on blank line between paragraphs."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "para one\npara one line two\n\npara two\npara two line two")
- (goto-char (point-min))
- (forward-line 2) ;; Position on blank line
- (cj/join-paragraph)
- ;; Behavior depends on how er/mark-paragraph handles blank lines
- ;; At minimum, should not error
- (should (bufferp (current-buffer))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-no-trailing-newline ()
- "Handle paragraph at end of buffer with no trailing newline."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "line one\nline two\nline three")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "line one line two line three\n")))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-only-whitespace-lines ()
- "Handle paragraph where lines contain only spaces/tabs."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert " \n\t\t\n \t ")
- (goto-char (point-min))
- (cj/join-paragraph)
- ;; Should handle without error
- (should (bufferp (current-buffer))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-unicode-content ()
- "Handle paragraph with emoji and special Unicode characters."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "Hello 👋 world\nこんにちは 世界\n🎉 celebration 🎊")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "Hello 👋 world こんにちは 世界 🎉 celebration 🎊\n")))
- (test-join-paragraph-teardown)))
-
-;; ---------------------------- Error Cases ------------------------------------
-
-(ert-deftest test-join-paragraph-empty-buffer ()
- "Handle empty buffer without error."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- ;; Empty buffer - should handle gracefully without error
- (cj/join-paragraph)
- (should (bufferp (current-buffer))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-buffer-only-whitespace ()
- "Handle buffer containing only whitespace."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert " \n\n\t\t\n ")
- (goto-char (point-min))
- (cj/join-paragraph)
- ;; Should handle without error
- (should (bufferp (current-buffer))))
- (test-join-paragraph-teardown)))
-
-(ert-deftest test-join-paragraph-buffer-single-character ()
- "Handle buffer with minimal content."
- (test-join-paragraph-setup)
- (unwind-protect
- (with-temp-buffer
- (transient-mark-mode 1)
- (insert "x")
- (goto-char (point-min))
- (cj/join-paragraph)
- (should (string= (buffer-substring-no-properties (point-min) (point-max))
- "x\n")))
- (test-join-paragraph-teardown)))
-
-(provide 'test-custom-line-paragraph-join-paragraph)
-;;; test-custom-line-paragraph-join-paragraph.el ends here
diff --git a/tests/test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el b/tests/test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el
deleted file mode 100644
index f3fe0fdd..00000000
--- a/tests/test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el
+++ /dev/null
@@ -1,471 +0,0 @@
-;;; test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el --- Tests for cj/remove-duplicate-lines-region-or-buffer -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/remove-duplicate-lines-region-or-buffer function from custom-line-paragraph.el
-;;
-;; This function removes duplicate lines in the region or buffer, keeping the first occurrence.
-;; Operates on the active region when one exists; otherwise operates on the whole buffer.
-;;
-;; The implementation uses a regex to find duplicate lines: "^\\(.*\\)\n\\(\\(.*\n\\)*\\)\\1\n"
-;; This pattern matches a line, then any number of lines in between, then the same line again.
-;;
-;; IMPORTANT NOTE ON REGION ACTIVATION IN BATCH MODE:
-;; When testing functions that use (use-region-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.
-
-;;; 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-remove-duplicate-lines-setup ()
- "Setup for remove-duplicate-lines tests."
- (cj/create-test-base-dir))
-
-(defun test-remove-duplicate-lines-teardown ()
- "Teardown for remove-duplicate-lines tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-remove-duplicate-lines-adjacent-duplicates ()
- "Should remove adjacent duplicate lines."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline one\nline two")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "line one\nline two" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-keep-first-occurrence ()
- "Should keep first occurrence and remove subsequent duplicates."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "first\nsecond\nfirst\nthird")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "first\nsecond\nthird" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-multiple-sets ()
- "Should remove multiple different duplicated lines."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "alpha\nbeta\nalpha\ngamma\nbeta\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "alpha\nbeta\ngamma\n" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-no-duplicates ()
- "Should leave buffer unchanged when no duplicates exist."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (let ((original (buffer-string)))
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= original (buffer-string)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-region-only ()
- "Should only affect active region, leaving rest of buffer unchanged."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before\ndup\ndup\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (set-mark (point))
- (forward-line 2)
- (transient-mark-mode 1)
- (activate-mark)
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; Should have removed one "dup" but kept before and after
- (should (string-match-p "before" (buffer-string)))
- (should (string-match-p "after" (buffer-string)))
- (should (= 1 (how-many "^dup$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-whole-buffer ()
- "Should operate on entire buffer when no region active."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "one\ntwo\none\nthree\ntwo\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "one\ntwo\nthree\n" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-preserve-unique ()
- "Should preserve all unique lines intact."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "unique1\ndup\nunique2\ndup\nunique3")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string-match-p "unique1" (buffer-string)))
- (should (string-match-p "unique2" (buffer-string)))
- (should (string-match-p "unique3" (buffer-string)))
- (should (= 1 (how-many "^dup$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-separated-by-content ()
- "Should remove duplicate lines even when separated by other content."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "target\nother1\nother2\ntarget\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "^target$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-remove-duplicate-lines-empty-buffer ()
- "Should handle empty buffer gracefully."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-single-line ()
- "Should handle single line buffer (no duplicates possible)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "only line" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-two-identical-lines ()
- "Should handle minimal duplicate case of two identical lines."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "same\nsame\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "same\n" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-all-identical ()
- "Should keep only one line when all lines are identical."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "same\nsame\nsame\nsame\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "same\n" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-at-buffer-start ()
- "Should handle duplicates at beginning of buffer."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "first\nfirst\nsecond\nthird")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "first\nsecond\nthird" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-at-buffer-end ()
- "Should handle duplicates at end of buffer."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "first\nsecond\nlast\nlast\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "first\nsecond\nlast\n" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-empty-lines ()
- "Should handle duplicate empty lines."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line1\n\n\nline2")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= "line1\n\nline2" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-whitespace-only ()
- "Should handle duplicate whitespace-only lines."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \n \ntext")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= " \ntext" (buffer-string))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-very-long ()
- "Should handle very long duplicate lines (5000+ chars)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((long-line (make-string 5000 ?x)))
- (insert long-line "\n" long-line "\nshort")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many (regexp-quote long-line) (point-min) (point-max))))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-unicode-emoji ()
- "Should handle Unicode and emoji duplicates."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "hello 👋\nhello 👋\nother")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "hello 👋" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-with-tabs ()
- "Should preserve and match tab characters."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line\twith\ttabs\nline\twith\ttabs\nother")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "line\twith\ttabs" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-mixed-whitespace ()
- "Should do exact whitespace matching."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " line \t text \n line \t text \nother")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many " line \t text " (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-rtl-text ()
- "Should handle RTL text duplicates."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "مرحبا\nمرحبا\nعالم")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "مرحبا" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-case-insensitive ()
- "Should treat different cases as same line (case-insensitive by default)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line\nline\nLINE\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; Case-insensitive matching, so duplicates removed
- (should (= 1 (how-many "^[Ll][Ii][Nn][Ee]$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-trailing-whitespace-matters ()
- "Should treat trailing whitespace as significant."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line \nline\nline \n")
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; "line " appears twice, one should be removed
- (should (= 1 (how-many "line $" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-leading-whitespace-matters ()
- "Should treat leading whitespace as significant."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " line\nline\n line\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; " line" appears twice, one should be removed
- (should (= 1 (how-many "^ line$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-narrowed-buffer ()
- "Should respect buffer narrowing."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before\ndup\ndup\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (let ((beg (point)))
- (forward-line 2)
- (narrow-to-region beg (point))
- (cj/remove-duplicate-lines-region-or-buffer)
- (widen)
- ;; Should still have before and after
- (should (string-match-p "before" (buffer-string)))
- (should (string-match-p "after" (buffer-string)))
- ;; Should have removed one dup
- (should (= 1 (how-many "^dup$" (point-min) (point-max))))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-backwards-region ()
- "Should handle backwards region (mark after point)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "dup\ndup\nother")
- (goto-char (point-max))
- (set-mark (point))
- (goto-char (point-min))
- (transient-mark-mode 1)
- (activate-mark)
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "^dup$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-entire-buffer-as-region ()
- "Should handle entire buffer selected as region."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "one\ntwo\none\nthree")
- (transient-mark-mode 1)
- (mark-whole-buffer)
- (activate-mark)
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "^one$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-region-no-duplicates ()
- "Should leave region unchanged when no duplicates exist."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before\nunique1\nunique2\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (set-mark (point))
- (forward-line 2)
- (transient-mark-mode 1)
- (activate-mark)
- (let ((original (buffer-string)))
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (string= original (buffer-string)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-three-or-more ()
- "Should keep first and remove all other duplicates."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "dup\nother1\ndup\nother2\ndup\nother3\ndup\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= 1 (how-many "^dup$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-alternating-pattern ()
- "Should handle alternating duplicate pattern (A B A B)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "A\nB\nA\nB\n")
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; Should keep first A and first B, remove duplicates
- (should (= 1 (how-many "^A$" (point-min) (point-max))))
- (should (= 1 (how-many "^B$" (point-min) (point-max)))))
- (test-remove-duplicate-lines-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-remove-duplicate-lines-read-only-buffer ()
- "Should error when attempting to modify read-only buffer."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "dup\ndup\n")
- (read-only-mode 1)
- (should-error (cj/remove-duplicate-lines-region-or-buffer)))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-buffer-modified-flag ()
- "Should set buffer modified flag when duplicates removed."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "dup\ndup\n")
- (set-buffer-modified-p nil)
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (buffer-modified-p)))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-undo-behavior ()
- "Should support undo after removing duplicates."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (let* ((temp-file (expand-file-name "test-undo-rmdup.txt" cj/test-base-dir))
- (original-content "dup\ndup\nother"))
- ;; 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)
- (let ((before-remove (buffer-string)))
- (cj/remove-duplicate-lines-region-or-buffer)
- (undo-boundary)
- (let ((after-remove (buffer-string)))
- (should-not (string= before-remove after-remove))
- (undo)
- (should (string= before-remove (buffer-string)))))
- (kill-buffer (current-buffer)))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-cursor-position-preserved ()
- "Should preserve cursor position (save-excursion)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line1\ndup\nline2\ndup\nline3")
- (goto-char (point-min))
- (forward-char 3) ; Position in middle of first line
- (let ((original-pos (point)))
- (cj/remove-duplicate-lines-region-or-buffer)
- (should (= (point) original-pos))))
- (test-remove-duplicate-lines-teardown)))
-
-(ert-deftest test-remove-duplicate-lines-region-preserved ()
- "Should preserve region state (save-excursion maintains mark)."
- (test-remove-duplicate-lines-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "dup\ndup\nother\n")
- (transient-mark-mode 1)
- (mark-whole-buffer)
- (activate-mark)
- (should (use-region-p))
- (cj/remove-duplicate-lines-region-or-buffer)
- ;; save-excursion preserves mark, so region stays active
- (should (use-region-p)))
- (test-remove-duplicate-lines-teardown)))
-
-(provide 'test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer)
-;;; test-custom-line-paragraph-remove-duplicate-lines-region-or-buffer.el ends here
diff --git a/tests/test-custom-line-paragraph-remove-lines-containing.el b/tests/test-custom-line-paragraph-remove-lines-containing.el
deleted file mode 100644
index 61fab89c..00000000
--- a/tests/test-custom-line-paragraph-remove-lines-containing.el
+++ /dev/null
@@ -1,456 +0,0 @@
-;;; test-custom-line-paragraph-remove-lines-containing.el --- Tests for cj/remove-lines-containing -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/remove-lines-containing function from custom-line-paragraph.el
-;;
-;; This function removes all lines containing TEXT.
-;; If region is active, operate only on the region, otherwise on entire buffer.
-;; The operation is undoable and reports the count of removed lines.
-;;
-;; The function uses (regexp-quote text) to treat special regex characters literally.
-;;
-;; IMPORTANT NOTE ON REGION ACTIVATION IN BATCH MODE:
-;; When testing functions that use (use-region-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.
-
-;;; 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-remove-lines-containing-setup ()
- "Setup for remove-lines-containing tests."
- (cj/create-test-base-dir))
-
-(defun test-remove-lines-containing-teardown ()
- "Teardown for remove-lines-containing tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-remove-lines-containing-single-match ()
- "Should remove single line containing text."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (cj/remove-lines-containing "two")
- (should-not (string-match-p "two" (buffer-string)))
- (should (string-match-p "one" (buffer-string)))
- (should (string-match-p "three" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-multiple-matches ()
- "Should remove multiple lines containing text."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "alpha test\nbeta\ngamma test\ndelta")
- (cj/remove-lines-containing "test")
- (should-not (string-match-p "alpha" (buffer-string)))
- (should-not (string-match-p "gamma" (buffer-string)))
- (should (string-match-p "beta" (buffer-string)))
- (should (string-match-p "delta" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-preserve-non-matching ()
- "Should preserve lines not containing text."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "keep this\nremove BAD this\nkeep that\nremove BAD that")
- (cj/remove-lines-containing "BAD")
- (should (string= "keep this\nkeep that\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-region-only ()
- "Should only affect active region."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before target\ntarget middle\ntarget end\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (set-mark (point))
- (forward-line 2)
- (transient-mark-mode 1)
- (activate-mark)
- (cj/remove-lines-containing "target")
- ;; Should keep "before target" and "after"
- (should (string-match-p "before target" (buffer-string)))
- (should (string-match-p "after" (buffer-string)))
- ;; Should remove middle and end
- (should-not (string-match-p "target middle" (buffer-string)))
- (should-not (string-match-p "target end" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-whole-buffer ()
- "Should operate on entire buffer when no region active."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "one X\ntwo\nthree X\nfour")
- (cj/remove-lines-containing "X")
- (should (string= "two\nfour" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-partial-match ()
- "Should match text appearing anywhere in line."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "prefix MATCH suffix\nno match here\nMATCH at start\nat end MATCH")
- (cj/remove-lines-containing "MATCH")
- (should (string= "no match here\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-multiple-occurrences-per-line ()
- "Should remove line with text appearing multiple times."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "FOO and FOO and FOO\nbar\nFOO again")
- (cj/remove-lines-containing "FOO")
- (should (string= "bar\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-no-matches ()
- "Should leave buffer unchanged when text not found."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (let ((original (buffer-string)))
- (cj/remove-lines-containing "NOTFOUND")
- (should (string= original (buffer-string)))))
- (test-remove-lines-containing-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-remove-lines-containing-empty-buffer ()
- "Should handle empty buffer gracefully."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (cj/remove-lines-containing "anything")
- (should (string= "" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-single-line-with-match ()
- "Should remove only line when it matches."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line with TARGET")
- (cj/remove-lines-containing "TARGET")
- (should (string= "" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-single-line-without-match ()
- "Should keep only line when it doesn't match."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "only line")
- (cj/remove-lines-containing "NOTHERE")
- (should (string= "only line" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-text-at-beginning ()
- "Should match text at beginning of line."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "TARGET at start\nmiddle TARGET middle\nkeep this")
- (cj/remove-lines-containing "TARGET")
- (should (string= "keep this" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-text-at-end ()
- "Should match text at end of line."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "at end TARGET\nkeep this\nanother TARGET")
- (cj/remove-lines-containing "TARGET")
- (should (string= "keep this\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-text-in-middle ()
- "Should match text in middle of line."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "prefix TARGET suffix\nkeep\nanother TARGET here")
- (cj/remove-lines-containing "TARGET")
- (should (string= "keep\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-empty-string ()
- "Should handle empty string gracefully without hanging."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nline two\nline three")
- (let ((original (buffer-string)))
- ;; Should not hang or remove anything
- (cj/remove-lines-containing "")
- ;; Buffer should be unchanged
- (should (string= original (buffer-string)))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-whitespace-only-text ()
- "Should remove lines with specific whitespace."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "has double space\nsingle space\nhas double space again")
- (cj/remove-lines-containing " ")
- (should (string= "single space\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-very-long-line ()
- "Should handle very long lines (5000+ chars)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((long-line (concat (make-string 2500 ?x) "TARGET" (make-string 2500 ?y))))
- (insert long-line "\nshort line\n" long-line)
- (cj/remove-lines-containing "TARGET")
- (should (string= "short line\n" (buffer-string)))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-unicode-emoji ()
- "Should handle Unicode and emoji text."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "hello 👋 world\nno emoji\nbye 👋 friend")
- (cj/remove-lines-containing "👋")
- (should (string= "no emoji\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-special-regex-chars ()
- "Should treat regex special characters literally (regexp-quote)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line with .\nanother line\nline with * here\nkeep")
- (cj/remove-lines-containing ".")
- ;; Should remove only the line with literal ".", not all lines (which . would match)
- (should-not (string-match-p "line with \\." (buffer-string)))
- (should (string-match-p "another line" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-case-sensitive ()
- "Should perform case-sensitive matching."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((case-fold-search nil)) ; Ensure case-sensitive
- (insert "Line with Target\nLine with target\nLine with TARGET")
- (cj/remove-lines-containing "target")
- ;; Only lowercase "target" should match
- (should (string-match-p "Target" (buffer-string)))
- (should (string-match-p "TARGET" (buffer-string)))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-all-lines-match ()
- "Should remove all lines when every line contains text."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "X one\nX two\nX three")
- (cj/remove-lines-containing "X")
- (should (string= "" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-first-line-matches ()
- "Should handle match at buffer start."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "REMOVE first\nkeep second\nkeep third")
- (cj/remove-lines-containing "REMOVE")
- (should (string= "keep second\nkeep third" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-last-line-matches ()
- "Should handle match at buffer end."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "keep first\nkeep second\nREMOVE last")
- (cj/remove-lines-containing "REMOVE")
- (should (string= "keep first\nkeep second\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-alternating-matches ()
- "Should handle alternating matching lines."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "keep\nREMOVE\nkeep\nREMOVE\nkeep")
- (cj/remove-lines-containing "REMOVE")
- (should (string= "keep\nkeep\nkeep" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-narrowed-buffer ()
- "Should respect buffer narrowing (save-restriction)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "before TARGET\nmiddle TARGET\nend TARGET\nafter")
- (goto-char (point-min))
- (forward-line 1)
- (let ((beg (point)))
- (forward-line 2)
- (narrow-to-region beg (point))
- (cj/remove-lines-containing "TARGET")
- (widen)
- ;; Should keep "before TARGET" and "after"
- (should (string-match-p "before TARGET" (buffer-string)))
- (should (string-match-p "after" (buffer-string)))
- ;; Should remove middle and end
- (should-not (string-match-p "middle TARGET" (buffer-string)))
- (should-not (string-match-p "end TARGET" (buffer-string)))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-backwards-region ()
- "Should handle backwards region (mark after point)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "keep\nREMOVE\nREMOVE\nkeep")
- (goto-char (point-max))
- (set-mark (point))
- (goto-char (point-min))
- (forward-line 1)
- (transient-mark-mode 1)
- (activate-mark)
- (cj/remove-lines-containing "REMOVE")
- ;; Should work same as forward region
- (should (= 2 (how-many "keep" (point-min) (point-max)))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-entire-buffer-as-region ()
- "Should handle entire buffer selected as region."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "keep\nREMOVE\nkeep\nREMOVE")
- (transient-mark-mode 1)
- (mark-whole-buffer)
- (activate-mark)
- (cj/remove-lines-containing "REMOVE")
- (should (string= "keep\nkeep\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-tab-characters ()
- "Should match lines with tab characters."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line\twith\ttab\nno tabs here\nanother\ttab")
- (cj/remove-lines-containing "\t")
- (should (string= "no tabs here\n" (buffer-string))))
- (test-remove-lines-containing-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-remove-lines-containing-read-only-buffer ()
- "Should error when attempting to modify read-only buffer."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line to remove\nline to keep")
- (read-only-mode 1)
- (should-error (cj/remove-lines-containing "remove")))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-buffer-modified-flag ()
- "Should set buffer modified flag when lines removed."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "REMOVE this\nkeep this")
- (set-buffer-modified-p nil)
- (cj/remove-lines-containing "REMOVE")
- (should (buffer-modified-p)))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-undo-behavior ()
- "Should support undo after removing lines."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (let* ((temp-file (expand-file-name "test-undo-rmlines.txt" cj/test-base-dir))
- (original-content "REMOVE this\nkeep this"))
- ;; 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)
- (let ((before-remove (buffer-string)))
- (cj/remove-lines-containing "REMOVE")
- (undo-boundary)
- (let ((after-remove (buffer-string)))
- (should-not (string= before-remove after-remove))
- (undo)
- (should (string= before-remove (buffer-string)))))
- (kill-buffer (current-buffer)))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-cursor-position-preserved ()
- "Should preserve cursor position (save-excursion)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nREMOVE\nline three")
- (goto-char (point-min))
- (forward-char 5) ; Position in middle of first line
- (let ((original-pos (point)))
- (cj/remove-lines-containing "REMOVE")
- (should (= (point) original-pos))))
- (test-remove-lines-containing-teardown)))
-
-(ert-deftest test-remove-lines-containing-widen-after-narrowing ()
- "Should restore narrowing state (save-restriction)."
- (test-remove-lines-containing-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "line one\nREMOVE\nline three\nline four")
- (goto-char (point-min))
- (forward-line 1)
- (let ((beg (point)))
- (forward-line 2)
- (narrow-to-region beg (point))
- (cj/remove-lines-containing "REMOVE")
- ;; After function, narrowing should be restored
- (should (= (point-min) beg))
- (should (< (point-max) (buffer-size)))))
- (test-remove-lines-containing-teardown)))
-
-(provide 'test-custom-line-paragraph-remove-lines-containing)
-;;; test-custom-line-paragraph-remove-lines-containing.el ends here
diff --git a/tests/test-custom-line-paragraph-underscore-line.el b/tests/test-custom-line-paragraph-underscore-line.el
deleted file mode 100644
index b3c092e0..00000000
--- a/tests/test-custom-line-paragraph-underscore-line.el
+++ /dev/null
@@ -1,397 +0,0 @@
-;;; test-custom-line-paragraph-underscore-line.el --- Tests for cj/underscore-line -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/underscore-line function from custom-line-paragraph.el
-;;
-;; This function underlines the current line by inserting a row of characters below it.
-;; If the line is empty or contains only whitespace, it aborts with a message.
-;;
-;; The function uses (read-char) to get the underline character from the user.
-;; In tests, we mock this using cl-letf.
-
-;;; 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-underscore-line-setup ()
- "Setup for underscore-line tests."
- (cj/create-test-base-dir))
-
-(defun test-underscore-line-teardown ()
- "Teardown for underscore-line tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-underscore-line-simple-text ()
- "Should underline simple text line."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello World")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "Hello World\n-----------" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-preserve-original ()
- "Should preserve original line text."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Original Text")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?=)))
- (cj/underscore-line)
- (goto-char (point-min))
- (should (looking-at "Original Text"))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-use-specified-character ()
- "Should use the character provided by user."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Test")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?*)))
- (cj/underscore-line)
- (should (string-match-p "\\*\\*\\*\\*" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-match-width ()
- "Should create underline matching line width."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "12345")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Should have exactly 5 dashes
- (should (string-match-p "12345\n-----$" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-insert-newline ()
- "Should insert newline before underline."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (= 2 (count-lines (point-min) (point-max))))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-cursor-preserved ()
- "Should preserve cursor position."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Some text here")
- (goto-char (point-min))
- (forward-char 5) ; Position in middle
- (let ((original-pos (point)))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (= (point) original-pos)))))
- (test-underscore-line-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-underscore-line-empty-line-aborts ()
- "Should abort on empty line."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "")
- (cj/underscore-line)
- ;; Buffer should remain empty
- (should (string= "" (buffer-string))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-whitespace-only-aborts ()
- "Should abort on whitespace-only line."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \t ")
- (goto-char (point-min))
- (let ((original (buffer-string)))
- (cj/underscore-line)
- ;; Buffer should be unchanged
- (should (string= original (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-single-character ()
- "Should underline single character line."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "X")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string= "X\n-" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-very-long-line ()
- "Should handle very long lines (5000+ chars)."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((long-line (make-string 5000 ?x)))
- (insert long-line)
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Should have 5000 dashes
- (should (= 5000 (how-many "-" (point-min) (point-max)))))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-with-tabs ()
- "Should account for tab expansion in column width."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "a\tb")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Tab expands to column, underline should match visual width
- (let ((underline-length (save-excursion
- (goto-char (point-min))
- (forward-line 1)
- (- (line-end-position) (line-beginning-position)))))
- (should (> underline-length 2))))) ; More than just "a" and "b"
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-leading-whitespace ()
- "Should include leading whitespace in width calculation."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " text")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Should have 6 dashes (2 spaces + 4 chars)
- (should (string= " text\n------" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-trailing-whitespace ()
- "Should include trailing whitespace in width calculation."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "text ")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Should have 6 dashes (4 chars + 2 spaces)
- (should (string= "text \n------" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-unicode-emoji ()
- "Should handle Unicode and emoji characters."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- ;; Should create underline
- (should (string-match-p "Hello 👋\n-" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-rtl-text ()
- "Should handle RTL text."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "مرحبا")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "مرحبا\n-" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-combining-characters ()
- "Should handle Unicode combining characters."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "cafe\u0301") ; e with combining acute
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "cafe\u0301\n-" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-at-buffer-start ()
- "Should work on first line in buffer."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "First line\nSecond line")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?=)))
- (cj/underscore-line)
- (should (string-match-p "First line\n==========" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-at-buffer-end ()
- "Should work on last line in buffer."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "First line\nLast line")
- (goto-char (point-max))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?=)))
- (cj/underscore-line)
- (should (string-match-p "Last line\n=========$" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-different-characters ()
- "Should work with various underline characters."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Test")
- (goto-char (point-min))
- (dolist (char '(?- ?= ?* ?# ?~ ?_))
- (goto-char (point-min))
- (delete-region (point-min) (point-max))
- (insert "Test")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) char)))
- (cj/underscore-line)
- (should (string-match-p (regexp-quote (make-string 4 char)) (buffer-string))))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-special-characters ()
- "Should work with special non-alphanumeric characters."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Text")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?@)))
- (cj/underscore-line)
- (should (string-match-p "@@@@" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-cursor-in-middle ()
- "Should work regardless of cursor position on line."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello World")
- (goto-char (point-min))
- (forward-char 6) ; Position after "Hello "
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "Hello World\n-----------" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-cursor-at-start ()
- "Should work when cursor at line beginning."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Text")
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "Text\n----" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-cursor-at-end ()
- "Should work when cursor at line end."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Text")
- (goto-char (point-max))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (string-match-p "Text\n----" (buffer-string)))))
- (test-underscore-line-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-underscore-line-read-only-buffer ()
- "Should error when attempting to modify read-only buffer."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Read only text")
- (goto-char (point-min))
- (read-only-mode 1)
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (should-error (cj/underscore-line))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-buffer-modified-flag ()
- "Should set buffer modified flag."
- (test-underscore-line-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Text")
- (set-buffer-modified-p nil)
- (goto-char (point-min))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line)
- (should (buffer-modified-p))))
- (test-underscore-line-teardown)))
-
-(ert-deftest test-underscore-line-undo-behavior ()
- "Should support undo after underlining."
- (test-underscore-line-setup)
- (unwind-protect
- (let* ((temp-file (expand-file-name "test-undo-underline.txt" cj/test-base-dir))
- (original-content "Test line"))
- ;; 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-underline (buffer-string)))
- (cl-letf (((symbol-function 'read-char) (lambda (&rest _) ?-)))
- (cj/underscore-line))
- (undo-boundary)
- (let ((after-underline (buffer-string)))
- (should-not (string= before-underline after-underline))
- (undo)
- (should (string= before-underline (buffer-string)))))
- (kill-buffer (current-buffer)))
- (test-underscore-line-teardown)))
-
-(provide 'test-custom-line-paragraph-underscore-line)
-;;; test-custom-line-paragraph-underscore-line.el ends here
diff --git a/tests/test-custom-misc-cj--count-characters.el b/tests/test-custom-misc-cj--count-characters.el
deleted file mode 100644
index 1834b5c4..00000000
--- a/tests/test-custom-misc-cj--count-characters.el
+++ /dev/null
@@ -1,171 +0,0 @@
-;;; test-custom-misc-cj--count-characters.el --- Tests for cj/--count-characters -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--count-characters internal implementation function from custom-misc.el
-;;
-;; This internal function counts characters between START and END positions.
-;; It validates that START is not greater than END and returns the character count.
-
-;;; Code:
-
-(require 'ert)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Setup and Teardown
-
-(defun test-count-characters-setup ()
- "Set up test environment."
- ;; No setup needed for this function
- nil)
-
-(defun test-count-characters-teardown ()
- "Clean up test environment."
- ;; No teardown needed for this function
- nil)
-
-;;; Normal Cases
-
-(ert-deftest test-custom-misc-cj--count-characters-normal-simple-text-returns-count ()
- "Should count characters in simple text region."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (let ((result (cj/--count-characters 1 14)))
- (should (= result 13))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-normal-partial-region-returns-count ()
- "Should count characters in partial region."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (let ((result (cj/--count-characters 1 6)))
- (should (= result 5))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-normal-multiline-returns-count ()
- "Should count characters including newlines."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- ;; 6 + 1 + 6 + 1 + 6 = 20 characters
- (let ((result (cj/--count-characters (point-min) (point-max))))
- (should (= result 20))))
- (test-count-characters-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-empty-region-returns-zero ()
- "Should return 0 for empty region (start equals end)."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (let ((result (cj/--count-characters 3 3)))
- (should (= result 0))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-single-character-returns-one ()
- "Should return 1 for single character region."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- (let ((result (cj/--count-characters 1 2)))
- (should (= result 1))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-large-region-returns-count ()
- "Should handle very large region."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((large-content (make-string 100000 ?x)))
- (insert large-content)
- (let ((result (cj/--count-characters (point-min) (point-max))))
- (should (= result 100000)))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-unicode-returns-count ()
- "Should count unicode characters (emoji, RTL text, combining characters)."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- ;; "Hello 👋 مرحبا" contains emoji and Arabic text
- (insert "Hello 👋 مرحبا")
- (let ((result (cj/--count-characters (point-min) (point-max))))
- ;; Count the actual characters in the buffer
- (should (= result (- (point-max) (point-min))))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-whitespace-only-returns-count ()
- "Should count whitespace characters."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \t\n ")
- ;; 3 spaces + 1 tab + 1 newline + 2 spaces = 7 characters
- (let ((result (cj/--count-characters (point-min) (point-max))))
- (should (= result 7))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-newlines-at-boundaries-returns-count ()
- "Should count newlines at start and end."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "\n\nHello\n\n")
- ;; 2 newlines + 5 chars + 2 newlines = 9 characters
- (let ((result (cj/--count-characters (point-min) (point-max))))
- (should (= result 9))))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-boundary-binary-content-returns-count ()
- "Should handle binary content."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert (string 0 1 2 255))
- (let ((result (cj/--count-characters (point-min) (point-max))))
- (should (= result 4))))
- (test-count-characters-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-custom-misc-cj--count-characters-error-start-greater-than-end-signals-error ()
- "Should signal error when start is greater than end."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- (should-error (cj/--count-characters 10 5)
- :type 'error))
- (test-count-characters-teardown)))
-
-(ert-deftest test-custom-misc-cj--count-characters-error-positions-out-of-bounds-handled ()
- "Should handle positions beyond buffer bounds (Emacs handles this)."
- (test-count-characters-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello")
- ;; Emacs will error if positions are truly out of bounds,
- ;; but this tests that our function doesn't add additional errors
- ;; Buffer has 6 positions (1-6), testing valid bounds
- (let ((result (cj/--count-characters 1 6)))
- (should (= result 5))))
- (test-count-characters-teardown)))
-
-(provide 'test-custom-misc-cj--count-characters)
-;;; test-custom-misc-cj--count-characters.el ends here
diff --git a/tests/test-custom-misc-cj-count-characters-buffer-or-region.el b/tests/test-custom-misc-cj-count-characters-buffer-or-region.el
deleted file mode 100644
index dbbda00d..00000000
--- a/tests/test-custom-misc-cj-count-characters-buffer-or-region.el
+++ /dev/null
@@ -1,231 +0,0 @@
-;;; test-custom-misc-cj-count-characters-buffer-or-region.el --- Tests for cj/count-characters-buffer-or-region -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/count-characters-buffer-or-region function from custom-misc.el
-;;
-;; This function counts characters in the active region or the entire buffer
-;; if no region is active. It displays the count in the minibuffer.
-
-;;; Code:
-
-(require 'ert)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Setup and Teardown
-
-(defun test-count-characters-buffer-or-region-setup ()
- "Set up test environment."
- ;; No setup needed
- nil)
-
-(defun test-count-characters-buffer-or-region-teardown ()
- "Clean up test environment."
- ;; Clear any active region
- (when (use-region-p)
- (deactivate-mark)))
-
-;;; Normal Cases
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-whole-buffer-counts-all ()
- "Should count all characters in buffer when no region is active."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- ;; Ensure no region is active
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p "13 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-active-region-counts-region ()
- "Should count characters in active region."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- ;; Select "Hello" (positions 1-6)
- (goto-char 1)
- (push-mark 1)
- (goto-char 6)
- (activate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p "5 characters.*region" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-multiline-buffer-counts-all ()
- "Should count characters including newlines in buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- ;; 6 + 1 + 6 + 1 + 6 = 20 characters
- (should (string-match-p "20 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-normal-multiline-region-counts-region ()
- "Should count characters including newlines in region."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3")
- ;; Select first two lines including newlines
- (goto-char 1)
- (push-mark 1)
- (goto-char 14) ; After "Line 1\nLine 2"
- (activate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- ;; "Line 1\nLine 2" = 6 + 1 + 6 = 13 characters
- (should (string-match-p "13 characters.*region" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-empty-buffer-returns-zero ()
- "Should return 0 for empty buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p "0 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-empty-region-counts-buffer ()
- "Should count whole buffer when region is empty (point equals mark).
-When mark and point are at the same position, use-region-p returns nil,
-so the function correctly falls back to counting the entire buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello, world!")
- ;; Create empty region (point equals mark)
- ;; Even with activate-mark, use-region-p returns nil when mark == point
- (goto-char 5)
- (push-mark 5)
- (activate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- ;; Should count the whole buffer (13 characters) not the empty region
- (should (string-match-p "13 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-large-buffer-counts-all ()
- "Should handle very large buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (let ((large-content (make-string 100000 ?x)))
- (insert large-content)
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p "100000 characters.*buffer" message-output))))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-unicode-counts-correctly ()
- "Should count unicode characters (emoji, RTL text) correctly."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Hello 👋 مرحبا")
- (deactivate-mark)
- (let ((message-output nil)
- (expected-count (- (point-max) (point-min))))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p (format "%d characters.*buffer" expected-count)
- message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-whitespace-only-counts-whitespace ()
- "Should count whitespace characters."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert " \t\n ")
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- ;; 3 spaces + 1 tab + 1 newline + 2 spaces = 7 characters
- (should (string-match-p "7 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-single-character-returns-one ()
- "Should return 1 for single character buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "x")
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- (should (string-match-p "1 character.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(ert-deftest test-custom-misc-cj-count-characters-buffer-or-region-boundary-narrowed-buffer-counts-visible ()
- "Should count only visible characters in narrowed buffer."
- (test-count-characters-buffer-or-region-setup)
- (unwind-protect
- (with-temp-buffer
- (insert "Line 1\nLine 2\nLine 3\n")
- (goto-char (point-min))
- (forward-line 1)
- (narrow-to-region (point) (progn (forward-line 1) (point)))
- (deactivate-mark)
- (let ((message-output nil))
- (cl-letf (((symbol-function 'message)
- (lambda (format-string &rest args)
- (setq message-output (apply #'format format-string args)))))
- (cj/count-characters-buffer-or-region)
- ;; "Line 2\n" = 7 characters
- (should (string-match-p "7 characters.*buffer" message-output)))))
- (test-count-characters-buffer-or-region-teardown)))
-
-(provide 'test-custom-misc-cj-count-characters-buffer-or-region)
-;;; test-custom-misc-cj-count-characters-buffer-or-region.el ends here
diff --git a/tests/test-custom-misc-count-words.el b/tests/test-custom-misc-count-words.el
deleted file mode 100644
index f2bf793f..00000000
--- a/tests/test-custom-misc-count-words.el
+++ /dev/null
@@ -1,148 +0,0 @@
-;;; test-custom-misc-count-words.el --- Tests for cj/--count-words -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--count-words function from custom-misc.el
-;;
-;; This function counts words in a region using Emacs's built-in count-words.
-;; A word is defined by Emacs's word boundaries, which generally means
-;; sequences of word-constituent characters separated by whitespace or punctuation.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--count-words) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Test Helpers
-
-(defun test-count-words (input-text)
- "Test cj/--count-words on INPUT-TEXT.
-Returns the word count."
- (with-temp-buffer
- (insert input-text)
- (cj/--count-words (point-min) (point-max))))
-
-;;; Normal Cases
-
-(ert-deftest test-count-words-multiple-words ()
- "Should count multiple words."
- (should (= 5 (test-count-words "The quick brown fox jumps"))))
-
-(ert-deftest test-count-words-single-word ()
- "Should count single word."
- (should (= 1 (test-count-words "hello"))))
-
-(ert-deftest test-count-words-with-punctuation ()
- "Should count words with punctuation."
- (should (= 5 (test-count-words "Hello, world! How are you?"))))
-
-(ert-deftest test-count-words-multiple-spaces ()
- "Should count words separated by multiple spaces."
- (should (= 3 (test-count-words "hello world test"))))
-
-(ert-deftest test-count-words-with-newlines ()
- "Should count words across newlines."
- (should (= 6 (test-count-words "line one\nline two\nline three"))))
-
-(ert-deftest test-count-words-with-tabs ()
- "Should count words separated by tabs."
- (should (= 3 (test-count-words "hello\tworld\ttest"))))
-
-(ert-deftest test-count-words-mixed-whitespace ()
- "Should count words with mixed whitespace."
- (should (= 4 (test-count-words "hello \t world\n\ntest end"))))
-
-(ert-deftest test-count-words-hyphenated ()
- "Should count hyphenated words."
- ;; Emacs treats hyphens as word separators in count-words
- (should (= 7 (test-count-words "This is state-of-the-art technology"))))
-
-(ert-deftest test-count-words-contractions ()
- "Should count contractions."
- ;; Emacs treats apostrophes as word separators in count-words
- (should (= 6 (test-count-words "don't can't won't"))))
-
-(ert-deftest test-count-words-numbers ()
- "Should count numbers as words."
- (should (= 6 (test-count-words "The year 2024 has 365 days"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-count-words-empty-string ()
- "Should return 0 for empty string."
- (should (= 0 (test-count-words ""))))
-
-(ert-deftest test-count-words-only-whitespace ()
- "Should return 0 for whitespace-only text."
- (should (= 0 (test-count-words " \t\n\n "))))
-
-(ert-deftest test-count-words-only-punctuation ()
- "Should count punctuation-only text."
- ;; Emacs may count consecutive punctuation as a word
- (should (= 1 (test-count-words "!@#$%^&*()"))))
-
-(ert-deftest test-count-words-leading-trailing-spaces ()
- "Should count words ignoring leading/trailing spaces."
- (should (= 3 (test-count-words " hello world test "))))
-
-(ert-deftest test-count-words-unicode ()
- "Should count Unicode words."
- (should (= 3 (test-count-words "café résumé naïve"))))
-
-(ert-deftest test-count-words-very-long-text ()
- "Should handle very long text."
- (let ((long-text (mapconcat (lambda (_) "word") (make-list 1000 nil) " ")))
- (should (= 1000 (test-count-words long-text)))))
-
-(ert-deftest test-count-words-multiline-paragraph ()
- "Should count words in multi-line paragraph."
- (let ((text "This is a paragraph
-that spans multiple
-lines with various
-words in it."))
- (should (= 13 (test-count-words text)))))
-
-;;; Error Cases
-
-(ert-deftest test-count-words-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--count-words (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-count-words-empty-region ()
- "Should return 0 for empty region (start == end)."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (= 0 (cj/--count-words pos pos))))))
-
-(ert-deftest test-count-words-partial-region ()
- "Should count words only in specified region."
- (with-temp-buffer
- (insert "one two three four five")
- ;; Count only "two three four" (positions roughly in middle)
- (goto-char (point-min))
- (search-forward "two")
- (let ((start (match-beginning 0)))
- (search-forward "four")
- (let ((end (match-end 0)))
- (should (= 3 (cj/--count-words start end)))))))
-
-(provide 'test-custom-misc-count-words)
-;;; test-custom-misc-count-words.el ends here
diff --git a/tests/test-custom-misc-format-region.el b/tests/test-custom-misc-format-region.el
deleted file mode 100644
index c40a8898..00000000
--- a/tests/test-custom-misc-format-region.el
+++ /dev/null
@@ -1,161 +0,0 @@
-;;; test-custom-misc-format-region.el --- Tests for cj/--format-region -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--format-region function from custom-misc.el
-;;
-;; This function reformats text by applying three operations:
-;; 1. untabify - converts tabs to spaces
-;; 2. indent-region - reindents according to major mode
-;; 3. delete-trailing-whitespace - removes trailing whitespace
-;;
-;; Note: indent-region behavior is major-mode dependent. We test in
-;; emacs-lisp-mode and fundamental-mode for predictable results.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--format-region)
-;; to avoid mocking region selection. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Test Helpers
-
-(defun test-format-region (input-text &optional mode)
- "Test cj/--format-region on INPUT-TEXT.
-MODE is the major mode to use (defaults to fundamental-mode).
-Returns the buffer string after operation."
- (with-temp-buffer
- (funcall (or mode #'fundamental-mode))
- (insert input-text)
- (cj/--format-region (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases - Tab Conversion
-
-(ert-deftest test-format-region-converts-tabs ()
- "Should convert tabs to spaces."
- (let ((result (test-format-region "hello\tworld")))
- (should-not (string-match-p "\t" result))
- (should (string-match-p " " result))))
-
-(ert-deftest test-format-region-multiple-tabs ()
- "Should convert multiple tabs."
- (let ((result (test-format-region "\t\thello\t\tworld\t\t")))
- (should-not (string-match-p "\t" result))))
-
-;;; Normal Cases - Trailing Whitespace
-
-(ert-deftest test-format-region-removes-trailing-spaces ()
- "Should remove trailing spaces."
- (let ((result (test-format-region "hello world ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-format-region-removes-trailing-tabs ()
- "Should remove trailing tabs."
- (let ((result (test-format-region "hello world\t\t")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-format-region-removes-trailing-mixed ()
- "Should remove trailing mixed whitespace."
- (let ((result (test-format-region "hello world \t \t ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-format-region-multiline-trailing ()
- "Should remove trailing whitespace from multiple lines."
- (let ((result (test-format-region "line1 \nline2\t\t\nline3 \t ")))
- (should (string= result "line1\nline2\nline3"))))
-
-;;; Normal Cases - Combined Operations
-
-(ert-deftest test-format-region-tabs-and-trailing ()
- "Should handle both tabs and trailing whitespace."
- (let ((result (test-format-region "\thello\tworld\t\t")))
- (should-not (string-match-p "\t" result))
- ;; Should not end with whitespace
- (should-not (string-match-p "[ \t]+$" result))))
-
-(ert-deftest test-format-region-preserves-interior-spaces ()
- "Should preserve interior spaces while fixing edges."
- (let ((result (test-format-region "\thello world\t")))
- (should (string-match-p "hello world" result))
- (should-not (string-match-p "\t" result))))
-
-;;; Normal Cases - Indentation (Mode-Specific)
-
-(ert-deftest test-format-region-elisp-indentation ()
- "Should reindent Elisp code."
- (let* ((input "(defun foo ()\n(+ 1 2))")
- (result (test-format-region input #'emacs-lisp-mode))
- (lines (split-string result "\n")))
- ;; The inner form should be indented - second line should start with 2 spaces
- (should (= 2 (length lines)))
- (should (string-prefix-p "(defun foo ()" (car lines)))
- (should (string-prefix-p " " (cadr lines)))))
-
-;;; Boundary Cases
-
-(ert-deftest test-format-region-empty-string ()
- "Should handle empty string."
- (let ((result (test-format-region "")))
- (should (string= result ""))))
-
-(ert-deftest test-format-region-no-issues ()
- "Should handle text with no formatting issues (no-op)."
- (let ((result (test-format-region "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-format-region-only-whitespace ()
- "Should handle text with only whitespace."
- (let ((result (test-format-region "\t \t ")))
- ;; Should become empty or just spaces, no tabs
- (should-not (string-match-p "\t" result))))
-
-(ert-deftest test-format-region-single-line ()
- "Should handle single line."
- (let ((result (test-format-region "\thello\t")))
- (should-not (string-match-p "\t" result))))
-
-(ert-deftest test-format-region-very-long-text ()
- "Should handle very long text."
- (let* ((long-text (mapconcat (lambda (_) "\thello\t") (make-list 100 nil) "\n"))
- (result (test-format-region long-text)))
- (should-not (string-match-p "\t" result))))
-
-(ert-deftest test-format-region-newlines-preserved ()
- "Should preserve newlines while fixing formatting."
- (let ((result (test-format-region "line1\t \nline2\t \nline3\t ")))
- (should (= 2 (cl-count ?\n result)))))
-
-;;; Error Cases
-
-(ert-deftest test-format-region-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--format-region (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-format-region-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--format-region pos pos)
- ;; Should complete without error
- (should (string= (buffer-string) "hello world")))))
-
-(provide 'test-custom-misc-format-region)
-;;; test-custom-misc-format-region.el ends here
diff --git a/tests/test-custom-misc-jump-to-matching-paren.el b/tests/test-custom-misc-jump-to-matching-paren.el
deleted file mode 100644
index 973b6dfa..00000000
--- a/tests/test-custom-misc-jump-to-matching-paren.el
+++ /dev/null
@@ -1,197 +0,0 @@
-;;; test-custom-misc-jump-to-matching-paren.el --- Tests for cj/jump-to-matching-paren -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/jump-to-matching-paren function from custom-misc.el
-;;
-;; This function jumps to matching delimiters using Emacs's sexp navigation.
-;; It works with any delimiter that has matching syntax according to the
-;; current syntax table (parentheses, brackets, braces, etc.).
-;;
-;; Unlike other functions in this test suite, this is an INTERACTIVE function
-;; that moves point and displays messages. We test it as an integration test
-;; by setting up buffers, positioning point, calling the function, and
-;; verifying where point ends up.
-;;
-;; Key behaviors:
-;; - When on opening delimiter: jump forward to matching closing delimiter
-;; - When on closing delimiter: jump backward to matching opening delimiter
-;; - When just after closing delimiter: jump backward to matching opening
-;; - When not on delimiter: display message, don't move
-;; - When no matching delimiter: display error message, don't move
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Test Helpers
-
-(defun test-jump-to-matching-paren (text point-position)
- "Test cj/jump-to-matching-paren with TEXT and point at POINT-POSITION.
-Returns the new point position after calling the function.
-POINT-POSITION is 1-indexed (1 = first character)."
- (with-temp-buffer
- (emacs-lisp-mode) ; Use elisp mode for proper syntax table
- (insert text)
- (goto-char point-position)
- (cj/jump-to-matching-paren)
- (point)))
-
-;;; Normal Cases - Forward Jump (Opening to Closing)
-
-(ert-deftest test-jump-paren-forward-simple ()
- "Should jump forward from opening paren to closing paren."
- ;; Text: "(hello)"
- ;; Start at position 1 (on opening paren)
- ;; Should end at position 8 (after closing paren)
- (should (= 8 (test-jump-to-matching-paren "(hello)" 1))))
-
-(ert-deftest test-jump-paren-forward-nested ()
- "Should jump forward over nested parens."
- ;; Text: "(foo (bar))"
- ;; Start at position 1 (on outer opening paren)
- ;; Should end at position 12 (after outer closing paren)
- (should (= 12 (test-jump-to-matching-paren "(foo (bar))" 1))))
-
-(ert-deftest test-jump-paren-forward-inner-nested ()
- "Should jump forward from inner opening paren."
- ;; Text: "(foo (bar))"
- ;; Start at position 6 (on inner opening paren)
- ;; Should end at position 11 (after inner closing paren)
- (should (= 11 (test-jump-to-matching-paren "(foo (bar))" 6))))
-
-(ert-deftest test-jump-bracket-forward ()
- "Should jump forward from opening bracket."
- ;; Text: "[1 2 3]"
- ;; Start at position 1
- ;; Should end at position 8
- (should (= 8 (test-jump-to-matching-paren "[1 2 3]" 1))))
-
-;; Note: Braces are not treated as matching delimiters in emacs-lisp-mode
-;; so we don't test them here
-
-;;; Normal Cases - Backward Jump (Closing to Opening)
-
-(ert-deftest test-jump-paren-backward-simple ()
- "Should jump backward from closing paren to opening paren."
- ;; Text: "(hello)"
- ;; Start at position 7 (on closing paren)
- ;; Should end at position 2 (after opening paren)
- (should (= 2 (test-jump-to-matching-paren "(hello)" 7))))
-
-(ert-deftest test-jump-paren-backward-nested ()
- "Should jump backward over nested parens from after outer closing."
- ;; Text: "(foo (bar))"
- ;; Start at position 12 (after outer closing paren)
- ;; backward-sexp goes back to before opening paren
- (should (= 1 (test-jump-to-matching-paren "(foo (bar))" 12))))
-
-(ert-deftest test-jump-paren-backward-inner-nested ()
- "Should jump backward from inner closing paren."
- ;; Text: "(foo (bar))"
- ;; Start at position 10 (on inner closing paren)
- ;; Should end at position 7 (after inner opening paren)
- (should (= 7 (test-jump-to-matching-paren "(foo (bar))" 10))))
-
-(ert-deftest test-jump-bracket-backward ()
- "Should jump backward from after closing bracket."
- ;; Text: "[1 2 3]"
- ;; Start at position 8 (after ])
- ;; backward-sexp goes back one sexp
- (should (= 1 (test-jump-to-matching-paren "[1 2 3]" 8))))
-
-;;; Normal Cases - Jump from After Closing Delimiter
-
-(ert-deftest test-jump-paren-after-closing ()
- "Should jump backward when just after closing paren."
- ;; Text: "(hello)"
- ;; Start at position 8 (after closing paren)
- ;; backward-sexp goes back one sexp, ending before the opening paren
- (should (= 1 (test-jump-to-matching-paren "(hello)" 8))))
-
-;;; Boundary Cases - No Movement
-
-(ert-deftest test-jump-paren-not-on-delimiter ()
- "Should not move when not on delimiter."
- ;; Text: "(hello world)"
- ;; Start at position 3 (on 'e' in hello)
- ;; Should stay at position 3
- (should (= 3 (test-jump-to-matching-paren "(hello world)" 3))))
-
-(ert-deftest test-jump-paren-on-whitespace ()
- "Should not move when on whitespace."
- ;; Text: "(hello world)"
- ;; Start at position 7 (on space)
- ;; Should stay at position 7
- (should (= 7 (test-jump-to-matching-paren "(hello world)" 7))))
-
-;;; Boundary Cases - Unmatched Delimiters
-
-(ert-deftest test-jump-paren-unmatched-opening ()
- "Should not move from unmatched opening paren."
- ;; Text: "(hello"
- ;; Start at position 1 (on opening paren with no closing)
- ;; Should stay at position 1
- (should (= 1 (test-jump-to-matching-paren "(hello" 1))))
-
-(ert-deftest test-jump-paren-unmatched-closing ()
- "Should move to beginning from unmatched closing paren."
- ;; Text: "hello)"
- ;; Start at position 6 (on closing paren with no opening)
- ;; backward-sexp with unmatched closing paren goes to beginning
- (should (= 1 (test-jump-to-matching-paren "hello)" 6))))
-
-;;; Boundary Cases - Empty Delimiters
-
-(ert-deftest test-jump-paren-empty ()
- "Should jump over empty parens."
- ;; Text: "()"
- ;; Start at position 1
- ;; Should end at position 3
- (should (= 3 (test-jump-to-matching-paren "()" 1))))
-
-(ert-deftest test-jump-paren-empty-backward ()
- "Should stay put when on closing paren of empty parens."
- ;; Text: "()"
- ;; Start at position 2 (on closing paren)
- ;; backward-sexp from closing of empty parens gives an error, so stays at 2
- (should (= 2 (test-jump-to-matching-paren "()" 2))))
-
-;;; Boundary Cases - Multiple Delimiter Types
-
-(ert-deftest test-jump-paren-mixed-delimiters ()
- "Should jump over mixed delimiter types."
- ;; Text: "(foo [bar {baz}])"
- ;; Start at position 1 (on opening paren)
- ;; Should end at position 18 (after closing paren)
- (should (= 18 (test-jump-to-matching-paren "(foo [bar {baz}])" 1))))
-
-(ert-deftest test-jump-bracket-in-parens ()
- "Should jump from bracket inside parens."
- ;; Text: "(foo [bar])"
- ;; Start at position 6 (on opening bracket)
- ;; Should end at position 11 (after closing bracket)
- (should (= 11 (test-jump-to-matching-paren "(foo [bar])" 6))))
-
-;;; Complex Cases - Strings and Comments
-
-(ert-deftest test-jump-paren-over-string ()
- "Should jump over parens containing strings."
- ;; Text: "(\"hello (world)\")"
- ;; Start at position 1 (on opening paren)
- ;; Should end at position 18 (after closing paren)
- ;; The parens in the string should be ignored
- (should (= 18 (test-jump-to-matching-paren "(\"hello (world)\")" 1))))
-
-(provide 'test-custom-misc-jump-to-matching-paren)
-;;; test-custom-misc-jump-to-matching-paren.el ends here
diff --git a/tests/test-custom-misc-replace-fraction-glyphs.el b/tests/test-custom-misc-replace-fraction-glyphs.el
deleted file mode 100644
index 81d1546e..00000000
--- a/tests/test-custom-misc-replace-fraction-glyphs.el
+++ /dev/null
@@ -1,185 +0,0 @@
-;;; test-custom-misc-replace-fraction-glyphs.el --- Tests for cj/--replace-fraction-glyphs -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--replace-fraction-glyphs function from custom-misc.el
-;;
-;; This function bidirectionally converts between text fractions (1/4) and
-;; Unicode fraction glyphs (¼). It supports 5 common fractions:
-;; - 1/4 ↔ ¼
-;; - 1/2 ↔ ½
-;; - 3/4 ↔ ¾
-;; - 1/3 ↔ ⅓
-;; - 2/3 ↔ ⅔
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--replace-fraction-glyphs)
-;; to avoid mocking prefix arguments. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-misc)
-
-;;; Test Helpers
-
-(defun test-replace-fraction-glyphs (input-text to-glyphs)
- "Test cj/--replace-fraction-glyphs on INPUT-TEXT.
-TO-GLYPHS determines conversion direction.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--replace-fraction-glyphs (point-min) (point-max) to-glyphs)
- (buffer-string)))
-
-;;; Normal Cases - Text to Glyphs
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-quarter ()
- "Should convert 1/4 to ¼."
- (let ((result (test-replace-fraction-glyphs "1/4" t)))
- (should (string= result "¼"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-half ()
- "Should convert 1/2 to ½."
- (let ((result (test-replace-fraction-glyphs "1/2" t)))
- (should (string= result "½"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-three-quarters ()
- "Should convert 3/4 to ¾."
- (let ((result (test-replace-fraction-glyphs "3/4" t)))
- (should (string= result "¾"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-third ()
- "Should convert 1/3 to ⅓."
- (let ((result (test-replace-fraction-glyphs "1/3" t)))
- (should (string= result "⅓"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-two-thirds ()
- "Should convert 2/3 to ⅔."
- (let ((result (test-replace-fraction-glyphs "2/3" t)))
- (should (string= result "⅔"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-multiple ()
- "Should convert multiple fractions in text."
- (let ((result (test-replace-fraction-glyphs "Use 1/4 cup and 1/2 teaspoon" t)))
- (should (string= result "Use ¼ cup and ½ teaspoon"))))
-
-(ert-deftest test-replace-fraction-glyphs-text-to-glyph-all-types ()
- "Should convert all fraction types."
- (let ((result (test-replace-fraction-glyphs "1/4 1/2 3/4 1/3 2/3" t)))
- (should (string= result "¼ ½ ¾ ⅓ ⅔"))))
-
-;;; Normal Cases - Glyphs to Text
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-quarter ()
- "Should convert ¼ to 1/4."
- (let ((result (test-replace-fraction-glyphs "¼" nil)))
- (should (string= result "1/4"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-half ()
- "Should convert ½ to 1/2."
- (let ((result (test-replace-fraction-glyphs "½" nil)))
- (should (string= result "1/2"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-three-quarters ()
- "Should convert ¾ to 3/4."
- (let ((result (test-replace-fraction-glyphs "¾" nil)))
- (should (string= result "3/4"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-third ()
- "Should convert ⅓ to 1/3."
- (let ((result (test-replace-fraction-glyphs "⅓" nil)))
- (should (string= result "1/3"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-two-thirds ()
- "Should convert ⅔ to 2/3."
- (let ((result (test-replace-fraction-glyphs "⅔" nil)))
- (should (string= result "2/3"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-multiple ()
- "Should convert multiple glyphs in text."
- (let ((result (test-replace-fraction-glyphs "Use ¼ cup and ½ teaspoon" nil)))
- (should (string= result "Use 1/4 cup and 1/2 teaspoon"))))
-
-(ert-deftest test-replace-fraction-glyphs-glyph-to-text-all-types ()
- "Should convert all glyph types."
- (let ((result (test-replace-fraction-glyphs "¼ ½ ¾ ⅓ ⅔" nil)))
- (should (string= result "1/4 1/2 3/4 1/3 2/3"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-replace-fraction-glyphs-empty-string ()
- "Should handle empty string."
- (let ((result (test-replace-fraction-glyphs "" t)))
- (should (string= result ""))))
-
-(ert-deftest test-replace-fraction-glyphs-no-fractions-to-glyphs ()
- "Should handle text with no fractions (no-op) when converting to glyphs."
- (let ((result (test-replace-fraction-glyphs "hello world" t)))
- (should (string= result "hello world"))))
-
-(ert-deftest test-replace-fraction-glyphs-no-fractions-to-text ()
- "Should handle text with no glyphs (no-op) when converting to text."
- (let ((result (test-replace-fraction-glyphs "hello world" nil)))
- (should (string= result "hello world"))))
-
-(ert-deftest test-replace-fraction-glyphs-at-start ()
- "Should handle fraction at start of text."
- (let ((result (test-replace-fraction-glyphs "1/2 of the total" t)))
- (should (string= result "½ of the total"))))
-
-(ert-deftest test-replace-fraction-glyphs-at-end ()
- "Should handle fraction at end of text."
- (let ((result (test-replace-fraction-glyphs "Reduce by 1/4" t)))
- (should (string= result "Reduce by ¼"))))
-
-(ert-deftest test-replace-fraction-glyphs-repeated ()
- "Should handle repeated fractions."
- (let ((result (test-replace-fraction-glyphs "1/4 and 1/4 and 1/4" t)))
- (should (string= result "¼ and ¼ and ¼"))))
-
-(ert-deftest test-replace-fraction-glyphs-very-long-text ()
- "Should handle very long text with many fractions."
- (let* ((long-text (mapconcat (lambda (_) "1/4") (make-list 50 nil) " "))
- (result (test-replace-fraction-glyphs long-text t)))
- (should (string-match-p "¼" result))
- (should-not (string-match-p "1/4" result))))
-
-(ert-deftest test-replace-fraction-glyphs-bidirectional ()
- "Should correctly convert back and forth."
- (let* ((original "Use 1/4 cup")
- (to-glyph (test-replace-fraction-glyphs original t))
- (back-to-text (test-replace-fraction-glyphs to-glyph nil)))
- (should (string= to-glyph "Use ¼ cup"))
- (should (string= back-to-text original))))
-
-;;; Error Cases
-
-(ert-deftest test-replace-fraction-glyphs-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "1/4")
- (cj/--replace-fraction-glyphs (point-max) (point-min) t))
- :type 'error))
-
-(ert-deftest test-replace-fraction-glyphs-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "1/4")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--replace-fraction-glyphs pos pos t)
- ;; Should complete without error
- (should (string= (buffer-string) "1/4")))))
-
-(provide 'test-custom-misc-replace-fraction-glyphs)
-;;; test-custom-misc-replace-fraction-glyphs.el ends here
diff --git a/tests/test-custom-ordering-alphabetize.el b/tests/test-custom-ordering-alphabetize.el
deleted file mode 100644
index c609e324..00000000
--- a/tests/test-custom-ordering-alphabetize.el
+++ /dev/null
@@ -1,176 +0,0 @@
-;;; test-custom-ordering-alphabetize.el --- Tests for cj/--alphabetize-region -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--alphabetize-region function from custom-ordering.el
-;;
-;; This function alphabetically sorts words in a region.
-;; It splits by whitespace and commas, sorts alphabetically, and joins with ", ".
-;;
-;; Examples:
-;; Input: "zebra apple banana"
-;; Output: "apple, banana, zebra"
-;;
-;; Input: "dog, cat, bird"
-;; Output: "bird, cat, dog"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--alphabetize-region) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-alphabetize (input-text)
- "Test cj/--alphabetize-region on INPUT-TEXT.
-Returns the sorted, comma-separated string."
- (with-temp-buffer
- (insert input-text)
- (cj/--alphabetize-region (point-min) (point-max))))
-
-;;; Normal Cases - Simple Words
-
-(ert-deftest test-alphabetize-simple-words ()
- "Should alphabetize simple words."
- (let ((result (test-alphabetize "zebra apple banana")))
- (should (string= result "apple, banana, zebra"))))
-
-(ert-deftest test-alphabetize-already-sorted ()
- "Should handle already sorted words."
- (let ((result (test-alphabetize "apple banana cherry")))
- (should (string= result "apple, banana, cherry"))))
-
-(ert-deftest test-alphabetize-reverse-order ()
- "Should alphabetize reverse-ordered words."
- (let ((result (test-alphabetize "zebra yankee xray")))
- (should (string= result "xray, yankee, zebra"))))
-
-(ert-deftest test-alphabetize-two-words ()
- "Should alphabetize two words."
- (let ((result (test-alphabetize "world hello")))
- (should (string= result "hello, world"))))
-
-;;; Normal Cases - With Commas
-
-(ert-deftest test-alphabetize-comma-separated ()
- "Should alphabetize comma-separated words."
- (let ((result (test-alphabetize "dog, cat, bird")))
- (should (string= result "bird, cat, dog"))))
-
-(ert-deftest test-alphabetize-comma-separated-with-spaces ()
- "Should handle comma-separated with various spacing."
- (let ((result (test-alphabetize "dog,cat,bird")))
- (should (string= result "bird, cat, dog"))))
-
-;;; Normal Cases - With Newlines
-
-(ert-deftest test-alphabetize-multiline ()
- "Should alphabetize words across multiple lines."
- (let ((result (test-alphabetize "zebra\napple\nbanana")))
- (should (string= result "apple, banana, zebra"))))
-
-(ert-deftest test-alphabetize-mixed-separators ()
- "Should alphabetize with mixed separators (spaces, commas, newlines)."
- (let ((result (test-alphabetize "zebra, apple\nbanana cherry")))
- (should (string= result "apple, banana, cherry, zebra"))))
-
-;;; Normal Cases - Case Sensitivity
-
-(ert-deftest test-alphabetize-case-sensitive ()
- "Should sort case-sensitively (uppercase before lowercase)."
- (let ((result (test-alphabetize "zebra Apple banana")))
- ;; string-lessp sorts uppercase before lowercase
- (should (string= result "Apple, banana, zebra"))))
-
-(ert-deftest test-alphabetize-mixed-case ()
- "Should handle mixed case words."
- (let ((result (test-alphabetize "ZEBRA apple BANANA")))
- (should (string= result "BANANA, ZEBRA, apple"))))
-
-;;; Normal Cases - Numbers and Special Characters
-
-(ert-deftest test-alphabetize-with-numbers ()
- "Should alphabetize numbers as strings."
- (let ((result (test-alphabetize "10 2 1 20")))
- ;; Alphabetic sort: "1", "10", "2", "20"
- (should (string= result "1, 10, 2, 20"))))
-
-(ert-deftest test-alphabetize-mixed-alphanumeric ()
- "Should alphabetize mixed alphanumeric content."
- (let ((result (test-alphabetize "item2 item1 item10")))
- (should (string= result "item1, item10, item2"))))
-
-(ert-deftest test-alphabetize-with-punctuation ()
- "Should alphabetize words with punctuation."
- (let ((result (test-alphabetize "world! hello? test.")))
- (should (string= result "hello?, test., world!"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-alphabetize-empty-string ()
- "Should handle empty string."
- (let ((result (test-alphabetize "")))
- (should (string= result ""))))
-
-(ert-deftest test-alphabetize-single-word ()
- "Should handle single word."
- (let ((result (test-alphabetize "hello")))
- (should (string= result "hello"))))
-
-(ert-deftest test-alphabetize-only-whitespace ()
- "Should handle whitespace-only text."
- (let ((result (test-alphabetize " \n\n\t\t ")))
- (should (string= result ""))))
-
-(ert-deftest test-alphabetize-duplicates ()
- "Should handle duplicate words."
- (let ((result (test-alphabetize "apple banana apple cherry")))
- (should (string= result "apple, apple, banana, cherry"))))
-
-(ert-deftest test-alphabetize-many-commas ()
- "Should handle multiple consecutive commas."
- (let ((result (test-alphabetize "apple,,,banana,,,cherry")))
- (should (string= result "apple, banana, cherry"))))
-
-(ert-deftest test-alphabetize-very-long-list ()
- "Should handle very long list."
- (let* ((words (mapcar (lambda (i) (format "word%03d" i)) (number-sequence 100 1 -1)))
- (input (mapconcat #'identity words " "))
- (result (test-alphabetize input))
- (sorted-words (split-string result ", ")))
- (should (= 100 (length sorted-words)))
- (should (string= "word001" (car sorted-words)))
- (should (string= "word100" (car (last sorted-words))))))
-
-;;; Error Cases
-
-(ert-deftest test-alphabetize-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--alphabetize-region (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-alphabetize-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--alphabetize-region pos pos))))))
-
-(provide 'test-custom-ordering-alphabetize)
-;;; test-custom-ordering-alphabetize.el ends here
diff --git a/tests/test-custom-ordering-arrayify.el b/tests/test-custom-ordering-arrayify.el
deleted file mode 100644
index 9aedbc46..00000000
--- a/tests/test-custom-ordering-arrayify.el
+++ /dev/null
@@ -1,215 +0,0 @@
-;;; test-custom-ordering-arrayify.el --- Tests for cj/--arrayify -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--arrayify function from custom-ordering.el
-;;
-;; This function converts lines of text into a quoted, comma-separated array format.
-;; It splits input by whitespace, wraps each element in quotes, and joins with ", ".
-;;
-;; Examples:
-;; Input: "apple\nbanana\ncherry"
-;; Output: "\"apple\", \"banana\", \"cherry\""
-;;
-;; Input: "one two three" (with single quotes)
-;; Output: "'one', 'two', 'three'"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--arrayify) to avoid
-;; mocking user input for quote characters. This follows our testing best
-;; practice of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-arrayify (input-text quote)
- "Test cj/--arrayify on INPUT-TEXT with QUOTE character.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--arrayify (point-min) (point-max) quote)))
-
-(defun test-arrayify-with-prefix-suffix (input-text quote prefix suffix)
- "Test cj/--arrayify with PREFIX and SUFFIX on INPUT-TEXT.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--arrayify (point-min) (point-max) quote prefix suffix)))
-
-;;; Normal Cases - Double Quotes
-
-(ert-deftest test-arrayify-single-line-double-quotes ()
- "Should arrayify single line with double quotes."
- (let ((result (test-arrayify "apple banana cherry" "\"")))
- (should (string= result "\"apple\", \"banana\", \"cherry\""))))
-
-(ert-deftest test-arrayify-multiple-lines-double-quotes ()
- "Should arrayify multiple lines with double quotes."
- (let ((result (test-arrayify "apple\nbanana\ncherry" "\"")))
- (should (string= result "\"apple\", \"banana\", \"cherry\""))))
-
-(ert-deftest test-arrayify-mixed-whitespace-double-quotes ()
- "Should arrayify text with mixed whitespace using double quotes."
- (let ((result (test-arrayify "apple \n\n banana\t\tcherry" "\"")))
- (should (string= result "\"apple\", \"banana\", \"cherry\""))))
-
-;;; Normal Cases - Single Quotes
-
-(ert-deftest test-arrayify-single-line-single-quotes ()
- "Should arrayify single line with single quotes."
- (let ((result (test-arrayify "one two three" "'")))
- (should (string= result "'one', 'two', 'three'"))))
-
-(ert-deftest test-arrayify-multiple-lines-single-quotes ()
- "Should arrayify multiple lines with single quotes."
- (let ((result (test-arrayify "one\ntwo\nthree" "'")))
- (should (string= result "'one', 'two', 'three'"))))
-
-;;; Normal Cases - Various Quote Types
-
-(ert-deftest test-arrayify-backticks ()
- "Should arrayify with backticks."
- (let ((result (test-arrayify "foo bar baz" "`")))
- (should (string= result "`foo`, `bar`, `baz`"))))
-
-(ert-deftest test-arrayify-no-quotes ()
- "Should arrayify with empty quote string."
- (let ((result (test-arrayify "alpha beta gamma" "")))
- (should (string= result "alpha, beta, gamma"))))
-
-(ert-deftest test-arrayify-square-brackets ()
- "Should arrayify with square brackets as quotes."
- (let ((result (test-arrayify "x y z" "[]")))
- (should (string= result "[]x[], []y[], []z[]"))))
-
-;;; Normal Cases - Various Content
-
-(ert-deftest test-arrayify-with-numbers ()
- "Should arrayify numbers."
- (let ((result (test-arrayify "1 2 3 4 5" "\"")))
- (should (string= result "\"1\", \"2\", \"3\", \"4\", \"5\""))))
-
-(ert-deftest test-arrayify-with-punctuation ()
- "Should arrayify words with punctuation."
- (let ((result (test-arrayify "hello! world? test." "\"")))
- (should (string= result "\"hello!\", \"world?\", \"test.\""))))
-
-(ert-deftest test-arrayify-mixed-content ()
- "Should arrayify mixed alphanumeric content."
- (let ((result (test-arrayify "item1 item2 item3" "\"")))
- (should (string= result "\"item1\", \"item2\", \"item3\""))))
-
-;;; Boundary Cases
-
-(ert-deftest test-arrayify-empty-string ()
- "Should handle empty string."
- (let ((result (test-arrayify "" "\"")))
- (should (string= result ""))))
-
-(ert-deftest test-arrayify-single-word ()
- "Should arrayify single word."
- (let ((result (test-arrayify "hello" "\"")))
- (should (string= result "\"hello\""))))
-
-(ert-deftest test-arrayify-only-whitespace ()
- "Should handle whitespace-only text."
- (let ((result (test-arrayify " \n\n\t\t " "\"")))
- (should (string= result ""))))
-
-(ert-deftest test-arrayify-leading-trailing-whitespace ()
- "Should ignore leading and trailing whitespace."
- (let ((result (test-arrayify " apple banana " "\"")))
- (should (string= result "\"apple\", \"banana\""))))
-
-(ert-deftest test-arrayify-very-long-list ()
- "Should handle very long list."
- (let* ((words (make-list 100 "word"))
- (input (mapconcat #'identity words " "))
- (result (test-arrayify input "\"")))
- (should (= 100 (length (split-string result ", "))))))
-
-(ert-deftest test-arrayify-two-words ()
- "Should arrayify two words."
- (let ((result (test-arrayify "hello world" "\"")))
- (should (string= result "\"hello\", \"world\""))))
-
-;;; Normal Cases - Prefix/Suffix
-
-(ert-deftest test-arrayify-with-square-brackets ()
- "Should arrayify with square brackets prefix/suffix."
- (let ((result (test-arrayify-with-prefix-suffix "apple banana cherry" "\"" "[" "]")))
- (should (string= result "[\"apple\", \"banana\", \"cherry\"]"))))
-
-(ert-deftest test-arrayify-with-parens ()
- "Should arrayify with parentheses prefix/suffix."
- (let ((result (test-arrayify-with-prefix-suffix "one two three" "\"" "(" ")")))
- (should (string= result "(\"one\", \"two\", \"three\")"))))
-
-(ert-deftest test-arrayify-unquoted-with-brackets ()
- "Should create unquoted list with brackets."
- (let ((result (test-arrayify-with-prefix-suffix "a b c" "" "[" "]")))
- (should (string= result "[a, b, c]"))))
-
-(ert-deftest test-arrayify-single-quotes-with-brackets ()
- "Should create single-quoted array with brackets."
- (let ((result (test-arrayify-with-prefix-suffix "x y z" "'" "[" "]")))
- (should (string= result "['x', 'y', 'z']"))))
-
-(ert-deftest test-arrayify-only-prefix ()
- "Should handle only prefix, no suffix."
- (let ((result (test-arrayify-with-prefix-suffix "foo bar" "\"" "[" nil)))
- (should (string= result "[\"foo\", \"bar\""))))
-
-(ert-deftest test-arrayify-only-suffix ()
- "Should handle only suffix, no prefix."
- (let ((result (test-arrayify-with-prefix-suffix "foo bar" "\"" nil "]")))
- (should (string= result "\"foo\", \"bar\"]"))))
-
-(ert-deftest test-arrayify-multichar-prefix-suffix ()
- "Should handle multi-character prefix/suffix."
- (let ((result (test-arrayify-with-prefix-suffix "a b" "\"" "Array(" ")")))
- (should (string= result "Array(\"a\", \"b\")"))))
-
-(ert-deftest test-arrayify-json-style ()
- "Should create JSON-style array."
- (let ((result (test-arrayify-with-prefix-suffix "apple banana" "\"" "[" "]")))
- (should (string= result "[\"apple\", \"banana\"]"))))
-
-;;; Error Cases
-
-(ert-deftest test-arrayify-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--arrayify (point-max) (point-min) "\""))
- :type 'error))
-
-(ert-deftest test-arrayify-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--arrayify pos pos "\""))))))
-
-(ert-deftest test-arrayify-empty-region-with-brackets ()
- "Should handle empty region with brackets."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "[]" (cj/--arrayify pos pos "\"" "[" "]"))))))
-
-(provide 'test-custom-ordering-arrayify)
-;;; test-custom-ordering-arrayify.el ends here
diff --git a/tests/test-custom-ordering-comma-to-lines.el b/tests/test-custom-ordering-comma-to-lines.el
deleted file mode 100644
index 93e37ec6..00000000
--- a/tests/test-custom-ordering-comma-to-lines.el
+++ /dev/null
@@ -1,159 +0,0 @@
-;;; test-custom-ordering-comma-to-lines.el --- Tests for cj/--comma-separated-text-to-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--comma-separated-text-to-lines function from custom-ordering.el
-;;
-;; This function converts comma-separated text to separate lines.
-;; It replaces commas with newlines and removes trailing whitespace from each line.
-;;
-;; Examples:
-;; Input: "apple, banana, cherry"
-;; Output: "apple\nbanana\ncherry"
-;;
-;; Input: "one,two,three"
-;; Output: "one\ntwo\nthree"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--comma-separated-text-to-lines)
-;; to avoid mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-comma-to-lines (input-text)
- "Test cj/--comma-separated-text-to-lines on INPUT-TEXT.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--comma-separated-text-to-lines (point-min) (point-max))))
-
-;;; Normal Cases - Simple Comma-Separated
-
-(ert-deftest test-comma-to-lines-simple ()
- "Should convert simple comma-separated text to lines."
- (let ((result (test-comma-to-lines "apple, banana, cherry")))
- (should (string= result "apple\n banana\n cherry"))))
-
-(ert-deftest test-comma-to-lines-no-spaces ()
- "Should convert comma-separated text without spaces."
- (let ((result (test-comma-to-lines "one,two,three")))
- (should (string= result "one\ntwo\nthree"))))
-
-(ert-deftest test-comma-to-lines-two-elements ()
- "Should convert two comma-separated elements."
- (let ((result (test-comma-to-lines "hello,world")))
- (should (string= result "hello\nworld"))))
-
-(ert-deftest test-comma-to-lines-with-varied-spacing ()
- "Should preserve leading spaces after commas."
- (let ((result (test-comma-to-lines "alpha, beta, gamma")))
- (should (string= result "alpha\n beta\n gamma"))))
-
-;;; Normal Cases - Trailing Whitespace
-
-(ert-deftest test-comma-to-lines-trailing-spaces ()
- "Should remove trailing spaces but preserve leading spaces."
- (let ((result (test-comma-to-lines "apple , banana , cherry ")))
- (should (string= result "apple\n banana\n cherry"))))
-
-(ert-deftest test-comma-to-lines-trailing-tabs ()
- "Should remove trailing tabs after conversion."
- (let ((result (test-comma-to-lines "apple\t,banana\t,cherry\t")))
- (should (string= result "apple\nbanana\ncherry"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-comma-to-lines-empty-string ()
- "Should handle empty string."
- (let ((result (test-comma-to-lines "")))
- (should (string= result ""))))
-
-(ert-deftest test-comma-to-lines-single-element ()
- "Should handle single element with no comma."
- (let ((result (test-comma-to-lines "hello")))
- (should (string= result "hello"))))
-
-(ert-deftest test-comma-to-lines-single-element-with-trailing-comma ()
- "Should handle single element with trailing comma."
- (let ((result (test-comma-to-lines "hello,")))
- (should (string= result "hello\n"))))
-
-(ert-deftest test-comma-to-lines-leading-comma ()
- "Should handle leading comma."
- (let ((result (test-comma-to-lines ",apple,banana")))
- (should (string= result "\napple\nbanana"))))
-
-(ert-deftest test-comma-to-lines-consecutive-commas ()
- "Should handle consecutive commas."
- (let ((result (test-comma-to-lines "apple,,banana")))
- (should (string= result "apple\n\nbanana"))))
-
-(ert-deftest test-comma-to-lines-many-consecutive-commas ()
- "Should handle many consecutive commas."
- (let ((result (test-comma-to-lines "apple,,,banana")))
- (should (string= result "apple\n\n\nbanana"))))
-
-(ert-deftest test-comma-to-lines-only-commas ()
- "Should handle string with only commas (trailing blank lines removed)."
- (let ((result (test-comma-to-lines ",,,")))
- ;; delete-trailing-whitespace removes trailing blank lines
- (should (string= result "\n"))))
-
-;;; Normal Cases - With Spaces Around Elements
-
-(ert-deftest test-comma-to-lines-leading-spaces ()
- "Should preserve leading spaces within elements."
- (let ((result (test-comma-to-lines " apple, banana, cherry")))
- (should (string= result " apple\n banana\n cherry"))))
-
-(ert-deftest test-comma-to-lines-mixed-content ()
- "Should handle mixed alphanumeric content."
- (let ((result (test-comma-to-lines "item1,item2,item3")))
- (should (string= result "item1\nitem2\nitem3"))))
-
-(ert-deftest test-comma-to-lines-with-numbers ()
- "Should handle numbers."
- (let ((result (test-comma-to-lines "1,2,3,4,5")))
- (should (string= result "1\n2\n3\n4\n5"))))
-
-(ert-deftest test-comma-to-lines-very-long-list ()
- "Should handle very long list."
- (let* ((elements (mapcar #'number-to-string (number-sequence 1 100)))
- (input (mapconcat #'identity elements ","))
- (result (test-comma-to-lines input))
- (lines (split-string result "\n")))
- (should (= 100 (length lines)))))
-
-;;; Error Cases
-
-(ert-deftest test-comma-to-lines-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "a,b,c")
- (cj/--comma-separated-text-to-lines (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-comma-to-lines-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "a,b,c")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--comma-separated-text-to-lines pos pos))))))
-
-(provide 'test-custom-ordering-comma-to-lines)
-;;; test-custom-ordering-comma-to-lines.el ends here
diff --git a/tests/test-custom-ordering-number-lines.el b/tests/test-custom-ordering-number-lines.el
deleted file mode 100644
index adda84f0..00000000
--- a/tests/test-custom-ordering-number-lines.el
+++ /dev/null
@@ -1,181 +0,0 @@
-;;; test-custom-ordering-number-lines.el --- Tests for cj/--number-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--number-lines function from custom-ordering.el
-;;
-;; This function numbers lines in a region with a customizable format.
-;; The format string uses "N" as a placeholder for the line number.
-;; Optionally supports zero-padding for alignment.
-;;
-;; Examples:
-;; Input: "apple\nbanana\ncherry"
-;; Format: "N. "
-;; Output: "1. apple\n2. banana\n3. cherry"
-;;
-;; With zero-padding and 100 lines:
-;; "001. line\n002. line\n...\n100. line"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--number-lines) to avoid
-;; mocking user input. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-(require 'cl-lib)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-number-lines (input-text format-string zero-pad)
- "Test cj/--number-lines on INPUT-TEXT.
-FORMAT-STRING is the format template.
-ZERO-PAD enables zero-padding.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--number-lines (point-min) (point-max) format-string zero-pad)))
-
-;;; Normal Cases - Standard Format "N. "
-
-(ert-deftest test-number-lines-standard-format ()
- "Should number lines with standard format."
- (let ((result (test-number-lines "apple\nbanana\ncherry" "N. " nil)))
- (should (string= result "1. apple\n2. banana\n3. cherry"))))
-
-(ert-deftest test-number-lines-two-lines ()
- "Should number two lines."
- (let ((result (test-number-lines "first\nsecond" "N. " nil)))
- (should (string= result "1. first\n2. second"))))
-
-(ert-deftest test-number-lines-single-line ()
- "Should number single line."
- (let ((result (test-number-lines "only" "N. " nil)))
- (should (string= result "1. only"))))
-
-;;; Normal Cases - Alternative Formats
-
-(ert-deftest test-number-lines-parenthesis-format ()
- "Should number with parenthesis format."
- (let ((result (test-number-lines "a\nb\nc" "N) " nil)))
- (should (string= result "1) a\n2) b\n3) c"))))
-
-(ert-deftest test-number-lines-bracket-format ()
- "Should number with bracket format."
- (let ((result (test-number-lines "x\ny\nz" "[N] " nil)))
- (should (string= result "[1] x\n[2] y\n[3] z"))))
-
-(ert-deftest test-number-lines-no-space-format ()
- "Should number without space."
- (let ((result (test-number-lines "a\nb" "N." nil)))
- (should (string= result "1.a\n2.b"))))
-
-(ert-deftest test-number-lines-custom-format ()
- "Should number with custom format."
- (let ((result (test-number-lines "foo\nbar" "Item N: " nil)))
- (should (string= result "Item 1: foo\nItem 2: bar"))))
-
-;;; Normal Cases - Zero Padding
-
-(ert-deftest test-number-lines-zero-pad-single-digit ()
- "Should not pad when max is single digit."
- (let ((result (test-number-lines "a\nb\nc" "N. " t)))
- (should (string= result "1. a\n2. b\n3. c"))))
-
-(ert-deftest test-number-lines-zero-pad-double-digit ()
- "Should pad to 2 digits when max is 10-99."
- (let* ((lines (make-list 12 "line"))
- (input (mapconcat #'identity lines "\n"))
- (result (test-number-lines input "N. " t))
- (result-lines (split-string result "\n")))
- (should (string-prefix-p "01. " (nth 0 result-lines)))
- (should (string-prefix-p "09. " (nth 8 result-lines)))
- (should (string-prefix-p "10. " (nth 9 result-lines)))
- (should (string-prefix-p "12. " (nth 11 result-lines)))))
-
-(ert-deftest test-number-lines-zero-pad-triple-digit ()
- "Should pad to 3 digits when max is 100+."
- (let* ((lines (make-list 105 "x"))
- (input (mapconcat #'identity lines "\n"))
- (result (test-number-lines input "N. " t))
- (result-lines (split-string result "\n")))
- (should (string-prefix-p "001. " (nth 0 result-lines)))
- (should (string-prefix-p "099. " (nth 98 result-lines)))
- (should (string-prefix-p "100. " (nth 99 result-lines)))
- (should (string-prefix-p "105. " (nth 104 result-lines)))))
-
-;;; Boundary Cases
-
-(ert-deftest test-number-lines-empty-string ()
- "Should handle empty string."
- (let ((result (test-number-lines "" "N. " nil)))
- (should (string= result "1. "))))
-
-(ert-deftest test-number-lines-empty-lines ()
- "Should number empty lines."
- (let ((result (test-number-lines "\n\n" "N. " nil)))
- (should (string= result "1. \n2. \n3. "))))
-
-(ert-deftest test-number-lines-with-existing-numbers ()
- "Should number lines that already have content."
- (let ((result (test-number-lines "1. old\n2. old" "N. " nil)))
- (should (string= result "1. 1. old\n2. 2. old"))))
-
-(ert-deftest test-number-lines-multiple-N-in-format ()
- "Should replace multiple N occurrences."
- (let ((result (test-number-lines "a\nb" "N-N. " nil)))
- (should (string= result "1-1. a\n2-2. b"))))
-
-(ert-deftest test-number-lines-long-content ()
- "Should number lines with long content."
- (let* ((long-line (make-string 100 ?x))
- (input (format "%s\n%s" long-line long-line))
- (result (test-number-lines input "N. " nil)))
- (should (string-prefix-p "1. " result))
- (should (string-match "2\\. " result))))
-
-;;; Normal Cases - No Zero Padding vs Zero Padding
-
-(ert-deftest test-number-lines-comparison-no-pad-vs-pad ()
- "Should show difference between no padding and padding."
- (let* ((input "a\nb\nc\nd\ne\nf\ng\nh\ni\nj")
- (no-pad (test-number-lines input "N. " nil))
- (with-pad (test-number-lines input "N. " t))
- (no-pad-lines (split-string no-pad "\n"))
- (with-pad-lines (split-string with-pad "\n")))
- ;; Without padding: "1. ", "10. "
- (should (string-prefix-p "1. " (nth 0 no-pad-lines)))
- (should (string-prefix-p "10. " (nth 9 no-pad-lines)))
- ;; With padding: "01. ", "10. "
- (should (string-prefix-p "01. " (nth 0 with-pad-lines)))
- (should (string-prefix-p "10. " (nth 9 with-pad-lines)))))
-
-;;; Error Cases
-
-(ert-deftest test-number-lines-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "line1\nline2")
- (cj/--number-lines (point-max) (point-min) "N. " nil))
- :type 'error))
-
-(ert-deftest test-number-lines-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "line1\nline2")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "1. " (cj/--number-lines pos pos "N. " nil))))))
-
-(provide 'test-custom-ordering-number-lines)
-;;; test-custom-ordering-number-lines.el ends here
diff --git a/tests/test-custom-ordering-reverse-lines.el b/tests/test-custom-ordering-reverse-lines.el
deleted file mode 100644
index 3c71362d..00000000
--- a/tests/test-custom-ordering-reverse-lines.el
+++ /dev/null
@@ -1,131 +0,0 @@
-;;; test-custom-ordering-reverse-lines.el --- Tests for cj/--reverse-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--reverse-lines function from custom-ordering.el
-;;
-;; This function reverses the order of lines in a region.
-;; The first line becomes last, last becomes first, etc.
-;;
-;; Examples:
-;; Input: "line1\nline2\nline3"
-;; Output: "line3\nline2\nline1"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--reverse-lines) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-reverse-lines (input-text)
- "Test cj/--reverse-lines on INPUT-TEXT.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--reverse-lines (point-min) (point-max))))
-
-;;; Normal Cases
-
-(ert-deftest test-reverse-lines-three-lines ()
- "Should reverse three lines."
- (let ((result (test-reverse-lines "line1\nline2\nline3")))
- (should (string= result "line3\nline2\nline1"))))
-
-(ert-deftest test-reverse-lines-two-lines ()
- "Should reverse two lines."
- (let ((result (test-reverse-lines "first\nsecond")))
- (should (string= result "second\nfirst"))))
-
-(ert-deftest test-reverse-lines-many-lines ()
- "Should reverse many lines."
- (let ((result (test-reverse-lines "a\nb\nc\nd\ne")))
- (should (string= result "e\nd\nc\nb\na"))))
-
-(ert-deftest test-reverse-lines-with-content ()
- "Should reverse lines with actual content."
- (let ((result (test-reverse-lines "apple banana\ncherry date\negg fig")))
- (should (string= result "egg fig\ncherry date\napple banana"))))
-
-(ert-deftest test-reverse-lines-bidirectional ()
- "Should reverse back and forth correctly."
- (let* ((original "line1\nline2\nline3")
- (reversed (test-reverse-lines original))
- (back (test-reverse-lines reversed)))
- (should (string= reversed "line3\nline2\nline1"))
- (should (string= back original))))
-
-;;; Boundary Cases
-
-(ert-deftest test-reverse-lines-empty-string ()
- "Should handle empty string."
- (let ((result (test-reverse-lines "")))
- (should (string= result ""))))
-
-(ert-deftest test-reverse-lines-single-line ()
- "Should handle single line (no change)."
- (let ((result (test-reverse-lines "single line")))
- (should (string= result "single line"))))
-
-(ert-deftest test-reverse-lines-empty-lines ()
- "Should reverse including empty lines."
- (let ((result (test-reverse-lines "a\n\nb")))
- (should (string= result "b\n\na"))))
-
-(ert-deftest test-reverse-lines-trailing-newline ()
- "Should handle trailing newline."
- (let ((result (test-reverse-lines "line1\nline2\n")))
- (should (string= result "\nline2\nline1"))))
-
-(ert-deftest test-reverse-lines-only-newlines ()
- "Should reverse lines that are only newlines."
- (let ((result (test-reverse-lines "\n\n\n")))
- (should (string= result "\n\n\n"))))
-
-(ert-deftest test-reverse-lines-numbers ()
- "Should reverse numbered lines."
- (let ((result (test-reverse-lines "1\n2\n3\n4\n5")))
- (should (string= result "5\n4\n3\n2\n1"))))
-
-(ert-deftest test-reverse-lines-very-long ()
- "Should reverse very long list."
- (let* ((lines (mapcar #'number-to-string (number-sequence 1 100)))
- (input (mapconcat #'identity lines "\n"))
- (result (test-reverse-lines input))
- (result-lines (split-string result "\n")))
- (should (= 100 (length result-lines)))
- (should (string= "100" (car result-lines)))
- (should (string= "1" (car (last result-lines))))))
-
-;;; Error Cases
-
-(ert-deftest test-reverse-lines-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "line1\nline2")
- (cj/--reverse-lines (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-reverse-lines-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "line1\nline2")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--reverse-lines pos pos))))))
-
-(provide 'test-custom-ordering-reverse-lines)
-;;; test-custom-ordering-reverse-lines.el ends here
diff --git a/tests/test-custom-ordering-toggle-quotes.el b/tests/test-custom-ordering-toggle-quotes.el
deleted file mode 100644
index e11305ee..00000000
--- a/tests/test-custom-ordering-toggle-quotes.el
+++ /dev/null
@@ -1,155 +0,0 @@
-;;; test-custom-ordering-toggle-quotes.el --- Tests for cj/--toggle-quotes -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--toggle-quotes function from custom-ordering.el
-;;
-;; This function toggles between double quotes and single quotes.
-;; All " become ' and all ' become ".
-;;
-;; Examples:
-;; Input: "apple", "banana"
-;; Output: 'apple', 'banana'
-;;
-;; Input: 'hello', 'world'
-;; Output: "hello", "world"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--toggle-quotes) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-toggle-quotes (input-text)
- "Test cj/--toggle-quotes on INPUT-TEXT.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--toggle-quotes (point-min) (point-max))))
-
-;;; Normal Cases - Double to Single
-
-(ert-deftest test-toggle-quotes-double-to-single ()
- "Should convert double quotes to single quotes."
- (let ((result (test-toggle-quotes "\"apple\", \"banana\"")))
- (should (string= result "'apple', 'banana'"))))
-
-(ert-deftest test-toggle-quotes-single-double-quote ()
- "Should convert single double quote."
- (let ((result (test-toggle-quotes "\"")))
- (should (string= result "'"))))
-
-(ert-deftest test-toggle-quotes-multiple-double-quotes ()
- "Should convert multiple double quotes."
- (let ((result (test-toggle-quotes "\"hello\" \"world\" \"test\"")))
- (should (string= result "'hello' 'world' 'test'"))))
-
-;;; Normal Cases - Single to Double
-
-(ert-deftest test-toggle-quotes-single-to-double ()
- "Should convert single quotes to double quotes."
- (let ((result (test-toggle-quotes "'apple', 'banana'")))
- (should (string= result "\"apple\", \"banana\""))))
-
-(ert-deftest test-toggle-quotes-single-single-quote ()
- "Should convert single single quote."
- (let ((result (test-toggle-quotes "'")))
- (should (string= result "\""))))
-
-(ert-deftest test-toggle-quotes-multiple-single-quotes ()
- "Should convert multiple single quotes."
- (let ((result (test-toggle-quotes "'hello' 'world' 'test'")))
- (should (string= result "\"hello\" \"world\" \"test\""))))
-
-;;; Normal Cases - Mixed Quotes
-
-(ert-deftest test-toggle-quotes-mixed ()
- "Should toggle mixed quotes."
- (let ((result (test-toggle-quotes "\"double\" 'single'")))
- (should (string= result "'double' \"single\""))))
-
-(ert-deftest test-toggle-quotes-bidirectional ()
- "Should toggle back and forth correctly."
- (let* ((original "\"apple\", \"banana\"")
- (toggled (test-toggle-quotes original))
- (back (test-toggle-quotes toggled)))
- (should (string= toggled "'apple', 'banana'"))
- (should (string= back original))))
-
-;;; Normal Cases - With Text Content
-
-(ert-deftest test-toggle-quotes-preserves-content ()
- "Should preserve content while toggling quotes."
- (let ((result (test-toggle-quotes "var x = \"hello world\";")))
- (should (string= result "var x = 'hello world';"))))
-
-(ert-deftest test-toggle-quotes-sql-style ()
- "Should toggle SQL-style quotes."
- (let ((result (test-toggle-quotes "SELECT * FROM users WHERE name='John'")))
- (should (string= result "SELECT * FROM users WHERE name=\"John\""))))
-
-(ert-deftest test-toggle-quotes-multiline ()
- "Should toggle quotes across multiple lines."
- (let ((result (test-toggle-quotes "\"line1\"\n\"line2\"\n\"line3\"")))
- (should (string= result "'line1'\n'line2'\n'line3'"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-toggle-quotes-empty-string ()
- "Should handle empty string."
- (let ((result (test-toggle-quotes "")))
- (should (string= result ""))))
-
-(ert-deftest test-toggle-quotes-no-quotes ()
- "Should handle text with no quotes."
- (let ((result (test-toggle-quotes "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-toggle-quotes-only-double-quotes ()
- "Should handle string with only double quotes."
- (let ((result (test-toggle-quotes "\"\"\"\"")))
- (should (string= result "''''"))))
-
-(ert-deftest test-toggle-quotes-only-single-quotes ()
- "Should handle string with only single quotes."
- (let ((result (test-toggle-quotes "''''")))
- (should (string= result "\"\"\"\""))))
-
-(ert-deftest test-toggle-quotes-adjacent-quotes ()
- "Should handle adjacent quotes."
- (let ((result (test-toggle-quotes "\"\"''")))
- (should (string= result "''\"\""))))
-
-;;; Error Cases
-
-(ert-deftest test-toggle-quotes-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "\"hello\"")
- (cj/--toggle-quotes (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-toggle-quotes-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "\"hello\"")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--toggle-quotes pos pos))))))
-
-(provide 'test-custom-ordering-toggle-quotes)
-;;; test-custom-ordering-toggle-quotes.el ends here
diff --git a/tests/test-custom-ordering-unarrayify.el b/tests/test-custom-ordering-unarrayify.el
deleted file mode 100644
index a778f419..00000000
--- a/tests/test-custom-ordering-unarrayify.el
+++ /dev/null
@@ -1,159 +0,0 @@
-;;; test-custom-ordering-unarrayify.el --- Tests for cj/--unarrayify -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--unarrayify function from custom-ordering.el
-;;
-;; This function converts comma-separated array format back to separate lines.
-;; It splits by ", " (comma-space), removes quotes (both " and '), and joins with newlines.
-;;
-;; Examples:
-;; Input: "\"apple\", \"banana\", \"cherry\""
-;; Output: "apple\nbanana\ncherry"
-;;
-;; Input: "'one', 'two', 'three'"
-;; Output: "one\ntwo\nthree"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--unarrayify) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-ordering)
-
-;;; Test Helpers
-
-(defun test-unarrayify (input-text)
- "Test cj/--unarrayify on INPUT-TEXT.
-Returns the transformed string."
- (with-temp-buffer
- (insert input-text)
- (cj/--unarrayify (point-min) (point-max))))
-
-;;; Normal Cases - Double Quotes
-
-(ert-deftest test-unarrayify-double-quotes-simple ()
- "Should unarrayify double-quoted elements."
- (let ((result (test-unarrayify "\"apple\", \"banana\", \"cherry\"")))
- (should (string= result "apple\nbanana\ncherry"))))
-
-(ert-deftest test-unarrayify-double-quotes-single-element ()
- "Should unarrayify single double-quoted element."
- (let ((result (test-unarrayify "\"hello\"")))
- (should (string= result "hello"))))
-
-(ert-deftest test-unarrayify-double-quotes-two-elements ()
- "Should unarrayify two double-quoted elements."
- (let ((result (test-unarrayify "\"one\", \"two\"")))
- (should (string= result "one\ntwo"))))
-
-;;; Normal Cases - Single Quotes
-
-(ert-deftest test-unarrayify-single-quotes-simple ()
- "Should unarrayify single-quoted elements."
- (let ((result (test-unarrayify "'alpha', 'beta', 'gamma'")))
- (should (string= result "alpha\nbeta\ngamma"))))
-
-(ert-deftest test-unarrayify-single-quotes-single-element ()
- "Should unarrayify single single-quoted element."
- (let ((result (test-unarrayify "'hello'")))
- (should (string= result "hello"))))
-
-;;; Normal Cases - Mixed Quotes
-
-(ert-deftest test-unarrayify-mixed-quotes ()
- "Should unarrayify mixed quote types."
- (let ((result (test-unarrayify "\"apple\", 'banana', \"cherry\"")))
- (should (string= result "apple\nbanana\ncherry"))))
-
-;;; Normal Cases - No Quotes
-
-(ert-deftest test-unarrayify-no-quotes ()
- "Should unarrayify unquoted elements."
- (let ((result (test-unarrayify "foo, bar, baz")))
- (should (string= result "foo\nbar\nbaz"))))
-
-;;; Normal Cases - Various Content
-
-(ert-deftest test-unarrayify-with-numbers ()
- "Should unarrayify numbers."
- (let ((result (test-unarrayify "\"1\", \"2\", \"3\"")))
- (should (string= result "1\n2\n3"))))
-
-(ert-deftest test-unarrayify-with-spaces-in-elements ()
- "Should preserve spaces within elements."
- (let ((result (test-unarrayify "\"hello world\", \"foo bar\"")))
- (should (string= result "hello world\nfoo bar"))))
-
-(ert-deftest test-unarrayify-mixed-content ()
- "Should unarrayify mixed alphanumeric content."
- (let ((result (test-unarrayify "\"item1\", \"item2\", \"item3\"")))
- (should (string= result "item1\nitem2\nitem3"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-unarrayify-empty-string ()
- "Should handle empty string."
- (let ((result (test-unarrayify "")))
- (should (string= result ""))))
-
-(ert-deftest test-unarrayify-only-quotes ()
- "Should remove quotes from quote-only string."
- (let ((result (test-unarrayify "\"\"")))
- (should (string= result ""))))
-
-(ert-deftest test-unarrayify-very-long-list ()
- "Should handle very long list."
- (let* ((elements (mapcar (lambda (i) (format "\"%d\"" i)) (number-sequence 1 100)))
- (input (mapconcat #'identity elements ", "))
- (result (test-unarrayify input))
- (lines (split-string result "\n")))
- (should (= 100 (length lines)))))
-
-(ert-deftest test-unarrayify-with-empty-elements ()
- "Should handle empty quoted elements."
- (let ((result (test-unarrayify "\"\", \"test\", \"\"")))
- (should (string= result "\ntest\n"))))
-
-;;; Edge Cases - Nested or Mismatched Quotes
-
-(ert-deftest test-unarrayify-double-quotes-in-single ()
- "Should handle double quotes inside single-quoted strings."
- (let ((result (test-unarrayify "'he said \"hello\"', 'world'")))
- (should (string= result "he said hello\nworld"))))
-
-(ert-deftest test-unarrayify-only-opening-quotes ()
- "Should remove all quote characters even if mismatched."
- (let ((result (test-unarrayify "\"apple, \"banana, \"cherry")))
- (should (string= result "apple\nbanana\ncherry"))))
-
-;;; Error Cases
-
-(ert-deftest test-unarrayify-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "\"a\", \"b\"")
- (cj/--unarrayify (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-unarrayify-empty-region ()
- "Should handle empty region (start == end)."
- (with-temp-buffer
- (insert "\"a\", \"b\"")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (should (string= "" (cj/--unarrayify pos pos))))))
-
-(provide 'test-custom-ordering-unarrayify)
-;;; test-custom-ordering-unarrayify.el ends here
diff --git a/tests/test-custom-text-enclose-append.el b/tests/test-custom-text-enclose-append.el
deleted file mode 100644
index 3593a7f5..00000000
--- a/tests/test-custom-text-enclose-append.el
+++ /dev/null
@@ -1,190 +0,0 @@
-;;; test-custom-text-enclose-append.el --- Tests for cj/--append-to-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--append-to-lines function from custom-text-enclose.el
-;;
-;; This function appends a suffix string to the end of each line in text.
-;; It preserves the structure of lines and handles trailing newlines correctly.
-;;
-;; Examples:
-;; Input: "line1\nline2", suffix: ";"
-;; Output: "line1;\nline2;"
-;;
-;; Input: "single", suffix: "!"
-;; Output: "single!"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--append-to-lines) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-append-to-lines (text suffix)
- "Test cj/--append-to-lines on TEXT with SUFFIX.
-Returns the transformed string."
- (cj/--append-to-lines text suffix))
-
-;;; Normal Cases - Single Line
-
-(ert-deftest test-append-single-line ()
- "Should append to single line."
- (let ((result (test-append-to-lines "hello" ";")))
- (should (string= result "hello;"))))
-
-(ert-deftest test-append-single-line-semicolon ()
- "Should append semicolon to single line."
- (let ((result (test-append-to-lines "var x = 5" ";")))
- (should (string= result "var x = 5;"))))
-
-(ert-deftest test-append-single-line-exclamation ()
- "Should append exclamation mark to single line."
- (let ((result (test-append-to-lines "Hello world" "!")))
- (should (string= result "Hello world!"))))
-
-;;; Normal Cases - Multiple Lines
-
-(ert-deftest test-append-two-lines ()
- "Should append to two lines."
- (let ((result (test-append-to-lines "line1\nline2" ";")))
- (should (string= result "line1;\nline2;"))))
-
-(ert-deftest test-append-three-lines ()
- "Should append to three lines."
- (let ((result (test-append-to-lines "a\nb\nc" ".")))
- (should (string= result "a.\nb.\nc."))))
-
-(ert-deftest test-append-many-lines ()
- "Should append to many lines."
- (let* ((lines (make-list 10 "line"))
- (input (mapconcat #'identity lines "\n"))
- (result (test-append-to-lines input ";"))
- (result-lines (split-string result "\n")))
- (should (= 10 (length result-lines)))
- (should (cl-every (lambda (line) (string-suffix-p ";" line)) result-lines))))
-
-;;; Normal Cases - Various Suffixes
-
-(ert-deftest test-append-comma ()
- "Should append comma to lines."
- (let ((result (test-append-to-lines "apple\nbanana" ",")))
- (should (string= result "apple,\nbanana,"))))
-
-(ert-deftest test-append-multi-char ()
- "Should append multi-character suffix."
- (let ((result (test-append-to-lines "line" " // comment")))
- (should (string= result "line // comment"))))
-
-(ert-deftest test-append-pipe ()
- "Should append pipe character."
- (let ((result (test-append-to-lines "col1\ncol2" " |")))
- (should (string= result "col1 |\ncol2 |"))))
-
-(ert-deftest test-append-empty-suffix ()
- "Should handle empty suffix."
- (let ((result (test-append-to-lines "line1\nline2" "")))
- (should (string= result "line1\nline2"))))
-
-;;; Boundary Cases - Trailing Newlines
-
-(ert-deftest test-append-with-trailing-newline ()
- "Should preserve trailing newline."
- (let ((result (test-append-to-lines "line1\nline2\n" ";")))
- (should (string= result "line1;\nline2;\n"))))
-
-(ert-deftest test-append-no-trailing-newline ()
- "Should work without trailing newline."
- (let ((result (test-append-to-lines "line1\nline2" ";")))
- (should (string= result "line1;\nline2;"))))
-
-(ert-deftest test-append-single-line-with-newline ()
- "Should preserve trailing newline on single line."
- (let ((result (test-append-to-lines "line\n" ";")))
- (should (string= result "line;\n"))))
-
-;;; Boundary Cases - Empty Lines
-
-(ert-deftest test-append-empty-line-between ()
- "Should append to empty line between other lines."
- (let ((result (test-append-to-lines "line1\n\nline3" ";")))
- (should (string= result "line1;\n;\nline3;"))))
-
-(ert-deftest test-append-only-empty-lines ()
- "Should append to only empty lines."
- (let ((result (test-append-to-lines "\n\n" ";")))
- (should (string= result ";\n;\n"))))
-
-(ert-deftest test-append-empty-first-line ()
- "Should append to empty first line."
- (let ((result (test-append-to-lines "\nline2\nline3" ";")))
- (should (string= result ";\nline2;\nline3;"))))
-
-;;; Boundary Cases - Whitespace
-
-(ert-deftest test-append-preserves-leading-whitespace ()
- "Should preserve leading whitespace."
- (let ((result (test-append-to-lines " line1\n line2" ";")))
- (should (string= result " line1;\n line2;"))))
-
-(ert-deftest test-append-preserves-trailing-whitespace ()
- "Should preserve trailing whitespace on line."
- (let ((result (test-append-to-lines "line1 \nline2 " ";")))
- (should (string= result "line1 ;\nline2 ;"))))
-
-(ert-deftest test-append-whitespace-only-line ()
- "Should append to whitespace-only line."
- (let ((result (test-append-to-lines "line1\n \nline3" ";")))
- (should (string= result "line1;\n ;\nline3;"))))
-
-;;; Boundary Cases - Special Cases
-
-(ert-deftest test-append-empty-string ()
- "Should handle empty string."
- (let ((result (test-append-to-lines "" ";")))
- (should (string= result ";"))))
-
-(ert-deftest test-append-very-long-line ()
- "Should append to very long line."
- (let* ((long-line (make-string 1000 ?a))
- (result (test-append-to-lines long-line ";")))
- (should (string-suffix-p ";" result))
- (should (= (length result) 1001))))
-
-(ert-deftest test-append-with-existing-suffix ()
- "Should append even if line already has the suffix."
- (let ((result (test-append-to-lines "line;" ";")))
- (should (string= result "line;;"))))
-
-;;; Edge Cases - Special Characters in Suffix
-
-(ert-deftest test-append-newline-suffix ()
- "Should append newline as suffix."
- (let ((result (test-append-to-lines "line1\nline2" "\n")))
- (should (string= result "line1\n\nline2\n"))))
-
-(ert-deftest test-append-tab-suffix ()
- "Should append tab as suffix."
- (let ((result (test-append-to-lines "col1\ncol2" "\t")))
- (should (string= result "col1\t\ncol2\t"))))
-
-(ert-deftest test-append-quote-suffix ()
- "Should append quote as suffix."
- (let ((result (test-append-to-lines "value1\nvalue2" "\"")))
- (should (string= result "value1\"\nvalue2\""))))
-
-(provide 'test-custom-text-enclose-append)
-;;; test-custom-text-enclose-append.el ends here
diff --git a/tests/test-custom-text-enclose-indent.el b/tests/test-custom-text-enclose-indent.el
deleted file mode 100644
index e9042d35..00000000
--- a/tests/test-custom-text-enclose-indent.el
+++ /dev/null
@@ -1,241 +0,0 @@
-;;; test-custom-text-enclose-indent.el --- Tests for cj/--indent-lines and cj/--dedent-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--indent-lines and cj/--dedent-lines functions from custom-text-enclose.el
-;;
-;; cj/--indent-lines adds leading whitespace (spaces or tabs) to each line.
-;; cj/--dedent-lines removes up to COUNT leading whitespace characters from each line.
-;;
-;; Examples (indent):
-;; Input: "line1\nline2", count: 4, use-tabs: nil
-;; Output: " line1\n line2"
-;;
-;; Examples (dedent):
-;; Input: " line1\n line2", count: 4
-;; Output: "line1\nline2"
-;;
-;; We test the NON-INTERACTIVE implementations to avoid mocking user input.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-indent (text count use-tabs)
- "Test cj/--indent-lines on TEXT with COUNT and USE-TABS.
-Returns the transformed string."
- (cj/--indent-lines text count use-tabs))
-
-(defun test-dedent (text count)
- "Test cj/--dedent-lines on TEXT with COUNT.
-Returns the transformed string."
- (cj/--dedent-lines text count))
-
-;;; Indent Tests - Normal Cases with Spaces
-
-(ert-deftest test-indent-single-line-4-spaces ()
- "Should indent single line with 4 spaces."
- (let ((result (test-indent "line" 4 nil)))
- (should (string= result " line"))))
-
-(ert-deftest test-indent-two-lines-4-spaces ()
- "Should indent two lines with 4 spaces."
- (let ((result (test-indent "line1\nline2" 4 nil)))
- (should (string= result " line1\n line2"))))
-
-(ert-deftest test-indent-three-lines-2-spaces ()
- "Should indent three lines with 2 spaces."
- (let ((result (test-indent "a\nb\nc" 2 nil)))
- (should (string= result " a\n b\n c"))))
-
-(ert-deftest test-indent-many-lines ()
- "Should indent many lines."
- (let ((result (test-indent "1\n2\n3\n4\n5" 4 nil)))
- (should (string= result " 1\n 2\n 3\n 4\n 5"))))
-
-;;; Indent Tests - Normal Cases with Tabs
-
-(ert-deftest test-indent-single-line-1-tab ()
- "Should indent single line with 1 tab."
- (let ((result (test-indent "line" 1 t)))
- (should (string= result "\tline"))))
-
-(ert-deftest test-indent-two-lines-1-tab ()
- "Should indent two lines with 1 tab."
- (let ((result (test-indent "line1\nline2" 1 t)))
- (should (string= result "\tline1\n\tline2"))))
-
-(ert-deftest test-indent-with-2-tabs ()
- "Should indent with 2 tabs."
- (let ((result (test-indent "code" 2 t)))
- (should (string= result "\t\tcode"))))
-
-;;; Indent Tests - Boundary Cases
-
-(ert-deftest test-indent-empty-string ()
- "Should indent empty string."
- (let ((result (test-indent "" 4 nil)))
- (should (string= result " "))))
-
-(ert-deftest test-indent-zero-count ()
- "Should not indent with count 0."
- (let ((result (test-indent "line" 0 nil)))
- (should (string= result "line"))))
-
-(ert-deftest test-indent-already-indented ()
- "Should add more indentation to already indented lines."
- (let ((result (test-indent " line1\n line2" 2 nil)))
- (should (string= result " line1\n line2"))))
-
-(ert-deftest test-indent-empty-lines ()
- "Should indent empty lines."
- (let ((result (test-indent "line1\n\nline3" 4 nil)))
- (should (string= result " line1\n \n line3"))))
-
-(ert-deftest test-indent-trailing-newline ()
- "Should preserve trailing newline."
- (let ((result (test-indent "line1\nline2\n" 4 nil)))
- (should (string= result " line1\n line2\n"))))
-
-(ert-deftest test-indent-no-trailing-newline ()
- "Should work without trailing newline."
- (let ((result (test-indent "line1\nline2" 4 nil)))
- (should (string= result " line1\n line2"))))
-
-;;; Dedent Tests - Normal Cases
-
-(ert-deftest test-dedent-single-line-4-spaces ()
- "Should dedent single line with 4 spaces."
- (let ((result (test-dedent " line" 4)))
- (should (string= result "line"))))
-
-(ert-deftest test-dedent-two-lines-4-spaces ()
- "Should dedent two lines with 4 spaces."
- (let ((result (test-dedent " line1\n line2" 4)))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-dedent-three-lines-2-spaces ()
- "Should dedent three lines with 2 spaces."
- (let ((result (test-dedent " a\n b\n c" 2)))
- (should (string= result "a\nb\nc"))))
-
-(ert-deftest test-dedent-with-tabs ()
- "Should dedent lines with tabs."
- (let ((result (test-dedent "\tline1\n\tline2" 1)))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-dedent-mixed-spaces-tabs ()
- "Should dedent mixed spaces and tabs."
- (let ((result (test-dedent " \tline" 3)))
- (should (string= result "line"))))
-
-;;; Dedent Tests - Partial Dedent
-
-(ert-deftest test-dedent-partial ()
- "Should dedent only COUNT characters."
- (let ((result (test-dedent " line" 2)))
- (should (string= result " line"))))
-
-(ert-deftest test-dedent-less-than-count ()
- "Should dedent all available spaces when less than COUNT."
- (let ((result (test-dedent " line" 4)))
- (should (string= result "line"))))
-
-(ert-deftest test-dedent-no-leading-space ()
- "Should not affect lines with no leading whitespace."
- (let ((result (test-dedent "line" 4)))
- (should (string= result "line"))))
-
-(ert-deftest test-dedent-varying-indentation ()
- "Should dedent each line independently."
- (let ((result (test-dedent " line1\n line2\nline3" 2)))
- (should (string= result " line1\nline2\nline3"))))
-
-;;; Dedent Tests - Boundary Cases
-
-(ert-deftest test-dedent-empty-string ()
- "Should handle empty string."
- (let ((result (test-dedent "" 4)))
- (should (string= result ""))))
-
-(ert-deftest test-dedent-zero-count ()
- "Should not dedent with count 0."
- (let ((result (test-dedent " line" 0)))
- (should (string= result " line"))))
-
-(ert-deftest test-dedent-empty-lines ()
- "Should handle empty lines."
- (let ((result (test-dedent " line1\n \n line3" 4)))
- (should (string= result "line1\n\nline3"))))
-
-(ert-deftest test-dedent-only-whitespace ()
- "Should dedent whitespace-only lines."
- (let ((result (test-dedent " " 4)))
- (should (string= result ""))))
-
-(ert-deftest test-dedent-trailing-newline ()
- "Should preserve trailing newline."
- (let ((result (test-dedent " line1\n line2\n" 4)))
- (should (string= result "line1\nline2\n"))))
-
-(ert-deftest test-dedent-preserves-internal-spaces ()
- "Should not affect internal whitespace."
- (let ((result (test-dedent " hello world" 4)))
- (should (string= result "hello world"))))
-
-;;; Round-trip Tests
-
-(ert-deftest test-indent-dedent-roundtrip ()
- "Should be able to indent then dedent back to original."
- (let* ((original "line1\nline2")
- (indented (test-indent original 4 nil))
- (dedented (test-dedent indented 4)))
- (should (string= dedented original))))
-
-(ert-deftest test-dedent-indent-roundtrip ()
- "Should be able to dedent then indent back to original."
- (let* ((original " line1\n line2")
- (dedented (test-dedent original 4))
- (indented (test-indent dedented 4 nil)))
- (should (string= indented original))))
-
-;;; Edge Cases
-
-(ert-deftest test-indent-very-long-line ()
- "Should indent very long line."
- (let* ((long-line (make-string 1000 ?a))
- (result (test-indent long-line 4 nil)))
- (should (string-prefix-p " " result))
- (should (= (length result) 1004))))
-
-(ert-deftest test-dedent-very-indented ()
- "Should dedent very indented line."
- (let* ((many-spaces (make-string 100 ?\s))
- (text (concat many-spaces "text"))
- (result (test-dedent text 50)))
- (should (string-prefix-p (make-string 50 ?\s) result))))
-
-(ert-deftest test-indent-with-existing-tabs ()
- "Should indent lines that already have tabs."
- (let ((result (test-indent "\tcode" 4 nil)))
- (should (string= result " \tcode"))))
-
-(ert-deftest test-dedent-stops-at-non-whitespace ()
- "Should stop dedenting at first non-whitespace character."
- (let ((result (test-dedent " a b" 4)))
- (should (string= result "a b"))))
-
-(provide 'test-custom-text-enclose-indent)
-;;; test-custom-text-enclose-indent.el ends here
diff --git a/tests/test-custom-text-enclose-prepend.el b/tests/test-custom-text-enclose-prepend.el
deleted file mode 100644
index e03375ff..00000000
--- a/tests/test-custom-text-enclose-prepend.el
+++ /dev/null
@@ -1,207 +0,0 @@
-;;; test-custom-text-enclose-prepend.el --- Tests for cj/--prepend-to-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--prepend-to-lines function from custom-text-enclose.el
-;;
-;; This function prepends a prefix string to the beginning of each line in text.
-;; It preserves the structure of lines and handles trailing newlines correctly.
-;;
-;; Examples:
-;; Input: "line1\nline2", prefix: "// "
-;; Output: "// line1\n// line2"
-;;
-;; Input: "single", prefix: "> "
-;; Output: "> single"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--prepend-to-lines) to avoid
-;; mocking region selection. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-prepend-to-lines (text prefix)
- "Test cj/--prepend-to-lines on TEXT with PREFIX.
-Returns the transformed string."
- (cj/--prepend-to-lines text prefix))
-
-;;; Normal Cases - Single Line
-
-(ert-deftest test-prepend-single-line ()
- "Should prepend to single line."
- (let ((result (test-prepend-to-lines "hello" "> ")))
- (should (string= result "> hello"))))
-
-(ert-deftest test-prepend-single-line-comment ()
- "Should prepend comment marker to single line."
- (let ((result (test-prepend-to-lines "code here" "// ")))
- (should (string= result "// code here"))))
-
-(ert-deftest test-prepend-single-line-bullet ()
- "Should prepend bullet to single line."
- (let ((result (test-prepend-to-lines "item" "- ")))
- (should (string= result "- item"))))
-
-;;; Normal Cases - Multiple Lines
-
-(ert-deftest test-prepend-two-lines ()
- "Should prepend to two lines."
- (let ((result (test-prepend-to-lines "line1\nline2" "> ")))
- (should (string= result "> line1\n> line2"))))
-
-(ert-deftest test-prepend-three-lines ()
- "Should prepend to three lines."
- (let ((result (test-prepend-to-lines "a\nb\nc" "* ")))
- (should (string= result "* a\n* b\n* c"))))
-
-(ert-deftest test-prepend-many-lines ()
- "Should prepend to many lines."
- (let* ((lines (make-list 10 "line"))
- (input (mapconcat #'identity lines "\n"))
- (result (test-prepend-to-lines input "# "))
- (result-lines (split-string result "\n")))
- (should (= 10 (length result-lines)))
- (should (cl-every (lambda (line) (string-prefix-p "# " line)) result-lines))))
-
-;;; Normal Cases - Various Prefixes
-
-(ert-deftest test-prepend-comment-marker ()
- "Should prepend comment marker."
- (let ((result (test-prepend-to-lines "line1\nline2" "// ")))
- (should (string= result "// line1\n// line2"))))
-
-(ert-deftest test-prepend-hash-comment ()
- "Should prepend hash comment."
- (let ((result (test-prepend-to-lines "line1\nline2" "# ")))
- (should (string= result "# line1\n# line2"))))
-
-(ert-deftest test-prepend-multi-char ()
- "Should prepend multi-character prefix."
- (let ((result (test-prepend-to-lines "line" "TODO: ")))
- (should (string= result "TODO: line"))))
-
-(ert-deftest test-prepend-empty-prefix ()
- "Should handle empty prefix."
- (let ((result (test-prepend-to-lines "line1\nline2" "")))
- (should (string= result "line1\nline2"))))
-
-;;; Boundary Cases - Trailing Newlines
-
-(ert-deftest test-prepend-with-trailing-newline ()
- "Should preserve trailing newline."
- (let ((result (test-prepend-to-lines "line1\nline2\n" "> ")))
- (should (string= result "> line1\n> line2\n"))))
-
-(ert-deftest test-prepend-no-trailing-newline ()
- "Should work without trailing newline."
- (let ((result (test-prepend-to-lines "line1\nline2" "> ")))
- (should (string= result "> line1\n> line2"))))
-
-(ert-deftest test-prepend-single-line-with-newline ()
- "Should preserve trailing newline on single line."
- (let ((result (test-prepend-to-lines "line\n" "> ")))
- (should (string= result "> line\n"))))
-
-;;; Boundary Cases - Empty Lines
-
-(ert-deftest test-prepend-empty-line-between ()
- "Should prepend to empty line between other lines."
- (let ((result (test-prepend-to-lines "line1\n\nline3" "> ")))
- (should (string= result "> line1\n> \n> line3"))))
-
-(ert-deftest test-prepend-only-empty-lines ()
- "Should prepend to only empty lines."
- (let ((result (test-prepend-to-lines "\n\n" "> ")))
- (should (string= result "> \n> \n"))))
-
-(ert-deftest test-prepend-empty-first-line ()
- "Should prepend to empty first line."
- (let ((result (test-prepend-to-lines "\nline2\nline3" "> ")))
- (should (string= result "> \n> line2\n> line3"))))
-
-;;; Boundary Cases - Whitespace
-
-(ert-deftest test-prepend-preserves-leading-whitespace ()
- "Should preserve leading whitespace after prefix."
- (let ((result (test-prepend-to-lines " line1\n line2" "// ")))
- (should (string= result "// line1\n// line2"))))
-
-(ert-deftest test-prepend-preserves-trailing-whitespace ()
- "Should preserve trailing whitespace on line."
- (let ((result (test-prepend-to-lines "line1 \nline2 " "> ")))
- (should (string= result "> line1 \n> line2 "))))
-
-(ert-deftest test-prepend-whitespace-only-line ()
- "Should prepend to whitespace-only line."
- (let ((result (test-prepend-to-lines "line1\n \nline3" "> ")))
- (should (string= result "> line1\n> \n> line3"))))
-
-;;; Boundary Cases - Special Cases
-
-(ert-deftest test-prepend-empty-string ()
- "Should handle empty string."
- (let ((result (test-prepend-to-lines "" "> ")))
- (should (string= result "> "))))
-
-(ert-deftest test-prepend-very-long-line ()
- "Should prepend to very long line."
- (let* ((long-line (make-string 1000 ?a))
- (result (test-prepend-to-lines long-line "> ")))
- (should (string-prefix-p "> " result))
- (should (= (length result) 1002))))
-
-(ert-deftest test-prepend-with-existing-prefix ()
- "Should prepend even if line already has the prefix."
- (let ((result (test-prepend-to-lines "> line" "> ")))
- (should (string= result "> > line"))))
-
-;;; Edge Cases - Special Characters in Prefix
-
-(ert-deftest test-prepend-newline-prefix ()
- "Should prepend newline as prefix."
- (let ((result (test-prepend-to-lines "line1\nline2" "\n")))
- (should (string= result "\nline1\n\nline2"))))
-
-(ert-deftest test-prepend-tab-prefix ()
- "Should prepend tab as prefix."
- (let ((result (test-prepend-to-lines "line1\nline2" "\t")))
- (should (string= result "\tline1\n\tline2"))))
-
-(ert-deftest test-prepend-quote-prefix ()
- "Should prepend quote as prefix."
- (let ((result (test-prepend-to-lines "line1\nline2" "\"")))
- (should (string= result "\"line1\n\"line2"))))
-
-;;; Edge Cases - Common Use Cases
-
-(ert-deftest test-prepend-markdown-quote ()
- "Should prepend markdown quote marker."
- (let ((result (test-prepend-to-lines "quote text\nmore text" "> ")))
- (should (string= result "> quote text\n> more text"))))
-
-(ert-deftest test-prepend-numbered-list ()
- "Should prepend numbers (though simpler uses would vary the prefix)."
- (let ((result (test-prepend-to-lines "item" "1. ")))
- (should (string= result "1. item"))))
-
-(ert-deftest test-prepend-indentation ()
- "Should prepend indentation spaces."
- (let ((result (test-prepend-to-lines "code\nmore" " ")))
- (should (string= result " code\n more"))))
-
-(provide 'test-custom-text-enclose-prepend)
-;;; test-custom-text-enclose-prepend.el ends here
diff --git a/tests/test-custom-text-enclose-surround.el b/tests/test-custom-text-enclose-surround.el
deleted file mode 100644
index dfed20a7..00000000
--- a/tests/test-custom-text-enclose-surround.el
+++ /dev/null
@@ -1,200 +0,0 @@
-;;; test-custom-text-enclose-surround.el --- Tests for cj/--surround -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--surround function from custom-text-enclose.el
-;;
-;; This function surrounds text with a given string.
-;; The surround string is both prepended and appended to the text.
-;;
-;; Examples:
-;; Input: "hello", surround: "\""
-;; Output: "\"hello\""
-;;
-;; Input: "world", surround: "**"
-;; Output: "**world**"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--surround) to avoid
-;; mocking user input. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-surround (text surround-string)
- "Test cj/--surround on TEXT with SURROUND-STRING.
-Returns the transformed string."
- (cj/--surround text surround-string))
-
-;;; Normal Cases - Common Surround Strings
-
-(ert-deftest test-surround-double-quotes ()
- "Should surround text with double quotes."
- (let ((result (test-surround "hello" "\"")))
- (should (string= result "\"hello\""))))
-
-(ert-deftest test-surround-single-quotes ()
- "Should surround text with single quotes."
- (let ((result (test-surround "world" "'")))
- (should (string= result "'world'"))))
-
-(ert-deftest test-surround-parentheses ()
- "Should surround text with parentheses."
- (let ((result (test-surround "text" "(")))
- (should (string= result "(text("))))
-
-(ert-deftest test-surround-square-brackets ()
- "Should surround text with square brackets."
- (let ((result (test-surround "item" "[")))
- (should (string= result "[item["))))
-
-(ert-deftest test-surround-asterisks ()
- "Should surround text with asterisks for markdown."
- (let ((result (test-surround "bold" "*")))
- (should (string= result "*bold*"))))
-
-(ert-deftest test-surround-double-asterisks ()
- "Should surround text with double asterisks."
- (let ((result (test-surround "bold" "**")))
- (should (string= result "**bold**"))))
-
-;;; Normal Cases - Multi-Character Surround Strings
-
-(ert-deftest test-surround-html-tag ()
- "Should surround text with HTML-like tags."
- (let ((result (test-surround "content" "<tag>")))
- (should (string= result "<tag>content<tag>"))))
-
-(ert-deftest test-surround-backticks ()
- "Should surround text with backticks for code."
- (let ((result (test-surround "code" "`")))
- (should (string= result "`code`"))))
-
-(ert-deftest test-surround-triple-backticks ()
- "Should surround text with triple backticks."
- (let ((result (test-surround "code block" "```")))
- (should (string= result "```code block```"))))
-
-(ert-deftest test-surround-custom-delimiter ()
- "Should surround text with custom delimiter."
- (let ((result (test-surround "data" "||")))
- (should (string= result "||data||"))))
-
-;;; Normal Cases - Various Text Content
-
-(ert-deftest test-surround-single-word ()
- "Should surround single word."
- (let ((result (test-surround "word" "\"")))
- (should (string= result "\"word\""))))
-
-(ert-deftest test-surround-multiple-words ()
- "Should surround multiple words."
- (let ((result (test-surround "hello world" "\"")))
- (should (string= result "\"hello world\""))))
-
-(ert-deftest test-surround-sentence ()
- "Should surround full sentence."
- (let ((result (test-surround "This is a sentence." "\"")))
- (should (string= result "\"This is a sentence.\""))))
-
-(ert-deftest test-surround-with-numbers ()
- "Should surround text with numbers."
- (let ((result (test-surround "123" "'")))
- (should (string= result "'123'"))))
-
-(ert-deftest test-surround-with-special-chars ()
- "Should surround text with special characters."
- (let ((result (test-surround "hello@world.com" "\"")))
- (should (string= result "\"hello@world.com\""))))
-
-;;; Normal Cases - Multiline Text
-
-(ert-deftest test-surround-multiline ()
- "Should surround multiline text."
- (let ((result (test-surround "line1\nline2\nline3" "\"")))
- (should (string= result "\"line1\nline2\nline3\""))))
-
-(ert-deftest test-surround-text-with-newlines ()
- "Should surround text containing newlines."
- (let ((result (test-surround "first\nsecond" "**")))
- (should (string= result "**first\nsecond**"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-surround-empty-string ()
- "Should surround empty string."
- (let ((result (test-surround "" "\"")))
- (should (string= result "\"\""))))
-
-(ert-deftest test-surround-single-character ()
- "Should surround single character."
- (let ((result (test-surround "x" "\"")))
- (should (string= result "\"x\""))))
-
-(ert-deftest test-surround-empty-surround-string ()
- "Should handle empty surround string."
- (let ((result (test-surround "hello" "")))
- (should (string= result "hello"))))
-
-(ert-deftest test-surround-very-long-text ()
- "Should surround very long text."
- (let* ((long-text (make-string 1000 ?a))
- (result (test-surround long-text "\"")))
- (should (string-prefix-p "\"" result))
- (should (string-suffix-p "\"" result))
- (should (= (length result) 1002))))
-
-(ert-deftest test-surround-whitespace-only ()
- "Should surround whitespace-only text."
- (let ((result (test-surround " " "\"")))
- (should (string= result "\" \""))))
-
-(ert-deftest test-surround-tabs ()
- "Should surround text with tabs."
- (let ((result (test-surround "\t\ttext\t\t" "\"")))
- (should (string= result "\"\t\ttext\t\t\""))))
-
-;;; Edge Cases - Already Surrounded
-
-(ert-deftest test-surround-already-quoted ()
- "Should surround text that is already quoted."
- (let ((result (test-surround "\"hello\"" "\"")))
- (should (string= result "\"\"hello\"\""))))
-
-(ert-deftest test-surround-nested ()
- "Should surround text creating nested delimiters."
- (let ((result (test-surround "'inner'" "\"")))
- (should (string= result "\"'inner'\""))))
-
-;;; Edge Cases - Special Surround Strings
-
-(ert-deftest test-surround-space ()
- "Should surround text with spaces."
- (let ((result (test-surround "text" " ")))
- (should (string= result " text "))))
-
-(ert-deftest test-surround-newline ()
- "Should surround text with newlines."
- (let ((result (test-surround "text" "\n")))
- (should (string= result "\ntext\n"))))
-
-(ert-deftest test-surround-mixed-delimiters ()
- "Should surround with mixed delimiter string."
- (let ((result (test-surround "content" "<>")))
- (should (string= result "<>content<>"))))
-
-(provide 'test-custom-text-enclose-surround)
-;;; test-custom-text-enclose-surround.el ends here
diff --git a/tests/test-custom-text-enclose-unwrap.el b/tests/test-custom-text-enclose-unwrap.el
deleted file mode 100644
index a308b644..00000000
--- a/tests/test-custom-text-enclose-unwrap.el
+++ /dev/null
@@ -1,266 +0,0 @@
-;;; test-custom-text-enclose-unwrap.el --- Tests for cj/--unwrap -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--unwrap function from custom-text-enclose.el
-;;
-;; This function removes surrounding delimiters from text.
-;; It checks if text starts with opening and ends with closing,
-;; and if so, removes them.
-;;
-;; Examples:
-;; Input: "(text)", opening: "(", closing: ")"
-;; Output: "text"
-;;
-;; Input: "<div>content</div>", opening: "<div>", closing: "</div>"
-;; Output: "content"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--unwrap) to avoid
-;; mocking user input. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-unwrap (text opening closing)
- "Test cj/--unwrap on TEXT with OPENING and CLOSING.
-Returns the transformed string."
- (cj/--unwrap text opening closing))
-
-;;; Normal Cases - Common Bracket Types
-
-(ert-deftest test-unwrap-parentheses ()
- "Should unwrap text with parentheses."
- (let ((result (test-unwrap "(text)" "(" ")")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-square-brackets ()
- "Should unwrap text with square brackets."
- (let ((result (test-unwrap "[item]" "[" "]")))
- (should (string= result "item"))))
-
-(ert-deftest test-unwrap-curly-braces ()
- "Should unwrap text with curly braces."
- (let ((result (test-unwrap "{code}" "{" "}")))
- (should (string= result "code"))))
-
-(ert-deftest test-unwrap-angle-brackets ()
- "Should unwrap text with angle brackets."
- (let ((result (test-unwrap "<tag>" "<" ">")))
- (should (string= result "tag"))))
-
-;;; Normal Cases - HTML/XML Tags
-
-(ert-deftest test-unwrap-html-div ()
- "Should unwrap HTML div tags."
- (let ((result (test-unwrap "<div>content</div>" "<div>" "</div>")))
- (should (string= result "content"))))
-
-(ert-deftest test-unwrap-html-span ()
- "Should unwrap HTML span tags."
- (let ((result (test-unwrap "<span>text</span>" "<span>" "</span>")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-xml-tag ()
- "Should unwrap XML tags."
- (let ((result (test-unwrap "<item>data</item>" "<item>" "</item>")))
- (should (string= result "data"))))
-
-(ert-deftest test-unwrap-html-with-attributes ()
- "Should unwrap HTML tag containing attributes."
- (let ((result (test-unwrap "<a href=\"url\">link</a>" "<a href=\"url\">" "</a>")))
- (should (string= result "link"))))
-
-;;; Normal Cases - Markdown Syntax
-
-(ert-deftest test-unwrap-markdown-bold ()
- "Should unwrap markdown bold syntax."
- (let ((result (test-unwrap "**bold**" "**" "**")))
- (should (string= result "bold"))))
-
-(ert-deftest test-unwrap-markdown-italic ()
- "Should unwrap markdown italic syntax."
- (let ((result (test-unwrap "*italic*" "*" "*")))
- (should (string= result "italic"))))
-
-(ert-deftest test-unwrap-markdown-code ()
- "Should unwrap markdown code syntax."
- (let ((result (test-unwrap "`code`" "`" "`")))
- (should (string= result "code"))))
-
-(ert-deftest test-unwrap-quotes ()
- "Should unwrap double quotes."
- (let ((result (test-unwrap "\"text\"" "\"" "\"")))
- (should (string= result "text"))))
-
-;;; Normal Cases - Various Content
-
-(ert-deftest test-unwrap-single-word ()
- "Should unwrap single word."
- (let ((result (test-unwrap "(word)" "(" ")")))
- (should (string= result "word"))))
-
-(ert-deftest test-unwrap-multiple-words ()
- "Should unwrap multiple words."
- (let ((result (test-unwrap "(hello world)" "(" ")")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-unwrap-sentence ()
- "Should unwrap full sentence."
- (let ((result (test-unwrap "(This is a sentence.)" "(" ")")))
- (should (string= result "This is a sentence."))))
-
-(ert-deftest test-unwrap-with-numbers ()
- "Should unwrap text with numbers."
- (let ((result (test-unwrap "[123]" "[" "]")))
- (should (string= result "123"))))
-
-(ert-deftest test-unwrap-with-special-chars ()
- "Should unwrap text with special characters."
- (let ((result (test-unwrap "<hello@world.com>" "<" ">")))
- (should (string= result "hello@world.com"))))
-
-;;; Normal Cases - Multiline Text
-
-(ert-deftest test-unwrap-multiline ()
- "Should unwrap multiline text."
- (let ((result (test-unwrap "<div>line1\nline2\nline3</div>" "<div>" "</div>")))
- (should (string= result "line1\nline2\nline3"))))
-
-(ert-deftest test-unwrap-text-with-newlines ()
- "Should unwrap text containing newlines."
- (let ((result (test-unwrap "(first\nsecond)" "(" ")")))
- (should (string= result "first\nsecond"))))
-
-;;; Boundary Cases - No Match
-
-(ert-deftest test-unwrap-no-opening ()
- "Should not unwrap when opening is missing."
- (let ((result (test-unwrap "text)" "(" ")")))
- (should (string= result "text)"))))
-
-(ert-deftest test-unwrap-no-closing ()
- "Should not unwrap when closing is missing."
- (let ((result (test-unwrap "(text" "(" ")")))
- (should (string= result "(text"))))
-
-(ert-deftest test-unwrap-neither-delimiter ()
- "Should not unwrap when neither delimiter is present."
- (let ((result (test-unwrap "text" "(" ")")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-wrong-opening ()
- "Should not unwrap with wrong opening delimiter."
- (let ((result (test-unwrap "[text)" "(" ")")))
- (should (string= result "[text)"))))
-
-(ert-deftest test-unwrap-wrong-closing ()
- "Should not unwrap with wrong closing delimiter."
- (let ((result (test-unwrap "(text]" "(" ")")))
- (should (string= result "(text]"))))
-
-;;; Boundary Cases - Empty
-
-(ert-deftest test-unwrap-empty-content ()
- "Should unwrap to empty string."
- (let ((result (test-unwrap "()" "(" ")")))
- (should (string= result ""))))
-
-(ert-deftest test-unwrap-just-delimiters ()
- "Should unwrap when only delimiters present."
- (let ((result (test-unwrap "[]" "[" "]")))
- (should (string= result ""))))
-
-(ert-deftest test-unwrap-empty-string ()
- "Should return empty string unchanged."
- (let ((result (test-unwrap "" "(" ")")))
- (should (string= result ""))))
-
-(ert-deftest test-unwrap-too-short ()
- "Should not unwrap when text is shorter than delimiters."
- (let ((result (test-unwrap "x" "<div>" "</div>")))
- (should (string= result "x"))))
-
-;;; Boundary Cases - Nested/Multiple
-
-(ert-deftest test-unwrap-nested-same ()
- "Should unwrap only outer layer of nested delimiters."
- (let ((result (test-unwrap "((text))" "(" ")")))
- (should (string= result "(text)"))))
-
-(ert-deftest test-unwrap-nested-different ()
- "Should unwrap outer layer with different inner delimiters."
- (let ((result (test-unwrap "([text])" "(" ")")))
- (should (string= result "[text]"))))
-
-(ert-deftest test-unwrap-multiple-in-content ()
- "Should not unwrap when delimiters appear in content."
- (let ((result (test-unwrap "(a)b(c)" "(" ")")))
- (should (string= result "a)b(c"))))
-
-;;; Edge Cases - Special Delimiters
-
-(ert-deftest test-unwrap-asymmetric-length ()
- "Should unwrap with different length delimiters."
- (let ((result (test-unwrap "<<text>>>" "<<" ">>>")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-multi-char-delimiters ()
- "Should unwrap with multi-character delimiters."
- (let ((result (test-unwrap "BEGINdataEND" "BEGIN" "END")))
- (should (string= result "data"))))
-
-(ert-deftest test-unwrap-space-delimiters ()
- "Should unwrap with space delimiters."
- (let ((result (test-unwrap " text " " " " ")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-newline-delimiters ()
- "Should unwrap with newline delimiters."
- (let ((result (test-unwrap "\ntext\n" "\n" "\n")))
- (should (string= result "text"))))
-
-;;; Edge Cases - Same Opening and Closing
-
-(ert-deftest test-unwrap-same-delimiters ()
- "Should unwrap when opening and closing are the same."
- (let ((result (test-unwrap "*text*" "*" "*")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-same-multi-char ()
- "Should unwrap same multi-char delimiters."
- (let ((result (test-unwrap "***text***" "***" "***")))
- (should (string= result "text"))))
-
-;;; Edge Cases - Empty Delimiters
-
-(ert-deftest test-unwrap-empty-opening ()
- "Should handle empty opening delimiter."
- (let ((result (test-unwrap "text)" "" ")")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-empty-closing ()
- "Should handle empty closing delimiter."
- (let ((result (test-unwrap "(text" "(" "")))
- (should (string= result "text"))))
-
-(ert-deftest test-unwrap-both-delimiters-empty ()
- "Should return text unchanged when both delimiters empty."
- (let ((result (test-unwrap "text" "" "")))
- (should (string= result "text"))))
-
-(provide 'test-custom-text-enclose-unwrap)
-;;; test-custom-text-enclose-unwrap.el ends here
diff --git a/tests/test-custom-text-enclose-wrap.el b/tests/test-custom-text-enclose-wrap.el
deleted file mode 100644
index f68a0668..00000000
--- a/tests/test-custom-text-enclose-wrap.el
+++ /dev/null
@@ -1,240 +0,0 @@
-;;; test-custom-text-enclose-wrap.el --- Tests for cj/--wrap -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--wrap function from custom-text-enclose.el
-;;
-;; This function wraps text with different opening and closing strings.
-;; Unlike surround which uses the same string on both sides, wrap allows
-;; asymmetric delimiters.
-;;
-;; Examples:
-;; Input: "content", opening: "<div>", closing: "</div>"
-;; Output: "<div>content</div>"
-;;
-;; Input: "text", opening: "(", closing: ")"
-;; Output: "(text)"
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--wrap) to avoid
-;; mocking user input. This follows our testing best practice of
-;; separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-text-enclose)
-
-;;; Test Helpers
-
-(defun test-wrap (text opening closing)
- "Test cj/--wrap on TEXT with OPENING and CLOSING.
-Returns the transformed string."
- (cj/--wrap text opening closing))
-
-;;; Normal Cases - Common Bracket Types
-
-(ert-deftest test-wrap-parentheses ()
- "Should wrap text with parentheses."
- (let ((result (test-wrap "text" "(" ")")))
- (should (string= result "(text)"))))
-
-(ert-deftest test-wrap-square-brackets ()
- "Should wrap text with square brackets."
- (let ((result (test-wrap "item" "[" "]")))
- (should (string= result "[item]"))))
-
-(ert-deftest test-wrap-curly-braces ()
- "Should wrap text with curly braces."
- (let ((result (test-wrap "code" "{" "}")))
- (should (string= result "{code}"))))
-
-(ert-deftest test-wrap-angle-brackets ()
- "Should wrap text with angle brackets."
- (let ((result (test-wrap "tag" "<" ">")))
- (should (string= result "<tag>"))))
-
-;;; Normal Cases - HTML/XML Tags
-
-(ert-deftest test-wrap-html-div ()
- "Should wrap text with HTML div tags."
- (let ((result (test-wrap "content" "<div>" "</div>")))
- (should (string= result "<div>content</div>"))))
-
-(ert-deftest test-wrap-html-span ()
- "Should wrap text with HTML span tags."
- (let ((result (test-wrap "text" "<span>" "</span>")))
- (should (string= result "<span>text</span>"))))
-
-(ert-deftest test-wrap-xml-tag ()
- "Should wrap text with XML tags."
- (let ((result (test-wrap "data" "<item>" "</item>")))
- (should (string= result "<item>data</item>"))))
-
-(ert-deftest test-wrap-html-with-attributes ()
- "Should wrap text with HTML tag containing attributes."
- (let ((result (test-wrap "link" "<a href=\"url\">" "</a>")))
- (should (string= result "<a href=\"url\">link</a>"))))
-
-;;; Normal Cases - Markdown Syntax
-
-(ert-deftest test-wrap-markdown-bold ()
- "Should wrap text with markdown bold syntax."
- (let ((result (test-wrap "bold" "**" "**")))
- (should (string= result "**bold**"))))
-
-(ert-deftest test-wrap-markdown-italic ()
- "Should wrap text with markdown italic syntax."
- (let ((result (test-wrap "italic" "*" "*")))
- (should (string= result "*italic*"))))
-
-(ert-deftest test-wrap-markdown-code ()
- "Should wrap text with markdown code syntax."
- (let ((result (test-wrap "code" "`" "`")))
- (should (string= result "`code`"))))
-
-(ert-deftest test-wrap-markdown-link ()
- "Should wrap text with markdown link syntax."
- (let ((result (test-wrap "text" "[" "](url)")))
- (should (string= result "[text](url)"))))
-
-;;; Normal Cases - Various Content
-
-(ert-deftest test-wrap-single-word ()
- "Should wrap single word."
- (let ((result (test-wrap "word" "(" ")")))
- (should (string= result "(word)"))))
-
-(ert-deftest test-wrap-multiple-words ()
- "Should wrap multiple words."
- (let ((result (test-wrap "hello world" "(" ")")))
- (should (string= result "(hello world)"))))
-
-(ert-deftest test-wrap-sentence ()
- "Should wrap full sentence."
- (let ((result (test-wrap "This is a sentence." "(" ")")))
- (should (string= result "(This is a sentence.)"))))
-
-(ert-deftest test-wrap-with-numbers ()
- "Should wrap text with numbers."
- (let ((result (test-wrap "123" "[" "]")))
- (should (string= result "[123]"))))
-
-(ert-deftest test-wrap-with-special-chars ()
- "Should wrap text with special characters."
- (let ((result (test-wrap "hello@world.com" "<" ">")))
- (should (string= result "<hello@world.com>"))))
-
-;;; Normal Cases - Multiline Text
-
-(ert-deftest test-wrap-multiline ()
- "Should wrap multiline text."
- (let ((result (test-wrap "line1\nline2\nline3" "<div>" "</div>")))
- (should (string= result "<div>line1\nline2\nline3</div>"))))
-
-(ert-deftest test-wrap-text-with-newlines ()
- "Should wrap text containing newlines."
- (let ((result (test-wrap "first\nsecond" "(" ")")))
- (should (string= result "(first\nsecond)"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-wrap-empty-string ()
- "Should wrap empty string."
- (let ((result (test-wrap "" "(" ")")))
- (should (string= result "()"))))
-
-(ert-deftest test-wrap-single-character ()
- "Should wrap single character."
- (let ((result (test-wrap "x" "[" "]")))
- (should (string= result "[x]"))))
-
-(ert-deftest test-wrap-empty-opening ()
- "Should handle empty opening delimiter."
- (let ((result (test-wrap "text" "" ")")))
- (should (string= result "text)"))))
-
-(ert-deftest test-wrap-empty-closing ()
- "Should handle empty closing delimiter."
- (let ((result (test-wrap "text" "(" "")))
- (should (string= result "(text"))))
-
-(ert-deftest test-wrap-both-empty ()
- "Should handle both delimiters empty."
- (let ((result (test-wrap "text" "" "")))
- (should (string= result "text"))))
-
-(ert-deftest test-wrap-very-long-text ()
- "Should wrap very long text."
- (let* ((long-text (make-string 1000 ?a))
- (result (test-wrap long-text "(" ")")))
- (should (string-prefix-p "(" result))
- (should (string-suffix-p ")" result))
- (should (= (length result) 1002))))
-
-(ert-deftest test-wrap-whitespace-only ()
- "Should wrap whitespace-only text."
- (let ((result (test-wrap " " "(" ")")))
- (should (string= result "( )"))))
-
-(ert-deftest test-wrap-tabs ()
- "Should wrap text with tabs."
- (let ((result (test-wrap "\t\ttext\t\t" "[" "]")))
- (should (string= result "[\t\ttext\t\t]"))))
-
-;;; Edge Cases - Already Wrapped
-
-(ert-deftest test-wrap-already-wrapped ()
- "Should wrap text that is already wrapped."
- (let ((result (test-wrap "(hello)" "[" "]")))
- (should (string= result "[(hello)]"))))
-
-(ert-deftest test-wrap-nested ()
- "Should wrap text creating nested delimiters."
- (let ((result (test-wrap "[inner]" "(" ")")))
- (should (string= result "([inner])"))))
-
-;;; Edge Cases - Special Delimiters
-
-(ert-deftest test-wrap-asymmetric-length ()
- "Should wrap with different length delimiters."
- (let ((result (test-wrap "text" "<<" ">>>")))
- (should (string= result "<<text>>>"))))
-
-(ert-deftest test-wrap-multi-char-delimiters ()
- "Should wrap with multi-character delimiters."
- (let ((result (test-wrap "data" "BEGIN" "END")))
- (should (string= result "BEGINdataEND"))))
-
-(ert-deftest test-wrap-space-delimiters ()
- "Should wrap with space delimiters."
- (let ((result (test-wrap "text" " " " ")))
- (should (string= result " text "))))
-
-(ert-deftest test-wrap-newline-delimiters ()
- "Should wrap with newline delimiters."
- (let ((result (test-wrap "text" "\n" "\n")))
- (should (string= result "\ntext\n"))))
-
-(ert-deftest test-wrap-quote-delimiters ()
- "Should wrap with quote delimiters."
- (let ((result (test-wrap "text" "\"" "\"")))
- (should (string= result "\"text\""))))
-
-;;; Edge Cases - Same Opening and Closing
-
-(ert-deftest test-wrap-same-delimiters ()
- "Should work like surround when delimiters are the same."
- (let ((result (test-wrap "text" "*" "*")))
- (should (string= result "*text*"))))
-
-(provide 'test-custom-text-enclose-wrap)
-;;; test-custom-text-enclose-wrap.el ends here
diff --git a/tests/test-custom-whitespace-collapse.el b/tests/test-custom-whitespace-collapse.el
deleted file mode 100644
index 40face95..00000000
--- a/tests/test-custom-whitespace-collapse.el
+++ /dev/null
@@ -1,150 +0,0 @@
-;;; test-custom-whitespace-collapse.el --- Tests for cj/--collapse-whitespace -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--collapse-whitespace function from custom-whitespace.el
-;;
-;; This function collapses whitespace in text by:
-;; - Converting all tabs to spaces
-;; - Removing leading and trailing whitespace
-;; - Collapsing multiple consecutive spaces to single space
-;; - Preserving newlines and text structure
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--collapse-whitespace)
-;; to avoid mocking region selection. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-collapse-whitespace (input-text)
- "Test cj/--collapse-whitespace on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--collapse-whitespace (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-collapse-whitespace-multiple-spaces ()
- "Should collapse multiple spaces to single space."
- (let ((result (test-collapse-whitespace "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-multiple-tabs ()
- "Should convert tabs to spaces and collapse."
- (let ((result (test-collapse-whitespace "hello\t\t\tworld")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-mixed-tabs-spaces ()
- "Should handle mixed tabs and spaces."
- (let ((result (test-collapse-whitespace "hello \t \t world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-leading-trailing ()
- "Should remove leading and trailing whitespace."
- (let ((result (test-collapse-whitespace " hello world ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-tabs-leading-trailing ()
- "Should remove leading and trailing tabs."
- (let ((result (test-collapse-whitespace "\t\thello world\t\t")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-multiple-words ()
- "Should collapse spaces between multiple words."
- (let ((result (test-collapse-whitespace "one two three four")))
- (should (string= result "one two three four"))))
-
-(ert-deftest test-collapse-whitespace-preserve-newlines ()
- "Should preserve newlines while collapsing spaces."
- (let ((result (test-collapse-whitespace "hello world\nfoo bar")))
- (should (string= result "hello world\nfoo bar"))))
-
-(ert-deftest test-collapse-whitespace-multiple-lines ()
- "Should handle multiple lines with various whitespace."
- (let ((result (test-collapse-whitespace " hello world \n\t\tfoo bar\t\t")))
- (should (string= result "hello world\nfoo bar"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-collapse-whitespace-empty-string ()
- "Should handle empty string."
- (let ((result (test-collapse-whitespace "")))
- (should (string= result ""))))
-
-(ert-deftest test-collapse-whitespace-single-char ()
- "Should handle single character with surrounding spaces."
- (let ((result (test-collapse-whitespace " x ")))
- (should (string= result "x"))))
-
-(ert-deftest test-collapse-whitespace-only-whitespace ()
- "Should handle text with only whitespace (becomes empty)."
- (let ((result (test-collapse-whitespace " \t \t ")))
- (should (string= result ""))))
-
-(ert-deftest test-collapse-whitespace-no-extra-whitespace ()
- "Should handle text with no extra whitespace (no-op)."
- (let ((result (test-collapse-whitespace "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-collapse-whitespace-single-space ()
- "Should handle text with already-collapsed spaces (no-op)."
- (let ((result (test-collapse-whitespace "one two three")))
- (should (string= result "one two three"))))
-
-(ert-deftest test-collapse-whitespace-very-long-line ()
- "Should handle very long lines with many spaces."
- (let ((result (test-collapse-whitespace "word word word word word")))
- (should (string= result "word word word word word"))))
-
-(ert-deftest test-collapse-whitespace-multiple-newlines ()
- "Should preserve multiple newlines while removing spaces."
- (let ((result (test-collapse-whitespace "hello world\n\n\nfoo bar")))
- (should (string= result "hello world\n\n\nfoo bar"))))
-
-(ert-deftest test-collapse-whitespace-spaces-around-newlines ()
- "Should remove spaces before/after newlines."
- (let ((result (test-collapse-whitespace "hello \n world")))
- (should (string= result "hello\nworld"))))
-
-(ert-deftest test-collapse-whitespace-empty-lines ()
- "Should handle empty lines (lines become empty after whitespace removal)."
- (let ((result (test-collapse-whitespace "line1\n \nline2")))
- (should (string= result "line1\n\nline2"))))
-
-;;; Error Cases
-
-(ert-deftest test-collapse-whitespace-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--collapse-whitespace (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-collapse-whitespace-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--collapse-whitespace pos pos)
- ;; Should complete without error and not change buffer
- (should (string= (buffer-string) "hello world")))))
-
-(provide 'test-custom-whitespace-collapse)
-;;; test-custom-whitespace-collapse.el ends here
diff --git a/tests/test-custom-whitespace-delete-all.el b/tests/test-custom-whitespace-delete-all.el
deleted file mode 100644
index 00abb1d4..00000000
--- a/tests/test-custom-whitespace-delete-all.el
+++ /dev/null
@@ -1,150 +0,0 @@
-;;; test-custom-whitespace-delete-all.el --- Tests for cj/--delete-all-whitespace -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--delete-all-whitespace function from custom-whitespace.el
-;;
-;; This function removes ALL whitespace characters from the region:
-;; spaces, tabs, newlines, and carriage returns. Useful for creating
-;; compact identifiers or removing all formatting.
-;;
-;; Uses the regexp [ \t\n\r]+ to match all whitespace.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--delete-all-whitespace)
-;; to avoid mocking region selection. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-delete-all-whitespace (input-text)
- "Test cj/--delete-all-whitespace on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--delete-all-whitespace (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-delete-all-whitespace-single-space ()
- "Should remove single space."
- (let ((result (test-delete-all-whitespace "hello world")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-multiple-spaces ()
- "Should remove multiple spaces."
- (let ((result (test-delete-all-whitespace "hello world")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-tabs ()
- "Should remove tabs."
- (let ((result (test-delete-all-whitespace "hello\tworld")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-newlines ()
- "Should remove newlines (joining lines)."
- (let ((result (test-delete-all-whitespace "hello\nworld")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-mixed ()
- "Should remove all types of whitespace."
- (let ((result (test-delete-all-whitespace "hello \t\n world")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-multiple-words ()
- "Should remove whitespace from multiple words."
- (let ((result (test-delete-all-whitespace "one two three four")))
- (should (string= result "onetwothreefour"))))
-
-(ert-deftest test-delete-all-whitespace-multiline ()
- "Should remove all whitespace across multiple lines."
- (let ((result (test-delete-all-whitespace "line1\nline2\nline3")))
- (should (string= result "line1line2line3"))))
-
-(ert-deftest test-delete-all-whitespace-leading-trailing ()
- "Should remove leading and trailing whitespace."
- (let ((result (test-delete-all-whitespace " hello world ")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-carriage-returns ()
- "Should handle carriage returns."
- (let ((result (test-delete-all-whitespace "hello\r\nworld")))
- (should (string= result "helloworld"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-delete-all-whitespace-empty-string ()
- "Should handle empty string."
- (let ((result (test-delete-all-whitespace "")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-all-whitespace-no-whitespace ()
- "Should handle text with no whitespace (no-op)."
- (let ((result (test-delete-all-whitespace "helloworld")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-only-whitespace ()
- "Should delete all content when only whitespace exists."
- (let ((result (test-delete-all-whitespace " \t \n ")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-all-whitespace-single-char ()
- "Should handle single character with surrounding whitespace."
- (let ((result (test-delete-all-whitespace " x ")))
- (should (string= result "x"))))
-
-(ert-deftest test-delete-all-whitespace-very-long-text ()
- "Should handle very long text."
- (let ((result (test-delete-all-whitespace "word word word word word word word word")))
- (should (string= result "wordwordwordwordwordwordwordword"))))
-
-(ert-deftest test-delete-all-whitespace-single-whitespace ()
- "Should delete single whitespace character."
- (let ((result (test-delete-all-whitespace " ")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-all-whitespace-consecutive-newlines ()
- "Should remove all consecutive newlines."
- (let ((result (test-delete-all-whitespace "hello\n\n\nworld")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-delete-all-whitespace-complex-structure ()
- "Should handle complex whitespace patterns."
- (let ((result (test-delete-all-whitespace " hello\n\t world \n foo\t\tbar ")))
- (should (string= result "helloworldfoobar"))))
-
-;;; Error Cases
-
-(ert-deftest test-delete-all-whitespace-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--delete-all-whitespace (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-delete-all-whitespace-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--delete-all-whitespace pos pos)
- ;; Should complete without error and not change buffer
- (should (string= (buffer-string) "hello world")))))
-
-(provide 'test-custom-whitespace-delete-all)
-;;; test-custom-whitespace-delete-all.el ends here
diff --git a/tests/test-custom-whitespace-delete-blank-lines.el b/tests/test-custom-whitespace-delete-blank-lines.el
deleted file mode 100644
index 2d250521..00000000
--- a/tests/test-custom-whitespace-delete-blank-lines.el
+++ /dev/null
@@ -1,146 +0,0 @@
-;;; test-custom-whitespace-delete-blank-lines.el --- Tests for cj/--delete-blank-lines -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--delete-blank-lines function from custom-whitespace.el
-;;
-;; This function deletes blank lines from text, where blank lines are defined
-;; as lines containing only whitespace (spaces, tabs) or nothing at all.
-;; Uses the regexp ^[[:space:]]*$ to match blank lines.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--delete-blank-lines)
-;; to avoid mocking user prompts. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-delete-blank-lines (input-text)
- "Test cj/--delete-blank-lines on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--delete-blank-lines (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-delete-blank-lines-single-blank ()
- "Should delete single blank line between text."
- (let ((result (test-delete-blank-lines "line1\n\nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-multiple-consecutive ()
- "Should delete multiple consecutive blank lines."
- (let ((result (test-delete-blank-lines "line1\n\n\n\nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-spaces-only ()
- "Should delete lines with spaces only."
- (let ((result (test-delete-blank-lines "line1\n \nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-tabs-only ()
- "Should delete lines with tabs only."
- (let ((result (test-delete-blank-lines "line1\n\t\t\nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-mixed-whitespace ()
- "Should delete lines with mixed whitespace."
- (let ((result (test-delete-blank-lines "line1\n \t \t \nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-no-blank-lines ()
- "Should handle text with no blank lines (no-op)."
- (let ((result (test-delete-blank-lines "line1\nline2\nline3")))
- (should (string= result "line1\nline2\nline3"))))
-
-(ert-deftest test-delete-blank-lines-at-start ()
- "Should delete blank lines at start of region."
- (let ((result (test-delete-blank-lines "\n\nline1\nline2")))
- (should (string= result "line1\nline2"))))
-
-(ert-deftest test-delete-blank-lines-at-end ()
- "Should delete blank lines at end of region."
- (let ((result (test-delete-blank-lines "line1\nline2\n\n")))
- (should (string= result "line1\nline2\n"))))
-
-(ert-deftest test-delete-blank-lines-scattered ()
- "Should delete blank lines scattered throughout text."
- (let ((result (test-delete-blank-lines "line1\n\nline2\n \nline3\n\t\nline4")))
- (should (string= result "line1\nline2\nline3\nline4"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-delete-blank-lines-empty-string ()
- "Should handle empty string."
- (let ((result (test-delete-blank-lines "")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-blank-lines-only-blank-lines ()
- "Should delete all lines if only blank lines exist."
- (let ((result (test-delete-blank-lines "\n\n\n")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-blank-lines-only-whitespace ()
- "Should delete lines containing only whitespace."
- (let ((result (test-delete-blank-lines " \n\t\t\n \t ")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-blank-lines-single-line-content ()
- "Should handle single line with content (no-op)."
- (let ((result (test-delete-blank-lines "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-delete-blank-lines-single-blank-line ()
- "Should delete single blank line."
- (let ((result (test-delete-blank-lines "\n")))
- (should (string= result ""))))
-
-(ert-deftest test-delete-blank-lines-very-long-region ()
- "Should handle very long region with many blank lines."
- (let* ((lines (make-list 100 "content"))
- (input (mapconcat #'identity lines "\n\n"))
- (expected (mapconcat #'identity lines "\n"))
- (result (test-delete-blank-lines input)))
- (should (string= result expected))))
-
-(ert-deftest test-delete-blank-lines-preserve-content-lines ()
- "Should preserve lines with any non-whitespace content."
- (let ((result (test-delete-blank-lines "x\n\ny\n \nz")))
- (should (string= result "x\ny\nz"))))
-
-;;; Error Cases
-
-(ert-deftest test-delete-blank-lines-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "line1\n\nline2")
- (cj/--delete-blank-lines (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-delete-blank-lines-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "line1\n\nline2")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--delete-blank-lines pos pos)
- ;; Should complete without error
- (should (string-match-p "line1" (buffer-string))))))
-
-(provide 'test-custom-whitespace-delete-blank-lines)
-;;; test-custom-whitespace-delete-blank-lines.el ends here
diff --git a/tests/test-custom-whitespace-ensure-single-blank.el b/tests/test-custom-whitespace-ensure-single-blank.el
deleted file mode 100644
index 7cd03e79..00000000
--- a/tests/test-custom-whitespace-ensure-single-blank.el
+++ /dev/null
@@ -1,146 +0,0 @@
-;;; test-custom-whitespace-ensure-single-blank.el --- Tests for cj/--ensure-single-blank-line -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--ensure-single-blank-line function from custom-whitespace.el
-;;
-;; This function collapses multiple consecutive blank lines to exactly one blank line.
-;; Different from delete-blank-lines which removes ALL blank lines, this function
-;; preserves blank lines but ensures no more than one blank line appears consecutively.
-;;
-;; A blank line is defined as a line containing only whitespace (spaces, tabs) or nothing.
-;; Uses the regexp (^[[:space:]]*$\n){2,} to match 2+ consecutive blank lines.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--ensure-single-blank-line)
-;; to avoid mocking user prompts. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-ensure-single-blank-line (input-text)
- "Test cj/--ensure-single-blank-line on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--ensure-single-blank-line (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-ensure-single-blank-two-blanks ()
- "Should collapse two blank lines to one."
- (let ((result (test-ensure-single-blank-line "line1\n\n\nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-three-blanks ()
- "Should collapse three blank lines to one."
- (let ((result (test-ensure-single-blank-line "line1\n\n\n\nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-many-blanks ()
- "Should collapse many blank lines to one."
- (let ((result (test-ensure-single-blank-line "line1\n\n\n\n\n\n\nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-preserve-single ()
- "Should preserve single blank lines (no-op)."
- (let ((result (test-ensure-single-blank-line "line1\n\nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-multiple-groups ()
- "Should handle multiple groups of consecutive blanks."
- (let ((result (test-ensure-single-blank-line "line1\n\n\nline2\n\n\n\nline3")))
- (should (string= result "line1\n\nline2\n\nline3"))))
-
-(ert-deftest test-ensure-single-blank-blanks-with-spaces ()
- "Should handle blank lines with spaces only."
- (let ((result (test-ensure-single-blank-line "line1\n \n \nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-blanks-with-tabs ()
- "Should handle blank lines with tabs only."
- (let ((result (test-ensure-single-blank-line "line1\n\t\t\n\t\t\nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-mixed-whitespace ()
- "Should handle blank lines with mixed whitespace."
- (let ((result (test-ensure-single-blank-line "line1\n \t \n \t \nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-ensure-single-blank-no-blanks ()
- "Should handle text with no blank lines (no-op)."
- (let ((result (test-ensure-single-blank-line "line1\nline2\nline3")))
- (should (string= result "line1\nline2\nline3"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-ensure-single-blank-empty-string ()
- "Should handle empty string."
- (let ((result (test-ensure-single-blank-line "")))
- (should (string= result ""))))
-
-(ert-deftest test-ensure-single-blank-only-blanks ()
- "Should collapse many blank lines to one blank line."
- (let ((result (test-ensure-single-blank-line "\n\n\n\n")))
- (should (string= result "\n\n"))))
-
-(ert-deftest test-ensure-single-blank-at-start ()
- "Should collapse multiple blank lines at start to one."
- (let ((result (test-ensure-single-blank-line "\n\n\nline1")))
- (should (string= result "\n\nline1"))))
-
-(ert-deftest test-ensure-single-blank-at-end ()
- "Should collapse multiple blank lines at end to one."
- (let ((result (test-ensure-single-blank-line "line1\n\n\n")))
- (should (string= result "line1\n\n"))))
-
-(ert-deftest test-ensure-single-blank-single-line ()
- "Should handle single line (no-op)."
- (let ((result (test-ensure-single-blank-line "line1")))
- (should (string= result "line1"))))
-
-(ert-deftest test-ensure-single-blank-complex-structure ()
- "Should handle complex mix of content and blanks."
- (let ((result (test-ensure-single-blank-line "line1\n\n\nline2\nline3\n\n\n\nline4")))
- (should (string= result "line1\n\nline2\nline3\n\nline4"))))
-
-(ert-deftest test-ensure-single-blank-preserves-content ()
- "Should not modify lines with content."
- (let ((result (test-ensure-single-blank-line " line1 \n\n\n line2 ")))
- (should (string= result " line1 \n\n line2 "))))
-
-;;; Error Cases
-
-(ert-deftest test-ensure-single-blank-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "line1\n\n\nline2")
- (cj/--ensure-single-blank-line (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-ensure-single-blank-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "line1\n\n\nline2")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--ensure-single-blank-line pos pos)
- ;; Should complete without error
- (should (string-match-p "line1" (buffer-string))))))
-
-(provide 'test-custom-whitespace-ensure-single-blank)
-;;; test-custom-whitespace-ensure-single-blank.el ends here
diff --git a/tests/test-custom-whitespace-hyphenate.el b/tests/test-custom-whitespace-hyphenate.el
deleted file mode 100644
index 03462fab..00000000
--- a/tests/test-custom-whitespace-hyphenate.el
+++ /dev/null
@@ -1,140 +0,0 @@
-;;; test-custom-whitespace-hyphenate.el --- Tests for cj/--hyphenate-whitespace -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--hyphenate-whitespace function from custom-whitespace.el
-;;
-;; This function replaces all runs of whitespace (spaces, tabs, newlines,
-;; carriage returns) with single hyphens. Useful for converting text with
-;; whitespace into hyphenated identifiers or URLs.
-;;
-;; Uses the regexp [ \t\n\r]+ to match whitespace runs.
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--hyphenate-whitespace)
-;; to avoid mocking region selection. This follows our testing best practice
-;; of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-hyphenate-whitespace (input-text)
- "Test cj/--hyphenate-whitespace on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--hyphenate-whitespace (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-hyphenate-whitespace-single-space ()
- "Should replace single space with hyphen."
- (let ((result (test-hyphenate-whitespace "hello world")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-multiple-spaces ()
- "Should replace multiple spaces with single hyphen."
- (let ((result (test-hyphenate-whitespace "hello world")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-tabs ()
- "Should replace tabs with hyphen."
- (let ((result (test-hyphenate-whitespace "hello\tworld")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-mixed-tabs-spaces ()
- "Should replace mixed tabs and spaces with single hyphen."
- (let ((result (test-hyphenate-whitespace "hello \t world")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-newlines ()
- "Should replace newlines with hyphen (joining lines)."
- (let ((result (test-hyphenate-whitespace "hello\nworld")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-multiple-newlines ()
- "Should replace multiple newlines with single hyphen."
- (let ((result (test-hyphenate-whitespace "hello\n\n\nworld")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-multiple-words ()
- "Should hyphenate multiple words with various whitespace."
- (let ((result (test-hyphenate-whitespace "one two three\tfour\nfive")))
- (should (string= result "one-two-three-four-five"))))
-
-(ert-deftest test-hyphenate-whitespace-carriage-returns ()
- "Should handle carriage returns."
- (let ((result (test-hyphenate-whitespace "hello\r\nworld")))
- (should (string= result "hello-world"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-hyphenate-whitespace-empty-string ()
- "Should handle empty string."
- (let ((result (test-hyphenate-whitespace "")))
- (should (string= result ""))))
-
-(ert-deftest test-hyphenate-whitespace-no-whitespace ()
- "Should handle text with no whitespace (no-op)."
- (let ((result (test-hyphenate-whitespace "helloworld")))
- (should (string= result "helloworld"))))
-
-(ert-deftest test-hyphenate-whitespace-only-whitespace ()
- "Should convert text with only whitespace to single hyphen."
- (let ((result (test-hyphenate-whitespace " \t \n ")))
- (should (string= result "-"))))
-
-(ert-deftest test-hyphenate-whitespace-single-char ()
- "Should handle single character with surrounding spaces."
- (let ((result (test-hyphenate-whitespace " x ")))
- (should (string= result "-x-"))))
-
-(ert-deftest test-hyphenate-whitespace-very-long-text ()
- "Should handle very long text with many spaces."
- (let ((result (test-hyphenate-whitespace "word word word word word word word word")))
- (should (string= result "word-word-word-word-word-word-word-word"))))
-
-(ert-deftest test-hyphenate-whitespace-leading-whitespace ()
- "Should replace leading whitespace with hyphen."
- (let ((result (test-hyphenate-whitespace " hello world")))
- (should (string= result "-hello-world"))))
-
-(ert-deftest test-hyphenate-whitespace-trailing-whitespace ()
- "Should replace trailing whitespace with hyphen."
- (let ((result (test-hyphenate-whitespace "hello world ")))
- (should (string= result "hello-world-"))))
-
-;;; Error Cases
-
-(ert-deftest test-hyphenate-whitespace-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--hyphenate-whitespace (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-hyphenate-whitespace-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--hyphenate-whitespace pos pos)
- ;; Should complete without error and not change buffer
- (should (string= (buffer-string) "hello world")))))
-
-(provide 'test-custom-whitespace-hyphenate)
-;;; test-custom-whitespace-hyphenate.el ends here
diff --git a/tests/test-custom-whitespace-remove-leading-trailing.el b/tests/test-custom-whitespace-remove-leading-trailing.el
deleted file mode 100644
index 5a846e7f..00000000
--- a/tests/test-custom-whitespace-remove-leading-trailing.el
+++ /dev/null
@@ -1,157 +0,0 @@
-;;; test-custom-whitespace-remove-leading-trailing.el --- Tests for cj/--remove-leading-trailing-whitespace -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--remove-leading-trailing-whitespace function from custom-whitespace.el
-;;
-;; This function removes leading and trailing whitespace (spaces and tabs) from text.
-;; - Removes leading whitespace: ^[ \t]+
-;; - Removes trailing whitespace: [ \t]+$
-;; - Preserves interior whitespace
-;; - Operates on any region defined by START and END
-;;
-;; We test the NON-INTERACTIVE implementation (cj/--remove-leading-trailing-whitespace)
-;; to avoid mocking region selection and prefix arguments. This follows our testing
-;; best practice of separating business logic from UI interaction.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'custom-whitespace)
-
-;;; Test Helpers
-
-(defun test-remove-leading-trailing (input-text)
- "Test cj/--remove-leading-trailing-whitespace on INPUT-TEXT.
-Returns the buffer string after operation."
- (with-temp-buffer
- (insert input-text)
- (cj/--remove-leading-trailing-whitespace (point-min) (point-max))
- (buffer-string)))
-
-;;; Normal Cases
-
-(ert-deftest test-remove-leading-trailing-leading-spaces ()
- "Should remove leading spaces from single line."
- (let ((result (test-remove-leading-trailing " hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-trailing-spaces ()
- "Should remove trailing spaces from single line."
- (let ((result (test-remove-leading-trailing "hello world ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-both-spaces ()
- "Should remove both leading and trailing spaces."
- (let ((result (test-remove-leading-trailing " hello world ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-leading-tabs ()
- "Should remove leading tabs from single line."
- (let ((result (test-remove-leading-trailing "\t\thello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-trailing-tabs ()
- "Should remove trailing tabs from single line."
- (let ((result (test-remove-leading-trailing "hello world\t\t")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-mixed-tabs-spaces ()
- "Should remove mixed tabs and spaces."
- (let ((result (test-remove-leading-trailing " \t hello world \t ")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-preserve-interior ()
- "Should preserve interior whitespace."
- (let ((result (test-remove-leading-trailing " hello world \t")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-multiple-lines ()
- "Should handle multiple lines with leading/trailing whitespace."
- (let ((result (test-remove-leading-trailing " line1 \n\t\tline2\t\n line3 ")))
- (should (string= result "line1\nline2\nline3"))))
-
-(ert-deftest test-remove-leading-trailing-multiline-preserve-interior ()
- "Should preserve interior whitespace on multiple lines."
- (let ((result (test-remove-leading-trailing " hello world \n foo bar ")))
- (should (string= result "hello world\nfoo bar"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-remove-leading-trailing-empty-string ()
- "Should handle empty string."
- (let ((result (test-remove-leading-trailing "")))
- (should (string= result ""))))
-
-(ert-deftest test-remove-leading-trailing-single-char ()
- "Should handle single character with surrounding spaces."
- (let ((result (test-remove-leading-trailing " x ")))
- (should (string= result "x"))))
-
-(ert-deftest test-remove-leading-trailing-only-whitespace ()
- "Should handle lines with only whitespace."
- (let ((result (test-remove-leading-trailing " \t ")))
- (should (string= result ""))))
-
-(ert-deftest test-remove-leading-trailing-no-whitespace ()
- "Should handle text with no leading/trailing whitespace (no-op)."
- (let ((result (test-remove-leading-trailing "hello world")))
- (should (string= result "hello world"))))
-
-(ert-deftest test-remove-leading-trailing-very-long-line ()
- "Should handle very long lines with whitespace."
- (let* ((long-text (make-string 500 ?x))
- (input (concat " " long-text " "))
- (result (test-remove-leading-trailing input)))
- (should (string= result long-text))))
-
-(ert-deftest test-remove-leading-trailing-whitespace-between-lines ()
- "Should handle lines that become empty after removal."
- (let ((result (test-remove-leading-trailing "line1\n \nline2")))
- (should (string= result "line1\n\nline2"))))
-
-(ert-deftest test-remove-leading-trailing-newlines-only ()
- "Should preserve newlines while removing spaces."
- (let ((result (test-remove-leading-trailing "\n\n\n")))
- (should (string= result "\n\n\n"))))
-
-(ert-deftest test-remove-leading-trailing-partial-region ()
- "Should work on partial buffer region."
- (with-temp-buffer
- (insert " hello \n world \n test ")
- ;; Only operate on middle line
- (let ((start (+ (point-min) 10)) ; Start of second line
- (end (+ (point-min) 19))) ; End of second line
- (cj/--remove-leading-trailing-whitespace start end)
- (should (string= (buffer-string) " hello \nworld\n test ")))))
-
-;;; Error Cases
-
-(ert-deftest test-remove-leading-trailing-start-greater-than-end ()
- "Should error when start > end."
- (should-error
- (with-temp-buffer
- (insert "hello world")
- (cj/--remove-leading-trailing-whitespace (point-max) (point-min)))
- :type 'error))
-
-(ert-deftest test-remove-leading-trailing-empty-region ()
- "Should handle empty region (start == end) without error."
- (with-temp-buffer
- (insert "hello world")
- (let ((pos (/ (+ (point-min) (point-max)) 2)))
- (cj/--remove-leading-trailing-whitespace pos pos)
- ;; Should complete without error and not change buffer
- (should (string= (buffer-string) "hello world")))))
-
-(provide 'test-custom-whitespace-remove-leading-trailing)
-;;; test-custom-whitespace-remove-leading-trailing.el ends here
diff --git a/tests/test-flycheck-languagetool-setup.el b/tests/test-flycheck-languagetool-setup.el
deleted file mode 100644
index a719e822..00000000
--- a/tests/test-flycheck-languagetool-setup.el
+++ /dev/null
@@ -1,71 +0,0 @@
-;;; test-flycheck-languagetool-setup.el --- Unit tests for LanguageTool setup -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests verifying LanguageTool installation and wrapper script setup.
-;; Focus: Testing OUR code (wrapper script, file setup), not flycheck internals.
-;;
-;; We trust that flycheck works correctly (it's an external framework).
-;; These tests verify:
-;; - LanguageTool is installed and accessible
-;; - Our wrapper script exists, is executable, and has correct structure
-;; - Python 3 dependency is available
-;;
-;; Categories: Normal (installation checks), Boundary (script structure), Error (missing dependencies)
-
-;;; Code:
-
-(require 'ert)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-flycheck-languagetool-setup-normal-wrapper-exists ()
- "Test that languagetool-flycheck wrapper script exists."
- (let ((wrapper-path (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck")))
- (should (file-exists-p wrapper-path))))
-
-(ert-deftest test-flycheck-languagetool-setup-normal-wrapper-executable ()
- "Test that languagetool-flycheck wrapper script is executable."
- (let ((wrapper-path (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck")))
- (should (file-executable-p wrapper-path))))
-
-(ert-deftest test-flycheck-languagetool-setup-normal-languagetool-installed ()
- "Test that languagetool command is available in PATH."
- (should (executable-find "languagetool")))
-
-(ert-deftest test-flycheck-languagetool-setup-normal-python3-available ()
- "Test that python3 is available for wrapper script."
- (should (executable-find "python3")))
-
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-flycheck-languagetool-setup-boundary-wrapper-script-format ()
- "Test that wrapper script has correct shebang and structure."
- (let ((wrapper-path (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck")))
- (with-temp-buffer
- (insert-file-contents wrapper-path)
- (goto-char (point-min))
- ;; Check shebang
- (should (looking-at "#!/usr/bin/env python3"))
- ;; Check it contains required imports
- (should (search-forward "import json" nil t))
- (should (search-forward "import subprocess" nil t)))))
-
-;; ----------------------------- Error Cases -----------------------------------
-
-(ert-deftest test-flycheck-languagetool-setup-error-missing-file-argument ()
- "Test that wrapper script requires file argument.
-When called without arguments, wrapper should exit with error."
- (let* ((wrapper (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck"))
- (exit-code nil))
- (with-temp-buffer
- (setq exit-code (call-process wrapper nil t nil))
- ;; Should exit with non-zero status when no file provided
- (should-not (= 0 exit-code))
- ;; Should print usage message to stderr (captured in buffer)
- (goto-char (point-min))
- (should (or (search-forward "Usage:" nil t)
- (search-forward "FILE" nil t))))))
-
-(provide 'test-flycheck-languagetool-setup)
-;;; test-flycheck-languagetool-setup.el ends here
diff --git a/tests/test-integration-buffer-diff.el b/tests/test-integration-buffer-diff.el
deleted file mode 100644
index 678e4816..00000000
--- a/tests/test-integration-buffer-diff.el
+++ /dev/null
@@ -1,300 +0,0 @@
-;;; test-integration-buffer-diff.el --- Integration tests for buffer diff functionality -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Integration tests covering the complete buffer diff workflow:
-;; - Comparing buffer contents with saved file version
-;; - Difftastic integration with fallback to regular diff
-;; - Output formatting and buffer display
-;; - Handling of no differences case
-;;
-;; Components integrated:
-;; - cj/executable-exists-p (program detection from system-lib)
-;; - cj/--diff-with-difftastic (difftastic execution and formatting)
-;; - cj/--diff-with-regular-diff (unified diff execution)
-;; - cj/diff-buffer-with-file (orchestration and user interaction)
-;; - File I/O (temp file creation/cleanup)
-;; - Buffer management (creating and displaying diff output)
-
-;;; Code:
-
-(require 'ert)
-(require 'system-lib)
-
-;; Stub out the keymap that custom-buffer-file requires
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'custom-buffer-file)
-
-;;; Test Utilities
-
-(defun test-integration-buffer-diff--get-diff-buffer ()
- "Get the diff buffer created by cj/diff-buffer-with-file.
-Returns either *Diff (difftastic)* or *Diff (unified)* buffer."
- (or (get-buffer "*Diff (difftastic)*")
- (get-buffer "*Diff (unified)*")))
-
-(defun test-integration-buffer-diff--create-test-file (content)
- "Create a temporary test file with CONTENT.
-Returns the file path."
- (let ((file (make-temp-file "test-buffer-diff-" nil ".org")))
- (with-temp-file file
- (insert content))
- file))
-
-(defun test-integration-buffer-diff--cleanup-buffers ()
- "Clean up test buffers created during tests."
- (when (get-buffer "*Diff (difftastic)*")
- (kill-buffer "*Diff (difftastic)*"))
- (when (get-buffer "*Diff (unified)*")
- (kill-buffer "*Diff (unified)*"))
- ;; Also clean old name for compatibility
- (when (get-buffer "*Diff*")
- (kill-buffer "*Diff*")))
-
-;;; Setup and Teardown
-
-(defun test-integration-buffer-diff-setup ()
- "Setup for buffer diff integration tests."
- (test-integration-buffer-diff--cleanup-buffers))
-
-(defun test-integration-buffer-diff-teardown ()
- "Teardown for buffer diff integration tests."
- (test-integration-buffer-diff--cleanup-buffers))
-
-;;; Normal Cases - Diff Detection and Display
-
-(ert-deftest test-integration-buffer-diff-normal-detects-added-lines ()
- "Test that diff correctly shows added lines in buffer.
-
-Creates a file, opens it, adds content, and verifies diff shows the additions.
-
-Components integrated:
-- cj/diff-buffer-with-file (main orchestration)
-- cj/executable-exists-p (tool detection)
-- cj/--diff-with-difftastic OR cj/--diff-with-regular-diff (diff execution)
-- File I/O (temp file creation)
-- Buffer display (showing diff output)
-
-Validates:
-- Modified buffer is compared against saved file
-- Added lines are detected and displayed
-- Output buffer is created and shown"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO Original heading\nSome content.\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- ;; Add new content to buffer
- (goto-char (point-max))
- (insert "\n* NEXT New task added\n")
- ;; Run diff
- (cj/diff-buffer-with-file)
- ;; Verify diff buffer was created
- (should (test-integration-buffer-diff--get-diff-buffer))
- (with-current-buffer (test-integration-buffer-diff--get-diff-buffer)
- (let ((content (buffer-string)))
- ;; Should have some diff output
- (should (> (length content) 0))
- ;; Content should show either the added line or indicate differences
- ;; (format differs between difft and regular diff)
- (should (or (string-match-p "NEXT" content)
- (string-match-p "New task" content)
- ;; Difft shows file differences in header
- (> (length content) 100)))))
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-(ert-deftest test-integration-buffer-diff-normal-detects-removed-lines ()
- "Test that diff correctly shows removed lines from buffer.
-
-Creates a file with multiple lines, removes content, verifies diff shows deletions.
-
-Components integrated:
-- cj/diff-buffer-with-file (orchestration)
-- Diff backend (difftastic or regular diff)
-- Buffer and file comparison logic
-
-Validates:
-- Removed lines are detected
-- Diff output indicates deletion"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO Heading\nLine to remove\nLine to keep\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- ;; Remove middle line
- (goto-char (point-min))
- (forward-line 1)
- (kill-line 1)
- ;; Run diff
- (cj/diff-buffer-with-file)
- ;; Verify diff shows removal
- (should (test-integration-buffer-diff--get-diff-buffer))
- (with-current-buffer (test-integration-buffer-diff--get-diff-buffer)
- (let ((content (buffer-string)))
- (should (> (length content) 0))))
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-(ert-deftest test-integration-buffer-diff-normal-shows-modified-lines ()
- "Test that diff shows modified lines correctly.
-
-Modifies existing content and verifies both old and new content shown.
-
-Components integrated:
-- cj/diff-buffer-with-file
-- Diff backend selection logic
-- Content comparison
-
-Validates:
-- Modified lines are detected
-- Both old and new content visible in diff"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO Original text\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- ;; Modify the text
- (goto-char (point-min))
- (search-forward "Original")
- (replace-match "Modified")
- ;; Run diff
- (cj/diff-buffer-with-file)
- ;; Verify diff shows change
- (should (test-integration-buffer-diff--get-diff-buffer))
- (with-current-buffer (test-integration-buffer-diff--get-diff-buffer)
- (let ((content (buffer-string)))
- (should (> (length content) 0))))
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-;;; Boundary Cases - No Differences
-
-(ert-deftest test-integration-buffer-diff-boundary-no-changes-shows-message ()
- "Test that no differences shows message instead of buffer.
-
-When buffer matches file exactly, should display message only.
-
-Components integrated:
-- cj/diff-buffer-with-file
-- diff -q (quick comparison)
-- Message display
-
-Validates:
-- No diff buffer created when no changes
-- User receives appropriate feedback"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO No changes\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- ;; No changes made
- ;; Run diff
- (cj/diff-buffer-with-file)
- ;; Should NOT create diff buffer for no changes
- ;; (implementation shows message only)
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-;; NOTE: Removed boundary-empty-file-with-content test due to unreliable behavior
-;; in batch mode where find-file-noselect + insert doesn't consistently create
-;; a buffer/file mismatch. The other tests adequately cover diff functionality.
-
-(ert-deftest test-integration-buffer-diff-boundary-org-mode-special-chars ()
- "Test that org-mode special characters are handled correctly.
-
-Boundary case: org asterisks, priorities, TODO keywords.
-
-Components integrated:
-- cj/diff-buffer-with-file
-- Diff backend (must handle special chars)
-- Org-mode content
-
-Validates:
-- Special org syntax doesn't break diff
-- Output is readable and correct"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO [#A] Original :tag:\n** DONE Subtask\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- ;; Modify with more special chars
- (goto-char (point-max))
- (insert "*** NEXT [#B] New subtask :work:urgent:\n")
- ;; Run diff
- (cj/diff-buffer-with-file)
- ;; Verify diff handled special chars
- (should (test-integration-buffer-diff--get-diff-buffer))
- (with-current-buffer (test-integration-buffer-diff--get-diff-buffer)
- (let ((content (buffer-string)))
- ;; Should have diff output (format varies)
- (should (> (length content) 0))))
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-integration-buffer-diff-error-not-visiting-file-signals-error ()
- "Test that calling diff on buffer not visiting file signals error.
-
-Error case: buffer exists but isn't associated with a file.
-
-Components integrated:
-- cj/diff-buffer-with-file (error handling)
-
-Validates:
-- Appropriate error signaled
-- Function fails fast with clear feedback"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (with-temp-buffer
- ;; Buffer not visiting a file
- (should-error (cj/diff-buffer-with-file)))
- (test-integration-buffer-diff-teardown)))
-
-;;; Difftastic vs Regular Diff Backend Selection
-
-(ert-deftest test-integration-buffer-diff-normal-uses-available-backend ()
- "Test that diff uses difftastic if available, otherwise regular diff.
-
-Validates backend selection logic works correctly.
-
-Components integrated:
-- cj/executable-exists-p (backend detection)
-- cj/--diff-with-difftastic OR cj/--diff-with-regular-diff
-- cj/diff-buffer-with-file (backend selection)
-
-Validates:
-- Correct backend is chosen based on availability
-- Fallback mechanism works
-- Both backends produce usable output"
- (test-integration-buffer-diff-setup)
- (unwind-protect
- (let* ((file (test-integration-buffer-diff--create-test-file
- "* TODO Test\n")))
- (unwind-protect
- (with-current-buffer (find-file-noselect file)
- (insert "* NEXT Added\n")
- ;; Run diff (will use whatever backend is available)
- (cj/diff-buffer-with-file)
- ;; Just verify it worked with some backend
- (should (test-integration-buffer-diff--get-diff-buffer))
- (with-current-buffer (test-integration-buffer-diff--get-diff-buffer)
- (should (> (buffer-size) 0)))
- (kill-buffer))
- (delete-file file)))
- (test-integration-buffer-diff-teardown)))
-
-(provide 'test-integration-buffer-diff)
-;;; test-integration-buffer-diff.el ends here
diff --git a/tests/test-integration-grammar-checking.el b/tests/test-integration-grammar-checking.el
deleted file mode 100644
index 8948c17a..00000000
--- a/tests/test-integration-grammar-checking.el
+++ /dev/null
@@ -1,190 +0,0 @@
-;;; test-integration-grammar-checking.el --- Integration tests for grammar checking -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Integration tests for the LanguageTool wrapper script with real grammar checking.
-;; Tests the integration: test fixture → wrapper script → LanguageTool → formatted output
-;;
-;; Components integrated:
-;; - scripts/languagetool-flycheck (our wrapper script)
-;; - languagetool command (external grammar checker)
-;; - Test fixtures with known grammar errors
-;; - Output formatting (JSON → flycheck format)
-;;
-;; Focus: Testing OUR integration code (wrapper), not flycheck framework.
-;; We trust that flycheck works; we test that our wrapper produces correct output.
-;;
-;; Categories: Normal workflow, Boundary cases, Error handling
-
-;;; Code:
-
-(require 'ert)
-
-;; ----------------------------- Test Helpers ----------------------------------
-
-(defun test-integration-grammar--fixture-path (filename)
- "Return absolute path to test fixture FILENAME."
- (expand-file-name (concat "tests/fixtures/" filename)
- user-emacs-directory))
-
-(defun test-integration-grammar--wrapper-output (file-path)
- "Run languagetool-flycheck wrapper directly on FILE-PATH.
-Returns output as string."
- (let ((wrapper (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck")))
- (with-temp-buffer
- (call-process wrapper nil t nil file-path)
- (buffer-string))))
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-integration-grammar-checking-normal-wrapper-detects-errors ()
- "Test that wrapper script detects grammar errors in fixture.
-
-Components integrated:
-- scripts/languagetool-flycheck (wrapper script)
-- languagetool command (external checker)
-- Test fixture with known errors"
- (let* ((fixture (test-integration-grammar--fixture-path "grammar-errors-basic.txt"))
- (output (test-integration-grammar--wrapper-output fixture)))
- ;; Should detect "This are" error
- (should (string-match-p "PLURAL_VERB_AFTER_THIS\\|This are" output))
- ;; Should detect "could of" error
- (should (string-match-p "COULD_OF\\|could of" output))
- ;; Output should be in flycheck format (filename:line:column:)
- (should (string-match-p "grammar-errors-basic\\.txt:[0-9]+:[0-9]+:" output))))
-
-(ert-deftest test-integration-grammar-checking-normal-wrapper-format ()
- "Test that wrapper outputs flycheck-compatible format.
-
-Components integrated:
-- scripts/languagetool-flycheck (output formatting)
-- languagetool command (JSON parsing)"
- (let* ((fixture (test-integration-grammar--fixture-path "grammar-errors-basic.txt"))
- (output (test-integration-grammar--wrapper-output fixture))
- (lines (split-string output "\n" t)))
- (dolist (line lines)
- ;; Each line should match: filename:line:column: message
- (should (string-match "^[^:]+:[0-9]+:[0-9]+: " line)))))
-
-(ert-deftest test-integration-grammar-checking-normal-correct-text-no-errors ()
- "Test that grammatically correct text produces no errors.
-
-Components integrated:
-- scripts/languagetool-flycheck (wrapper script)
-- languagetool command (validation)
-- Test fixture with correct grammar"
- (let* ((fixture (test-integration-grammar--fixture-path "grammar-correct.txt"))
- (output (test-integration-grammar--wrapper-output fixture)))
- ;; Correct grammar should produce no output (or only whitespace)
- (should (or (string-empty-p (string-trim output))
- (= 0 (length (string-trim output)))))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-integration-grammar-checking-boundary-empty-file ()
- "Test that empty file produces no errors.
-
-Components integrated:
-- scripts/languagetool-flycheck (empty input handling)
-- languagetool command"
- (let ((temp-file (make-temp-file "grammar-test-" nil ".txt")))
- (unwind-protect
- (let ((output (test-integration-grammar--wrapper-output temp-file)))
- (should (or (string-empty-p (string-trim output))
- (= 0 (length (string-trim output))))))
- (delete-file temp-file))))
-
-(ert-deftest test-integration-grammar-checking-boundary-single-word ()
- "Test that single word file produces no errors.
-
-Components integrated:
-- scripts/languagetool-flycheck (minimal input)
-- languagetool command"
- (let ((temp-file (make-temp-file "grammar-test-" nil ".txt")))
- (unwind-protect
- (progn
- (with-temp-file temp-file
- (insert "Hello"))
- (let ((output (test-integration-grammar--wrapper-output temp-file)))
- ;; Single word might produce no errors or might flag as incomplete sentence
- ;; Just verify it doesn't crash
- (should (stringp output))))
- (delete-file temp-file))))
-
-(ert-deftest test-integration-grammar-checking-boundary-multiple-paragraphs ()
- "Test that file with multiple paragraphs is checked completely.
-
-Components integrated:
-- scripts/languagetool-flycheck (multi-paragraph handling)
-- languagetool command (full file processing)"
- (let* ((fixture (test-integration-grammar--fixture-path "grammar-errors-basic.txt"))
- (output (test-integration-grammar--wrapper-output fixture))
- (lines (split-string output "\n" t)))
- ;; Should detect errors in multiple lines
- ;; Check that we have multiple error reports with different line numbers
- (let ((line-numbers '()))
- (dolist (line lines)
- (when (string-match ":[0-9]+:" line)
- (let ((line-num (string-to-number
- (nth 1 (split-string line ":")))))
- (push line-num line-numbers))))
- ;; Should have errors from multiple lines
- (should (> (length (delete-dups line-numbers)) 1)))))
-
-;; ----------------------------- Error Cases -----------------------------------
-
-(ert-deftest test-integration-grammar-checking-error-nonexistent-file ()
- "Test that wrapper handles nonexistent file with error.
-
-Components integrated:
-- scripts/languagetool-flycheck (error handling)
-- File system (missing file)
-- Python exception handling"
- (let* ((nonexistent "/tmp/this-file-does-not-exist-12345.txt")
- (wrapper (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck"))
- (exit-code nil)
- (output nil))
- (with-temp-buffer
- (setq exit-code (call-process wrapper nil t nil nonexistent))
- (setq output (buffer-string)))
- ;; LanguageTool/Python should handle the error
- ;; Check that we get output (error message or error in flycheck format)
- (should (stringp output))
- ;; Output should contain some indication of the error (filename or error marker)
- (should (or (string-match-p nonexistent output)
- (string-match-p "error" output)
- (string-match-p "Error" output)
- ;; Or it might report no errors for a nonexistent file
- (string-empty-p (string-trim output))))))
-
-(ert-deftest test-integration-grammar-checking-error-no-file-argument ()
- "Test that wrapper requires file argument.
-
-Components integrated:
-- scripts/languagetool-flycheck (argument validation)"
- (let* ((wrapper (expand-file-name "~/.emacs.d/scripts/languagetool-flycheck"))
- (exit-code nil))
- (with-temp-buffer
- (setq exit-code (call-process wrapper nil t nil))
- ;; Should exit with non-zero status when no file provided
- (should-not (= 0 exit-code)))))
-
-;; ----------------------------- Integration with Real Files -------------------
-
-(ert-deftest test-integration-grammar-checking-integration-comprehensive-errors ()
- "Test that wrapper catches multiple types of grammar errors in one file.
-
-Components integrated:
-- scripts/languagetool-flycheck (our wrapper)
-- languagetool command (comprehensive checking)
-- Test fixture with various error types"
- (let* ((fixture (test-integration-grammar--fixture-path "grammar-errors-basic.txt"))
- (output (test-integration-grammar--wrapper-output fixture))
- (lines (split-string output "\n" t)))
- ;; Should detect multiple errors (at least 3-4 in the fixture)
- (should (>= (length lines) 3))
- ;; All lines should be properly formatted
- (dolist (line lines)
- (should (string-match "^[^:]+:[0-9]+:[0-9]+: " line)))))
-
-(provide 'test-integration-grammar-checking)
-;;; test-integration-grammar-checking.el ends here
diff --git a/tests/test-integration-recording-device-workflow.el b/tests/test-integration-recording-device-workflow.el
deleted file mode 100644
index ba92d700..00000000
--- a/tests/test-integration-recording-device-workflow.el
+++ /dev/null
@@ -1,232 +0,0 @@
-;;; test-integration-recording-device-workflow.el --- Integration tests for recording device workflow -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Integration tests covering the complete device detection and grouping workflow.
-;;
-;; This tests the full pipeline from raw pactl output through parsing, grouping,
-;; and friendly name assignment. The workflow enables users to select audio devices
-;; for recording calls/meetings.
-;;
-;; Components integrated:
-;; - cj/recording--parse-pactl-output (parse raw pactl output into structured data)
-;; - cj/recording-parse-sources (shell command wrapper)
-;; - cj/recording-group-devices-by-hardware (group inputs/monitors by device)
-;; - cj/recording-friendly-state (convert technical state names)
-;; - Bluetooth MAC address normalization (colons → underscores)
-;; - Device name pattern matching (USB, PCI, Bluetooth)
-;; - Friendly name assignment (user-facing device names)
-;;
-;; Critical integration points:
-;; - Parse output must produce data that group-devices can process
-;; - Bluetooth MAC normalization must work across parse→group boundary
-;; - Incomplete devices (only mic OR only monitor) must be filtered
-;; - Friendly names must correctly identify device types
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Test Fixtures Helper
-
-(defun test-load-fixture (filename)
- "Load fixture file FILENAME from tests/fixtures directory."
- (let ((fixture-path (expand-file-name
- (concat "tests/fixtures/" filename)
- user-emacs-directory)))
- (with-temp-buffer
- (insert-file-contents fixture-path)
- (buffer-string))))
-
-;;; Normal Cases - Complete Workflow
-
-(ert-deftest test-integration-recording-device-workflow-parse-to-group-all-devices ()
- "Test complete workflow from pactl output to grouped devices.
-
-When pactl output contains all three device types (built-in, USB, Bluetooth),
-the workflow should parse, group, and assign friendly names to all devices.
-
-Components integrated:
-- cj/recording--parse-pactl-output (parsing)
-- cj/recording-group-devices-by-hardware (grouping + MAC normalization)
-- Device pattern matching (USB/PCI/Bluetooth detection)
-- Friendly name assignment
-
-Validates:
-- All three device types are detected
-- Bluetooth MAC addresses normalized (colons → underscores)
-- Each device has both mic and monitor
-- Friendly names correctly assigned
-- Complete data flow: raw output → parsed list → grouped pairs"
- (let ((output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- ;; Test parse step
- (let ((parsed (cj/recording-parse-sources)))
- (should (= 6 (length parsed)))
-
- ;; Test group step (receives parsed data)
- (let ((grouped (cj/recording-group-devices-by-hardware)))
- (should (= 3 (length grouped)))
-
- ;; Validate built-in device
- (let ((built-in (assoc "Built-in Laptop Audio" grouped)))
- (should built-in)
- (should (string-prefix-p "alsa_input.pci" (cadr built-in)))
- (should (string-prefix-p "alsa_output.pci" (cddr built-in))))
-
- ;; Validate USB device
- (let ((usb (assoc "Jabra SPEAK 510 USB" grouped)))
- (should usb)
- (should (string-match-p "Jabra" (cadr usb)))
- (should (string-match-p "Jabra" (cddr usb))))
-
- ;; Validate Bluetooth device (CRITICAL: MAC normalization)
- (let ((bluetooth (assoc "Bluetooth Headset" grouped)))
- (should bluetooth)
- ;; Input has colons
- (should (string-match-p "00:1B:66:C0:91:6D" (cadr bluetooth)))
- ;; Output has underscores
- (should (string-match-p "00_1B_66_C0_91_6D" (cddr bluetooth)))
- ;; But they're grouped together!
- (should (equal "bluez_input.00:1B:66:C0:91:6D" (cadr bluetooth)))
- (should (equal "bluez_output.00_1B_66_C0_91_6D.1.monitor" (cddr bluetooth)))))))))
-
-(ert-deftest test-integration-recording-device-workflow-friendly-states-in-list ()
- "Test that friendly state names appear in device list output.
-
-When listing devices, technical state names (SUSPENDED, RUNNING) should be
-converted to friendly names (Ready, Active) for better UX.
-
-Components integrated:
-- cj/recording-parse-sources (parsing with state)
-- cj/recording-friendly-state (state name conversion)
-
-Validates:
-- SUSPENDED → Ready
-- RUNNING → Active
-- State conversion works across the parse workflow"
- (let ((output (concat
- "49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- "81\tbluez_output.00_1B_66_C0_91_6D.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((parsed (cj/recording-parse-sources)))
- ;; Verify states are parsed correctly
- (should (equal "SUSPENDED" (nth 2 (nth 0 parsed))))
- (should (equal "RUNNING" (nth 2 (nth 1 parsed))))
-
- ;; Verify friendly conversion works
- (should (equal "Ready" (cj/recording-friendly-state (nth 2 (nth 0 parsed)))))
- (should (equal "Active" (cj/recording-friendly-state (nth 2 (nth 1 parsed)))))))))
-
-;;; Boundary Cases - Incomplete Devices
-
-(ert-deftest test-integration-recording-device-workflow-incomplete-devices-filtered ()
- "Test that devices with only mic OR only monitor are filtered out.
-
-For call recording, we need BOTH mic and monitor from the same device.
-Incomplete devices should not appear in the grouped output.
-
-Components integrated:
-- cj/recording-parse-sources (parsing all devices)
-- cj/recording-group-devices-by-hardware (filtering incomplete pairs)
-
-Validates:
-- Device with only mic is filtered
-- Device with only monitor is filtered
-- Only complete devices (both mic and monitor) are returned
-- Filtering happens at group stage, not parse stage"
- (let ((output (concat
- ;; Complete device
- "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- "49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- ;; Incomplete: USB mic with no monitor
- "100\talsa_input.usb-device.mono-fallback\tPipeWire\ts16le 1ch 16000Hz\tSUSPENDED\n"
- ;; Incomplete: Bluetooth monitor with no mic
- "81\tbluez_output.AA_BB_CC_DD_EE_FF.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- ;; Parse sees all 4 devices
- (let ((parsed (cj/recording-parse-sources)))
- (should (= 4 (length parsed)))
-
- ;; Group returns only 1 complete device
- (let ((grouped (cj/recording-group-devices-by-hardware)))
- (should (= 1 (length grouped)))
- (should (equal "Built-in Laptop Audio" (caar grouped))))))))
-
-;;; Edge Cases - Bluetooth MAC Normalization
-
-(ert-deftest test-integration-recording-device-workflow-bluetooth-mac-variations ()
- "Test Bluetooth MAC normalization with different formats.
-
-Bluetooth devices use colons in input names but underscores in output names.
-The grouping must normalize these to match devices correctly.
-
-Components integrated:
-- cj/recording-parse-sources (preserves original MAC format)
-- cj/recording-group-devices-by-hardware (normalizes MAC for matching)
-- Base name extraction (regex patterns)
-- MAC address transformation (underscores → colons)
-
-Validates:
-- Input with colons (bluez_input.AA:BB:CC:DD:EE:FF) parsed correctly
-- Output with underscores (bluez_output.AA_BB_CC_DD_EE_FF) parsed correctly
-- Normalization happens during grouping
-- Devices paired despite format difference
-- Original device names preserved (not mutated)"
- (let ((output (concat
- "79\tbluez_input.11:22:33:44:55:66\tPipeWire\tfloat32le 1ch 48000Hz\tSUSPENDED\n"
- "81\tbluez_output.11_22_33_44_55_66.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((parsed (cj/recording-parse-sources)))
- ;; Original formats preserved in parse
- (should (string-match-p "11:22:33" (caar parsed)))
- (should (string-match-p "11_22_33" (caadr parsed)))
-
- ;; But grouping matches them
- (let ((grouped (cj/recording-group-devices-by-hardware)))
- (should (= 1 (length grouped)))
- (should (equal "Bluetooth Headset" (caar grouped)))
- ;; Original names preserved
- (should (equal "bluez_input.11:22:33:44:55:66" (cadar grouped)))
- (should (equal "bluez_output.11_22_33_44_55_66.1.monitor" (cddar grouped))))))))
-
-;;; Error Cases - Malformed Data
-
-(ert-deftest test-integration-recording-device-workflow-malformed-output-handled ()
- "Test that malformed pactl output is handled gracefully.
-
-When pactl output is malformed or unparseable, the workflow should not crash.
-It should return empty results at appropriate stages.
-
-Components integrated:
-- cj/recording--parse-pactl-output (malformed line handling)
-- cj/recording-group-devices-by-hardware (empty input handling)
-
-Validates:
-- Malformed lines are silently skipped during parse
-- Empty parse results don't crash grouping
-- Workflow degrades gracefully
-- No exceptions thrown"
- (let ((output (test-load-fixture "pactl-output-malformed.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((parsed (cj/recording-parse-sources)))
- ;; Malformed output produces empty parse
- (should (null parsed))
-
- ;; Empty parse produces empty grouping (no crash)
- (let ((grouped (cj/recording-group-devices-by-hardware)))
- (should (null grouped)))))))
-
-(provide 'test-integration-recording-device-workflow)
-;;; test-integration-recording-device-workflow.el ends here
diff --git a/tests/test-integration-recording-modeline-sync.el b/tests/test-integration-recording-modeline-sync.el
deleted file mode 100644
index fab442bd..00000000
--- a/tests/test-integration-recording-modeline-sync.el
+++ /dev/null
@@ -1,384 +0,0 @@
-;;; test-integration-recording-modeline-sync.el --- Integration tests for modeline sync -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Integration tests validating that the modeline indicator NEVER desyncs
-;; from the actual recording state throughout the entire toggle lifecycle.
-;;
-;; This tests the critical requirement: modeline must always accurately
-;; reflect whether recording is happening, with NO desyncs.
-;;
-;; Components integrated:
-;; - cj/audio-recording-toggle (state changes)
-;; - cj/video-recording-toggle (state changes)
-;; - cj/recording-modeline-indicator (UI state display)
-;; - cj/ffmpeg-record-audio (process creation)
-;; - cj/ffmpeg-record-video (process creation)
-;; - cj/recording-process-sentinel (auto-updates modeline)
-;; - cj/audio-recording-stop (cleanup triggers update)
-;; - cj/video-recording-stop (cleanup triggers update)
-;; - force-mode-line-update (explicit refresh calls)
-;;
-;; Validates:
-;; - Modeline updates immediately on toggle start
-;; - Modeline updates immediately on toggle stop
-;; - Modeline updates when sentinel runs (process dies)
-;; - Modeline shows correct state for audio, video, or both
-;; - Modeline never shows stale state
-;; - process-live-p check prevents desync on dead processes
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Stub directory variables
-(defvar video-recordings-dir "/tmp/video-recordings/")
-(defvar audio-recordings-dir "/tmp/audio-recordings/")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-integration-modeline-setup ()
- "Reset all variables before each test."
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor"))
-
-(defun test-integration-modeline-teardown ()
- "Clean up after each test."
- (when cj/video-recording-ffmpeg-process
- (ignore-errors (delete-process cj/video-recording-ffmpeg-process)))
- (when cj/audio-recording-ffmpeg-process
- (ignore-errors (delete-process cj/audio-recording-ffmpeg-process)))
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Integration Tests - Modeline Sync on Toggle
-
-(ert-deftest test-integration-recording-modeline-sync-audio-start-updates-immediately ()
- "Test that modeline updates immediately when audio recording starts.
-
-When user toggles audio recording on:
-1. Process is created
-2. Modeline indicator updates to show 🔴Audio
-3. State is in sync immediately (not delayed)
-
-Components integrated:
-- cj/audio-recording-toggle
-- cj/ffmpeg-record-audio (calls force-mode-line-update)
-- cj/recording-modeline-indicator
-
-Validates:
-- Modeline syncs on start
-- No delay or race condition"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Before toggle: no recording
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Toggle on
- (cj/audio-recording-toggle nil)
-
- ;; Immediately after toggle: modeline should show recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Process should be alive
- (should (process-live-p cj/audio-recording-ffmpeg-process)))
- (test-integration-modeline-teardown)))
-
-(ert-deftest test-integration-recording-modeline-sync-audio-stop-updates-immediately ()
- "Test that modeline updates immediately when audio recording stops.
-
-When user toggles audio recording off:
-1. Process is interrupted
-2. Variable is cleared
-3. Modeline indicator updates to show empty
-4. State is in sync immediately
-
-Components integrated:
-- cj/audio-recording-toggle (stop path)
-- cj/audio-recording-stop (calls force-mode-line-update)
-- cj/recording-modeline-indicator
-
-Validates:
-- Modeline syncs on stop
-- No stale indicator after stop"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Start recording
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Stop recording
- (cj/audio-recording-toggle nil)
-
- ;; Immediately after stop: modeline should be empty
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Process should be nil
- (should (null cj/audio-recording-ffmpeg-process)))
- (test-integration-modeline-teardown)))
-
-(ert-deftest test-integration-recording-modeline-sync-video-lifecycle ()
- "Test modeline sync through complete video recording lifecycle.
-
-Components integrated:
-- cj/video-recording-toggle (both start and stop)
-- cj/ffmpeg-record-video
-- cj/video-recording-stop
-- cj/recording-modeline-indicator
-
-Validates:
-- Video recording follows same sync pattern as audio
-- Modeline shows 🔴Video correctly"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Initial state
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Start video
- (cj/video-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
-
- ;; Stop video
- (cj/video-recording-toggle nil)
- (should (equal "" (cj/recording-modeline-indicator))))
- (test-integration-modeline-teardown)))
-
-;;; Integration Tests - Modeline Sync with Both Recordings
-
-(ert-deftest test-integration-recording-modeline-sync-both-recordings-transitions ()
- "Test modeline sync through all possible state transitions.
-
-Tests transitions:
-- none → audio → both → video → none
-- Validates modeline updates at every transition
-
-Components integrated:
-- cj/audio-recording-toggle
-- cj/video-recording-toggle
-- cj/recording-modeline-indicator (handles all states)
-
-Validates:
-- Modeline accurately reflects all combinations
-- Transitions are clean with no stale state"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; State 1: None
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; State 2: Audio only
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; State 3: Both
- (cj/video-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
-
- ;; State 4: Video only (stop audio)
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
-
- ;; State 5: None (stop video)
- (cj/video-recording-toggle nil)
- (should (equal "" (cj/recording-modeline-indicator))))
- (test-integration-modeline-teardown)))
-
-;;; Integration Tests - Modeline Sync with Sentinel
-
-(ert-deftest test-integration-recording-modeline-sync-sentinel-updates-on-crash ()
- "Test that modeline syncs when process dies and sentinel runs.
-
-When recording process crashes:
-1. Sentinel detects process death
-2. Sentinel clears variable
-3. Sentinel calls force-mode-line-update
-4. Modeline indicator shows no recording
-
-Components integrated:
-- cj/ffmpeg-record-audio (attaches sentinel)
-- cj/recording-process-sentinel (cleanup + modeline update)
-- cj/recording-modeline-indicator
-
-Validates:
-- Sentinel updates modeline on process death
-- Modeline syncs automatically without user action
-- Critical: prevents desync when process crashes"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- ;; Create process that exits immediately
- (make-process :name name :command '("sh" "-c" "exit 1")))))
-
- ;; Start recording
- (cj/audio-recording-toggle nil)
-
- ;; Immediately after start: should show recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Wait for process to exit and sentinel to run
- (sit-for 0.3)
-
- ;; After sentinel runs: modeline should be clear
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Variable should be nil
- (should (null cj/audio-recording-ffmpeg-process)))
- (test-integration-modeline-teardown)))
-
-(ert-deftest test-integration-recording-modeline-sync-dead-process-not-shown ()
- "Test that modeline never shows dead process as recording.
-
-The modeline indicator uses process-live-p to check if process is ACTUALLY
-alive, not just if the variable is set. This prevents desync.
-
-Components integrated:
-- cj/recording-modeline-indicator (uses process-live-p)
-
-Validates:
-- Dead process doesn't show as recording
-- process-live-p check prevents desync
-- Critical: if variable is set but process is dead, shows empty"
- (test-integration-modeline-setup)
- (unwind-protect
- (let ((dead-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0"))))
- ;; Set variable to dead process (simulating race condition)
- (setq cj/audio-recording-ffmpeg-process dead-process)
-
- ;; Wait for process to die
- (sit-for 0.1)
-
- ;; Modeline should NOT show recording (process is dead)
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Even though variable is set
- (should (eq dead-process cj/audio-recording-ffmpeg-process))
-
- ;; Process is dead
- (should-not (process-live-p dead-process)))
- (test-integration-modeline-teardown)))
-
-;;; Integration Tests - Modeline Sync Under Rapid Toggling
-
-(ert-deftest test-integration-recording-modeline-sync-rapid-toggle-stays-synced ()
- "Test modeline stays synced under rapid start/stop toggling.
-
-When user rapidly toggles recording on and off:
-- Modeline should stay in sync at every step
-- No race conditions or stale state
-
-Components integrated:
-- cj/audio-recording-toggle (rapid calls)
-- cj/ffmpeg-record-audio
-- cj/audio-recording-stop
-- cj/recording-modeline-indicator
-
-Validates:
-- Modeline syncs even with rapid state changes
-- No race conditions in update logic"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Rapid toggling
- (dotimes (_i 5)
- ;; Start
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
- (should cj/audio-recording-ffmpeg-process)
-
- ;; Stop
- (cj/audio-recording-toggle nil)
- (should (equal "" (cj/recording-modeline-indicator)))
- (should (null cj/audio-recording-ffmpeg-process))))
- (test-integration-modeline-teardown)))
-
-(ert-deftest test-integration-recording-modeline-sync-both-recordings-independent ()
- "Test that audio and video modeline updates are independent.
-
-When one recording stops, the other's indicator persists.
-When one recording starts, both indicators combine correctly.
-
-Components integrated:
-- cj/audio-recording-toggle
-- cj/video-recording-toggle
-- cj/recording-modeline-indicator (combines states)
-
-Validates:
-- Independent recordings don't interfere
-- Modeline correctly shows: audio-only, video-only, or both
-- Stopping one doesn't affect other's indicator"
- (test-integration-modeline-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Start audio
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Add video - modeline should combine
- (cj/video-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
-
- ;; Stop audio - video indicator should persist
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
-
- ;; Start audio again - should recombine
- (cj/audio-recording-toggle nil)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
-
- ;; Stop video - audio indicator should persist
- (cj/video-recording-toggle nil)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Stop audio - should be empty
- (cj/audio-recording-toggle nil)
- (should (equal "" (cj/recording-modeline-indicator))))
- (test-integration-modeline-teardown)))
-
-(provide 'test-integration-recording-modeline-sync)
-;;; test-integration-recording-modeline-sync.el ends here
diff --git a/tests/test-integration-recording-toggle-workflow.el b/tests/test-integration-recording-toggle-workflow.el
deleted file mode 100644
index c61698c5..00000000
--- a/tests/test-integration-recording-toggle-workflow.el
+++ /dev/null
@@ -1,347 +0,0 @@
-;;; test-integration-recording-toggle-workflow.el --- Integration tests for recording toggle workflow -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Integration tests covering the complete recording toggle workflow from
-;; user action through device setup, recording, and cleanup.
-;;
-;; This tests the ACTUAL user workflow: Press C-; r a → setup → record → stop → cleanup
-;;
-;; Components integrated:
-;; - cj/audio-recording-toggle (entry point)
-;; - cj/video-recording-toggle (entry point)
-;; - cj/recording-get-devices (device prompting and setup)
-;; - cj/recording-quick-setup-for-calls (device selection workflow)
-;; - cj/ffmpeg-record-audio (process creation and ffmpeg command)
-;; - cj/ffmpeg-record-video (process creation and ffmpeg command)
-;; - cj/recording-modeline-indicator (UI state display)
-;; - cj/audio-recording-stop (cleanup)
-;; - cj/video-recording-stop (cleanup)
-;; - cj/recording-process-sentinel (auto-cleanup on process death)
-;;
-;; Validates:
-;; - Complete workflow from toggle to cleanup
-;; - Device setup on first use
-;; - Process creation and management
-;; - Modeline updates at each step
-;; - Cleanup on user stop
-;; - Auto-cleanup when process dies
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Stub directory variables
-(defvar video-recordings-dir "/tmp/video-recordings/")
-(defvar audio-recordings-dir "/tmp/audio-recordings/")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-integration-toggle-setup ()
- "Reset all variables before each test."
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-(defun test-integration-toggle-teardown ()
- "Clean up after each test."
- (when cj/video-recording-ffmpeg-process
- (ignore-errors (delete-process cj/video-recording-ffmpeg-process)))
- (when cj/audio-recording-ffmpeg-process
- (ignore-errors (delete-process cj/audio-recording-ffmpeg-process)))
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Integration Tests - Audio Recording Workflow
-
-(ert-deftest test-integration-recording-toggle-workflow-audio-first-use-full-cycle ()
- "Test complete audio recording workflow from first use through cleanup.
-
-When user presses C-; r a for the first time:
-1. Device setup prompt appears (no devices configured)
-2. User chooses quick setup
-3. Devices are selected and saved
-4. Recording starts with correct ffmpeg command
-5. Process is created and sentinel attached
-6. Modeline shows recording indicator
-7. User presses C-; r a again to stop
-8. Recording stops gracefully
-9. Modeline indicator clears
-
-Components integrated:
-- cj/audio-recording-toggle (toggles start/stop)
-- cj/recording-get-devices (prompts for setup on first use)
-- cj/recording-quick-setup-for-calls (device selection)
-- cj/ffmpeg-record-audio (creates recording process)
-- cj/recording-modeline-indicator (UI state)
-- cj/audio-recording-stop (cleanup)
-
-Validates:
-- Full user workflow from first use to stop
-- Device setup on first toggle
-- Recording starts after setup
-- Modeline updates correctly
-- Stop works after recording"
- (test-integration-toggle-setup)
- (unwind-protect
- (let ((setup-called nil)
- (ffmpeg-cmd nil)
- (process-created nil))
- ;; Mock the device setup to simulate user choosing quick setup
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t)) ; User says yes to quick setup
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq setup-called t)
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor")))
- ((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer cmd)
- (setq process-created t)
- (setq ffmpeg-cmd cmd)
- (make-process :name "fake-audio" :command '("sleep" "1000")))))
-
- ;; STEP 1: First toggle - should trigger device setup
- (cj/audio-recording-toggle nil)
-
- ;; Verify setup was called
- (should setup-called)
-
- ;; Verify devices were set
- (should (equal "test-mic" cj/recording-mic-device))
- (should (equal "test-monitor" cj/recording-system-device))
-
- ;; Verify recording started
- (should process-created)
- (should cj/audio-recording-ffmpeg-process)
- (should (string-match-p "ffmpeg" ffmpeg-cmd))
- (should (string-match-p "test-mic" ffmpeg-cmd))
- (should (string-match-p "test-monitor" ffmpeg-cmd))
-
- ;; Verify modeline shows recording
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; STEP 2: Second toggle - should stop recording
- (cj/audio-recording-toggle nil)
-
- ;; Verify recording stopped
- (should (null cj/audio-recording-ffmpeg-process))
-
- ;; Verify modeline cleared
- (should (equal "" (cj/recording-modeline-indicator)))))
- (test-integration-toggle-teardown)))
-
-(ert-deftest test-integration-recording-toggle-workflow-audio-subsequent-use-no-setup ()
- "Test that subsequent audio recordings skip device setup.
-
-After devices are configured, pressing C-; r a should:
-1. Skip device setup (already configured)
-2. Start recording immediately
-3. Use previously configured devices
-
-Components integrated:
-- cj/audio-recording-toggle
-- cj/recording-get-devices (returns cached devices)
-- cj/ffmpeg-record-audio (uses cached devices)
-
-Validates:
-- Device setup is cached across recordings
-- Second recording doesn't prompt
-- Same devices are used"
- (test-integration-toggle-setup)
- (unwind-protect
- (progn
- ;; Pre-configure devices (simulating previous setup)
- (setq cj/recording-mic-device "cached-mic")
- (setq cj/recording-system-device "cached-monitor")
-
- (let ((setup-called nil)
- (ffmpeg-cmd nil))
- (cl-letf (((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda () (setq setup-called t)))
- ((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer cmd)
- (setq ffmpeg-cmd cmd)
- (make-process :name "fake-audio" :command '("sleep" "1000")))))
-
- ;; Toggle to start recording
- (cj/audio-recording-toggle nil)
-
- ;; Setup should NOT be called
- (should-not setup-called)
-
- ;; Should use cached devices
- (should (string-match-p "cached-mic" ffmpeg-cmd))
- (should (string-match-p "cached-monitor" ffmpeg-cmd)))))
- (test-integration-toggle-teardown)))
-
-;;; Integration Tests - Video Recording Workflow
-
-(ert-deftest test-integration-recording-toggle-workflow-video-full-cycle ()
- "Test complete video recording workflow.
-
-Components integrated:
-- cj/video-recording-toggle
-- cj/recording-get-devices
-- cj/ffmpeg-record-video
-- cj/recording-modeline-indicator
-- cj/video-recording-stop
-
-Validates:
-- Video recording follows same workflow as audio
-- Modeline shows video indicator
-- Toggle works for video"
- (test-integration-toggle-setup)
- (unwind-protect
- (let ((setup-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq setup-called t)
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor")))
- ((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _cmd)
- (make-process :name "fake-video" :command '("sleep" "1000")))))
-
- ;; Start video recording
- (cj/video-recording-toggle nil)
-
- ;; Verify setup and recording
- (should setup-called)
- (should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
-
- ;; Stop recording
- (cj/video-recording-toggle nil)
-
- ;; Verify cleanup
- (should (null cj/video-recording-ffmpeg-process))
- (should (equal "" (cj/recording-modeline-indicator)))))
- (test-integration-toggle-teardown)))
-
-;;; Integration Tests - Both Recordings Simultaneously
-
-(ert-deftest test-integration-recording-toggle-workflow-both-simultaneous ()
- "Test that both audio and video can record simultaneously.
-
-Components integrated:
-- cj/audio-recording-toggle
-- cj/video-recording-toggle
-- cj/recording-modeline-indicator (shows both)
-- Both ffmpeg-record functions
-
-Validates:
-- Audio and video can run together
-- Modeline shows both indicators
-- Stopping one doesn't affect the other"
- (test-integration-toggle-setup)
- (unwind-protect
- (progn
- ;; Pre-configure devices
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor")
-
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (make-process :name name :command '("sleep" "1000")))))
-
- ;; Start both recordings
- (cj/audio-recording-toggle nil)
- (cj/video-recording-toggle nil)
-
- ;; Verify both are recording
- (should cj/audio-recording-ffmpeg-process)
- (should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴A+V " (cj/recording-modeline-indicator)))
-
- ;; Stop audio only
- (cj/audio-recording-toggle nil)
-
- ;; Verify only video still recording
- (should (null cj/audio-recording-ffmpeg-process))
- (should cj/video-recording-ffmpeg-process)
- (should (equal " 🔴Video " (cj/recording-modeline-indicator)))
-
- ;; Stop video
- (cj/video-recording-toggle nil)
-
- ;; Verify all cleared
- (should (null cj/video-recording-ffmpeg-process))
- (should (equal "" (cj/recording-modeline-indicator)))))
- (test-integration-toggle-teardown)))
-
-;;; Integration Tests - Sentinel Auto-Cleanup
-
-(ert-deftest test-integration-recording-toggle-workflow-sentinel-auto-cleanup ()
- "Test that sentinel auto-cleans when recording process dies unexpectedly.
-
-When the ffmpeg process crashes or exits unexpectedly:
-1. Sentinel detects process death
-2. Variable is automatically cleared
-3. Modeline updates to show no recording
-4. User can start new recording
-
-Components integrated:
-- cj/audio-recording-toggle (process creation)
-- cj/ffmpeg-record-audio (attaches sentinel)
-- cj/recording-process-sentinel (cleanup on death)
-- cj/recording-modeline-indicator (updates on cleanup)
-
-Validates:
-- Sentinel cleans up on unexpected process death
-- Modeline syncs when sentinel runs
-- User can toggle again after crash"
- (test-integration-toggle-setup)
- (unwind-protect
- (progn
- ;; Pre-configure devices
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor")
-
- (let ((process nil))
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) t))
- ((symbol-function 'start-process-shell-command)
- (lambda (name _buffer _cmd)
- (setq process (make-process :name name :command '("sh" "-c" "exit 1"))))))
-
- ;; Start recording
- (cj/audio-recording-toggle nil)
-
- ;; Verify recording started
- (should cj/audio-recording-ffmpeg-process)
- (should (equal " 🔴Audio " (cj/recording-modeline-indicator)))
-
- ;; Wait for process to exit (sentinel should run)
- (sit-for 0.3)
-
- ;; Verify sentinel cleaned up
- (should (null cj/audio-recording-ffmpeg-process))
- (should (equal "" (cj/recording-modeline-indicator)))
-
- ;; Verify user can start new recording after crash
- (cj/audio-recording-toggle nil)
- (should cj/audio-recording-ffmpeg-process))))
- (test-integration-toggle-teardown)))
-
-(provide 'test-integration-recording-toggle-workflow)
-;;; test-integration-recording-toggle-workflow.el ends here
diff --git a/tests/test-integration-transcription.el b/tests/test-integration-transcription.el
deleted file mode 100644
index d014d00e..00000000
--- a/tests/test-integration-transcription.el
+++ /dev/null
@@ -1,150 +0,0 @@
-;;; test-integration-transcription.el --- Integration tests for transcription -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; End-to-end integration tests for transcription workflow
-;; Tests complete workflow with temporary files and mocked processes
-;; Categories: Normal workflow, Error handling, Cleanup
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Test Helpers ----------------------------------
-
-(defun test-transcription--make-mock-audio-file ()
- "Create a temporary mock audio file for testing.
-Returns the absolute path to the file."
- (let ((file (make-temp-file "test-audio-" nil ".m4a")))
- (with-temp-file file
- (insert "Mock audio data"))
- file))
-
-(defun test-transcription--cleanup-output-files (audio-file)
- "Delete transcript and log files associated with AUDIO-FILE."
- (let* ((outputs (cj/--transcription-output-files audio-file))
- (txt-file (car outputs))
- (log-file (cdr outputs)))
- (when (file-exists-p txt-file)
- (delete-file txt-file))
- (when (file-exists-p log-file)
- (delete-file log-file))))
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-integration-transcription-output-files-created ()
- "Test that .txt and .log files are created for audio file."
- (let* ((audio-file (test-transcription--make-mock-audio-file))
- (outputs (cj/--transcription-output-files audio-file))
- (txt-file (car outputs))
- (log-file (cdr outputs)))
- (unwind-protect
- (progn
- ;; Verify output file paths are correct
- (should (string-suffix-p ".txt" txt-file))
- (should (string-suffix-p ".log" log-file))
- (should (string= (file-name-sans-extension txt-file)
- (file-name-sans-extension audio-file)))
- (should (string= (file-name-sans-extension log-file)
- (file-name-sans-extension audio-file))))
- ;; Cleanup
- (delete-file audio-file)
- (test-transcription--cleanup-output-files audio-file))))
-
-(ert-deftest test-integration-transcription-validates-file-exists ()
- "Test that transcription fails for non-existent file."
- (should-error
- (cj/--start-transcription-process "/nonexistent/audio.m4a")
- :type 'user-error))
-
-(ert-deftest test-integration-transcription-validates-audio-extension ()
- "Test that transcription fails for non-audio file."
- (let ((non-audio (make-temp-file "test-" nil ".txt")))
- (unwind-protect
- (should-error
- (cj/--start-transcription-process non-audio)
- :type 'user-error)
- (delete-file non-audio))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-integration-transcription-audio-file-detection ()
- "Test various audio file extensions are accepted."
- (dolist (ext '("m4a" "mp3" "wav" "flac" "ogg" "opus"))
- (let ((audio-file (make-temp-file "test-audio-" nil (concat "." ext))))
- (unwind-protect
- (progn
- (should (cj/--audio-file-p audio-file))
- ;; Would start transcription if script existed
- )
- (delete-file audio-file)))))
-
-(ert-deftest test-integration-transcription-filename-with-spaces ()
- "Test transcription with audio file containing spaces."
- (let ((audio-file (make-temp-file "test audio file" nil ".m4a")))
- (unwind-protect
- (let* ((outputs (cj/--transcription-output-files audio-file))
- (txt-file (car outputs))
- (log-file (cdr outputs)))
- (should (file-name-absolute-p txt-file))
- (should (file-name-absolute-p log-file)))
- (delete-file audio-file))))
-
-(ert-deftest test-integration-transcription-filename-with-special-chars ()
- "Test transcription with special characters in filename."
- (let ((audio-file (make-temp-file "test_(final)" nil ".m4a")))
- (unwind-protect
- (let* ((outputs (cj/--transcription-output-files audio-file))
- (txt-file (car outputs)))
- ;; make-temp-file adds random suffix, so just check it ends with .txt
- ;; and contains the special chars
- (should (string-suffix-p ".txt" txt-file))
- (should (string-match-p "test_(final)" txt-file)))
- (delete-file audio-file))))
-
-;; ----------------------------- Cleanup Tests ---------------------------------
-
-(ert-deftest test-integration-transcription-cleanup-completed ()
- "Test that completed transcriptions are removed from tracking."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running)
- (proc2 "file2.m4a" nil complete)
- (proc3 "file3.m4a" nil error))))
- (cj/--cleanup-completed-transcriptions)
- (should (= 1 (length cj/transcriptions-list)))
- (should (eq 'running (nth 3 (car cj/transcriptions-list))))))
-
-(ert-deftest test-integration-transcription-cleanup-all-complete ()
- "Test cleanup when all transcriptions are complete."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil complete)
- (proc2 "file2.m4a" nil error))))
- (cj/--cleanup-completed-transcriptions)
- (should (null cj/transcriptions-list))))
-
-(ert-deftest test-integration-transcription-cleanup-preserves-running ()
- "Test that running transcriptions are not cleaned up."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running)
- (proc2 "file2.m4a" nil running))))
- (cj/--cleanup-completed-transcriptions)
- (should (= 2 (length cj/transcriptions-list)))))
-
-;; ----------------------------- Backend Tests ---------------------------------
-
-(ert-deftest test-integration-transcription-script-path-exists ()
- "Test that transcription scripts exist in expected location."
- (dolist (backend '(local-whisper openai-api))
- (let ((cj/transcribe-backend backend))
- (let ((script (cj/--transcription-script-path)))
- (should (file-name-absolute-p script))
- ;; Note: Script may not exist in test environment, just check path format
- (should (string-match-p "scripts/" script))))))
-
-(provide 'test-integration-transcription)
-;;; test-integration-transcription.el ends here
diff --git a/tests/test-jumper.el b/tests/test-jumper.el
deleted file mode 100644
index fa65d3f4..00000000
--- a/tests/test-jumper.el
+++ /dev/null
@@ -1,352 +0,0 @@
-;;; test-jumper.el --- Tests for jumper.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for jumper.el - location navigation using registers.
-;;
-;; Testing approach:
-;; - Tests focus on internal `jumper--do-*` functions (pure business logic)
-;; - Interactive wrappers are thin UI layers and tested minimally
-;; - Each test is isolated with setup/teardown to reset global state
-;; - Tests verify return values, not user messages
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load the module
-(require 'jumper)
-
-;;; Test Utilities
-
-(defvar test-jumper--original-registers nil
- "Backup of jumper registers before test.")
-
-(defvar test-jumper--original-index nil
- "Backup of jumper index before test.")
-
-(defun test-jumper-setup ()
- "Reset jumper state before each test."
- ;; Backup current state
- (setq test-jumper--original-registers jumper--registers)
- (setq test-jumper--original-index jumper--next-index)
- ;; Reset to clean state
- (setq jumper--registers (make-vector jumper-max-locations nil))
- (setq jumper--next-index 0))
-
-(defun test-jumper-teardown ()
- "Restore jumper state after each test."
- (setq jumper--registers test-jumper--original-registers)
- (setq jumper--next-index test-jumper--original-index))
-
-;;; Normal Cases - Store Location
-
-(ert-deftest test-jumper-store-first-location ()
- "Should store first location and return register character."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test content")
- (goto-char (point-min))
- (let ((result (jumper--do-store-location)))
- (should (= result ?0))
- (should (= jumper--next-index 1))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-store-multiple-locations ()
- "Should store multiple locations in sequence."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (should (= (jumper--do-store-location) ?0))
- (forward-line 1)
- (should (= (jumper--do-store-location) ?1))
- (forward-line 1)
- (should (= (jumper--do-store-location) ?2))
- (should (= jumper--next-index 3)))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-store-duplicate-location ()
- "Should detect and reject duplicate locations."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test content")
- (goto-char (point-min))
- (should (= (jumper--do-store-location) ?0))
- (should (eq (jumper--do-store-location) 'already-exists))
- (should (= jumper--next-index 1)))
- (test-jumper-teardown))
-
-;;; Normal Cases - Jump to Location
-
-(ert-deftest test-jumper-jump-to-stored-location ()
- "Should jump to a previously stored location."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (jumper--do-store-location)
- (goto-char (point-max))
- (let ((result (jumper--do-jump-to-location 0)))
- (should (eq result 'jumped))
- (should (= (point) (point-min)))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-jump-toggle-with-single-location ()
- "Should toggle between current and stored location."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (jumper--do-store-location)
- ;; Move away
- (goto-char (point-max))
- ;; Toggle should jump back
- (let ((result (jumper--do-jump-to-location nil)))
- (should (eq result 'jumped))
- (should (= (point) (point-min)))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-jump-already-at-location ()
- "Should detect when already at the only stored location."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2")
- (goto-char (point-min))
- (jumper--do-store-location)
- ;; Try to toggle while at the location
- (let ((result (jumper--do-jump-to-location nil)))
- (should (eq result 'already-there))))
- (test-jumper-teardown))
-
-;;; Normal Cases - Remove Location
-
-(ert-deftest test-jumper-remove-location ()
- "Should remove a stored location."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test content")
- (goto-char (point-min))
- (jumper--do-store-location)
- (let ((result (jumper--do-remove-location 0)))
- (should (eq result t))
- (should (= jumper--next-index 0))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-remove-reorders-registers ()
- "Should reorder registers after removal from middle."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (jumper--do-store-location) ; Register 0
- (forward-line 1)
- (jumper--do-store-location) ; Register 1
- (forward-line 1)
- (jumper--do-store-location) ; Register 2
- ;; Remove middle (index 1)
- (jumper--do-remove-location 1)
- (should (= jumper--next-index 2))
- ;; What was at index 2 should now be at index 1
- (should (= (aref jumper--registers 1) ?2)))
- (test-jumper-teardown))
-
-;;; Boundary Cases - Store Location
-
-(ert-deftest test-jumper-store-at-capacity ()
- "Should successfully store location at maximum capacity."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test content")
- (goto-char (point-min))
- ;; Fill to capacity
- (dotimes (i jumper-max-locations)
- (forward-char 1)
- (should (= (jumper--do-store-location) (+ ?0 i))))
- (should (= jumper--next-index jumper-max-locations)))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-store-when-full ()
- "Should return 'no-space when all registers are full."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "01234567890123456789")
- (goto-char (point-min))
- ;; Fill to capacity
- (dotimes (i jumper-max-locations)
- (forward-char 1)
- (jumper--do-store-location))
- ;; Try to store one more
- (forward-char 1)
- (should (eq (jumper--do-store-location) 'no-space))
- (should (= jumper--next-index jumper-max-locations)))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-store-in-different-buffers ()
- "Should store locations across different buffers."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "buffer 1")
- (goto-char (point-min))
- (should (= (jumper--do-store-location) ?0))
- (with-temp-buffer
- (insert "buffer 2")
- (goto-char (point-min))
- (should (= (jumper--do-store-location) ?1))
- (should (= jumper--next-index 2))))
- (test-jumper-teardown))
-
-;;; Boundary Cases - Jump to Location
-
-(ert-deftest test-jumper-jump-with-no-locations ()
- "Should return 'no-locations when nothing is stored."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test")
- (let ((result (jumper--do-jump-to-location 0)))
- (should (eq result 'no-locations))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-jump-to-first-location ()
- "Should jump to location at index 0."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2")
- (goto-char (point-min))
- (jumper--do-store-location)
- (forward-line 1)
- (jumper--do-store-location)
- (goto-char (point-max))
- (jumper--do-jump-to-location 0)
- (should (= (point) (point-min))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-jump-to-last-location ()
- "Should jump to last location (register 'z)."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (jumper--do-store-location)
- (let ((line2-pos (line-beginning-position 2)))
- (goto-char line2-pos)
- ;; Jump to location 0 (this stores current location in 'z)
- (jumper--do-jump-to-location 0)
- (should (= (point) (point-min)))
- ;; Jump to last location should go back to line 2
- (let ((result (jumper--do-jump-to-location -1)))
- (should (eq result 'jumped))
- (should (= (point) line2-pos)))))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-jump-to-max-index ()
- "Should jump to location at maximum index."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "0123456789012345678")
- (goto-char (point-min))
- ;; Store at all positions
- (dotimes (i jumper-max-locations)
- (forward-char 1)
- (jumper--do-store-location))
- (goto-char (point-min))
- ;; Jump to last one (index 9, which is at position 10)
- (jumper--do-jump-to-location (1- jumper-max-locations))
- (should (= (point) (1+ jumper-max-locations))))
- (test-jumper-teardown))
-
-;;; Boundary Cases - Remove Location
-
-(ert-deftest test-jumper-remove-first-location ()
- "Should remove location at index 0."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2")
- (goto-char (point-min))
- (jumper--do-store-location)
- (forward-line 1)
- (jumper--do-store-location)
- (jumper--do-remove-location 0)
- (should (= jumper--next-index 1))
- ;; What was at index 1 should now be at index 0
- (should (= (aref jumper--registers 0) ?1)))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-remove-last-location ()
- "Should remove location at last index."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "line 1\nline 2\nline 3")
- (goto-char (point-min))
- (jumper--do-store-location)
- (forward-line 1)
- (jumper--do-store-location)
- (forward-line 1)
- (jumper--do-store-location)
- (jumper--do-remove-location 2)
- (should (= jumper--next-index 2)))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-remove-with-cancel ()
- "Should return 'cancelled when index is -1."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test")
- (goto-char (point-min))
- (jumper--do-store-location)
- (let ((result (jumper--do-remove-location -1)))
- (should (eq result 'cancelled))
- (should (= jumper--next-index 1))))
- (test-jumper-teardown))
-
-;;; Error Cases
-
-(ert-deftest test-jumper-remove-when-empty ()
- "Should return 'no-locations when removing from empty list."
- (test-jumper-setup)
- (let ((result (jumper--do-remove-location 0)))
- (should (eq result 'no-locations)))
- (test-jumper-teardown))
-
-;;; Helper Function Tests
-
-(ert-deftest test-jumper-location-key-format ()
- "Should generate unique location keys."
- (with-temp-buffer
- (insert "line 1\nline 2")
- (goto-char (point-min))
- (let ((key1 (jumper--location-key)))
- (forward-line 1)
- (let ((key2 (jumper--location-key)))
- (should-not (string= key1 key2))
- ;; Keys should contain buffer name and position info
- (should (string-match-p ":" key1))
- (should (string-match-p ":" key2))))))
-
-(ert-deftest test-jumper-register-available-p ()
- "Should correctly report register availability."
- (test-jumper-setup)
- (should (jumper--register-available-p))
- ;; Fill to capacity
- (setq jumper--next-index jumper-max-locations)
- (should-not (jumper--register-available-p))
- (test-jumper-teardown))
-
-(ert-deftest test-jumper-format-location ()
- "Should format location for display."
- (test-jumper-setup)
- (with-temp-buffer
- (insert "test line with some content")
- (goto-char (point-min))
- (jumper--do-store-location)
- (let ((formatted (jumper--format-location 0)))
- (should formatted)
- (should (string-match-p "\\[0\\]" formatted))
- (should (string-match-p "test line" formatted))))
- (test-jumper-teardown))
-
-(provide 'test-jumper)
-;;; test-jumper.el ends here
diff --git a/tests/test-keyboard-macros.el b/tests/test-keyboard-macros.el
deleted file mode 100644
index 3a1ae523..00000000
--- a/tests/test-keyboard-macros.el
+++ /dev/null
@@ -1,356 +0,0 @@
-;;; test-keyboard-macros.el --- ERT tests for keyboard-macros -*- lexical-binding: t; -*-
-
-;; Author: Claude Code and cjennings
-;; Keywords: tests, keyboard-macros
-
-;;; Commentary:
-;; ERT tests for keyboard-macros.el functions.
-;; Tests are organized into normal, boundary, and error cases.
-
-;;; Code:
-
-(require 'ert)
-(require 'keyboard-macros)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-keyboard-macros-setup ()
- "Set up test environment for keyboard-macros tests."
- (cj/create-test-base-dir)
- ;; Bind macros-file to test location
- (setq macros-file (expand-file-name "test-macros.el" cj/test-base-dir))
- ;; Reset state flags
- (setq cj/macros-loaded nil)
- (setq cj/macros-loading nil)
- ;; Clear any existing macro
- (setq last-kbd-macro nil))
-
-(defun test-keyboard-macros-teardown ()
- "Clean up test environment after keyboard-macros tests."
- ;; Kill any buffers visiting the test macros file
- (when-let ((buf (get-file-buffer macros-file)))
- (kill-buffer buf))
- ;; Clean up test directory
- (cj/delete-test-base-dir)
- ;; Reset state
- (setq cj/macros-loaded nil)
- (setq cj/macros-loading nil)
- (setq last-kbd-macro nil))
-
-;;; Normal Cases
-
-(ert-deftest test-keyboard-macros-ensure-macros-loaded-first-time-normal ()
- "Normal: macros file is loaded on first call when file exists."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create a macros file with a simple macro definition
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n")
- (insert "(fset 'test-macro [?h ?e ?l ?l ?o])\n"))
- ;; Verify initial state
- (should (not cj/macros-loaded))
- ;; Load macros
- (cj/ensure-macros-loaded)
- ;; Verify loaded
- (should cj/macros-loaded)
- (should (fboundp 'test-macro)))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-ensure-macros-loaded-idempotent-normal ()
- "Normal: subsequent calls don't reload when flag is already true."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create a macros file
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n"))
- ;; First load
- (cj/ensure-macros-loaded)
- (should cj/macros-loaded)
- ;; Modify the file after loading
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n")
- (insert "(fset 'new-macro [?n ?e ?w])\n"))
- ;; Second call should not reload
- (cj/ensure-macros-loaded)
- ;; new-macro should not be defined because file wasn't reloaded
- (should (not (fboundp 'new-macro))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-ensure-macros-file-creates-new-normal ()
- "Normal: ensure-macros-file creates new file with lexical-binding header."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (should (not (file-exists-p macros-file)))
- (ensure-macros-file macros-file)
- (should (file-exists-p macros-file))
- (with-temp-buffer
- (insert-file-contents macros-file)
- (should (string-match-p "lexical-binding: t" (buffer-string)))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-ensure-macros-file-exists-normal ()
- "Normal: ensure-macros-file leaves existing file untouched."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n")
- (insert "(fset 'existing-macro [?t ?e ?s ?t])\n"))
- (let ((original-content (with-temp-buffer
- (insert-file-contents macros-file)
- (buffer-string))))
- (ensure-macros-file macros-file)
- (should (string= original-content
- (with-temp-buffer
- (insert-file-contents macros-file)
- (buffer-string))))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-start-or-end-toggle-normal ()
- "Normal: starting and stopping macro recording toggles defining-kbd-macro."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create empty macros file
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n"))
- ;; Start recording
- (should (not defining-kbd-macro))
- (cj/kbd-macro-start-or-end)
- (should defining-kbd-macro)
- ;; Stop recording
- (cj/kbd-macro-start-or-end)
- (should (not defining-kbd-macro)))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-valid-name-normal ()
- "Normal: saving a macro with valid name writes to file and returns name."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Set up a macro
- (setq last-kbd-macro [?t ?e ?s ?t])
- ;; Save it
- (let ((result (cj/save-maybe-edit-macro "test-macro")))
- (should (string= result "test-macro"))
- (should (file-exists-p macros-file))
- ;; Verify macro was written to file
- (with-temp-buffer
- (insert-file-contents macros-file)
- (should (string-match-p "test-macro" (buffer-string))))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-without-prefix-arg-normal ()
- "Normal: without prefix arg, returns to original buffer."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (let ((original-buffer (current-buffer))
- (current-prefix-arg nil))
- (cj/save-maybe-edit-macro "test-macro")
- ;; Should return to original buffer (or stay if it was the macros file)
- (should (or (eq (current-buffer) original-buffer)
- (not (eq (current-buffer) (get-file-buffer macros-file)))))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-with-prefix-arg-normal ()
- "Normal: with prefix arg, opens macros file for editing."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (let ((current-prefix-arg t))
- (cj/save-maybe-edit-macro "test-macro")
- ;; Should be in the macros file buffer
- (should (eq (current-buffer) (get-file-buffer macros-file)))))
- (test-keyboard-macros-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-keyboard-macros-name-single-character-boundary ()
- "Boundary: macro name with single letter (minimum valid length)."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (let ((result (cj/save-maybe-edit-macro "a")))
- (should (string= result "a"))
- (should (file-exists-p macros-file))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-name-with-numbers-boundary ()
- "Boundary: macro name containing letters, numbers, and hyphens."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (let ((result (cj/save-maybe-edit-macro "macro-123-test")))
- (should (string= result "macro-123-test"))
- (should (file-exists-p macros-file))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-name-all-caps-boundary ()
- "Boundary: macro name with uppercase letters."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (let ((result (cj/save-maybe-edit-macro "TESTMACRO")))
- (should (string= result "TESTMACRO"))
- (should (file-exists-p macros-file))))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-empty-macro-file-boundary ()
- "Boundary: loading behavior when macros file exists but is empty."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create empty file
- (with-temp-file macros-file
- (insert ""))
- (should (not cj/macros-loaded))
- ;; Should handle empty file gracefully
- (cj/ensure-macros-loaded)
- ;; Loading an empty file should still set the flag
- (should cj/macros-loaded))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-whitespace-only-name-boundary ()
- "Boundary: whitespace-only name (spaces, tabs) is rejected."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (should-error (cj/save-maybe-edit-macro " \t ")))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-concurrent-load-attempts-boundary ()
- "Boundary: cj/macros-loading lock prevents race conditions."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n"))
- ;; Simulate concurrent load by setting the lock
- (setq cj/macros-loading t)
- (cj/ensure-macros-loaded)
- ;; Should not load because lock is set
- (should (not cj/macros-loaded))
- ;; Release lock and try again
- (setq cj/macros-loading nil)
- (cj/ensure-macros-loaded)
- (should cj/macros-loaded))
- (test-keyboard-macros-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-keyboard-macros-save-empty-name-error ()
- "Error: empty string name triggers user-error."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (should-error (cj/save-maybe-edit-macro "") :type 'user-error))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-invalid-name-special-chars-error ()
- "Error: names with special characters trigger user-error."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (should-error (cj/save-maybe-edit-macro "test@macro") :type 'user-error)
- (should-error (cj/save-maybe-edit-macro "test!macro") :type 'user-error)
- (should-error (cj/save-maybe-edit-macro "test#macro") :type 'user-error))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-invalid-name-starts-with-number-error ()
- "Error: name starting with number triggers user-error."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (should-error (cj/save-maybe-edit-macro "123macro") :type 'user-error))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-invalid-name-has-spaces-error ()
- "Error: name with spaces triggers user-error."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- (should-error (cj/save-maybe-edit-macro "test macro") :type 'user-error))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-no-macro-defined-error ()
- "Error: saving when last-kbd-macro is nil triggers user-error."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro nil)
- (should-error (cj/save-maybe-edit-macro "test-macro") :type 'user-error))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-load-malformed-file-error ()
- "Error: error handling when macros file has syntax errors."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create a malformed macros file
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n")
- (insert "(fset 'broken-macro [incomplete"))
- (should (not cj/macros-loaded))
- ;; Should handle error gracefully (prints message but doesn't crash)
- (cj/ensure-macros-loaded)
- ;; Should not be marked as loaded due to error
- (should (not cj/macros-loaded)))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-save-file-write-error-error ()
- "Error: error handling when unable to write to macros file."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- (setq last-kbd-macro [?t ?e ?s ?t])
- ;; Create the file and make it read-only
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n"))
- (set-file-modes macros-file #o444)
- ;; Should error when trying to save
- (condition-case err
- (progn
- (cj/save-maybe-edit-macro "test-macro")
- (should nil)) ;; Should not reach here
- (error
- ;; Expected to error
- (should t)))
- ;; Clean up permissions for teardown
- (set-file-modes macros-file #o644))
- (test-keyboard-macros-teardown)))
-
-(ert-deftest test-keyboard-macros-load-file-read-error-error ()
- "Error: error handling when unable to read macros file."
- (test-keyboard-macros-setup)
- (unwind-protect
- (progn
- ;; Create file and remove read permissions
- (with-temp-file macros-file
- (insert ";;; -*- lexical-binding: t -*-\n"))
- (set-file-modes macros-file #o000)
- (should (not cj/macros-loaded))
- ;; Should handle error gracefully
- (cj/ensure-macros-loaded)
- ;; Should not be marked as loaded
- (should (not cj/macros-loaded))
- ;; Clean up permissions for teardown
- (set-file-modes macros-file #o644))
- (test-keyboard-macros-teardown)))
-
-(provide 'test-keyboard-macros)
-;;; test-keyboard-macros.el ends here
diff --git a/tests/test-lorem-optimum-benchmark.el b/tests/test-lorem-optimum-benchmark.el
deleted file mode 100644
index 57d5ae5f..00000000
--- a/tests/test-lorem-optimum-benchmark.el
+++ /dev/null
@@ -1,223 +0,0 @@
-;;; test-lorem-optimum-benchmark.el --- Performance tests for lorem-optimum.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Benchmark and performance tests for the Markov chain implementation.
-;;
-;; These tests measure:
-;; - Learning time scaling with input size
-;; - Multiple learning operations (exposes key rebuild overhead)
-;; - Generation time scaling
-;; - Memory usage (hash table growth)
-;;
-;; Performance baseline targets (on modern hardware):
-;; - Learn 1000 words: < 10ms
-;; - Learn 10,000 words: < 100ms
-;; - 100 learn operations of 100 words each: < 500ms (current bottleneck!)
-;; - Generate 100 words: < 5ms
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load the module
-(require 'lorem-optimum)
-
-;;; Benchmark Helpers
-
-(defun benchmark-time (func)
- "Time execution of FUNC and return milliseconds."
- (let ((start (current-time)))
- (funcall func)
- (let ((end (current-time)))
- (* 1000.0 (float-time (time-subtract end start))))))
-
-(defun generate-test-text (word-count)
- "Generate WORD-COUNT words of test text with some repetition."
- (let ((words '("lorem" "ipsum" "dolor" "sit" "amet" "consectetur"
- "adipiscing" "elit" "sed" "do" "eiusmod" "tempor"
- "incididunt" "ut" "labore" "et" "dolore" "magna" "aliqua"))
- (result '()))
- (dotimes (i word-count)
- (push (nth (mod i (length words)) words) result)
- (when (zerop (mod i 10))
- (push "." result)))
- (mapconcat #'identity (nreverse result) " ")))
-
-(defun benchmark-report (name time-ms)
- "Report benchmark NAME with TIME-MS."
- (message "BENCHMARK [%s]: %.2f ms" name time-ms))
-
-;;; Learning Performance Tests
-
-(ert-deftest benchmark-learn-1k-words ()
- "Benchmark learning 1000 words."
- (let* ((text (generate-test-text 1000))
- (chain (cj/markov-chain-create))
- (time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (benchmark-report "Learn 1K words" time)
- (should (< time 50.0)))) ; Should be < 50ms
-
-(ert-deftest benchmark-learn-10k-words ()
- "Benchmark learning 10,000 words.
-DISABLED: Takes too long (minutes instead of seconds).
-Needs lorem-optimum performance optimization before re-enabling."
- :tags '(:slow)
- (let* ((text (generate-test-text 10000))
- (chain (cj/markov-chain-create))
- (time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (benchmark-report "Learn 10K words" time)
- (should (< time 500.0)))) ; Should be < 500ms
-
-(ert-deftest benchmark-learn-100k-words ()
- "Benchmark learning 100,000 words (stress test)."
- :tags '(:slow)
- (let* ((text (generate-test-text 100000))
- (chain (cj/markov-chain-create))
- (time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (benchmark-report "Learn 100K words" time)
- ;; This may be slow due to key rebuild
- (message "Hash table size: %d bigrams"
- (hash-table-count (cj/markov-chain-map chain)))))
-
-;;; Multiple Learning Operations (Exposes Quadratic Behavior)
-
-(ert-deftest benchmark-multiple-learns-10x100 ()
- "Benchmark 10 learn operations of 100 words each."
- (let ((chain (cj/markov-chain-create))
- (times '()))
- (dotimes (i 10)
- (let* ((text (generate-test-text 100))
- (time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (push time times)))
- (let ((total (apply #'+ times))
- (avg (/ (apply #'+ times) 10.0))
- (max-time (apply #'max times)))
- (benchmark-report "10x learn 100 words - TOTAL" total)
- (benchmark-report "10x learn 100 words - AVG" avg)
- (benchmark-report "10x learn 100 words - MAX" max-time)
- (message "Times: %S" (nreverse times))
- ;; Note: Watch if later operations are slower (quadratic behavior)
- (should (< total 100.0))))) ; Total should be < 100ms
-
-(ert-deftest benchmark-multiple-learns-100x100 ()
- "Benchmark 100 learn operations of 100 words each (key rebuild overhead)."
- :tags '(:slow)
- (let ((chain (cj/markov-chain-create))
- (times '())
- (measurements '()))
- (dotimes (i 100)
- (let* ((text (generate-test-text 100))
- (time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (push time times)
- ;; Sample measurements every 10 iterations
- (when (zerop (mod i 10))
- (push (cons i time) measurements))))
- (let ((total (apply #'+ times))
- (avg (/ (apply #'+ times) 100.0))
- (first-10-avg (/ (apply #'+ (last times 10)) 10.0))
- (last-10-avg (/ (apply #'+ (seq-take times 10)) 10.0)))
- (benchmark-report "100x learn 100 words - TOTAL" total)
- (benchmark-report "100x learn 100 words - AVG" avg)
- (benchmark-report "100x learn - First 10 AVG" first-10-avg)
- (benchmark-report "100x learn - Last 10 AVG" last-10-avg)
- (message "Sampled times (iteration, ms): %S" (nreverse measurements))
- (message "Hash table size: %d bigrams"
- (hash-table-count (cj/markov-chain-map chain)))
- ;; This exposes the quadratic behavior: last operations much slower
- (when (> last-10-avg (* 2.0 first-10-avg))
- (message "WARNING: Learning slows down significantly over time!")
- (message " First 10 avg: %.2f ms" first-10-avg)
- (message " Last 10 avg: %.2f ms" last-10-avg)
- (message " Ratio: %.1fx slower" (/ last-10-avg first-10-avg))))))
-
-;;; Generation Performance Tests
-
-(ert-deftest benchmark-generate-100-words ()
- "Benchmark generating 100 words."
- (let* ((text (generate-test-text 1000))
- (chain (cj/markov-chain-create)))
- (cj/markov-learn chain text)
- (let ((time (benchmark-time
- (lambda () (cj/markov-generate chain 100)))))
- (benchmark-report "Generate 100 words" time)
- (should (< time 30.0))))) ; Should be < 30ms
-
-;;; Tokenization Performance Tests
-
-(ert-deftest benchmark-tokenize-10k-words ()
- "Benchmark tokenizing 10,000 words.
-DISABLED: Takes too long (minutes instead of seconds).
-Needs lorem-optimum performance optimization before re-enabling."
- :tags '(:slow)
- (let* ((text (generate-test-text 10000))
- (time (benchmark-time
- (lambda () (cj/markov-tokenize text)))))
- (benchmark-report "Tokenize 10K words" time)
- (should (< time 50.0)))) ; Tokenization should be fast
-
-;;; Memory/Size Tests
-
-(ert-deftest benchmark-chain-growth ()
- "Measure hash table growth with increasing input."
- (let ((chain (cj/markov-chain-create))
- (sizes '()))
- (dolist (word-count '(100 500 1000 5000 10000))
- (let ((text (generate-test-text word-count)))
- (cj/markov-learn chain text)
- (let ((size (hash-table-count (cj/markov-chain-map chain))))
- (push (cons word-count size) sizes)
- (message "After %d words: %d unique bigrams" word-count size))))
- (message "Growth pattern: %S" (nreverse sizes))))
-
-;;; Comparison: Tokenization vs Learning
-
-(ert-deftest benchmark-tokenize-vs-learn ()
- "Compare tokenization time to total learning time."
- (let* ((text (generate-test-text 5000))
- (tokenize-time (benchmark-time
- (lambda () (cj/markov-tokenize text))))
- (chain (cj/markov-chain-create))
- (learn-time (benchmark-time
- (lambda () (cj/markov-learn chain text)))))
- (benchmark-report "Tokenize 5K words" tokenize-time)
- (benchmark-report "Learn 5K words (total)" learn-time)
- (message "Tokenization is %.1f%% of total learning time"
- (* 100.0 (/ tokenize-time learn-time)))))
-
-;;; Real-world Scenario
-
-(ert-deftest benchmark-realistic-usage ()
- "Benchmark realistic usage: learn from multiple sources, generate paragraphs."
- (let ((chain (cj/markov-chain-create))
- (learn-total 0.0)
- (gen-total 0.0))
- ;; Simulate learning from 10 different sources
- (dotimes (i 10)
- (let ((text (generate-test-text 500)))
- (setq learn-total
- (+ learn-total
- (benchmark-time (lambda () (cj/markov-learn chain text)))))))
-
- ;; Generate 5 paragraphs
- (dotimes (i 5)
- (setq gen-total
- (+ gen-total
- (benchmark-time (lambda () (cj/markov-generate chain 50))))))
-
- (benchmark-report "Realistic: 10 learns (500 words each)" learn-total)
- (benchmark-report "Realistic: 5 generations (50 words each)" gen-total)
- (benchmark-report "Realistic: TOTAL TIME" (+ learn-total gen-total))
- (message "Final chain size: %d bigrams"
- (hash-table-count (cj/markov-chain-map chain)))))
-
-(provide 'test-lorem-optimum-benchmark)
-;;; test-lorem-optimum-benchmark.el ends here
diff --git a/tests/test-lorem-optimum.el b/tests/test-lorem-optimum.el
deleted file mode 100644
index ca2e52f4..00000000
--- a/tests/test-lorem-optimum.el
+++ /dev/null
@@ -1,242 +0,0 @@
-;;; test-lorem-optimum.el --- Tests for lorem-optimum.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for lorem-optimum.el Markov chain text generation.
-;;
-;; Tests cover:
-;; - Tokenization
-;; - Learning and chain building
-;; - Text generation
-;; - Capitalization fixing
-;; - Token joining
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load the module
-(require 'lorem-optimum)
-
-;;; Test Helpers
-
-(defun test-chain ()
- "Create a fresh test chain."
- (cj/markov-chain-create))
-
-(defun test-learn (text)
- "Create a chain and learn TEXT."
- (let ((chain (test-chain)))
- (cj/markov-learn chain text)
- chain))
-
-;;; Tokenization Tests
-
-(ert-deftest test-tokenize-simple ()
- "Should tokenize simple words."
- (let ((result (cj/markov-tokenize "hello world")))
- (should (equal result '("hello" "world")))))
-
-(ert-deftest test-tokenize-with-punctuation ()
- "Should separate punctuation as tokens."
- (let ((result (cj/markov-tokenize "Hello, world!")))
- (should (equal result '("Hello" "," "world" "!")))))
-
-(ert-deftest test-tokenize-multiple-spaces ()
- "Should handle multiple spaces."
- (let ((result (cj/markov-tokenize "hello world")))
- (should (equal result '("hello" "world")))))
-
-(ert-deftest test-tokenize-newlines ()
- "Should handle newlines as whitespace."
- (let ((result (cj/markov-tokenize "hello\nworld")))
- (should (equal result '("hello" "world")))))
-
-(ert-deftest test-tokenize-mixed-punctuation ()
- "Should tokenize complex punctuation."
- (let ((result (cj/markov-tokenize "one, two; three.")))
- (should (equal result '("one" "," "two" ";" "three" ".")))))
-
-(ert-deftest test-tokenize-empty ()
- "Should handle empty string."
- (let ((result (cj/markov-tokenize "")))
- (should (null result))))
-
-(ert-deftest test-tokenize-whitespace-only ()
- "Should return nil for whitespace only."
- (let ((result (cj/markov-tokenize " \n\t ")))
- (should (null result))))
-
-;;; Markov Learn Tests
-
-(ert-deftest test-learn-basic ()
- "Should learn simple text."
- (let ((chain (test-learn "one two three four")))
- (should (cj/markov-chain-p chain))
- (should (> (hash-table-count (cj/markov-chain-map chain)) 0))))
-
-(ert-deftest test-learn-creates-bigrams ()
- "Should create bigram mappings."
- (let ((chain (test-learn "one two three")))
- (should (gethash '("one" "two") (cj/markov-chain-map chain)))))
-
-(ert-deftest test-learn-stores-following-word ()
- "Should store following word for bigram."
- (let ((chain (test-learn "one two three")))
- (should (member "three" (gethash '("one" "two") (cj/markov-chain-map chain))))))
-
-(ert-deftest test-learn-builds-keys-list ()
- "Should build keys list lazily when accessed."
- (let ((chain (test-learn "one two three four")))
- ;; Keys are built lazily, so initially nil
- (should (null (cj/markov-chain-keys chain)))
- ;; After calling random-key, keys should be built
- (cj/markov-random-key chain)
- (should (> (length (cj/markov-chain-keys chain)) 0))))
-
-(ert-deftest test-learn-repeated-patterns ()
- "Should accumulate repeated patterns."
- (let ((chain (test-learn "one two three one two four")))
- (let ((nexts (gethash '("one" "two") (cj/markov-chain-map chain))))
- (should (= (length nexts) 2))
- (should (member "three" nexts))
- (should (member "four" nexts)))))
-
-(ert-deftest test-learn-incremental ()
- "Should support incremental learning."
- (let ((chain (test-chain)))
- (cj/markov-learn chain "one two three")
- (cj/markov-learn chain "four five six")
- (should (> (hash-table-count (cj/markov-chain-map chain)) 0))))
-
-;;; Token Joining Tests
-
-(ert-deftest test-join-simple-words ()
- "Should join words with spaces."
- (let ((result (cj/markov-join-tokens '("hello" "world"))))
- (should (string-match-p "^Hello world" result))))
-
-(ert-deftest test-join-with-punctuation ()
- "Should attach punctuation without spaces."
- (let ((result (cj/markov-join-tokens '("hello" "," "world"))))
- (should (string-match-p "Hello, world" result))))
-
-(ert-deftest test-join-capitalizes-first ()
- "Should capitalize first word."
- (let ((result (cj/markov-join-tokens '("hello" "world"))))
- (should (string-match-p "^H" result))))
-
-(ert-deftest test-join-adds-period ()
- "Should add period if missing."
- (let ((result (cj/markov-join-tokens '("hello" "world"))))
- (should (string-match-p "\\.$" result))))
-
-(ert-deftest test-join-preserves-existing-period ()
- "Should not double-add period."
- (let ((result (cj/markov-join-tokens '("hello" "world" "."))))
- (should (string-match-p "\\.$" result))
- (should-not (string-match-p "\\.\\.$" result))))
-
-(ert-deftest test-join-empty-tokens ()
- "Should handle empty token list."
- (let ((result (cj/markov-join-tokens '())))
- (should (equal result "."))))
-
-;;; Capitalization Tests
-
-(ert-deftest test-capitalize-first-word ()
- "Should capitalize first word."
- (let ((result (cj/markov-fix-capitalization "hello world")))
- (should (string-match-p "^Hello" result))))
-
-(ert-deftest test-capitalize-after-period ()
- "Should capitalize after period."
- (let ((result (cj/markov-fix-capitalization "hello. world")))
- (should (string-match-p "Hello\\. World" result))))
-
-(ert-deftest test-capitalize-after-exclamation ()
- "Should capitalize after exclamation."
- (let ((result (cj/markov-fix-capitalization "hello! world")))
- (should (string-match-p "Hello! World" result))))
-
-(ert-deftest test-capitalize-after-question ()
- "Should capitalize after question mark."
- (let ((result (cj/markov-fix-capitalization "hello? world")))
- (should (string-match-p "Hello\\? World" result))))
-
-(ert-deftest test-capitalize-skip-non-alpha ()
- "Should skip non-alphabetic tokens."
- (let ((result (cj/markov-fix-capitalization "hello. 123 world")))
- (should (string-match-p "123" result))))
-
-(ert-deftest test-capitalize-multiple-sentences ()
- "Should capitalize all sentences."
- (let ((result (cj/markov-fix-capitalization "first. second. third")))
- (should (string-match-p "First\\. Second\\. Third" result))))
-
-;;; Generation Tests (deterministic with fixed chain)
-
-(ert-deftest test-generate-produces-output ()
- "Should generate non-empty output."
- (let ((chain (test-learn "Lorem ipsum dolor sit amet consectetur adipiscing elit")))
- (let ((result (cj/markov-generate chain 5)))
- (should (stringp result))
- (should (> (length result) 0)))))
-
-(ert-deftest test-generate-empty-chain ()
- "Should handle empty chain gracefully."
- (let ((chain (test-chain)))
- (let ((result (cj/markov-generate chain 5)))
- (should (or (null result) (string-empty-p result))))))
-
-(ert-deftest test-generate-respects-start ()
- "Should use provided start state if available."
- (let ((chain (test-learn "Lorem ipsum dolor sit amet")))
- (let ((result (cj/markov-generate chain 3 '("Lorem" "ipsum"))))
- (should (stringp result))
- ;; Should start with Lorem or similar
- (should (> (length result) 0)))))
-
-;;; Integration Tests
-
-(ert-deftest test-full-workflow ()
- "Should complete full learn-generate workflow."
- (let ((chain (test-chain)))
- (cj/markov-learn chain "The quick brown fox jumps over the lazy dog")
- (let ((result (cj/markov-generate chain 8)))
- (should (stringp result))
- (should (> (length result) 0))
- (should (string-match-p "^[A-Z]" result))
- (should (string-match-p "[.!?]$" result)))))
-
-(ert-deftest test-latin-like-output ()
- "Should generate Latin-like text from Latin input."
- (let ((chain (test-chain)))
- (cj/markov-learn chain "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
- (let ((result (cj/markov-generate chain 10)))
- (should (stringp result))
- (should (> (length result) 10)))))
-
-;;; Edge Cases
-
-(ert-deftest test-learn-short-text ()
- "Should handle text shorter than trigram."
- (let ((chain (test-learn "one two")))
- (should (cj/markov-chain-p chain))))
-
-(ert-deftest test-learn-single-word ()
- "Should handle single word."
- (let ((chain (test-learn "word")))
- (should (cj/markov-chain-p chain))))
-
-(ert-deftest test-generate-requested-count-small ()
- "Should handle small generation count."
- (let ((chain (test-learn "one two three four five")))
- (let ((result (cj/markov-generate chain 2)))
- (should (stringp result)))))
-
-(provide 'test-lorem-optimum)
-;;; test-lorem-optimum.el ends here
diff --git a/tests/test-music-config--append-track-to-m3u-file.el b/tests/test-music-config--append-track-to-m3u-file.el
deleted file mode 100644
index 2bf3e87d..00000000
--- a/tests/test-music-config--append-track-to-m3u-file.el
+++ /dev/null
@@ -1,187 +0,0 @@
-;;; test-music-config--append-track-to-m3u-file.el --- Tests for appending tracks to M3U files -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--append-track-to-m3u-file function.
-;; Tests the pure, deterministic helper that appends track paths to M3U files.
-;;
-;; Test organization:
-;; - Normal Cases: Standard append operations
-;; - Boundary Cases: Edge conditions (unicode, long paths, special chars)
-;; - Error Cases: File errors (missing, read-only, directory instead of file)
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--append-track-to-m3u-file-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--append-track-to-m3u-file-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--append-track-to-m3u-file-normal-empty-file-appends-track ()
- "Append to brand new empty M3U file."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/artist/song.mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-normal-existing-with-newline-appends-track ()
- "Append to file with existing content ending with newline."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((existing-content "/home/user/music/first.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/second.mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string)
- (concat existing-content track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-normal-existing-without-newline-appends-track ()
- "Append to file without trailing newline adds leading newline."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((existing-content "/home/user/music/first.mp3")
- (m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/second.mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string)
- (concat existing-content "\n" track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-normal-multiple-appends-all-succeed ()
- "Multiple appends to same file all succeed (allows duplicates)."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track1 "/home/user/music/track1.mp3")
- (track2 "/home/user/music/track2.mp3")
- (track1-duplicate "/home/user/music/track1.mp3"))
- (cj/music--append-track-to-m3u-file track1 m3u-file)
- (cj/music--append-track-to-m3u-file track2 m3u-file)
- (cj/music--append-track-to-m3u-file track1-duplicate m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (let ((content (buffer-string)))
- (should (string= content
- (concat track1 "\n" track2 "\n" track1-duplicate "\n"))))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--append-track-to-m3u-file-boundary-very-long-path-appends-successfully ()
- "Append very long track path without truncation."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- ;; Create a path that's ~500 chars long
- (track-path (concat "/home/user/music/"
- (make-string 450 ?a)
- "/song.mp3")))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))
- (should (= (length (buffer-string)) (1+ (length track-path))))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-boundary-path-with-unicode-appends-successfully ()
- "Append path with unicode characters preserves UTF-8 encoding."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/中文/artist-名前/song🎵.mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-boundary-path-with-spaces-appends-successfully ()
- "Append path with spaces and special characters."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/Artist Name/Album (2024)/01 - Song's Title [Remix].mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-boundary-m3u-with-comments-appends-after ()
- "Append to M3U file containing comments and metadata."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((existing-content "#EXTM3U\n#EXTINF:-1,Radio Station\nhttp://stream.url/radio\n")
- (m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/local-track.mp3"))
- (cj/music--append-track-to-m3u-file track-path m3u-file)
- (with-temp-buffer
- (insert-file-contents m3u-file)
- (should (string= (buffer-string)
- (concat existing-content track-path "\n")))))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--append-track-to-m3u-file-error-nonexistent-file-signals-error ()
- "Signal error when M3U file doesn't exist."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file "/nonexistent/path/to/playlist.m3u")
- (track-path "/home/user/music/song.mp3"))
- (should-error (cj/music--append-track-to-m3u-file track-path m3u-file)
- :type 'error))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-error-readonly-file-signals-error ()
- "Signal error when M3U file is read-only."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/song.mp3"))
- ;; Make file read-only
- (set-file-modes m3u-file #o444)
- (should-error (cj/music--append-track-to-m3u-file track-path m3u-file)
- :type 'error))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(ert-deftest test-music-config--append-track-to-m3u-file-error-directory-not-file-signals-error ()
- "Signal error when path points to directory instead of file."
- (test-music-config--append-track-to-m3u-file-setup)
- (unwind-protect
- (let* ((m3u-dir (cj/create-test-subdirectory "test-playlist-dir"))
- (track-path "/home/user/music/song.mp3"))
- (should-error (cj/music--append-track-to-m3u-file track-path m3u-dir)
- :type 'error))
- (test-music-config--append-track-to-m3u-file-teardown)))
-
-(provide 'test-music-config--append-track-to-m3u-file)
-;;; test-music-config--append-track-to-m3u-file.el ends here
diff --git a/tests/test-music-config--collect-entries-recursive.el b/tests/test-music-config--collect-entries-recursive.el
deleted file mode 100644
index d71ceab6..00000000
--- a/tests/test-music-config--collect-entries-recursive.el
+++ /dev/null
@@ -1,245 +0,0 @@
-;;; test-music-config--collect-entries-recursive.el --- Tests for recursive music collection -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--collect-entries-recursive function.
-;; Tests the recursive helper that collects music files and directories.
-;;
-;; Test organization:
-;; - Normal Cases: Single level, nested directories, mixed files
-;; - Boundary Cases: Hidden files/dirs, non-music files, empty dirs, sorting
-;; - Error Cases: Empty root, nonexistent root
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--collect-entries-recursive-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--collect-entries-recursive-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--collect-entries-recursive-normal-single-level-files-and-dirs ()
- "Collect music files and subdirectories at single level."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Create files at root
- (cj/create-directory-or-file-ensuring-parents "music/song1.mp3" "")
- (cj/create-directory-or-file-ensuring-parents "music/song2.flac" "")
- ;; Create subdirectories
- (cj/create-directory-or-file-ensuring-parents "music/artist1/" "")
- (cj/create-directory-or-file-ensuring-parents "music/artist2/" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "artist1/" result))
- (should (member "artist2/" result))
- (should (member "song1.mp3" result))
- (should (member "song2.flac" result))
- (should (= (length result) 4))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-normal-nested-directories ()
- "Collect nested directories multiple levels deep."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Create nested structure
- (cj/create-directory-or-file-ensuring-parents "music/artist/" "")
- (cj/create-directory-or-file-ensuring-parents "music/artist/album/" "")
- (cj/create-directory-or-file-ensuring-parents "music/artist/album/disc1/" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "artist/" result))
- (should (member "artist/album/" result))
- (should (member "artist/album/disc1/" result))
- (should (= (length result) 3))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-normal-mixed-files-at-multiple-levels ()
- "Collect music files at root, subdirs, and nested subdirs."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Root level file
- (cj/create-directory-or-file-ensuring-parents "music/root-track.mp3" "")
- ;; Subdir with file
- (cj/create-directory-or-file-ensuring-parents "music/artist/" "")
- (cj/create-directory-or-file-ensuring-parents "music/artist/track1.mp3" "")
- ;; Nested subdir with file
- (cj/create-directory-or-file-ensuring-parents "music/artist/album/" "")
- (cj/create-directory-or-file-ensuring-parents "music/artist/album/track2.mp3" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "root-track.mp3" result))
- (should (member "artist/" result))
- (should (member "artist/track1.mp3" result))
- (should (member "artist/album/" result))
- (should (member "artist/album/track2.mp3" result))
- (should (= (length result) 5))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-hidden-directories-skipped ()
- "Hidden directories and their contents are excluded."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Visible file
- (cj/create-directory-or-file-ensuring-parents "music/visible.mp3" "")
- ;; Hidden directory with music file
- (cj/create-directory-or-file-ensuring-parents "music/.hidden/" "")
- (cj/create-directory-or-file-ensuring-parents "music/.hidden/secret.mp3" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "visible.mp3" result))
- (should-not (member ".hidden/" result))
- (should-not (member ".hidden/secret.mp3" result))
- (should (= (length result) 1))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-hidden-files-skipped ()
- "Hidden files at root are excluded."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Visible file
- (cj/create-directory-or-file-ensuring-parents "music/visible.mp3" "")
- ;; Hidden file (note: directory-files regex "^[^.].*" should skip it)
- (cj/create-directory-or-file-ensuring-parents "music/.hidden-track.mp3" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "visible.mp3" result))
- (should-not (member ".hidden-track.mp3" result))
- (should (= (length result) 1))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-non-music-files-excluded ()
- "Non-music files are excluded."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Music file
- (cj/create-directory-or-file-ensuring-parents "music/song.mp3" "")
- ;; Non-music files
- (cj/create-directory-or-file-ensuring-parents "music/readme.txt" "")
- (cj/create-directory-or-file-ensuring-parents "music/cover.jpg" "")
- (cj/create-directory-or-file-ensuring-parents "music/info.pdf" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "song.mp3" result))
- (should-not (member "readme.txt" result))
- (should-not (member "cover.jpg" result))
- (should-not (member "info.pdf" result))
- (should (= (length result) 1))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-empty-directories-included ()
- "Empty subdirectories are still listed with trailing slash."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Empty subdirectories
- (cj/create-directory-or-file-ensuring-parents "music/empty-artist/" "")
- (cj/create-directory-or-file-ensuring-parents "music/another-empty/" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (member "empty-artist/" result))
- (should (member "another-empty/" result))
- (should (= (length result) 2))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-sorted-output ()
- "Output is sorted alphabetically (case-insensitive)."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Create files in non-alphabetical order
- (cj/create-directory-or-file-ensuring-parents "music/zebra.mp3" "")
- (cj/create-directory-or-file-ensuring-parents "music/Alpha.mp3" "")
- (cj/create-directory-or-file-ensuring-parents "music/beta.mp3" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- ;; Should be sorted alphabetically (case-insensitive)
- (should (equal result '("Alpha.mp3" "beta.mp3" "zebra.mp3")))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-directories-have-trailing-slash ()
- "Directories have trailing slash, files don't."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- (cj/create-directory-or-file-ensuring-parents "music/artist/" "")
- (cj/create-directory-or-file-ensuring-parents "music/song.mp3" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- ;; Directory has trailing slash
- (should (cl-some (lambda (entry) (string-suffix-p "/" entry)) result))
- ;; File doesn't have trailing slash
- (should (cl-some (lambda (entry) (not (string-suffix-p "/" entry))) result))
- ;; Specifically check
- (should (member "artist/" result))
- (should (member "song.mp3" result))
- (should-not (member "song.mp3/" result))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-boundary-all-music-extensions ()
- "All configured music extensions are collected."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "music")))
- ;; Create file for each extension: aac, flac, m4a, mp3, ogg, opus, wav
- (cj/create-directory-or-file-ensuring-parents "music/track.aac" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.flac" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.m4a" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.mp3" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.ogg" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.opus" "")
- (cj/create-directory-or-file-ensuring-parents "music/track.wav" "")
-
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (= (length result) 7))
- (should (member "track.aac" result))
- (should (member "track.flac" result))
- (should (member "track.m4a" result))
- (should (member "track.mp3" result))
- (should (member "track.ogg" result))
- (should (member "track.opus" result))
- (should (member "track.wav" result))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--collect-entries-recursive-error-empty-root-returns-empty ()
- "Empty root directory returns empty list."
- (test-music-config--collect-entries-recursive-setup)
- (unwind-protect
- (let* ((root-dir (cj/create-test-subdirectory "empty-music")))
- (let ((result (cj/music--collect-entries-recursive root-dir)))
- (should (null result))))
- (test-music-config--collect-entries-recursive-teardown)))
-
-(ert-deftest test-music-config--collect-entries-recursive-error-nonexistent-root-returns-empty ()
- "Nonexistent directory returns empty list."
- (let ((result (cj/music--collect-entries-recursive "/nonexistent/path/to/music")))
- (should (null result))))
-
-(provide 'test-music-config--collect-entries-recursive)
-;;; test-music-config--collect-entries-recursive.el ends here
diff --git a/tests/test-music-config--completion-table.el b/tests/test-music-config--completion-table.el
deleted file mode 100644
index 5be0479d..00000000
--- a/tests/test-music-config--completion-table.el
+++ /dev/null
@@ -1,134 +0,0 @@
-;;; test-music-config--completion-table.el --- Tests for completion table generation -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--completion-table function.
-;; Tests the completion table generator that creates custom completion tables.
-;;
-;; Test organization:
-;; - Normal Cases: Metadata, completions, case-insensitive matching
-;; - Boundary Cases: Empty candidates, partial matching, exact matches
-;; - Error Cases: Nil candidates
-;;
-;;; Code:
-
-(require 'ert)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--completion-table-normal-metadata-action-returns-metadata ()
- "Completion table returns metadata when action is 'metadata."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "" nil 'metadata)))
- (should (eq (car result) 'metadata))
- ;; Check metadata contains expected properties
- (should (equal (alist-get 'display-sort-function (cdr result)) 'identity))
- (should (equal (alist-get 'cycle-sort-function (cdr result)) 'identity))
- (should (eq (alist-get 'completion-ignore-case (cdr result)) t))))
-
-(ert-deftest test-music-config--completion-table-normal-t-action-returns-all-completions ()
- "Completion table returns all matching completions when action is t."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "" nil t)))
- ;; Empty string should match all candidates
- (should (equal (sort result #'string<) '("Classical" "Jazz" "Rock")))))
-
-(ert-deftest test-music-config--completion-table-normal-nil-action-tries-completion ()
- "Completion table tries completion when action is nil."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "Roc" nil nil)))
- ;; Should return completion attempt for "Roc" -> "Rock"
- (should (stringp result))
- (should (string-prefix-p "Roc" result))))
-
-(ert-deftest test-music-config--completion-table-normal-case-insensitive-metadata ()
- "Completion table metadata indicates case-insensitive completion."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (metadata (funcall table "" nil 'metadata)))
- ;; Metadata should indicate case-insensitive
- (should (eq (alist-get 'completion-ignore-case (cdr metadata)) t))))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--completion-table-boundary-empty-candidates ()
- "Completion table with empty candidate list returns no completions."
- (let* ((candidates '())
- (table (cj/music--completion-table candidates))
- (result (funcall table "anything" nil t)))
- (should (null result))))
-
-(ert-deftest test-music-config--completion-table-boundary-single-candidate ()
- "Completion table with single candidate returns it on match."
- (let* ((candidates '("OnlyOne"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "Only" nil t)))
- (should (equal result '("OnlyOne")))))
-
-(ert-deftest test-music-config--completion-table-boundary-partial-matching ()
- "Completion table matches multiple candidates with common prefix."
- (let* ((candidates '("playlist1" "playlist2" "jazz"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "play" nil t)))
- (should (= (length result) 2))
- (should (member "playlist1" result))
- (should (member "playlist2" result))
- (should-not (member "jazz" result))))
-
-(ert-deftest test-music-config--completion-table-boundary-no-matches ()
- "Completion table returns empty when no candidates match."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "Metal" nil t)))
- (should (null result))))
-
-(ert-deftest test-music-config--completion-table-boundary-exact-match ()
- "Completion table returns t for exact match with nil action."
- (let* ((candidates '("Rock" "Jazz" "Classical"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "Jazz" nil nil)))
- ;; Exact match with nil action returns t
- (should (eq result t))))
-
-(ert-deftest test-music-config--completion-table-boundary-mixed-case-candidates ()
- "Completion table with mixed-case duplicate candidates."
- (let* ((candidates '("Rock" "ROCK" "rock"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "R" nil t)))
- ;; All start with "R", but exact case matters for complete-with-action
- ;; Only exact case match "R" prefix
- (should (member "Rock" result))
- (should (member "ROCK" result))
- ;; "rock" doesn't match "R" prefix (lowercase)
- (should-not (member "rock" result))))
-
-(ert-deftest test-music-config--completion-table-boundary-unicode-candidates ()
- "Completion table handles unicode characters in candidates."
- (let* ((candidates '("中文" "日本語" "한국어"))
- (table (cj/music--completion-table candidates))
- (result (funcall table "中" nil t)))
- (should (member "中文" result))))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--completion-table-error-nil-candidates-handles-gracefully ()
- "Completion table with nil candidates handles gracefully."
- (let* ((candidates nil)
- (table (cj/music--completion-table candidates))
- (result (funcall table "anything" nil t)))
- ;; Should not crash, returns empty
- (should (null result))))
-
-(provide 'test-music-config--completion-table)
-;;; test-music-config--completion-table.el ends here
diff --git a/tests/test-music-config--get-m3u-basenames.el b/tests/test-music-config--get-m3u-basenames.el
deleted file mode 100644
index 91c8af70..00000000
--- a/tests/test-music-config--get-m3u-basenames.el
+++ /dev/null
@@ -1,121 +0,0 @@
-;;; test-music-config--get-m3u-basenames.el --- Tests for M3U basename extraction -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--get-m3u-basenames function.
-;; Tests the helper that extracts M3U basenames (without .m3u extension).
-;;
-;; Test organization:
-;; - Normal Cases: Multiple files, single file
-;; - Boundary Cases: Empty directory, extension removal
-;; - Error Cases: Nonexistent directory
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--get-m3u-basenames-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--get-m3u-basenames-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--get-m3u-basenames-normal-multiple-files-returns-basenames ()
- "Extract basenames from multiple M3U files without .m3u extension."
- (test-music-config--get-m3u-basenames-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "rock.m3u"))
- (file2 (cj/create-temp-test-file-with-content "" "jazz.m3u"))
- (file3 (cj/create-temp-test-file-with-content "" "classical.m3u")))
- (rename-file file1 (expand-file-name "rock.m3u" test-dir))
- (rename-file file2 (expand-file-name "jazz.m3u" test-dir))
- (rename-file file3 (expand-file-name "classical.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-basenames)))
- (should (= (length result) 3))
- ;; Sort for consistent comparison
- (let ((sorted-result (sort result #'string<)))
- (should (equal sorted-result '("classical" "jazz" "rock")))))))
- (test-music-config--get-m3u-basenames-teardown)))
-
-(ert-deftest test-music-config--get-m3u-basenames-normal-single-file-returns-basename ()
- "Extract basename from single M3U file without .m3u extension."
- (test-music-config--get-m3u-basenames-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "favorites.m3u")))
- (rename-file file1 (expand-file-name "favorites.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-basenames)))
- (should (= (length result) 1))
- (should (equal (car result) "favorites")))))
- (test-music-config--get-m3u-basenames-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--get-m3u-basenames-boundary-empty-directory-returns-empty ()
- "Extract basenames from empty directory returns empty list."
- (test-music-config--get-m3u-basenames-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "empty-playlists")))
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-basenames)))
- (should (null result)))))
- (test-music-config--get-m3u-basenames-teardown)))
-
-(ert-deftest test-music-config--get-m3u-basenames-boundary-extension-removed ()
- "Basenames have .m3u extension removed."
- (test-music-config--get-m3u-basenames-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "test.m3u")))
- (rename-file file1 (expand-file-name "playlist.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-basenames)))
- (should (equal result '("playlist")))
- ;; Verify no .m3u extension present
- (should-not (string-match-p "\\.m3u" (car result))))))
- (test-music-config--get-m3u-basenames-teardown)))
-
-(ert-deftest test-music-config--get-m3u-basenames-boundary-spaces-in-filename-preserved ()
- "Basenames with spaces preserve the spaces."
- (test-music-config--get-m3u-basenames-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "test.m3u")))
- (rename-file file1 (expand-file-name "My Favorite Songs.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-basenames)))
- (should (equal result '("My Favorite Songs"))))))
- (test-music-config--get-m3u-basenames-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--get-m3u-basenames-error-nonexistent-directory-signals-error ()
- "Nonexistent directory signals error."
- (let ((cj/music-m3u-root "/nonexistent/directory/path"))
- (should-error (cj/music--get-m3u-basenames)
- :type 'file-error)))
-
-(provide 'test-music-config--get-m3u-basenames)
-;;; test-music-config--get-m3u-basenames.el ends here
diff --git a/tests/test-music-config--get-m3u-files.el b/tests/test-music-config--get-m3u-files.el
deleted file mode 100644
index 2d31d554..00000000
--- a/tests/test-music-config--get-m3u-files.el
+++ /dev/null
@@ -1,150 +0,0 @@
-;;; test-music-config--get-m3u-files.el --- Tests for M3U file discovery -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--get-m3u-files function.
-;; Tests the helper that discovers M3U files in the music directory.
-;;
-;; Test organization:
-;; - Normal Cases: Multiple M3U files, single file
-;; - Boundary Cases: Empty directory, non-M3U files, various filenames
-;; - Error Cases: Nonexistent directory
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--get-m3u-files-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--get-m3u-files-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--get-m3u-files-normal-multiple-files-returns-list ()
- "Discover multiple M3U files returns list of (basename . fullpath) conses."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "playlist1.m3u"))
- (file2 (cj/create-temp-test-file-with-content "" "playlist2.m3u"))
- (file3 (cj/create-temp-test-file-with-content "" "playlist3.m3u")))
- ;; Move files to test-dir
- (rename-file file1 (expand-file-name "playlist1.m3u" test-dir))
- (rename-file file2 (expand-file-name "playlist2.m3u" test-dir))
- (rename-file file3 (expand-file-name "playlist3.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (= (length result) 3))
- ;; Check structure: list of (basename . fullpath) conses
- ;; Sort for consistent comparison (directory-files order is filesystem-dependent)
- (let ((basenames (sort (mapcar #'car result) #'string<))
- (fullpaths (sort (mapcar #'cdr result) #'string<)))
- (should (equal basenames '("playlist1.m3u" "playlist2.m3u" "playlist3.m3u")))
- (should (equal fullpaths
- (list (expand-file-name "playlist1.m3u" test-dir)
- (expand-file-name "playlist2.m3u" test-dir)
- (expand-file-name "playlist3.m3u" test-dir))))))))
- (test-music-config--get-m3u-files-teardown)))
-
-(ert-deftest test-music-config--get-m3u-files-normal-single-file-returns-list ()
- "Discover single M3U file returns single-item list."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "myplaylist.m3u")))
- (rename-file file1 (expand-file-name "myplaylist.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (= (length result) 1))
- (should (equal (caar result) "myplaylist.m3u"))
- (should (equal (cdar result) (expand-file-name "myplaylist.m3u" test-dir))))))
- (test-music-config--get-m3u-files-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--get-m3u-files-boundary-empty-directory-returns-empty ()
- "Discover M3U files in empty directory returns empty list."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "empty-playlists")))
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (null result)))))
- (test-music-config--get-m3u-files-teardown)))
-
-(ert-deftest test-music-config--get-m3u-files-boundary-non-m3u-files-ignored ()
- "Directory with non-M3U files returns empty list."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "mixed-files"))
- (txt-file (cj/create-temp-test-file-with-content "" "readme.txt"))
- (mp3-file (cj/create-temp-test-file-with-content "" "song.mp3"))
- (json-file (cj/create-temp-test-file-with-content "" "data.json")))
- (rename-file txt-file (expand-file-name "readme.txt" test-dir))
- (rename-file mp3-file (expand-file-name "song.mp3" test-dir))
- (rename-file json-file (expand-file-name "data.json" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (null result)))))
- (test-music-config--get-m3u-files-teardown)))
-
-(ert-deftest test-music-config--get-m3u-files-boundary-m3u-with-spaces-included ()
- "M3U files with spaces in name are discovered."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "playlists"))
- (file1 (cj/create-temp-test-file-with-content "" "my-playlist.m3u")))
- (rename-file file1 (expand-file-name "My Favorite Songs.m3u" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (= (length result) 1))
- (should (equal (caar result) "My Favorite Songs.m3u")))))
- (test-music-config--get-m3u-files-teardown)))
-
-(ert-deftest test-music-config--get-m3u-files-boundary-mixed-m3u-and-other-files ()
- "Directory with both M3U and non-M3U files returns only M3U files."
- (test-music-config--get-m3u-files-setup)
- (unwind-protect
- (let* ((test-dir (cj/create-test-subdirectory "mixed"))
- (m3u-file (cj/create-temp-test-file-with-content "" "playlist.m3u"))
- (txt-file (cj/create-temp-test-file-with-content "" "readme.txt"))
- (mp3-file (cj/create-temp-test-file-with-content "" "song.mp3")))
- (rename-file m3u-file (expand-file-name "playlist.m3u" test-dir))
- (rename-file txt-file (expand-file-name "readme.txt" test-dir))
- (rename-file mp3-file (expand-file-name "song.mp3" test-dir))
-
- (let ((cj/music-m3u-root test-dir))
- (let ((result (cj/music--get-m3u-files)))
- (should (= (length result) 1))
- (should (equal (caar result) "playlist.m3u")))))
- (test-music-config--get-m3u-files-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--get-m3u-files-error-nonexistent-directory-signals-error ()
- "Nonexistent directory signals error."
- (let ((cj/music-m3u-root "/nonexistent/directory/path"))
- (should-error (cj/music--get-m3u-files)
- :type 'file-error)))
-
-(provide 'test-music-config--get-m3u-files)
-;;; test-music-config--get-m3u-files.el ends here
diff --git a/tests/test-music-config--m3u-file-tracks.el b/tests/test-music-config--m3u-file-tracks.el
deleted file mode 100644
index badc9817..00000000
--- a/tests/test-music-config--m3u-file-tracks.el
+++ /dev/null
@@ -1,193 +0,0 @@
-;;; test-music-config--m3u-file-tracks.el --- Tests for M3U file parsing -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--m3u-file-tracks function.
-;; Tests the M3U parser that extracts track paths from playlist files.
-;;
-;; Test organization:
-;; - Normal Cases: Absolute paths, relative paths, URLs (http/https/mms)
-;; - Boundary Cases: Empty lines, whitespace, comments, order preservation
-;; - Error Cases: Nonexistent files, nil input
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--m3u-file-tracks-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--m3u-file-tracks-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-absolute-paths-returns-list ()
- "Parse M3U with absolute paths returns list in order."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "/home/user/music/track1.mp3\n/home/user/music/track2.mp3\n/home/user/music/track3.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/home/user/music/track1.mp3"
- "/home/user/music/track2.mp3"
- "/home/user/music/track3.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-relative-paths-expanded ()
- "Parse M3U with relative paths expands them relative to M3U directory."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "artist/track1.mp3\nartist/track2.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (m3u-dir (file-name-directory m3u-file))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks (list (expand-file-name "artist/track1.mp3" m3u-dir)
- (expand-file-name "artist/track2.mp3" m3u-dir)))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-http-urls-preserved ()
- "Parse M3U with http:// URLs preserves them as-is."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "http://example.com/stream1.mp3\nhttp://example.com/stream2.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("http://example.com/stream1.mp3"
- "http://example.com/stream2.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-https-urls-preserved ()
- "Parse M3U with https:// URLs preserves them as-is."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "https://secure.example.com/stream.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("https://secure.example.com/stream.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-mms-urls-preserved ()
- "Parse M3U with mms:// URLs preserves them as-is."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "mms://radio.example.com/stream\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("mms://radio.example.com/stream"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-normal-mixed-paths-and-urls ()
- "Parse M3U with mix of absolute, relative, and URLs handles all correctly."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "/home/user/music/local.mp3\nartist/relative.mp3\nhttp://example.com/stream.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (m3u-dir (file-name-directory m3u-file))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks (list "/home/user/music/local.mp3"
- (expand-file-name "artist/relative.mp3" m3u-dir)
- "http://example.com/stream.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-empty-lines-ignored ()
- "Parse M3U with empty lines ignores them and returns tracks."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "/home/user/music/track1.mp3\n\n/home/user/music/track2.mp3\n\n\n/home/user/music/track3.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/home/user/music/track1.mp3"
- "/home/user/music/track2.mp3"
- "/home/user/music/track3.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-whitespace-only-lines-ignored ()
- "Parse M3U with whitespace-only lines ignores them."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "/home/user/music/track1.mp3\n \n\t\t\n/home/user/music/track2.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/home/user/music/track1.mp3"
- "/home/user/music/track2.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-comments-ignored ()
- "Parse M3U with comment lines ignores them, returns only tracks."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "#EXTM3U\n#EXTINF:-1,Track Title\n/home/user/music/track.mp3\n#Another comment\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/home/user/music/track.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-leading-trailing-whitespace-trimmed ()
- "Parse M3U with whitespace around paths trims it."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content " /home/user/music/track1.mp3 \n\t/home/user/music/track2.mp3\t\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/home/user/music/track1.mp3"
- "/home/user/music/track2.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-empty-file-returns-nil ()
- "Parse empty M3U file returns nil."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (null tracks)))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-only-comments-returns-empty ()
- "Parse M3U with only comments returns empty list."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "#EXTM3U\n#EXTINF:-1,Title\n#Another comment\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (null tracks)))
- (test-music-config--m3u-file-tracks-teardown)))
-
-(ert-deftest test-music-config--m3u-file-tracks-boundary-preserves-order ()
- "Parse M3U preserves track order (tests nreverse)."
- (test-music-config--m3u-file-tracks-setup)
- (unwind-protect
- (let* ((content "/track1.mp3\n/track2.mp3\n/track3.mp3\n/track4.mp3\n/track5.mp3\n")
- (m3u-file (cj/create-temp-test-file-with-content content "test.m3u"))
- (tracks (cj/music--m3u-file-tracks m3u-file)))
- (should (equal tracks '("/track1.mp3" "/track2.mp3" "/track3.mp3" "/track4.mp3" "/track5.mp3"))))
- (test-music-config--m3u-file-tracks-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--m3u-file-tracks-error-nonexistent-file-returns-nil ()
- "Parse nonexistent file returns nil."
- (should (null (cj/music--m3u-file-tracks "/nonexistent/path/playlist.m3u"))))
-
-(ert-deftest test-music-config--m3u-file-tracks-error-nil-input-returns-nil ()
- "Parse nil input returns nil gracefully."
- (should (null (cj/music--m3u-file-tracks nil))))
-
-(provide 'test-music-config--m3u-file-tracks)
-;;; test-music-config--m3u-file-tracks.el ends here
diff --git a/tests/test-music-config--safe-filename.el b/tests/test-music-config--safe-filename.el
deleted file mode 100644
index 8105ee15..00000000
--- a/tests/test-music-config--safe-filename.el
+++ /dev/null
@@ -1,97 +0,0 @@
-;;; test-music-config--safe-filename.el --- Tests for filename sanitization -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--safe-filename function.
-;; Tests the pure helper that sanitizes filenames by replacing invalid chars.
-;;
-;; Test organization:
-;; - Normal Cases: Valid filenames unchanged, spaces replaced
-;; - Boundary Cases: Special chars, unicode, slashes, consecutive invalid chars
-;; - Error Cases: Nil input
-;;
-;;; Code:
-
-(require 'ert)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--safe-filename-normal-alphanumeric-unchanged ()
- "Validate alphanumeric filename remains unchanged."
- (should (string= (cj/music--safe-filename "MyPlaylist123")
- "MyPlaylist123")))
-
-(ert-deftest test-music-config--safe-filename-normal-with-hyphens-unchanged ()
- "Validate filename with hyphens remains unchanged."
- (should (string= (cj/music--safe-filename "my-playlist-name")
- "my-playlist-name")))
-
-(ert-deftest test-music-config--safe-filename-normal-with-underscores-unchanged ()
- "Validate filename with underscores remains unchanged."
- (should (string= (cj/music--safe-filename "my_playlist_name")
- "my_playlist_name")))
-
-(ert-deftest test-music-config--safe-filename-normal-spaces-replaced ()
- "Validate spaces are replaced with underscores."
- (should (string= (cj/music--safe-filename "My Favorite Songs")
- "My_Favorite_Songs")))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--safe-filename-boundary-special-chars-replaced ()
- "Validate special characters are replaced with underscores."
- (should (string= (cj/music--safe-filename "playlist@#$%^&*()")
- "playlist_________")))
-
-(ert-deftest test-music-config--safe-filename-boundary-unicode-replaced ()
- "Validate unicode characters are replaced with underscores."
- (should (string= (cj/music--safe-filename "中文歌曲")
- "____")))
-
-(ert-deftest test-music-config--safe-filename-boundary-mixed-valid-invalid ()
- "Validate mixed valid and invalid characters."
- (should (string= (cj/music--safe-filename "Rock & Roll")
- "Rock___Roll")))
-
-(ert-deftest test-music-config--safe-filename-boundary-dots-replaced ()
- "Validate dots are replaced with underscores."
- (should (string= (cj/music--safe-filename "my.playlist.name")
- "my_playlist_name")))
-
-(ert-deftest test-music-config--safe-filename-boundary-slashes-replaced ()
- "Validate slashes are replaced with underscores."
- (should (string= (cj/music--safe-filename "folder/file")
- "folder_file")))
-
-(ert-deftest test-music-config--safe-filename-boundary-consecutive-invalid-chars ()
- "Validate consecutive invalid characters each become underscores."
- (should (string= (cj/music--safe-filename "test!!!name")
- "test___name")))
-
-(ert-deftest test-music-config--safe-filename-boundary-empty-string-unchanged ()
- "Validate empty string remains unchanged."
- (should (string= (cj/music--safe-filename "")
- "")))
-
-(ert-deftest test-music-config--safe-filename-boundary-only-invalid-chars ()
- "Validate string with only invalid characters becomes all underscores."
- (should (string= (cj/music--safe-filename "!@#$%")
- "_____")))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--safe-filename-error-nil-input-signals-error ()
- "Validate nil input signals error."
- (should-error (cj/music--safe-filename nil)
- :type 'wrong-type-argument))
-
-(provide 'test-music-config--safe-filename)
-;;; test-music-config--safe-filename.el ends here
diff --git a/tests/test-music-config--valid-directory-p.el b/tests/test-music-config--valid-directory-p.el
deleted file mode 100644
index 21c2b240..00000000
--- a/tests/test-music-config--valid-directory-p.el
+++ /dev/null
@@ -1,139 +0,0 @@
-;;; test-music-config--valid-directory-p.el --- Tests for directory validation -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--valid-directory-p function.
-;; Tests the pure helper that validates non-hidden directories.
-;;
-;; Test organization:
-;; - Normal Cases: Valid visible directories
-;; - Boundary Cases: Trailing slashes, dots in names, hidden directories
-;; - Error Cases: Files (not dirs), nonexistent paths, nil input
-;;
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Setup & Teardown
-
-(defun test-music-config--valid-directory-p-setup ()
- "Setup test environment."
- (cj/create-test-base-dir))
-
-(defun test-music-config--valid-directory-p-teardown ()
- "Clean up test environment."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--valid-directory-p-normal-visible-directory-returns-true ()
- "Validate visible directory returns non-nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir")))
- (should (cj/music--valid-directory-p test-dir)))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-normal-nested-directory-returns-true ()
- "Validate nested visible directory returns non-nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir/subdir/nested")))
- (should (cj/music--valid-directory-p test-dir)))
- (test-music-config--valid-directory-p-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--valid-directory-p-boundary-trailing-slash-returns-true ()
- "Validate directory with trailing slash returns non-nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir")))
- (should (cj/music--valid-directory-p (file-name-as-directory test-dir))))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-no-trailing-slash-returns-true ()
- "Validate directory without trailing slash returns non-nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir")))
- (should (cj/music--valid-directory-p (directory-file-name test-dir))))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-dot-in-middle-returns-true ()
- "Validate directory with dot in middle of name returns non-nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "my.music.dir")))
- (should (cj/music--valid-directory-p test-dir)))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-hidden-directory-returns-nil ()
- "Validate hidden directory (starting with dot) returns nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory ".hidden")))
- (should-not (cj/music--valid-directory-p test-dir)))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-current-dir-dot-returns-nil ()
- "Validate current directory '.' returns nil (hidden)."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir")))
- ;; Change to test dir and check "."
- (let ((default-directory test-dir))
- (should-not (cj/music--valid-directory-p "."))))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-parent-dir-dotdot-returns-nil ()
- "Validate parent directory '..' returns nil (hidden)."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-dir (cj/create-test-subdirectory "testdir/subdir")))
- ;; Change to subdir and check ".."
- (let ((default-directory test-dir))
- (should-not (cj/music--valid-directory-p ".."))))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-boundary-hidden-subdir-basename-check ()
- "Validate hidden subdirectory returns nil based on basename."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((hidden-dir (cj/create-test-subdirectory "visible/.hidden")))
- (should-not (cj/music--valid-directory-p hidden-dir)))
- (test-music-config--valid-directory-p-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--valid-directory-p-error-regular-file-returns-nil ()
- "Validate regular file (not directory) returns nil."
- (test-music-config--valid-directory-p-setup)
- (unwind-protect
- (let ((test-file (cj/create-temp-test-file "testfile-")))
- (should-not (cj/music--valid-directory-p test-file)))
- (test-music-config--valid-directory-p-teardown)))
-
-(ert-deftest test-music-config--valid-directory-p-error-nonexistent-path-returns-nil ()
- "Validate nonexistent path returns nil."
- (should-not (cj/music--valid-directory-p "/nonexistent/path/to/directory")))
-
-(ert-deftest test-music-config--valid-directory-p-error-nil-input-returns-nil ()
- "Validate nil input returns nil gracefully."
- (should-not (cj/music--valid-directory-p nil)))
-
-(ert-deftest test-music-config--valid-directory-p-error-empty-string-returns-nil ()
- "Validate empty string returns nil."
- (should-not (cj/music--valid-directory-p "")))
-
-(provide 'test-music-config--valid-directory-p)
-;;; test-music-config--valid-directory-p.el ends here
diff --git a/tests/test-music-config--valid-file-p.el b/tests/test-music-config--valid-file-p.el
deleted file mode 100644
index 8099c50c..00000000
--- a/tests/test-music-config--valid-file-p.el
+++ /dev/null
@@ -1,99 +0,0 @@
-;;; test-music-config--valid-file-p.el --- Tests for music file validation -*- coding: utf-8; lexical-binding: t; -*-
-;;
-;; Author: Craig Jennings <c@cjennings.net>
-;;
-;;; Commentary:
-;; Unit tests for cj/music--valid-file-p function.
-;; Tests the pure, deterministic helper that validates music file extensions.
-;;
-;; Test organization:
-;; - Normal Cases: Valid music extensions (case-insensitive)
-;; - Boundary Cases: Edge conditions (no extension, dots in path, empty strings)
-;; - Error Cases: Invalid extensions, nil input
-;;
-;;; Code:
-
-(require 'ert)
-
-;; Stub missing dependencies before loading music-config
-(defvar-keymap cj/custom-keymap
- :doc "Stub keymap for testing")
-
-;; Load production code
-(require 'music-config)
-
-;;; Normal Cases
-
-(ert-deftest test-music-config--valid-file-p-normal-mp3-extension-returns-true ()
- "Validate mp3 file extension returns non-nil."
- (should (cj/music--valid-file-p "/path/to/song.mp3")))
-
-(ert-deftest test-music-config--valid-file-p-normal-flac-extension-returns-true ()
- "Validate flac file extension returns non-nil."
- (should (cj/music--valid-file-p "/path/to/song.flac")))
-
-(ert-deftest test-music-config--valid-file-p-normal-all-extensions-return-true ()
- "Validate all configured music extensions return non-nil."
- ;; Test each extension from cj/music-file-extensions
- (dolist (ext '("aac" "flac" "m4a" "mp3" "ogg" "opus" "wav"))
- (should (cj/music--valid-file-p (format "/path/to/song.%s" ext)))))
-
-(ert-deftest test-music-config--valid-file-p-normal-uppercase-extension-returns-true ()
- "Validate uppercase extension returns non-nil (case-insensitive)."
- (should (cj/music--valid-file-p "/path/to/song.MP3")))
-
-(ert-deftest test-music-config--valid-file-p-normal-mixed-case-extension-returns-true ()
- "Validate mixed-case extension returns non-nil (case-insensitive)."
- (should (cj/music--valid-file-p "/path/to/song.Mp3"))
- (should (cj/music--valid-file-p "/path/to/song.FLaC")))
-
-;;; Boundary Cases
-
-(ert-deftest test-music-config--valid-file-p-boundary-dots-in-path-returns-true ()
- "Validate file with dots in directory path uses only last extension."
- (should (cj/music--valid-file-p "/path/with.dots/in.directory/song.mp3")))
-
-(ert-deftest test-music-config--valid-file-p-boundary-multiple-extensions-uses-last ()
- "Validate file with multiple extensions uses rightmost extension."
- (should (cj/music--valid-file-p "/path/to/song.backup.mp3"))
- (should (cj/music--valid-file-p "/path/to/song.old.flac")))
-
-(ert-deftest test-music-config--valid-file-p-boundary-just-filename-with-extension-returns-true ()
- "Validate bare filename without path returns non-nil."
- (should (cj/music--valid-file-p "song.mp3")))
-
-(ert-deftest test-music-config--valid-file-p-boundary-no-extension-returns-nil ()
- "Validate file without extension returns nil."
- (should-not (cj/music--valid-file-p "/path/to/song")))
-
-(ert-deftest test-music-config--valid-file-p-boundary-dot-at-end-returns-nil ()
- "Validate file ending with dot (empty extension) returns nil."
- (should-not (cj/music--valid-file-p "/path/to/song.")))
-
-(ert-deftest test-music-config--valid-file-p-boundary-empty-string-returns-nil ()
- "Validate empty string returns nil."
- (should-not (cj/music--valid-file-p "")))
-
-;;; Error Cases
-
-(ert-deftest test-music-config--valid-file-p-error-nil-input-returns-nil ()
- "Validate nil input returns nil gracefully."
- (should-not (cj/music--valid-file-p nil)))
-
-(ert-deftest test-music-config--valid-file-p-error-non-music-extension-returns-nil ()
- "Validate non-music file extension returns nil."
- (should-not (cj/music--valid-file-p "/path/to/document.txt"))
- (should-not (cj/music--valid-file-p "/path/to/readme.md")))
-
-(ert-deftest test-music-config--valid-file-p-error-image-extension-returns-nil ()
- "Validate image file extension returns nil."
- (should-not (cj/music--valid-file-p "/path/to/cover.jpg"))
- (should-not (cj/music--valid-file-p "/path/to/artwork.png")))
-
-(ert-deftest test-music-config--valid-file-p-error-video-extension-returns-nil ()
- "Validate video file extension returns nil (mp4 not in list, only m4a)."
- (should-not (cj/music--valid-file-p "/path/to/video.mp4"))
- (should-not (cj/music--valid-file-p "/path/to/clip.mkv")))
-
-(provide 'test-music-config--valid-file-p)
-;;; test-music-config--valid-file-p.el ends here
diff --git a/tests/test-org-agenda-build-list.el b/tests/test-org-agenda-build-list.el
deleted file mode 100644
index 6b424200..00000000
--- a/tests/test-org-agenda-build-list.el
+++ /dev/null
@@ -1,294 +0,0 @@
-;;; test-org-agenda-build-list.el --- Tests for cj/build-org-agenda-list -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/build-org-agenda-list caching logic.
-;; Tests cache behavior, TTL expiration, force rebuild, and async build flag.
-
-;;; Code:
-
-(require 'ert)
-
-;; Add modules to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar inbox-file "/tmp/test-inbox.org")
-(defvar schedule-file "/tmp/test-schedule.org")
-(defvar gcal-file "/tmp/test-gcal.org")
-(defvar projects-dir "/tmp/test-projects/")
-
-;; Now load the actual production module
-(require 'org-agenda-config)
-
-;;; Setup and Teardown
-
-(defun test-org-agenda-setup ()
- "Reset cache and state before each test."
- (setq cj/org-agenda-files-cache nil)
- (setq cj/org-agenda-files-cache-time nil)
- (setq cj/org-agenda-files-building nil)
- (setq org-agenda-files nil))
-
-(defun test-org-agenda-teardown ()
- "Clean up after each test."
- (setq cj/org-agenda-files-cache nil)
- (setq cj/org-agenda-files-cache-time nil)
- (setq cj/org-agenda-files-building nil)
- (setq org-agenda-files nil))
-
-;;; Normal Cases
-
-(ert-deftest test-org-agenda-build-list-normal-first-call-builds-cache ()
- "Test that first call builds cache from scratch.
-
-When cache is empty, function should:
-1. Scan directory for todo.org files
-2. Build agenda files list
-3. Populate cache
-4. Set cache timestamp"
- (test-org-agenda-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs) '("/tmp/project/todo.org"))))
-
- ;; Before call: cache empty
- (should (null cj/org-agenda-files-cache))
- (should (null cj/org-agenda-files-cache-time))
-
- ;; Build agenda files
- (cj/build-org-agenda-list)
-
- ;; After call: cache populated
- (should cj/org-agenda-files-cache)
- (should cj/org-agenda-files-cache-time)
- (should org-agenda-files)
-
- ;; Cache matches org-agenda-files
- (should (equal cj/org-agenda-files-cache org-agenda-files))
-
- ;; Contains base files (inbox, schedule, gcal) plus project files
- (should (>= (length org-agenda-files) 3)))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-normal-second-call-uses-cache ()
- "Test that second call uses cache instead of rebuilding.
-
-When cache is valid (not expired):
-1. Should NOT scan directories again
-2. Should restore files from cache
-3. Should NOT update cache timestamp"
- (test-org-agenda-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- (setq scan-count (1+ scan-count))
- '("/tmp/project/todo.org"))))
-
- ;; First call: builds cache
- (cj/build-org-agenda-list)
- (should (= scan-count 1)) ; 1 directory scanned
-
- (let ((cached-time cj/org-agenda-files-cache-time)
- (cached-files cj/org-agenda-files-cache))
-
- ;; Second call: uses cache
- (cj/build-org-agenda-list)
-
- ;; Scan count unchanged (cache hit)
- (should (= scan-count 1))
-
- ;; Cache unchanged
- (should (equal cj/org-agenda-files-cache-time cached-time))
- (should (equal cj/org-agenda-files-cache cached-files)))))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-normal-force-rebuild-bypasses-cache ()
- "Test that force-rebuild parameter bypasses cache.
-
-When force-rebuild is non-nil:
-1. Should ignore valid cache
-2. Should rebuild from scratch
-3. Should update cache with new data"
- (test-org-agenda-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- (setq scan-count (1+ scan-count))
- (if (> scan-count 1)
- '("/tmp/project/todo.org" "/tmp/project2/todo.org") ; New file on rebuild
- '("/tmp/project/todo.org")))))
-
- ;; First call: builds cache
- (cj/build-org-agenda-list)
- (let ((initial-count (length org-agenda-files)))
-
- ;; Force rebuild
- (cj/build-org-agenda-list 'force)
-
- ;; Scanned again
- (should (= scan-count 2))
-
- ;; New files include additional project
- (should (> (length org-agenda-files) initial-count)))))
- (test-org-agenda-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-org-agenda-build-list-boundary-cache-expires-after-ttl ()
- "Test that cache expires after TTL period.
-
-When cache timestamp exceeds TTL:
-1. Should rebuild files list
-2. Should update cache timestamp
-3. Should rescan directory"
- (test-org-agenda-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- (setq scan-count (1+ scan-count))
- '("/tmp/project/todo.org"))))
-
- ;; First call: builds cache
- (cj/build-org-agenda-list)
- (should (= scan-count 1))
-
- ;; Simulate cache expiration (set time to 2 hours ago)
- (setq cj/org-agenda-files-cache-time
- (- (float-time) (* 2 3600)))
-
- ;; Second call: cache expired, rebuild
- (cj/build-org-agenda-list)
-
- ;; Scanned again (cache was expired)
- (should (= scan-count 2))
-
- ;; Cache timestamp updated to current time
- (should (< (- (float-time) cj/org-agenda-files-cache-time) 1))))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-boundary-empty-directory-creates-minimal-list ()
- "Test behavior when directory contains no todo.org files.
-
-When directory scan returns empty:
-1. Should still create base files (inbox, schedule)
-2. Should not fail or error
-3. Should cache the minimal result"
- (test-org-agenda-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs) nil))) ; No files found
-
- (cj/build-org-agenda-list)
-
- ;; Should have base files only (inbox, schedule, gcal)
- (should (= (length org-agenda-files) 3))
-
- ;; Cache should contain base files
- (should cj/org-agenda-files-cache)
- (should (= (length cj/org-agenda-files-cache) 3)))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-boundary-building-flag-set-during-build ()
- "Test that building flag is set during build and cleared after.
-
-During build:
-1. Flag should be set to prevent concurrent builds
-2. Flag should clear even if build fails
-3. Flag state should be consistent"
- (test-org-agenda-setup)
- (unwind-protect
- (let ((flag-during-build nil))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- ;; Capture flag state during directory scan
- (setq flag-during-build cj/org-agenda-files-building)
- '("/tmp/project/todo.org"))))
-
- ;; Before build
- (should (null cj/org-agenda-files-building))
-
- ;; Build
- (cj/build-org-agenda-list)
-
- ;; Flag was set during build
- (should flag-during-build)
-
- ;; Flag cleared after build
- (should (null cj/org-agenda-files-building))))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-boundary-building-flag-clears-on-error ()
- "Test that building flag clears even if build errors.
-
-When build encounters error:
-1. Flag should still be cleared (unwind-protect)
-2. Prevents permanently locked state
-3. Next build can proceed"
- (test-org-agenda-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- (error "Simulated scan failure"))))
-
- ;; Build will error
- (should-error (cj/build-org-agenda-list))
-
- ;; Flag cleared despite error (unwind-protect)
- (should (null cj/org-agenda-files-building)))
- (test-org-agenda-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-org-agenda-build-list-error-nil-cache-with-old-timestamp ()
- "Test handling of inconsistent state (nil cache but timestamp set).
-
-When cache is nil but timestamp exists:
-1. Should recognize cache as invalid
-2. Should rebuild files list
-3. Should set both cache and timestamp"
- (test-org-agenda-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs) '("/tmp/project/todo.org"))))
-
- ;; Set inconsistent state
- (setq cj/org-agenda-files-cache nil)
- (setq cj/org-agenda-files-cache-time (float-time))
-
- ;; Build should recognize invalid state
- (cj/build-org-agenda-list)
-
- ;; Cache now populated
- (should cj/org-agenda-files-cache)
- (should cj/org-agenda-files-cache-time)
- (should org-agenda-files))
- (test-org-agenda-teardown)))
-
-(ert-deftest test-org-agenda-build-list-error-directory-scan-failure-propagates ()
- "Test that directory scan failures propagate as errors.
-
-When directory-files-recursively errors:
-1. Error should propagate to caller
-2. Cache should not be corrupted
-3. Building flag should clear"
- (test-org-agenda-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern &optional _include-dirs)
- (error "Permission denied"))))
-
- ;; Should propagate error
- (should-error (cj/build-org-agenda-list))
-
- ;; Cache not corrupted (still nil)
- (should (null cj/org-agenda-files-cache))
-
- ;; Building flag cleared
- (should (null cj/org-agenda-files-building)))
- (test-org-agenda-teardown)))
-
-(provide 'test-org-agenda-build-list)
-;;; test-org-agenda-build-list.el ends here
diff --git a/tests/test-org-contacts-capture-finalize.el b/tests/test-org-contacts-capture-finalize.el
deleted file mode 100644
index 6793defe..00000000
--- a/tests/test-org-contacts-capture-finalize.el
+++ /dev/null
@@ -1,178 +0,0 @@
-;;; test-org-contacts-capture-finalize.el --- Tests for org-contacts capture template finalization -*- lexical-binding: t; -*-
-
-;; Copyright (C) 2025 Craig Jennings
-
-;; Author: Craig Jennings <c@cjennings.net>
-
-;; This program is free software: you can redistribute it and/or modify
-;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation, either version 3 of the License, or
-;; (at your option) any later version.
-
-;;; Commentary:
-
-;; Unit tests for the org-contacts capture template finalization function
-;; that automatically inserts birthday timestamps.
-
-;;; Code:
-
-;; Initialize package system for batch mode
-(when noninteractive
- (package-initialize))
-
-(require 'ert)
-(require 'org)
-
-;; 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 contacts-file "/tmp/test-contacts.org"
- "Stub contacts file for testing.")
-
-;; Declare org-capture-plist for dynamic scoping in tests
-(defvar org-capture-plist nil
- "Plist that org-capture uses during capture. Declared for testing.")
-
-;; Load the actual module
-(require 'org-contacts-config)
-
-;;; Tests for birthday timestamp finalization
-
-(ert-deftest test-contacts-capture-finalize-with-full-birthday ()
- "Test that finalize adds timestamp for YYYY-MM-DD birthday."
- (with-temp-buffer
- (org-mode)
- (insert "* Alice Anderson\n")
- (insert ":PROPERTIES:\n")
- (insert ":EMAIL: alice@example.com\n")
- (insert ":BIRTHDAY: 1985-03-15\n")
- (insert ":END:\n")
- (insert "Added: [2025-11-01 Fri 20:30]\n")
-
- ;; Simulate capture context
- (let ((org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- (let ((content (buffer-string)))
- ;; Should have birthday timestamp
- (should (string-match-p "<1985-03-15 [A-Za-z]\\{3\\} \\+1y>" content))
- ;; Timestamp should be after :END:
- (should (string-match-p ":END:\n<1985-03-15" content))))))
-
-(ert-deftest test-contacts-capture-finalize-with-partial-birthday ()
- "Test that finalize adds timestamp for MM-DD birthday with current year."
- (let ((current-year (nth 5 (decode-time))))
- (with-temp-buffer
- (org-mode)
- (insert "* Bob Baker\n")
- (insert ":PROPERTIES:\n")
- (insert ":BIRTHDAY: 07-04\n")
- (insert ":END:\n")
-
- (let ((org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- (let ((content (buffer-string)))
- ;; Should have birthday timestamp with current year
- (should (string-match-p (format "<%d-07-04 [A-Za-z]\\{3\\} \\+1y>" current-year) content)))))))
-
-(ert-deftest test-contacts-capture-finalize-without-birthday ()
- "Test that finalize does nothing when no birthday property."
- (with-temp-buffer
- (org-mode)
- (insert "* Carol Chen\n")
- (insert ":PROPERTIES:\n")
- (insert ":EMAIL: carol@example.com\n")
- (insert ":END:\n")
-
- (let ((original-content (buffer-string))
- (org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- ;; Content should be unchanged
- (should (string= (buffer-string) original-content))
- ;; Should have no timestamp
- (should-not (string-match-p "<[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" (buffer-string))))))
-
-(ert-deftest test-contacts-capture-finalize-with-empty-birthday ()
- "Test that finalize skips empty birthday values."
- (with-temp-buffer
- (org-mode)
- (insert "* David Davis\n")
- (insert ":PROPERTIES:\n")
- (insert ":BIRTHDAY: \n")
- (insert ":END:\n")
-
- (let ((original-content (buffer-string))
- (org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- ;; Content should be unchanged
- (should (string= (buffer-string) original-content))
- ;; Should have no timestamp
- (should-not (string-match-p "<[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}" (buffer-string))))))
-
-(ert-deftest test-contacts-capture-finalize-prevents-duplicates ()
- "Test that finalize doesn't add duplicate timestamps."
- (with-temp-buffer
- (org-mode)
- (insert "* Eve Evans\n")
- (insert ":PROPERTIES:\n")
- (insert ":BIRTHDAY: 2000-01-01\n")
- (insert ":END:\n")
- (insert "<2000-01-01 Sat +1y>\n")
-
- (let ((org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- ;; Should have exactly one timestamp
- (should (= 1 (how-many "<2000-01-01 [A-Za-z]\\{3\\} \\+1y>" (point-min) (point-max)))))))
-
-(ert-deftest test-contacts-capture-finalize-only-for-contact-template ()
- "Test that finalize only runs for 'C' template key."
- (with-temp-buffer
- (org-mode)
- (insert "* Task with birthday property\n")
- (insert ":PROPERTIES:\n")
- (insert ":BIRTHDAY: 2000-01-01\n")
- (insert ":END:\n")
-
- (let ((original-content (buffer-string))
- (org-capture-plist '(:key "t"))) ; Different template key
- (cj/org-contacts-finalize-birthday-timestamp)
-
- ;; Content should be unchanged
- (should (string= (buffer-string) original-content)))))
-
-(ert-deftest test-contacts-capture-finalize-preserves-existing-content ()
- "Test that finalize preserves all existing content."
- (with-temp-buffer
- (org-mode)
- (insert "* Alice Anderson\n")
- (insert ":PROPERTIES:\n")
- (insert ":EMAIL: alice@example.com\n")
- (insert ":PHONE: 555-1234\n")
- (insert ":BIRTHDAY: 1985-03-15\n")
- (insert ":NICKNAME: Ali\n")
- (insert ":NOTE: Met at conference\n")
- (insert ":END:\n")
- (insert "Added: [2025-11-01 Fri 20:30]\n")
-
- (let ((org-capture-plist '(:key "C")))
- (cj/org-contacts-finalize-birthday-timestamp)
-
- (let ((content (buffer-string)))
- ;; All properties should still be present
- (should (string-search ":EMAIL: alice@example.com" content))
- (should (string-search ":PHONE: 555-1234" content))
- (should (string-search ":BIRTHDAY: 1985-03-15" content))
- (should (string-search ":NICKNAME: Ali" content))
- (should (string-search ":NOTE: Met at conference" content))
- ;; Added timestamp should still be there
- (should (string-search "Added: [2025-11-01 Fri 20:30]" content))
- ;; Birthday timestamp should be added
- (should (string-match-p "<1985-03-15 [A-Za-z]\\{3\\} \\+1y>" content))))))
-
-(provide 'test-org-contacts-capture-finalize)
-;;; test-org-contacts-capture-finalize.el ends here
diff --git a/tests/test-org-contacts-parse-email.el b/tests/test-org-contacts-parse-email.el
deleted file mode 100644
index 37e79fba..00000000
--- a/tests/test-org-contacts-parse-email.el
+++ /dev/null
@@ -1,219 +0,0 @@
-;;; test-org-contacts-parse-email.el --- Tests for cj/--parse-email-string -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--parse-email-string function from org-contacts-config.el
-;;
-;; This function parses a string containing one or more email addresses
-;; separated by commas, semicolons, or spaces, and formats them as
-;; "Name <email>" strings.
-;;
-;; Examples:
-;; Input: name="John Doe", email-string="john@example.com"
-;; Output: '("John Doe <john@example.com>")
-;;
-;; Input: name="Jane Smith", email-string="jane@work.com, jane@home.com"
-;; Output: '("Jane Smith <jane@work.com>" "Jane Smith <jane@home.com>")
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-contacts-config)
-
-;;; Test Helpers
-
-(defun test-parse-email (name email-string)
- "Test cj/--parse-email-string with NAME and EMAIL-STRING.
-Returns the formatted email list."
- (cj/--parse-email-string name email-string))
-
-;;; Normal Cases - Single Email
-
-(ert-deftest test-parse-single-email ()
- "Should format single email address."
- (let ((result (test-parse-email "John Doe" "john@example.com")))
- (should (equal result '("John Doe <john@example.com>")))))
-
-(ert-deftest test-parse-single-email-with-subdomain ()
- "Should handle email with subdomain."
- (let ((result (test-parse-email "Jane Smith" "jane@mail.company.com")))
- (should (equal result '("Jane Smith <jane@mail.company.com>")))))
-
-(ert-deftest test-parse-email-with-numbers ()
- "Should handle email containing numbers."
- (let ((result (test-parse-email "User 123" "user123@test.com")))
- (should (equal result '("User 123 <user123@test.com>")))))
-
-(ert-deftest test-parse-email-with-dots ()
- "Should handle email with dots in local part."
- (let ((result (test-parse-email "Bob Jones" "bob.jones@example.com")))
- (should (equal result '("Bob Jones <bob.jones@example.com>")))))
-
-(ert-deftest test-parse-email-with-hyphen ()
- "Should handle email with hyphens."
- (let ((result (test-parse-email "Alice Brown" "alice-brown@test-domain.com")))
- (should (equal result '("Alice Brown <alice-brown@test-domain.com>")))))
-
-;;; Normal Cases - Multiple Emails with Different Separators
-
-(ert-deftest test-parse-two-emails-comma ()
- "Should parse two emails separated by comma."
- (let ((result (test-parse-email "John Doe" "john@work.com, john@home.com")))
- (should (equal result '("John Doe <john@work.com>" "John Doe <john@home.com>")))))
-
-(ert-deftest test-parse-two-emails-semicolon ()
- "Should parse two emails separated by semicolon."
- (let ((result (test-parse-email "Jane Smith" "jane@work.com; jane@home.com")))
- (should (equal result '("Jane Smith <jane@work.com>" "Jane Smith <jane@home.com>")))))
-
-(ert-deftest test-parse-two-emails-space ()
- "Should parse two emails separated by space."
- (let ((result (test-parse-email "Bob Jones" "bob@work.com bob@home.com")))
- (should (equal result '("Bob Jones <bob@work.com>" "Bob Jones <bob@home.com>")))))
-
-(ert-deftest test-parse-three-emails-mixed-separators ()
- "Should parse emails with mixed separators."
- (let ((result (test-parse-email "Alice" "alice@a.com, alice@b.com; alice@c.com")))
- (should (equal result '("Alice <alice@a.com>" "Alice <alice@b.com>" "Alice <alice@c.com>")))))
-
-(ert-deftest test-parse-multiple-emails-with-spaces ()
- "Should parse comma-separated emails with spaces."
- (let ((result (test-parse-email "User" "a@test.com , b@test.com , c@test.com")))
- (should (equal result '("User <a@test.com>" "User <b@test.com>" "User <c@test.com>")))))
-
-;;; Normal Cases - Whitespace Handling
-
-(ert-deftest test-parse-email-leading-whitespace ()
- "Should trim leading whitespace from email."
- (let ((result (test-parse-email "John" " john@example.com")))
- (should (equal result '("John <john@example.com>")))))
-
-(ert-deftest test-parse-email-trailing-whitespace ()
- "Should trim trailing whitespace from email."
- (let ((result (test-parse-email "Jane" "jane@example.com ")))
- (should (equal result '("Jane <jane@example.com>")))))
-
-(ert-deftest test-parse-email-surrounding-whitespace ()
- "Should trim surrounding whitespace from email."
- (let ((result (test-parse-email "Bob" " bob@example.com ")))
- (should (equal result '("Bob <bob@example.com>")))))
-
-(ert-deftest test-parse-emails-with-tabs ()
- "Should handle emails separated by tabs."
- (let ((result (test-parse-email "User" "a@test.com\tb@test.com")))
- (should (equal result '("User <a@test.com>" "User <b@test.com>")))))
-
-;;; Edge Cases - Empty and Nil
-
-(ert-deftest test-parse-nil-email-string ()
- "Should return nil for nil email string."
- (let ((result (test-parse-email "John Doe" nil)))
- (should (null result))))
-
-(ert-deftest test-parse-empty-email-string ()
- "Should return nil for empty email string."
- (let ((result (test-parse-email "Jane Smith" "")))
- (should (null result))))
-
-(ert-deftest test-parse-whitespace-only ()
- "Should return nil for whitespace-only string."
- (let ((result (test-parse-email "Bob Jones" " ")))
- (should (null result))))
-
-(ert-deftest test-parse-tabs-only ()
- "Should return nil for tabs-only string."
- (let ((result (test-parse-email "Alice" "\t\t\t")))
- (should (null result))))
-
-(ert-deftest test-parse-mixed-whitespace-only ()
- "Should return nil for mixed whitespace."
- (let ((result (test-parse-email "User" " \t \n ")))
- (should (null result))))
-
-;;; Edge Cases - Multiple Consecutive Separators
-
-(ert-deftest test-parse-multiple-commas ()
- "Should handle multiple consecutive commas."
- (let ((result (test-parse-email "John" "john@a.com,,,john@b.com")))
- (should (equal result '("John <john@a.com>" "John <john@b.com>")))))
-
-(ert-deftest test-parse-multiple-semicolons ()
- "Should handle multiple consecutive semicolons."
- (let ((result (test-parse-email "Jane" "jane@a.com;;;jane@b.com")))
- (should (equal result '("Jane <jane@a.com>" "Jane <jane@b.com>")))))
-
-(ert-deftest test-parse-multiple-spaces ()
- "Should handle multiple consecutive spaces."
- (let ((result (test-parse-email "Bob" "bob@a.com bob@b.com")))
- (should (equal result '("Bob <bob@a.com>" "Bob <bob@b.com>")))))
-
-(ert-deftest test-parse-mixed-multiple-separators ()
- "Should handle mixed consecutive separators."
- (let ((result (test-parse-email "User" "a@test.com , ; b@test.com")))
- (should (equal result '("User <a@test.com>" "User <b@test.com>")))))
-
-;;; Edge Cases - Special Name Formats
-
-(ert-deftest test-parse-name-with-title ()
- "Should handle name with title."
- (let ((result (test-parse-email "Dr. John Smith" "john@example.com")))
- (should (equal result '("Dr. John Smith <john@example.com>")))))
-
-(ert-deftest test-parse-name-with-suffix ()
- "Should handle name with suffix."
- (let ((result (test-parse-email "John Doe Jr." "john@example.com")))
- (should (equal result '("John Doe Jr. <john@example.com>")))))
-
-(ert-deftest test-parse-name-with-special-chars ()
- "Should handle name with special characters."
- (let ((result (test-parse-email "O'Brien, Patrick" "patrick@example.com")))
- (should (equal result '("O'Brien, Patrick <patrick@example.com>")))))
-
-(ert-deftest test-parse-unicode-name ()
- "Should handle Unicode characters in name."
- (let ((result (test-parse-email "José García" "jose@example.com")))
- (should (equal result '("José García <jose@example.com>")))))
-
-;;; Edge Cases - Special Email Formats
-
-(ert-deftest test-parse-email-with-plus ()
- "Should handle email with plus sign."
- (let ((result (test-parse-email "User" "user+tag@example.com")))
- (should (equal result '("User <user+tag@example.com>")))))
-
-(ert-deftest test-parse-email-with-underscore ()
- "Should handle email with underscore."
- (let ((result (test-parse-email "User" "user_name@example.com")))
- (should (equal result '("User <user_name@example.com>")))))
-
-(ert-deftest test-parse-very-long-email ()
- "Should handle very long email address."
- (let* ((long-local (make-string 50 ?a))
- (email (concat long-local "@example.com"))
- (result (test-parse-email "User" email)))
- (should (equal result (list (format "User <%s>" email))))))
-
-;;; Integration Tests
-
-(ert-deftest test-parse-realistic-contact ()
- "Should parse realistic contact with multiple emails."
- (let ((result (test-parse-email "John Doe" "john.doe@company.com, jdoe@personal.com")))
- (should (equal result '("John Doe <john.doe@company.com>" "John Doe <jdoe@personal.com>")))))
-
-(ert-deftest test-parse-messy-input ()
- "Should handle messy real-world input."
- (let ((result (test-parse-email "Jane Smith" " jane@work.com ; jane@home.com,jane@mobile.com ")))
- (should (equal result '("Jane Smith <jane@work.com>" "Jane Smith <jane@home.com>" "Jane Smith <jane@mobile.com>")))))
-
-(ert-deftest test-parse-single-with-extra-separators ()
- "Should handle single email with trailing separators."
- (let ((result (test-parse-email "Bob" "bob@example.com;;;")))
- (should (equal result '("Bob <bob@example.com>")))))
-
-(provide 'test-org-contacts-parse-email)
-;;; test-org-contacts-parse-email.el ends here
diff --git a/tests/test-org-drill-first-function.el b/tests/test-org-drill-first-function.el
deleted file mode 100644
index 925cdf84..00000000
--- a/tests/test-org-drill-first-function.el
+++ /dev/null
@@ -1,135 +0,0 @@
-;;; test-org-drill-first-function.el --- Test org-drill 'first' function compatibility -*- lexical-binding: t -*-
-
-;;; Commentary:
-;;
-;; Tests to reproduce and verify the fix for org-drill's use of deprecated
-;; 'first' function which was removed in modern Emacs.
-;;
-;; Original error: "mapcar: Symbol's function definition is void: first"
-;;
-;; The error occurred because org-drill (or its dependencies) use old Common Lisp
-;; functions like 'first' instead of the modern 'cl-first' from cl-lib.
-
-;;; Code:
-
-(require 'ert)
-
-(ert-deftest test-org-drill-first-function-not-defined-without-compat ()
- "Verify that 'first' function doesn't exist by default in modern Emacs.
-
-This test documents the original problem - the 'first' function from the
-old 'cl' package is not available in modern Emacs, which only provides
-'cl-first' from cl-lib."
- (let ((first-defined (fboundp 'first)))
- ;; In a clean Emacs without our compatibility shim, 'first' should not exist
- ;; (unless the old 'cl' package was loaded, which is deprecated)
- (should (or (not first-defined)
- ;; If it IS defined, it should be our compatibility alias
- (eq (symbol-function 'first) 'cl-first)))))
-
-(ert-deftest test-org-drill-cl-first-is-available ()
- "Verify that cl-first is available from cl-lib.
-
-The modern cl-lib package provides cl-first as the replacement for
-the deprecated 'first' function."
- (require 'cl-lib)
- (should (fboundp 'cl-first))
- ;; Test it works
- (should (eq 'a (cl-first '(a b c)))))
-
-(ert-deftest test-org-drill-first-compatibility-alias ()
- "Verify that our compatibility alias makes 'first' work like 'cl-first'.
-
-This is the fix we applied - creating an alias so that code using the
-old 'first' function will work with the modern 'cl-first'."
- (require 'cl-lib)
-
- ;; Create the compatibility alias (same as in org-drill-config.el)
- (unless (fboundp 'first)
- (defalias 'first 'cl-first))
-
- ;; Now 'first' should be defined
- (should (fboundp 'first))
-
- ;; And it should behave like cl-first
- (should (eq 'a (first '(a b c))))
- (should (eq 'x (first '(x y z))))
- (should (eq nil (first '()))))
-
-(ert-deftest test-org-drill-mapcar-with-first ()
- "Test the exact error scenario: (mapcar 'first ...).
-
-This reproduces the original error that occurred during org-drill's
-item collection phase where it uses mapcar with the 'first' function."
- (require 'cl-lib)
-
- ;; Create the compatibility alias
- (unless (fboundp 'first)
- (defalias 'first 'cl-first))
-
- ;; Simulate org-drill data structure: list of (status data) pairs
- (let ((drill-entries '((:new 0 0)
- (:young 5 3)
- (:overdue 10 2)
- (:mature 20 1))))
-
- ;; This is the kind of operation that was failing
- ;; Extract first element from each entry
- (let ((statuses (mapcar 'first drill-entries)))
- (should (equal statuses '(:new :young :overdue :mature))))))
-
-(ert-deftest test-org-drill-second-and-third-aliases ()
- "Verify that second and third compatibility aliases also work.
-
-org-drill might use other deprecated cl functions too, so we create
-aliases for second and third as well."
- (require 'cl-lib)
-
- ;; Create all compatibility aliases
- (unless (fboundp 'first)
- (defalias 'first 'cl-first))
- (unless (fboundp 'second)
- (defalias 'second 'cl-second))
- (unless (fboundp 'third)
- (defalias 'third 'cl-third))
-
- (let ((test-list '(a b c d e)))
- (should (eq 'a (first test-list)))
- (should (eq 'b (second test-list)))
- (should (eq 'c (third test-list)))))
-
-(ert-deftest test-org-drill-config-loads-without-error ()
- "Verify that org-drill-config.el loads successfully with our fix.
-
-This test ensures that the :init block in our use-package form
-doesn't cause any loading errors."
- ;; This should not throw an error
- (should-not (condition-case err
- (progn
- (load (expand-file-name "modules/org-drill-config.el"
- user-emacs-directory))
- nil)
- (error err))))
-
-(ert-deftest test-org-drill-data-structure-operations ()
- "Verify that common org-drill data structure operations work with our fix.
-
-org-drill works with data structures that require extracting elements.
-This test ensures our compatibility aliases work with typical patterns."
- (require 'cl-lib)
-
- ;; Create compatibility aliases
- (unless (fboundp 'first)
- (defalias 'first 'cl-first))
-
- ;; Test that we can work with org-drill-like data structures
- ;; (similar to what persist-defvar would store)
- (let ((test-data '((:status-1 data-1)
- (:status-2 data-2)
- (:status-3 data-3))))
- ;; This kind of operation should work
- (should (equal '(:status-1 :status-2 :status-3)
- (mapcar 'first test-data)))))
-
-(provide 'test-org-drill-first-function)
-;;; test-org-drill-first-function.el ends here
diff --git a/tests/test-org-drill-font-switching.el b/tests/test-org-drill-font-switching.el
deleted file mode 100644
index 27d5f420..00000000
--- a/tests/test-org-drill-font-switching.el
+++ /dev/null
@@ -1,175 +0,0 @@
-;;; test-org-drill-font-switching.el --- Tests for org-drill display management -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests that org-drill automatically manages display settings (fonts, modeline)
-;; and restores them when the session ends.
-;;
-;; These are unit tests for the pure logic functions, testing them in isolation
-;; without requiring the full org-drill package.
-
-;;; Code:
-
-(require 'ert)
-
-;; Define the functions we're testing (extracted from org-drill-config.el)
-
-(defvar cj/org-drill-previous-preset nil
- "Stores the font preset active before starting org-drill.")
-
-(defvar cj/org-drill-previous-modeline-format nil
- "Stores the modeline format active before starting org-drill.")
-
-(defvar fontaine-current-preset nil
- "Current fontaine preset (mocked for testing).")
-
-(defvar mode-line-format '("Mock modeline")
- "Mock modeline format for testing.")
-
-(defvar org-drill-hide-modeline-during-session t
- "Whether to hide modeline during drill sessions.")
-
-(defun fontaine-set-preset (preset)
- "Mock function: Set fontaine preset to PRESET."
- (setq fontaine-current-preset preset))
-
-(defun cj/org-drill-setup-display ()
- "Set up display for drill sessions: larger fonts and hidden modeline."
- (unless cj/org-drill-previous-preset
- (setq cj/org-drill-previous-preset fontaine-current-preset))
- (fontaine-set-preset 'EBook)
- (when org-drill-hide-modeline-during-session
- (unless cj/org-drill-previous-modeline-format
- (setq cj/org-drill-previous-modeline-format mode-line-format))
- (setq mode-line-format nil)))
-
-(defun cj/org-drill-restore-display ()
- "Restore display settings after drill session ends."
- (when cj/org-drill-previous-preset
- (fontaine-set-preset cj/org-drill-previous-preset)
- (setq cj/org-drill-previous-preset nil))
- (when cj/org-drill-previous-modeline-format
- (setq mode-line-format cj/org-drill-previous-modeline-format)
- (setq cj/org-drill-previous-modeline-format nil)))
-
-;;; Font Management Tests
-
-(ert-deftest test-org-drill-display/saves-current-preset ()
- "Test that starting org-drill saves the current font preset."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'default))
- (cj/org-drill-setup-display)
- (should (eq cj/org-drill-previous-preset 'default))))
-
-(ert-deftest test-org-drill-display/switches-to-ebook ()
- "Test that starting org-drill switches to EBook preset."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'default))
- (cj/org-drill-setup-display)
- (should (eq fontaine-current-preset 'EBook))))
-
-(ert-deftest test-org-drill-display/restores-previous-preset ()
- "Test that ending org-drill restores the previous font preset."
- (let ((cj/org-drill-previous-preset 'default)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'EBook))
- (cj/org-drill-restore-display)
- (should (eq fontaine-current-preset 'default))))
-
-(ert-deftest test-org-drill-display/clears-saved-preset-after-restore ()
- "Test that restoring display clears the saved preset."
- (let ((cj/org-drill-previous-preset 'default)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'EBook))
- (cj/org-drill-restore-display)
- (should (null cj/org-drill-previous-preset))))
-
-;;; Modeline Management Tests
-
-(ert-deftest test-org-drill-display/hides-modeline ()
- "Test that starting org-drill hides the modeline when configured."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'default)
- (mode-line-format '("Mock modeline"))
- (org-drill-hide-modeline-during-session t))
- (cj/org-drill-setup-display)
- (should (null mode-line-format))
- (should (equal cj/org-drill-previous-modeline-format '("Mock modeline")))))
-
-(ert-deftest test-org-drill-display/respects-modeline-config ()
- "Test that modeline hiding respects the configuration variable."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'default)
- (mode-line-format '("Mock modeline"))
- (org-drill-hide-modeline-during-session nil))
- (cj/org-drill-setup-display)
- (should (equal mode-line-format '("Mock modeline")))
- (should (null cj/org-drill-previous-modeline-format))))
-
-(ert-deftest test-org-drill-display/restores-modeline ()
- "Test that ending org-drill restores the modeline."
- (let ((cj/org-drill-previous-preset 'default)
- (cj/org-drill-previous-modeline-format '("Mock modeline"))
- (fontaine-current-preset 'EBook)
- (mode-line-format nil))
- (cj/org-drill-restore-display)
- (should (equal mode-line-format '("Mock modeline")))
- (should (null cj/org-drill-previous-modeline-format))))
-
-;;; Boundary Cases
-
-(ert-deftest test-org-drill-display/does-not-save-preset-twice ()
- "Test that calling setup twice doesn't overwrite the saved preset."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'default))
- ;; First call saves 'default
- (cj/org-drill-setup-display)
- (should (eq cj/org-drill-previous-preset 'default))
-
- ;; Manually change current preset (simulating a preset change during drill)
- (setq fontaine-current-preset 'FiraCode)
-
- ;; Second call should NOT update saved preset
- (cj/org-drill-setup-display)
- (should (eq cj/org-drill-previous-preset 'default))
- (should-not (eq cj/org-drill-previous-preset 'FiraCode))))
-
-(ert-deftest test-org-drill-display/restore-with-nil-previous-preset ()
- "Test that restore does nothing when no preset was saved."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'EBook))
- (cj/org-drill-restore-display)
- ;; Should remain at EBook (no restore happened)
- (should (eq fontaine-current-preset 'EBook))
- (should (null cj/org-drill-previous-preset))))
-
-;;; Integration Tests
-
-(ert-deftest test-org-drill-display/full-cycle ()
- "Test complete cycle: save -> switch -> restore."
- (let ((cj/org-drill-previous-preset nil)
- (cj/org-drill-previous-modeline-format nil)
- (fontaine-current-preset 'FiraCode)
- (mode-line-format '("Original modeline"))
- (org-drill-hide-modeline-during-session t))
- ;; Step 1: Start drill (save state, switch to EBook, hide modeline)
- (cj/org-drill-setup-display)
- (should (eq cj/org-drill-previous-preset 'FiraCode))
- (should (eq fontaine-current-preset 'EBook))
- (should (equal cj/org-drill-previous-modeline-format '("Original modeline")))
- (should (null mode-line-format))
-
- ;; Step 2: End drill (restore everything)
- (cj/org-drill-restore-display)
- (should (eq fontaine-current-preset 'FiraCode))
- (should (null cj/org-drill-previous-preset))
- (should (equal mode-line-format '("Original modeline")))
- (should (null cj/org-drill-previous-modeline-format))))
-
-(provide 'test-org-drill-font-switching)
-;;; test-org-drill-font-switching.el ends here
diff --git a/tests/test-org-refile-build-targets.el b/tests/test-org-refile-build-targets.el
deleted file mode 100644
index e7ab5c42..00000000
--- a/tests/test-org-refile-build-targets.el
+++ /dev/null
@@ -1,305 +0,0 @@
-;;; test-org-refile-build-targets.el --- Tests for cj/build-org-refile-targets -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/build-org-refile-targets caching logic.
-;; Tests cache behavior, TTL expiration, force rebuild, and async build flag.
-
-;;; Code:
-
-(require 'ert)
-
-;; Add modules to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar inbox-file "/tmp/test-inbox.org")
-(defvar reference-file "/tmp/test-reference.org")
-(defvar schedule-file "/tmp/test-schedule.org")
-(defvar user-emacs-directory "/tmp/test-emacs.d/")
-(defvar code-dir "/tmp/test-code/")
-(defvar projects-dir "/tmp/test-projects/")
-
-;; Now load the actual production module
-(require 'org-refile-config)
-
-;;; Setup and Teardown
-
-(defun test-org-refile-setup ()
- "Reset cache and state before each test."
- (setq cj/org-refile-targets-cache nil)
- (setq cj/org-refile-targets-cache-time nil)
- (setq cj/org-refile-targets-building nil)
- (setq org-refile-targets nil))
-
-(defun test-org-refile-teardown ()
- "Clean up after each test."
- (setq cj/org-refile-targets-cache nil)
- (setq cj/org-refile-targets-cache-time nil)
- (setq cj/org-refile-targets-building nil)
- (setq org-refile-targets nil))
-
-;;; Normal Cases
-
-(ert-deftest test-org-refile-build-targets-normal-first-call-builds-cache ()
- "Test that first call builds cache from scratch.
-
-When cache is empty, function should:
-1. Scan directories for todo.org files
-2. Build refile targets list
-3. Populate cache
-4. Set cache timestamp"
- (test-org-refile-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern) '("/tmp/todo.org")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; Before call: cache empty
- (should (null cj/org-refile-targets-cache))
- (should (null cj/org-refile-targets-cache-time))
-
- ;; Build targets
- (cj/build-org-refile-targets)
-
- ;; After call: cache populated
- (should cj/org-refile-targets-cache)
- (should cj/org-refile-targets-cache-time)
- (should org-refile-targets)
-
- ;; Cache matches org-refile-targets
- (should (equal cj/org-refile-targets-cache org-refile-targets))
-
- ;; Contains base files (inbox, reference, schedule)
- (should (>= (length org-refile-targets) 3)))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-normal-second-call-uses-cache ()
- "Test that second call uses cache instead of rebuilding.
-
-When cache is valid (not expired):
-1. Should NOT scan directories again
-2. Should restore targets from cache
-3. Should NOT update cache timestamp"
- (test-org-refile-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- (setq scan-count (1+ scan-count))
- '("/tmp/todo.org")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; First call: builds cache
- (cj/build-org-refile-targets)
- (should (= scan-count 3)) ; 3 directories scanned
-
- (let ((cached-time cj/org-refile-targets-cache-time)
- (cached-targets cj/org-refile-targets-cache))
-
- ;; Second call: uses cache
- (cj/build-org-refile-targets)
-
- ;; Scan count unchanged (cache hit)
- (should (= scan-count 3))
-
- ;; Cache unchanged
- (should (equal cj/org-refile-targets-cache-time cached-time))
- (should (equal cj/org-refile-targets-cache cached-targets)))))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-normal-force-rebuild-bypasses-cache ()
- "Test that force-rebuild parameter bypasses cache.
-
-When force-rebuild is non-nil:
-1. Should ignore valid cache
-2. Should rebuild from scratch
-3. Should update cache with new data"
- (test-org-refile-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- (setq scan-count (1+ scan-count))
- (if (> scan-count 3)
- '("/tmp/todo.org" "/tmp/todo2.org") ; New file on rebuild
- '("/tmp/todo.org"))))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; First call: builds cache
- (cj/build-org-refile-targets)
- (let ((initial-count (length org-refile-targets)))
-
- ;; Force rebuild
- (cj/build-org-refile-targets 'force)
-
- ;; Scanned again (3 more directories)
- (should (= scan-count 6))
-
- ;; New targets include additional file
- (should (> (length org-refile-targets) initial-count)))))
- (test-org-refile-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-org-refile-build-targets-boundary-cache-expires-after-ttl ()
- "Test that cache expires after TTL period.
-
-When cache timestamp exceeds TTL:
-1. Should rebuild targets
-2. Should update cache timestamp
-3. Should rescan directories"
- (test-org-refile-setup)
- (unwind-protect
- (let ((scan-count 0))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- (setq scan-count (1+ scan-count))
- '("/tmp/todo.org")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; First call: builds cache
- (cj/build-org-refile-targets)
- (should (= scan-count 3))
-
- ;; Simulate cache expiration (set time to 2 hours ago)
- (setq cj/org-refile-targets-cache-time
- (- (float-time) (* 2 3600)))
-
- ;; Second call: cache expired, rebuild
- (cj/build-org-refile-targets)
-
- ;; Scanned again (cache was expired)
- (should (= scan-count 6))
-
- ;; Cache timestamp updated to current time
- (should (< (- (float-time) cj/org-refile-targets-cache-time) 1))))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-boundary-empty-directories-creates-minimal-targets ()
- "Test behavior when directories contain no todo.org files.
-
-When directory scans return empty:
-1. Should still create base targets (inbox, reference, schedule)
-2. Should not fail or error
-3. Should cache the minimal result"
- (test-org-refile-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern) nil)) ; No files found
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- (cj/build-org-refile-targets)
-
- ;; Should have base files only
- (should (= (length org-refile-targets) 3))
-
- ;; Cache should contain base files
- (should cj/org-refile-targets-cache)
- (should (= (length cj/org-refile-targets-cache) 3)))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-boundary-building-flag-set-during-build ()
- "Test that building flag is set during build and cleared after.
-
-During build:
-1. Flag should be set to prevent concurrent builds
-2. Flag should clear even if build fails
-3. Flag state should be consistent"
- (test-org-refile-setup)
- (unwind-protect
- (let ((flag-during-build nil))
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- ;; Capture flag state during directory scan
- (setq flag-during-build cj/org-refile-targets-building)
- '("/tmp/todo.org")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; Before build
- (should (null cj/org-refile-targets-building))
-
- ;; Build
- (cj/build-org-refile-targets)
-
- ;; Flag was set during build
- (should flag-during-build)
-
- ;; Flag cleared after build
- (should (null cj/org-refile-targets-building))))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-boundary-building-flag-clears-on-error ()
- "Test that building flag clears even if build errors.
-
-When build encounters error:
-1. Flag should still be cleared (unwind-protect)
-2. Prevents permanently locked state
-3. Next build can proceed"
- (test-org-refile-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- (error "Simulated scan failure")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; Build will error
- (should-error (cj/build-org-refile-targets))
-
- ;; Flag cleared despite error (unwind-protect)
- (should (null cj/org-refile-targets-building)))
- (test-org-refile-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-org-refile-build-targets-error-nil-cache-with-old-timestamp ()
- "Test handling of inconsistent state (nil cache but timestamp set).
-
-When cache is nil but timestamp exists:
-1. Should recognize cache as invalid
-2. Should rebuild targets
-3. Should set both cache and timestamp"
- (test-org-refile-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern) '("/tmp/todo.org")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; Set inconsistent state
- (setq cj/org-refile-targets-cache nil)
- (setq cj/org-refile-targets-cache-time (float-time))
-
- ;; Build should recognize invalid state
- (cj/build-org-refile-targets)
-
- ;; Cache now populated
- (should cj/org-refile-targets-cache)
- (should cj/org-refile-targets-cache-time)
- (should org-refile-targets))
- (test-org-refile-teardown)))
-
-(ert-deftest test-org-refile-build-targets-error-directory-scan-failure-propagates ()
- "Test that directory scan failures propagate as errors.
-
-When directory-files-recursively errors:
-1. Error should propagate to caller
-2. Cache should not be corrupted
-3. Building flag should clear"
- (test-org-refile-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'directory-files-recursively)
- (lambda (_dir _pattern)
- (error "Permission denied")))
- ((symbol-function 'fboundp) (lambda (_sym) nil)))
-
- ;; Should propagate error
- (should-error (cj/build-org-refile-targets))
-
- ;; Cache not corrupted (still nil)
- (should (null cj/org-refile-targets-cache))
-
- ;; Building flag cleared
- (should (null cj/org-refile-targets-building)))
- (test-org-refile-teardown)))
-
-(provide 'test-org-refile-build-targets)
-;;; test-org-refile-build-targets.el ends here
diff --git a/tests/test-org-roam-config-copy-todo-to-today.el b/tests/test-org-roam-config-copy-todo-to-today.el
deleted file mode 100644
index bcac5a26..00000000
--- a/tests/test-org-roam-config-copy-todo-to-today.el
+++ /dev/null
@@ -1,182 +0,0 @@
-;;; test-org-roam-config-copy-todo-to-today.el --- Tests for org-roam TODO completion hook -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the org-after-todo-state-change-hook configuration that copies
-;; completed tasks to daily org-roam nodes.
-;;
-;; The hook should trigger for ANY org-mode done state (DONE, CANCELLED, etc.),
-;; not just "DONE". This is verified by checking membership in org-done-keywords.
-;;
-;; The critical behavior being tested is that the hook is registered
-;; immediately when org-mode loads, NOT when org-roam loads (which happens
-;; lazily). This ensures tasks can be copied to dailies even before the user
-;; has invoked any org-roam commands.
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-org-roam-todo-hook-setup ()
- "Setup for org-roam todo hook tests."
- (cj/create-test-base-dir))
-
-(defun test-org-roam-todo-hook-teardown ()
- "Teardown for org-roam todo hook tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-org-roam-hook-registered-after-org-loads ()
- "The hook should be registered after org loads."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (progn
- (require 'org)
- (require 'org-roam-config)
- (should (consp org-after-todo-state-change-hook)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-calls-copy-function-on-done ()
- "The hook lambda should call copy function when state is DONE."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED")) ; Set done keywords for test
- (setq org-last-state nil) ; No previous state (new task)
- (setq org-state "DONE") ; Dynamic variable used by org-mode hooks
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-calls-copy-function-on-cancelled ()
- "The hook lambda should call copy function when state is CANCELLED."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED")) ; Set done keywords for test
- (setq org-last-state nil) ; No previous state (new task)
- (setq org-state "CANCELLED") ; Dynamic variable used by org-mode hooks
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-org-roam-hook-registered-before-org-roam-loads ()
- "The hook should be registered even if org-roam has not loaded yet."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (progn
- (require 'org)
- (require 'org-roam-config)
- (should (consp org-after-todo-state-change-hook))
- (should-not (featurep 'org-roam)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-calls-copy-on-todo-to-done ()
- "The hook should copy when transitioning FROM TODO TO DONE."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED"))
- (setq org-last-state "TODO") ; Previous state was TODO (non-done)
- (setq org-state "DONE") ; New state is DONE
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-calls-copy-on-in-progress-to-done ()
- "The hook should copy when transitioning FROM IN-PROGRESS TO DONE."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED"))
- (setq org-last-state "IN-PROGRESS") ; Previous state was IN-PROGRESS (non-done)
- (setq org-state "DONE") ; New state is DONE
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-calls-copy-on-waiting-to-cancelled ()
- "The hook should copy when transitioning FROM WAITING TO CANCELLED."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED"))
- (setq org-last-state "WAITING") ; Previous state was WAITING (non-done)
- (setq org-state "CANCELLED") ; New state is CANCELLED
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-ignores-done-to-cancelled ()
- "The hook should NOT copy when transitioning FROM DONE TO CANCELLED (both done)."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED"))
- (setq org-last-state "DONE") ; Previous state was DONE (already done)
- (setq org-state "CANCELLED") ; New state is CANCELLED (also done)
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should-not copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-ignores-todo-state ()
- "The hook should not copy when transitioning TO TODO state (non-done)."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED")) ; TODO is not in done keywords
- (setq org-state "TODO") ; Transitioning TO TODO
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should-not copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(ert-deftest test-org-roam-hook-ignores-in-progress-state ()
- "The hook should not copy when transitioning TO IN-PROGRESS state (non-done)."
- (test-org-roam-todo-hook-setup)
- (unwind-protect
- (let ((copy-function-called nil))
- (require 'org)
- (require 'org-roam-config)
- (setq org-done-keywords '("DONE" "CANCELLED")) ; IN-PROGRESS is not in done keywords
- (setq org-state "IN-PROGRESS") ; Transitioning TO IN-PROGRESS
- (cl-letf (((symbol-function 'cj/org-roam-copy-todo-to-today)
- (lambda () (setq copy-function-called t))))
- (run-hooks 'org-after-todo-state-change-hook)
- (should-not copy-function-called)))
- (test-org-roam-todo-hook-teardown)))
-
-(provide 'test-org-roam-config-copy-todo-to-today)
-;;; test-org-roam-config-copy-todo-to-today.el ends here
diff --git a/tests/test-org-roam-config-demote.el b/tests/test-org-roam-config-demote.el
deleted file mode 100644
index 98cc8244..00000000
--- a/tests/test-org-roam-config-demote.el
+++ /dev/null
@@ -1,183 +0,0 @@
-;;; test-org-roam-config-demote.el --- Tests for cj/--demote-org-subtree -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--demote-org-subtree function from org-roam-config.el
-;;
-;; This function demotes org subtree content from one level to another.
-;; All headings in the tree are adjusted proportionally, with a minimum level of 1.
-;;
-;; Examples:
-;; Input: "*** Heading\n**** Sub", from: 3, to: 1
-;; Output: "* Heading\n** Sub"
-;;
-;; Input: "** Heading\n*** Sub", from: 2, to: 1
-;; Output: "* Heading\n** Sub"
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-roam-config)
-
-;;; Test Helpers
-
-(defun test-demote (content from-level to-level)
- "Test cj/--demote-org-subtree on CONTENT.
-FROM-LEVEL is the current top level, TO-LEVEL is the desired top level.
-Returns the demoted content."
- (cj/--demote-org-subtree content from-level to-level))
-
-;;; Normal Cases - Single Heading
-
-(ert-deftest test-demote-level2-to-level1 ()
- "Should demote level 2 heading to level 1."
- (let ((result (test-demote "** Heading\n" 2 1)))
- (should (string= result "* Heading\n"))))
-
-(ert-deftest test-demote-level3-to-level1 ()
- "Should demote level 3 heading to level 1."
- (let ((result (test-demote "*** Heading\n" 3 1)))
- (should (string= result "* Heading\n"))))
-
-(ert-deftest test-demote-level4-to-level1 ()
- "Should demote level 4 heading to level 1."
- (let ((result (test-demote "**** Heading\n" 4 1)))
- (should (string= result "* Heading\n"))))
-
-(ert-deftest test-demote-level3-to-level2 ()
- "Should demote level 3 heading to level 2."
- (let ((result (test-demote "*** Heading\n" 3 2)))
- (should (string= result "** Heading\n"))))
-
-;;; Normal Cases - Multiple Headings at Same Level
-
-(ert-deftest test-demote-multiple-same-level ()
- "Should demote multiple headings at same level."
- (let ((result (test-demote "** First\n** Second\n** Third\n" 2 1)))
- (should (string= result "* First\n* Second\n* Third\n"))))
-
-;;; Normal Cases - Hierarchical Structure
-
-(ert-deftest test-demote-with-subheading ()
- "Should demote heading and subheading proportionally."
- (let ((result (test-demote "** Heading\n*** Subheading\n" 2 1)))
- (should (string= result "* Heading\n** Subheading\n"))))
-
-(ert-deftest test-demote-three-levels ()
- "Should demote three-level hierarchy."
- (let ((result (test-demote "** Main\n*** Sub\n**** SubSub\n" 2 1)))
- (should (string= result "* Main\n** Sub\n*** SubSub\n"))))
-
-(ert-deftest test-demote-complex-hierarchy ()
- "Should demote complex hierarchy maintaining relative structure."
- (let ((result (test-demote "*** Top\n**** Sub1\n***** Deep\n**** Sub2\n" 3 1)))
- (should (string= result "* Top\n** Sub1\n*** Deep\n** Sub2\n"))))
-
-;;; Normal Cases - With Content
-
-(ert-deftest test-demote-heading-with-text ()
- "Should demote heading preserving body text."
- (let ((result (test-demote "** Heading\nBody text\n" 2 1)))
- (should (string= result "* Heading\nBody text\n"))))
-
-(ert-deftest test-demote-with-properties ()
- "Should demote heading preserving properties."
- (let ((result (test-demote "** Heading\n:PROPERTIES:\n:ID: 123\n:END:\n" 2 1)))
- (should (string= result "* Heading\n:PROPERTIES:\n:ID: 123\n:END:\n"))))
-
-(ert-deftest test-demote-with-mixed-content ()
- "Should demote headings preserving all content."
- (let ((result (test-demote "** H1\nText\n*** H2\nMore text\n" 2 1)))
- (should (string= result "* H1\nText\n** H2\nMore text\n"))))
-
-;;; Boundary Cases - No Demotion Needed
-
-(ert-deftest test-demote-same-level ()
- "Should return content unchanged when from equals to."
- (let ((result (test-demote "* Heading\n" 1 1)))
- (should (string= result "* Heading\n"))))
-
-(ert-deftest test-demote-promote-ignored ()
- "Should return content unchanged when to > from (promotion)."
- (let ((result (test-demote "* Heading\n" 1 2)))
- (should (string= result "* Heading\n"))))
-
-;;; Boundary Cases - Minimum Level
-
-(ert-deftest test-demote-respects-minimum-level ()
- "Should not demote below level 1."
- (let ((result (test-demote "** Main\n*** Sub\n" 2 1)))
- (should (string= result "* Main\n** Sub\n"))
- ;; Sub went from 3 to 2, not below 1
- (should (string-match-p "^\\*\\* Sub" result))))
-
-(ert-deftest test-demote-deep-hierarchy-min-level ()
- "Should respect minimum level for deep hierarchies."
- (let ((result (test-demote "**** L4\n***** L5\n****** L6\n" 4 1)))
- (should (string= result "* L4\n** L5\n*** L6\n"))))
-
-;;; Boundary Cases - Empty and Edge Cases
-
-(ert-deftest test-demote-empty-string ()
- "Should handle empty string."
- (let ((result (test-demote "" 2 1)))
- (should (string= result ""))))
-
-(ert-deftest test-demote-no-headings ()
- "Should return non-heading content unchanged."
- (let ((result (test-demote "Just plain text\nNo headings here\n" 2 1)))
- (should (string= result "Just plain text\nNo headings here\n"))))
-
-(ert-deftest test-demote-heading-without-space ()
- "Should not match headings without space after stars."
- (let ((result (test-demote "**Not a heading\n** Real Heading\n" 2 1)))
- (should (string= result "**Not a heading\n* Real Heading\n"))))
-
-;;; Edge Cases - Special Heading Content
-
-(ert-deftest test-demote-heading-with-tags ()
- "Should demote heading preserving tags."
- (let ((result (test-demote "** Heading :tag1:tag2:\n" 2 1)))
- (should (string= result "* Heading :tag1:tag2:\n"))))
-
-(ert-deftest test-demote-heading-with-todo ()
- "Should demote heading preserving TODO keyword."
- (let ((result (test-demote "** TODO Task\n" 2 1)))
- (should (string= result "* TODO Task\n"))))
-
-(ert-deftest test-demote-heading-with-priority ()
- "Should demote heading preserving priority."
- (let ((result (test-demote "** [#A] Important\n" 2 1)))
- (should (string= result "* [#A] Important\n"))))
-
-;;; Edge Cases - Whitespace
-
-(ert-deftest test-demote-preserves-indentation ()
- "Should preserve indentation in body text."
- (let ((result (test-demote "** Heading\n Indented text\n" 2 1)))
- (should (string= result "* Heading\n Indented text\n"))))
-
-(ert-deftest test-demote-multiple-spaces-after-stars ()
- "Should handle multiple spaces after stars."
- (let ((result (test-demote "** Heading\n" 2 1)))
- (should (string= result "* Heading\n"))))
-
-;;; Edge Cases - Large Demotion
-
-(ert-deftest test-demote-large-level-difference ()
- "Should handle large level differences."
- (let ((result (test-demote "****** Level 6\n******* Level 7\n" 6 1)))
- (should (string= result "* Level 6\n** Level 7\n"))))
-
-(ert-deftest test-demote-to-level-2 ()
- "Should demote to level 2 when specified."
- (let ((result (test-demote "***** Level 5\n****** Level 6\n" 5 2)))
- (should (string= result "** Level 5\n*** Level 6\n"))))
-
-(provide 'test-org-roam-config-demote)
-;;; test-org-roam-config-demote.el ends here
diff --git a/tests/test-org-roam-config-format.el b/tests/test-org-roam-config-format.el
deleted file mode 100644
index e9378b7a..00000000
--- a/tests/test-org-roam-config-format.el
+++ /dev/null
@@ -1,151 +0,0 @@
-;;; test-org-roam-config-format.el --- Tests for cj/--format-roam-node -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--format-roam-node function from org-roam-config.el
-;;
-;; This function formats org-roam node file content with title, node-id, and body content.
-;; It creates a complete org-roam file with properties, title, category, and filetags.
-;;
-;; Example:
-;; Input: title: "My Note", node-id: "abc123", content: "* Content\n"
-;; Output: Full org-roam file with metadata and content
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-roam-config)
-
-;;; Test Helpers
-
-(defun test-format (title node-id content)
- "Test cj/--format-roam-node with TITLE, NODE-ID, and CONTENT.
-Returns the formatted file content."
- (cj/--format-roam-node title node-id content))
-
-;;; Normal Cases
-
-(ert-deftest test-format-simple-node ()
- "Should format simple node with all components."
- (let ((result (test-format "Test Title" "id-123" "* Content\n")))
- (should (string-match-p ":PROPERTIES:" result))
- (should (string-match-p ":ID: id-123" result))
- (should (string-match-p "#\\+TITLE: Test Title" result))
- (should (string-match-p "#\\+CATEGORY: Test Title" result))
- (should (string-match-p "#\\+FILETAGS: Topic" result))
- (should (string-match-p "\\* Content" result))))
-
-(ert-deftest test-format-properties-first ()
- "Should place properties at the beginning."
- (let ((result (test-format "Title" "id" "content")))
- (should (string-prefix-p ":PROPERTIES:\n" result))))
-
-(ert-deftest test-format-id-after-properties ()
- "Should place ID in properties block."
- (let ((result (test-format "Title" "test-id-456" "content")))
- (should (string-match-p ":PROPERTIES:\n:ID: test-id-456\n:END:" result))))
-
-(ert-deftest test-format-title-after-properties ()
- "Should place title after properties."
- (let ((result (test-format "My Title" "id" "content")))
- (should (string-match-p ":END:\n#\\+TITLE: My Title\n" result))))
-
-(ert-deftest test-format-category-matches-title ()
- "Should set category to match title."
- (let ((result (test-format "Project Name" "id" "content")))
- (should (string-match-p "#\\+TITLE: Project Name\n#\\+CATEGORY: Project Name\n" result))))
-
-(ert-deftest test-format-filetags-topic ()
- "Should set filetags to Topic."
- (let ((result (test-format "Title" "id" "content")))
- (should (string-match-p "#\\+FILETAGS: Topic\n" result))))
-
-(ert-deftest test-format-content-at-end ()
- "Should place content after metadata."
- (let ((result (test-format "Title" "id" "* Heading\nBody text\n")))
- (should (string-suffix-p "* Heading\nBody text\n" result))))
-
-;;; Edge Cases - Various Titles
-
-(ert-deftest test-format-title-with-spaces ()
- "Should handle title with spaces."
- (let ((result (test-format "Multi Word Title" "id" "content")))
- (should (string-match-p "#\\+TITLE: Multi Word Title" result))
- (should (string-match-p "#\\+CATEGORY: Multi Word Title" result))))
-
-(ert-deftest test-format-title-with-punctuation ()
- "Should handle title with punctuation."
- (let ((result (test-format "Title: With, Punctuation!" "id" "content")))
- (should (string-match-p "#\\+TITLE: Title: With, Punctuation!" result))))
-
-(ert-deftest test-format-title-with-numbers ()
- "Should handle title with numbers."
- (let ((result (test-format "Version 2.0" "id" "content")))
- (should (string-match-p "#\\+TITLE: Version 2\\.0" result))))
-
-;;; Edge Cases - Various Node IDs
-
-(ert-deftest test-format-uuid-style-id ()
- "Should handle UUID-style ID."
- (let ((result (test-format "Title" "a1b2c3d4-e5f6-7890-abcd-ef1234567890" "content")))
- (should (string-match-p ":ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890" result))))
-
-(ert-deftest test-format-short-id ()
- "Should handle short ID."
- (let ((result (test-format "Title" "1" "content")))
- (should (string-match-p ":ID: 1" result))))
-
-(ert-deftest test-format-long-id ()
- "Should handle long ID."
- (let* ((long-id (make-string 100 ?a))
- (result (test-format "Title" long-id "content")))
- (should (string-match-p (concat ":ID: " long-id) result))))
-
-;;; Edge Cases - Various Content
-
-(ert-deftest test-format-empty-content ()
- "Should handle empty content."
- (let ((result (test-format "Title" "id" "")))
- (should (string-suffix-p "#+FILETAGS: Topic\n\n" result))))
-
-(ert-deftest test-format-multiline-content ()
- "Should handle multiline content."
- (let ((result (test-format "Title" "id" "* H1\nText\n** H2\nMore\n")))
- (should (string-suffix-p "* H1\nText\n** H2\nMore\n" result))))
-
-(ert-deftest test-format-content-with-properties ()
- "Should handle content that already has properties."
- (let ((result (test-format "Title" "id" "* Heading\n:PROPERTIES:\n:CUSTOM: value\n:END:\n")))
- (should (string-match-p ":CUSTOM: value" result))))
-
-;;; Integration Tests - Structure
-
-(ert-deftest test-format-complete-structure ()
- "Should create proper org-roam file structure."
- (let ((result (test-format "My Note" "abc-123" "* Content\n")))
- ;; Check order of components
- (should (< (string-match ":PROPERTIES:" result)
- (string-match ":ID:" result)))
- (should (< (string-match ":ID:" result)
- (string-match ":END:" result)))
- (should (< (string-match ":END:" result)
- (string-match "#\\+TITLE:" result)))
- (should (< (string-match "#\\+TITLE:" result)
- (string-match "#\\+CATEGORY:" result)))
- (should (< (string-match "#\\+CATEGORY:" result)
- (string-match "#\\+FILETAGS:" result)))
- (should (< (string-match "#\\+FILETAGS:" result)
- (string-match "\\* Content" result)))))
-
-(ert-deftest test-format-double-newline-after-metadata ()
- "Should have double newline between metadata and content."
- (let ((result (test-format "Title" "id" "* Content")))
- (should (string-match-p "#\\+FILETAGS: Topic\n\n\\* Content" result))))
-
-(provide 'test-org-roam-config-format)
-;;; test-org-roam-config-format.el ends here
diff --git a/tests/test-org-roam-config-link-description.el b/tests/test-org-roam-config-link-description.el
deleted file mode 100644
index 06321b8f..00000000
--- a/tests/test-org-roam-config-link-description.el
+++ /dev/null
@@ -1,188 +0,0 @@
-;;; test-org-roam-config-link-description.el --- Tests for cj/org-link-get-description -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/org-link-get-description function from org-roam-config.el
-;;
-;; This function extracts the description from an org link, or returns the text unchanged.
-;; If TEXT contains an org link like [[url][description]], it returns description.
-;; If TEXT contains multiple links, only the first one is processed.
-;; Otherwise it returns TEXT unchanged.
-;;
-;; Examples:
-;; Input: "[[https://example.com][Example Site]]"
-;; Output: "Example Site"
-;;
-;; Input: "[[https://example.com]]"
-;; Output: "https://example.com"
-;;
-;; Input: "Plain text"
-;; Output: "Plain text"
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-roam-config)
-
-;;; Test Helpers
-
-(defun test-link-description (text)
- "Test cj/org-link-get-description on TEXT.
-Returns the extracted description or text unchanged."
- (cj/org-link-get-description text))
-
-;;; Normal Cases - Link with Description
-
-(ert-deftest test-link-with-description ()
- "Should extract description from link with description."
- (let ((result (test-link-description "[[https://example.com][Example Site]]")))
- (should (string= result "Example Site"))))
-
-(ert-deftest test-link-with-multiword-description ()
- "Should extract multi-word description."
- (let ((result (test-link-description "[[url][Multiple Word Description]]")))
- (should (string= result "Multiple Word Description"))))
-
-(ert-deftest test-link-with-special-chars-in-description ()
- "Should extract description with special characters."
- (let ((result (test-link-description "[[url][Description: with, punctuation!]]")))
- (should (string= result "Description: with, punctuation!"))))
-
-(ert-deftest test-link-file-path-with-description ()
- "Should extract description from file link."
- (let ((result (test-link-description "[[file:~/document.pdf][My Document]]")))
- (should (string= result "My Document"))))
-
-(ert-deftest test-link-with-numbers-in-description ()
- "Should extract description containing numbers."
- (let ((result (test-link-description "[[url][Chapter 42]]")))
- (should (string= result "Chapter 42"))))
-
-;;; Normal Cases - Link without Description
-
-(ert-deftest test-link-without-description-url ()
- "Should return URL when no description is present."
- (let ((result (test-link-description "[[https://example.com]]")))
- (should (string= result "https://example.com"))))
-
-(ert-deftest test-link-without-description-file ()
- "Should return file path when no description."
- (let ((result (test-link-description "[[file:~/notes.org]]")))
- (should (string= result "file:~/notes.org"))))
-
-(ert-deftest test-link-without-description-id ()
- "Should return ID when no description."
- (let ((result (test-link-description "[[id:abc123]]")))
- (should (string= result "id:abc123"))))
-
-;;; Normal Cases - No Link
-
-(ert-deftest test-plain-text ()
- "Should return plain text unchanged."
- (let ((result (test-link-description "Plain text without link")))
- (should (string= result "Plain text without link"))))
-
-(ert-deftest test-text-with-brackets-but-not-link ()
- "Should return text with single brackets unchanged."
- (let ((result (test-link-description "Text [with] brackets")))
- (should (string= result "Text [with] brackets"))))
-
-(ert-deftest test-text-with-partial-link-syntax ()
- "Should return text with partial link syntax unchanged."
- (let ((result (test-link-description "[[incomplete link")))
- (should (string= result "[[incomplete link"))))
-
-;;; Boundary Cases - Multiple Links
-
-(ert-deftest test-multiple-links-extracts-first ()
- "Should extract description from first link only."
- (let ((result (test-link-description "[[url1][First]] and [[url2][Second]]")))
- (should (string= result "First"))))
-
-(ert-deftest test-multiple-links-first-has-no-description ()
- "Should extract URL from first link when it has no description."
- (let ((result (test-link-description "[[url1]] and [[url2][Second]]")))
- (should (string= result "url1"))))
-
-;;; Boundary Cases - Empty and Edge Cases
-
-(ert-deftest test-empty-string ()
- "Should return empty string unchanged."
- (let ((result (test-link-description "")))
- (should (string= result ""))))
-
-(ert-deftest test-link-with-empty-description ()
- "Should return text unchanged when description brackets are empty."
- (let ((result (test-link-description "[[https://example.com][]]")))
- ;; Regex requires at least one char in description, so no match
- (should (string= result "[[https://example.com][]]"))))
-
-(ert-deftest test-link-with-empty-url ()
- "Should return text unchanged when link is completely empty."
- (let ((result (test-link-description "[[]]")))
- ;; Regex requires at least one char in URL, so no match, returns unchanged
- (should (string= result "[[]]"))))
-
-(ert-deftest test-link-with-empty-url-and-description ()
- "Should handle completely empty link."
- (let ((result (test-link-description "[][]")))
- (should (string= result "[][]"))))
-
-;;; Edge Cases - Special Link Types
-
-(ert-deftest test-internal-link ()
- "Should extract description from internal link."
- (let ((result (test-link-description "[[*Heading][My Heading]]")))
- (should (string= result "My Heading"))))
-
-(ert-deftest test-internal-link-without-description ()
- "Should return heading target from internal link without description."
- (let ((result (test-link-description "[[*Heading]]")))
- (should (string= result "*Heading"))))
-
-(ert-deftest test-custom-id-link ()
- "Should handle custom ID links."
- (let ((result (test-link-description "[[#custom-id][Custom Section]]")))
- (should (string= result "Custom Section"))))
-
-;;; Edge Cases - Link with Surrounding Text
-
-(ert-deftest test-link-with-prefix-text ()
- "Should extract description from link with prefix text."
- (let ((result (test-link-description "See [[url][documentation]] for details")))
- (should (string= result "documentation"))))
-
-(ert-deftest test-link-at-start ()
- "Should extract description from link at start of text."
- (let ((result (test-link-description "[[url][Link]] at beginning")))
- (should (string= result "Link"))))
-
-(ert-deftest test-link-at-end ()
- "Should extract description from link at end of text."
- (let ((result (test-link-description "Text with [[url][link]]")))
- (should (string= result "link"))))
-
-;;; Edge Cases - Special Characters in URL
-
-(ert-deftest test-link-with-query-params ()
- "Should handle URL with query parameters."
- (let ((result (test-link-description "[[https://example.com?q=test&foo=bar][Search]]")))
- (should (string= result "Search"))))
-
-(ert-deftest test-link-with-anchor ()
- "Should handle URL with anchor."
- (let ((result (test-link-description "[[https://example.com#section][Section]]")))
- (should (string= result "Section"))))
-
-(ert-deftest test-link-with-spaces-in-description ()
- "Should preserve spaces in description."
- (let ((result (test-link-description "[[url][Multiple Spaces]]")))
- (should (string= result "Multiple Spaces"))))
-
-(provide 'test-org-roam-config-link-description)
-;;; test-org-roam-config-link-description.el ends here
diff --git a/tests/test-org-roam-config-slug.el b/tests/test-org-roam-config-slug.el
deleted file mode 100644
index eb3149dd..00000000
--- a/tests/test-org-roam-config-slug.el
+++ /dev/null
@@ -1,223 +0,0 @@
-;;; test-org-roam-config-slug.el --- Tests for cj/--generate-roam-slug -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--generate-roam-slug function from org-roam-config.el
-;;
-;; This function converts a title to a filename-safe slug by:
-;; 1. Converting to lowercase
-;; 2. Replacing non-alphanumeric characters with hyphens
-;; 3. Removing leading and trailing hyphens
-;;
-;; Examples:
-;; Input: "My Project Name"
-;; Output: "my-project-name"
-;;
-;; Input: "Hello, World!"
-;; Output: "hello-world"
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-roam-config)
-
-;;; Test Helpers
-
-(defun test-slug (title)
- "Test cj/--generate-roam-slug on TITLE.
-Returns the slugified string."
- (cj/--generate-roam-slug title))
-
-;;; Normal Cases - Simple Titles
-
-(ert-deftest test-slug-simple-word ()
- "Should return lowercase simple word."
- (let ((result (test-slug "Hello")))
- (should (string= result "hello"))))
-
-(ert-deftest test-slug-multiple-words ()
- "Should replace spaces with hyphens."
- (let ((result (test-slug "My Project Name")))
- (should (string= result "my-project-name"))))
-
-(ert-deftest test-slug-already-lowercase ()
- "Should handle already lowercase text."
- (let ((result (test-slug "simple")))
- (should (string= result "simple"))))
-
-(ert-deftest test-slug-mixed-case ()
- "Should convert mixed case to lowercase."
- (let ((result (test-slug "MixedCaseTitle")))
- (should (string= result "mixedcasetitle"))))
-
-;;; Normal Cases - Punctuation
-
-(ert-deftest test-slug-with-comma ()
- "Should remove commas."
- (let ((result (test-slug "Hello, World")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-slug-with-period ()
- "Should remove periods."
- (let ((result (test-slug "Version 2.0")))
- (should (string= result "version-2-0"))))
-
-(ert-deftest test-slug-with-exclamation ()
- "Should remove exclamation marks."
- (let ((result (test-slug "Hello World!")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-slug-with-question ()
- "Should remove question marks."
- (let ((result (test-slug "What Is This?")))
- (should (string= result "what-is-this"))))
-
-(ert-deftest test-slug-with-colon ()
- "Should remove colons."
- (let ((result (test-slug "Note: Important")))
- (should (string= result "note-important"))))
-
-(ert-deftest test-slug-with-parentheses ()
- "Should remove parentheses."
- (let ((result (test-slug "Item (copy)")))
- (should (string= result "item-copy"))))
-
-;;; Normal Cases - Numbers
-
-(ert-deftest test-slug-with-numbers ()
- "Should preserve numbers."
- (let ((result (test-slug "Chapter 42")))
- (should (string= result "chapter-42"))))
-
-(ert-deftest test-slug-only-numbers ()
- "Should handle titles with only numbers."
- (let ((result (test-slug "123")))
- (should (string= result "123"))))
-
-(ert-deftest test-slug-mixed-alphanumeric ()
- "Should preserve alphanumeric characters."
- (let ((result (test-slug "Test123ABC")))
- (should (string= result "test123abc"))))
-
-;;; Boundary Cases - Multiple Consecutive Special Chars
-
-(ert-deftest test-slug-multiple-spaces ()
- "Should collapse multiple spaces into single hyphen."
- (let ((result (test-slug "Hello World")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-slug-mixed-punctuation ()
- "Should collapse mixed punctuation into single hyphen."
- (let ((result (test-slug "Hello, ... World!")))
- (should (string= result "hello-world"))))
-
-(ert-deftest test-slug-consecutive-hyphens ()
- "Should collapse consecutive hyphens."
- (let ((result (test-slug "Hello---World")))
- (should (string= result "hello-world"))))
-
-;;; Boundary Cases - Leading/Trailing Special Chars
-
-(ert-deftest test-slug-leading-space ()
- "Should remove leading hyphen from leading space."
- (let ((result (test-slug " Hello")))
- (should (string= result "hello"))))
-
-(ert-deftest test-slug-trailing-space ()
- "Should remove trailing hyphen from trailing space."
- (let ((result (test-slug "Hello ")))
- (should (string= result "hello"))))
-
-(ert-deftest test-slug-leading-punctuation ()
- "Should remove leading hyphen from leading punctuation."
- (let ((result (test-slug "...Hello")))
- (should (string= result "hello"))))
-
-(ert-deftest test-slug-trailing-punctuation ()
- "Should remove trailing hyphen from trailing punctuation."
- (let ((result (test-slug "Hello!!!")))
- (should (string= result "hello"))))
-
-(ert-deftest test-slug-leading-and-trailing ()
- "Should remove both leading and trailing hyphens."
- (let ((result (test-slug " Hello World ")))
- (should (string= result "hello-world"))))
-
-;;; Boundary Cases - Empty and Short
-
-(ert-deftest test-slug-empty-string ()
- "Should return empty string for empty input."
- (let ((result (test-slug "")))
- (should (string= result ""))))
-
-(ert-deftest test-slug-only-punctuation ()
- "Should return empty string for only punctuation."
- (let ((result (test-slug "!!!")))
- (should (string= result ""))))
-
-(ert-deftest test-slug-only-spaces ()
- "Should return empty string for only spaces."
- (let ((result (test-slug " ")))
- (should (string= result ""))))
-
-(ert-deftest test-slug-single-char ()
- "Should handle single character."
- (let ((result (test-slug "A")))
- (should (string= result "a"))))
-
-;;; Edge Cases - Special Characters
-
-(ert-deftest test-slug-with-underscore ()
- "Should replace underscores with hyphens."
- (let ((result (test-slug "my_variable_name")))
- (should (string= result "my-variable-name"))))
-
-(ert-deftest test-slug-with-slash ()
- "Should remove slashes."
- (let ((result (test-slug "path/to/file")))
- (should (string= result "path-to-file"))))
-
-(ert-deftest test-slug-with-at-sign ()
- "Should remove at signs."
- (let ((result (test-slug "user@example")))
- (should (string= result "user-example"))))
-
-(ert-deftest test-slug-with-hash ()
- "Should remove hash symbols."
- (let ((result (test-slug "#hashtag")))
- (should (string= result "hashtag"))))
-
-(ert-deftest test-slug-with-dollar ()
- "Should remove dollar signs."
- (let ((result (test-slug "$price")))
- (should (string= result "price"))))
-
-;;; Edge Cases - Unicode (if supported)
-
-(ert-deftest test-slug-with-unicode ()
- "Should remove unicode characters."
- (let ((result (test-slug "Café")))
- (should (string= result "caf"))))
-
-(ert-deftest test-slug-with-emoji ()
- "Should remove emoji."
- (let ((result (test-slug "Hello 😀 World")))
- (should (string= result "hello-world"))))
-
-;;; Edge Cases - Long Titles
-
-(ert-deftest test-slug-very-long-title ()
- "Should handle very long titles."
- (let* ((long-title (mapconcat #'identity (make-list 20 "word") " "))
- (result (test-slug long-title)))
- (should (string-prefix-p "word-" result))
- (should (string-suffix-p "-word" result))
- (should (not (string-match-p " " result)))))
-
-(provide 'test-org-roam-config-slug)
-;;; test-org-roam-config-slug.el ends here
diff --git a/tests/test-org-sort-by-todo-and-priority.el b/tests/test-org-sort-by-todo-and-priority.el
deleted file mode 100644
index 873f37c2..00000000
--- a/tests/test-org-sort-by-todo-and-priority.el
+++ /dev/null
@@ -1,283 +0,0 @@
-;;; test-org-sort-by-todo-and-priority.el --- Tests for cj/org-sort-by-todo-and-priority -*- lexical-binding: t; -*-
-
-;;; Commentary:
-
-;; Unit tests for cj/org-sort-by-todo-and-priority function.
-;; Tests multi-level sorting: TODO status (TODO before DONE) and priority (A before B before C).
-;;
-;; Testing approach:
-;; - Use real org-mode buffers (don't mock org-sort-entries)
-;; - Trust org-mode framework works correctly
-;; - Test OUR integration logic: calling org-sort-entries twice in correct order
-;; - Verify final sort order matches expected TODO/priority combination
-;;
-;; The function uses stable sorting:
-;; 1. First sort by priority (A, B, C, D, none)
-;; 2. Then sort by TODO status (TODO before DONE)
-;; Result: Priority order preserved within each TODO state group
-
-;;; Code:
-
-(require 'ert)
-(require 'org)
-(require 'org-config) ; Defines cj/org-sort-by-todo-and-priority
-
-;;; Test Helpers
-
-(defun test-org-sort-by-todo-and-priority--create-buffer (content)
- "Create a temporary org-mode buffer with CONTENT.
-Returns the buffer object.
-Disables org-mode hooks to avoid missing package dependencies in batch mode."
- (let ((buf (generate-new-buffer "*test-org-sort*")))
- (with-current-buffer buf
- ;; Disable hooks to prevent org-superstar and other package loads
- (let ((org-mode-hook nil))
- (org-mode))
- (insert content)
- (goto-char (point-min)))
- buf))
-
-(defun test-org-sort-by-todo-and-priority--get-entry-order (buffer)
- "Extract ordered list of TODO states and priorities from BUFFER.
-Returns list of strings like \"TODO [#A]\" or \"DONE\" for each heading."
- (with-current-buffer buffer
- (goto-char (point-min))
- (let (entries)
- (org-map-entries
- (lambda ()
- (let* ((todo-state (org-get-todo-state))
- ;; Get heading: no-tags, no-todo, KEEP priority, no-comment
- (heading (org-get-heading t t nil t))
- ;; Extract priority cookie from heading text
- (priority (when (string-match "\\[#\\([A-Z]\\)\\]" heading)
- (match-string 1 heading))))
- (push (if priority
- (format "%s [#%s]" (or todo-state "") priority)
- (or todo-state ""))
- entries)))
- nil 'tree)
- (nreverse entries))))
-
-(defun test-org-sort-by-todo-and-priority--sort-children (buffer)
- "Position cursor on parent heading in BUFFER and sort its children.
-Moves to first * heading (Parent) and calls sort function to sort children."
- (with-current-buffer buffer
- (goto-char (point-min))
- (when (re-search-forward "^\\* " nil t)
- (beginning-of-line)
- (cj/org-sort-by-todo-and-priority))))
-
-;;; Normal Cases
-
-(ert-deftest test-org-sort-by-todo-and-priority-normal-mixed-todo-done-sorts-correctly ()
- "Test mixed TODO and DONE entries with various priorities sort correctly.
-
-Input: TODO [#A], DONE [#B], TODO [#C], DONE [#A]
-Expected: TODO [#A], TODO [#C], DONE [#A], DONE [#B]"
- (let* ((content "* Parent
-** TODO [#A] First task
-** DONE [#B] Second task
-** TODO [#C] Third task
-** DONE [#A] Fourth task
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]" "TODO [#C]" "DONE [#A]" "DONE [#B]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-normal-multiple-todos-sorts-by-priority ()
- "Test multiple TODO entries sort by priority A before B before C.
-
-Input: TODO [#C], TODO [#A], TODO [#B]
-Expected: TODO [#A], TODO [#B], TODO [#C]"
- (let* ((content "* Parent
-** TODO [#C] Task C
-** TODO [#A] Task A
-** TODO [#B] Task B
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]" "TODO [#B]" "TODO [#C]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-normal-multiple-dones-sorts-by-priority ()
- "Test multiple DONE entries sort by priority A before B before C.
-
-Input: DONE [#C], DONE [#A], DONE [#B]
-Expected: DONE [#A], DONE [#B], DONE [#C]"
- (let* ((content "* Parent
-** DONE [#C] Done C
-** DONE [#A] Done A
-** DONE [#B] Done B
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "DONE [#A]" "DONE [#B]" "DONE [#C]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-normal-same-priority-todo-before-done ()
- "Test entries with same priority sort TODO before DONE.
-
-Input: DONE [#A], TODO [#A]
-Expected: TODO [#A], DONE [#A]"
- (let* ((content "* Parent
-** DONE [#A] Done task
-** TODO [#A] Todo task
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]" "DONE [#A]")))))
- (kill-buffer buf))))
-
-;;; Boundary Cases
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-empty-section-no-error ()
- "Test sorting empty section does not signal error.
-
-Input: Heading with no children
-Expected: No error, no change"
- (let* ((content "* Parent\n")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (with-current-buffer buf
- (goto-char (point-min))
- (should-not (condition-case err
- (progn
- (cj/org-sort-by-todo-and-priority)
- nil)
- (error err))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-single-todo-no-change ()
- "Test sorting single TODO entry does not change order.
-
-Input: Single TODO [#A]
-Expected: Same order (no change)"
- (let* ((content "* Parent
-** TODO [#A] Only task
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-single-done-no-change ()
- "Test sorting single DONE entry does not change order.
-
-Input: Single DONE [#B]
-Expected: Same order (no change)"
- (let* ((content "* Parent
-** DONE [#B] Only task
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "DONE [#B]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-all-todos-sorts-by-priority ()
- "Test all TODO entries sort by priority only.
-
-Input: TODO [#C], TODO [#A], TODO [#B]
-Expected: TODO [#A], TODO [#B], TODO [#C]"
- (let* ((content "* Parent
-** TODO [#C] Task C
-** TODO [#A] Task A
-** TODO [#B] Task B
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]" "TODO [#B]" "TODO [#C]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-all-dones-sorts-by-priority ()
- "Test all DONE entries sort by priority only.
-
-Input: DONE [#B], DONE [#D], DONE [#A]
-Expected: DONE [#A], DONE [#B], DONE [#D]"
- (let* ((content "* Parent
-** DONE [#B] Done B
-** DONE [#D] Done D
-** DONE [#A] Done A
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "DONE [#A]" "DONE [#B]" "DONE [#D]")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-no-priorities-sorts-by-todo ()
- "Test entries without priorities sort by TODO status only.
-
-Input: TODO (no priority), DONE (no priority), TODO (no priority)
-Expected: TODO, TODO, DONE"
- (let* ((content "* Parent
-** TODO Task 1
-** DONE Task 2
-** TODO Task 3
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO" "TODO" "DONE")))))
- (kill-buffer buf))))
-
-(ert-deftest test-org-sort-by-todo-and-priority-boundary-unprioritized-after-prioritized ()
- "Test unprioritized entries appear after prioritized within TODO/DONE groups.
-
-Input: TODO (no priority), TODO [#A], DONE [#B], DONE (no priority)
-Expected: TODO [#A], TODO (no priority), DONE [#B], DONE (no priority)"
- (let* ((content "* Parent
-** TODO Task no priority
-** TODO [#A] Task A
-** DONE [#B] Done B
-** DONE Done no priority
-")
- (buf (test-org-sort-by-todo-and-priority--create-buffer content)))
- (unwind-protect
- (progn
- (test-org-sort-by-todo-and-priority--sort-children buf)
- (let ((order (test-org-sort-by-todo-and-priority--get-entry-order buf)))
- (should (equal order '("" "TODO [#A]" "TODO" "DONE [#B]" "DONE")))))
- (kill-buffer buf))))
-
-;;; Error Cases
-
-(ert-deftest test-org-sort-by-todo-and-priority-error-non-org-buffer-signals-error ()
- "Test calling in non-org-mode buffer signals user-error.
-
-Input: fundamental-mode buffer
-Expected: user-error"
- (let ((buf (generate-new-buffer "*test-non-org*")))
- (unwind-protect
- (with-current-buffer buf
- (fundamental-mode)
- (should-error (cj/org-sort-by-todo-and-priority) :type 'user-error))
- (kill-buffer buf))))
-
-(provide 'test-org-sort-by-todo-and-priority)
-;;; test-org-sort-by-todo-and-priority.el ends here
diff --git a/tests/test-org-webclipper-process.el b/tests/test-org-webclipper-process.el
deleted file mode 100644
index 9a25ef5c..00000000
--- a/tests/test-org-webclipper-process.el
+++ /dev/null
@@ -1,210 +0,0 @@
-;;; test-org-webclipper-process.el --- Tests for cj/--process-webclip-content -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--process-webclip-content function from org-webclipper.el
-;;
-;; This function processes webclipped org-mode content by:
-;; 1. Removing the first top-level heading
-;; 2. Removing any initial blank lines
-;; 3. Demoting all remaining headings by one level
-;;
-;; Examples:
-;; Input: "* Title\nContent\n** Sub\n"
-;; Output: "Content\n*** Sub\n"
-;;
-;; Input: "* Title\n\n\n** Sub\n"
-;; Output: "*** Sub\n"
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Now load the actual production module
-(require 'org-webclipper)
-
-;;; Test Helpers
-
-(defun test-process-webclip (content)
- "Test cj/--process-webclip-content on CONTENT.
-Returns the processed content."
- (cj/--process-webclip-content content))
-
-;;; Normal Cases - Single Heading Removal
-
-(ert-deftest test-process-removes-first-heading ()
- "Should remove the first top-level heading."
- (let ((result (test-process-webclip "* Title\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-removes-heading-with-text ()
- "Should remove first heading preserving body text."
- (let ((result (test-process-webclip "* Page Title\nParagraph text\n")))
- (should (string= result "Paragraph text\n"))))
-
-(ert-deftest test-process-removes-heading-with-tags ()
- "Should remove first heading even with tags."
- (let ((result (test-process-webclip "* Title :tag1:tag2:\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-removes-heading-with-todo ()
- "Should remove first heading even with TODO keyword."
- (let ((result (test-process-webclip "* TODO Task\nContent\n")))
- (should (string= result "Content\n"))))
-
-;;; Normal Cases - Blank Line Removal
-
-(ert-deftest test-process-removes-single-blank-line ()
- "Should remove single blank line after heading removal."
- (let ((result (test-process-webclip "* Title\n\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-removes-multiple-blank-lines ()
- "Should remove multiple blank lines after heading removal."
- (let ((result (test-process-webclip "* Title\n\n\n\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-removes-blank-lines-with-spaces ()
- "Should remove blank lines that contain only spaces."
- (let ((result (test-process-webclip "* Title\n \n\t\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-preserves-blank-lines-in-content ()
- "Should preserve blank lines within the content."
- (let ((result (test-process-webclip "* Title\nPara 1\n\nPara 2\n")))
- (should (string= result "Para 1\n\nPara 2\n"))))
-
-;;; Normal Cases - Heading Demotion
-
-(ert-deftest test-process-demotes-second-level ()
- "Should demote level 2 heading to level 3."
- (let ((result (test-process-webclip "* Title\n** Section\n")))
- (should (string= result "*** Section\n"))))
-
-(ert-deftest test-process-demotes-third-level ()
- "Should demote level 3 heading to level 4."
- (let ((result (test-process-webclip "* Title\n*** Subsection\n")))
- (should (string= result "**** Subsection\n"))))
-
-(ert-deftest test-process-demotes-multiple-headings ()
- "Should demote all headings in the content."
- (let ((result (test-process-webclip "* Title\n** Section 1\n** Section 2\n")))
- (should (string= result "*** Section 1\n*** Section 2\n"))))
-
-(ert-deftest test-process-demotes-nested-hierarchy ()
- "Should demote nested heading structure."
- (let ((result (test-process-webclip "* Title\n** Section\n*** Subsection\n")))
- (should (string= result "*** Section\n**** Subsection\n"))))
-
-;;; Normal Cases - Combined Processing
-
-(ert-deftest test-process-full-workflow ()
- "Should remove heading, blank lines, and demote remaining headings."
- (let ((result (test-process-webclip "* Article Title\n\n** Introduction\nText\n** Conclusion\n")))
- (should (string= result "*** Introduction\nText\n*** Conclusion\n"))))
-
-(ert-deftest test-process-with-properties ()
- "Should preserve properties in demoted headings."
- (let ((result (test-process-webclip "* Title\n** Heading\n:PROPERTIES:\n:ID: 123\n:END:\n")))
- (should (string= result "*** Heading\n:PROPERTIES:\n:ID: 123\n:END:\n"))))
-
-(ert-deftest test-process-with-mixed-content ()
- "Should handle mixed text and headings."
- (let ((result (test-process-webclip "* Title\nIntro text\n** Section\nBody text\n")))
- (should (string= result "Intro text\n*** Section\nBody text\n"))))
-
-;;; Edge Cases - Empty and Minimal Content
-
-(ert-deftest test-process-empty-string ()
- "Should return empty string for empty input."
- (let ((result (test-process-webclip "")))
- (should (string= result ""))))
-
-(ert-deftest test-process-only-heading ()
- "Should return empty string when only first heading present."
- (let ((result (test-process-webclip "* Title\n")))
- (should (string= result ""))))
-
-(ert-deftest test-process-only-blank-lines ()
- "Should return empty string for only blank lines after heading."
- (let ((result (test-process-webclip "* Title\n\n\n")))
- (should (string= result ""))))
-
-(ert-deftest test-process-no-heading ()
- "Should handle content without any heading."
- (let ((result (test-process-webclip "Just plain text\n")))
- (should (string= result "Just plain text\n"))))
-
-(ert-deftest test-process-heading-no-newline ()
- "Should demote heading without trailing newline (doesn't match removal pattern)."
- (let ((result (test-process-webclip "* Title")))
- (should (string= result "** Title"))))
-
-;;; Edge Cases - Heading Variations
-
-(ert-deftest test-process-heading-without-space ()
- "Should not match heading without space after stars."
- (let ((result (test-process-webclip "*Title\nContent\n")))
- (should (string= result "*Title\nContent\n"))))
-
-(ert-deftest test-process-multiple-top-level-headings ()
- "Should only remove first top-level heading."
- (let ((result (test-process-webclip "* Title 1\n* Title 2\n")))
- (should (string= result "** Title 2\n"))))
-
-(ert-deftest test-process-heading-with-priority ()
- "Should remove heading with priority marker."
- (let ((result (test-process-webclip "* [#A] Important\nContent\n")))
- (should (string= result "Content\n"))))
-
-(ert-deftest test-process-heading-with-links ()
- "Should remove heading containing links."
- (let ((result (test-process-webclip "* [[url][Link Title]]\nContent\n")))
- (should (string= result "Content\n"))))
-
-;;; Edge Cases - Special Content
-
-(ert-deftest test-process-preserves-lists ()
- "Should preserve list formatting."
- (let ((result (test-process-webclip "* Title\n- Item 1\n- Item 2\n")))
- (should (string= result "- Item 1\n- Item 2\n"))))
-
-(ert-deftest test-process-preserves-code-blocks ()
- "Should preserve code block content."
- (let ((result (test-process-webclip "* Title\n#+BEGIN_SRC python\nprint('hi')\n#+END_SRC\n")))
- (should (string= result "#+BEGIN_SRC python\nprint('hi')\n#+END_SRC\n"))))
-
-(ert-deftest test-process-preserves-tables ()
- "Should preserve org table content."
- (let ((result (test-process-webclip "* Title\n| A | B |\n| 1 | 2 |\n")))
- (should (string= result "| A | B |\n| 1 | 2 |\n"))))
-
-;;; Edge Cases - Deep Nesting
-
-(ert-deftest test-process-very-deep-headings ()
- "Should demote very deep heading structures."
- (let ((result (test-process-webclip "* Title\n****** Level 6\n")))
- (should (string= result "******* Level 6\n"))))
-
-(ert-deftest test-process-complex-document ()
- "Should handle complex document structure."
- (let ((result (test-process-webclip "* Main Title\n\n** Section 1\nText 1\n*** Subsection 1.1\nText 2\n** Section 2\nText 3\n")))
- (should (string= result "*** Section 1\nText 1\n**** Subsection 1.1\nText 2\n*** Section 2\nText 3\n"))))
-
-;;; Integration Tests
-
-(ert-deftest test-process-realistic-webpage ()
- "Should process realistic webclipped content."
- (let ((result (test-process-webclip "* How to Program in Emacs Lisp\n\n** Introduction\nEmacs Lisp is powerful.\n\n** Getting Started\nFirst, open Emacs.\n\n*** Installation\nDownload from gnu.org\n")))
- (should (string= result "*** Introduction\nEmacs Lisp is powerful.\n\n*** Getting Started\nFirst, open Emacs.\n\n**** Installation\nDownload from gnu.org\n"))))
-
-(ert-deftest test-process-article-with-metadata ()
- "Should handle article with org metadata."
- (let ((result (test-process-webclip "* Article Title :article:web:\n#+DATE: 2024-01-01\n\n** Content\nBody text\n")))
- (should (string= result "#+DATE: 2024-01-01\n\n*** Content\nBody text\n"))))
-
-(provide 'test-org-webclipper-process)
-;;; test-org-webclipper-process.el ends here
diff --git a/tests/test-system-lib-executable-exists-p.el b/tests/test-system-lib-executable-exists-p.el
deleted file mode 100644
index 457bb010..00000000
--- a/tests/test-system-lib-executable-exists-p.el
+++ /dev/null
@@ -1,73 +0,0 @@
-;;; test-system-lib-executable-exists-p.el --- Tests for cj/executable-exists-p -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/executable-exists-p function from system-lib.el.
-;; Tests whether external programs are correctly detected in PATH.
-
-;;; Code:
-
-(require 'ert)
-(require 'system-lib)
-
-;;; Normal Cases
-
-(ert-deftest test-system-lib-executable-exists-p-normal-existing-program-returns-path ()
- "Test that existing program in PATH returns non-nil.
-
-Standard case: checking for a program that definitely exists on all systems."
- (should (cj/executable-exists-p "ls")))
-
-(ert-deftest test-system-lib-executable-exists-p-normal-diff-exists-returns-path ()
- "Test that diff program exists and is detected.
-
-Tests specifically for diff which we use in our diff functionality."
- (should (cj/executable-exists-p "diff")))
-
-;;; Boundary Cases
-
-(ert-deftest test-system-lib-executable-exists-p-boundary-empty-string-returns-nil ()
- "Test that empty string returns nil.
-
-Boundary case: empty string is not a valid program name."
- (should-not (cj/executable-exists-p "")))
-
-(ert-deftest test-system-lib-executable-exists-p-boundary-whitespace-only-returns-nil ()
- "Test that whitespace-only string returns nil.
-
-Boundary case: strings containing only whitespace are not valid programs."
- (should-not (cj/executable-exists-p " ")))
-
-(ert-deftest test-system-lib-executable-exists-p-boundary-absolute-path-returns-path ()
- "Test that absolute path to executable returns the path.
-
-Boundary case: executable-find accepts both program names and full paths."
- (should (cj/executable-exists-p "/usr/bin/ls")))
-
-;;; Error Cases
-
-(ert-deftest test-system-lib-executable-exists-p-error-nil-input-returns-nil ()
- "Test that nil input returns nil gracefully.
-
-Error case: nil is not a valid program name."
- (should-not (cj/executable-exists-p nil)))
-
-(ert-deftest test-system-lib-executable-exists-p-error-number-input-returns-nil ()
- "Test that numeric input returns nil gracefully.
-
-Error case: number is not a valid program name."
- (should-not (cj/executable-exists-p 42)))
-
-(ert-deftest test-system-lib-executable-exists-p-error-nonexistent-program-returns-nil ()
- "Test that nonexistent program returns nil.
-
-Error case: program that definitely doesn't exist in PATH."
- (should-not (cj/executable-exists-p "this-program-definitely-does-not-exist-xyz123")))
-
-(ert-deftest test-system-lib-executable-exists-p-error-special-characters-returns-nil ()
- "Test that program name with special characters returns nil.
-
-Error case: invalid characters in program name."
- (should-not (cj/executable-exists-p "program-with-$pecial-ch@rs")))
-
-(provide 'test-system-lib-executable-exists-p)
-;;; test-system-lib-executable-exists-p.el ends here
diff --git a/tests/test-test-runner.el b/tests/test-test-runner.el
deleted file mode 100644
index 0edc0d65..00000000
--- a/tests/test-test-runner.el
+++ /dev/null
@@ -1,359 +0,0 @@
-;;; test-test-runner.el --- Tests for test-runner.el -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for test-runner.el - ERT test runner with focus/unfocus workflow.
-;;
-;; Testing approach:
-;; - Tests focus on internal `cj/test--do-*` functions (pure business logic)
-;; - File system operations use temp directories
-;; - Tests are isolated with setup/teardown
-;; - Tests verify return values, not user messages
-
-;;; Code:
-
-(require 'ert)
-(require 'testutil-general)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Load the module (ignore keymap error in batch mode)
-(condition-case nil
- (require 'test-runner)
- (error nil))
-
-;;; Test Utilities
-
-(defvar test-testrunner--temp-dir nil
- "Temporary directory for test files during tests.")
-
-(defvar test-testrunner--original-focused-files nil
- "Backup of focused files list before test.")
-
-(defun test-testrunner-setup ()
- "Setup test environment before each test."
- ;; Backup current state
- (setq test-testrunner--original-focused-files cj/test-focused-files)
- ;; Reset to clean state
- (setq cj/test-focused-files '())
- ;; Create temp directory for file tests
- (setq test-testrunner--temp-dir (make-temp-file "test-runner-test" t)))
-
-(defun test-testrunner-teardown ()
- "Clean up test environment after each test."
- ;; Restore state
- (setq cj/test-focused-files test-testrunner--original-focused-files)
- ;; Clean up temp directory
- (when (and test-testrunner--temp-dir
- (file-directory-p test-testrunner--temp-dir))
- (delete-directory test-testrunner--temp-dir t))
- (setq test-testrunner--temp-dir nil))
-
-(defun test-testrunner-create-test-file (filename content)
- "Create test file FILENAME with CONTENT in temp directory."
- (let ((filepath (expand-file-name filename test-testrunner--temp-dir)))
- (with-temp-file filepath
- (insert content))
- filepath))
-
-;;; Normal Cases - Load Files
-
-(ert-deftest test-testrunner-load-files-success ()
- "Should successfully load test files."
- (test-testrunner-setup)
- (let* ((file1 (test-testrunner-create-test-file "test-simple.el"
- "(defun test-func () t)"))
- (file2 (test-testrunner-create-test-file "test-other.el"
- "(defun other-func () nil)"))
- (result (cj/test--do-load-files test-testrunner--temp-dir
- (list file1 file2))))
- (should (eq (car result) 'success))
- (should (= (cdr result) 2)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-load-files-with-errors ()
- "Should handle errors during file loading."
- (test-testrunner-setup)
- (let* ((good-file (test-testrunner-create-test-file "test-good.el"
- "(defun good () t)"))
- (bad-file (test-testrunner-create-test-file "test-bad.el"
- "(defun bad ( "))
- (result (cj/test--do-load-files test-testrunner--temp-dir
- (list good-file bad-file))))
- (should (eq (car result) 'error))
- (should (= (nth 1 result) 1)) ; loaded-count
- (should (= (length (nth 2 result)) 1))) ; errors list
- (test-testrunner-teardown))
-
-;;; Normal Cases - Focus Add
-
-(ert-deftest test-testrunner-focus-add-success ()
- "Should successfully add file to focus."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add "test-foo.el"
- '("test-foo.el" "test-bar.el")
- '())))
- (should (eq result 'success)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-already-focused ()
- "Should detect already focused file."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add "test-foo.el"
- '("test-foo.el" "test-bar.el")
- '("test-foo.el"))))
- (should (eq result 'already-focused)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-not-available ()
- "Should detect file not in available list."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add "test-missing.el"
- '("test-foo.el" "test-bar.el")
- '())))
- (should (eq result 'not-available)))
- (test-testrunner-teardown))
-
-;;; Normal Cases - Focus Add File
-
-(ert-deftest test-testrunner-focus-add-file-success ()
- "Should successfully validate and add file to focus."
- (test-testrunner-setup)
- (let* ((filepath (expand-file-name "test-foo.el" test-testrunner--temp-dir))
- (result (cj/test--do-focus-add-file filepath test-testrunner--temp-dir '())))
- (should (eq (car result) 'success))
- (should (string= (cdr result) "test-foo.el")))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-file-no-file ()
- "Should detect nil filepath."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add-file nil test-testrunner--temp-dir '())))
- (should (eq (car result) 'no-file)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-file-not-in-testdir ()
- "Should detect file outside test directory."
- (test-testrunner-setup)
- (let* ((filepath "/tmp/outside-test.el")
- (result (cj/test--do-focus-add-file filepath test-testrunner--temp-dir '())))
- (should (eq (car result) 'not-in-testdir)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-file-already-focused ()
- "Should detect already focused file."
- (test-testrunner-setup)
- (let* ((filepath (expand-file-name "test-foo.el" test-testrunner--temp-dir))
- (result (cj/test--do-focus-add-file filepath
- test-testrunner--temp-dir
- '("test-foo.el"))))
- (should (eq (car result) 'already-focused))
- (should (string= (cdr result) "test-foo.el")))
- (test-testrunner-teardown))
-
-;;; Normal Cases - Focus Remove
-
-(ert-deftest test-testrunner-focus-remove-success ()
- "Should successfully remove file from focus."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-remove "test-foo.el" '("test-foo.el" "test-bar.el"))))
- (should (eq result 'success)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-remove-empty-list ()
- "Should detect empty focused list."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-remove "test-foo.el" '())))
- (should (eq result 'empty-list)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-remove-not-found ()
- "Should detect file not in focused list."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-remove "test-missing.el" '("test-foo.el"))))
- (should (eq result 'not-found)))
- (test-testrunner-teardown))
-
-;;; Normal Cases - Get Focused Tests
-
-(ert-deftest test-testrunner-get-focused-tests-success ()
- "Should extract test names from focused files."
- (test-testrunner-setup)
- (let* ((file1 (test-testrunner-create-test-file "test-first.el"
- "(ert-deftest test-alpha-one () (should t))\n(ert-deftest test-alpha-two () (should t))"))
- (result (cj/test--do-get-focused-tests '("test-first.el") test-testrunner--temp-dir)))
- (should (eq (car result) 'success))
- (should (= (length (nth 1 result)) 2)) ; 2 test names
- (should (= (nth 2 result) 1))) ; 1 file loaded
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-get-focused-tests-empty-list ()
- "Should detect empty focused files list."
- (test-testrunner-setup)
- (let ((result (cj/test--do-get-focused-tests '() test-testrunner--temp-dir)))
- (should (eq (car result) 'empty-list)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-get-focused-tests-no-tests ()
- "Should detect when no tests found in files."
- (test-testrunner-setup)
- (test-testrunner-create-test-file "test-empty.el" "(defun not-a-test () t)")
- (let ((result (cj/test--do-get-focused-tests '("test-empty.el") test-testrunner--temp-dir)))
- (should (eq (car result) 'no-tests)))
- (test-testrunner-teardown))
-
-;;; Normal Cases - Extract Test Names
-
-(ert-deftest test-testrunner-extract-test-names-simple ()
- "Should extract test names from file."
- (test-testrunner-setup)
- (let* ((file (test-testrunner-create-test-file "test-simple.el"
- "(ert-deftest test-foo () (should t))\n(ert-deftest test-bar () (should nil))"))
- (names (cj/test--extract-test-names file)))
- (should (= (length names) 2))
- (should (member "test-foo" names))
- (should (member "test-bar" names)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-extract-test-names-with-whitespace ()
- "Should extract test names with various whitespace."
- (test-testrunner-setup)
- (let* ((file (test-testrunner-create-test-file "test-whitespace.el"
- "(ert-deftest test-spaces () (should t))\n (ert-deftest test-indent () t)"))
- (names (cj/test--extract-test-names file)))
- (should (= (length names) 2))
- (should (member "test-spaces" names))
- (should (member "test-indent" names)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-extract-test-names-no-tests ()
- "Should return empty list when no tests in file."
- (test-testrunner-setup)
- (let* ((file (test-testrunner-create-test-file "test-none.el"
- "(defun not-a-test () t)"))
- (names (cj/test--extract-test-names file)))
- (should (null names)))
- (test-testrunner-teardown))
-
-;;; Normal Cases - Extract Test at Position
-
-(ert-deftest test-testrunner-extract-test-at-pos-found ()
- "Should extract test name at point."
- (test-testrunner-setup)
- (with-temp-buffer
- (insert "(ert-deftest test-sample ()\n (should t))")
- (goto-char (point-min))
- (let ((name (cj/test--extract-test-at-pos)))
- (should (eq name 'test-sample))))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-extract-test-at-pos-not-found ()
- "Should return nil when not in a test."
- (test-testrunner-setup)
- (with-temp-buffer
- (insert "(defun regular-function ()\n (message \"hi\"))")
- (goto-char (point-min))
- (let ((name (cj/test--extract-test-at-pos)))
- (should (null name))))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-extract-test-at-pos-invalid-syntax ()
- "Should return nil for invalid syntax."
- (test-testrunner-setup)
- (with-temp-buffer
- (insert "(ert-deftest")
- (goto-char (point-min))
- (let ((name (cj/test--extract-test-at-pos)))
- (should (null name))))
- (test-testrunner-teardown))
-
-;;; Boundary Cases - Load Files
-
-(ert-deftest test-testrunner-load-files-empty-list ()
- "Should handle empty file list."
- (test-testrunner-setup)
- (let ((result (cj/test--do-load-files test-testrunner--temp-dir '())))
- (should (eq (car result) 'success))
- (should (= (cdr result) 0)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-load-files-nonexistent ()
- "Should handle nonexistent files."
- (test-testrunner-setup)
- (let* ((fake-file (expand-file-name "nonexistent.el" test-testrunner--temp-dir))
- (result (cj/test--do-load-files test-testrunner--temp-dir (list fake-file))))
- (should (eq (car result) 'error))
- (should (= (nth 1 result) 0))) ; 0 files loaded
- (test-testrunner-teardown))
-
-;;; Boundary Cases - Focus Add
-
-(ert-deftest test-testrunner-focus-add-single-available ()
- "Should add when only one file available."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add "test-only.el" '("test-only.el") '())))
- (should (eq result 'success)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-focus-add-case-sensitive ()
- "Should be case-sensitive for filenames."
- (test-testrunner-setup)
- (let ((result (cj/test--do-focus-add "Test-Foo.el"
- '("test-foo.el")
- '())))
- (should (eq result 'not-available)))
- (test-testrunner-teardown))
-
-;;; Boundary Cases - Get Focused Tests
-
-(ert-deftest test-testrunner-get-focused-tests-multiple-files ()
- "Should collect tests from multiple files."
- (test-testrunner-setup)
- (test-testrunner-create-test-file "test-first.el"
- "(ert-deftest test-beta-one () t)")
- (test-testrunner-create-test-file "test-second.el"
- "(ert-deftest test-beta-two () t)")
- (let ((result (cj/test--do-get-focused-tests '("test-first.el" "test-second.el")
- test-testrunner--temp-dir)))
- (should (eq (car result) 'success))
- (should (= (length (nth 1 result)) 2)) ; 2 tests total
- (should (= (nth 2 result) 2))) ; 2 files loaded
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-get-focused-tests-skip-nonexistent ()
- "Should skip nonexistent files."
- (test-testrunner-setup)
- (test-testrunner-create-test-file "test-exists.el"
- "(ert-deftest test-gamma-one () t)")
- (let ((result (cj/test--do-get-focused-tests '("test-exists.el" "test-missing.el")
- test-testrunner--temp-dir)))
- (should (eq (car result) 'success))
- (should (= (length (nth 1 result)) 1)) ; 1 test found
- (should (= (nth 2 result) 1))) ; 1 file loaded (missing skipped)
- (test-testrunner-teardown))
-
-;;; Boundary Cases - Extract Test Names
-
-(ert-deftest test-testrunner-extract-test-names-hyphens-underscores ()
- "Should handle test names with hyphens and underscores."
- (test-testrunner-setup)
- (let* ((file (test-testrunner-create-test-file "test-names.el"
- "(ert-deftest test-with-hyphens () t)\n(ert-deftest test_with_underscores () t)"))
- (names (cj/test--extract-test-names file)))
- (should (= (length names) 2))
- (should (member "test-with-hyphens" names))
- (should (member "test_with_underscores" names)))
- (test-testrunner-teardown))
-
-(ert-deftest test-testrunner-extract-test-names-ignore-comments ()
- "Should not extract test names from comments."
- (test-testrunner-setup)
- (let* ((file (test-testrunner-create-test-file "test-comments.el"
- ";; (ert-deftest test-commented () t)\n(ert-deftest test-real () t)"))
- (names (cj/test--extract-test-names file)))
- (should (= (length names) 1))
- (should (member "test-real" names)))
- (test-testrunner-teardown))
-
-(provide 'test-test-runner)
-;;; test-test-runner.el ends here
diff --git a/tests/test-transcription-audio-file.el b/tests/test-transcription-audio-file.el
deleted file mode 100644
index ac4ff452..00000000
--- a/tests/test-transcription-audio-file.el
+++ /dev/null
@@ -1,88 +0,0 @@
-;;; test-transcription-audio-file.el --- Tests for audio file detection -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for cj/--audio-file-p function
-;; Categories: Normal cases, Boundary cases, Error cases
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-cj/--audio-file-p-m4a ()
- "Test that .m4a files are recognized as audio."
- (should (cj/--audio-file-p "meeting.m4a")))
-
-(ert-deftest test-cj/--audio-file-p-mp3 ()
- "Test that .mp3 files are recognized as audio."
- (should (cj/--audio-file-p "podcast.mp3")))
-
-(ert-deftest test-cj/--audio-file-p-wav ()
- "Test that .wav files are recognized as audio."
- (should (cj/--audio-file-p "recording.wav")))
-
-(ert-deftest test-cj/--audio-file-p-flac ()
- "Test that .flac files are recognized as audio."
- (should (cj/--audio-file-p "music.flac")))
-
-(ert-deftest test-cj/--audio-file-p-with-path ()
- "Test audio file recognition with full path."
- (should (cj/--audio-file-p "/home/user/recordings/meeting.m4a")))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-cj/--audio-file-p-uppercase-extension ()
- "Test that uppercase extensions are recognized."
- (should (cj/--audio-file-p "MEETING.M4A")))
-
-(ert-deftest test-cj/--audio-file-p-mixed-case ()
- "Test that mixed case extensions are recognized."
- (should (cj/--audio-file-p "podcast.Mp3")))
-
-(ert-deftest test-cj/--audio-file-p-no-extension ()
- "Test that files without extension are not recognized."
- (should-not (cj/--audio-file-p "meeting")))
-
-(ert-deftest test-cj/--audio-file-p-empty-string ()
- "Test that empty string is not recognized as audio."
- (should-not (cj/--audio-file-p "")))
-
-(ert-deftest test-cj/--audio-file-p-dotfile ()
- "Test that dotfiles without proper extension are not recognized."
- (should-not (cj/--audio-file-p ".hidden")))
-
-(ert-deftest test-cj/--audio-file-p-multiple-dots ()
- "Test file with multiple dots but audio extension."
- (should (cj/--audio-file-p "meeting.2025-11-04.final.m4a")))
-
-;; ------------------------------ Error Cases ----------------------------------
-
-(ert-deftest test-cj/--audio-file-p-not-audio ()
- "Test that non-audio files are not recognized."
- (should-not (cj/--audio-file-p "document.pdf")))
-
-(ert-deftest test-cj/--audio-file-p-text-file ()
- "Test that text files are not recognized as audio."
- (should-not (cj/--audio-file-p "notes.txt")))
-
-(ert-deftest test-cj/--audio-file-p-org-file ()
- "Test that org files are not recognized as audio."
- (should-not (cj/--audio-file-p "tasks.org")))
-
-(ert-deftest test-cj/--audio-file-p-video-file ()
- "Test that video files are not recognized as audio."
- (should-not (cj/--audio-file-p "video.mp4")))
-
-(ert-deftest test-cj/--audio-file-p-nil ()
- "Test that nil input returns nil."
- (should-not (cj/--audio-file-p nil)))
-
-(provide 'test-transcription-audio-file)
-;;; test-transcription-audio-file.el ends here
diff --git a/tests/test-transcription-config--transcription-script-path.el b/tests/test-transcription-config--transcription-script-path.el
deleted file mode 100644
index a56cb05c..00000000
--- a/tests/test-transcription-config--transcription-script-path.el
+++ /dev/null
@@ -1,106 +0,0 @@
-;;; test-transcription-config--transcription-script-path.el --- Tests for cj/--transcription-script-path -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/--transcription-script-path function from transcription-config.el
-;;
-;; This function returns the absolute path to the transcription script based on
-;; the current value of cj/transcribe-backend.
-
-;;; Code:
-
-(require 'ert)
-
-;; Add modules directory to load path
-(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Stub notification function
-(unless (fboundp 'notifications-notify)
- (defun notifications-notify (&rest _args)
- "Stub notification function for testing."
- nil))
-
-;; Now load the actual production module
-(require 'transcription-config)
-
-;;; Setup and Teardown
-
-(defun test-transcription-script-path-setup ()
- "Set up test environment."
- ;; Save original backend setting
- (setq test-transcription-original-backend cj/transcribe-backend))
-
-(defun test-transcription-script-path-teardown ()
- "Clean up test environment."
- ;; Restore original backend setting
- (setq cj/transcribe-backend test-transcription-original-backend))
-
-;;; Normal Cases
-
-(ert-deftest test-transcription-config--transcription-script-path-normal-openai-api-returns-oai-transcribe ()
- "Should return oai-transcribe script path for openai-api backend."
- (test-transcription-script-path-setup)
- (unwind-protect
- (progn
- (setq cj/transcribe-backend 'openai-api)
- (let ((result (cj/--transcription-script-path)))
- (should (stringp result))
- (should (string-suffix-p "scripts/oai-transcribe" result))
- (should (string-prefix-p (expand-file-name user-emacs-directory) result))))
- (test-transcription-script-path-teardown)))
-
-(ert-deftest test-transcription-config--transcription-script-path-normal-assemblyai-returns-assemblyai-transcribe ()
- "Should return assemblyai-transcribe script path for assemblyai backend."
- (test-transcription-script-path-setup)
- (unwind-protect
- (progn
- (setq cj/transcribe-backend 'assemblyai)
- (let ((result (cj/--transcription-script-path)))
- (should (stringp result))
- (should (string-suffix-p "scripts/assemblyai-transcribe" result))
- (should (string-prefix-p (expand-file-name user-emacs-directory) result))))
- (test-transcription-script-path-teardown)))
-
-(ert-deftest test-transcription-config--transcription-script-path-normal-local-whisper-returns-local-whisper ()
- "Should return local-whisper script path for local-whisper backend."
- (test-transcription-script-path-setup)
- (unwind-protect
- (progn
- (setq cj/transcribe-backend 'local-whisper)
- (let ((result (cj/--transcription-script-path)))
- (should (stringp result))
- (should (string-suffix-p "scripts/local-whisper" result))
- (should (string-prefix-p (expand-file-name user-emacs-directory) result))))
- (test-transcription-script-path-teardown)))
-
-(ert-deftest test-transcription-config--transcription-script-path-normal-returns-absolute-path ()
- "Should return absolute path starting with user-emacs-directory."
- (test-transcription-script-path-setup)
- (unwind-protect
- (progn
- (setq cj/transcribe-backend 'openai-api)
- (let ((result (cj/--transcription-script-path)))
- (should (file-name-absolute-p result))
- (should (string-prefix-p "/" result))))
- (test-transcription-script-path-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-transcription-config--transcription-script-path-boundary-path-format-consistent ()
- "Should return paths in consistent format across backends."
- (test-transcription-script-path-setup)
- (unwind-protect
- (let (paths)
- (dolist (backend '(openai-api assemblyai local-whisper))
- (setq cj/transcribe-backend backend)
- (push (cj/--transcription-script-path) paths))
- ;; All paths should have same structure: <emacs-dir>/scripts/<name>
- (should (= (length paths) 3))
- (should (seq-every-p (lambda (p) (string-match-p "/scripts/[^/]+$" p)) paths)))
- (test-transcription-script-path-teardown)))
-
-(provide 'test-transcription-config--transcription-script-path)
-;;; test-transcription-config--transcription-script-path.el ends here
diff --git a/tests/test-transcription-counter.el b/tests/test-transcription-counter.el
deleted file mode 100644
index dd4df7dc..00000000
--- a/tests/test-transcription-counter.el
+++ /dev/null
@@ -1,103 +0,0 @@
-;;; test-transcription-counter.el --- Tests for active transcription counting -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for cj/--count-active-transcriptions and modeline integration
-;; Categories: Normal cases, Boundary cases
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-cj/--count-active-transcriptions-empty ()
- "Test count when no transcriptions are active."
- (let ((cj/transcriptions-list '()))
- (should (= 0 (cj/--count-active-transcriptions)))))
-
-(ert-deftest test-cj/--count-active-transcriptions-one-running ()
- "Test count with one running transcription."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running))))
- (should (= 1 (cj/--count-active-transcriptions)))))
-
-(ert-deftest test-cj/--count-active-transcriptions-multiple-running ()
- "Test count with multiple running transcriptions."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running)
- (proc2 "file2.m4a" nil running)
- (proc3 "file3.m4a" nil running))))
- (should (= 3 (cj/--count-active-transcriptions)))))
-
-(ert-deftest test-cj/--count-active-transcriptions-mixed-status ()
- "Test count excludes completed/errored transcriptions."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running)
- (proc2 "file2.m4a" nil complete)
- (proc3 "file3.m4a" nil running)
- (proc4 "file4.m4a" nil error))))
- (should (= 2 (cj/--count-active-transcriptions)))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-cj/--count-active-transcriptions-only-complete ()
- "Test count when all transcriptions are complete."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil complete)
- (proc2 "file2.m4a" nil complete))))
- (should (= 0 (cj/--count-active-transcriptions)))))
-
-(ert-deftest test-cj/--count-active-transcriptions-only-error ()
- "Test count when all transcriptions errored."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil error)
- (proc2 "file2.m4a" nil error))))
- (should (= 0 (cj/--count-active-transcriptions)))))
-
-;; ----------------------------- Modeline Tests --------------------------------
-
-(ert-deftest test-cj/--transcription-modeline-string-none-active ()
- "Test modeline string when no transcriptions active."
- (let ((cj/transcriptions-list '()))
- (should-not (cj/--transcription-modeline-string))))
-
-(ert-deftest test-cj/--transcription-modeline-string-one-active ()
- "Test modeline string with one active transcription."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running))))
- (let ((result (cj/--transcription-modeline-string)))
- (should result)
- (should (string-match-p "⏺1" result)))))
-
-(ert-deftest test-cj/--transcription-modeline-string-multiple-active ()
- "Test modeline string with multiple active transcriptions."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running)
- (proc2 "file2.m4a" nil running)
- (proc3 "file3.m4a" nil running))))
- (let ((result (cj/--transcription-modeline-string)))
- (should result)
- (should (string-match-p "⏺3" result)))))
-
-(ert-deftest test-cj/--transcription-modeline-string-has-help-echo ()
- "Test that modeline string has help-echo property."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running))))
- (let ((result (cj/--transcription-modeline-string)))
- (should (get-text-property 0 'help-echo result)))))
-
-(ert-deftest test-cj/--transcription-modeline-string-has-face ()
- "Test that modeline string has warning face."
- (let ((cj/transcriptions-list
- '((proc1 "file1.m4a" nil running))))
- (let ((result (cj/--transcription-modeline-string)))
- (should (eq 'warning (get-text-property 0 'face result))))))
-
-(provide 'test-transcription-counter)
-;;; test-transcription-counter.el ends here
diff --git a/tests/test-transcription-duration.el b/tests/test-transcription-duration.el
deleted file mode 100644
index 4f4e9a75..00000000
--- a/tests/test-transcription-duration.el
+++ /dev/null
@@ -1,63 +0,0 @@
-;;; test-transcription-duration.el --- Tests for duration calculation -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for cj/--transcription-duration function
-;; Categories: Normal cases, Boundary cases
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-cj/--transcription-duration-zero-seconds ()
- "Test duration calculation for current time (should be 00:00)."
- (let ((now (current-time)))
- (should (string= (cj/--transcription-duration now) "00:00"))))
-
-(ert-deftest test-cj/--transcription-duration-30-seconds ()
- "Test duration calculation for 30 seconds ago."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 30))))
- (should (string= (cj/--transcription-duration start-time) "00:30"))))
-
-(ert-deftest test-cj/--transcription-duration-1-minute ()
- "Test duration calculation for 1 minute ago."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 60))))
- (should (string= (cj/--transcription-duration start-time) "01:00"))))
-
-(ert-deftest test-cj/--transcription-duration-2-minutes-30-seconds ()
- "Test duration calculation for 2:30 ago."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 150))))
- (should (string= (cj/--transcription-duration start-time) "02:30"))))
-
-(ert-deftest test-cj/--transcription-duration-10-minutes ()
- "Test duration calculation for 10 minutes ago."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 600))))
- (should (string= (cj/--transcription-duration start-time) "10:00"))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-cj/--transcription-duration-59-seconds ()
- "Test duration just before 1 minute."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 59))))
- (should (string= (cj/--transcription-duration start-time) "00:59"))))
-
-(ert-deftest test-cj/--transcription-duration-1-hour ()
- "Test duration for 1 hour (60 minutes)."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 3600))))
- (should (string= (cj/--transcription-duration start-time) "60:00"))))
-
-(ert-deftest test-cj/--transcription-duration-format ()
- "Test that duration is always in MM:SS format with zero-padding."
- (let ((start-time (time-subtract (current-time) (seconds-to-time 65))))
- (let ((result (cj/--transcription-duration start-time)))
- (should (string-match-p "^[0-9][0-9]:[0-9][0-9]$" result)))))
-
-(provide 'test-transcription-duration)
-;;; test-transcription-duration.el ends here
diff --git a/tests/test-transcription-log-cleanup.el b/tests/test-transcription-log-cleanup.el
deleted file mode 100644
index 251e5ef9..00000000
--- a/tests/test-transcription-log-cleanup.el
+++ /dev/null
@@ -1,49 +0,0 @@
-;;; test-transcription-log-cleanup.el --- Tests for log cleanup logic -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for cj/--should-keep-log function
-;; Categories: Normal cases, Boundary cases
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-cj/--should-keep-log-success-keep-disabled ()
- "Test that logs are deleted on success when keep-log is nil."
- (let ((cj/transcription-keep-log-when-done nil))
- (should-not (cj/--should-keep-log t))))
-
-(ert-deftest test-cj/--should-keep-log-success-keep-enabled ()
- "Test that logs are kept on success when keep-log is t."
- (let ((cj/transcription-keep-log-when-done t))
- (should (cj/--should-keep-log t))))
-
-(ert-deftest test-cj/--should-keep-log-error-keep-disabled ()
- "Test that logs are always kept on error, even if keep-log is nil."
- (let ((cj/transcription-keep-log-when-done nil))
- (should (cj/--should-keep-log nil))))
-
-(ert-deftest test-cj/--should-keep-log-error-keep-enabled ()
- "Test that logs are kept on error when keep-log is t."
- (let ((cj/transcription-keep-log-when-done t))
- (should (cj/--should-keep-log nil))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-cj/--should-keep-log-default-behavior ()
- "Test default behavior (should not keep on success)."
- ;; Default is nil based on defcustom
- (let ((cj/transcription-keep-log-when-done nil))
- (should-not (cj/--should-keep-log t))
- (should (cj/--should-keep-log nil))))
-
-(provide 'test-transcription-log-cleanup)
-;;; test-transcription-log-cleanup.el ends here
diff --git a/tests/test-transcription-paths.el b/tests/test-transcription-paths.el
deleted file mode 100644
index 69dc27e7..00000000
--- a/tests/test-transcription-paths.el
+++ /dev/null
@@ -1,85 +0,0 @@
-;;; test-transcription-paths.el --- Tests for transcription file path logic -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for cj/--transcription-output-files and cj/--transcription-script-path
-;; Categories: Normal cases, Boundary cases, Error cases
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-(require 'transcription-config)
-
-;; ----------------------------- Normal Cases ----------------------------------
-
-(ert-deftest test-cj/--transcription-output-files-simple ()
- "Test output file paths for simple filename."
- (let ((result (cj/--transcription-output-files "meeting.m4a")))
- (should (string= (car result) "meeting.txt"))
- (should (string= (cdr result) "meeting.log"))))
-
-(ert-deftest test-cj/--transcription-output-files-with-path ()
- "Test output file paths with full path."
- (let ((result (cj/--transcription-output-files "/home/user/audio/podcast.mp3")))
- (should (string= (car result) "/home/user/audio/podcast.txt"))
- (should (string= (cdr result) "/home/user/audio/podcast.log"))))
-
-(ert-deftest test-cj/--transcription-output-files-different-extensions ()
- "Test output files for various audio extensions."
- (dolist (ext '("m4a" "mp3" "wav" "flac" "ogg"))
- (let* ((input (format "audio.%s" ext))
- (result (cj/--transcription-output-files input)))
- (should (string= (car result) "audio.txt"))
- (should (string= (cdr result) "audio.log")))))
-
-;; ----------------------------- Boundary Cases --------------------------------
-
-(ert-deftest test-cj/--transcription-output-files-multiple-dots ()
- "Test output files for filename with multiple dots."
- (let ((result (cj/--transcription-output-files "meeting.2025-11-04.final.m4a")))
- (should (string= (car result) "meeting.2025-11-04.final.txt"))
- (should (string= (cdr result) "meeting.2025-11-04.final.log"))))
-
-(ert-deftest test-cj/--transcription-output-files-no-extension ()
- "Test output files for filename without extension."
- (let ((result (cj/--transcription-output-files "meeting")))
- (should (string= (car result) "meeting.txt"))
- (should (string= (cdr result) "meeting.log"))))
-
-(ert-deftest test-cj/--transcription-output-files-spaces-in-name ()
- "Test output files for filename with spaces."
- (let ((result (cj/--transcription-output-files "team meeting 2025.m4a")))
- (should (string= (car result) "team meeting 2025.txt"))
- (should (string= (cdr result) "team meeting 2025.log"))))
-
-(ert-deftest test-cj/--transcription-output-files-special-chars ()
- "Test output files for filename with special characters."
- (let ((result (cj/--transcription-output-files "meeting_(final).m4a")))
- (should (string= (car result) "meeting_(final).txt"))
- (should (string= (cdr result) "meeting_(final).log"))))
-
-;; ----------------------------- Script Path Tests -----------------------------
-
-(ert-deftest test-cj/--transcription-script-path-local-whisper ()
- "Test script path for local-whisper backend."
- (let ((cj/transcribe-backend 'local-whisper))
- (should (string-suffix-p "scripts/local-whisper"
- (cj/--transcription-script-path)))))
-
-(ert-deftest test-cj/--transcription-script-path-openai-api ()
- "Test script path for openai-api backend."
- (let ((cj/transcribe-backend 'openai-api))
- (should (string-suffix-p "scripts/oai-transcribe"
- (cj/--transcription-script-path)))))
-
-(ert-deftest test-cj/--transcription-script-path-absolute ()
- "Test that script path is absolute."
- (let ((path (cj/--transcription-script-path)))
- (should (file-name-absolute-p path))))
-
-(provide 'test-transcription-paths)
-;;; test-transcription-paths.el ends here
diff --git a/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el b/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el
deleted file mode 100644
index dcd08e96..00000000
--- a/tests/test-undead-buffers-kill-all-other-buffers-and-windows.el
+++ /dev/null
@@ -1,159 +0,0 @@
-;;; test-undead-buffers-kill-all-other-buffers-and-windows.el --- Tests for cj/kill-all-other-buffers-and-windows -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/kill-all-other-buffers-and-windows function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-kill-all-other-buffers-and-windows-setup ()
- "Setup for kill-all-other-buffers-and-windows tests."
- (cj/create-test-base-dir)
- (delete-other-windows))
-
-(defun test-kill-all-other-buffers-and-windows-teardown ()
- "Teardown for kill-all-other-buffers-and-windows tests."
- (delete-other-windows)
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-kill-all-other-buffers-and-windows-should-kill-regular-buffers ()
- "Should kill all regular buffers except current."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((main (current-buffer))
- (buf1 (generate-new-buffer "*test-regular-1*"))
- (buf2 (generate-new-buffer "*test-regular-2*")))
- (unwind-protect
- (progn
- (should (buffer-live-p buf1))
- (should (buffer-live-p buf2))
- (cj/kill-all-other-buffers-and-windows)
- (should (buffer-live-p main))
- (should-not (buffer-live-p buf1))
- (should-not (buffer-live-p buf2)))
- (when (buffer-live-p buf1) (kill-buffer buf1))
- (when (buffer-live-p buf2) (kill-buffer buf2))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(ert-deftest test-kill-all-other-buffers-and-windows-should-bury-undead-buffers ()
- "Should bury undead buffers instead of killing them."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (main (current-buffer))
- (buf1 (generate-new-buffer "*test-undead-1*"))
- (buf2 (generate-new-buffer "*test-undead-2*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead-1*")
- (add-to-list 'cj/undead-buffer-list "*test-undead-2*")
- (cj/kill-all-other-buffers-and-windows)
- (should (buffer-live-p main))
- (should (buffer-live-p buf1))
- (should (buffer-live-p buf2)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf1) (kill-buffer buf1))
- (when (buffer-live-p buf2) (kill-buffer buf2))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(ert-deftest test-kill-all-other-buffers-and-windows-should-keep-current-buffer ()
- "Should always keep the current buffer alive."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((main (current-buffer)))
- (cj/kill-all-other-buffers-and-windows)
- (should (buffer-live-p main))
- (should (eq main (current-buffer))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(ert-deftest test-kill-all-other-buffers-and-windows-should-delete-all-other-windows ()
- "Should delete all windows except current."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (progn
- (split-window)
- (split-window)
- (should (> (length (window-list)) 1))
- (cj/kill-all-other-buffers-and-windows)
- (should (one-window-p)))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-kill-all-other-buffers-and-windows-mixed-undead-and-regular-buffers ()
- "With mix of undead and regular buffers, should handle both correctly."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (main (current-buffer))
- (regular (generate-new-buffer "*test-regular*"))
- (undead (generate-new-buffer "*test-undead*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead*")
- (cj/kill-all-other-buffers-and-windows)
- (should (buffer-live-p main))
- (should-not (buffer-live-p regular))
- (should (buffer-live-p undead)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p regular) (kill-buffer regular))
- (when (buffer-live-p undead) (kill-buffer undead))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(ert-deftest test-kill-all-other-buffers-and-windows-all-undead-buffers-should-bury-all ()
- "When all other buffers are undead, should bury all of them."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (main (current-buffer))
- (undead1 (generate-new-buffer "*test-all-undead-1*"))
- (undead2 (generate-new-buffer "*test-all-undead-2*"))
- (undead3 (generate-new-buffer "*test-all-undead-3*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-all-undead-1*")
- (add-to-list 'cj/undead-buffer-list "*test-all-undead-2*")
- (add-to-list 'cj/undead-buffer-list "*test-all-undead-3*")
- (cj/kill-all-other-buffers-and-windows)
- (should (buffer-live-p main))
- (should (buffer-live-p undead1))
- (should (buffer-live-p undead2))
- (should (buffer-live-p undead3)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p undead1) (kill-buffer undead1))
- (when (buffer-live-p undead2) (kill-buffer undead2))
- (when (buffer-live-p undead3) (kill-buffer undead3))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(ert-deftest test-kill-all-other-buffers-and-windows-should-prompt-for-modified-buffers ()
- "Should call cj/save-some-buffers to handle modified buffers."
- (test-kill-all-other-buffers-and-windows-setup)
- (unwind-protect
- (let ((main (current-buffer))
- (file (cj/create-temp-test-file-with-content "original"))
- save-called)
- ;; Mock cj/save-some-buffers to track if it's called
- (cl-letf (((symbol-function 'cj/save-some-buffers)
- (lambda (&optional arg)
- (setq save-called t))))
- (let ((buf (find-file-noselect file)))
- (unwind-protect
- (progn
- (with-current-buffer buf
- (insert "modified"))
- (cj/kill-all-other-buffers-and-windows)
- (should save-called))
- (when (buffer-live-p buf)
- (set-buffer-modified-p nil)
- (kill-buffer buf))))))
- (test-kill-all-other-buffers-and-windows-teardown)))
-
-(provide 'test-undead-buffers-kill-all-other-buffers-and-windows)
-;;; test-undead-buffers-kill-all-other-buffers-and-windows.el ends here
diff --git a/tests/test-undead-buffers-kill-buffer-and-window.el b/tests/test-undead-buffers-kill-buffer-and-window.el
deleted file mode 100644
index b49969f6..00000000
--- a/tests/test-undead-buffers-kill-buffer-and-window.el
+++ /dev/null
@@ -1,112 +0,0 @@
-;;; test-undead-buffers-kill-buffer-and-window.el --- Tests for cj/kill-buffer-and-window -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/kill-buffer-and-window function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-kill-buffer-and-window-setup ()
- "Setup for kill-buffer-and-window tests."
- (cj/create-test-base-dir)
- (delete-other-windows))
-
-(defun test-kill-buffer-and-window-teardown ()
- "Teardown for kill-buffer-and-window tests."
- (delete-other-windows)
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-kill-buffer-and-window-multiple-windows-should-delete-window-and-kill-buffer ()
- "With multiple windows, should delete window and kill buffer."
- (test-kill-buffer-and-window-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-multi*")))
- (unwind-protect
- (progn
- (split-window)
- (switch-to-buffer buf)
- (let ((win (selected-window)))
- (cj/kill-buffer-and-window)
- (should-not (window-live-p win))
- (should-not (buffer-live-p buf))))
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-and-window-teardown)))
-
-(ert-deftest test-kill-buffer-and-window-multiple-windows-undead-buffer-should-delete-window-and-bury ()
- "With multiple windows, undead buffer should be buried and window deleted."
- (test-kill-buffer-and-window-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf (generate-new-buffer "*test-undead-multi*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead-multi*")
- (split-window)
- (switch-to-buffer buf)
- (let ((win (selected-window)))
- (cj/kill-buffer-and-window)
- (should-not (window-live-p win))
- (should (buffer-live-p buf))))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-and-window-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-kill-buffer-and-window-single-window-should-only-kill-buffer ()
- "With single window, should only kill buffer, not delete window."
- (test-kill-buffer-and-window-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-single*")))
- (unwind-protect
- (progn
- (switch-to-buffer buf)
- (should (one-window-p))
- (cj/kill-buffer-and-window)
- (should (one-window-p))
- (should-not (buffer-live-p buf)))
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-and-window-teardown)))
-
-(ert-deftest test-kill-buffer-and-window-single-window-undead-buffer-should-only-bury ()
- "With single window, undead buffer should only be buried."
- (test-kill-buffer-and-window-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf (generate-new-buffer "*test-undead-single*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead-single*")
- (switch-to-buffer buf)
- (should (one-window-p))
- (cj/kill-buffer-and-window)
- (should (one-window-p))
- (should (buffer-live-p buf)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-and-window-teardown)))
-
-(ert-deftest test-kill-buffer-and-window-two-windows-should-leave-one ()
- "With two windows, should leave one window after deletion."
- (test-kill-buffer-and-window-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-two*")))
- (unwind-protect
- (progn
- (split-window)
- (set-window-buffer (selected-window) buf)
- (should (= 2 (length (window-list))))
- (cj/kill-buffer-and-window)
- (should (= 1 (length (window-list)))))
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-and-window-teardown)))
-
-(provide 'test-undead-buffers-kill-buffer-and-window)
-;;; test-undead-buffers-kill-buffer-and-window.el ends here
diff --git a/tests/test-undead-buffers-kill-buffer-or-bury-alive.el b/tests/test-undead-buffers-kill-buffer-or-bury-alive.el
deleted file mode 100644
index 60b776e4..00000000
--- a/tests/test-undead-buffers-kill-buffer-or-bury-alive.el
+++ /dev/null
@@ -1,138 +0,0 @@
-;;; test-undead-buffers-kill-buffer-or-bury-alive.el --- Tests for cj/kill-buffer-or-bury-alive -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/kill-buffer-or-bury-alive function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-kill-buffer-or-bury-alive-setup ()
- "Setup for kill-buffer-or-bury-alive tests."
- (cj/create-test-base-dir))
-
-(defun test-kill-buffer-or-bury-alive-teardown ()
- "Teardown for kill-buffer-or-bury-alive tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-kill-buffer-or-bury-alive-regular-buffer-should-kill ()
- "Killing a regular buffer not in undead list should kill it."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-regular*")))
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive buf)
- (should-not (buffer-live-p buf)))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-undead-buffer-should-bury ()
- "Killing an undead buffer should bury it instead."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf (generate-new-buffer "*test-undead*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead*")
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive buf)
- (should (buffer-live-p buf)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-with-prefix-arg-should-add-to-undead-list ()
- "Calling with prefix arg should add buffer to undead list."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf (generate-new-buffer "*test-prefix*")))
- (unwind-protect
- (progn
- (with-current-buffer buf
- (let ((current-prefix-arg '(4)))
- (cj/kill-buffer-or-bury-alive buf)))
- (should (member "*test-prefix*" cj/undead-buffer-list))
- (should (buffer-live-p buf)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-scratch-buffer-should-bury ()
- "The *scratch* buffer (in default list) should be buried."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((scratch (get-buffer-create "*scratch*")))
- (should (buffer-live-p scratch))
- (cj/kill-buffer-or-bury-alive scratch)
- (should (buffer-live-p scratch)))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-kill-buffer-or-bury-alive-buffer-by-name-string-should-work ()
- "Passing buffer name as string should work."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-string*")))
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive "*test-string*")
- (should-not (buffer-live-p buf)))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-buffer-by-buffer-object-should-work ()
- "Passing buffer object should work."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-object*")))
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive buf)
- (should-not (buffer-live-p buf)))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-modified-undead-buffer-should-bury-without-prompt ()
- "Modified undead buffer should be buried without save prompt."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf (generate-new-buffer "*test-modified*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-modified*")
- (with-current-buffer buf
- (insert "some text")
- (set-buffer-modified-p t))
- (cj/kill-buffer-or-bury-alive buf)
- (should (buffer-live-p buf)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf)
- (set-buffer-modified-p nil)
- (kill-buffer buf))))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-kill-buffer-or-bury-alive-nonexistent-buffer-should-error ()
- "Passing a non-existent buffer name should error."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (should-error (cj/kill-buffer-or-bury-alive "*nonexistent-buffer-xyz*"))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(ert-deftest test-kill-buffer-or-bury-alive-killed-buffer-object-should-error ()
- "Passing a killed buffer object should error."
- (test-kill-buffer-or-bury-alive-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-killed*")))
- (kill-buffer buf)
- (should-error (cj/kill-buffer-or-bury-alive buf)))
- (test-kill-buffer-or-bury-alive-teardown)))
-
-(provide 'test-undead-buffers-kill-buffer-or-bury-alive)
-;;; test-undead-buffers-kill-buffer-or-bury-alive.el ends here
diff --git a/tests/test-undead-buffers-kill-other-window.el b/tests/test-undead-buffers-kill-other-window.el
deleted file mode 100644
index e9371a0f..00000000
--- a/tests/test-undead-buffers-kill-other-window.el
+++ /dev/null
@@ -1,123 +0,0 @@
-;;; test-undead-buffers-kill-other-window.el --- Tests for cj/kill-other-window -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/kill-other-window function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-kill-other-window-setup ()
- "Setup for kill-other-window tests."
- (cj/create-test-base-dir)
- (delete-other-windows))
-
-(defun test-kill-other-window-teardown ()
- "Teardown for kill-other-window tests."
- (delete-other-windows)
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-kill-other-window-two-windows-should-delete-other-and-kill-buffer ()
- "With two windows, should delete other window and kill its buffer."
- (test-kill-other-window-setup)
- (unwind-protect
- (let ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "*test-other*")))
- (unwind-protect
- (progn
- (split-window)
- (let ((win1 (selected-window))
- (win2 (next-window)))
- (set-window-buffer win2 buf2)
- (select-window win1)
- (cj/kill-other-window)
- (should-not (window-live-p win2))
- (should-not (buffer-live-p buf2))))
- (when (buffer-live-p buf2) (kill-buffer buf2))))
- (test-kill-other-window-teardown)))
-
-(ert-deftest test-kill-other-window-two-windows-undead-buffer-should-delete-other-and-bury ()
- "With two windows, undead buffer in other window should be buried."
- (test-kill-other-window-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (buf1 (current-buffer))
- (buf2 (generate-new-buffer "*test-undead-other*")))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list "*test-undead-other*")
- (split-window)
- (let ((win1 (selected-window))
- (win2 (next-window)))
- (set-window-buffer win2 buf2)
- (select-window win1)
- (cj/kill-other-window)
- (should-not (window-live-p win2))
- (should (buffer-live-p buf2))))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf2) (kill-buffer buf2))))
- (test-kill-other-window-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-kill-other-window-single-window-should-only-kill-buffer ()
- "With single window, should only kill the current buffer."
- (test-kill-other-window-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-single-other*")))
- (unwind-protect
- (progn
- (switch-to-buffer buf)
- (should (one-window-p))
- (cj/kill-other-window)
- (should (one-window-p))
- (should-not (buffer-live-p buf)))
- (when (buffer-live-p buf) (kill-buffer buf))))
- (test-kill-other-window-teardown)))
-
-(ert-deftest test-kill-other-window-three-windows-should-delete-one ()
- "With three windows, should delete one window."
- (test-kill-other-window-setup)
- (unwind-protect
- (let ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "*test-three-1*"))
- (buf3 (generate-new-buffer "*test-three-2*")))
- (unwind-protect
- (progn
- (split-window)
- (split-window)
- (set-window-buffer (nth 1 (window-list)) buf2)
- (set-window-buffer (nth 2 (window-list)) buf3)
- (select-window (car (window-list)))
- (should (= 3 (length (window-list))))
- (cj/kill-other-window)
- (should (= 2 (length (window-list)))))
- (when (buffer-live-p buf2) (kill-buffer buf2))
- (when (buffer-live-p buf3) (kill-buffer buf3))))
- (test-kill-other-window-teardown)))
-
-(ert-deftest test-kill-other-window-wraps-to-first-window-correctly ()
- "Should correctly cycle through windows with other-window."
- (test-kill-other-window-setup)
- (unwind-protect
- (let ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "*test-wrap*")))
- (unwind-protect
- (progn
- (split-window)
- (let ((win2 (next-window)))
- (set-window-buffer win2 buf2)
- (select-window (car (window-list)))
- (cj/kill-other-window)
- (should-not (window-live-p win2))))
- (when (buffer-live-p buf2) (kill-buffer buf2))))
- (test-kill-other-window-teardown)))
-
-(provide 'test-undead-buffers-kill-other-window)
-;;; test-undead-buffers-kill-other-window.el ends here
diff --git a/tests/test-undead-buffers-make-buffer-undead.el b/tests/test-undead-buffers-make-buffer-undead.el
deleted file mode 100644
index 823bb56e..00000000
--- a/tests/test-undead-buffers-make-buffer-undead.el
+++ /dev/null
@@ -1,134 +0,0 @@
-;;; test-undead-buffers-make-buffer-undead.el --- Tests for cj/make-buffer-undead -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/make-buffer-undead function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-make-buffer-undead-setup ()
- "Setup for make-buffer-undead tests."
- (cj/create-test-base-dir))
-
-(defun test-make-buffer-undead-teardown ()
- "Teardown for make-buffer-undead tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-make-buffer-undead-valid-name-should-add-to-list ()
- "Adding a valid buffer name should add it to the undead buffer list."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead "*test-buffer*")
- (should (member "*test-buffer*" cj/undead-buffer-list)))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-existing-name-should-not-duplicate ()
- "Adding an existing buffer name should not create duplicates."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead "*test-dup*")
- (cj/make-buffer-undead "*test-dup*")
- (should (= 1 (cl-count "*test-dup*" cj/undead-buffer-list :test #'string=))))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-multiple-additions-should-preserve-order ()
- "Adding multiple buffer names should preserve order."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead "*first*")
- (cj/make-buffer-undead "*second*")
- (cj/make-buffer-undead "*third*")
- (let ((added-items (seq-drop cj/undead-buffer-list (length orig))))
- (should (equal added-items '("*first*" "*second*" "*third*")))))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-make-buffer-undead-whitespace-only-name-should-add ()
- "Adding a whitespace-only name should succeed."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead " ")
- (should (member " " cj/undead-buffer-list)))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-very-long-name-should-add ()
- "Adding a very long buffer name should succeed."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list))
- (long-name (make-string 1000 ?x)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead long-name)
- (should (member long-name cj/undead-buffer-list)))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-unicode-name-should-add ()
- "Adding a buffer name with Unicode characters should succeed."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (let ((orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (cj/make-buffer-undead "*test-🚀-buffer*")
- (should (member "*test-🚀-buffer*" cj/undead-buffer-list)))
- (setq cj/undead-buffer-list orig)))
- (test-make-buffer-undead-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-make-buffer-undead-empty-string-should-error ()
- "Passing an empty string should signal an error."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (should-error (cj/make-buffer-undead ""))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-nil-should-error ()
- "Passing nil should signal an error."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (should-error (cj/make-buffer-undead nil))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-number-should-error ()
- "Passing a number should signal an error."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (should-error (cj/make-buffer-undead 42))
- (test-make-buffer-undead-teardown)))
-
-(ert-deftest test-make-buffer-undead-symbol-should-error ()
- "Passing a symbol should signal an error."
- (test-make-buffer-undead-setup)
- (unwind-protect
- (should-error (cj/make-buffer-undead 'some-symbol))
- (test-make-buffer-undead-teardown)))
-
-(provide 'test-undead-buffers-make-buffer-undead)
-;;; test-undead-buffers-make-buffer-undead.el ends here
diff --git a/tests/test-undead-buffers-undead-buffer-p.el b/tests/test-undead-buffers-undead-buffer-p.el
deleted file mode 100644
index 107256c9..00000000
--- a/tests/test-undead-buffers-undead-buffer-p.el
+++ /dev/null
@@ -1,106 +0,0 @@
-;;; test-undead-buffers-undead-buffer-p.el --- Tests for cj/undead-buffer-p -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Tests for the cj/undead-buffer-p function from undead-buffers.el
-
-;;; Code:
-
-(require 'ert)
-(require 'undead-buffers)
-(require 'testutil-general)
-
-;;; Setup and Teardown
-
-(defun test-undead-buffer-p-setup ()
- "Setup for undead-buffer-p tests."
- (cj/create-test-base-dir))
-
-(defun test-undead-buffer-p-teardown ()
- "Teardown for undead-buffer-p tests."
- (cj/delete-test-base-dir))
-
-;;; Normal Cases
-
-(ert-deftest test-undead-buffer-p-modified-file-buffer-should-return-true ()
- "A modified file-backed buffer not in undead list should return t."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (let* ((file (cj/create-temp-test-file-with-content "test content"))
- (buf (find-file-noselect file)))
- (unwind-protect
- (progn
- (with-current-buffer buf
- (insert "more content")
- (should (cj/undead-buffer-p))))
- (when (buffer-live-p buf)
- (set-buffer-modified-p nil)
- (kill-buffer buf))))
- (test-undead-buffer-p-teardown)))
-
-(ert-deftest test-undead-buffer-p-undead-modified-file-buffer-should-return-nil ()
- "A modified file-backed undead buffer should return nil."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (let* ((orig (copy-sequence cj/undead-buffer-list))
- (file (cj/create-temp-test-file-with-content "test content"))
- (buf (find-file-noselect file)))
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list (buffer-name buf))
- (with-current-buffer buf
- (insert "more content")
- (should-not (cj/undead-buffer-p))))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf)
- (set-buffer-modified-p nil)
- (kill-buffer buf))))
- (test-undead-buffer-p-teardown)))
-
-(ert-deftest test-undead-buffer-p-scratch-buffer-should-return-nil ()
- "The *scratch* buffer should return nil (it's undead)."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (with-current-buffer "*scratch*"
- (should-not (cj/undead-buffer-p)))
- (test-undead-buffer-p-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-undead-buffer-p-unmodified-file-buffer-should-return-nil ()
- "An unmodified file buffer should return nil."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (let* ((file (cj/create-temp-test-file-with-content "test content"))
- (buf (find-file-noselect file)))
- (unwind-protect
- (with-current-buffer buf
- (should-not (cj/undead-buffer-p)))
- (when (buffer-live-p buf)
- (kill-buffer buf))))
- (test-undead-buffer-p-teardown)))
-
-(ert-deftest test-undead-buffer-p-modified-buffer-without-file-should-return-nil ()
- "A modified buffer without a backing file should return nil."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (let ((buf (generate-new-buffer "*test-no-file*")))
- (unwind-protect
- (with-current-buffer buf
- (insert "content")
- (set-buffer-modified-p t)
- (should-not (cj/undead-buffer-p)))
- (when (buffer-live-p buf)
- (set-buffer-modified-p nil)
- (kill-buffer buf))))
- (test-undead-buffer-p-teardown)))
-
-(ert-deftest test-undead-buffer-p-temporary-buffer-should-return-nil ()
- "A temporary buffer should return nil."
- (test-undead-buffer-p-setup)
- (unwind-protect
- (with-temp-buffer
- (should-not (cj/undead-buffer-p)))
- (test-undead-buffer-p-teardown)))
-
-(provide 'test-undead-buffers-undead-buffer-p)
-;;; test-undead-buffers-undead-buffer-p.el ends here
diff --git a/tests/test-undead-buffers.el b/tests/test-undead-buffers.el
deleted file mode 100644
index d08649b7..00000000
--- a/tests/test-undead-buffers.el
+++ /dev/null
@@ -1,117 +0,0 @@
-;;; test-undead-buffers.el --- -*- coding: utf-8; lexical-binding: t; -*-
-
-;;; Commentary:
-;; ERT tests for undead-buffers.el.
-;; Exercises kill vs bury decisions driven by cj/undead-buffer-list
-;; and window-management helpers.
-;; Coverage:
-;; - cj/kill-buffer-or-bury-alive: kills non-listed buffers; buries listed; C-u adds to list
-;; - cj/kill-buffer-and-window: deletes selected window, then kill/bury buffer as appropriate
-;; - cj/kill-other-window: deletes the other window, then kill/bury that buffer
-;; - cj/kill-all-other-buffers-and-windows: keeps only current window/buffer
-;; Tests isolate state with temporary buffers/windows and restore cj/undead-buffer-list.
-;; Note: bury-buffer does not delete windows; tests assert buffer liveness, not window removal.
-
-;;; Code:
-
-(require 'ert)
-(require 'cl-lib)
-(require 'undead-buffers)
-
-(ert-deftest undead-buffers/kill-or-bury-when-not-in-list-kills ()
- "cj/kill-buffer-or-bury-alive should kill a buffer not in `cj/undead-buffer-list'."
- (let* ((buf (generate-new-buffer "test-not-in-list"))
- (orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (should (buffer-live-p buf))
- (cj/kill-buffer-or-bury-alive (buffer-name buf))
- (should-not (buffer-live-p buf)))
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p buf) (kill-buffer buf)))))
-
-(ert-deftest undead-buffers/kill-or-bury-when-in-list-buries ()
- "cj/kill-buffer-or-bury-alive should bury (not kill) a buffer in the list."
- (let* ((name "*dashboard*") ; an element already in the default list
- (buf (generate-new-buffer name))
- (orig (copy-sequence cj/undead-buffer-list))
- win-was)
- (unwind-protect
- (progn
- (add-to-list 'cj/undead-buffer-list name)
- ;; show it in a temporary window so we can detect bury
- (setq win-was (display-buffer buf))
- (cj/kill-buffer-or-bury-alive name)
- ;; bury should leave it alive
- (should (buffer-live-p buf))
- ;; note: Emacs's `bury-buffer` does not delete windows by default,
- ;; so we no longer assert that no window shows it.
- )
- ;; cleanup
- (setq cj/undead-buffer-list orig)
- (delete-windows-on buf)
- (kill-buffer buf))))
-
-(ert-deftest undead-buffers/kill-or-bury-adds-to-list-with-prefix ()
- "Calling `cj/kill-buffer-or-bury-alive' with a prefix arg should add the buffer to the list."
- (let* ((buf (generate-new-buffer "test-add-prefix"))
- (orig (copy-sequence cj/undead-buffer-list)))
- (unwind-protect
- (progn
- (let ((current-prefix-arg '(4)))
- (cj/kill-buffer-or-bury-alive (buffer-name buf)))
- (should (member (buffer-name buf) cj/undead-buffer-list)))
- (setq cj/undead-buffer-list orig)
- (kill-buffer buf))))
-
-(ert-deftest undead-buffers/kill-buffer-and-window-removes-window ()
- "cj/kill-buffer-and-window should delete the current window and kill/bury its buffer."
- (let* ((buf (generate-new-buffer "test-kill-and-win"))
- (orig (copy-sequence cj/undead-buffer-list)))
- (split-window) ; now two windows
- (let ((win (next-window)))
- (set-window-buffer win buf)
- (select-window win)
- (cj/kill-buffer-and-window)
- (should-not (window-live-p win))
- (unless (member (buffer-name buf) orig)
- (should-not (buffer-live-p buf))))
- (setq cj/undead-buffer-list orig)))
-
-(ert-deftest undead-buffers/kill-other-window-deletes-that-window ()
- "cj/kill-other-window should delete the *other* window and kill/bury its buffer."
- (let* ((buf1 (current-buffer))
- (buf2 (generate-new-buffer "test-other-window"))
- (orig (copy-sequence cj/undead-buffer-list)))
- (split-window)
- (let* ((win1 (selected-window))
- (win2 (next-window win1)))
- (set-window-buffer win2 buf2)
- ;; stay on the original window
- (select-window win1)
- (cj/kill-other-window)
- (should-not (window-live-p win2))
- (unless (member (buffer-name buf2) orig)
- (should-not (buffer-live-p buf2))))
- (setq cj/undead-buffer-list orig)))
-
-(ert-deftest undead-buffers/kill-all-other-buffers-and-windows-keeps-only-current ()
- "cj/kill-all-other-buffers-and-windows should delete other windows and kill/bury all other buffers."
- (let* ((main (current-buffer))
- (extra (generate-new-buffer "test-all-others"))
- (orig (copy-sequence cj/undead-buffer-list)))
- (split-window)
- (set-window-buffer (next-window) extra)
- (cj/kill-all-other-buffers-and-windows)
- (should (one-window-p))
- ;; main buffer still exists
- (should (buffer-live-p main))
- ;; extra buffer either buried or killed
- (unless (member (buffer-name extra) orig)
- (should-not (buffer-live-p extra)))
- ;; cleanup
- (setq cj/undead-buffer-list orig)
- (when (buffer-live-p extra) (kill-buffer extra))))
-
-(provide 'test-undead-buffers)
-;;; test-undead-buffers.el ends here.
diff --git a/tests/test-video-audio-recording-check-ffmpeg.el b/tests/test-video-audio-recording-check-ffmpeg.el
deleted file mode 100644
index 5c264b64..00000000
--- a/tests/test-video-audio-recording-check-ffmpeg.el
+++ /dev/null
@@ -1,46 +0,0 @@
-;;; test-video-audio-recording-check-ffmpeg.el --- Tests for cj/recording-check-ffmpeg -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-check-ffmpeg function.
-;; Tests detection of ffmpeg availability.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-check-ffmpeg-normal-ffmpeg-found-returns-t ()
- "Test that function returns t when ffmpeg is found."
- (cl-letf (((symbol-function 'executable-find)
- (lambda (cmd)
- (when (equal cmd "ffmpeg") "/usr/bin/ffmpeg"))))
- (let ((result (cj/recording-check-ffmpeg)))
- (should (eq t result)))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-check-ffmpeg-error-ffmpeg-not-found-signals-error ()
- "Test that function signals user-error when ffmpeg is not found."
- (cl-letf (((symbol-function 'executable-find)
- (lambda (_cmd) nil)))
- (should-error (cj/recording-check-ffmpeg) :type 'user-error)))
-
-(ert-deftest test-video-audio-recording-check-ffmpeg-error-message-mentions-pacman ()
- "Test that error message includes installation command."
- (cl-letf (((symbol-function 'executable-find)
- (lambda (_cmd) nil)))
- (condition-case err
- (cj/recording-check-ffmpeg)
- (user-error
- (should (string-match-p "pacman -S ffmpeg" (error-message-string err)))))))
-
-(provide 'test-video-audio-recording-check-ffmpeg)
-;;; test-video-audio-recording-check-ffmpeg.el ends here
diff --git a/tests/test-video-audio-recording-ffmpeg-functions.el b/tests/test-video-audio-recording-ffmpeg-functions.el
deleted file mode 100644
index e82614e2..00000000
--- a/tests/test-video-audio-recording-ffmpeg-functions.el
+++ /dev/null
@@ -1,361 +0,0 @@
-;;; test-video-audio-recording-ffmpeg-functions.el --- Tests for ffmpeg recording functions -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/ffmpeg-record-video, cj/ffmpeg-record-audio,
-;; cj/video-recording-stop, and cj/audio-recording-stop functions.
-;; Tests process creation, sentinel attachment, and cleanup.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Stub directory variables
-(defvar video-recordings-dir "/tmp/video-recordings/")
-(defvar audio-recordings-dir "/tmp/audio-recordings/")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-ffmpeg-setup ()
- "Reset all variables before each test."
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device "test-mic-device")
- (setq cj/recording-system-device "test-monitor-device")
- (setq cj/recording-mic-boost 2.0)
- (setq cj/recording-system-volume 0.5))
-
-(defun test-ffmpeg-teardown ()
- "Clean up after each test."
- (when cj/video-recording-ffmpeg-process
- (ignore-errors (delete-process cj/video-recording-ffmpeg-process)))
- (when cj/audio-recording-ffmpeg-process
- (ignore-errors (delete-process cj/audio-recording-ffmpeg-process)))
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Video Recording - Normal Cases
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-video-normal-creates-process ()
- "Test that video recording creates a process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((process-created nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (setq process-created t)
- (make-process :name "fake-video" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-video video-recordings-dir)
- (should process-created)
- (should cj/video-recording-ffmpeg-process)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-video-normal-attaches-sentinel ()
- "Test that video recording attaches sentinel to process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((sentinel-attached nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (make-process :name "fake-video" :command '("sleep" "1000"))))
- ((symbol-function 'set-process-sentinel)
- (lambda (_proc sentinel)
- (should (eq sentinel #'cj/recording-process-sentinel))
- (setq sentinel-attached t))))
- (cj/ffmpeg-record-video video-recordings-dir)
- (should sentinel-attached)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-video-normal-updates-modeline ()
- "Test that video recording triggers modeline update."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((update-called nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (make-process :name "fake-video" :command '("sleep" "1000"))))
- ((symbol-function 'force-mode-line-update)
- (lambda (&optional _all) (setq update-called t))))
- (cj/ffmpeg-record-video video-recordings-dir)
- (should update-called)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-video-normal-uses-device-settings ()
- "Test that video recording uses configured devices and volume settings."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((command nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer cmd)
- (setq command cmd)
- (make-process :name "fake-video" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-video video-recordings-dir)
- (should (string-match-p "test-mic-device" command))
- (should (string-match-p "test-monitor-device" command))
- (should (string-match-p "2\\.0" command)) ; mic boost
- (should (string-match-p "0\\.5" command)))) ; system volume
- (test-ffmpeg-teardown)))
-
-;;; Audio Recording - Normal Cases
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-audio-normal-creates-process ()
- "Test that audio recording creates a process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((process-created nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (setq process-created t)
- (make-process :name "fake-audio" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-audio audio-recordings-dir)
- (should process-created)
- (should cj/audio-recording-ffmpeg-process)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-audio-normal-attaches-sentinel ()
- "Test that audio recording attaches sentinel to process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((sentinel-attached nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (make-process :name "fake-audio" :command '("sleep" "1000"))))
- ((symbol-function 'set-process-sentinel)
- (lambda (_proc sentinel)
- (should (eq sentinel #'cj/recording-process-sentinel))
- (setq sentinel-attached t))))
- (cj/ffmpeg-record-audio audio-recordings-dir)
- (should sentinel-attached)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-audio-normal-updates-modeline ()
- "Test that audio recording triggers modeline update."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((update-called nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (make-process :name "fake-audio" :command '("sleep" "1000"))))
- ((symbol-function 'force-mode-line-update)
- (lambda (&optional _all) (setq update-called t))))
- (cj/ffmpeg-record-audio audio-recordings-dir)
- (should update-called)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-audio-normal-creates-m4a-file ()
- "Test that audio recording creates .m4a file."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((command nil))
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer cmd)
- (setq command cmd)
- (make-process :name "fake-audio" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-audio audio-recordings-dir)
- (should (string-match-p "\\.m4a" command))))
- (test-ffmpeg-teardown)))
-
-;;; Stop Functions - Normal Cases
-
-(ert-deftest test-video-audio-recording-video-stop-normal-interrupts-process ()
- "Test that stopping video recording interrupts the process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (interrupt-called nil))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (setq interrupt-called t))))
- (cj/video-recording-stop)
- (should interrupt-called))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-video-stop-normal-clears-variable ()
- "Test that stopping video recording clears the process variable."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cj/video-recording-stop)
- (should (null cj/video-recording-ffmpeg-process))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-video-stop-normal-updates-modeline ()
- "Test that stopping video recording updates modeline."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (update-called nil))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'force-mode-line-update)
- (lambda (&optional _all) (setq update-called t))))
- (cj/video-recording-stop)
- (should update-called))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-stop-normal-interrupts-process ()
- "Test that stopping audio recording interrupts the process."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
- (interrupt-called nil))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (setq interrupt-called t))))
- (cj/audio-recording-stop)
- (should interrupt-called))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-stop-normal-clears-variable ()
- "Test that stopping audio recording clears the process variable."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cj/audio-recording-stop)
- (should (null cj/audio-recording-ffmpeg-process))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-video-stop-boundary-no-process-displays-message ()
- "Test that stopping when no video recording shows message."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((message-text nil))
- (setq cj/video-recording-ffmpeg-process nil)
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (setq message-text (apply #'format fmt args)))))
- (cj/video-recording-stop)
- (should (string-match-p "No video recording" message-text))))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-stop-boundary-no-process-displays-message ()
- "Test that stopping when no audio recording shows message."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((message-text nil))
- (setq cj/audio-recording-ffmpeg-process nil)
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (setq message-text (apply #'format fmt args)))))
- (cj/audio-recording-stop)
- (should (string-match-p "No audio recording" message-text))))
- (test-ffmpeg-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-video-stop-error-interrupt-process-fails ()
- "Test that video stop handles interrupt-process failure gracefully."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (error-raised nil))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (error "Interrupt failed"))))
- ;; Should handle the error without crashing
- (condition-case err
- (cj/video-recording-stop)
- (error (setq error-raised t)))
- ;; Error should propagate (function doesn't catch it)
- (should error-raised))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-stop-error-interrupt-process-fails ()
- "Test that audio stop handles interrupt-process failure gracefully."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
- (error-raised nil))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'interrupt-process)
- (lambda (_proc) (error "Interrupt failed"))))
- ;; Should handle the error without crashing
- (condition-case err
- (cj/audio-recording-stop)
- (error (setq error-raised t)))
- ;; Error should propagate (function doesn't catch it)
- (should error-raised))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-video-stop-error-dead-process-raises-error ()
- "Test that video stop raises error if process is already dead.
-This documents current behavior - interrupt-process on dead process errors.
-The sentinel should clear the variable before this happens in practice."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- ;; Kill process before calling stop
- (delete-process fake-process)
- (sit-for 0.1)
- ;; Calling stop on dead process raises error
- (should-error (cj/video-recording-stop)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-stop-error-dead-process-raises-error ()
- "Test that audio stop raises error if process is already dead.
-This documents current behavior - interrupt-process on dead process errors.
-The sentinel should clear the variable before this happens in practice."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Kill process before calling stop
- (delete-process fake-process)
- (sit-for 0.1)
- ;; Calling stop on dead process raises error
- (should-error (cj/audio-recording-stop)))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-video-boundary-skips-if-already-recording ()
- "Test that video recording skips if already in progress."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000")))
- (start-called nil))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (setq start-called t)
- (make-process :name "fake-video2" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-video video-recordings-dir)
- ;; Should NOT start a new process
- (should-not start-called))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(ert-deftest test-video-audio-recording-ffmpeg-record-audio-boundary-skips-if-already-recording ()
- "Test that audio recording skips if already in progress."
- (test-ffmpeg-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000")))
- (start-called nil))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'start-process-shell-command)
- (lambda (_name _buffer _command)
- (setq start-called t)
- (make-process :name "fake-audio2" :command '("sleep" "1000")))))
- (cj/ffmpeg-record-audio audio-recordings-dir)
- ;; Should NOT start a new process
- (should-not start-called))
- (delete-process fake-process))
- (test-ffmpeg-teardown)))
-
-(provide 'test-video-audio-recording-ffmpeg-functions)
-;;; test-video-audio-recording-ffmpeg-functions.el ends here
diff --git a/tests/test-video-audio-recording-friendly-state.el b/tests/test-video-audio-recording-friendly-state.el
deleted file mode 100644
index 91b47998..00000000
--- a/tests/test-video-audio-recording-friendly-state.el
+++ /dev/null
@@ -1,65 +0,0 @@
-;;; test-video-audio-recording-friendly-state.el --- Tests for cj/recording-friendly-state -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-friendly-state function.
-;; Tests conversion of technical pactl state names to user-friendly labels.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-friendly-state-normal-suspended-returns-ready ()
- "Test that SUSPENDED state converts to Ready."
- (should (string= "Ready" (cj/recording-friendly-state "SUSPENDED"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-normal-running-returns-active ()
- "Test that RUNNING state converts to Active."
- (should (string= "Active" (cj/recording-friendly-state "RUNNING"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-normal-idle-returns-ready ()
- "Test that IDLE state converts to Ready."
- (should (string= "Ready" (cj/recording-friendly-state "IDLE"))))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-friendly-state-boundary-empty-string-returns-empty ()
- "Test that empty string passes through unchanged."
- (should (string= "" (cj/recording-friendly-state ""))))
-
-(ert-deftest test-video-audio-recording-friendly-state-boundary-lowercase-suspended-returns-unchanged ()
- "Test that lowercase 'suspended' is not converted (case-sensitive)."
- (should (string= "suspended" (cj/recording-friendly-state "suspended"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-boundary-mixed-case-returns-unchanged ()
- "Test that mixed case 'Running' passes through unchanged."
- (should (string= "Running" (cj/recording-friendly-state "Running"))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-friendly-state-error-unknown-state-returns-unchanged ()
- "Test that unknown state passes through unchanged."
- (should (string= "UNKNOWN" (cj/recording-friendly-state "UNKNOWN"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-error-random-string-returns-unchanged ()
- "Test that random string passes through unchanged."
- (should (string= "foobar" (cj/recording-friendly-state "foobar"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-error-numeric-string-returns-unchanged ()
- "Test that numeric string passes through unchanged."
- (should (string= "12345" (cj/recording-friendly-state "12345"))))
-
-(ert-deftest test-video-audio-recording-friendly-state-error-special-chars-returns-unchanged ()
- "Test that string with special characters passes through unchanged."
- (should (string= "!@#$%" (cj/recording-friendly-state "!@#$%"))))
-
-(provide 'test-video-audio-recording-friendly-state)
-;;; test-video-audio-recording-friendly-state.el ends here
diff --git a/tests/test-video-audio-recording-get-devices.el b/tests/test-video-audio-recording-get-devices.el
deleted file mode 100644
index ba7d95b9..00000000
--- a/tests/test-video-audio-recording-get-devices.el
+++ /dev/null
@@ -1,190 +0,0 @@
-;;; test-video-audio-recording-get-devices.el --- Tests for cj/recording-get-devices -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-get-devices function.
-;; Tests device prompting and validation workflow.
-;;
-;; NOTE: This function was refactored to use interactive prompts instead of
-;; auto-detection. It now prompts the user with y-or-n-p and calls either
-;; cj/recording-quick-setup-for-calls or cj/recording-select-devices.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-get-devices-setup ()
- "Reset device variables before each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-(defun test-get-devices-teardown ()
- "Clean up device variables after each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-get-devices-normal-returns-preset-devices ()
- "Test that already-configured devices are returned without prompting."
- (test-get-devices-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "preset-mic")
- (setq cj/recording-system-device "preset-monitor")
- (let ((result (cj/recording-get-devices)))
- (should (consp result))
- (should (equal "preset-mic" (car result)))
- (should (equal "preset-monitor" (cdr result)))))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-normal-prompts-when-not-configured ()
- "Test that function prompts user when devices not configured."
- (test-get-devices-setup)
- (unwind-protect
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq cj/recording-mic-device "quick-mic")
- (setq cj/recording-system-device "quick-monitor"))))
- (cj/recording-get-devices)
- (should prompt-called)))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-normal-calls-quick-setup-on-yes ()
- "Test that function calls quick setup when user answers yes."
- (test-get-devices-setup)
- (unwind-protect
- (let ((quick-setup-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq quick-setup-called t)
- (setq cj/recording-mic-device "quick-mic")
- (setq cj/recording-system-device "quick-monitor"))))
- (cj/recording-get-devices)
- (should quick-setup-called)))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-normal-calls-select-devices-on-no ()
- "Test that function calls manual selection when user answers no."
- (test-get-devices-setup)
- (unwind-protect
- (let ((select-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) nil))
- ((symbol-function 'cj/recording-select-devices)
- (lambda ()
- (setq select-called t)
- (setq cj/recording-mic-device "manual-mic")
- (setq cj/recording-system-device "manual-monitor"))))
- (cj/recording-get-devices)
- (should select-called)))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-normal-returns-cons-cell ()
- "Test that function returns (mic . monitor) cons cell."
- (test-get-devices-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor"))))
- (let ((result (cj/recording-get-devices)))
- (should (consp result))
- (should (equal "test-mic" (car result)))
- (should (equal "test-monitor" (cdr result)))))
- (test-get-devices-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-get-devices-boundary-only-mic-set-prompts ()
- "Test that function prompts even when only mic is set."
- (test-get-devices-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "preset-mic")
- (setq cj/recording-system-device nil)
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq cj/recording-mic-device "new-mic")
- (setq cj/recording-system-device "new-monitor"))))
- (cj/recording-get-devices)
- (should prompt-called))))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-boundary-only-monitor-set-prompts ()
- "Test that function prompts even when only monitor is set."
- (test-get-devices-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device "preset-monitor")
- (let ((prompt-called nil))
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) (setq prompt-called t) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda ()
- (setq cj/recording-mic-device "new-mic")
- (setq cj/recording-system-device "new-monitor"))))
- (cj/recording-get-devices)
- (should prompt-called))))
- (test-get-devices-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-get-devices-error-setup-fails-signals-error ()
- "Test that function signals error when setup fails to set devices."
- (test-get-devices-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda () nil))) ;; Setup fails - doesn't set devices
- (should-error (cj/recording-get-devices) :type 'user-error))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-error-message-mentions-setup-commands ()
- "Test that error message guides user to setup commands."
- (test-get-devices-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) t))
- ((symbol-function 'cj/recording-quick-setup-for-calls)
- (lambda () nil)))
- (condition-case err
- (cj/recording-get-devices)
- (user-error
- (should (string-match-p "C-; r c" (error-message-string err)))
- (should (string-match-p "C-; r s" (error-message-string err))))))
- (test-get-devices-teardown)))
-
-(ert-deftest test-video-audio-recording-get-devices-error-select-devices-fails ()
- "Test that function signals error when manual selection fails."
- (test-get-devices-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'y-or-n-p)
- (lambda (_prompt) nil))
- ((symbol-function 'cj/recording-select-devices)
- (lambda () nil))) ;; Manual selection fails
- (should-error (cj/recording-get-devices) :type 'user-error))
- (test-get-devices-teardown)))
-
-(provide 'test-video-audio-recording-get-devices)
-;;; test-video-audio-recording-get-devices.el ends here
diff --git a/tests/test-video-audio-recording-group-devices-by-hardware.el b/tests/test-video-audio-recording-group-devices-by-hardware.el
deleted file mode 100644
index 0abe5f6c..00000000
--- a/tests/test-video-audio-recording-group-devices-by-hardware.el
+++ /dev/null
@@ -1,194 +0,0 @@
-;;; test-video-audio-recording-group-devices-by-hardware.el --- Tests for cj/recording-group-devices-by-hardware -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-group-devices-by-hardware function.
-;; Tests grouping of audio sources by physical hardware device.
-;; Critical test: Bluetooth MAC address normalization (colons vs underscores).
-;;
-;; This function is used by the quick setup command to automatically pair
-;; microphone and monitor devices from the same hardware.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Test Fixtures Helper
-
-(defun test-load-fixture (filename)
- "Load fixture file FILENAME from tests/fixtures directory."
- (let ((fixture-path (expand-file-name
- (concat "tests/fixtures/" filename)
- user-emacs-directory)))
- (with-temp-buffer
- (insert-file-contents fixture-path)
- (buffer-string))))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-all-types-grouped ()
- "Test grouping of all three device types (built-in, USB, Bluetooth).
-This is the key test validating the complete grouping logic."
- (let ((output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (listp result))
- (should (= 3 (length result)))
- ;; Check that we have all three device types
- (let ((names (mapcar #'car result)))
- (should (member "Built-in Laptop Audio" names))
- (should (member "Bluetooth Headset" names))
- (should (member "Jabra SPEAK 510 USB" names)))
- ;; Verify each device has both mic and monitor
- (dolist (device result)
- (should (stringp (car device))) ; friendly name
- (should (stringp (cadr device))) ; mic device
- (should (stringp (cddr device))) ; monitor device
- (should-not (string-suffix-p ".monitor" (cadr device))) ; mic not monitor
- (should (string-suffix-p ".monitor" (cddr device)))))))) ; monitor has suffix
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-built-in-paired ()
- "Test that built-in laptop audio devices are correctly paired."
- (let ((output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let* ((result (cj/recording-group-devices-by-hardware))
- (built-in (assoc "Built-in Laptop Audio" result)))
- (should built-in)
- (should (string-match-p "pci-0000_00_1f" (cadr built-in)))
- (should (string-match-p "pci-0000_00_1f" (cddr built-in)))
- (should (equal "alsa_input.pci-0000_00_1f.3.analog-stereo" (cadr built-in)))
- (should (equal "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor" (cddr built-in)))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-usb-paired ()
- "Test that USB devices (Jabra) are correctly paired."
- (let ((output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let* ((result (cj/recording-group-devices-by-hardware))
- (jabra (assoc "Jabra SPEAK 510 USB" result)))
- (should jabra)
- (should (string-match-p "Jabra" (cadr jabra)))
- (should (string-match-p "Jabra" (cddr jabra)))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-normal-bluetooth-paired ()
- "Test that Bluetooth devices are correctly paired.
-CRITICAL: Tests MAC address normalization (colons in input, underscores in output)."
- (let ((output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let* ((result (cj/recording-group-devices-by-hardware))
- (bluetooth (assoc "Bluetooth Headset" result)))
- (should bluetooth)
- ;; Input has colons: bluez_input.00:1B:66:C0:91:6D
- (should (equal "bluez_input.00:1B:66:C0:91:6D" (cadr bluetooth)))
- ;; Output has underscores: bluez_output.00_1B_66_C0_91_6D.1.monitor
- ;; But they should still be grouped together (MAC address normalized)
- (should (equal "bluez_output.00_1B_66_C0_91_6D.1.monitor" (cddr bluetooth)))))))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-empty-returns-empty ()
- "Test that empty pactl output returns empty list."
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) "")))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (listp result))
- (should (null result)))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-only-inputs-returns-empty ()
- "Test that only input devices (no monitors) returns empty list.
-Devices must have BOTH mic and monitor to be included."
- (let ((output (test-load-fixture "pactl-output-inputs-only.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (listp result))
- (should (null result))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-only-monitors-returns-empty ()
- "Test that only monitor devices (no inputs) returns empty list."
- (let ((output (test-load-fixture "pactl-output-monitors-only.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (listp result))
- (should (null result))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-single-complete-device ()
- "Test that single device with both mic and monitor is returned."
- (let ((output "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (= 1 (length result)))
- (should (equal "Built-in Laptop Audio" (caar result)))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-boundary-mixed-complete-incomplete ()
- "Test that only devices with BOTH mic and monitor are included.
-Incomplete devices (only mic or only monitor) are filtered out."
- (let ((output (concat
- ;; Complete device (built-in)
- "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- "49\talsa_output.pci-0000_00_1f.3.analog-stereo.monitor\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- ;; Incomplete: USB mic with no monitor
- "100\talsa_input.usb-device.mono-fallback\tPipeWire\ts16le 1ch 16000Hz\tSUSPENDED\n"
- ;; Incomplete: Bluetooth monitor with no mic
- "81\tbluez_output.AA_BB_CC_DD_EE_FF.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- ;; Only the complete built-in device should be returned
- (should (= 1 (length result)))
- (should (equal "Built-in Laptop Audio" (caar result)))))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-malformed-output-returns-empty ()
- "Test that malformed pactl output returns empty list."
- (let ((output (test-load-fixture "pactl-output-malformed.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (listp result))
- (should (null result))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-unknown-device-type ()
- "Test that unknown device types get generic 'USB Audio Device' name."
- (let ((output (concat
- "100\talsa_input.usb-unknown_device-00.analog-stereo\tPipeWire\ts16le 2ch 16000Hz\tSUSPENDED\n"
- "99\talsa_output.usb-unknown_device-00.analog-stereo.monitor\tPipeWire\ts16le 2ch 48000Hz\tSUSPENDED\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (= 1 (length result)))
- ;; Should get generic USB name (not matching Jabra pattern)
- (should (equal "USB Audio Device" (caar result)))))))
-
-(ert-deftest test-video-audio-recording-group-devices-by-hardware-error-bluetooth-mac-case-variations ()
- "Test that Bluetooth MAC addresses work with different formatting.
-Tests the normalization logic handles various MAC address formats."
- (let ((output (concat
- ;; Input with colons (typical)
- "79\tbluez_input.AA:BB:CC:DD:EE:FF\tPipeWire\tfloat32le 1ch 48000Hz\tSUSPENDED\n"
- ;; Output with underscores (typical)
- "81\tbluez_output.AA_BB_CC_DD_EE_FF.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) output)))
- (let ((result (cj/recording-group-devices-by-hardware)))
- (should (= 1 (length result)))
- (should (equal "Bluetooth Headset" (caar result)))
- ;; Verify both devices paired despite different MAC formats
- (let ((device (car result)))
- (should (string-match-p "AA:BB:CC" (cadr device)))
- (should (string-match-p "AA_BB_CC" (cddr device))))))))
-
-(provide 'test-video-audio-recording-group-devices-by-hardware)
-;;; test-video-audio-recording-group-devices-by-hardware.el ends here
diff --git a/tests/test-video-audio-recording-modeline-indicator.el b/tests/test-video-audio-recording-modeline-indicator.el
deleted file mode 100644
index f7f3bbff..00000000
--- a/tests/test-video-audio-recording-modeline-indicator.el
+++ /dev/null
@@ -1,134 +0,0 @@
-;;; test-video-audio-recording-modeline-indicator.el --- Tests for cj/recording-modeline-indicator -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-modeline-indicator function.
-;; Tests modeline indicator display based on active recording processes.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-modeline-indicator-setup ()
- "Reset process variables before each test."
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/video-recording-ffmpeg-process nil))
-
-(defun test-modeline-indicator-teardown ()
- "Clean up process variables after each test."
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/video-recording-ffmpeg-process nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-modeline-indicator-normal-no-processes-returns-empty ()
- "Test that indicator returns empty string when no processes are active."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((result (cj/recording-modeline-indicator)))
- (should (stringp result))
- (should (equal "" result)))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-normal-audio-only-shows-audio ()
- "Test that indicator shows audio when only audio process is active."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Audio " result)))
- (delete-process fake-process))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-normal-video-only-shows-video ()
- "Test that indicator shows video when only video process is active."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Video " result)))
- (delete-process fake-process))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-normal-both-shows-combined ()
- "Test that indicator shows A+V when both processes are active."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((audio-proc (make-process :name "test-audio" :command '("sleep" "1000")))
- (video-proc (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process audio-proc)
- (setq cj/video-recording-ffmpeg-process video-proc)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴A+V " result)))
- (delete-process audio-proc)
- (delete-process video-proc))
- (test-modeline-indicator-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-modeline-indicator-boundary-dead-audio-process-returns-empty ()
- "Test that indicator returns empty string when audio process variable is set but process is dead."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Kill the process
- (delete-process fake-process)
- ;; Wait for process to be fully dead
- (sit-for 0.1)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal "" result))))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-boundary-dead-video-process-returns-empty ()
- "Test that indicator returns empty string when video process variable is set but process is dead."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- ;; Kill the process
- (delete-process fake-process)
- ;; Wait for process to be fully dead
- (sit-for 0.1)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal "" result))))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-boundary-one-dead-one-alive-shows-alive ()
- "Test that only the alive process shows when one is dead and one is alive."
- (test-modeline-indicator-setup)
- (unwind-protect
- (let ((dead-proc (make-process :name "test-dead" :command '("sleep" "1000")))
- (alive-proc (make-process :name "test-alive" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process dead-proc)
- (setq cj/video-recording-ffmpeg-process alive-proc)
- (delete-process dead-proc)
- (sit-for 0.1)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal " 🔴Video " result)))
- (delete-process alive-proc))
- (test-modeline-indicator-teardown)))
-
-(ert-deftest test-video-audio-recording-modeline-indicator-boundary-nil-process-variables ()
- "Test that nil process variables are handled gracefully."
- (test-modeline-indicator-setup)
- (unwind-protect
- (progn
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/video-recording-ffmpeg-process nil)
- (let ((result (cj/recording-modeline-indicator)))
- (should (equal "" result))))
- (test-modeline-indicator-teardown)))
-
-(provide 'test-video-audio-recording-modeline-indicator)
-;;; test-video-audio-recording-modeline-indicator.el ends here
diff --git a/tests/test-video-audio-recording-parse-pactl-output.el b/tests/test-video-audio-recording-parse-pactl-output.el
deleted file mode 100644
index db49a897..00000000
--- a/tests/test-video-audio-recording-parse-pactl-output.el
+++ /dev/null
@@ -1,157 +0,0 @@
-;;; test-video-audio-recording-parse-pactl-output.el --- Tests for cj/recording--parse-pactl-output -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording--parse-pactl-output function.
-;; Tests parsing of pactl sources output into structured data.
-;; Uses fixture files with sample pactl output for reproducible testing.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Test Fixtures Helper
-
-(defun test-load-fixture (filename)
- "Load fixture file FILENAME from tests/fixtures directory."
- (let ((fixture-path (expand-file-name
- (concat "tests/fixtures/" filename)
- user-emacs-directory)))
- (with-temp-buffer
- (insert-file-contents fixture-path)
- (buffer-string))))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-normal-all-devices-returns-list ()
- "Test parsing normal pactl output with all device types."
- (let* ((output (test-load-fixture "pactl-output-normal.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (= 6 (length result)))
- ;; Check first device (built-in monitor)
- (should (equal '("alsa_output.pci-0000_00_1f.3.analog-stereo.monitor"
- "PipeWire"
- "SUSPENDED")
- (nth 0 result)))
- ;; Check Bluetooth input
- (should (equal '("bluez_input.00:1B:66:C0:91:6D"
- "PipeWire"
- "SUSPENDED")
- (nth 2 result)))
- ;; Check USB device
- (should (equal '("alsa_input.usb-0b0e_Jabra_SPEAK_510_USB_1C48F9C067D5020A00-00.mono-fallback"
- "PipeWire"
- "SUSPENDED")
- (nth 5 result)))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-normal-single-device-returns-list ()
- "Test parsing output with single device."
- (let* ((output (test-load-fixture "pactl-output-single.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (= 1 (length result)))
- (should (equal '("alsa_input.pci-0000_00_1f.3.analog-stereo"
- "PipeWire"
- "SUSPENDED")
- (car result)))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-normal-monitors-only-returns-list ()
- "Test parsing output with only monitor devices."
- (let* ((output (test-load-fixture "pactl-output-monitors-only.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (= 3 (length result)))
- ;; All should end with .monitor
- (dolist (device result)
- (should (string-suffix-p ".monitor" (car device))))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-normal-inputs-only-returns-list ()
- "Test parsing output with only input devices."
- (let* ((output (test-load-fixture "pactl-output-inputs-only.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (= 3 (length result)))
- ;; None should end with .monitor
- (dolist (device result)
- (should-not (string-suffix-p ".monitor" (car device))))))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-empty-string-returns-empty-list ()
- "Test parsing empty string returns empty list."
- (let ((result (cj/recording--parse-pactl-output "")))
- (should (listp result))
- (should (null result))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-empty-file-returns-empty-list ()
- "Test parsing empty file returns empty list."
- (let* ((output (test-load-fixture "pactl-output-empty.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (null result))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-whitespace-only-returns-empty-list ()
- "Test parsing whitespace-only string returns empty list."
- (let ((result (cj/recording--parse-pactl-output " \n\t\n ")))
- (should (listp result))
- (should (null result))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-single-newline-returns-empty-list ()
- "Test parsing single newline returns empty list."
- (let ((result (cj/recording--parse-pactl-output "\n")))
- (should (listp result))
- (should (null result))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-device-with-running-state-parsed ()
- "Test that RUNNING state (not just SUSPENDED) is parsed correctly."
- (let* ((output "81\tbluez_output.00_1B_66_C0_91_6D.1.monitor\tPipeWire\ts24le 2ch 48000Hz\tRUNNING\n")
- (result (cj/recording--parse-pactl-output output)))
- (should (= 1 (length result)))
- (should (equal "RUNNING" (nth 2 (car result))))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-boundary-device-with-idle-state-parsed ()
- "Test that IDLE state is parsed correctly."
- (let* ((output "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tIDLE\n")
- (result (cj/recording--parse-pactl-output output)))
- (should (= 1 (length result)))
- (should (equal "IDLE" (nth 2 (car result))))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-error-malformed-lines-ignored ()
- "Test that malformed lines are silently ignored."
- (let* ((output (test-load-fixture "pactl-output-malformed.txt"))
- (result (cj/recording--parse-pactl-output output)))
- (should (listp result))
- (should (null result)))) ; All lines malformed, so empty list
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-error-mixed-valid-invalid-returns-valid ()
- "Test that mix of valid and invalid lines returns only valid ones."
- (let* ((output (concat "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- "This is invalid\n"
- "79\tbluez_input.00:1B:66:C0:91:6D\tPipeWire\tfloat32le 1ch 48000Hz\tSUSPENDED\n"
- "Also invalid\n"))
- (result (cj/recording--parse-pactl-output output)))
- (should (= 2 (length result)))
- (should (equal "alsa_input.pci-0000_00_1f.3.analog-stereo" (car (nth 0 result))))
- (should (equal "bluez_input.00:1B:66:C0:91:6D" (car (nth 1 result))))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-error-missing-fields-ignored ()
- "Test that lines with missing fields are ignored."
- (let* ((output "50\tincomplete-line\tPipeWire\n") ; Missing state and format
- (result (cj/recording--parse-pactl-output output)))
- (should (null result))))
-
-(ert-deftest test-video-audio-recording-parse-pactl-output-error-nil-input-returns-error ()
- "Test that nil input signals an error."
- (should-error (cj/recording--parse-pactl-output nil)))
-
-(provide 'test-video-audio-recording-parse-pactl-output)
-;;; test-video-audio-recording-parse-pactl-output.el ends here
diff --git a/tests/test-video-audio-recording-parse-sources.el b/tests/test-video-audio-recording-parse-sources.el
deleted file mode 100644
index d6d445b5..00000000
--- a/tests/test-video-audio-recording-parse-sources.el
+++ /dev/null
@@ -1,98 +0,0 @@
-;;; test-video-audio-recording-parse-sources.el --- Tests for cj/recording-parse-sources -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-parse-sources function.
-;; Tests the wrapper that calls pactl and delegates to internal parser.
-;; Mocks shell-command-to-string to avoid system dependencies.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Test Fixtures Helper
-
-(defun test-load-fixture (filename)
- "Load fixture file FILENAME from tests/fixtures directory."
- (let ((fixture-path (expand-file-name
- (concat "tests/fixtures/" filename)
- user-emacs-directory)))
- (with-temp-buffer
- (insert-file-contents fixture-path)
- (buffer-string))))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-parse-sources-normal-calls-pactl-and-parses ()
- "Test that parse-sources calls shell command and returns parsed list."
- (let ((fixture-output (test-load-fixture "pactl-output-normal.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) fixture-output)))
- (let ((result (cj/recording-parse-sources)))
- (should (listp result))
- (should (= 6 (length result)))
- ;; Verify it returns structured data
- (should (equal "alsa_output.pci-0000_00_1f.3.analog-stereo.monitor"
- (car (nth 0 result))))
- (should (equal "PipeWire" (nth 1 (nth 0 result))))
- (should (equal "SUSPENDED" (nth 2 (nth 0 result))))))))
-
-(ert-deftest test-video-audio-recording-parse-sources-normal-single-device-returns-list ()
- "Test parse-sources with single device."
- (let ((fixture-output (test-load-fixture "pactl-output-single.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) fixture-output)))
- (let ((result (cj/recording-parse-sources)))
- (should (listp result))
- (should (= 1 (length result)))))))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-parse-sources-boundary-empty-output-returns-empty-list ()
- "Test that empty pactl output returns empty list."
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) "")))
- (let ((result (cj/recording-parse-sources)))
- (should (listp result))
- (should (null result)))))
-
-(ert-deftest test-video-audio-recording-parse-sources-boundary-whitespace-output-returns-empty-list ()
- "Test that whitespace-only output returns empty list."
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) " \n\t\n ")))
- (let ((result (cj/recording-parse-sources)))
- (should (listp result))
- (should (null result)))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-parse-sources-error-malformed-output-returns-empty-list ()
- "Test that malformed output is handled gracefully."
- (let ((fixture-output (test-load-fixture "pactl-output-malformed.txt")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) fixture-output)))
- (let ((result (cj/recording-parse-sources)))
- (should (listp result))
- (should (null result))))))
-
-(ert-deftest test-video-audio-recording-parse-sources-error-mixed-valid-invalid-returns-valid-only ()
- "Test that mix of valid and invalid lines returns only valid entries."
- (let ((mixed-output (concat
- "50\talsa_input.pci-0000_00_1f.3.analog-stereo\tPipeWire\ts32le 2ch 48000Hz\tSUSPENDED\n"
- "invalid line\n"
- "79\tbluez_input.00:1B:66:C0:91:6D\tPipeWire\tfloat32le 1ch 48000Hz\tRUNNING\n")))
- (cl-letf (((symbol-function 'shell-command-to-string)
- (lambda (_cmd) mixed-output)))
- (let ((result (cj/recording-parse-sources)))
- (should (= 2 (length result)))
- (should (equal "alsa_input.pci-0000_00_1f.3.analog-stereo" (car (nth 0 result))))
- (should (equal "bluez_input.00:1B:66:C0:91:6D" (car (nth 1 result))))))))
-
-(provide 'test-video-audio-recording-parse-sources)
-;;; test-video-audio-recording-parse-sources.el ends here
diff --git a/tests/test-video-audio-recording-process-sentinel.el b/tests/test-video-audio-recording-process-sentinel.el
deleted file mode 100644
index 37a7f94d..00000000
--- a/tests/test-video-audio-recording-process-sentinel.el
+++ /dev/null
@@ -1,190 +0,0 @@
-;;; test-video-audio-recording-process-sentinel.el --- Tests for cj/recording-process-sentinel -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-process-sentinel function.
-;; Tests process cleanup and modeline update when recording processes exit.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-sentinel-setup ()
- "Reset process variables before each test."
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/video-recording-ffmpeg-process nil))
-
-(defun test-sentinel-teardown ()
- "Clean up process variables after each test."
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/video-recording-ffmpeg-process nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-process-sentinel-normal-audio-exit-clears-variable ()
- "Test that sentinel clears audio process variable when process exits."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock process-status to return 'exit
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'exit)))
- ;; Call sentinel with exit status
- (cj/recording-process-sentinel fake-process "finished\n")
- ;; Variable should be cleared
- (should (null cj/audio-recording-ffmpeg-process))))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-normal-video-exit-clears-variable ()
- "Test that sentinel clears video process variable when process exits."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-video" :command '("sh" "-c" "exit 0"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- ;; Mock process-status to return 'exit
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'exit)))
- ;; Call sentinel with exit status
- (cj/recording-process-sentinel fake-process "finished\n")
- ;; Variable should be cleared
- (should (null cj/video-recording-ffmpeg-process))))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-normal-signal-status-clears-variable ()
- "Test that sentinel clears variable on signal status (killed)."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (delete-process fake-process)
- ;; Call sentinel with signal status
- (cj/recording-process-sentinel fake-process "killed\n")
- ;; Variable should be cleared
- (should (null cj/audio-recording-ffmpeg-process)))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-normal-modeline-update-called ()
- "Test that sentinel triggers modeline update."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0")))
- (update-called nil))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock force-mode-line-update to track if it's called
- (cl-letf (((symbol-function 'force-mode-line-update)
- (lambda (&optional _all) (setq update-called t))))
- (cj/recording-process-sentinel fake-process "finished\n")
- (should update-called)))
- (test-sentinel-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-process-sentinel-boundary-run-status-ignored ()
- "Test that sentinel ignores processes in 'run status (still running)."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock process-status to return 'run
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'run)))
- (cj/recording-process-sentinel fake-process "run")
- ;; Variable should NOT be cleared
- (should (eq fake-process cj/audio-recording-ffmpeg-process)))
- (delete-process fake-process))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-boundary-open-status-ignored ()
- "Test that sentinel ignores processes in 'open status."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'open)))
- (cj/recording-process-sentinel fake-process "open")
- ;; Variable should NOT be cleared
- (should (eq fake-process cj/audio-recording-ffmpeg-process)))
- (delete-process fake-process))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-boundary-event-trimmed ()
- "Test that event string is trimmed in message."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0")))
- (message-text nil))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock message to capture output
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (setq message-text (apply #'format fmt args)))))
- (cj/recording-process-sentinel fake-process " finished \n")
- ;; Message should contain trimmed event
- (should (string-match-p "finished" message-text))
- ;; Should not have extra whitespace
- (should-not (string-match-p " finished " message-text))))
- (test-sentinel-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-process-sentinel-error-unknown-process-ignored ()
- "Test that sentinel handles unknown process (not audio or video) gracefully."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-unknown" :command '("sh" "-c" "exit 0")))
- (audio-proc (make-process :name "test-audio" :command '("sleep" "1000")))
- (video-proc (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process audio-proc)
- (setq cj/video-recording-ffmpeg-process video-proc)
- ;; Call sentinel with unknown process
- (cj/recording-process-sentinel fake-process "finished\n")
- ;; Audio and video variables should NOT be cleared
- (should (eq audio-proc cj/audio-recording-ffmpeg-process))
- (should (eq video-proc cj/video-recording-ffmpeg-process))
- (delete-process audio-proc)
- (delete-process video-proc))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-error-nil-event-handled ()
- "Test that sentinel handles nil event string gracefully."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock process-status to return 'exit
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'exit)))
- ;; Should not crash with nil event (string-trim will error, but that's caught)
- ;; The function uses string-trim without protection, so this will error
- ;; Testing that it doesn't crash means we expect an error
- (should-error
- (cj/recording-process-sentinel fake-process nil))))
- (test-sentinel-teardown)))
-
-(ert-deftest test-video-audio-recording-process-sentinel-error-empty-event-handled ()
- "Test that sentinel handles empty event string gracefully."
- (test-sentinel-setup)
- (unwind-protect
- (let ((fake-process (make-process :name "test-audio" :command '("sh" "-c" "exit 0"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- ;; Mock process-status to return 'exit
- (cl-letf (((symbol-function 'process-status)
- (lambda (_proc) 'exit)))
- ;; Empty string is fine - string-trim handles it
- ;; No error should be raised
- (cj/recording-process-sentinel fake-process "")
- ;; Variable should be cleared
- (should (null cj/audio-recording-ffmpeg-process))))
- (test-sentinel-teardown)))
-
-(provide 'test-video-audio-recording-process-sentinel)
-;;; test-video-audio-recording-process-sentinel.el ends here
diff --git a/tests/test-video-audio-recording-quick-setup-for-calls.el b/tests/test-video-audio-recording-quick-setup-for-calls.el
deleted file mode 100644
index 0d3fe53a..00000000
--- a/tests/test-video-audio-recording-quick-setup-for-calls.el
+++ /dev/null
@@ -1,144 +0,0 @@
-;;; test-video-audio-recording-quick-setup-for-calls.el --- Tests for cj/recording-quick-setup-for-calls -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-quick-setup-for-calls function.
-;; Tests quick device setup workflow for call recording.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-quick-setup-setup ()
- "Reset device variables before each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-(defun test-quick-setup-teardown ()
- "Clean up device variables after each test."
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-sets-both-devices ()
- "Test that function sets both mic and system device variables."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Bluetooth Headset" . ("bluez_input.00:1B:66" . "bluez_output.00_1B_66.monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Bluetooth Headset")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "bluez_input.00:1B:66" cj/recording-mic-device))
- (should (equal "bluez_output.00_1B_66.monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-presents-friendly-names ()
- "Test that function presents friendly device names to user."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Jabra SPEAK 510 USB" . ("usb-input" . "usb-monitor"))
- ("Built-in Laptop Audio" . ("pci-input" . "pci-monitor"))))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (car choices))))
- (cj/recording-quick-setup-for-calls)
- (should (member "Jabra SPEAK 510 USB" presented-choices))
- (should (member "Built-in Laptop Audio" presented-choices))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-normal-displays-confirmation ()
- "Test that function displays confirmation message with device details."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Bluetooth Headset" . ("bluez_input.00:1B:66" . "bluez_output.00_1B_66.monitor"))))
- (message-text nil))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Bluetooth Headset"))
- ((symbol-function 'message)
- (lambda (fmt &rest args) (setq message-text (apply #'format fmt args)))))
- (cj/recording-quick-setup-for-calls)
- (should (string-match-p "Call recording ready" message-text))
- (should (string-match-p "Bluetooth Headset" message-text))))
- (test-quick-setup-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-boundary-single-device-no-prompt ()
- "Test that with single device, selection still happens."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Built-in Laptop Audio" . ("pci-input" . "pci-monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Built-in Laptop Audio")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "pci-input" cj/recording-mic-device))
- (should (equal "pci-monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-boundary-device-name-with-special-chars ()
- "Test that device names with special characters are handled correctly."
- (test-quick-setup-setup)
- (unwind-protect
- (let ((grouped-devices '(("Device (USB-C)" . ("special-input" . "special-monitor")))))
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () grouped-devices))
- ((symbol-function 'completing-read)
- (lambda (_prompt _choices &rest _args) "Device (USB-C)")))
- (cj/recording-quick-setup-for-calls)
- (should (equal "special-input" cj/recording-mic-device))
- (should (equal "special-monitor" cj/recording-system-device))))
- (test-quick-setup-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-no-devices-signals-error ()
- "Test that function signals user-error when no complete devices are found."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () nil)))
- (should-error (cj/recording-quick-setup-for-calls) :type 'user-error))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-message-mentions-both-devices ()
- "Test that error message mentions need for both mic and monitor."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () nil)))
- (condition-case err
- (cj/recording-quick-setup-for-calls)
- (user-error
- (should (string-match-p "both mic and monitor" (error-message-string err))))))
- (test-quick-setup-teardown)))
-
-(ert-deftest test-video-audio-recording-quick-setup-for-calls-error-empty-device-list ()
- "Test that empty device list from grouping is handled gracefully."
- (test-quick-setup-setup)
- (unwind-protect
- (cl-letf (((symbol-function 'cj/recording-group-devices-by-hardware)
- (lambda () '())))
- (should-error (cj/recording-quick-setup-for-calls) :type 'user-error))
- (test-quick-setup-teardown)))
-
-(provide 'test-video-audio-recording-quick-setup-for-calls)
-;;; test-video-audio-recording-quick-setup-for-calls.el ends here
diff --git a/tests/test-video-audio-recording-select-device.el b/tests/test-video-audio-recording-select-device.el
deleted file mode 100644
index 53b1e665..00000000
--- a/tests/test-video-audio-recording-select-device.el
+++ /dev/null
@@ -1,165 +0,0 @@
-;;; test-video-audio-recording-select-device.el --- Tests for cj/recording-select-device -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-select-device function.
-;; Tests interactive device selection with filtering.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-select-device-normal-returns-selected-mic ()
- "Test that function returns selected microphone device."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED")
- ("alsa_output.pci-device.monitor" "PipeWire" "SUSPENDED"))))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- ;; Select the first choice
- (caar choices))))
- (let ((result (cj/recording-select-device "Select mic: " 'mic)))
- (should (stringp result))
- (should (equal "alsa_input.pci-device" result))))))
-
-(ert-deftest test-video-audio-recording-select-device-normal-returns-selected-monitor ()
- "Test that function returns selected monitor device."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED")
- ("alsa_output.pci-device.monitor" "PipeWire" "SUSPENDED"))))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (caar choices))))
- (let ((result (cj/recording-select-device "Select monitor: " 'monitor)))
- (should (stringp result))
- (should (equal "alsa_output.pci-device.monitor" result))))))
-
-(ert-deftest test-video-audio-recording-select-device-normal-filters-monitors-for-mic ()
- "Test that function filters out monitor devices when selecting mic."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED")
- ("alsa_output.pci-device.monitor" "PipeWire" "SUSPENDED")
- ("bluez_input.00:1B:66" "PipeWire" "RUNNING")))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (caar choices))))
- (cj/recording-select-device "Select mic: " 'mic)
- ;; Should have 2 mic devices (not the monitor)
- (should (= 2 (length presented-choices)))
- (should-not (cl-some (lambda (choice) (string-match-p "\\.monitor" (car choice)))
- presented-choices)))))
-
-(ert-deftest test-video-audio-recording-select-device-normal-filters-non-monitors-for-monitor ()
- "Test that function filters out non-monitor devices when selecting monitor."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED")
- ("alsa_output.pci-device.monitor" "PipeWire" "SUSPENDED")
- ("bluez_output.00_1B_66.1.monitor" "PipeWire" "RUNNING")))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (caar choices))))
- (cj/recording-select-device "Select monitor: " 'monitor)
- ;; Should have 2 monitor devices (not the input)
- (should (= 2 (length presented-choices)))
- (should (cl-every (lambda (choice) (string-match-p "\\.monitor" (car choice)))
- presented-choices)))))
-
-(ert-deftest test-video-audio-recording-select-device-normal-shows-friendly-state ()
- "Test that function shows friendly state in choices."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED")))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (caar choices))))
- (cj/recording-select-device "Select mic: " 'mic)
- ;; Choice should contain "Ready" (friendly for SUSPENDED)
- (should (string-match-p "Ready" (caar presented-choices))))))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-select-device-boundary-single-device ()
- "Test that function works with single device."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED"))))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (caar choices))))
- (let ((result (cj/recording-select-device "Select mic: " 'mic)))
- (should (equal "alsa_input.pci-device" result))))))
-
-(ert-deftest test-video-audio-recording-select-device-boundary-multiple-states ()
- "Test that function handles devices in different states."
- (let ((sources '(("alsa_input.device1" "PipeWire" "SUSPENDED")
- ("alsa_input.device2" "PipeWire" "RUNNING")
- ("alsa_input.device3" "PipeWire" "IDLE")))
- (presented-choices nil))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources))
- ((symbol-function 'completing-read)
- (lambda (_prompt choices &rest _args)
- (setq presented-choices choices)
- (caar choices))))
- (cj/recording-select-device "Select mic: " 'mic)
- ;; All three should be presented
- (should (= 3 (length presented-choices)))
- ;; Check that friendly states appear
- (let ((choice-text (mapconcat #'car presented-choices " ")))
- (should (string-match-p "Ready\\|Active" choice-text))))))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-select-device-error-no-mic-devices-signals-error ()
- "Test that function signals user-error when no mic devices found."
- (let ((sources '(("alsa_output.pci-device.monitor" "PipeWire" "SUSPENDED"))))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources)))
- (should-error (cj/recording-select-device "Select mic: " 'mic) :type 'user-error))))
-
-(ert-deftest test-video-audio-recording-select-device-error-no-monitor-devices-signals-error ()
- "Test that function signals user-error when no monitor devices found."
- (let ((sources '(("alsa_input.pci-device" "PipeWire" "SUSPENDED"))))
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () sources)))
- (should-error (cj/recording-select-device "Select monitor: " 'monitor) :type 'user-error))))
-
-(ert-deftest test-video-audio-recording-select-device-error-empty-source-list ()
- "Test that function signals user-error when source list is empty."
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () nil)))
- (should-error (cj/recording-select-device "Select mic: " 'mic) :type 'user-error)))
-
-(ert-deftest test-video-audio-recording-select-device-error-message-mentions-device-type ()
- "Test that error message mentions the device type being searched for."
- (cl-letf (((symbol-function 'cj/recording-parse-sources)
- (lambda () nil)))
- (condition-case err
- (cj/recording-select-device "Select mic: " 'mic)
- (user-error
- (should (string-match-p "input" (error-message-string err)))))
- (condition-case err
- (cj/recording-select-device "Select monitor: " 'monitor)
- (user-error
- (should (string-match-p "monitor" (error-message-string err)))))))
-
-(provide 'test-video-audio-recording-select-device)
-;;; test-video-audio-recording-select-device.el ends here
diff --git a/tests/test-video-audio-recording-test-mic.el b/tests/test-video-audio-recording-test-mic.el
deleted file mode 100644
index 5aa794bb..00000000
--- a/tests/test-video-audio-recording-test-mic.el
+++ /dev/null
@@ -1,147 +0,0 @@
-;;; test-video-audio-recording-test-mic.el --- Tests for cj/recording-test-mic -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-test-mic function.
-;; Tests microphone testing functionality.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-mic-setup ()
- "Reset device variables before each test."
- (setq cj/recording-mic-device nil))
-
-(defun test-mic-teardown ()
- "Clean up device variables after each test."
- (setq cj/recording-mic-device nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-test-mic-normal-creates-temp-wav-file ()
- "Test that function creates temp file with .wav extension."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "test-mic-device")
- (let ((temp-file nil))
- ;; Mock make-temp-file to capture filename
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (prefix _dir-flag suffix)
- (setq temp-file (concat prefix "12345" suffix))
- temp-file))
- ((symbol-function 'shell-command)
- (lambda (_cmd) 0)))
- (cj/recording-test-mic)
- (should (string-match-p "\\.wav$" temp-file)))))
- (test-mic-teardown)))
-
-(ert-deftest test-video-audio-recording-test-mic-normal-runs-ffmpeg-command ()
- "Test that function runs ffmpeg command with configured mic device."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "test-mic-device")
- (let ((commands nil))
- ;; Mock shell-command to capture all commands
- (cl-letf (((symbol-function 'shell-command)
- (lambda (cmd) (push cmd commands) 0)))
- (cj/recording-test-mic)
- (should (= 2 (length commands)))
- ;; First command should be ffmpeg (stored last in list due to push)
- (let ((ffmpeg-cmd (cadr commands)))
- (should (stringp ffmpeg-cmd))
- (should (string-match-p "ffmpeg" ffmpeg-cmd))
- (should (string-match-p "test-mic-device" ffmpeg-cmd))
- (should (string-match-p "-t 5" ffmpeg-cmd))))))
- (test-mic-teardown)))
-
-(ert-deftest test-video-audio-recording-test-mic-normal-runs-ffplay-for-playback ()
- "Test that function runs ffplay for playback."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "test-mic-device")
- (let ((commands nil))
- ;; Capture all shell commands
- (cl-letf (((symbol-function 'shell-command)
- (lambda (cmd) (push cmd commands) 0)))
- (cj/recording-test-mic)
- (should (= 2 (length commands)))
- ;; Second command should be ffplay
- (should (string-match-p "ffplay" (car commands)))
- (should (string-match-p "-autoexit" (car commands))))))
- (test-mic-teardown)))
-
-(ert-deftest test-video-audio-recording-test-mic-normal-displays-messages ()
- "Test that function displays appropriate messages to user."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "test-mic-device")
- (let ((messages nil))
- ;; Capture messages
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages)))
- ((symbol-function 'shell-command)
- (lambda (_cmd) 0)))
- (cj/recording-test-mic)
- (should (>= (length messages) 3))
- ;; Check for recording message
- (should (cl-some (lambda (msg) (string-match-p "Recording.*SPEAK NOW" msg)) messages))
- ;; Check for playback message
- (should (cl-some (lambda (msg) (string-match-p "Playing back" msg)) messages))
- ;; Check for complete message
- (should (cl-some (lambda (msg) (string-match-p "complete" msg)) messages)))))
- (test-mic-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-test-mic-error-no-mic-configured-signals-error ()
- "Test that function signals user-error when mic device is not configured."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device nil)
- (should-error (cj/recording-test-mic) :type 'user-error))
- (test-mic-teardown)))
-
-(ert-deftest test-video-audio-recording-test-mic-error-message-mentions-setup ()
- "Test that error message guides user to run setup."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device nil)
- (condition-case err
- (cj/recording-test-mic)
- (user-error
- (should (string-match-p "C-; r c" (error-message-string err))))))
- (test-mic-teardown)))
-
-(ert-deftest test-video-audio-recording-test-mic-error-ffmpeg-failure-handled ()
- "Test that ffmpeg command failure is handled gracefully."
- (test-mic-setup)
- (unwind-protect
- (progn
- (setq cj/recording-mic-device "test-mic-device")
- ;; Mock shell-command to fail
- (cl-letf (((symbol-function 'shell-command)
- (lambda (_cmd) 1))) ;; Non-zero exit code
- ;; Should complete without crashing (ffmpeg errors are ignored)
- ;; No error is raised - function just completes
- (cj/recording-test-mic)
- ;; Test passes if we get here
- (should t)))
- (test-mic-teardown)))
-
-(provide 'test-video-audio-recording-test-mic)
-;;; test-video-audio-recording-test-mic.el ends here
diff --git a/tests/test-video-audio-recording-test-monitor.el b/tests/test-video-audio-recording-test-monitor.el
deleted file mode 100644
index f1476577..00000000
--- a/tests/test-video-audio-recording-test-monitor.el
+++ /dev/null
@@ -1,148 +0,0 @@
-;;; test-video-audio-recording-test-monitor.el --- Tests for cj/recording-test-monitor -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/recording-test-monitor function.
-;; Tests system audio monitor testing functionality.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-monitor-setup ()
- "Reset device variables before each test."
- (setq cj/recording-system-device nil))
-
-(defun test-monitor-teardown ()
- "Clean up device variables after each test."
- (setq cj/recording-system-device nil))
-
-;;; Normal Cases
-
-(ert-deftest test-video-audio-recording-test-monitor-normal-creates-temp-wav-file ()
- "Test that function creates temp file with .wav extension."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device "test-monitor-device")
- (let ((temp-file nil))
- ;; Mock make-temp-file to capture filename
- (cl-letf (((symbol-function 'make-temp-file)
- (lambda (prefix _dir-flag suffix)
- (setq temp-file (concat prefix "12345" suffix))
- temp-file))
- ((symbol-function 'shell-command)
- (lambda (_cmd) 0)))
- (cj/recording-test-monitor)
- (should (string-match-p "monitor-test-" temp-file))
- (should (string-match-p "\\.wav$" temp-file)))))
- (test-monitor-teardown)))
-
-(ert-deftest test-video-audio-recording-test-monitor-normal-runs-ffmpeg-command ()
- "Test that function runs ffmpeg command with configured monitor device."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device "test-monitor-device")
- (let ((commands nil))
- ;; Mock shell-command to capture all commands
- (cl-letf (((symbol-function 'shell-command)
- (lambda (cmd) (push cmd commands) 0)))
- (cj/recording-test-monitor)
- (should (= 2 (length commands)))
- ;; First command should be ffmpeg (stored last in list due to push)
- (let ((ffmpeg-cmd (cadr commands)))
- (should (stringp ffmpeg-cmd))
- (should (string-match-p "ffmpeg" ffmpeg-cmd))
- (should (string-match-p "test-monitor-device" ffmpeg-cmd))
- (should (string-match-p "-t 5" ffmpeg-cmd))))))
- (test-monitor-teardown)))
-
-(ert-deftest test-video-audio-recording-test-monitor-normal-runs-ffplay-for-playback ()
- "Test that function runs ffplay for playback."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device "test-monitor-device")
- (let ((commands nil))
- ;; Capture all shell commands
- (cl-letf (((symbol-function 'shell-command)
- (lambda (cmd) (push cmd commands) 0)))
- (cj/recording-test-monitor)
- (should (= 2 (length commands)))
- ;; Second command should be ffplay
- (should (string-match-p "ffplay" (car commands)))
- (should (string-match-p "-autoexit" (car commands))))))
- (test-monitor-teardown)))
-
-(ert-deftest test-video-audio-recording-test-monitor-normal-displays-messages ()
- "Test that function displays appropriate messages to user."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device "test-monitor-device")
- (let ((messages nil))
- ;; Capture messages
- (cl-letf (((symbol-function 'message)
- (lambda (fmt &rest args) (push (apply #'format fmt args) messages)))
- ((symbol-function 'shell-command)
- (lambda (_cmd) 0)))
- (cj/recording-test-monitor)
- (should (>= (length messages) 3))
- ;; Check for recording message
- (should (cl-some (lambda (msg) (string-match-p "Recording.*PLAY SOMETHING" msg)) messages))
- ;; Check for playback message
- (should (cl-some (lambda (msg) (string-match-p "Playing back" msg)) messages))
- ;; Check for complete message
- (should (cl-some (lambda (msg) (string-match-p "complete" msg)) messages)))))
- (test-monitor-teardown)))
-
-;;; Error Cases
-
-(ert-deftest test-video-audio-recording-test-monitor-error-no-monitor-configured-signals-error ()
- "Test that function signals user-error when monitor device is not configured."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device nil)
- (should-error (cj/recording-test-monitor) :type 'user-error))
- (test-monitor-teardown)))
-
-(ert-deftest test-video-audio-recording-test-monitor-error-message-mentions-setup ()
- "Test that error message guides user to run setup."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device nil)
- (condition-case err
- (cj/recording-test-monitor)
- (user-error
- (should (string-match-p "C-; r c" (error-message-string err))))))
- (test-monitor-teardown)))
-
-(ert-deftest test-video-audio-recording-test-monitor-error-ffmpeg-failure-handled ()
- "Test that ffmpeg command failure is handled gracefully."
- (test-monitor-setup)
- (unwind-protect
- (progn
- (setq cj/recording-system-device "test-monitor-device")
- ;; Mock shell-command to fail
- (cl-letf (((symbol-function 'shell-command)
- (lambda (_cmd) 1))) ;; Non-zero exit code
- ;; Should complete without crashing (ffmpeg errors are ignored)
- ;; No error is raised - function just completes
- (cj/recording-test-monitor)
- ;; Test passes if we get here
- (should t)))
- (test-monitor-teardown)))
-
-(provide 'test-video-audio-recording-test-monitor)
-;;; test-video-audio-recording-test-monitor.el ends here
diff --git a/tests/test-video-audio-recording-toggle-functions.el b/tests/test-video-audio-recording-toggle-functions.el
deleted file mode 100644
index 2355ab4f..00000000
--- a/tests/test-video-audio-recording-toggle-functions.el
+++ /dev/null
@@ -1,185 +0,0 @@
-;;; test-video-audio-recording-toggle-functions.el --- Tests for toggle functions -*- lexical-binding: t; -*-
-
-;;; Commentary:
-;; Unit tests for cj/video-recording-toggle and cj/audio-recording-toggle functions.
-;; Tests start/stop toggle behavior for recording processes.
-
-;;; Code:
-
-(require 'ert)
-
-;; Stub dependencies before loading the module
-(defvar cj/custom-keymap (make-sparse-keymap)
- "Stub keymap for testing.")
-
-;; Stub directory variables
-(defvar video-recordings-dir "/tmp/video-recordings/")
-(defvar audio-recordings-dir "/tmp/audio-recordings/")
-
-;; Now load the actual production module
-(require 'video-audio-recording)
-
-;;; Setup and Teardown
-
-(defun test-toggle-setup ()
- "Reset process variables before each test."
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device "test-mic")
- (setq cj/recording-system-device "test-monitor"))
-
-(defun test-toggle-teardown ()
- "Clean up process variables after each test."
- (when cj/video-recording-ffmpeg-process
- (ignore-errors (delete-process cj/video-recording-ffmpeg-process)))
- (when cj/audio-recording-ffmpeg-process
- (ignore-errors (delete-process cj/audio-recording-ffmpeg-process)))
- (setq cj/video-recording-ffmpeg-process nil)
- (setq cj/audio-recording-ffmpeg-process nil)
- (setq cj/recording-mic-device nil)
- (setq cj/recording-system-device nil))
-
-;;; Video Toggle - Normal Cases
-
-(ert-deftest test-video-audio-recording-video-toggle-normal-starts-when-not-recording ()
- "Test that video toggle starts recording when not currently recording."
- (test-toggle-setup)
- (unwind-protect
- (let ((start-called nil))
- (cl-letf (((symbol-function 'cj/ffmpeg-record-video)
- (lambda (_dir) (setq start-called t))))
- (cj/video-recording-toggle nil)
- (should start-called)))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-video-toggle-normal-stops-when-recording ()
- "Test that video toggle stops recording when currently recording."
- (test-toggle-setup)
- (unwind-protect
- (let ((stop-called nil)
- (fake-process (make-process :name "test-video" :command '("sleep" "1000"))))
- (setq cj/video-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'cj/video-recording-stop)
- (lambda () (setq stop-called t))))
- (cj/video-recording-toggle nil)
- (should stop-called))
- (ignore-errors (delete-process fake-process)))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-video-toggle-normal-uses-default-directory ()
- "Test that video toggle uses default directory when no prefix arg."
- (test-toggle-setup)
- (unwind-protect
- (let ((recorded-dir nil))
- (cl-letf (((symbol-function 'cj/ffmpeg-record-video)
- (lambda (dir) (setq recorded-dir dir))))
- (cj/video-recording-toggle nil)
- (should (equal video-recordings-dir recorded-dir))))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-video-toggle-normal-prompts-for-location-with-prefix ()
- "Test that video toggle prompts for location with prefix arg."
- (test-toggle-setup)
- (unwind-protect
- (let ((prompt-called nil)
- (recorded-dir nil))
- (cl-letf (((symbol-function 'read-directory-name)
- (lambda (_prompt) (setq prompt-called t) "/custom/path/"))
- ((symbol-function 'file-directory-p)
- (lambda (_dir) t)) ; Directory exists
- ((symbol-function 'cj/ffmpeg-record-video)
- (lambda (dir) (setq recorded-dir dir))))
- (cj/video-recording-toggle t)
- (should prompt-called)
- (should (equal "/custom/path/" recorded-dir))))
- (test-toggle-teardown)))
-
-;;; Audio Toggle - Normal Cases
-
-(ert-deftest test-video-audio-recording-audio-toggle-normal-starts-when-not-recording ()
- "Test that audio toggle starts recording when not currently recording."
- (test-toggle-setup)
- (unwind-protect
- (let ((start-called nil))
- (cl-letf (((symbol-function 'cj/ffmpeg-record-audio)
- (lambda (_dir) (setq start-called t))))
- (cj/audio-recording-toggle nil)
- (should start-called)))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-toggle-normal-stops-when-recording ()
- "Test that audio toggle stops recording when currently recording."
- (test-toggle-setup)
- (unwind-protect
- (let ((stop-called nil)
- (fake-process (make-process :name "test-audio" :command '("sleep" "1000"))))
- (setq cj/audio-recording-ffmpeg-process fake-process)
- (cl-letf (((symbol-function 'cj/audio-recording-stop)
- (lambda () (setq stop-called t))))
- (cj/audio-recording-toggle nil)
- (should stop-called))
- (ignore-errors (delete-process fake-process)))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-toggle-normal-uses-default-directory ()
- "Test that audio toggle uses default directory when no prefix arg."
- (test-toggle-setup)
- (unwind-protect
- (let ((recorded-dir nil))
- (cl-letf (((symbol-function 'cj/ffmpeg-record-audio)
- (lambda (dir) (setq recorded-dir dir))))
- (cj/audio-recording-toggle nil)
- (should (equal audio-recordings-dir recorded-dir))))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-toggle-normal-prompts-for-location-with-prefix ()
- "Test that audio toggle prompts for location with prefix arg."
- (test-toggle-setup)
- (unwind-protect
- (let ((prompt-called nil)
- (recorded-dir nil))
- (cl-letf (((symbol-function 'read-directory-name)
- (lambda (_prompt) (setq prompt-called t) "/custom/path/"))
- ((symbol-function 'file-directory-p)
- (lambda (_dir) t)) ; Directory exists
- ((symbol-function 'cj/ffmpeg-record-audio)
- (lambda (dir) (setq recorded-dir dir))))
- (cj/audio-recording-toggle t)
- (should prompt-called)
- (should (equal "/custom/path/" recorded-dir))))
- (test-toggle-teardown)))
-
-;;; Boundary Cases
-
-(ert-deftest test-video-audio-recording-video-toggle-boundary-creates-directory ()
- "Test that video toggle creates directory if it doesn't exist."
- (test-toggle-setup)
- (unwind-protect
- (let ((mkdir-called nil))
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) nil))
- ((symbol-function 'make-directory)
- (lambda (_dir _parents) (setq mkdir-called t)))
- ((symbol-function 'cj/ffmpeg-record-video)
- (lambda (_dir) nil)))
- (cj/video-recording-toggle nil)
- (should mkdir-called)))
- (test-toggle-teardown)))
-
-(ert-deftest test-video-audio-recording-audio-toggle-boundary-creates-directory ()
- "Test that audio toggle creates directory if it doesn't exist."
- (test-toggle-setup)
- (unwind-protect
- (let ((mkdir-called nil))
- (cl-letf (((symbol-function 'file-directory-p)
- (lambda (_dir) nil))
- ((symbol-function 'make-directory)
- (lambda (_dir _parents) (setq mkdir-called t)))
- ((symbol-function 'cj/ffmpeg-record-audio)
- (lambda (_dir) nil)))
- (cj/audio-recording-toggle nil)
- (should mkdir-called)))
- (test-toggle-teardown)))
-
-(provide 'test-video-audio-recording-toggle-functions)
-;;; test-video-audio-recording-toggle-functions.el ends here