diff options
Diffstat (limited to 'tests/test-pearl-comments.el')
| -rw-r--r-- | tests/test-pearl-comments.el | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/tests/test-pearl-comments.el b/tests/test-pearl-comments.el new file mode 100644 index 0000000..7fa4482 --- /dev/null +++ b/tests/test-pearl-comments.el @@ -0,0 +1,170 @@ +;;; test-pearl-comments.el --- Tests for issue comments -*- 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 comment thread: rendering a normalized comment and the +;; oldest-first Comments subtree, including comments in the issue render, the +;; commentCreate helper (stubbed at the HTTP boundary), the in-place append +;; under the Comments subtree (creating it when absent), and the +;; `pearl-add-comment' command. + +;;; Code: + +(require 'test-bootstrap (expand-file-name "test-bootstrap.el")) +(require 'testutil-request (expand-file-name "testutil-request.el")) +(require 'cl-lib) + +(defmacro test-pearl--in-org (content &rest body) + "Run BODY in an org-mode temp buffer holding CONTENT, default mapping bound." + (declare (indent 1)) + `(let ((pearl-state-to-todo-mapping + '(("Todo" . "TODO") ("In Progress" . "IN-PROGRESS") ("Done" . "DONE"))) + (org-todo-keywords '((sequence "TODO" "IN-PROGRESS" "|" "DONE")))) + (with-temp-buffer + (insert ,content) + (org-mode) + (goto-char (point-min)) + ,@body))) + +;;; --format-comment / --format-comments + +(ert-deftest test-pearl-format-comment-renders-author-time-body () + "A comment renders as a level-4 heading with author and timestamp, body below." + (let ((out (pearl--format-comment + '(:id "c1" :author "Craig" :created-at "2026-05-23T10:00:00.000Z" + :body "Looks **good** to me")))) + (should (string-match-p "^\\*\\*\\*\\* Craig — 2026-05-23T10:00:00.000Z$" out)) + ;; body runs through the md->org tier + (should (string-match-p "Looks \\*good\\* to me" out)))) + +(ert-deftest test-pearl-format-comment-null-author () + "A comment with no resolved author renders a placeholder, not an error." + (let ((out (pearl--format-comment + '(:id "c1" :author nil :created-at "2026-05-23T10:00:00.000Z" :body "hi")))) + (should (string-match-p "^\\*\\*\\*\\* (unknown) — 2026-05-23T10:00:00.000Z$" out)))) + +(ert-deftest test-pearl-format-comments-empty-is-blank () + "No comments renders nothing (no empty Comments subtree)." + (should (string= "" (pearl--format-comments nil)))) + +(ert-deftest test-pearl-format-comments-oldest-first () + "Comments render under a Comments heading, oldest first regardless of input order." + (let ((out (pearl--format-comments + '((:id "c2" :author "B" :created-at "2026-05-23T12:00:00.000Z" :body "second") + (:id "c1" :author "A" :created-at "2026-05-23T09:00:00.000Z" :body "first"))))) + (should (string-match-p "^\\*\\*\\* Comments$" out)) + (should (< (string-match "first" out) (string-match "second" out))))) + +;;; comments in the issue render + +(ert-deftest test-pearl-format-issue-includes-comments () + "A normalized issue carrying comments renders the Comments subtree after the body." + (test-pearl--in-org "" + (let ((out (pearl--format-issue-as-org-entry + '(:id "u" :identifier "ENG-1" :title "Title" :priority 2 + :state (:name "Todo") :description "Body text." + :comments ((:id "c1" :author "A" :created-at "2026-05-23T09:00:00.000Z" + :body "a comment")))))) + (should (string-match-p "^\\*\\*\\* Comments$" out)) + (should (< (string-match "Body text." out) (string-match "a comment" out)))))) + +;;; --create-comment-async + +(ert-deftest test-pearl-create-comment-parses-payload () + "A successful commentCreate yields the normalized comment." + (testutil-linear-with-response + '((data (commentCreate + (success . t) + (comment (id . "c9") (body . "new one") + (createdAt . "2026-05-23T13:00:00.000Z") + (user (name . "Craig")))))) + (let (result) + (pearl--create-comment-async "issue-a" "new one" (lambda (r) (setq result r))) + (should (string= "c9" (plist-get result :id))) + (should (string= "Craig" (plist-get result :author)))))) + +(ert-deftest test-pearl-create-comment-soft-fail () + "A non-success commentCreate yields nil rather than erroring." + (testutil-linear-with-response + '((data (commentCreate (success . :json-false) (comment . nil)))) + (let ((called nil) (result 'untouched)) + (pearl--create-comment-async "issue-a" "x" (lambda (r) (setq called t result r))) + (should called) + (should (null result))))) + +;;; --append-comment-to-issue + +(ert-deftest test-pearl-append-comment-creates-subtree () + "Appending to an issue with no Comments subtree creates one." + (test-pearl--in-org + "*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n" + (pearl--append-comment-to-issue + '(:id "c1" :author "A" :created-at "2026-05-23T09:00:00.000Z" :body "first comment")) + (goto-char (point-min)) + (should (re-search-forward "^\\*\\*\\* Comments$" nil t)) + (should (re-search-forward "first comment" nil t)))) + +(ert-deftest test-pearl-append-comment-after-existing () + "A new comment appends after an existing one under the Comments subtree." + (test-pearl--in-org + "** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n*** Comments\n**** A — 2026-05-23T09:00:00.000Z\nfirst\n" + (pearl--append-comment-to-issue + '(:id "c2" :author "B" :created-at "2026-05-23T12:00:00.000Z" :body "second")) + (goto-char (point-min)) + ;; only one Comments heading, and the new comment follows the first + (should (re-search-forward "^\\*\\*\\* Comments$" nil t)) + (should-not (re-search-forward "^\\*\\*\\* Comments$" nil t)) + (goto-char (point-min)) + (should (< (progn (re-search-forward "first") (point)) + (progn (re-search-forward "second") (point)))))) + +;;; pearl-add-comment + +(ert-deftest test-pearl-add-comment-appends-returned-comment () + "The command creates a comment and inserts the returned one in the buffer." + (test-pearl--in-org + "*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n" + (cl-letf (((symbol-function 'pearl--create-comment-async) + (lambda (_id body cb) + (funcall cb (list :id "c1" :author "Craig" + :created-at "2026-05-23T14:00:00.000Z" :body body))))) + (re-search-forward "Body.") + (pearl-add-comment "my new comment") + (goto-char (point-min)) + (should (re-search-forward "^\\*\\*\\* Comments$" nil t)) + (should (re-search-forward "my new comment" nil t))))) + +(ert-deftest test-pearl-add-comment-reports-failure () + "A failed create does not insert a Comments subtree." + (test-pearl--in-org + "*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n" + (cl-letf (((symbol-function 'pearl--create-comment-async) + (lambda (_id _body cb) (funcall cb nil)))) + (pearl-add-comment "x") + (goto-char (point-min)) + (should-not (re-search-forward "^\\*\\*\\* Comments$" nil t))))) + +(ert-deftest test-pearl-add-comment-not-on-issue-errors () + "Adding a comment outside a Linear issue heading signals a user error." + (test-pearl--in-org "* Plain heading\nno id\n" + (should-error (pearl-add-comment "x") :type 'user-error))) + +(provide 'test-pearl-comments) +;;; test-pearl-comments.el ends here |
