aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-output.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-pearl-output.el')
-rw-r--r--tests/test-pearl-output.el145
1 files changed, 145 insertions, 0 deletions
diff --git a/tests/test-pearl-output.el b/tests/test-pearl-output.el
new file mode 100644
index 0000000..79b7a82
--- /dev/null
+++ b/tests/test-pearl-output.el
@@ -0,0 +1,145 @@
+;;; test-pearl-output.el --- Tests for the active-file output 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 active-file output model: the filter summary, the
+;; source-tracking header (with the affordance preamble) written by
+;; `--build-org-content', reading the active source back from a buffer, and
+;; `pearl-refresh-current-view' re-running the recorded source.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+(require 'cl-lib)
+
+;;; --summarize-filter
+
+(ert-deftest test-pearl-summarize-filter-fields ()
+ "A filter plist summarizes its set dimensions in a readable string."
+ (let ((s (pearl--summarize-filter '(:assignee :me :open t :state "In Progress"))))
+ (should (string-match-p "assignee" s))
+ (should (string-match-p "open" s))
+ (should (string-match-p "In Progress" s))))
+
+(ert-deftest test-pearl-summarize-filter-empty ()
+ "An empty filter summarizes as all issues."
+ (should (string-match-p "all" (pearl--summarize-filter nil))))
+
+;;; --build-org-content with a source
+
+(ert-deftest test-pearl-build-org-content-source-header ()
+ "With a source, the header carries the title, serialized source, and count."
+ (let* ((source '(:type filter :name "My open issues" :filter (:assignee :me :open t)))
+ (out (pearl--build-org-content '() source)))
+ (should (string-match-p "^#\\+title: Linear — My open issues$" out))
+ (should (string-match-p "^#\\+LINEAR-SOURCE: " out))
+ (should (string-match-p "^#\\+LINEAR-COUNT: 0$" out))
+ ;; affordance preamble is present as org comments, not content
+ (should (string-match-p "^# .*pearl-sync-current-issue" out))))
+
+(ert-deftest test-pearl-build-org-content-source-roundtrips ()
+ "The serialized source in the header reads back to the original plist."
+ (let* ((source '(:type filter :name "Bugs" :filter (:labels ("bug") :open t)))
+ (out (pearl--build-org-content '() source)))
+ (with-temp-buffer
+ (insert out)
+ (should (equal source (pearl--read-active-source))))))
+
+(ert-deftest test-pearl-build-org-content-default-source-back-compat ()
+ "Called with no source, the content still has a title and no entries."
+ (let ((out (pearl--build-org-content '())))
+ (should (string-match-p "^#\\+title:" out))
+ (should-not (string-match-p "^\\*\\*\\* " out))))
+
+;;; --read-active-source
+
+(ert-deftest test-pearl-read-active-source-absent ()
+ "A buffer with no source header reads back nil."
+ (with-temp-buffer
+ (insert "#+title: something\n\n* a heading\n")
+ (should-not (pearl--read-active-source))))
+
+;;; refresh-current-view
+
+(ert-deftest test-pearl-refresh-current-view-reruns-source ()
+ "Refresh reads the recorded filter source and merges the re-run result."
+ (let ((ran nil) (merged-source nil)
+ (source '(:type filter :name "My open issues" :filter (:assignee :me :open t))))
+ (with-temp-buffer
+ (insert (format "#+title: Linear — My open issues\n#+LINEAR-SOURCE: %s\n\n"
+ (prin1-to-string source)))
+ (org-mode)
+ (cl-letf (((symbol-function 'pearl--query-issues-async)
+ (lambda (_filter cb)
+ (setq ran t)
+ (funcall cb (pearl--make-query-result 'ok :issues nil))))
+ ((symbol-function 'pearl--merge-query-result)
+ (lambda (_result src) (setq merged-source src))))
+ (pearl-refresh-current-view)
+ (should ran)
+ (should (equal source merged-source))))))
+
+(ert-deftest test-pearl-refresh-current-view-no-source-errors ()
+ "Refresh with no recorded source signals a user error."
+ (with-temp-buffer
+ (insert "#+title: plain\n")
+ (org-mode)
+ (should-error (pearl-refresh-current-view) :type 'user-error)))
+
+;;; --update-org-from-issues surfaces the result
+
+(ert-deftest test-pearl-update-org-surfaces-fresh-buffer ()
+ "With no buffer visiting the file, the write creates one and surfaces it."
+ (let* ((tmp (make-temp-file "pearl-out" nil ".org"))
+ (pearl-org-file-path tmp)
+ (surfaced nil))
+ (unwind-protect
+ (progn
+ (when (find-buffer-visiting tmp) (kill-buffer (find-buffer-visiting tmp)))
+ (cl-letf (((symbol-function 'pearl--surface-buffer)
+ (lambda (b) (setq surfaced b))))
+ (pearl--update-org-from-issues '() '(:type filter :name "X" :filter nil) nil))
+ (should (bufferp surfaced))
+ (should (buffer-live-p surfaced))
+ (should (string= (file-truename tmp)
+ (file-truename (buffer-file-name surfaced)))))
+ (when (find-buffer-visiting tmp) (kill-buffer (find-buffer-visiting tmp)))
+ (ignore-errors (delete-file tmp)))))
+
+(ert-deftest test-pearl-update-org-surfaces-existing-buffer ()
+ "With a clean buffer visiting the file, the update surfaces that buffer."
+ (let* ((tmp (make-temp-file "pearl-out" nil ".org"))
+ (pearl-org-file-path tmp)
+ (surfaced nil)
+ (buf (find-file-noselect tmp)))
+ (unwind-protect
+ (progn
+ (with-current-buffer buf (set-buffer-modified-p nil))
+ (cl-letf (((symbol-function 'pearl--surface-buffer)
+ (lambda (b) (setq surfaced b))))
+ (pearl--update-org-from-issues '() '(:type filter :name "X" :filter nil) nil))
+ (should (eq surfaced buf)))
+ (when (buffer-live-p buf)
+ (with-current-buffer buf (set-buffer-modified-p nil))
+ (kill-buffer buf))
+ (ignore-errors (delete-file tmp)))))
+
+(provide 'test-pearl-output)
+;;; test-pearl-output.el ends here