;;; test-pearl-fetch-all-comments.el --- Tests for fetching a full comment thread -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Craig Jennings ;; Author: Craig Jennings ;; 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 . ;;; Commentary: ;; Tests for pearl-fetch-all-comments: the keybinding, the in-place ;; Comments-subtree replacement with an exact N/N count, and the unpushed-edit ;; guard that refuses to clobber a locally-edited comment. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el")) (require 'cl-lib) (defmacro test-pearl-fac--in-org (content &rest body) "Run BODY in an org-mode temp buffer holding CONTENT." (declare (indent 1)) `(let ((org-todo-keywords '((sequence "TODO" "IN-PROGRESS" "|" "DONE")))) (with-temp-buffer (insert ,content) (org-mode) (goto-char (point-min)) ,@body))) (defconst test-pearl-fac--with-comments (concat "* My open issues\n" "** TODO SE-1: Alpha\n:PROPERTIES:\n:LINEAR-ID: i1\n:LINEAR-IDENTIFIER: SE-1\n:END:\n" "The description body.\n" "*** Comments 2/5+\n" "**** Old Author — 2026-01-01T00:00:00.000Z\n" ":PROPERTIES:\n:LINEAR-COMMENT-ID: oc1\n:LINEAR-COMMENT-AUTHOR-ID: u1\n" ":LINEAR-COMMENT-SHA256: deadbeef\n:LINEAR-COMMENT-ORG-SHA256: deadbeef\n:END:\n" "old comment one\n") "An issue with a capped (overflowing) Comments subtree and a stale-hash comment.") (defconst test-pearl-fac--no-comments (concat "* My open issues\n" "** TODO SE-1: Alpha\n:PROPERTIES:\n:LINEAR-ID: i1\n:LINEAR-IDENTIFIER: SE-1\n:END:\n" "The description body.\n") "An issue with no Comments subtree.") (defun test-pearl-fac--goto-issue () "Move point onto the issue heading." (goto-char (point-min)) (re-search-forward "^\\*\\* TODO SE-1") (beginning-of-line)) ;;; keybinding (ert-deftest test-pearl-fetch-all-comments-bound () "The command is bound under the fetch sub-keymap at `c'." (should (eq 'pearl-fetch-all-comments (lookup-key pearl-fetch-map (kbd "c"))))) ;;; subtree replacement (ert-deftest test-pearl-replace-comments-subtree-replaces-and-counts () "Replacing swaps the capped thread for the full set with an exact N/N count." (test-pearl-fac--in-org test-pearl-fac--with-comments (test-pearl-fac--goto-issue) (pearl--replace-comments-subtree (list '(:id "c1" :author "Bob" :created-at "2026-02-01T00:00:00.000Z" :body "new one") '(:id "c2" :author "Sue" :created-at "2026-02-02T00:00:00.000Z" :body "new two"))) (goto-char (point-min)) (should (search-forward "Comments 2/2" nil t)) (goto-char (point-min)) (should (search-forward "The description body." nil t)) (goto-char (point-min)) (should (search-forward "new one" nil t)) (goto-char (point-min)) (should-not (search-forward "old comment one" nil t)))) (ert-deftest test-pearl-replace-comments-subtree-creates-when-absent () "An issue with no Comments subtree gets one inserted." (test-pearl-fac--in-org test-pearl-fac--no-comments (test-pearl-fac--goto-issue) (pearl--replace-comments-subtree (list '(:id "c1" :author "Bob" :created-at "2026-02-01T00:00:00.000Z" :body "hello"))) (goto-char (point-min)) (should (search-forward "Comments 1/1" nil t)) (goto-char (point-min)) (should (search-forward "hello" nil t)) (goto-char (point-min)) (should (search-forward "The description body." nil t)))) (ert-deftest test-pearl-replace-comments-subtree-empty-clears () "An empty thread clears the Comments subtree." (test-pearl-fac--in-org test-pearl-fac--with-comments (test-pearl-fac--goto-issue) (pearl--replace-comments-subtree nil) (goto-char (point-min)) (should-not (search-forward "Comments" nil t)) (goto-char (point-min)) (should (search-forward "The description body." nil t)))) ;;; unpushed-edit guard (ert-deftest test-pearl-unpushed-comment-edits-p-detects-dirty () "A comment whose body no longer matches its stored hash is an unpushed edit." (test-pearl-fac--in-org test-pearl-fac--with-comments (test-pearl-fac--goto-issue) (should (pearl--unpushed-comment-edits-p)))) (ert-deftest test-pearl-unpushed-comment-edits-p-clean-when-no-comments () "An issue with no comments has no unpushed comment edits." (test-pearl-fac--in-org test-pearl-fac--no-comments (test-pearl-fac--goto-issue) (should-not (pearl--unpushed-comment-edits-p)))) ;;; save-after-fetch helper (ert-deftest test-pearl-save-if-visiting-noop-without-file () "In a non-file buffer the save helper is a no-op, not an error." (with-temp-buffer (insert "x") (should-not (pearl--save-if-visiting)))) (ert-deftest test-pearl-save-if-visiting-saves-file-buffer () "In a file-visiting buffer the helper writes the buffer to disk." (let ((f (make-temp-file "pearl-save-" nil ".org"))) (unwind-protect (let ((buf (find-file-noselect f))) (unwind-protect (with-current-buffer buf (insert "hello pearl") (should (buffer-modified-p)) (pearl--save-if-visiting) (should-not (buffer-modified-p)) (should (string-match-p "hello pearl" (with-temp-buffer (insert-file-contents f) (buffer-string))))) (kill-buffer buf))) (delete-file f)))) (provide 'test-pearl-fetch-all-comments) ;;; test-pearl-fetch-all-comments.el ends here