diff options
| -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." |
