;;; test-pearl-mention.el --- Tests for @-mentioning Linear users -*- 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 the user @-mention picker: the candidate builder, the insert ;; command (`pearl-mention-user'), the compose-buffer self-inserting `@' ;; trigger, and the keymap binding. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el")) (require 'cl-lib) (defun test-pearl-mention--members () "Member node alists as `pearl--team-collection' returns them." '(((id . "u1") (name . "Eric Bell") (displayName . "eric") (email . "e@x")) ((id . "u2") (name . "vrezh.mikayelyan@deepsat.com") (displayName . "vrezh.mikayelyan") (email . "v@x")) ((id . "u3") (name . "Bot") (displayName . nil) (email . "b@x")))) ;;; --mention-candidates (ert-deftest test-pearl-mention-candidates-maps-to-handle () "Each member maps a display string to its `@displayName' token; the display carries the full name so you can find a teammate without recalling the handle." (let ((cands (pearl--mention-candidates (test-pearl-mention--members)))) (should (equal "@eric" (cdr (assoc "Eric Bell (@eric)" cands)))) (should (equal "@vrezh.mikayelyan" (cdr (assoc "vrezh.mikayelyan@deepsat.com (@vrezh.mikayelyan)" cands)))))) (ert-deftest test-pearl-mention-candidates-drops-handleless () "A member with no displayName has no mention handle and is dropped." (let ((cands (pearl--mention-candidates (test-pearl-mention--members)))) (should-not (cl-find "@" (mapcar #'cdr cands) :test (lambda (_ v) (string-match-p "Bot" (or v ""))))) (should (= 2 (length cands))))) ;;; pearl-mention-user (insert at point) (ert-deftest test-pearl-mention-user-inserts-handle-at-point () "With a team in context, the command inserts the chosen `@displayName'." (cl-letf (((symbol-function 'pearl--team-collection) (lambda (_kind _team &optional _force) (test-pearl-mention--members))) ((symbol-function 'completing-read) (lambda (&rest _) "Eric Bell (@eric)"))) (with-temp-buffer (insert "*** TODO Title\n:PROPERTIES:\n:LINEAR-ID: a\n:LINEAR-TEAM-ID: t1\n:END:\nPing ") (org-mode) (goto-char (point-max)) (pearl-mention-user) (should (string-suffix-p "Ping @eric" (buffer-substring (line-beginning-position) (point))))))) (ert-deftest test-pearl-mention-user-no-team-errors () "Outside any team context (no compose team, no issue heading) it errors." (with-temp-buffer (insert "plain text, no Linear heading") (org-mode) (goto-char (point-max)) (should-error (pearl-mention-user) :type 'user-error))) ;;; keymap (ert-deftest test-pearl-mention-bound-under-prefix () "C-; L @ resolves to the mention command." (should (eq 'pearl-mention-user (lookup-key pearl-prefix-map "@")))) ;;; compose-buffer self-inserting @ (ert-deftest test-pearl-compose-at-self-insert-picks-at-boundary () "At a token boundary the `@' trigger pops the picker and inserts the handle." (cl-letf (((symbol-function 'pearl--read-mention) (lambda (_team) "@eric"))) (with-temp-buffer (setq-local pearl--compose-team-id "t1") (insert "Ping ") ; point preceded by a space = boundary (pearl--compose-at-self-insert) (should (string= "Ping @eric" (buffer-string)))))) (ert-deftest test-pearl-compose-at-self-insert-literal-mid-token () "Mid-token (e.g. inside an email) `@' inserts a literal at-sign, no picker." (cl-letf (((symbol-function 'pearl--read-mention) (lambda (_team) (error "picker should not run mid-token")))) (with-temp-buffer (setq-local pearl--compose-team-id "t1") (insert "foo") ; point after a word char = not a boundary (pearl--compose-at-self-insert) (should (string= "foo@" (buffer-string)))))) (ert-deftest test-pearl-compose-at-self-insert-cancel-leaves-literal () "Cancelling the picker (quit) leaves a literal `@' rather than aborting input." (cl-letf (((symbol-function 'pearl--read-mention) (lambda (_team) (signal 'quit nil)))) (with-temp-buffer (setq-local pearl--compose-team-id "t1") (insert "Ping ") (pearl--compose-at-self-insert) (should (string= "Ping @" (buffer-string)))))) (provide 'test-pearl-mention) ;;; test-pearl-mention.el ends here