From b081d62276378b3168c92c06153fd59db0589535 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 24 May 2026 13:44:34 -0500 Subject: feat: pearl — manage Linear issues from org-mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pearl fetches Linear issues into an org file and syncs edits back. It covers list / custom views / saved queries, per-issue and bulk rendering with comments inline, conflict-aware sync of descriptions, titles, and comments, field commands for priority / state / assignee / labels, and a transient dispatch menu. The render folds to a scannable outline and nests issues under a sortable parent. Based on and inspired by Gael Blanchemain's linear-emacs. --- tests/test-pearl-menu.el | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/test-pearl-menu.el (limited to 'tests/test-pearl-menu.el') diff --git a/tests/test-pearl-menu.el b/tests/test-pearl-menu.el new file mode 100644 index 0000000..1362b2e --- /dev/null +++ b/tests/test-pearl-menu.el @@ -0,0 +1,73 @@ +;;; 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 plist +\(a list whose car is a keyword) it reads :key and :command from it." + (cond + ((vectorp node) + (apply #'append (mapcar #'test-pearl-menu--suffixes + (append node nil)))) + ((and (consp node) (keywordp (car node))) + (let ((cmd (plist-get node :command)) + (key (plist-get node :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-run-view + pearl-run-saved-query + pearl-sync-current-issue + pearl-set-state + pearl-add-comment + pearl-new-issue + pearl-delete-current-issue)) + (should (memq expected cmds))))) + +(provide 'test-pearl-menu) +;;; test-pearl-menu.el ends here -- cgit v1.2.3