From d77ca19cf7106a0eecbff1588c13b8b52b98b85f Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 27 Oct 2025 21:05:06 -0500 Subject: refactor: Rename custom-file-buffer to custom-buffer-file Renamed the module 'custom-file-buffer' to 'custom-buffer-file' to ensure consistency across the codebase. This change affects module imports and test files. Additionally, new module 'system-commands.el' has been created to handle system power and session management commands, removing these functionalities from 'wip.el'. --- init.el | 7 +- modules/custom-buffer-file.el | 260 ++++++ modules/custom-file-buffer.el | 260 ------ modules/system-commands.el | 138 +++ modules/wip.el | 154 ---- ...custom-buffer-file-clear-to-bottom-of-buffer.el | 163 ++++ ...st-custom-buffer-file-clear-to-top-of-buffer.el | 162 ++++ ...-custom-buffer-file-copy-link-to-buffer-file.el | 209 +++++ ...buffer-file-copy-path-to-buffer-file-as-kill.el | 205 +++++ tests/test-custom-buffer-file-copy-whole-buffer.el | 194 +++++ ...st-custom-buffer-file-delete-buffer-and-file.el | 671 +++++++++++++++ ...test-custom-buffer-file-move-buffer-and-file.el | 936 ++++++++++++++++++++ ...st-custom-buffer-file-rename-buffer-and-file.el | 939 +++++++++++++++++++++ ...custom-file-buffer-clear-to-bottom-of-buffer.el | 163 ---- ...st-custom-file-buffer-clear-to-top-of-buffer.el | 162 ---- ...-custom-file-buffer-copy-link-to-buffer-file.el | 209 ----- ...file-buffer-copy-path-to-buffer-file-as-kill.el | 205 ----- tests/test-custom-file-buffer-copy-whole-buffer.el | 194 ----- ...st-custom-file-buffer-delete-buffer-and-file.el | 671 --------------- ...test-custom-file-buffer-move-buffer-and-file.el | 936 -------------------- ...st-custom-file-buffer-rename-buffer-and-file.el | 939 --------------------- 21 files changed, 3881 insertions(+), 3896 deletions(-) create mode 100644 modules/custom-buffer-file.el delete mode 100644 modules/custom-file-buffer.el create mode 100644 modules/system-commands.el create mode 100644 tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el create mode 100644 tests/test-custom-buffer-file-clear-to-top-of-buffer.el create mode 100644 tests/test-custom-buffer-file-copy-link-to-buffer-file.el create mode 100644 tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el create mode 100644 tests/test-custom-buffer-file-copy-whole-buffer.el create mode 100644 tests/test-custom-buffer-file-delete-buffer-and-file.el create mode 100644 tests/test-custom-buffer-file-move-buffer-and-file.el create mode 100644 tests/test-custom-buffer-file-rename-buffer-and-file.el delete mode 100644 tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el delete mode 100644 tests/test-custom-file-buffer-clear-to-top-of-buffer.el delete mode 100644 tests/test-custom-file-buffer-copy-link-to-buffer-file.el delete mode 100644 tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el delete mode 100644 tests/test-custom-file-buffer-copy-whole-buffer.el delete mode 100644 tests/test-custom-file-buffer-delete-buffer-and-file.el delete mode 100644 tests/test-custom-file-buffer-move-buffer-and-file.el delete mode 100644 tests/test-custom-file-buffer-rename-buffer-and-file.el diff --git a/init.el b/init.el index 0fdc3dec..8fc37c4f 100644 --- a/init.el +++ b/init.el @@ -29,7 +29,7 @@ (require 'custom-case) ;; operations for upper/lower/title case (require 'custom-comments) ;; operations with comments (require 'custom-datetime) ;; date/timestamp insertion in various formats -(require 'custom-file-buffer) ;; custom buffer and file operations and keymap +(require 'custom-buffer-file) ;; custom buffer and file operations and keymap (require 'custom-line-paragraph) ;; operations on lines and paragraphs (require 'custom-misc) ;; miscellaneous functions (require 'custom-ordering) ;; ordering and sorting operations @@ -110,7 +110,7 @@ (require 'org-contacts-config) ;; fully integrated org-mode contacts management (require 'org-drill-config) (require 'org-export-config) -(require 'org-gcal-config) +(require 'org-gcal-config) ;; bi directional sync google calendar for org-agenda (require 'org-refile-config) ;; refile org-branches (require 'org-roam-config) ;; personal knowledge management in org mode (require 'org-webclipper) ;; "instapaper" to org-roam workflow @@ -134,7 +134,7 @@ ;; ------------------------- Personal Workflow Related ------------------------- (require 'reconcile-open-repos) -(require 'local-repository) +(require 'local-repository) ;; local repository for easy config portability ;; ------------------------------- Entertainment ------------------------------- @@ -146,6 +146,7 @@ ;;(require 'wip) (require 'lorem-optimum) (require 'jumper) +(require 'system-commands) ;; ---------------------------------- Wrap Up ---------------------------------- diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el new file mode 100644 index 00000000..9438e8ed --- /dev/null +++ b/modules/custom-buffer-file.el @@ -0,0 +1,260 @@ +;;; custom-buffer-file.el --- Custom Buffer and File Operations -*- coding: utf-8; lexical-binding: t; -*- +;; +;;; Commentary: +;; This module provides custom buffer and file operations including PostScript +;; printing capabilities. +;; +;; Functions include: +;; - printing buffers or regions as PostScript to the default printer (with color support) +;; - moving/renaming/deleting buffer files +;; - copying file paths and file:// links to the kill ring +;; - copying entire buffer contents +;; - clearing buffer contents from point to top or bottom. +;; +;; The PostScript printing auto-detects the system print spooler (lpr or lp) +;; and prints with face/syntax highlighting. Bound to keymap prefix ~C-; b~. +;; +;;; Code: + +;; cj/custom-keymap defined in keybindings.el +(eval-when-compile (defvar cj/custom-keymap)) +(eval-when-compile (require 'ps-print)) ;; for ps-print variables +(declare-function ps-print-buffer-with-faces "ps-print") +(declare-function ps-print-region-with-faces "ps-print") + +;; ------------------------- Print Buffer As Postscript ------------------------ + +(defvar cj/print-spooler-command 'auto + "Command used to send PostScript to the system print spooler. +Set to a string to force a specific command (e.g., lpr or lp). Set to `auto' to +auto-detect once per session.") + +(defvar cj/print--spooler-cache nil + "Cached spooler command detected for the current Emacs session.") + +(defun cj/print--resolve-spooler () + "Return the spooler command to use, auto-detecting and caching if needed." + (cond + ;; User-specified command + ((and (stringp cj/print-spooler-command) + (> (length cj/print-spooler-command) 0)) + (or (executable-find cj/print-spooler-command) + (user-error "Cannot print: spooler command '%s' not found in PATH" + cj/print-spooler-command)) + cj/print-spooler-command) + ;; Auto-detect once per session + ((eq cj/print-spooler-command 'auto) + (or cj/print--spooler-cache + (let ((cmd (or (and (executable-find "lpr") "lpr") + (and (executable-find "lp") "lp")))) + (unless cmd + (user-error "Cannot print: neither 'lpr' nor 'lp' found in PATH")) + (setq cj/print--spooler-cache cmd) + cmd))) + (t + (user-error "Invalid value for cj/print-spooler-command: %S" + cj/print-spooler-command)))) + +(defun cj/print-buffer-ps (&optional color) + "Print the buffer (or active region) as PostScript to the default printer. +With prefix argument COLOR, print in color; otherwise print in monochrome. +Sends directly to the system spooler with no header." + (interactive "P") + (unless (require 'ps-print nil t) + (user-error "Cannot print: ps-print library not found")) + (let* ((spooler (cj/print--resolve-spooler)) + (want-color (not (null color))) + (have-region (use-region-p))) + (let ((ps-lpr-command spooler) + (ps-printer-name nil) ; default system printer + (ps-lpr-switches nil) + (ps-print-color-p want-color) + (ps-use-face-background want-color) + (ps-print-header nil)) ; no headers + (if have-region + (ps-print-region-with-faces (region-beginning) (region-end)) + (ps-print-buffer-with-faces))) + (message "Sent %s to default printer via %s (%s)" + (if have-region "region" "buffer") + spooler + (if want-color "color" "monochrome")))) + +;; ------------------------- Buffer And File Operations ------------------------ + +(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) + (substring dir 0 -1) dir)) + (newname (concat dir "/" name))) + (if (not filename) + (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 _ + (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. +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)))))) + (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." + (interactive) + (let ((filename (buffer-file-name))) + (when filename + (if (vc-backend filename) + (vc-delete-file filename) + (progn + (delete-file filename t) + (message "Deleted file %s" filename) + (kill-buffer)))))) + +(defun cj/copy-link-to-buffer-file () + "Copy the full file:// path of the current buffer's source file to the kill ring." + (interactive) + (let ((file-path (buffer-file-name))) + (when file-path + (setq file-path (concat "file://" file-path)) + (kill-new file-path) + (message "Copied file link to kill ring: %s" file-path)))) + +(defun cj/copy-path-to-buffer-file-as-kill () + "Copy the full path of the current buffer's file to the kill ring. +Signal an error if the buffer is not visiting a file." + (interactive) + (let ((path (buffer-file-name))) + (if (not path) + (user-error "Current buffer is not visiting a file") + (kill-new path) + (message "Copied file path: %s" path) + path))) + +(defun cj/copy-whole-buffer () + "Copy 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. A message is displayed when done." + (interactive) + (let ((contents (buffer-substring-no-properties (point-min) (point-max)))) + (kill-new contents) + (message "Buffer contents copied to kill ring"))) + +(defun cj/clear-to-bottom-of-buffer () + "Delete all text from point to the end of the current buffer. +This does not save the deleted text in the kill ring." + (interactive) + (delete-region (point) (point-max)) + (message "Buffer contents removed to the end of the buffer.")) + +(defun cj/clear-to-top-of-buffer () + "Delete all text from point to the beginning of the current buffer. +Do not save the deleted text in the kill ring." + (interactive) + (delete-region (point) (point-min)) + (message "Buffer contents removed to the beginning of the buffer.")) + +(defun cj/copy-buffer-name () + "Copy current buffer name to kill ring." + (interactive) + (kill-new (buffer-name)) + (message "Copied: %s" (buffer-name))) + +;; --------------------------- Buffer And File Keymap -------------------------- + +;; Buffer & file operations prefix and keymap +(defvar-keymap cj/buffer-and-file-map + :doc "Keymap for buffer and file operations." + "m" #'cj/move-buffer-and-file + "r" #'cj/rename-buffer-and-file + "p" #'cj/print-buffer-ps + "d" #'cj/delete-buffer-and-file + "c" #'cj/copy-whole-buffer + "n" #'cj/copy-buffer-name + "t" #'cj/clear-to-top-of-buffer + "b" #'cj/clear-to-bottom-of-buffer + "x" #'erase-buffer + "s" #'write-file ;; save as + + "l" #'cj/copy-link-to-buffer-file + "P" #'cj/copy-path-to-buffer-file-as-kill) +(keymap-set cj/custom-keymap "b" cj/buffer-and-file-map) + +(with-eval-after-load 'which-key + (which-key-add-key-based-replacements + "C-; b" "buffer and file menu" + "C-; b m" "move file" + "C-; b r" "rename file" + "C-; b p" "print to PS" + "C-; b d" "delete file" + "C-; b c" "copy buffer" + "C-; b n" "copy buffer name" + "C-; b t" "clear to top" + "C-; b b" "clear to bottom" + "C-; b x" "erase buffer" + "C-; b s" "save as" + "C-; b l" "copy file link" + "C-; b P" "copy file path")) + + +(provide 'custom-buffer-file) +;;; custom-buffer-file.el ends here. diff --git a/modules/custom-file-buffer.el b/modules/custom-file-buffer.el deleted file mode 100644 index 08f974fd..00000000 --- a/modules/custom-file-buffer.el +++ /dev/null @@ -1,260 +0,0 @@ -;;; custom-file-buffer.el --- Custom Buffer and File Operations -*- coding: utf-8; lexical-binding: t; -*- -;; -;;; Commentary: -;; This module provides custom buffer and file operations including PostScript -;; printing capabilities. -;; -;; Functions include: -;; - printing buffers or regions as PostScript to the default printer (with color support) -;; - moving/renaming/deleting buffer files -;; - copying file paths and file:// links to the kill ring -;; - copying entire buffer contents -;; - clearing buffer contents from point to top or bottom. -;; -;; The PostScript printing auto-detects the system print spooler (lpr or lp) -;; and prints with face/syntax highlighting. Bound to keymap prefix ~C-; b~. -;; -;;; Code: - -;; cj/custom-keymap defined in keybindings.el -(eval-when-compile (defvar cj/custom-keymap)) -(eval-when-compile (require 'ps-print)) ;; for ps-print variables -(declare-function ps-print-buffer-with-faces "ps-print") -(declare-function ps-print-region-with-faces "ps-print") - -;; ------------------------- Print Buffer As Postscript ------------------------ - -(defvar cj/print-spooler-command 'auto - "Command used to send PostScript to the system print spooler. -Set to a string to force a specific command (e.g., lpr or lp). Set to `auto' to -auto-detect once per session.") - -(defvar cj/print--spooler-cache nil - "Cached spooler command detected for the current Emacs session.") - -(defun cj/print--resolve-spooler () - "Return the spooler command to use, auto-detecting and caching if needed." - (cond - ;; User-specified command - ((and (stringp cj/print-spooler-command) - (> (length cj/print-spooler-command) 0)) - (or (executable-find cj/print-spooler-command) - (user-error "Cannot print: spooler command '%s' not found in PATH" - cj/print-spooler-command)) - cj/print-spooler-command) - ;; Auto-detect once per session - ((eq cj/print-spooler-command 'auto) - (or cj/print--spooler-cache - (let ((cmd (or (and (executable-find "lpr") "lpr") - (and (executable-find "lp") "lp")))) - (unless cmd - (user-error "Cannot print: neither 'lpr' nor 'lp' found in PATH")) - (setq cj/print--spooler-cache cmd) - cmd))) - (t - (user-error "Invalid value for cj/print-spooler-command: %S" - cj/print-spooler-command)))) - -(defun cj/print-buffer-ps (&optional color) - "Print the buffer (or active region) as PostScript to the default printer. -With prefix argument COLOR, print in color; otherwise print in monochrome. -Sends directly to the system spooler with no header." - (interactive "P") - (unless (require 'ps-print nil t) - (user-error "Cannot print: ps-print library not found")) - (let* ((spooler (cj/print--resolve-spooler)) - (want-color (not (null color))) - (have-region (use-region-p))) - (let ((ps-lpr-command spooler) - (ps-printer-name nil) ; default system printer - (ps-lpr-switches nil) - (ps-print-color-p want-color) - (ps-use-face-background want-color) - (ps-print-header nil)) ; no headers - (if have-region - (ps-print-region-with-faces (region-beginning) (region-end)) - (ps-print-buffer-with-faces))) - (message "Sent %s to default printer via %s (%s)" - (if have-region "region" "buffer") - spooler - (if want-color "color" "monochrome")))) - -;; ------------------------- Buffer And File Operations ------------------------ - -(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) - (substring dir 0 -1) dir)) - (newname (concat dir "/" name))) - (if (not filename) - (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 _ - (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. -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)))))) - (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." - (interactive) - (let ((filename (buffer-file-name))) - (when filename - (if (vc-backend filename) - (vc-delete-file filename) - (progn - (delete-file filename t) - (message "Deleted file %s" filename) - (kill-buffer)))))) - -(defun cj/copy-link-to-buffer-file () - "Copy the full file:// path of the current buffer's source file to the kill ring." - (interactive) - (let ((file-path (buffer-file-name))) - (when file-path - (setq file-path (concat "file://" file-path)) - (kill-new file-path) - (message "Copied file link to kill ring: %s" file-path)))) - -(defun cj/copy-path-to-buffer-file-as-kill () - "Copy the full path of the current buffer's file to the kill ring. -Signal an error if the buffer is not visiting a file." - (interactive) - (let ((path (buffer-file-name))) - (if (not path) - (user-error "Current buffer is not visiting a file") - (kill-new path) - (message "Copied file path: %s" path) - path))) - -(defun cj/copy-whole-buffer () - "Copy 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. A message is displayed when done." - (interactive) - (let ((contents (buffer-substring-no-properties (point-min) (point-max)))) - (kill-new contents) - (message "Buffer contents copied to kill ring"))) - -(defun cj/clear-to-bottom-of-buffer () - "Delete all text from point to the end of the current buffer. -This does not save the deleted text in the kill ring." - (interactive) - (delete-region (point) (point-max)) - (message "Buffer contents removed to the end of the buffer.")) - -(defun cj/clear-to-top-of-buffer () - "Delete all text from point to the beginning of the current buffer. -Do not save the deleted text in the kill ring." - (interactive) - (delete-region (point) (point-min)) - (message "Buffer contents removed to the beginning of the buffer.")) - -(defun cj/copy-buffer-name () - "Copy current buffer name to kill ring." - (interactive) - (kill-new (buffer-name)) - (message "Copied: %s" (buffer-name))) - -;; --------------------------- Buffer And File Keymap -------------------------- - -;; Buffer & file operations prefix and keymap -(defvar-keymap cj/buffer-and-file-map - :doc "Keymap for buffer and file operations." - "m" #'cj/move-buffer-and-file - "r" #'cj/rename-buffer-and-file - "p" #'cj/print-buffer-ps - "d" #'cj/delete-buffer-and-file - "c" #'cj/copy-whole-buffer - "n" #'cj/copy-buffer-name - "t" #'cj/clear-to-top-of-buffer - "b" #'cj/clear-to-bottom-of-buffer - "x" #'erase-buffer - "s" #'write-file ;; save as - - "l" #'cj/copy-link-to-buffer-file - "P" #'cj/copy-path-to-buffer-file-as-kill) -(keymap-set cj/custom-keymap "b" cj/buffer-and-file-map) - -(with-eval-after-load 'which-key - (which-key-add-key-based-replacements - "C-; b" "buffer and file menu" - "C-; b m" "move file" - "C-; b r" "rename file" - "C-; b p" "print to PS" - "C-; b d" "delete file" - "C-; b c" "copy buffer" - "C-; b n" "copy buffer name" - "C-; b t" "clear to top" - "C-; b b" "clear to bottom" - "C-; b x" "erase buffer" - "C-; b s" "save as" - "C-; b l" "copy file link" - "C-; b P" "copy file path")) - - -(provide 'custom-file-buffer) -;;; custom-file-buffer.el ends here. diff --git a/modules/system-commands.el b/modules/system-commands.el new file mode 100644 index 00000000..fb8c0611 --- /dev/null +++ b/modules/system-commands.el @@ -0,0 +1,138 @@ +;;; system-commands.el --- System power and session management -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings +;; +;;; Commentary: +;; +;; System commands for logout, lock, suspend, shutdown, reboot, and Emacs +;; exit/restart. Provides both a keymap (C-; !) and a completing-read menu. +;; +;; Commands include: +;; - Logout (terminate user session) +;; - Lock screen (slock) +;; - Suspend (systemctl suspend) +;; - Shutdown (systemctl poweroff) +;; - Reboot (systemctl reboot) +;; - Exit Emacs (kill-emacs) +;; - Restart Emacs (via systemctl --user restart emacs.service) +;; +;; Dangerous commands (logout, suspend, shutdown, reboot) require confirmation. +;; +;;; Code: + +(eval-when-compile (require 'keybindings)) +(eval-when-compile (require 'subr-x)) +(require 'rx) + +;; ------------------------------ System Commands ------------------------------ + +(defun cj/system-cmd--resolve (cmd) + "Return (values symbol-or-nil command-string label) for CMD." + (cond + ((symbolp cmd) + (let ((val (and (boundp cmd) (symbol-value cmd)))) + (unless (and (stringp val) (not (string-empty-p val))) + (user-error "Variable %s is not a non-empty string" cmd)) + (list cmd val (symbol-name cmd)))) + ((stringp cmd) + (let ((s (string-trim cmd))) + (when (string-empty-p s) (user-error "Command string is empty")) + (list nil s "command"))) + (t (user-error "Error: cj/system-cmd expects a string or a symbol")))) + +(defun cj/system-cmd (cmd) + "Run CMD (string or symbol naming a string) detached via the shell. +Shell expansions like $(...) are supported. Output is silenced. +If CMD is deemed dangerous, ask for confirmation." + (interactive (list (read-shell-command "System command: "))) + (pcase-let ((`(,sym ,cmdstr ,label) (cj/system-cmd--resolve cmd))) + (when (and sym (get sym 'cj/system-confirm) + (memq (read-char-choice + (format "Run %s now (%s)? (Y/n) " label cmdstr) + '(?y ?Y ?n ?N ?\r ?\n ?\s)) + '(?n ?N))) + (user-error "Aborted")) + (let ((proc (start-process-shell-command "cj/system-cmd" nil + (format "nohup %s >/dev/null 2>&1 &" cmdstr)))) + (set-process-query-on-exit-flag proc nil) + (set-process-sentinel proc #'ignore) + (message "Running %s..." label)))) + +(defmacro cj/defsystem-command (name var cmdstr &optional confirm) + "Define VAR with CMDSTR and interactive command NAME to run it. +If CONFIRM is non-nil, mark VAR to always require confirmation." + (declare (indent defun)) + `(progn + (defvar ,var ,cmdstr) + ,(when confirm `(put ',var 'cj/system-confirm t)) + (defun ,name () + ,(format "Run %s via `cj/system-cmd'." var) + (interactive) + (cj/system-cmd ',var)))) + +;; Define system commands +(cj/defsystem-command cj/system-cmd-logout logout-cmd "loginctl terminate-user $(whoami)" t) +(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd "slock") +(cj/defsystem-command cj/system-cmd-suspend suspend-cmd "systemctl suspend" t) +(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" t) +(cj/defsystem-command cj/system-cmd-reboot reboot-cmd "systemctl reboot" t) + +(defun cj/system-cmd-exit-emacs () + "Exit Emacs server and all clients." + (interactive) + (when (memq (read-char-choice + "Exit Emacs? (Y/n) " + '(?y ?Y ?n ?N ?\r ?\n ?\s)) + '(?n ?N)) + (user-error "Aborted")) + (kill-emacs)) + +(defun cj/system-cmd-restart-emacs () + "Restart Emacs server after saving buffers." + (interactive) + (when (memq (read-char-choice + "Restart Emacs? (Y/n) " + '(?y ?Y ?n ?N ?\r ?\n ?\s)) + '(?n ?N)) + (user-error "Aborted")) + (save-some-buffers) + ;; Start the restart process before killing Emacs + (run-at-time 0.5 nil + (lambda () + (call-process-shell-command + "systemctl --user restart emacs.service && emacsclient -c" + nil 0))) + (run-at-time 1 nil #'kill-emacs) + (message "Restarting Emacs...")) + +(defvar-keymap cj/system-command-map + :doc "Keymap for system commands." + "L" #'cj/system-cmd-logout + "r" #'cj/system-cmd-reboot + "s" #'cj/system-cmd-shutdown + "S" #'cj/system-cmd-suspend + "l" #'cj/system-cmd-lock + "E" #'cj/system-cmd-exit-emacs + "e" #'cj/system-cmd-restart-emacs) +(keymap-set cj/custom-keymap "!" cj/system-command-map) + +(defun cj/system-command-menu () + "Present system commands via \='completing-read\='." + (interactive) + (let* ((commands '(("Logout System" . cj/system-cmd-logout) + ("Lock Screen" . cj/system-cmd-lock) + ("Suspend System" . cj/system-cmd-suspend) + ("Shutdown System" . cj/system-cmd-shutdown) + ("Reboot System" . cj/system-cmd-reboot) + ("Exit Emacs" . cj/system-cmd-exit-emacs) + ("Restart Emacs" . cj/system-cmd-restart-emacs))) + (choice (completing-read "System command: " commands nil t))) + (when-let ((cmd (alist-get choice commands nil nil #'equal))) + (call-interactively cmd)))) + +(keymap-set cj/custom-keymap "!" #'cj/system-command-menu) + +(with-eval-after-load 'which-key + (which-key-add-key-based-replacements "C-; !" "system commands")) + +(provide 'system-commands) +;;; system-commands.el ends here diff --git a/modules/wip.el b/modules/wip.el index db94cdb1..93c799fb 100644 --- a/modules/wip.el +++ b/modules/wip.el @@ -14,135 +14,6 @@ ;; ;;; Code: -(eval-when-compile (require 'user-constants)) -(eval-when-compile (require 'keybindings)) -(eval-when-compile (require 'subr-x)) ;; for system commands -(require 'rx) ;; for system commands - -;; ------------------------------ System Commands ------------------------------ - -(defun cj/system-cmd--resolve (cmd) - "Return (values symbol-or-nil command-string label) for CMD." - (cond - ((symbolp cmd) - (let ((val (and (boundp cmd) (symbol-value cmd)))) - (unless (and (stringp val) (not (string-empty-p val))) - (user-error "Variable %s is not a non-empty string" cmd)) - (list cmd val (symbol-name cmd)))) - ((stringp cmd) - (let ((s (string-trim cmd))) - (when (string-empty-p s) (user-error "Command string is empty")) - (list nil s "command"))) - (t (user-error "Error: cj/system-cmd expects a string or a symbol")))) - -(defun cj/system-cmd (cmd) - "Run CMD (string or symbol naming a string) detached via the shell. -Shell expansions like $(...) are supported. Output is silenced. -If CMD is deemed dangerous, ask for confirmation." - (interactive (list (read-shell-command "System command: "))) - (pcase-let ((`(,sym ,cmdstr ,label) (cj/system-cmd--resolve cmd))) - (when (and sym (get sym 'cj/system-confirm) - (memq (read-char-choice - (format "Run %s now (%s)? (Y/n) " label camdstr) - '(?y ?Y ?n ?N ?\r ?\n ?\s)) - '(?n ?N))) - (user-error "Aborted")) - (let ((proc (start-process-shell-command "cj/system-cmd" nil - (format "nohup %s >/dev/null 2>&1 &" cmdstr)))) - (set-process-query-on-exit-flag proc nil) - (set-process-sentinel proc #'ignore) - (message "Running %s..." label)))) - -(defmacro cj/defsystem-command (name var cmdstr &optional confirm) - "Define VAR with CMDSTR and interactive command NAME to run it. -If CONFIRM is non-nil, mark VAR to always require confirmation." - (declare (indent defun)) - `(progn - (defvar ,var ,cmdstr) - ,(when confirm `(put ',var 'cj/system-confirm t)) - (defun ,name () - ,(format "Run %s via `cj/system-cmd'." var) - (interactive) - (cj/system-cmd ',var)))) - -;; Define system commands -(cj/defsystem-command cj/system-cmd-logout logout-cmd "loginctl terminate-user $(whoami)" t) -(cj/defsystem-command cj/system-cmd-lock lockscreen-cmd "slock") -(cj/defsystem-command cj/system-cmd-suspend suspend-cmd "systemctl suspend" t) -(cj/defsystem-command cj/system-cmd-shutdown shutdown-cmd "systemctl poweroff" t) -(cj/defsystem-command cj/system-cmd-reboot reboot-cmd "systemctl reboot" t) - -(defun cj/system-cmd-exit-emacs () - "Exit Emacs server and all clients." - (interactive) - (when (memq (read-char-choice - "Exit Emacs? (Y/n) " - '(?y ?Y ?n ?N ?\r ?\n ?\s)) - '(?n ?N)) - (user-error "Aborted")) - (kill-emacs)) - -(defun cj/system-cmd-restart-emacs () - "Restart Emacs server after saving buffers." - (interactive) - (when (memq (read-char-choice - "Restart Emacs? (Y/n) " - '(?y ?Y ?n ?N ?\r ?\n ?\s)) - '(?n ?N)) - (user-error "Aborted")) - (save-some-buffers) - ;; Start the restart process before killing Emacs - (run-at-time 0.5 nil - (lambda () - (call-process-shell-command - "systemctl --user restart emacs.service && emacsclient -c" - nil 0))) - (run-at-time 1 nil #'kill-emacs) - (message "Restarting Emacs...")) - -;; (defvar-keymap cj/system-command-map -;; :doc "Keymap for system commands." -;; "L" #'cj/system-cmd-logout -;; "r" #'cj/system-cmd-reboot -;; "s" #'cj/system-cmd-shutdown -;; "S" #'cj/system-cmd-suspend -;; "l" #'cj/system-cmd-lock -;; "E" #'cj/system-cmd-exit-emacs -;; "e" #'cj/system-cmd-restart-emacs) -;; (keymap-set cj/custom-keymap "!" cj/system-command-map) - -(defun cj/system-command-menu () - "Present system commands via \='completing-read\='." - (interactive) - (let* ((commands '(("Logout System" . cj/system-cmd-logout) - ("Lock Screen" . cj/system-cmd-lock) - ("Suspend System" . cj/system-cmd-suspend) - ("Shutdown System" . cj/system-cmd-shutdown) - ("Reboot System" . cj/system-cmd-reboot) - ("Exit Emacs" . cj/system-cmd-exit-emacs) - ("Restart Emacs" . cj/system-cmd-restart-emacs))) - (choice (completing-read "System command: " commands nil t))) - (when-let ((cmd (alist-get choice commands nil nil #'equal))) - (call-interactively cmd)))) - -(keymap-set cj/custom-keymap "!" #'cj/system-command-menu) - -(with-eval-after-load 'which-key - (which-key-add-key-based-replacements "C-; !" "system commands")) - -;; --------------------------- Org Upcoming Modeline --------------------------- - -;; (use-package org-upcoming-modeline -;; :after org -;; :load-path "~/code/org-upcoming-modeline/org-upcoming-modeline.el" -;; :config -;; (setq org-upcoming-modeline-keep-late 300) -;; (setq org-upcoming-modeline-ignored-keywords '("DONE" "CANCELLED" "FAILED")) -;; (setq org-upcoming-modeline-trim 30) -;; (setq org-upcoming-modeline-days-ahead 5) -;; (setq org-upcoming-modeline-format (lambda (ms mh) (format "📅 %s %s" ms mh))) -;; (org-upcoming-modeline-mode)) - ;; ----------------------------------- Efrit ----------------------------------- ;; not working as of Wednesday, September 03, 2025 at 12:44:09 AM CDT @@ -185,30 +56,5 @@ If CONFIRM is non-nil, mark VAR to always require confirmation." :bind ("M-p" . pomm) :commands (pomm pomm-third-time)) -;; ----------------------------------- Popper ---------------------------------- - -;; (use-package popper -;; :bind (("C-`" . popper-toggle) -;; ("M-`" . popper-cycle) -;; ("C-M-`" . popper-toggle-type)) -;; :custom -;; (popper-display-control-nil) -;; :init -;; (setq popper-reference-buffers -;; '("\\*Messages\\*" -;; "Output\\*$" -;; "\\*Async Shell Command\\*" -;; ;; "\\*scratch\\*" -;; help-mode -;; compilation-mode)) -;; (add-to-list 'display-buffer-alist -;; '(popper-display-control-p ; Predicate to match popper buffers -;; (display-buffer-in-side-window) -;; (side . bottom) -;; (slot . 0) -;; (window-height . 0.5))) ; Half the frame height -;; (popper-mode +1) -;; (popper-echo-mode +1)) - (provide 'wip) ;;; wip.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 new file mode 100644 index 00000000..bd309880 --- /dev/null +++ b/tests/test-custom-buffer-file-clear-to-bottom-of-buffer.el @@ -0,0 +1,163 @@ +;;; 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 new file mode 100644 index 00000000..2bf79b27 --- /dev/null +++ b/tests/test-custom-buffer-file-clear-to-top-of-buffer.el @@ -0,0 +1,162 @@ +;;; 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 new file mode 100644 index 00000000..262968d6 --- /dev/null +++ b/tests/test-custom-buffer-file-copy-link-to-buffer-file.el @@ -0,0 +1,209 @@ +;;; 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 new file mode 100644 index 00000000..08959a85 --- /dev/null +++ b/tests/test-custom-buffer-file-copy-path-to-buffer-file-as-kill.el @@ -0,0 +1,205 @@ +;;; 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-whole-buffer.el b/tests/test-custom-buffer-file-copy-whole-buffer.el new file mode 100644 index 00000000..181c491a --- /dev/null +++ b/tests/test-custom-buffer-file-copy-whole-buffer.el @@ -0,0 +1,194 @@ +;;; 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 new file mode 100644 index 00000000..4af8d2a7 --- /dev/null +++ b/tests/test-custom-buffer-file-delete-buffer-and-file.el @@ -0,0 +1,671 @@ +;;; 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 new file mode 100644 index 00000000..e8f4563d --- /dev/null +++ b/tests/test-custom-buffer-file-move-buffer-and-file.el @@ -0,0 +1,936 @@ +;;; 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 new file mode 100644 index 00000000..1eb61f1b --- /dev/null +++ b/tests/test-custom-buffer-file-rename-buffer-and-file.el @@ -0,0 +1,939 @@ +;;; 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-file-buffer-clear-to-bottom-of-buffer.el b/tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el deleted file mode 100644 index 969f9bb7..00000000 --- a/tests/test-custom-file-buffer-clear-to-bottom-of-buffer.el +++ /dev/null @@ -1,163 +0,0 @@ -;;; test-custom-file-buffer-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-file-buffer.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-file-buffer) - -;;; 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-file-buffer-clear-to-bottom-of-buffer) -;;; test-custom-file-buffer-clear-to-bottom-of-buffer.el ends here diff --git a/tests/test-custom-file-buffer-clear-to-top-of-buffer.el b/tests/test-custom-file-buffer-clear-to-top-of-buffer.el deleted file mode 100644 index 18e3f71b..00000000 --- a/tests/test-custom-file-buffer-clear-to-top-of-buffer.el +++ /dev/null @@ -1,162 +0,0 @@ -;;; test-custom-file-buffer-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-file-buffer.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-file-buffer) - -;;; 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-file-buffer-clear-to-top-of-buffer) -;;; test-custom-file-buffer-clear-to-top-of-buffer.el ends here diff --git a/tests/test-custom-file-buffer-copy-link-to-buffer-file.el b/tests/test-custom-file-buffer-copy-link-to-buffer-file.el deleted file mode 100644 index 94d1e01e..00000000 --- a/tests/test-custom-file-buffer-copy-link-to-buffer-file.el +++ /dev/null @@ -1,209 +0,0 @@ -;;; test-custom-file-buffer-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-file-buffer.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-file-buffer) - -;;; 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-file-buffer-copy-link-to-buffer-file) -;;; test-custom-file-buffer-copy-link-to-buffer-file.el ends here diff --git a/tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el b/tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el deleted file mode 100644 index e7a6f64b..00000000 --- a/tests/test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el +++ /dev/null @@ -1,205 +0,0 @@ -;;; test-custom-file-buffer-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-file-buffer.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-file-buffer) - -;;; 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-file-buffer-copy-path-to-buffer-file-as-kill) -;;; test-custom-file-buffer-copy-path-to-buffer-file-as-kill.el ends here diff --git a/tests/test-custom-file-buffer-copy-whole-buffer.el b/tests/test-custom-file-buffer-copy-whole-buffer.el deleted file mode 100644 index a0546b18..00000000 --- a/tests/test-custom-file-buffer-copy-whole-buffer.el +++ /dev/null @@ -1,194 +0,0 @@ -;;; test-custom-file-buffer-copy-whole-buffer.el --- Tests for cj/copy-whole-buffer -*- lexical-binding: t; -*- - -;;; Commentary: -;; Tests for the cj/copy-whole-buffer function from custom-file-buffer.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-file-buffer) - -;;; 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-file-buffer-copy-whole-buffer) -;;; test-custom-file-buffer-copy-whole-buffer.el ends here diff --git a/tests/test-custom-file-buffer-delete-buffer-and-file.el b/tests/test-custom-file-buffer-delete-buffer-and-file.el deleted file mode 100644 index 1c43ff3b..00000000 --- a/tests/test-custom-file-buffer-delete-buffer-and-file.el +++ /dev/null @@ -1,671 +0,0 @@ -;;; test-custom-file-buffer-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-file-buffer.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-file-buffer) - -;;; 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-file-buffer-delete-buffer-and-file) -;;; test-custom-file-buffer-delete-buffer-and-file.el ends here diff --git a/tests/test-custom-file-buffer-move-buffer-and-file.el b/tests/test-custom-file-buffer-move-buffer-and-file.el deleted file mode 100644 index 1fc16011..00000000 --- a/tests/test-custom-file-buffer-move-buffer-and-file.el +++ /dev/null @@ -1,936 +0,0 @@ -;;; 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 deleted file mode 100644 index ca8acff8..00000000 --- a/tests/test-custom-file-buffer-rename-buffer-and-file.el +++ /dev/null @@ -1,939 +0,0 @@ -;;; 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 -- cgit v1.2.3