;;; test-pearl-menu.el --- Tests for the transient menu -*- lexical-binding: t; -*- ;;; Commentary: ;; Tests for `pearl-menu', the transient dispatcher. The menu is ;; interactive UI, so these test the integration -- the prefix is a real ;; command, every suffix dispatches to a bound command, and the key bindings ;; don't collide -- rather than transient's own rendering behavior. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el" (file-name-directory (or load-file-name buffer-file-name)))) (require 'transient) (defun test-pearl-menu--suffixes (node) "Collect (KEY . COMMAND) pairs from a transient layout NODE. Walks vectors and lists recursively; whenever it reaches a suffix plist it reads :key and :command from it. Transient has stored a suffix two ways: inside a (LEVEL CLASS (:key ... :command ...)) list, where the bare plist is reached by recursion, and, since transient 0.13 (the version Emacs 31 bundles), as a list headed by the class symbol (transient-suffix :key ...). Both shapes are read here so the test survives the library's own layout changes." (cond ((vectorp node) (apply #'append (mapcar #'test-pearl-menu--suffixes (append node nil)))) ((and (consp node) (or (keywordp (car node)) (and (symbolp (car node)) (keywordp (cadr node))))) (let* ((plist (if (keywordp (car node)) node (cdr node))) (cmd (plist-get plist :command)) (key (plist-get plist :key))) (when cmd (list (cons key cmd))))) ((consp node) (apply #'append (mapcar #'test-pearl-menu--suffixes node))) (t nil))) (defun test-pearl-menu--pairs () "Return the (KEY . COMMAND) pairs declared in `pearl-menu'." (test-pearl-menu--suffixes (get 'pearl-menu 'transient--layout))) (ert-deftest test-pearl-menu-is-command () "The dispatcher is defined and is an interactive command." (should (fboundp 'pearl-menu)) (should (commandp 'pearl-menu))) (ert-deftest test-pearl-menu-suffixes-dispatch-to-real-commands () "Every suffix in the menu names a bound, interactive command. This is the regression guard: rename or remove a command and the menu entry that still points at it fails here." (let ((pairs (test-pearl-menu--pairs))) (should pairs) (dolist (pair pairs) (let ((cmd (cdr pair))) (should (fboundp cmd)) (should (commandp cmd)))))) (ert-deftest test-pearl-menu-keys-are-unique () "No two suffixes share a key binding." (let* ((pairs (test-pearl-menu--pairs)) (keys (delq nil (mapcar #'car pairs)))) (should (= (length keys) (length (delete-dups (copy-sequence keys))))))) (ert-deftest test-pearl-menu-covers-core-commands () "A representative slice of the command surface is reachable from the menu." (let ((cmds (mapcar #'cdr (test-pearl-menu--pairs)))) (dolist (expected '(pearl-list-issues pearl-open-default-view pearl-run-linear-view pearl-run-local-view pearl-publish-local-view pearl-publish-current-view pearl-save-issue pearl-save-all pearl-edit-state pearl-create-comment pearl-create-issue pearl-delete-current-issue ;; previously missing from the menu -- added in the ;; keybinding reconciliation so both surfaces agree pearl-switch-account pearl-copy-issue-url ;; set-default-view: the new Views-group entry pearl-set-default-view ;; fetch the full comment thread for the issue at point pearl-fetch-all-comments ;; single-issue source: open one issue by identifier pearl-open-issue-by-id ;; view-shaping commands added to the Views group pearl-set-sort pearl-set-grouping pearl-toggle-sort-order)) (should (memq expected cmds))))) ;; Synthetic layouts pin both suffix shapes the walker must read, so the ;; branch for whichever transient version isn't installed can't rot unseen. (defconst test-pearl-menu--old-layout '([1 transient-columns nil ([1 transient-column (:description "Save") ((1 transient-suffix (:key "s" :command pearl-save-issue)) (1 transient-suffix (:key "S" :command pearl-save-all)))])]) "A menu layout in the pre-0.13 transient shape: (LEVEL CLASS PLIST) suffixes.") (defconst test-pearl-menu--new-layout '([transient-columns nil ([transient-column (:description "Save") ((transient-suffix :key "s" :command pearl-save-issue) (transient-suffix :key "S" :command pearl-save-all))])]) "The same menu in the transient 0.13 shape: (CLASS :key ...) suffixes.") (ert-deftest test-pearl-menu-suffixes-reads-old-layout-shape () "The walker finds every suffix in the pre-0.13 (LEVEL CLASS PLIST) layout." (should (equal (test-pearl-menu--suffixes test-pearl-menu--old-layout) '(("s" . pearl-save-issue) ("S" . pearl-save-all))))) (ert-deftest test-pearl-menu-suffixes-reads-new-layout-shape () "The walker finds every suffix in the transient 0.13 (CLASS :key ...) layout." (should (equal (test-pearl-menu--suffixes test-pearl-menu--new-layout) '(("s" . pearl-save-issue) ("S" . pearl-save-all))))) (ert-deftest test-pearl-menu-suffixes-empty-layout-is-nil () "An empty layout and a group with no suffixes both yield no pairs." (should (null (test-pearl-menu--suffixes nil))) (should (null (test-pearl-menu--suffixes '([transient-column (:description "x") ()]))))) (provide 'test-pearl-menu) ;;; test-pearl-menu.el ends here