diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-24 17:12:52 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-24 17:12:52 -0500 |
| commit | bb30b0f9146722b279832a991540917c3fa3cb81 (patch) | |
| tree | 01ebe73400e3aa28136764c180fe174db9900839 /tests | |
| parent | 424d7048d5450131283f6bdb99822aa6bccd6b16 (diff) | |
| download | pearl-bb30b0f9146722b279832a991540917c3fa3cb81.tar.gz pearl-bb30b0f9146722b279832a991540917c3fa3cb81.zip | |
feat: compose comments and descriptions in an Org buffer
The minibuffer is too cramped to write a real comment or description. I added a shared compose buffer: a focused Org buffer with a read-only instructional header at the top (like a git commit template) and an editable body below, where C-c C-c submits and C-c C-k cancels. It's the sibling of the smerge conflict buffer, built the same way.
Two commands use it. pearl-add-comment, run interactively, now opens the composer and converts the Org body to Markdown before sending. Called with an explicit body (tests, programmatic callers), it still sends that directly. pearl-compose-current-description is new: it pops the issue's current description into the composer and, on submit, writes it back into the body and syncs through the existing conflict gate. Both work from anywhere inside an issue subtree.
The header is genuinely uneditable: read-only text properties, with only the last character rear-nonsticky so the body stays editable while edits inside the header are refused. The body is everything below it, extracted by a marker.
I left pearl-new-issue on the minibuffer for now. Wiring its description into the composer means restructuring that long, untested interactive flow to defer the create into the submit callback, which is worth doing on its own rather than riding along here. Filed as a follow-up.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-pearl-compose.el | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/tests/test-pearl-compose.el b/tests/test-pearl-compose.el new file mode 100644 index 0000000..bda75cb --- /dev/null +++ b/tests/test-pearl-compose.el @@ -0,0 +1,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 |
