aboutsummaryrefslogtreecommitdiff
path: root/tests/test-custom-buffer-file--diff-whitespace-only.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-30 08:11:02 -0400
committerCraig Jennings <c@cjennings.net>2026-06-30 08:11:02 -0400
commit63a4b9522600b221115fd79f10bde87fc484644d (patch)
tree56a6a57bed33b875c48067af0995f84fb339dd8e /tests/test-custom-buffer-file--diff-whitespace-only.el
parentf1cc929451dc9068d302006f9b9af6ed40fd9cf1 (diff)
downloaddotemacs-63a4b9522600b221115fd79f10bde87fc484644d.tar.gz
dotemacs-63a4b9522600b221115fd79f10bde87fc484644d.zip
feat(buffer-file): legible save prompts for save-some-buffers and disk-changed saves
Replace the cryptic single-key save prompts with read-multiple-choice menus whose options are labeled on screen instead of recalled as keys. save-some-buffers (C-x s and save-on-exit) is overridden with a reimplementation that reuses the stock candidate selection and abbrev-save tail but swaps map-y-or-n-p's terse key list for a labeled menu, and adds a clean-whitespace-and-save action. C-x C-s gains a wrapper that fast-paths normal saves to save-buffer; only when the buffer has unsaved edits and the file changed on disk does it show a labeled menu (save / diff / clean / revert / cancel) instead of the bare "Save anyway?" yes/no. Both menus share a diff toggle, and whitespace-only diffs route to a plain unified diff with trailing whitespace highlighted, since difftastic normalizes trailing whitespace and renders such changes blank. The interactive wrappers stay thin over pure, tested helpers: the save-loop planner, the key-to-action maps, the whitespace detector, and the renderer choice.
Diffstat (limited to 'tests/test-custom-buffer-file--diff-whitespace-only.el')
-rw-r--r--tests/test-custom-buffer-file--diff-whitespace-only.el146
1 files changed, 146 insertions, 0 deletions
diff --git a/tests/test-custom-buffer-file--diff-whitespace-only.el b/tests/test-custom-buffer-file--diff-whitespace-only.el
new file mode 100644
index 000000000..e792637b5
--- /dev/null
+++ b/tests/test-custom-buffer-file--diff-whitespace-only.el
@@ -0,0 +1,146 @@
+;;; test-custom-buffer-file--diff-whitespace-only.el --- whitespace-only diff detection -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for cj/--diff-whitespace-only-p, the route-1 detector behind the
+;; buffer-differs save prompt: two files differ ONLY in whitespace when a plain
+;; diff finds changes but `diff -w' (ignore all whitespace) finds none. Uses
+;; real temp files and the real diff(1) binary (a system boundary we keep), so
+;; nothing is mocked.
+
+;;; Code:
+
+(require 'ert)
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'custom-buffer-file)
+
+(declare-function cj/--diff-whitespace-only-p "custom-buffer-file" (file-a file-b))
+
+(defun test-cbf-ws--two-files (content-a content-b fn)
+ "Write CONTENT-A and CONTENT-B to temp files, call FN with their paths, clean up."
+ (let ((a (make-temp-file "cbf-ws-a-"))
+ (b (make-temp-file "cbf-ws-b-")))
+ (unwind-protect
+ (progn
+ (with-temp-file a (insert content-a))
+ (with-temp-file b (insert content-b))
+ (funcall fn a b))
+ (delete-file a)
+ (delete-file b))))
+
+(ert-deftest test-cbf-diff-whitespace-only-trailing ()
+ "Normal: files differing only by trailing whitespace are whitespace-only."
+ (test-cbf-ws--two-files
+ "alpha\nbeta\n" "alpha \nbeta\n"
+ (lambda (a b) (should (cj/--diff-whitespace-only-p a b)))))
+
+(ert-deftest test-cbf-diff-whitespace-only-indentation ()
+ "Normal: files differing only by leading indentation are whitespace-only."
+ (test-cbf-ws--two-files
+ "(foo)\n(bar)\n" "(foo)\n (bar)\n"
+ (lambda (a b) (should (cj/--diff-whitespace-only-p a b)))))
+
+(ert-deftest test-cbf-diff-whitespace-only-real-content ()
+ "Normal: files differing in actual content are NOT whitespace-only."
+ (test-cbf-ws--two-files
+ "alpha\nbeta\n" "alpha\nGAMMA\n"
+ (lambda (a b) (should-not (cj/--diff-whitespace-only-p a b)))))
+
+(ert-deftest test-cbf-diff-whitespace-only-identical ()
+ "Boundary: identical files do not differ at all, so not whitespace-only."
+ (test-cbf-ws--two-files
+ "alpha\nbeta\n" "alpha\nbeta\n"
+ (lambda (a b) (should-not (cj/--diff-whitespace-only-p a b)))))
+
+(ert-deftest test-cbf-diff-whitespace-only-mixed ()
+ "Boundary: whitespace change plus a real content change is NOT whitespace-only."
+ (test-cbf-ws--two-files
+ "alpha\nbeta\n" "alpha \nGAMMA\n"
+ (lambda (a b) (should-not (cj/--diff-whitespace-only-p a b)))))
+
+;;; -------------------- cj/--diff-buffer-renderer -----------------------------
+;; Which renderer the diff command uses: whitespace-only diffs go to a plain
+;; unified diff (trailing whitespace highlighted, so it is actually visible)
+;; because difftastic treats trailing-whitespace as no change and renders it
+;; blank. Real content diffs use difftastic when available, else plain diff.
+
+(declare-function cj/--diff-buffer-renderer "custom-buffer-file" (ws-only difft-available))
+
+(ert-deftest test-cbf-diff-renderer-whitespace-over-difftastic ()
+ "Normal: a whitespace-only diff uses the whitespace renderer even when difft is present."
+ (should (eq (cj/--diff-buffer-renderer t t) 'whitespace)))
+
+(ert-deftest test-cbf-diff-renderer-whitespace-no-difft ()
+ "Boundary: whitespace-only still uses the whitespace renderer without difft."
+ (should (eq (cj/--diff-buffer-renderer t nil) 'whitespace)))
+
+(ert-deftest test-cbf-diff-renderer-content-uses-difftastic ()
+ "Normal: a content diff uses difftastic when it is available."
+ (should (eq (cj/--diff-buffer-renderer nil t) 'difftastic)))
+
+(ert-deftest test-cbf-diff-renderer-content-no-difft-regular ()
+ "Boundary: a content diff falls back to the regular renderer without difft."
+ (should (eq (cj/--diff-buffer-renderer nil nil) 'regular)))
+
+;;; --------------- cj/--buffer-file-whitespace-only-p (buffer) ----------------
+;; Buffer-vs-its-file variant: writes the buffer to a temp file and reuses the
+;; detector against the buffer's visited file. Uses a real visited-file buffer.
+
+(declare-function cj/--buffer-file-whitespace-only-p "custom-buffer-file" (buffer))
+
+(defun test-cbf-ws--with-visited (disk-content edit-fn body-fn)
+ "Visit a temp file holding DISK-CONTENT, apply EDIT-FN in it, call BODY-FN with the buffer."
+ (let ((f (make-temp-file "cbf-bws-" nil ".txt")))
+ (unwind-protect
+ (progn
+ (with-temp-file f (insert disk-content))
+ (let ((buf (find-file-noselect f)))
+ (unwind-protect
+ (with-current-buffer buf
+ (funcall edit-fn)
+ (funcall body-fn buf))
+ (with-current-buffer buf (set-buffer-modified-p nil))
+ (kill-buffer buf))))
+ (when (file-exists-p f) (delete-file f)))))
+
+(ert-deftest test-cbf-buffer-file-ws-only-trailing ()
+ "Normal: an unsaved trailing-whitespace edit is whitespace-only vs the file."
+ (test-cbf-ws--with-visited
+ "alpha\nbeta\n"
+ (lambda () (goto-char (point-min)) (end-of-line) (insert " "))
+ (lambda (buf) (should (cj/--buffer-file-whitespace-only-p buf)))))
+
+(ert-deftest test-cbf-buffer-file-ws-only-content ()
+ "Normal: an unsaved content edit is NOT whitespace-only vs the file."
+ (test-cbf-ws--with-visited
+ "alpha\nbeta\n"
+ (lambda () (goto-char (point-max)) (insert "gamma\n"))
+ (lambda (buf) (should-not (cj/--buffer-file-whitespace-only-p buf)))))
+
+;;; --------- cj/diff-buffer-with-file return value (for the toggle) -----------
+
+(ert-deftest test-cbf-diff-returns-buffer-when-differs ()
+ "Normal: cj/diff-buffer-with-file returns the live diff buffer when the buffer differs."
+ (test-cbf-ws--with-visited
+ "x\ny\n"
+ (lambda () (goto-char (point-max)) (insert "ADDED\n"))
+ (lambda (buf)
+ (let ((db (with-current-buffer buf (cj/diff-buffer-with-file))))
+ (should (bufferp db))
+ (should (buffer-live-p db))))))
+
+(ert-deftest test-cbf-diff-returns-nil-when-identical ()
+ "Boundary: with no differences, cj/diff-buffer-with-file returns nil."
+ (test-cbf-ws--with-visited
+ "x\ny\n"
+ (lambda () nil)
+ (lambda (buf)
+ (should-not (with-current-buffer buf (cj/diff-buffer-with-file))))))
+
+(ert-deftest test-cbf-buffer-file-ws-only-non-file ()
+ "Boundary: a buffer not visiting a file is not whitespace-only."
+ (with-temp-buffer
+ (insert "scratch")
+ (should-not (cj/--buffer-file-whitespace-only-p (current-buffer)))))
+
+(provide 'test-custom-buffer-file--diff-whitespace-only)
+;;; test-custom-buffer-file--diff-whitespace-only.el ends here