diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-03 11:53:12 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-03 11:53:50 -0500 |
| commit | cb73a13c85c9818979f21e5ca0fd6aee631fb61e (patch) | |
| tree | b69074438512112571d9e677785163f59aadfed7 /tests | |
| parent | 1f729465d2247fb6882185e426860de869fe9b71 (diff) | |
| download | pearl-cb73a13c85c9818979f21e5ca0fd6aee631fb61e.tar.gz pearl-cb73a13c85c9818979f21e5ca0fd6aee631fb61e.zip | |
feat(views): surface the issue assignee as an @-tag on the heading
On a team or "by person" view you couldn't tell who owned an issue at a glance: the assignee was captured but folded inside the :LINEAR-ASSIGNEE-NAME: drawer, with nothing in the heading.
I render the assignee as a leading @-tag (:@eric:bug:backend:), behind a pearl-show-assignee defcustom (default on). I picked a tag over a heading suffix because Org strips tags before it hashes the title for sync, so the tag can't corrupt title-sync the way a suffix would. The @ prefix keeps it in its own namespace, separate from label tags: labels are tracked in their drawer, not read back from the heading, so the tag never gets mistaken for a label. The label-edit path preserves any @-tag when it rewrites the heading's tags, and edit-assignee refreshes the @-tag so a reassign doesn't leave a stale name until the next fetch.
Verified live against a by-person view: assigned issues carry their @-tag, unassigned ones stay bare.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-pearl-assignee-labels.el | 13 | ||||
| -rw-r--r-- | tests/test-pearl-format.el | 4 | ||||
| -rw-r--r-- | tests/test-pearl-labels.el | 69 |
3 files changed, 80 insertions, 6 deletions
diff --git a/tests/test-pearl-assignee-labels.el b/tests/test-pearl-assignee-labels.el index e7be619..09d0fbd 100644 --- a/tests/test-pearl-assignee-labels.el +++ b/tests/test-pearl-assignee-labels.el @@ -55,6 +55,19 @@ (should (string= "Craig" (org-entry-get nil "LINEAR-ASSIGNEE-NAME"))) (should (string= "u9" (org-entry-get nil "LINEAR-ASSIGNEE-ID"))))))) +(ert-deftest test-pearl-edit-assignee-updates-heading-tag-preserving-labels () + "edit-assignee refreshes the @-assignee tag on the heading (so it isn't stale +until the next fetch) while leaving the label tags in place." + (test-pearl--in-org + "*** TODO Title :@someone:bug:\n:PROPERTIES:\n:LINEAR-ID: a\n:LINEAR-TEAM-ID: team-1\n:LINEAR-ASSIGNEE-ID: old\n:LINEAR-ASSIGNEE-NAME: Someone\n:END:\n" + (cl-letf (((symbol-function 'pearl--resolve-team-id) + (lambda (_kind _name _team &optional _force) "u9"))) + (pearl-edit-assignee "Eric") + (goto-char (point-min)) + (should (re-search-forward "^\\*\\*\\* TODO Title[ \t]+:@eric:bug:$" nil t)) + (should-not (save-excursion (goto-char (point-min)) + (re-search-forward ":@someone:" nil t)))))) + (ert-deftest test-pearl-edit-assignee-unresolvable-errors () "An unresolvable assignee name signals a user error and writes nothing." (test-pearl--in-org diff --git a/tests/test-pearl-format.el b/tests/test-pearl-format.el index ee15a2a..da9f975 100644 --- a/tests/test-pearl-format.el +++ b/tests/test-pearl-format.el @@ -51,7 +51,7 @@ so these tests no longer need a state mapping bound." "A full issue renders the heading and the namespaced LINEAR-* drawer." (test-pearl--with-default-mapping (let ((out (pearl--format-issue-as-org-entry (test-pearl--norm-full)))) - (should (string-match-p "^\\*\\* IN-PROGRESS \\[#B\\] ENG-42: Fix the Thing :bug:backend:$" out)) + (should (string-match-p "^\\*\\* IN-PROGRESS \\[#B\\] ENG-42: Fix the Thing :@craig:bug:backend:$" out)) (should (string-match-p "^:LINEAR-ID: +uuid-1$" out)) (should (string-match-p "^:LINEAR-IDENTIFIER: +ENG-42$" out)) (should (string-match-p "^:LINEAR-STATE-NAME: +In Progress$" out)) @@ -173,7 +173,7 @@ blanket :PROPERTIES: check would catch that instead of an actual id leak." (test-pearl--with-default-mapping (let ((out (pearl--build-org-content (list (test-pearl--norm-full) (test-pearl--norm-bare))))) - (should (string-match-p "^\\*\\* IN-PROGRESS \\[#B\\] ENG-42: Fix the Thing :bug:backend:$" out)) + (should (string-match-p "^\\*\\* IN-PROGRESS \\[#B\\] ENG-42: Fix the Thing :@craig:bug:backend:$" out)) (should (string-match-p "^\\*\\* TODO ENG-7: Bare Issue$" out))))) ;;; --restore-page-visibility diff --git a/tests/test-pearl-labels.el b/tests/test-pearl-labels.el index 0195b27..3872f31 100644 --- a/tests/test-pearl-labels.el +++ b/tests/test-pearl-labels.el @@ -75,13 +75,34 @@ "No labels yields no tags." (should (null (pearl--label-tags nil)))) +;;; pearl--assignee-tag + +(ert-deftest test-pearl-assignee-tag-prefixes-and-slugs () + "An assignee renders as an @-prefixed, slugified Org tag." + (should (string= "@eric" (pearl--assignee-tag '(:name "Eric")))) + (should (string= "@eric_smith" (pearl--assignee-tag '(:name "Eric Smith"))))) + +(ert-deftest test-pearl-assignee-tag-nil-when-unassigned () + "No assignee yields no tag." + (should-not (pearl--assignee-tag nil))) + +(ert-deftest test-pearl-assignee-tag-nil-when-disabled () + "With `pearl-show-assignee' off, no tag is produced even for a real assignee." + (let ((pearl-show-assignee nil)) + (should-not (pearl--assignee-tag '(:name "Eric"))))) + +(ert-deftest test-pearl-assignee-tag-empty-slug-is-nil () + "A name that slugifies to empty (all punctuation) yields no tag." + (should-not (pearl--assignee-tag '(:name "///")))) + ;;; rendering on the issue heading -(defun test-pearl-labels--entry (labels) - "Render a minimal issue with LABELS via `pearl--format-issue-as-org-entry'." +(defun test-pearl-labels--entry (labels &optional assignee) + "Render a minimal issue with LABELS (and optional ASSIGNEE) via +`pearl--format-issue-as-org-entry'." (pearl--format-issue-as-org-entry (list :id "u" :identifier "ENG-1" :title "Fix the thing" - :priority 2 :state '(:name "Todo") :labels labels))) + :priority 2 :state '(:name "Todo") :labels labels :assignee assignee))) (ert-deftest test-pearl-format-issue-renders-label-tags () "Labels render as Org tags on the issue heading; the drawer keeps the names." @@ -115,6 +136,32 @@ (re-search-forward "^\\*\\* ") (should (string= "Fix the thing" (pearl--issue-title-at-point))))) +(ert-deftest test-pearl-format-issue-renders-assignee-tag-before-labels () + "The assignee renders as the leading @-tag, ahead of the label tags." + (let ((out (test-pearl-labels--entry '((:name "Bug")) '(:name "Eric")))) + (should (string-match-p "^\\*\\* TODO \\[#B\\] ENG-1: Fix the thing :@eric:bug:$" out)))) + +(ert-deftest test-pearl-format-issue-assignee-tag-only () + "An assigned issue with no labels gets just the @-tag." + (let ((out (test-pearl-labels--entry nil '(:name "Eric")))) + (should (string-match-p "^\\*\\* TODO \\[#B\\] ENG-1: Fix the thing :@eric:$" out)))) + +(ert-deftest test-pearl-format-issue-assignee-tag-disabled () + "With `pearl-show-assignee' off, the heading carries no @-tag." + (let* ((pearl-show-assignee nil) + (out (test-pearl-labels--entry '((:name "Bug")) '(:name "Eric")))) + (should (string-match-p "^\\*\\* TODO \\[#B\\] ENG-1: Fix the thing :bug:$" out)) + (should-not (string-match-p "@eric" out)))) + +(ert-deftest test-pearl-format-issue-assignee-tag-does-not-corrupt-title-sync () + "The assignee tag stays outside the hashed title span, like label tags." + (with-temp-buffer + (insert (test-pearl-labels--entry '((:name "Bug")) '(:name "Eric"))) + (org-mode) + (goto-char (point-min)) + (re-search-forward "^\\*\\* ") + (should (string= "Fix the thing" (pearl--issue-title-at-point))))) + ;;; pearl--set-heading-label-tags (ert-deftest test-pearl-set-heading-label-tags-replaces-preserving-the-rest () @@ -130,8 +177,22 @@ ;; drawer and body untouched (should (string= "a" (org-entry-get nil "LINEAR-ID"))))) +(ert-deftest test-pearl-set-heading-label-tags-preserves-assignee-tag () + "Rewriting the label tags keeps the @-assignee tag (pearl owns label tags, but +the assignee tag is a separate namespace) and drops other stale tags." + (with-temp-buffer + (insert "** TODO [#B] ENG-1: Fix the thing :@eric:old:\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nbody\n") + (org-mode) + (goto-char (point-min)) + (re-search-forward "^\\*\\* ") + (pearl--set-heading-label-tags '("bug" "backend")) + (goto-char (point-min)) + (should (re-search-forward "^\\*\\* TODO \\[#B\\] ENG-1: Fix the thing[ \t]+:@eric:bug:backend:$" nil t)) + (should-not (save-excursion (goto-char (point-min)) (re-search-forward ":old:" nil t))))) + (ert-deftest test-pearl-set-heading-label-tags-empty-clears () - "An empty tag set clears the heading's tags." + "An empty tag set clears the heading's label tags (the @-assignee tag, if any, +is kept -- it isn't a label)." (with-temp-buffer (insert "** TODO [#B] ENG-1: Fix :bug:\n") (org-mode) |
