diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-27 19:19:35 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-27 19:19:35 -0500 |
| commit | a74334bc9a321ddbb9b2ba81266f264d3522cc8c (patch) | |
| tree | 72214b77d2b27b205f9450763fdfd00ee4166524 | |
| parent | 73f7656d02f916c361fc0da06d97d9bdaf9062a0 (diff) | |
| download | pearl-a74334bc9a321ddbb9b2ba81266f264d3522cc8c.tar.gz pearl-a74334bc9a321ddbb9b2ba81266f264d3522cc8c.zip | |
refactor: verb-align command names and prefixes, use issue not ticket
The keybinding prefixes now each name an action verb: f fetch, e edit, c create, k delete, o open, y copy. The url group is gone, its actions split into open and copy, and the direct lowercase c (new comment) is dropped so c can head the create group. Two commands carried non-verb names, so I renamed them: pearl-new-issue is now pearl-create-issue and pearl-add-comment is now pearl-create-comment. The old sync helper that already held the pearl-create-issue name moved to the private pearl--create-issue, where it belongs as internal plumbing.
I also swept "ticket" to "issue" across the labels, transient, docstrings, and README. Linear's own term is "issue" and the buffer mirrors Linear, so the command names already said "issue" while the labels said "ticket". Now they agree. The transient's per-entry keys still trail the keymap verbs, which stays its own task.
| -rw-r--r-- | README.org | 34 | ||||
| -rw-r--r-- | pearl.el | 65 | ||||
| -rw-r--r-- | tests/test-integration-acceptance.el | 4 | ||||
| -rw-r--r-- | tests/test-pearl-commands.el | 10 | ||||
| -rw-r--r-- | tests/test-pearl-comment-editing.el | 4 | ||||
| -rw-r--r-- | tests/test-pearl-comments.el | 16 | ||||
| -rw-r--r-- | tests/test-pearl-compose.el | 10 | ||||
| -rw-r--r-- | tests/test-pearl-keymap.el | 50 | ||||
| -rw-r--r-- | tests/test-pearl-menu.el | 4 | ||||
| -rw-r--r-- | tests/test-pearl-save.el | 2 | ||||
| -rw-r--r-- | tests/test-pearl-smoke.el | 2 | ||||
| -rw-r--r-- | tests/test-pearl-sync-wrappers.el | 4 |
12 files changed, 105 insertions, 100 deletions
@@ -7,7 +7,7 @@ " /We like lists because we don't want to die./ " — /Umberto Eco/ -Pearl (backronym: *Pearl Edits and Reflects Linear*) brings Linear issues into Emacs as a working Org file. Fetch your open issues, a project, a Linear Custom View, an ad-hoc filter, or a saved local query; Pearl renders each issue as an Org heading with the description and comments in the body and Linear metadata in a namespaced property drawer. Edit what you need in the buffer, then save the ticket; Pearl reconciles each changed field against the remote with a conflict check. +Pearl (backronym: *Pearl Edits and Reflects Linear*) brings Linear issues into Emacs as a working Org file. Fetch your open issues, a project, a Linear Custom View, an ad-hoc filter, or a saved local query; Pearl renders each issue as an Org heading with the description and comments in the body and Linear metadata in a namespaced property drawer. Edit what you need in the buffer, then save the issue; Pearl reconciles each changed field against the remote with a conflict check. ** Features :PROPERTIES: @@ -48,7 +48,7 @@ Pearl is not on MELPA yet. #+begin_src emacs-lisp (use-package pearl :vc (:url "https://git.cjennings.net/pearl.git" :rev :newest) - :commands (pearl-menu pearl-list-issues pearl-run-view pearl-new-issue) + :commands (pearl-menu pearl-list-issues pearl-run-view pearl-create-issue) :bind ("C-c L" . pearl-menu) :custom (pearl-org-file-path (expand-file-name "gtd/linear.org" org-directory)) @@ -76,7 +76,7 @@ In =config.el=: #+begin_src emacs-lisp (use-package! pearl - :commands (pearl-menu pearl-list-issues pearl-new-issue + :commands (pearl-menu pearl-list-issues pearl-create-issue pearl-run-view pearl-save-issue) :init (setq pearl-org-file-path (expand-file-name "gtd/linear.org" org-directory)) @@ -144,7 +144,7 @@ Pearl writes one active Org file. Running a different query or view replaces tha *** Command menu -=M-x pearl-menu= opens a transient dispatcher. Ticket actions are grouped as save, edit, new, and delete; workspace actions as fetch, view, and setup. Bind that command if you use Pearl regularly: +=M-x pearl-menu= opens a transient dispatcher. Issue actions are grouped as save, edit, create, and delete; workspace actions as fetch, view, and setup. Bind that command if you use Pearl regularly: #+begin_src emacs-lisp (global-set-key (kbd "C-c L") #'pearl-menu) @@ -158,22 +158,22 @@ Pearl is fully keyboard-drivable. =pearl-mode=, a minor mode, turns on automatic (setq pearl-keymap-prefix "C-c l") ; or nil to bind nothing #+end_src -The hot-path commands sit one key under the prefix; the rest are grouped into category sub-maps. The common ones appear in both places, so =d= and =e d= both edit the description. +The hot-path commands sit one key under the prefix; the rest are grouped into sub-maps, one per action verb. The common ones appear in both places, so =d= and =e d= both edit the description. | Key | Command | |-----------+----------------------------------------------------------------------| | =l= | list my open issues | | =g= | refresh the view | | =r= | refresh the issue at point | -| =s= / =S= | save the ticket at point / save every ticket in the file | +| =s= / =S= | save the issue at point / save every issue in the file | | =d= | edit the description | -| =c= | add a comment | | =m= | open the full transient menu | | =f= ... | fetch: =o= open issues, =p= by project, =f= filter, =v= view, =q= saved query | | =e= ... | edit: =d= description, =s= state, =a= assignee, =l= labels, =c= comment | -| =n= ... | new: =t= ticket, =c= comment | -| =k= ... | delete: =t= ticket, =c= comment | -| =u= ... | url: =o= open issue in browser, =v= open view in Linear | +| =c= ... | create: =t= issue, =c= comment | +| =k= ... | delete: =t= issue, =c= comment | +| =o= ... | open: =i= issue in browser, =v= view in Linear | +| =y= ... | copy: =u= issue URL | To reach the map outside a Pearl buffer, bind it globally as well: @@ -207,17 +207,17 @@ Sorting is local and deterministic. Query filters are AND-only; use a Linear Cus *** Editing issues -Pearl has one write path. Edit a ticket however you like in the buffer, then save it -- Pearl diffs each field against what it last fetched and pushes only what changed. Nothing pushes the moment you pick a value, and there is no per-field "push" command to remember. All issue commands work from anywhere inside an issue subtree. +Pearl has one write path. Edit an issue however you like in the buffer, then save it -- Pearl diffs each field against what it last fetched and pushes only what changed. Nothing pushes the moment you pick a value, and there is no per-field "push" command to remember. All issue commands work from anywhere inside an issue subtree. | Command | What it does | |-----------------------------------+-------------------------------------------------------| -| =pearl-save-issue= | Save every changed field of the ticket at point | -| =pearl-save-all= | Save every changed ticket in the file (confirms once) | +| =pearl-save-issue= | Save every changed field of the issue at point | +| =pearl-save-all= | Save every changed issue in the file (confirms once) | | =pearl-edit-description= | Edit the description in a focused compose buffer | | =pearl-edit-state= | Pick a workflow state (reaches any of the team's) | | =pearl-edit-assignee= | Pick a team member as assignee | | =pearl-edit-labels= | Pick labels; an empty selection clears them | -| =pearl-add-comment= | Add a new Linear comment | +| =pearl-create-comment= | Add a new Linear comment | | =pearl-edit-current-comment= | Edit one of your own comments | | =pearl-delete-current-comment= | Delete one of your own comments after confirming | | =pearl-delete-current-issue= | Soft-delete the current issue after confirmation | @@ -230,7 +230,7 @@ How each field is edited: - *Priority* -- the Org priority cookie (=C-c ,=, or =S-up= / =S-down=). =[#A]/[#B]/[#C]/[#D]= are Urgent/High/Medium/Low; no cookie is None. - *State* -- =pearl-edit-state=, completing over the team's workflow states. - *Assignee and labels* -- =pearl-edit-assignee= / =pearl-edit-labels=, completing over the team's members and labels. Labels also render as Org tags on the heading (=:bug:backend:=), so you can filter, sparse-tree, and build agendas on them. The =:LINEAR-LABELS:= drawer stays the source of truth; hand-edited heading tags are ignored and rewritten from Linear on the next change or fetch, so change labels with =pearl-edit-labels=. -- *Comments* -- edit your own in place, or =pearl-add-comment= for a new one. +- *Comments* -- edit your own in place, or =pearl-create-comment= for a new one. Picking a constrained field writes the value into the buffer and marks it changed; it doesn't push until you save. The display name or label text is there to read -- Pearl reconciles by the underlying id, and a refresh rewrites the display from the remote, so hand-editing the visible name has no effect. @@ -242,9 +242,9 @@ Only comments you authored are editable or deletable. Pearl refuses edits and de A fetched issue renders with a TODO keyword derived from its Linear state name: the name is slugified (upcased, non-alphanumeric runs collapsed to hyphens), so "In Progress" becomes =IN-PROGRESS= and "Dev Review" becomes =DEV-REVIEW=. The buffer's =#+TODO= line is built from the real workflow states of the teams on display, so every keyword you see is a state that team actually has, partitioned active / done by Linear's state type. -You change a ticket's state two ways, both reconciled and pushed at the next save: +You change an issue's state two ways, both reconciled and pushed at the next save: -- Cycle the keyword with =C-c C-t= (or =S-<right>= / =S-<left>=). The keyword you cycle to is matched back to a team state by the same slug rule, so cycling =TODO= → =IN-PROGRESS= moves the ticket to the "In Progress" state. A keyword no team state slugifies to can't be resolved, so that save is reported skipped. +- Cycle the keyword with =C-c C-t= (or =S-<right>= / =S-<left>=). The keyword you cycle to is matched back to a team state by the same slug rule, so cycling =TODO= → =IN-PROGRESS= moves the issue to the "In Progress" state. A keyword no team state slugifies to can't be resolved, so that save is reported skipped. - =pearl-edit-state= completes over every state on the team and writes the keyword for you. Use it to reach a state whose keyword you don't remember, or one the visible keyword set doesn't cover. ** The Org File @@ -1157,7 +1157,7 @@ CALLBACK is called with the created issue data." (pearl--graphql-request-async query variables success-fn error-fn))) -(defun pearl-create-issue (title description team-id) +(defun pearl--create-issue (title description team-id) "Create a new issue (synchronous wrapper for backward compatibility). TITLE is the issue title. DESCRIPTION is the issue description. @@ -2577,7 +2577,7 @@ create callback fires with another buffer current (the compose path)." (message "Added comment to %s" issue-id))))) ;;;###autoload -(defun pearl-add-comment (&optional body) +(defun pearl-create-comment (&optional body) "Add a comment to the Linear issue at point. Interactively, opens an Org compose buffer (C-c C-c sends, C-c C-k cancels) and converts the composed Org to Markdown before sending -- room to write a real @@ -2946,7 +2946,7 @@ yields the hardcoded default. Pure function, no side effects." ;; Affordance preamble (org comments -- not rendered content). (insert "#\n") (insert "# Body = the issue description; edit it, then M-x pearl-save-issue to push.\n") - (insert "# Comments subtree = the thread; add with M-x pearl-add-comment.\n") + (insert "# Comments subtree = the thread; add with M-x pearl-create-comment.\n") (insert "# State/assignee/labels: M-x pearl-edit-state / -assignee / -labels; priority: the org cookie (C-c ,). All push at save.\n") (insert "# Refresh with M-x pearl-refresh-current-view (whole file) or -current-issue (one).\n") (insert "\n") @@ -3552,7 +3552,7 @@ the deferred smerge path." "Save the description of the issue subtree at MARKER, calling CALLBACK. A push advances both `LINEAR-DESC-SHA256' (the markdown baseline used by the remote conflict gate) and `LINEAR-DESC-ORG-SHA256' (the rendered-Org baseline -used by the local dirty scan), so the next scan sees the ticket as clean." +used by the local dirty scan), so the next scan sees the issue as clean." (org-with-point-at marker (let* ((issue-id (org-entry-get nil "LINEAR-ID")) (identifier (org-entry-get nil "LINEAR-IDENTIFIER")) @@ -4147,7 +4147,7 @@ yours\". Returns a plist (:issues :titles :descriptions :priorities :states VIEWER-ID gates comment ownership; VIEWER-FAILED marks a failed viewer lookup so the prompt counts comments as viewer-unavailable rather than read-only. BUFFER is surfaced if anything pushed. Declining the prompt mutates nothing. The -queue continues past a per-field conflict, so one conflicted ticket does not +queue continues past a per-field conflict, so one conflicted issue does not stop the rest." (if (not (y-or-n-p (pearl--save-all-prompt (pearl--save-all-counts scan viewer-id viewer-failed)))) @@ -4195,7 +4195,7 @@ reported. A clean file does nothing." "Non-nil if the issue subtree at point has any unpushed local edit. Broader than `pearl--subtree-dirty-p', which inspects only the description: this also covers a changed title, state, priority, assignee, labels, or comment. -A merge refresh uses it so it keeps a ticket you have touched in any field, not +A merge refresh uses it so it keeps an issue you have touched in any field, not just ones whose description changed." (pearl--issue-has-dirty-fields-p (pearl--issue-dirty-fields))) @@ -4477,7 +4477,7 @@ resolves to a single label." (when (and selected-label-name (not (string-empty-p selected-label-name))) (cdr (assoc selected-label-name issue-types))))) -(defun pearl-new-issue () +(defun pearl-create-issue () "Create a new Linear issue with additional attributes." (interactive) ;; Select team first (needed for states, members, etc.) @@ -4846,9 +4846,9 @@ body stay." ;; delete); fetch / view / setup are the operational groups with no keymap ;; equivalent. The org-sync commands (enable / disable / push-file) are ;; deliberately not surfaced -- they are plumbing (see the todo). - ["Ticket" + ["Issue" ["Save" - ("e" "save ticket" pearl-save-issue) + ("e" "save issue" pearl-save-issue) ("E" "save all" pearl-save-all)] ["Edit" ("D" "edit description" pearl-edit-description) @@ -4856,11 +4856,11 @@ body stay." ("s" "edit state" pearl-edit-state) ("a" "edit assignee" pearl-edit-assignee) ("L" "edit labels" pearl-edit-labels)] - ["New" - ("n" "new ticket" pearl-new-issue) - ("c" "new comment" pearl-add-comment)] + ["Create" + ("n" "create issue" pearl-create-issue) + ("c" "create comment" pearl-create-comment)] ["Delete" - ("k" "delete ticket" pearl-delete-current-issue) + ("k" "delete issue" pearl-delete-current-issue) ("K" "delete comment" pearl-delete-current-comment)]] ["Workspace" ["Fetch" @@ -4873,7 +4873,7 @@ body stay." ("g" "refresh view" pearl-refresh-current-view) ("r" "refresh issue" pearl-refresh-current-issue) ("b" "open view in Linear" pearl-open-current-view-in-linear) - ("o" "open ticket in browser" pearl-open-current-issue)] + ("o" "open issue in browser" pearl-open-current-issue)] ["Setup" ("T" "test connection" pearl-test-connection) ("C" "check setup" pearl-check-setup) @@ -4922,27 +4922,32 @@ body stay." map) "Pearl edit commands; a sub-keymap of `pearl-prefix-map'.") -(defvar pearl-new-map +(defvar pearl-create-map (let ((map (make-sparse-keymap))) - (define-key map "t" (cons "new ticket" #'pearl-new-issue)) - (define-key map "c" (cons "new comment" #'pearl-add-comment)) + (define-key map "t" (cons "create issue" #'pearl-create-issue)) + (define-key map "c" (cons "create comment" #'pearl-create-comment)) map) - "Pearl new-object commands; a sub-keymap of `pearl-prefix-map'.") + "Pearl create commands; a sub-keymap of `pearl-prefix-map'.") (defvar pearl-delete-map (let ((map (make-sparse-keymap))) - (define-key map "t" (cons "delete ticket" #'pearl-delete-current-issue)) + (define-key map "t" (cons "delete issue" #'pearl-delete-current-issue)) (define-key map "c" (cons "delete comment" #'pearl-delete-current-comment)) map) "Pearl delete commands; a sub-keymap of `pearl-prefix-map'.") -(defvar pearl-url-map +(defvar pearl-open-map (let ((map (make-sparse-keymap))) - (define-key map "o" (cons "open issue in browser" #'pearl-open-current-issue)) - (define-key map "c" (cons "copy issue URL" #'pearl-copy-issue-url)) + (define-key map "i" (cons "open issue in browser" #'pearl-open-current-issue)) (define-key map "v" (cons "open view in Linear" #'pearl-open-current-view-in-linear)) map) - "Pearl URL commands; a sub-keymap of `pearl-prefix-map'.") + "Pearl open commands; a sub-keymap of `pearl-prefix-map'.") + +(defvar pearl-copy-map + (let ((map (make-sparse-keymap))) + (define-key map "u" (cons "copy issue URL" #'pearl-copy-issue-url)) + map) + "Pearl copy commands; a sub-keymap of `pearl-prefix-map'.") (defvar pearl-prefix-map (let ((map (make-sparse-keymap))) @@ -4950,21 +4955,21 @@ body stay." (define-key map "l" (cons "list my open issues" #'pearl-list-issues)) (define-key map "g" (cons "refresh view" #'pearl-refresh-current-view)) (define-key map "r" (cons "refresh issue" #'pearl-refresh-current-issue)) - (define-key map "s" (cons "save ticket" #'pearl-save-issue)) + (define-key map "s" (cons "save issue" #'pearl-save-issue)) (define-key map "S" (cons "save all" #'pearl-save-all)) (define-key map "d" (cons "edit description" #'pearl-edit-description)) - (define-key map "c" (cons "new comment" #'pearl-add-comment)) (define-key map "m" (cons "menu" #'pearl-menu)) - ;; Category sub-maps -- two keys. + ;; Category sub-maps -- two keys; each prefix names an action verb. (define-key map "f" (cons "+fetch" pearl-fetch-map)) (define-key map "e" (cons "+edit" pearl-edit-map)) - (define-key map "n" (cons "+new" pearl-new-map)) + (define-key map "c" (cons "+create" pearl-create-map)) (define-key map "k" (cons "+delete" pearl-delete-map)) - (define-key map "u" (cons "+url" pearl-url-map)) + (define-key map "o" (cons "+open" pearl-open-map)) + (define-key map "y" (cons "+copy" pearl-copy-map)) map) "Pearl command prefix keymap. -Hot-path commands are bound directly; the rest live under the category -sub-maps (fetch / edit / new / delete / url). `pearl-mode' binds this under +Hot-path commands are bound directly; the rest live under verb sub-maps +\(fetch / edit / create / delete / open / copy). `pearl-mode' binds this under `pearl-keymap-prefix' inside Pearl buffers; you can also bind it globally \(see the Commentary above).") diff --git a/tests/test-integration-acceptance.el b/tests/test-integration-acceptance.el index 48dd70a..37dce34 100644 --- a/tests/test-integration-acceptance.el +++ b/tests/test-integration-acceptance.el @@ -36,7 +36,7 @@ ;; - --read-active-source / --build-org-content header round-trip ;; - pearl-refresh-current-view -> --merge-query-result (in-place merge) ;; - pearl-sync-current-issue -> --sync-decision -> --update-issue-description-async -;; - pearl-add-comment -> --create-comment-async -> --append-comment-to-issue +;; - pearl-create-comment -> --create-comment-async -> --append-comment-to-issue ;; - edit the priority cookie + pearl-save-issue -> --save-priority-field -> --update-issue-async ;;; Code: @@ -189,7 +189,7 @@ because the description-update mutation contains both `IssueDescription' and (goto-char (point-min)) (re-search-forward "First issue") (setq test-integration--ops nil) - (pearl-add-comment "looks good") + (pearl-create-comment "looks good") (should (memq 'comment-create test-integration--ops)) (goto-char (point-min)) (should (re-search-forward "^\\*\\*\\* 💬 Comments 1/1$" nil t)) diff --git a/tests/test-pearl-commands.el b/tests/test-pearl-commands.el index 77f2d46..ccd39c8 100644 --- a/tests/test-pearl-commands.el +++ b/tests/test-pearl-commands.el @@ -98,9 +98,9 @@ (pearl-list-issues-by-project) (should-not called)))) -;;; pearl-new-issue +;;; pearl-create-issue -(ert-deftest test-pearl-new-issue-success-returns-issue () +(ert-deftest test-pearl-create-issue-success-returns-issue () "new-issue builds the input, sends it, and returns the created issue." (let ((pearl-default-team-id "team-1")) (cl-letf (((symbol-function 'read-string) @@ -120,13 +120,13 @@ (lambda (_q &optional _v) '((data (issueCreate (success . t) (issue (id . "i1") (identifier . "ENG-1") (title . "My Issue")))))))) - (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-new-issue)))))))) + (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-create-issue)))))))) -(ert-deftest test-pearl-new-issue-no-team-stops () +(ert-deftest test-pearl-create-issue-no-team-stops () "new-issue stops cleanly when no team is selected." (let ((pearl-default-team-id nil)) (cl-letf (((symbol-function 'pearl-select-team) (lambda () nil))) - (should (progn (pearl-new-issue) t))))) + (should (progn (pearl-create-issue) t))))) ;;; pearl-test-connection / check-setup diff --git a/tests/test-pearl-comment-editing.el b/tests/test-pearl-comment-editing.el index 4853548..5d5204a 100644 --- a/tests/test-pearl-comment-editing.el +++ b/tests/test-pearl-comment-editing.el @@ -328,7 +328,7 @@ the refresh / add-comment / view-merge paths, never on the initial render." (when (find-buffer-visiting tmp) (kill-buffer (find-buffer-visiting tmp))) (ignore-errors (delete-file tmp))))) -(ert-deftest test-pearl-add-comment-from-inside-comment-walks-up () +(ert-deftest test-pearl-create-comment-from-inside-comment-walks-up () "From inside a comment subtree, add-comment walks up to the enclosing issue and adds the comment there rather than refusing." (test-pearl--in-org (test-pearl--comment-doc "u-me" "stored" "uniquecommentline") @@ -340,7 +340,7 @@ adds the comment there rather than refusing." :created-at "2026-05-26T00:00:00.000Z" :body "hi"))))) (goto-char (point-min)) (re-search-forward "uniquecommentline") ; point inside the comment subtree - (pearl-add-comment "hi")) + (pearl-create-comment "hi")) ;; resolved the enclosing issue's LINEAR-ID ("a"), not the comment's (should (equal "a" created-on))))) diff --git a/tests/test-pearl-comments.el b/tests/test-pearl-comments.el index 99b705a..323d7b7 100644 --- a/tests/test-pearl-comments.el +++ b/tests/test-pearl-comments.el @@ -23,7 +23,7 @@ ;; Comments subtree (ordered per `pearl-comment-sort-order'), including comments ;; in the issue render, the commentCreate helper (stubbed at the HTTP boundary), ;; the in-place append under the Comments subtree (creating it when absent), and the -;; `pearl-add-comment' command. +;; `pearl-create-comment' command. ;;; Code: @@ -165,9 +165,9 @@ (should (< (progn (re-search-forward "first") (point)) (progn (re-search-forward "second") (point))))))) -;;; pearl-add-comment +;;; pearl-create-comment -(ert-deftest test-pearl-add-comment-appends-returned-comment () +(ert-deftest test-pearl-create-comment-appends-returned-comment () "The command creates a comment and inserts the returned one in the buffer." (test-pearl--in-org "*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n" @@ -176,25 +176,25 @@ (funcall cb (list :id "c1" :author "Craig" :created-at "2026-05-23T14:00:00.000Z" :body body))))) (re-search-forward "Body.") - (pearl-add-comment "my new comment") + (pearl-create-comment "my new comment") (goto-char (point-min)) (should (re-search-forward "^\\*\\*\\* 💬 Comments 1/1$" nil t)) (should (re-search-forward "my new comment" nil t))))) -(ert-deftest test-pearl-add-comment-reports-failure () +(ert-deftest test-pearl-create-comment-reports-failure () "A failed create does not insert a Comments subtree." (test-pearl--in-org "*** TODO [#B] Title\n:PROPERTIES:\n:LINEAR-ID: a\n:END:\nBody.\n" (cl-letf (((symbol-function 'pearl--create-comment-async) (lambda (_id _body cb) (funcall cb nil)))) - (pearl-add-comment "x") + (pearl-create-comment "x") (goto-char (point-min)) (should-not (re-search-forward "^\\*\\*\\* Comments$" nil t))))) -(ert-deftest test-pearl-add-comment-not-on-issue-errors () +(ert-deftest test-pearl-create-comment-not-on-issue-errors () "Adding a comment outside a Linear issue heading signals a user error." (test-pearl--in-org "* Plain heading\nno id\n" - (should-error (pearl-add-comment "x") :type 'user-error))) + (should-error (pearl-create-comment "x") :type 'user-error))) (ert-deftest test-pearl-append-comment-targets-current-issue-not-sibling () "Appending adds under the current issue's Comments, not a later issue's subtree." diff --git a/tests/test-pearl-compose.el b/tests/test-pearl-compose.el index 3e62c09..4f4d611 100644 --- a/tests/test-pearl-compose.el +++ b/tests/test-pearl-compose.el @@ -21,7 +21,7 @@ ;; Tests for the shared compose buffer (`pearl--compose-in-buffer' and its ;; submit/abort/body helpers) and the two commands wired onto it: -;; `pearl-add-comment' (interactive composer path) and +;; `pearl-create-comment' (interactive composer path) and ;; `pearl-edit-description'. The body is extracted below a read-only ;; header and converted Org->Markdown by the callers. @@ -102,7 +102,7 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs (re-search-forward "Body\\.") ,@body))) -(ert-deftest test-pearl-add-comment-interactive-composes-and-converts () +(ert-deftest test-pearl-create-comment-interactive-composes-and-converts () "An interactive add-comment composes Org and sends the Markdown conversion." (test-pearl--in-issue (let (sent) @@ -113,11 +113,11 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs (funcall on-finish "see *bold* and `code`"))) ((symbol-function 'pearl--create-comment-async) (lambda (_id body _cb) (setq sent body)))) - (pearl-add-comment) ; interactive form: body nil + (pearl-create-comment) ; interactive form: body nil ;; Org markup converted to Markdown before sending (should (string= "see **bold** and `code`" sent)))))) -(ert-deftest test-pearl-add-comment-with-body-skips-composer () +(ert-deftest test-pearl-create-comment-with-body-skips-composer () "A non-interactive add-comment with BODY sends it directly, no composer." (test-pearl--in-issue (let ((composed nil) sent) @@ -125,7 +125,7 @@ Binds `captured' to the value the on-finish receives (or `:none'). Stubs (lambda (&rest _) (setq composed t))) ((symbol-function 'pearl--create-comment-async) (lambda (_id body _cb) (setq sent body)))) - (pearl-add-comment "literal body") + (pearl-create-comment "literal body") (should-not composed) (should (string= "literal body" sent)))))) diff --git a/tests/test-pearl-keymap.el b/tests/test-pearl-keymap.el index 25b63d9..b465395 100644 --- a/tests/test-pearl-keymap.el +++ b/tests/test-pearl-keymap.el @@ -46,23 +46,24 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)." (should (keymapp pearl-prefix-map))) (ert-deftest test-pearl-prefix-map-direct-hot-keys () - "The hot-path commands resolve in one key under the prefix." + "The hot-path commands resolve in one key under the prefix. +The lowercase `c' is no longer a direct command -- it is the create prefix." (should (eq 'pearl-list-issues (lookup-key pearl-prefix-map (kbd "l")))) (should (eq 'pearl-refresh-current-view (lookup-key pearl-prefix-map (kbd "g")))) (should (eq 'pearl-refresh-current-issue (lookup-key pearl-prefix-map (kbd "r")))) (should (eq 'pearl-save-issue (lookup-key pearl-prefix-map (kbd "s")))) (should (eq 'pearl-save-all (lookup-key pearl-prefix-map (kbd "S")))) (should (eq 'pearl-edit-description (lookup-key pearl-prefix-map (kbd "d")))) - (should (eq 'pearl-add-comment (lookup-key pearl-prefix-map (kbd "c")))) (should (eq 'pearl-menu (lookup-key pearl-prefix-map (kbd "m"))))) -(ert-deftest test-pearl-prefix-map-sub-prefixes-are-keymaps () - "The category keys are sub-prefixes, not commands." - (should (keymapp (lookup-key pearl-prefix-map (kbd "f")))) - (should (keymapp (lookup-key pearl-prefix-map (kbd "e")))) - (should (keymapp (lookup-key pearl-prefix-map (kbd "n")))) - (should (keymapp (lookup-key pearl-prefix-map (kbd "k")))) - (should (keymapp (lookup-key pearl-prefix-map (kbd "u"))))) +(ert-deftest test-pearl-prefix-map-sub-prefixes-are-verbs () + "Each category key is a sub-prefix keymap named for an action verb." + (should (keymapp (lookup-key pearl-prefix-map (kbd "f")))) ; fetch + (should (keymapp (lookup-key pearl-prefix-map (kbd "e")))) ; edit + (should (keymapp (lookup-key pearl-prefix-map (kbd "c")))) ; create + (should (keymapp (lookup-key pearl-prefix-map (kbd "k")))) ; delete + (should (keymapp (lookup-key pearl-prefix-map (kbd "o")))) ; open + (should (keymapp (lookup-key pearl-prefix-map (kbd "y"))))) ; copy (ert-deftest test-pearl-prefix-map-fetch-group () "The fetch group resolves each source command." @@ -82,27 +83,24 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)." ;; priority has no edit command (org-native cookie); e p is unbound (should-not (lookup-key pearl-prefix-map (kbd "e p")))) -(ert-deftest test-pearl-prefix-map-new-and-delete-groups () - "New and delete groups resolve ticket/comment, with delete under k." - (should (eq 'pearl-new-issue (lookup-key pearl-prefix-map (kbd "n t")))) - (should (eq 'pearl-add-comment (lookup-key pearl-prefix-map (kbd "n c")))) +(ert-deftest test-pearl-prefix-map-create-and-delete-groups () + "Create and delete groups resolve issue/comment under c and k." + (should (eq 'pearl-create-issue (lookup-key pearl-prefix-map (kbd "c t")))) + (should (eq 'pearl-create-comment (lookup-key pearl-prefix-map (kbd "c c")))) (should (eq 'pearl-delete-current-issue (lookup-key pearl-prefix-map (kbd "k t")))) (should (eq 'pearl-delete-current-comment (lookup-key pearl-prefix-map (kbd "k c"))))) -(ert-deftest test-pearl-prefix-map-url-group () - "The url group resolves open-in-browser, copy-url, and open-view." - (should (eq 'pearl-open-current-issue (lookup-key pearl-prefix-map (kbd "u o")))) - (should (eq 'pearl-copy-issue-url (lookup-key pearl-prefix-map (kbd "u c")))) - (should (eq 'pearl-open-current-view-in-linear (lookup-key pearl-prefix-map (kbd "u v"))))) +(ert-deftest test-pearl-prefix-map-open-and-copy-groups () + "The open group resolves the two open actions; copy resolves copy-url." + (should (eq 'pearl-open-current-issue (lookup-key pearl-prefix-map (kbd "o i")))) + (should (eq 'pearl-open-current-view-in-linear (lookup-key pearl-prefix-map (kbd "o v")))) + (should (eq 'pearl-copy-issue-url (lookup-key pearl-prefix-map (kbd "y u"))))) (ert-deftest test-pearl-prefix-map-common-commands-bound-twice () "The most common commands resolve both directly and inside their group." ;; edit-description: direct d and e d (should (eq (lookup-key pearl-prefix-map (kbd "d")) (lookup-key pearl-prefix-map (kbd "e d")))) - ;; new comment: direct c and n c - (should (eq (lookup-key pearl-prefix-map (kbd "c")) - (lookup-key pearl-prefix-map (kbd "n c")))) ;; list issues: direct l and f o (should (eq (lookup-key pearl-prefix-map (kbd "l")) (lookup-key pearl-prefix-map (kbd "f o"))))) @@ -111,12 +109,14 @@ Walks MAP looking for a labeled binding (\"label\" . COMMAND)." "Leaf bindings carry verb labels; sub-prefixes carry +labels." (should (string= "edit state" (test-pearl-keymap--label-of pearl-edit-map 'pearl-edit-state))) - (should (string= "new ticket" - (test-pearl-keymap--label-of pearl-new-map 'pearl-new-issue))) + (should (string= "create issue" + (test-pearl-keymap--label-of pearl-create-map 'pearl-create-issue))) (should (string= "open issue in browser" - (test-pearl-keymap--label-of pearl-url-map 'pearl-open-current-issue))) + (test-pearl-keymap--label-of pearl-open-map 'pearl-open-current-issue))) + (should (string= "copy issue URL" + (test-pearl-keymap--label-of pearl-copy-map 'pearl-copy-issue-url))) (should (string= "+edit" (test-pearl-keymap--label-of pearl-prefix-map pearl-edit-map))) - (should (string= "+url" (test-pearl-keymap--label-of pearl-prefix-map pearl-url-map)))) + (should (string= "+open" (test-pearl-keymap--label-of pearl-prefix-map pearl-open-map)))) (ert-deftest test-pearl-prefix-map-not-bound-at-load () "Loading pearl binds nothing globally; the keys are live only via pearl-mode." diff --git a/tests/test-pearl-menu.el b/tests/test-pearl-menu.el index 5964d31..63b925c 100644 --- a/tests/test-pearl-menu.el +++ b/tests/test-pearl-menu.el @@ -65,8 +65,8 @@ menu entry that still points at it fails here." pearl-save-issue pearl-save-all pearl-edit-state - pearl-add-comment - pearl-new-issue + pearl-create-comment + pearl-create-issue pearl-delete-current-issue)) (should (memq expected cmds))))) diff --git a/tests/test-pearl-save.el b/tests/test-pearl-save.el index 2f7b312..47b48c2 100644 --- a/tests/test-pearl-save.el +++ b/tests/test-pearl-save.el @@ -600,7 +600,7 @@ with no push." (ert-deftest test-pearl-save-description-advances-both-hashes () "A description push advances both the markdown and the Org provenance hashes, -so the next local dirty scan (which uses the Org hash) sees the ticket as clean." +so the next local dirty scan (which uses the Org hash) sees the issue as clean." (test-pearl-save--in-rendered (test-pearl-save--issue) (re-search-forward "Original description\\.") (end-of-line) diff --git a/tests/test-pearl-smoke.el b/tests/test-pearl-smoke.el index 53c4d06..760da2a 100644 --- a/tests/test-pearl-smoke.el +++ b/tests/test-pearl-smoke.el @@ -31,7 +31,7 @@ "The package source loads and defines its public commands." (should (featurep 'pearl)) (should (fboundp 'pearl-list-issues)) - (should (fboundp 'pearl-new-issue))) + (should (fboundp 'pearl-create-issue))) (provide 'test-pearl-smoke) ;;; test-pearl-smoke.el ends here diff --git a/tests/test-pearl-sync-wrappers.el b/tests/test-pearl-sync-wrappers.el index a5c9c70..cb4ac96 100644 --- a/tests/test-pearl-sync-wrappers.el +++ b/tests/test-pearl-sync-wrappers.el @@ -43,11 +43,11 @@ '((data (team (states (nodes . (((id . "s1") (name . "Todo")))))) )) (should (= 1 (length (pearl-get-states "team-1")))))) -(ert-deftest test-pearl-create-issue-sync-returns-issue () +(ert-deftest test-pearl--create-issue-sync-returns-issue () "The sync create wrapper returns the created issue node." (testutil-linear-with-response '((data (issueCreate (success . t) (issue (id . "i1") (identifier . "ENG-1") (title . "T"))))) - (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl-create-issue "T" "" "team"))))))) + (should (string-equal "ENG-1" (cdr (assoc 'identifier (pearl--create-issue "T" "" "team"))))))) (ert-deftest test-pearl-sync-wrapper-times-out-without-callback () "A sync wrapper returns nil instead of hanging when no callback fires." |
