emacs-patch-review
Port of Thunderbird Patch Review to mu4e.
git clone git://mccd.space/emacs-patch-review| Log | Files | Refs | README | Mail |
patch-review-test.el (26604B)
1 ;;; patch-review-test.el --- ERT suite -*- lexical-binding: t -*-
2
3 (require 'ert)
4 (require 'patch-review-parse)
5 (require 'patch-review-reply)
6 (require 'patch-review-git)
7 (require 'patch-review)
8
9 (defun patch-review-test-fixture (name)
10 "Return the contents of fixture NAME."
11 (with-temp-buffer
12 (insert-file-contents
13 (expand-file-name
14 (concat "fixtures/" name)
15 (file-name-directory (or load-file-name buffer-file-name))))
16 (buffer-string)))
17
18 (ert-deftest patch-review-test-fixtures-load ()
19 (dolist (f '("patch1.body" "patch2.body" "patch3.body" "cover.body"
20 "patch1.eml"))
21 (should (stringp (patch-review-test-fixture f)))))
22
23 ;;;; Parsing (ports of the diff-parse.js checks)
24
25 (ert-deftest patch-review-test-parse-patch1 ()
26 (let* ((p (patch-review-parse-email (patch-review-test-fixture "patch1.body")))
27 (file (car (patch-review-email-files p)))
28 (hunk (car (patch-review-file-hunks file))))
29 (should (= 1 (length (patch-review-email-files p))))
30 (should (equal "src/socket.c" (patch-review-file-display-path file)))
31 (should (string-prefix-p "Reject negative descriptors"
32 (patch-review-email-commit-message p)))
33 (should (string-match-p "1 file changed" (patch-review-email-diffstat p)))
34 (should (= 1 (length (patch-review-file-hunks file))))
35 (should (= 15 (patch-review-hunk-old-count hunk)))
36 (should (= 20 (patch-review-hunk-new-count hunk)))
37 (let ((first-add (cl-find 'add (patch-review-hunk-lines hunk)
38 :key #'patch-review-line-origin)))
39 (should (equal "#include <errno.h>" (patch-review-line-text first-add)))
40 (should (= 2 (patch-review-line-new-line first-add)))
41 (should (null (patch-review-line-old-line first-add))))
42 (let ((first-del (cl-find 'del (patch-review-hunk-lines hunk)
43 :key #'patch-review-line-origin)))
44 (should (= 5 (patch-review-line-old-line first-del)))
45 (should (null (patch-review-line-new-line first-del))))
46 ;; Raw lines must round-trip exactly for quoting in replies.
47 (let* ((lines (patch-review-hunk-lines hunk))
48 (start (patch-review-line-body-line (car lines)))
49 (body-lines (patch-review-email-body-lines p))
50 (slice (cl-loop for i from start below (+ start (length lines))
51 collect (aref body-lines i))))
52 (should (equal slice (mapcar #'patch-review-line-raw lines))))))
53
54 (ert-deftest patch-review-test-parse-patch2-new-file ()
55 (let* ((p (patch-review-parse-email (patch-review-test-fixture "patch2.body")))
56 (file (car (patch-review-email-files p))))
57 (should (patch-review-file-is-new file))
58 (should (equal "src/log.c" (patch-review-file-display-path file)))
59 (should (cl-every (lambda (l) (eq 'add (patch-review-line-origin l)))
60 (patch-review-hunk-lines
61 (car (patch-review-file-hunks file)))))))
62
63 (ert-deftest patch-review-test-parse-patch3-binary ()
64 (let* ((p (patch-review-parse-email (patch-review-test-fixture "patch3.body")))
65 (file (car (patch-review-email-files p))))
66 (should (patch-review-file-is-binary file))
67 (should (null (patch-review-file-hunks file)))))
68
69 (ert-deftest patch-review-test-parse-no-diff ()
70 (let ((p (patch-review-parse-email "just a message\nwith two lines")))
71 (should (null (patch-review-email-files p)))
72 (should (string-match-p "just a message"
73 (patch-review-email-commit-message p)))))
74
75 ;;;; Subject heuristics (ports of the patch-detect.js checks)
76
77 (ert-deftest patch-review-test-parse-subject ()
78 (let ((info (patch-review-parse-subject "[PATCH 1/2] x")))
79 (should (equal "PATCH" (plist-get info :prefix)))
80 (should (= 1 (plist-get info :version)))
81 (should (= 1 (plist-get info :n)))
82 (should (= 2 (plist-get info :m)))
83 (should (equal "x" (plist-get info :title))))
84 (let ((info (patch-review-parse-subject "[RFC PATCH v2 0/5] y")))
85 (should (equal "RFC PATCH" (plist-get info :prefix)))
86 (should (= 2 (plist-get info :version)))
87 (should (= 0 (plist-get info :n)))
88 (should (= 5 (plist-get info :m))))
89 (let ((info (patch-review-parse-subject "[PATCH net-next v3 1/2] z")))
90 (should (equal "PATCH NET-NEXT" (plist-get info :prefix)))
91 (should (= 3 (plist-get info :version))))
92 (let ((info (patch-review-parse-subject "Re: [PATCH] w")))
93 (should info)
94 (should (null (plist-get info :n)))
95 (should (null (plist-get info :m))))
96 (should (null (patch-review-parse-subject "not a patch"))))
97
98 (ert-deftest patch-review-test-detect ()
99 (should (patch-review-body-looks-like-diff-p
100 (patch-review-test-fixture "patch1.body")))
101 (should-not (patch-review-body-looks-like-diff-p "just some prose"))
102 (should (patch-review-patch-message-p
103 "[PATCH 1/2] x" (patch-review-test-fixture "patch1.body")))
104 (should (patch-review-patch-message-p
105 "a fix" (patch-review-test-fixture "patch1.body")))
106 ;; Cover letter: 0/m tag, no diff, still part of a series.
107 (should (patch-review-patch-message-p
108 "[PATCH 0/3] cover" (patch-review-test-fixture "cover.body")))
109 (should-not (patch-review-patch-message-p "a fix" "prose only")))
110
111 (ert-deftest patch-review-test-same-series ()
112 (should (patch-review-same-series-p
113 (patch-review-parse-subject "[PATCH v2 1/3] a")
114 (patch-review-parse-subject "[PATCH v2 3/3] c")))
115 (should-not (patch-review-same-series-p
116 (patch-review-parse-subject "[PATCH 1/3] a")
117 (patch-review-parse-subject "[PATCH v2 3/3] c")))
118 (should-not (patch-review-same-series-p
119 (patch-review-parse-subject "[PATCH 1/3] a")
120 (patch-review-parse-subject "[PATCH 1/4] b"))))
121
122 ;;;; Reply formatting (ports of the reply-format.js checks)
123
124 (ert-deftest patch-review-test-reply-format ()
125 (let* ((p (patch-review-parse-email (patch-review-test-fixture "patch1.body")))
126 (hunk (car (patch-review-file-hunks
127 (car (patch-review-email-files p)))))
128 (lines (patch-review-hunk-lines hunk))
129 (target (cl-position-if
130 (lambda (l) (equal "#include <errno.h>"
131 (patch-review-line-text l)))
132 lines))
133 (later (cl-position-if
134 (lambda (l) (string-match-p "errno = EBADF"
135 (patch-review-line-text l)))
136 lines))
137 (comments
138 (list (cons patch-review-reply-general "Looks good overall, two nits.")
139 (cons (patch-review-reply-locator 0 0 target)
140 "Is errno.h needed on all platforms?")
141 (cons (patch-review-reply-locator 0 0 later)
142 "EBADF or EINVAL here?")))
143 (reply (patch-review-reply-format p comments)))
144 (should (string-prefix-p "Looks good overall" reply))
145 (should (string-match-p
146 (regexp-quote "> diff --git a/src/socket.c b/src/socket.c") reply))
147 ;; The hunk header itself is not quoted; only hunk lines are.
148 (should-not (string-match-p
149 (regexp-quote (concat "> " (patch-review-hunk-header hunk)))
150 reply))
151 (let ((quoted (string-match (regexp-quote "> +#include <errno.h>") reply))
152 (comment (string-match (regexp-quote "Is errno.h needed") reply))
153 (second (string-match (regexp-quote "EBADF or EINVAL") reply)))
154 (should quoted)
155 (should (< quoted comment))
156 (should (< comment second)))))
157
158 (ert-deftest patch-review-test-reply-elision ()
159 ;; A comment deep into a hunk elides the unquoted head with "> [...]".
160 (let* ((p (patch-review-parse-email (patch-review-test-fixture "patch1.body")))
161 (hunk (car (patch-review-file-hunks
162 (car (patch-review-email-files p)))))
163 (last-idx (1- (length (patch-review-hunk-lines hunk))))
164 (reply (patch-review-reply-format
165 p (list (cons (patch-review-reply-locator 0 0 last-idx)
166 "tail comment")))))
167 (should (string-match-p (regexp-quote "> [...]") reply))
168 (should (string-suffix-p "tail comment\n" reply))))
169
170 (ert-deftest patch-review-test-reply-empty ()
171 (should (equal "" (patch-review-reply-format nil nil))))
172
173 ;;;; git integration
174
175 (defconst patch-review-test--base-content
176 "#include <stdio.h>\n\nint open_socket(int fd)\n{\n\tif (fd < 0)\n\t\treturn -1;\n\tprintf(\"opening %d\\n\", fd);\n\treturn fd;\n}\n\nint close_socket(int fd)\n{\n\tprintf(\"closing %d\\n\", fd);\n\treturn 0;\n}\n")
177
178 (defconst patch-review-test--new-content
179 "#include <stdio.h>\n#include <errno.h>\n\nint open_socket(int fd)\n{\n\tif (fd < 0) {\n\t\terrno = EBADF;\n\t\treturn -1;\n\t}\n\tprintf(\"opening %d\\n\", fd);\n\treturn fd;\n}\n\nint close_socket(int fd)\n{\n\tif (fd < 0)\n\t\treturn -1;\n\tprintf(\"closing %d\\n\", fd);\n\treturn 0;\n}\n")
180
181 (defun patch-review-test--write (dir path text)
182 (let ((file (expand-file-name path dir)))
183 (make-directory (file-name-directory file) t)
184 (write-region text nil file nil 'silent)))
185
186 (defun patch-review-test--git (dir &rest args)
187 (let ((result (apply #'patch-review-git--run dir args)))
188 (should (= 0 (car result)))
189 (cdr result)))
190
191 (defmacro patch-review-test--with-repo (dir &rest body)
192 "Create a throwaway git repo, bind DIR, evaluate BODY."
193 (declare (indent 1))
194 `(let ((,dir (make-temp-file "patch-review-repo" t))
195 (process-environment
196 (append '("GIT_AUTHOR_NAME=Test"
197 "GIT_AUTHOR_EMAIL=test@example.org"
198 "GIT_COMMITTER_NAME=Test"
199 "GIT_COMMITTER_EMAIL=test@example.org")
200 process-environment)))
201 (unwind-protect
202 (progn
203 (patch-review-test--git ,dir "init" "-q" "-b" "main")
204 ,@body)
205 (delete-directory ,dir t))))
206
207 (defun patch-review-test--make-patch (dir)
208 "Build DIR's history: base commit, patch commit; reset to base.
209 Return the path of a format-patch file for the patch commit."
210 (patch-review-test--write dir "src/socket.c"
211 patch-review-test--base-content)
212 (patch-review-test--git dir "add" ".")
213 (patch-review-test--git dir "commit" "-q" "-m" "base socket code")
214 (patch-review-test--write dir "src/socket.c"
215 patch-review-test--new-content)
216 (patch-review-test--git dir "add" ".")
217 (patch-review-test--git dir "commit" "-q" "-m"
218 "Reject negative descriptors in socket paths")
219 (let ((mbox (expand-file-name "patch.mbox" dir)))
220 (write-region (patch-review-test--git dir "format-patch" "-1" "--stdout")
221 nil mbox nil 'silent)
222 (patch-review-test--git dir "reset" "-q" "--hard" "HEAD~1")
223 mbox))
224
225 (ert-deftest patch-review-test-git-describe-and-dirty ()
226 (patch-review-test--with-repo dir
227 (patch-review-test--write dir "src/socket.c"
228 patch-review-test--base-content)
229 (patch-review-test--git dir "add" ".")
230 (patch-review-test--git dir "commit" "-q" "-m" "base")
231 (should (string-match-p (regexp-quote dir)
232 (patch-review-git-describe dir)))
233 (should-not (patch-review-git-dirty-p dir))
234 (patch-review-test--write dir "src/socket.c"
235 patch-review-test--new-content)
236 (should (eq t (patch-review-git-dirty-p dir)))))
237
238 (ert-deftest patch-review-test-git-check-applicable ()
239 (patch-review-test--with-repo dir
240 (let ((mbox (patch-review-test--make-patch dir)))
241 (should (eq 'applicable (car (patch-review-git-check dir mbox)))))))
242
243 (ert-deftest patch-review-test-git-apply-then-applied ()
244 (patch-review-test--with-repo dir
245 (let ((mbox (patch-review-test--make-patch dir)))
246 (should (eq 'applied (car (patch-review-git-apply dir mbox))))
247 (should (equal patch-review-test--new-content
248 (with-temp-buffer
249 (insert-file-contents
250 (expand-file-name "src/socket.c" dir))
251 (buffer-string))))
252 ;; Once applied, the probe must report it.
253 (should (eq 'applied (car (patch-review-git-check dir mbox)))))))
254
255 (ert-deftest patch-review-test-git-check-conflict ()
256 (patch-review-test--with-repo dir
257 (let ((mbox (patch-review-test--make-patch dir)))
258 ;; Commit a change that breaks the patch's context.
259 (patch-review-test--write
260 dir "src/socket.c"
261 (string-replace "if (fd < 0)" "if (fd < -1)"
262 patch-review-test--base-content))
263 (patch-review-test--git dir "add" ".")
264 (patch-review-test--git dir "commit" "-q" "-m" "diverge")
265 (should (eq 'conflict (car (patch-review-git-check dir mbox)))))))
266
267 (ert-deftest patch-review-test-git-check-dirty ()
268 (patch-review-test--with-repo dir
269 (let ((mbox (patch-review-test--make-patch dir)))
270 (patch-review-test--write dir "src/socket.c"
271 patch-review-test--new-content)
272 (should (eq 'dirty (car (patch-review-git-check dir mbox)))))))
273
274 (ert-deftest patch-review-test-git-am-in-progress-guard ()
275 (patch-review-test--with-repo dir
276 (let ((mbox (patch-review-test--make-patch dir)))
277 (should-not (patch-review-git-am-in-progress-p dir))
278 (make-directory (expand-file-name ".git/rebase-apply" dir) t)
279 (should (patch-review-git-am-in-progress-p dir))
280 (should (eq 'am-in-progress (car (patch-review-git-apply dir mbox))))
281 ;; The repository must be untouched.
282 (should (string-match-p "base socket code"
283 (patch-review-test--git
284 dir "log" "-1" "--format=%s"))))))
285
286 (ert-deftest patch-review-test-git-concat-mboxes ()
287 (let ((a (make-temp-file "pr-a")) (b (make-temp-file "pr-b")))
288 (unwind-protect
289 (progn
290 (write-region "first-no-eol" nil a nil 'silent)
291 (write-region "second\n" nil b nil 'silent)
292 (let ((cat (patch-review-git-concat-mboxes (list a b))))
293 (unwind-protect
294 (should (equal "first-no-eol\nsecond\n"
295 (with-temp-buffer
296 (insert-file-contents cat)
297 (buffer-string))))
298 (delete-file cat))))
299 (delete-file a)
300 (delete-file b))))
301
302 ;;;; Comment extraction
303
304 (defun patch-review-test--insert-after-line (text prefix insertion)
305 "Return TEXT with INSERTION added on a new line after the first line
306 starting with PREFIX."
307 (let ((lines (split-string text "\n")) (out nil) (done nil))
308 (dolist (l lines)
309 (push l out)
310 (when (and (not done) (string-prefix-p prefix l))
311 (push insertion out)
312 (setq done t)))
313 (unless done (error "No line starting with %S" prefix))
314 (mapconcat #'identity (nreverse out) "\n")))
315
316 (defun patch-review-test--remove-line (text prefix)
317 "Return TEXT without its first line starting with PREFIX."
318 (let ((lines (split-string text "\n")) (out nil) (done nil))
319 (dolist (l lines)
320 (if (and (not done) (string-prefix-p prefix l))
321 (setq done t)
322 (push l out)))
323 (unless done (error "No line starting with %S" prefix))
324 (mapconcat #'identity (nreverse out) "\n")))
325
326 (defun patch-review-test--extract (parsed pristine current)
327 (patch-review-extract-comments parsed pristine current))
328
329 (ert-deftest patch-review-test-extract-no-edits ()
330 (let* ((body (patch-review-test-fixture "patch1.body"))
331 (parsed (patch-review-parse-email body)))
332 (should (equal '(nil 0 0)
333 (patch-review-test--extract parsed body body)))))
334
335 (ert-deftest patch-review-test-extract-general ()
336 (let* ((body (patch-review-test-fixture "patch1.body"))
337 (parsed (patch-review-parse-email body))
338 (edited (concat "Ship it.\n" body)))
339 (pcase-let ((`(,comments ,del ,chg)
340 (patch-review-test--extract parsed body edited)))
341 (should (equal '(0 0) (list del chg)))
342 (should (equal "Ship it."
343 (cdr (assoc patch-review-reply-general comments)))))))
344
345 (ert-deftest patch-review-test-extract-hunk-comment ()
346 (let* ((body (patch-review-test-fixture "patch1.body"))
347 (parsed (patch-review-parse-email body))
348 (edited (patch-review-test--insert-after-line
349 body "+#include <errno.h>" "Is this portable?"))
350 (hunk (car (patch-review-file-hunks
351 (car (patch-review-email-files parsed)))))
352 (target (cl-position-if
353 (lambda (l) (equal "#include <errno.h>"
354 (patch-review-line-text l)))
355 (patch-review-hunk-lines hunk))))
356 (pcase-let ((`(,comments ,del ,chg)
357 (patch-review-test--extract parsed body edited)))
358 (should (equal '(0 0) (list del chg)))
359 (should (equal "Is this portable?"
360 (cdr (assoc (patch-review-reply-locator 0 0 target)
361 comments)))))))
362
363 (ert-deftest patch-review-test-extract-diffstat-is-general ()
364 (let* ((body (patch-review-test-fixture "patch1.body"))
365 (parsed (patch-review-parse-email body))
366 (edited (patch-review-test--insert-after-line
367 body " src/socket.c |" "Nice diffstat.")))
368 (pcase-let ((`(,comments ,_ ,_)
369 (patch-review-test--extract parsed body edited)))
370 (should (equal "Nice diffstat."
371 (cdr (assoc patch-review-reply-general comments)))))))
372
373 (ert-deftest patch-review-test-extract-deletion ()
374 (let* ((body (patch-review-test-fixture "patch1.body"))
375 (parsed (patch-review-parse-email body))
376 (edited (patch-review-test--remove-line body " \tprintf(\"opening")))
377 (pcase-let ((`(,comments ,del ,chg)
378 (patch-review-test--extract parsed body edited)))
379 (should (null comments))
380 (should (equal '(1 0) (list del chg))))))
381
382 (ert-deftest patch-review-test-extract-change-is-comment ()
383 (let* ((body (patch-review-test-fixture "patch1.body"))
384 (parsed (patch-review-parse-email body))
385 (edited (patch-review-test--insert-after-line
386 (patch-review-test--remove-line body "+#include <errno.h>")
387 " #include <stdio.h>" "Why not <errno.h>?")))
388 (pcase-let ((`(,comments ,del ,chg)
389 (patch-review-test--extract parsed body edited)))
390 ;; One context line was not rewritten; the removed + line counts
391 ;; as a change and the new text is a comment.
392 (should (= 1 chg))
393 (should (= 0 del))
394 (should (cl-some (lambda (e) (string-match-p "Why not" (cdr e)))
395 comments)))))
396
397 (ert-deftest patch-review-test-extract-trailing-newline-deletion ()
398 ;; patch1.body ends with "2.55.0\n\n": dropping one trailing newline
399 ;; deletes the (empty) final line and is reported as a deletion.
400 (let* ((body (patch-review-test-fixture "patch1.body"))
401 (parsed (patch-review-parse-email body))
402 (edited (string-remove-suffix "\n" body)))
403 (should (equal '(nil 1 0)
404 (patch-review-test--extract parsed body edited)))))
405
406 (ert-deftest patch-review-test-extract-final-newline-normalized ()
407 ;; A missing final newline in the edited buffer is normalized away
408 ;; before diffing: it produces neither comments nor deletion counts.
409 (let* ((parsed (patch-review-parse-email "l1\nl2\n"))
410 (pristine "l1\nl2\n")
411 (edited "l1\nl2"))
412 (should (equal '(nil 0 0)
413 (patch-review-test--extract parsed pristine edited)))))
414
415 ;;;; End-to-end: open, comment, send
416
417 (ert-deftest patch-review-test-open-and-send ()
418 (let* ((eml (expand-file-name
419 "fixtures/patch1.eml"
420 (file-name-directory (or load-file-name buffer-file-name))))
421 (buf (patch-review-open-message-file eml)))
422 (unwind-protect
423 (with-current-buffer buf
424 (should (eq major-mode 'patch-review-mode))
425 (should (eq (lookup-key patch-review-mode-map (kbd "C-c C-c"))
426 #'patch-review-send-review))
427 (goto-char (point-min))
428 (re-search-forward "^\+#include <errno.h>$")
429 (end-of-line)
430 (insert "\nIs this portable?")
431 (patch-review-send-review)
432 ;; Now in the message-mode draft.
433 (let ((mail (buffer-string)))
434 (should (string-match-p
435 "^To: Aisha Developer <aisha@example.org>$" mail))
436 (should (string-match-p
437 (concat "^" (regexp-quote
438 "Subject: Re: [PATCH 1/3] Reject negative"))
439 mail))
440 (should (string-match-p
441 "^In-Reply-To: <patch1@example.org>$" mail))
442 (should (string-match-p
443 (regexp-quote "> +#include <errno.h>") mail))
444 (should (string-match-p
445 (concat "^" (regexp-quote "Is this portable?") "$")
446 mail))
447 (kill-buffer)))
448 (when (buffer-live-p buf) (kill-buffer buf)))))
449
450 (ert-deftest patch-review-test-send-without-comments-aborts ()
451 (let* ((eml (expand-file-name
452 "fixtures/patch1.eml"
453 (file-name-directory (or load-file-name buffer-file-name))))
454 (buf (patch-review-open-message-file eml)))
455 (unwind-protect
456 (with-current-buffer buf
457 (cl-letf (((symbol-function #'y-or-n-p) (lambda (_) nil)))
458 (should-error (patch-review-send-review) :type 'user-error)))
459 (when (buffer-live-p buf) (kill-buffer buf)))))
460
461 (ert-deftest patch-review-test-open-in-place ()
462 "Opening with IN-PLACE reuses the current buffer rather than making one."
463 (let* ((eml (expand-file-name
464 "fixtures/patch1.eml"
465 (file-name-directory (or load-file-name buffer-file-name))))
466 (text (with-temp-buffer
467 (insert-file-contents eml)
468 (buffer-string)))
469 (view (get-buffer-create " *patch-review-fake-view* ")))
470 (unwind-protect
471 (let ((returned
472 (with-current-buffer view
473 (insert "stale mu4e contents")
474 (patch-review--open-text text eml nil t))))
475 (should (eq returned view))
476 (with-current-buffer view
477 (should (eq major-mode 'patch-review-mode))
478 (should (string-prefix-p "*Review:" (buffer-name)))
479 ;; The review body, not the discarded text, is now the buffer.
480 (should (string-match-p "diff --git" patch-review--pristine))
481 (should-not (string-match-p "stale mu4e contents"
482 patch-review--pristine))
483 ;; The on-disk message file is the git-am source.
484 (should (file-equal-p patch-review--source-file eml))))
485 (when (buffer-live-p view) (kill-buffer view)))))
486
487 (ert-deftest patch-review-test-in-place-editable-from-readonly ()
488 "An in-place review taken over from a read-only special-mode buffer
489 (like a mu4e article) must be fully editable.
490
491 `diff-mode' latches `diff-mode-read-only' to the read-only state its
492 parent body sees; a buffer that started read-only would otherwise keep
493 `diff-mode-shared-map' active, so RET became `diff-goto-source' (the
494 read-file-name prompt from diff-find-file-name), digits hit
495 digit-argument, and the special-mode remap of self-insert-command to
496 undefined made every printable key a no-op. We clear the latch in
497 `patch-review-mode' so the review buffer behaves like a normal editable
498 buffer."
499 (let* ((eml (expand-file-name
500 "fixtures/patch1.eml"
501 (file-name-directory (or load-file-name buffer-file-name))))
502 (text (with-temp-buffer
503 (insert-file-contents eml)
504 (buffer-string)))
505 (view (get-buffer-create " *patch-review-fake-ro-view* ")))
506 (unwind-protect
507 (progn
508 ;; Start read-only, the way a mu4e article buffer is.
509 (with-current-buffer view
510 (special-mode)
511 (let ((inhibit-read-only t)) (erase-buffer) (insert "stale"))
512 (should (eq buffer-read-only t)))
513 (let ((returned
514 (with-current-buffer view
515 (patch-review--open-text text eml nil t))))
516 (should (eq returned view))
517 (with-current-buffer view
518 (should (eq major-mode 'patch-review-mode))
519 (should (null buffer-read-only))
520 ;; The diff-mode short-key latch must be cleared, and with
521 ;; it gone the inherited diff/special bindings don't hijack
522 ;; ordinary editing keys.
523 (should (null diff-mode-read-only))
524 (should (eq (key-binding (kbd "RET")) #'newline))
525 (should (eq (key-binding "1") #'self-insert-command))
526 ;; Direct insertion succeeds (no read-only short-circuit).
527 (let ((before (buffer-size)))
528 (goto-char (point-max))
529 (insert "new comment\n")
530 (should (= (- (buffer-size) before)
531 (length "new comment\n")))))))
532 (when (buffer-live-p view) (kill-buffer view)))))
533
534 ;;;; End-to-end: status probe and apply
535
536 (ert-deftest patch-review-test-status-and-apply ()
537 (patch-review-test--with-repo dir
538 (let* ((mbox (patch-review-test--make-patch dir))
539 (text (with-temp-buffer
540 (insert-file-contents mbox)
541 (buffer-string)))
542 (buf (patch-review--open-text text mbox dir)))
543 (unwind-protect
544 (with-current-buffer buf
545 (should (eq 'applicable patch-review--status))
546 (should (string-match-p "applies cleanly"
547 (patch-review--header-line)))
548 (patch-review-apply)
549 (should (eq 'applied patch-review--status))
550 (should (equal patch-review-test--new-content
551 (with-temp-buffer
552 (insert-file-contents
553 (expand-file-name "src/socket.c" dir))
554 (buffer-string))))
555 ;; And the probe agrees afterwards.
556 (patch-review-refresh-status)
557 (should (eq 'applied patch-review--status)))
558 (when (buffer-live-p buf) (kill-buffer buf))))))
559
560 (provide 'patch-review-test)
561 ;;; patch-review-test.el ends here