From 424d7048d5450131283f6bdb99822aa6bccd6b16 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 24 May 2026 16:58:49 -0500 Subject: feat: render recent comments in the bulk issue list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A populated list used to look like nothing had comments, because only a single-issue refresh fetched them. The bulk and Custom View queries now pull each issue's most recent comments and render them under the Comments heading, with a marker showing how many are shown against the total: 💬 5/18, or 💬 5/25+ once the count passes the cap. Linear's API has no comment total (no commentCount on Issue, no totalCount on the connection), so I fetch one more than pearl-list-comments-count-cap newest-first: that gives an exact total up to the cap and a "+" beyond it, in a single round trip. pearl-list-comments-shown (default 5) caps how many render. pearl-fetch-comments-in-list (default on) turns the whole thing off to keep the fetch light. The single-issue refresh still shows the full thread, uncapped and unmarked. The marker sits on the Comments heading rather than the issue title, so it stays out of the title-sync hash. I relaxed the append locator to match a marked heading, so adding a comment still finds the existing subtree instead of creating a second one. Verified live: the capped fragment is accepted and the markers render correctly. --- tests/test-pearl-list-comments.el | 142 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/test-pearl-list-comments.el (limited to 'tests/test-pearl-list-comments.el') diff --git a/tests/test-pearl-list-comments.el b/tests/test-pearl-list-comments.el new file mode 100644 index 0000000..630c712 --- /dev/null +++ b/tests/test-pearl-list-comments.el @@ -0,0 +1,142 @@ +;;; test-pearl-list-comments.el --- Tests for comments in the bulk list -*- 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 rendering comments in the bulk issue list: the per-issue cap +;; (`pearl--cap-issue-list-comments'), the `💬 shown/total' marker +;; (`pearl--comment-count-marker', the Comments heading), and the way the +;; append locator tolerates the marked heading so a later add-comment finds the +;; existing subtree instead of creating a second one. + +;;; Code: + +(require 'test-bootstrap (expand-file-name "test-bootstrap.el")) + +(defun test-pearl--comments (n) + "Return N comment plists, newest-first (createdAt descending), as the fetch returns." + (let (out) + (dotimes (i n) + ;; i=0 is the newest; pad the index so string< orders correctly + (push (list :id (format "c%02d" (- n i)) + :author "A" + :created-at (format "2026-05-%02dT00:00:00.000Z" (- n i)) + :body (format "comment %d" (- n i))) + out)) + (nreverse out))) + +;;; --cap-issue-list-comments + +(ert-deftest test-pearl-cap-comments-under-shown () + "Fewer comments than the shown cap keeps them all with an exact total." + (let ((pearl-list-comments-shown 5) (pearl-list-comments-count-cap 25)) + (let ((out (pearl--cap-issue-list-comments + (list :id "u" :comments (test-pearl--comments 3))))) + (should (= 3 (length (plist-get out :comments)))) + (should (equal '(:shown 3 :total 3 :overflow nil) + (plist-get out :comment-count)))))) + +(ert-deftest test-pearl-cap-comments-over-shown-under-cap () + "More than the shown cap but within the count cap: show 5, total exact." + (let ((pearl-list-comments-shown 5) (pearl-list-comments-count-cap 25)) + (let ((out (pearl--cap-issue-list-comments + (list :id "u" :comments (test-pearl--comments 18))))) + (should (= 5 (length (plist-get out :comments)))) + (should (equal '(:shown 5 :total 18 :overflow nil) + (plist-get out :comment-count)))))) + +(ert-deftest test-pearl-cap-comments-over-count-cap-overflows () + "More than the count cap: total pins to the cap and overflows." + (let ((pearl-list-comments-shown 5) (pearl-list-comments-count-cap 25)) + ;; the fetch pulls cap+1 = 26 to detect overflow + (let ((out (pearl--cap-issue-list-comments + (list :id "u" :comments (test-pearl--comments 26))))) + (should (= 5 (length (plist-get out :comments)))) + (should (equal '(:shown 5 :total 25 :overflow t) + (plist-get out :comment-count)))))) + +(ert-deftest test-pearl-cap-comments-none-unchanged () + "An issue with no fetched comments is returned untouched, with no marker." + (let ((issue (list :id "u" :comments nil))) + (let ((out (pearl--cap-issue-list-comments issue))) + (should-not (plist-get out :comments)) + (should-not (plist-get out :comment-count))))) + +(ert-deftest test-pearl-cap-comments-keeps-the-newest () + "The cap keeps the newest `shown' comments (input arrives newest-first)." + (let ((pearl-list-comments-shown 3) (pearl-list-comments-count-cap 25)) + (let* ((out (pearl--cap-issue-list-comments + (list :id "u" :comments (test-pearl--comments 7)))) + (ids (mapcar (lambda (c) (plist-get c :id)) (plist-get out :comments)))) + ;; newest three are c07, c06, c05 (the head of the newest-first list) + (should (equal '("c07" "c06" "c05") ids))))) + +;;; --comment-count-marker + +(ert-deftest test-pearl-comment-count-marker-forms () + "The marker renders shown/total, with a `+' past the cap and nothing for nil." + (should (string= " 💬 5/18" (pearl--comment-count-marker '(:shown 5 :total 18 :overflow nil)))) + (should (string= " 💬 5/25+" (pearl--comment-count-marker '(:shown 5 :total 25 :overflow t)))) + (should (string= " 💬 3/3" (pearl--comment-count-marker '(:shown 3 :total 3 :overflow nil)))) + (should (string= "" (pearl--comment-count-marker nil)))) + +;;; --format-comments with a marker + +(ert-deftest test-pearl-format-comments-renders-marker-and-oldest-first () + "With count-info, the Comments heading carries the marker; bodies stay oldest-first." + (let* ((comments (test-pearl--comments 5)) ; newest-first as fetched + (out (pearl--format-comments comments '(:shown 5 :total 12 :overflow nil)))) + (should (string-match-p "^\\*\\*\\* Comments 💬 5/12$" out)) + ;; oldest (comment 1) renders before newest (comment 5) + (should (< (string-match "comment 1\\b" out) (string-match "comment 5\\b" out))))) + +(ert-deftest test-pearl-format-comments-no-marker-without-count () + "Without count-info (the single-issue thread), the heading has no marker." + (let ((out (pearl--format-comments (test-pearl--comments 2)))) + (should (string-match-p "^\\*\\*\\* Comments$" out)))) + +;;; the marked heading is still found by the append locator + +(ert-deftest test-pearl-append-finds-marked-comments-heading () + "Adding a comment to an issue whose Comments heading carries a marker reuses it." + (let ((pearl-state-to-todo-mapping '(("Todo" . "TODO"))) + (pearl-list-comments-shown 5) (pearl-list-comments-count-cap 25)) + (let ((entry (pearl--format-issue-as-org-entry + (pearl--cap-issue-list-comments + (list :id "a" :identifier "ENG-1" :title "issue" + :priority 3 :state '(:name "Todo") + :comments (test-pearl--comments 8)))))) + (with-temp-buffer + (insert entry) + (org-mode) + (goto-char (point-min)) + ;; the rendered Comments heading carries the marker + (should (re-search-forward "^\\*\\*\\* Comments 💬 5/8$" nil t)) + (goto-char (point-min)) + (re-search-forward "issue") + (pearl--append-comment-to-issue + '(:id "cnew" :author "Z" :created-at "2026-06-01T00:00:00.000Z" :body "appended")) + ;; exactly one Comments heading, and the new comment landed under it + (goto-char (point-min)) + (should (re-search-forward "^\\*\\*\\* Comments" nil t)) + (should-not (re-search-forward "^\\*\\*\\* Comments" nil t)) + (should (string-match-p "appended" (buffer-string))))))) + +(provide 'test-pearl-list-comments) +;;; test-pearl-list-comments.el ends here -- cgit v1.2.3