aboutsummaryrefslogtreecommitdiff
path: root/tests/test-custom-buffer-file--buffer-differs-prompt.el
blob: 109ca121f819f638bb07b55df51d688b8d2d39a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
;;; test-custom-buffer-file--buffer-differs-prompt.el --- disk-changed save prompt pieces -*- lexical-binding: t; -*-

;;; Commentary:
;; Pure-logic tests for the disk-changed save prompt (the C-x C-s case where the
;; buffer is modified AND the file changed on disk): the question string (with a
;; terse whitespace-only parenthetical), the labeled-but-terse choice list, and
;; the key->action mapping.  The interactive read loop, the diff display, and the
;; save/revert dispatch are exercised live, not here.

;;; Code:

(require 'ert)
(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
(require 'custom-buffer-file)

(declare-function cj/--buffer-differs-prompt-string "custom-buffer-file" (name ws-only-p))
(declare-function cj/--buffer-differs-choices "custom-buffer-file" ())
(declare-function cj/--buffer-differs-action "custom-buffer-file" (key))

;;; ------------------- cj/--buffer-differs-prompt-string ----------------------

(ert-deftest test-cbf-buffer-differs-prompt-plain ()
  "Normal: without whitespace-only, the prompt names the buffer, no parenthetical."
  (let ((s (cj/--buffer-differs-prompt-string "todo.org" nil)))
    (should (string-match-p "todo\\.org" s))
    (should-not (string-match-p "whitespace" s))))

(ert-deftest test-cbf-buffer-differs-prompt-whitespace-only ()
  "Normal: whitespace-only folds in a terse \"(whitespace only)\" parenthetical."
  (let ((s (cj/--buffer-differs-prompt-string "todo.org" t)))
    (should (string-match-p "todo\\.org" s))
    (should (string-match-p "(whitespace only)" s))))

;;; ---------------------- cj/--buffer-differs-choices -------------------------

(ert-deftest test-cbf-buffer-differs-choices-keys ()
  "Normal: the menu offers save, diff, clean, revert, and cancel."
  (let ((c (cj/--buffer-differs-choices)))
    (dolist (key '(?s ?d ?w ?r ?c))
      (should (assq key c)))))

(ert-deftest test-cbf-buffer-differs-choices-terse-names ()
  "Boundary: inline names stay terse (one word) so the menu fits at a glance."
  (dolist (entry (cj/--buffer-differs-choices))
    (let ((name (nth 1 entry)))
      (should (stringp name))
      (should-not (string-match-p " " name)))))

(ert-deftest test-cbf-buffer-differs-choices-clean-help-mentions-whitespace ()
  "Normal: the clean option's description (the ? help) names whitespace."
  (let ((entry (assq ?w (cj/--buffer-differs-choices))))
    (should (string-match-p "whitespace" (or (nth 2 entry) "")))))

(ert-deftest test-cbf-buffer-differs-choices-revert-help-mentions-disk ()
  "Normal: the revert option's description makes clear it rereads from disk."
  (let ((entry (assq ?r (cj/--buffer-differs-choices))))
    (should (string-match-p "disk" (or (nth 2 entry) "")))))

;;; ---------------------- cj/--buffer-differs-action --------------------------

(ert-deftest test-cbf-buffer-differs-action-save ()
  "Normal: s overwrites the file with the buffer."
  (should (eq (cj/--buffer-differs-action ?s) 'save)))

(ert-deftest test-cbf-buffer-differs-action-clean ()
  "Normal: w cleans whitespace, then saves."
  (should (eq (cj/--buffer-differs-action ?w) 'clean-save)))

(ert-deftest test-cbf-buffer-differs-action-revert ()
  "Normal: r discards edits and rereads from disk."
  (should (eq (cj/--buffer-differs-action ?r) 'revert)))

(ert-deftest test-cbf-buffer-differs-action-diff ()
  "Normal: d peeks at the diff (re-prompt is the caller's concern)."
  (should (eq (cj/--buffer-differs-action ?d) 'diff)))

(ert-deftest test-cbf-buffer-differs-action-cancel ()
  "Boundary: c cancels, leaving the buffer untouched."
  (should (eq (cj/--buffer-differs-action ?c) 'cancel)))

(ert-deftest test-cbf-buffer-differs-action-unknown ()
  "Error: an unmapped key returns nil."
  (should-not (cj/--buffer-differs-action ?z)))

;;; ------------------- cj/--buffer-changed-on-disk-p --------------------------
;; Real visited-file buffers; modtime state is driven (set-visited-file-modtime),
;; not mocked.  The trigger is the disk-changed conflict: modified AND the file
;; changed on disk since visited.

(declare-function cj/--buffer-changed-on-disk-p "custom-buffer-file" (buffer))

(defun test-cbf-cod--with-visited (edit-fn body-fn)
  "Visit a temp file, run EDIT-FN in its buffer, call BODY-FN with the buffer."
  (let ((f (make-temp-file "cbf-cod-" nil ".txt")))
    (unwind-protect
        (progn
          (with-temp-file f (insert "original\n"))
          (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-changed-on-disk-detects ()
  "Normal: modified buffer whose recorded modtime no longer matches is changed-on-disk."
  (test-cbf-cod--with-visited
   (lambda () (goto-char (point-max)) (insert "edit\n") (set-visited-file-modtime '(0 0)))
   (lambda (buf) (should (cj/--buffer-changed-on-disk-p buf)))))

(ert-deftest test-cbf-changed-on-disk-clean-modtime ()
  "Boundary: modified buffer whose modtime still matches is not changed-on-disk."
  (test-cbf-cod--with-visited
   (lambda () (goto-char (point-max)) (insert "edit\n"))
   (lambda (buf) (should-not (cj/--buffer-changed-on-disk-p buf)))))

(ert-deftest test-cbf-changed-on-disk-unmodified ()
  "Boundary: an unmodified buffer is never changed-on-disk (nothing of mine to lose)."
  (test-cbf-cod--with-visited
   (lambda () (set-visited-file-modtime '(0 0)))
   (lambda (buf) (should-not (cj/--buffer-changed-on-disk-p buf)))))

;;; -------------- cj/--buffer-differs-dispatch (data direction) ---------------
;; The destructive directions, driven against real files: `save' overwrites the
;; disk with the buffer (buffer wins); `revert' discards the buffer's edits and
;; rereads the disk (disk wins); `clean-save' strips trailing whitespace first.

(declare-function cj/--buffer-differs-dispatch "custom-buffer-file" (buffer action))
(declare-function cj/save-buffer "custom-buffer-file" ())

(defun test-cbf-disp--with-conflict (buffer-insert disk-content body-fn)
  "Visit a temp file, BUFFER-INSERT into the buffer, overwrite the file with
DISK-CONTENT underneath, then call BODY-FN with the buffer and file path."
  (let ((f (make-temp-file "cbf-disp-" nil ".txt")))
    (unwind-protect
        (progn
          (with-temp-file f (insert "original\n"))
          (let ((buf (find-file-noselect f)))
            (unwind-protect
                (with-current-buffer buf
                  (goto-char (point-max)) (insert buffer-insert)
                  (with-temp-file f (insert disk-content))
                  (funcall body-fn buf f))
              (with-current-buffer buf (set-buffer-modified-p nil))
              (kill-buffer buf))))
      (when (file-exists-p f) (delete-file f)))))

(defun test-cbf-disp--disk (f)
  "Return the on-disk contents of F."
  (with-temp-buffer (insert-file-contents f) (buffer-string)))

(ert-deftest test-cbf-buffer-differs-dispatch-save-overwrites-disk ()
  "Normal: save writes the buffer over the disk version (buffer wins)."
  (test-cbf-disp--with-conflict
   "my edit\n" "disk changed underneath\n"
   (lambda (buf f)
     (cj/--buffer-differs-dispatch buf 'save)
     (should-not (buffer-modified-p buf))
     (should (string= (test-cbf-disp--disk f) "original\nmy edit\n")))))

(ert-deftest test-cbf-buffer-differs-dispatch-revert-discards-edits ()
  "Normal: revert discards the buffer's edits and rereads the disk (disk wins)."
  (test-cbf-disp--with-conflict
   "my edit\n" "disk version\n"
   (lambda (buf f)
     (cj/--buffer-differs-dispatch buf 'revert)
     (should-not (buffer-modified-p buf))
     (should (string= (with-current-buffer buf (buffer-string)) "disk version\n")))))

(ert-deftest test-cbf-buffer-differs-dispatch-clean-save-strips-whitespace ()
  "Normal: clean-save strips trailing whitespace, then overwrites the disk."
  (test-cbf-disp--with-conflict
   "edit   \n" "disk changed\n"
   (lambda (buf f)
     (cj/--buffer-differs-dispatch buf 'clean-save)
     (should-not (buffer-modified-p buf))
     (should (string= (test-cbf-disp--disk f) "original\nedit\n")))))

(ert-deftest test-cbf-save-buffer-fast-path-no-conflict ()
  "Boundary: with no disk conflict, cj/save-buffer just saves (no prompt path)."
  (let ((f (make-temp-file "cbf-fast-" nil ".txt")))
    (unwind-protect
        (progn
          (with-temp-file f (insert "base\n"))
          (let ((buf (find-file-noselect f)))
            (unwind-protect
                (with-current-buffer buf
                  (goto-char (point-max)) (insert "added\n")
                  (cj/save-buffer)               ; modtime matches -> fast path
                  (should-not (buffer-modified-p))
                  (should (string= (test-cbf-disp--disk f) "base\nadded\n")))
              (with-current-buffer buf (set-buffer-modified-p nil))
              (kill-buffer buf))))
      (when (file-exists-p f) (delete-file f)))))

(provide 'test-custom-buffer-file--buffer-differs-prompt)
;;; test-custom-buffer-file--buffer-differs-prompt.el ends here