diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-pearl-modified.el | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/tests/test-pearl-modified.el b/tests/test-pearl-modified.el new file mode 100644 index 0000000..cfef923 --- /dev/null +++ b/tests/test-pearl-modified.el @@ -0,0 +1,175 @@ +;;; test-pearl-modified.el --- Tests for the modified-ticket indicator -*- lexical-binding: t; -*- + +;; Copyright (C) 2026 Craig Jennings + +;; Author: Craig Jennings <c@cjennings.net> + +;;; Commentary: + +;; Tests for the modified-ticket indicator (docs/modified-ticket-indicator-spec.org): +;; phase 1 covers the pushable-ticket count and its mode-line segment. The count +;; is "tickets, not fields": each ticket with pushable dirty work counts once. + +;;; Code: + +(require 'test-bootstrap (expand-file-name "test-bootstrap.el")) +(require 'cl-lib) + +(defun test-pearl-modified--scan (&rest dirtys) + "Build a scan list ((MARKER . DIRTY) ...) from DIRTY plists (marker unused)." + (mapcar (lambda (d) (cons nil d)) dirtys)) + +;;; pushable-ticket count + +(ert-deftest test-pearl-count-pushable-field-dirty-ticket-counts () + "A ticket with any dirty non-comment field counts, regardless of the viewer." + (let ((scan (test-pearl-modified--scan '(:title t) '(:description t) '(:state t)))) + (should (= 3 (pearl--count-pushable-tickets scan "v"))) + (should (= 3 (pearl--count-pushable-tickets scan nil))))) + +(ert-deftest test-pearl-count-pushable-many-fields-one-ticket-counts-once () + "A ticket dirty in several fields counts once, not per field." + (let ((scan (test-pearl-modified--scan '(:title t :description t :labels t)))) + (should (= 1 (pearl--count-pushable-tickets scan "v"))))) + +(ert-deftest test-pearl-count-pushable-own-comment-only-counts () + "A ticket dirty only via an own comment (author = viewer) counts." + (let ((scan (test-pearl-modified--scan + '(:comment-candidates ((:author-id "v")))))) + (should (= 1 (pearl--count-pushable-tickets scan "v"))))) + +(ert-deftest test-pearl-count-pushable-readonly-comment-only-excluded () + "A ticket dirty only via a non-own comment does not count." + (let ((scan (test-pearl-modified--scan + '(:comment-candidates ((:author-id "someone-else")))))) + (should (= 0 (pearl--count-pushable-tickets scan "v"))))) + +(ert-deftest test-pearl-count-pushable-comment-only-unknown-viewer-excluded () + "Comment-only dirt doesn't count while the viewer is unresolved or failed." + (let ((scan (test-pearl-modified--scan + '(:comment-candidates ((:author-id "v")))))) + (should (= 0 (pearl--count-pushable-tickets scan nil))) + (should (= 0 (pearl--count-pushable-tickets scan "v" t))))) ; viewer-failed + +(ert-deftest test-pearl-count-pushable-mixed () + "A field-dirty ticket counts even when another ticket is only read-only-dirty." + (let ((scan (test-pearl-modified--scan + '(:priority t) + '(:comment-candidates ((:author-id "other")))))) + (should (= 1 (pearl--count-pushable-tickets scan "v"))))) + +;;; mode-line segment + +(ert-deftest test-pearl-mode-line-appends-changed-count-with-glyph () + "The lighter appends \"N <glyph> changed\" and keeps the account name." + (let ((pearl-accounts '(("work" :api-key "k"))) + (pearl-active-account "work") + (pearl-show-modified-indicator t) + (pearl-ticket-glyph "🎫") + (pearl--modified-count 3)) + (let ((s (pearl--mode-line-lighter))) + (should (string-match-p "Pearl\\[work\\]" s)) + (should (string-match-p "3" s)) + (should (string-match-p "🎫" s)) + (should (string-match-p "changed" s))))) + +(ert-deftest test-pearl-mode-line-changed-count-falls-back-to-word () + "With the glyph unset, the segment reads \"N tickets changed\"." + (let ((pearl-accounts '(("work" :api-key "k"))) + (pearl-active-account "work") + (pearl-show-modified-indicator t) + (pearl-ticket-glyph "") + (pearl--modified-count 2)) + (should (string-match-p "2 tickets changed" (pearl--mode-line-lighter))))) + +(ert-deftest test-pearl-mode-line-no-segment-when-clean () + "A zero count adds no segment; the account name still shows." + (let ((pearl-accounts '(("work" :api-key "k"))) + (pearl-active-account "work") + (pearl-show-modified-indicator t) + (pearl--modified-count 0)) + (let ((s (pearl--mode-line-lighter))) + (should (string-match-p "Pearl\\[work\\]" s)) + (should-not (string-match-p "changed" s))))) + +(ert-deftest test-pearl-mode-line-indicator-off-suppresses-count () + "With the indicator disabled, no count shows even when dirty." + (let ((pearl-accounts nil) + (pearl-active-account nil) + (pearl-show-modified-indicator nil) + (pearl--modified-count 5)) + (should-not (string-match-p "changed" (pearl--mode-line-lighter))))) + +;;; phase 2 -- field-region highlight + +(ert-deftest test-pearl-modified-regions-clean-is-nil () + "A clean ticket highlights nothing." + (should (null (pearl--modified-highlight-regions '() "v")))) + +(ert-deftest test-pearl-modified-regions-title-lights-heading () + (should (equal '(heading) (pearl--modified-highlight-regions '(:title t) "v")))) + +(ert-deftest test-pearl-modified-regions-description-lights-both () + "A dirty description lights the heading (bubble-up) and the body." + (should (equal '(heading description) + (pearl--modified-highlight-regions '(:description t) "v")))) + +(ert-deftest test-pearl-modified-regions-own-comment-lights-heading () + (should (equal '(heading) + (pearl--modified-highlight-regions + '(:comment-candidates ((:author-id "v"))) "v")))) + +(ert-deftest test-pearl-modified-regions-readonly-comment-nothing () + "A non-own comment does not light the heading." + (should (null (pearl--modified-highlight-regions + '(:comment-candidates ((:author-id "other"))) "v")))) + +(ert-deftest test-pearl-modified-regions-comment-unknown-viewer-nothing () + "A comment-only ticket lights nothing while the viewer is unresolved." + (should (null (pearl--modified-highlight-regions + '(:comment-candidates ((:author-id "v"))) nil)))) + +(ert-deftest test-pearl-issue-description-region-spans-body-only () + "The description region covers the body, not the drawer or the Comments child." + (with-temp-buffer + (insert "* F\n** TODO PEARL-1 Alpha\n:PROPERTIES:\n:LINEAR-ID: i1\n:END:\n" + "Body one.\nBody two.\n*** Comments\n**** c\nhi\n") + (org-mode) + (goto-char (point-min)) (re-search-forward "PEARL-1") (org-back-to-heading t) + (let* ((r (pearl--issue-description-region)) + (text (buffer-substring-no-properties (car r) (cdr r)))) + (should (string-match-p "Body one" text)) + (should (string-match-p "Body two" text)) + (should-not (string-match-p "Comments" text)) + (should-not (string-match-p "LINEAR-ID" text))))) + +(ert-deftest test-pearl-apply-modified-highlights-marks-dirty-heading-idempotently () + "A title-dirty issue gets exactly one heading overlay, even on a second pass." + (with-temp-buffer + (insert "* F\n** TODO PEARL-1 Alpha\n:PROPERTIES:\n:LINEAR-ID: i1\n" + ":LINEAR-TITLE-SHA256: deadbeef\n:END:\n") + (org-mode) + (let ((pearl-show-modified-indicator t)) + (cl-flet ((modified-ovs () + (seq-filter (lambda (o) (overlay-get o 'pearl-modified)) + (overlays-in (point-min) (point-max))))) + (pearl--apply-modified-highlights) + (should (= 1 (length (modified-ovs)))) + (pearl--apply-modified-highlights) + (should (= 1 (length (modified-ovs)))))))) + +(ert-deftest test-pearl-apply-modified-highlights-off-clears () + "With the indicator disabled, the applier lays nothing (and clears prior)." + (with-temp-buffer + (insert "* F\n** TODO PEARL-1 Alpha\n:PROPERTIES:\n:LINEAR-ID: i1\n" + ":LINEAR-TITLE-SHA256: deadbeef\n:END:\n") + (org-mode) + (let ((pearl-show-modified-indicator t)) + (pearl--apply-modified-highlights)) + (let ((pearl-show-modified-indicator nil)) + (pearl--apply-modified-highlights)) + (should (null (seq-filter (lambda (o) (overlay-get o 'pearl-modified)) + (overlays-in (point-min) (point-max))))))) + +(provide 'test-pearl-modified) +;;; test-pearl-modified.el ends here |
