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
|
;;; test-pearl-compose.el --- Tests for the compose buffer -*- lexical-binding: t; -*-
;; Copyright (C) 2026 Craig Jennings
;; Author: Craig Jennings <c@cjennings.net>
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Tests for the shared compose buffer (`pearl--compose-in-buffer' and its
;; submit/abort/body helpers) and the two commands wired onto it:
;; `pearl-add-comment' (interactive composer path) and
;; `pearl-compose-current-description'. The body is extracted below a read-only
;; header and converted Org->Markdown by the callers.
;;; Code:
(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
(require 'cl-lib)
(defmacro test-pearl--with-compose (initial &rest body)
"Open a compose buffer holding INITIAL, run BODY in it, capturing the submit.
Binds `captured' to the value the on-finish receives (or `:none'). Stubs
`pop-to-buffer' so the helper does not touch windows in batch."
(declare (indent 1))
`(let ((captured :none))
(cl-letf (((symbol-function 'pop-to-buffer) (lambda (b &rest _) b)))
(let ((buf (pearl--compose-in-buffer
"test" "# header line\n" ,initial
(lambda (text) (setq captured text)))))
(with-current-buffer buf ,@body)
(when (buffer-live-p buf) (kill-buffer buf))))))
;;; --compose-in-buffer setup
(ert-deftest test-pearl-compose-sets-up-org-buffer ()
"The compose buffer is an Org buffer with the header and the initial body."
(test-pearl--with-compose "draft text"
(should (eq major-mode 'org-mode))
(should (markerp pearl--compose-body-start))
(should (string-match-p "# header line" (buffer-string)))
(should (string= "draft text" (pearl--compose-body)))))
(ert-deftest test-pearl-compose-body-excludes-header ()
"The body is only the text below the read-only header."
(test-pearl--with-compose ""
(goto-char (point-max))
(insert "the new body")
(should (string= "the new body" (pearl--compose-body)))))
(ert-deftest test-pearl-compose-header-is-read-only ()
"Editing inside the header is refused."
(test-pearl--with-compose "body"
(goto-char (+ (point-min) 3))
(should-error (insert "x") :type 'text-read-only)))
;;; submit / abort
(ert-deftest test-pearl-compose-submit-hands-body-to-callback ()
"C-c C-c hands the trimmed body to the callback and kills the buffer."
(let ((captured :none) buf)
(cl-letf (((symbol-function 'pop-to-buffer) (lambda (b &rest _) b)))
(setq buf (pearl--compose-in-buffer
"t" "# h\n" " hello "
(lambda (text) (setq captured text))))
(with-current-buffer buf (pearl--compose-submit))
(should (string= "hello" captured))
(should-not (buffer-live-p buf)))))
(ert-deftest test-pearl-compose-abort-skips-callback ()
"C-c C-k kills the buffer without invoking the callback."
(let ((called nil) buf)
(cl-letf (((symbol-function 'pop-to-buffer) (lambda (b &rest _) b)))
(setq buf (pearl--compose-in-buffer
"t" "# h\n" "body" (lambda (_) (setq called t))))
(with-current-buffer buf (pearl--compose-abort))
(should-not called)
(should-not (buffer-live-p buf)))))
;;; add-comment composer wiring
(defmacro test-pearl--in-issue (&rest body)
"Run BODY in an org buffer with point inside a Linear issue subtree."
(declare (indent 0))
`(let ((pearl-state-to-todo-mapping '(("Todo" . "TODO"))))
(with-temp-buffer
(insert "** TODO [#B] ENG-1: Issue\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n")
(org-mode)
(goto-char (point-min))
(re-search-forward "Body\\.")
,@body)))
(ert-deftest test-pearl-add-comment-interactive-composes-and-converts ()
"An interactive add-comment composes Org and sends the Markdown conversion."
(test-pearl--in-issue
(let (sent)
(cl-letf (((symbol-function 'pearl--compose-in-buffer)
(lambda (_label _instr initial on-finish)
;; the composer would start empty for a new comment
(should (string= "" initial))
(funcall on-finish "see *bold* and `code`")))
((symbol-function 'pearl--create-comment-async)
(lambda (_id body _cb) (setq sent body))))
(pearl-add-comment) ; interactive form: body nil
;; Org markup converted to Markdown before sending
(should (string= "see **bold** and `code`" sent))))))
(ert-deftest test-pearl-add-comment-with-body-skips-composer ()
"A non-interactive add-comment with BODY sends it directly, no composer."
(test-pearl--in-issue
(let ((composed nil) sent)
(cl-letf (((symbol-function 'pearl--compose-in-buffer)
(lambda (&rest _) (setq composed t)))
((symbol-function 'pearl--create-comment-async)
(lambda (_id body _cb) (setq sent body))))
(pearl-add-comment "literal body")
(should-not composed)
(should (string= "literal body" sent))))))
;;; compose-current-description wiring
(ert-deftest test-pearl-compose-description-seeds-body-and-syncs ()
"The description composer seeds the current body and, on submit, writes it back and syncs."
(test-pearl--in-issue
(let (seeded synced)
(cl-letf (((symbol-function 'pearl--compose-in-buffer)
(lambda (_label _instr initial on-finish)
(setq seeded initial)
(funcall on-finish "Edited description")))
((symbol-function 'pearl-sync-current-issue)
(lambda () (setq synced t))))
(pearl-compose-current-description)
;; seeded with the existing body
(should (string= "Body." seeded))
;; the edited text was written into the issue body, then synced
(should synced)
(goto-char (point-min))
(should (re-search-forward "Edited description" nil t))))))
(provide 'test-pearl-compose)
;;; test-pearl-compose.el ends here
|