diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-pearl-save.el | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el new file mode 100644 index 0000000..24db264 --- /dev/null +++ b/tests/test-pearl-save.el @@ -0,0 +1,123 @@ +;;; test-pearl-save.el --- Tests for the unified ticket save model -*- 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 unified ticket save model (see +;; docs/ticket-save-model-spec.org). Phase 1: the local dirty scanners +;; (`pearl--issue-dirty-fields', `pearl--changed-comment-candidates') and the +;; viewer-based ownership classifier (`pearl--classify-comment-candidates'). + +;;; Code: + +(require 'test-bootstrap (expand-file-name "test-bootstrap.el")) + +(defun test-pearl-save--issue () + "A normalized issue with a description and one own comment (author-id u-craig)." + '(:id "u" :identifier "ENG-1" :title "fix the bug" :priority 2 + :state (:name "Todo") :description "Original description." + :comments ((:id "c1" :author "Craig" :author-id "u-craig" + :created-at "2026-05-24T00:00:00.000Z" :body "my comment")))) + +(defmacro test-pearl-save--in-rendered (issue &rest body) + "Render ISSUE into an org buffer, then run BODY with point at the start." + (declare (indent 1)) + `(let ((pearl-state-to-todo-mapping '(("Todo" . "TODO"))) + (pearl-comment-sort-order 'newest-first)) + (with-temp-buffer + (insert (pearl--format-issue-as-org-entry ,issue)) + (org-mode) + (goto-char (point-min)) + ,@body))) + +;;; --issue-dirty-fields (Phase A, local) + +(ert-deftest test-pearl-dirty-fields-clean-issue () + "A freshly rendered issue has no dirty fields and no comment candidates." + (test-pearl-save--in-rendered (test-pearl-save--issue) + (re-search-forward "Original description") + (let ((d (pearl--issue-dirty-fields))) + (should-not (plist-get d :title)) + (should-not (plist-get d :description)) + (should-not (plist-get d :comment-candidates))))) + +(ert-deftest test-pearl-dirty-fields-title-edit () + "Editing the heading title (past the identifier prefix) marks the title dirty." + (test-pearl-save--in-rendered (test-pearl-save--issue) + (re-search-forward "Fix the Bug") + (org-back-to-heading t) + (org-edit-headline "ENG-1: Fix the Cache Bug") + (let ((d (pearl--issue-dirty-fields))) + (should (plist-get d :title)) + (should-not (plist-get d :description))))) + +(ert-deftest test-pearl-dirty-fields-description-edit () + "Editing the body marks the description dirty, not the title." + (test-pearl-save--in-rendered (test-pearl-save--issue) + (re-search-forward "Original description\\.") + (end-of-line) + (insert " EDITED") + (let ((d (pearl--issue-dirty-fields))) + (should (plist-get d :description)) + (should-not (plist-get d :title))))) + +(ert-deftest test-pearl-dirty-fields-lossy-description-stays-clean () + "A description whose markdown is lossy under org->md is not falsely dirty (HP1)." + (test-pearl-save--in-rendered + '(:id "u" :identifier "ENG-2" :title "t" :priority 3 + :state (:name "Todo") :description "# Heading\nbody text") + (re-search-forward "body text") + (should-not (plist-get (pearl--issue-dirty-fields) :description)))) + +(ert-deftest test-pearl-dirty-fields-comment-candidate () + "An edited comment surfaces as a candidate carrying its id and author-id." + (test-pearl-save--in-rendered (test-pearl-save--issue) + ;; edit the comment body + (re-search-forward "my comment") + (end-of-line) + (insert " edited") + ;; scan from inside the issue body (not the comment) + (goto-char (point-min)) + (re-search-forward "Original description") + (let ((cands (plist-get (pearl--issue-dirty-fields) :comment-candidates))) + (should (= 1 (length cands))) + (should (string= "c1" (plist-get (car cands) :comment-id))) + (should (string= "u-craig" (plist-get (car cands) :author-id)))))) + +(ert-deftest test-pearl-dirty-fields-unedited-comment-no-candidate () + "An unedited comment is not a candidate." + (test-pearl-save--in-rendered (test-pearl-save--issue) + (re-search-forward "Original description") + (should-not (plist-get (pearl--issue-dirty-fields) :comment-candidates)))) + +;;; --classify-comment-candidates (Phase B, by viewer id) + +(ert-deftest test-pearl-classify-comment-candidates () + "Candidates split into own (author = viewer) and read-only (everyone else)." + (let* ((cands '((:comment-id "c1" :author-id "u-craig") + (:comment-id "c2" :author-id "u-other") + (:comment-id "c3" :author-id ""))) ; bot/external: not own + (r (pearl--classify-comment-candidates cands "u-craig"))) + (should (equal '("c1") (mapcar (lambda (c) (plist-get c :comment-id)) + (plist-get r :own)))) + (should (equal '("c2" "c3") (mapcar (lambda (c) (plist-get c :comment-id)) + (plist-get r :read-only)))))) + +(provide 'test-pearl-save) +;;; test-pearl-save.el ends here |
