summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/custom-file-buffer.el79
-rw-r--r--tests/test-custom-file-buffer-move-buffer-and-file.el936
-rw-r--r--tests/test-custom-file-buffer-rename-buffer-and-file.el939
3 files changed, 1938 insertions, 16 deletions
diff --git a/modules/custom-file-buffer.el b/modules/custom-file-buffer.el
index 6ed19d73..a56edf18 100644
--- a/modules/custom-file-buffer.el
+++ b/modules/custom-file-buffer.el
@@ -81,35 +81,82 @@ Sends directly to the system spooler with no header."
;; ------------------------- Buffer And File Operations ------------------------
-(defun cj/move-buffer-and-file (dir)
- "Move both current buffer and the file it visits to DIR."
- (interactive "DMove buffer and file (to new directory): ")
+(defun cj/--move-buffer-and-file (dir &optional ok-if-exists)
+ "Internal implementation: Move buffer and file to DIR.
+If OK-IF-EXISTS is nil and target exists, signal an error.
+If OK-IF-EXISTS is non-nil, overwrite existing file.
+Returns t on success, nil if buffer not visiting a file."
(let* ((name (buffer-name))
(filename (buffer-file-name))
+ (dir (expand-file-name dir))
(dir
- (if (string-match dir "\\(?:/\\|\\\\)$")
+ (if (string-match "[/\\\\]$" dir)
(substring dir 0 -1) dir))
(newname (concat dir "/" name)))
(if (not filename)
- (message "Buffer '%s' is not visiting a file!" name)
- (progn (copy-file filename newname 1) (delete-file filename)
- (set-visited-file-name newname) (set-buffer-modified-p nil) t))))
+ (progn
+ (message "Buffer '%s' is not visiting a file!" name)
+ nil)
+ (progn (copy-file filename newname ok-if-exists)
+ (delete-file filename)
+ (set-visited-file-name newname)
+ (set-buffer-modified-p nil)
+ t))))
+
+(defun cj/move-buffer-and-file (dir)
+ "Move both current buffer and the file it visits to DIR.
+When called interactively, prompts for confirmation if target file exists."
+ (interactive (list (read-directory-name "Move buffer and file (to new directory): ")))
+ (let* ((target (expand-file-name (buffer-name) (expand-file-name dir))))
+ (condition-case err
+ (cj/--move-buffer-and-file dir nil)
+ (file-already-exists
+ (if (yes-or-no-p (format "File %s exists; overwrite? " target))
+ (cj/--move-buffer-and-file dir t)
+ (message "File not moved"))))))
+
+(defun cj/--rename-buffer-and-file (new-name &optional ok-if-exists)
+ "Internal implementation: Rename buffer and file to NEW-NAME.
+NEW-NAME can be just a basename or a full path to move to different directory.
+If OK-IF-EXISTS is nil and target exists, signal an error.
+If OK-IF-EXISTS is non-nil, overwrite existing file.
+Returns t on success, nil if buffer not visiting a file."
+ (let ((filename (buffer-file-name))
+ (new-basename (file-name-nondirectory new-name)))
+ (if (not filename)
+ (progn
+ (message "Buffer '%s' is not visiting a file!" (buffer-name))
+ nil)
+ ;; Check if a buffer with the new name already exists
+ (when (and (get-buffer new-basename)
+ (not (eq (get-buffer new-basename) (current-buffer))))
+ (error "A buffer named '%s' already exists" new-basename))
+ ;; Expand new-name to absolute path (preserves directory if just basename)
+ (let ((expanded-name (expand-file-name new-name
+ (file-name-directory filename))))
+ (rename-file filename expanded-name ok-if-exists)
+ (rename-buffer new-basename)
+ (set-visited-file-name expanded-name)
+ (set-buffer-modified-p nil)
+ t))))
(defun cj/rename-buffer-and-file (new-name)
- "Rename both current buffer and the file it visits to NEW-NAME."
+ "Rename both current buffer and the file it visits to NEW-NAME.
+When called interactively, prompts for confirmation if target file exists."
(interactive
(list (if (not (buffer-file-name))
(user-error "Buffer '%s' is not visiting a file!" (buffer-name))
(read-string "Rename buffer and file (to new name): "
(file-name-nondirectory (buffer-file-name))))))
- (let ((filename (buffer-file-name)))
- (if (get-buffer new-name)
- (message "A buffer named '%s' already exists!" new-name)
- (progn
- (rename-file filename new-name 1)
- (rename-buffer new-name)
- (set-visited-file-name new-name)
- (set-buffer-modified-p nil)))))
+ (condition-case err
+ (cj/--rename-buffer-and-file new-name nil)
+ (file-already-exists
+ (if (yes-or-no-p (format "File %s exists; overwrite? " new-name))
+ (cj/--rename-buffer-and-file new-name t)
+ (message "File not renamed")))
+ (error
+ ;; Handle buffer-already-exists and other errors
+ (message "%s" (error-message-string err)))))
(defun cj/delete-buffer-and-file ()
"Kill the current buffer and delete the file it visits."
diff --git a/tests/test-custom-file-buffer-move-buffer-and-file.el b/tests/test-custom-file-buffer-move-buffer-and-file.el
new file mode 100644
index 00000000..1fc16011
--- /dev/null
+++ b/tests/test-custom-file-buffer-move-buffer-and-file.el
@@ -0,0 +1,936 @@
+;;; test-custom-file-buffer-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-file-buffer.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-file-buffer)
+
+;;; 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-file-buffer-move-buffer-and-file)
+;;; test-custom-file-buffer-move-buffer-and-file.el ends here
diff --git a/tests/test-custom-file-buffer-rename-buffer-and-file.el b/tests/test-custom-file-buffer-rename-buffer-and-file.el
new file mode 100644
index 00000000..ca8acff8
--- /dev/null
+++ b/tests/test-custom-file-buffer-rename-buffer-and-file.el
@@ -0,0 +1,939 @@
+;;; test-custom-file-buffer-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-file-buffer.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-file-buffer)
+
+;;; 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-file-buffer-rename-buffer-and-file)
+;;; test-custom-file-buffer-rename-buffer-and-file.el ends here