diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-pearl-merge.el | 79 | ||||
| -rw-r--r-- | tests/test-pearl-output.el | 47 | ||||
| -rw-r--r-- | tests/test-pearl-refresh.el | 4 | ||||
| -rw-r--r-- | tests/test-pearl-views.el | 70 |
4 files changed, 198 insertions, 2 deletions
diff --git a/tests/test-pearl-merge.el b/tests/test-pearl-merge.el index 931849d..9f9307c 100644 --- a/tests/test-pearl-merge.el +++ b/tests/test-pearl-merge.el @@ -72,6 +72,22 @@ "#+LINEAR-TRUNCATED: no\n\n" (mapconcat #'pearl--format-issue-as-org-entry issues ""))) +(defun test-pearl-merge--issue-st (id title state-name state-type) + "A normalized issue plist in state STATE-NAME/STATE-TYPE, for grouping tests." + (list :id id :identifier (concat "ENG-" id) :title title + :description (concat "Desc " id ".") + :priority 2 :url (concat "https://linear.app/x/" id) + :updated-at "2026-05-23T03:00:00.000Z" + :state (list :id (concat "s-" state-name) :name state-name :type state-type) + :team (list :id "t1" :key "ENG" :name "Engineering"))) + +(defun test-pearl-merge--grouped-buffer (grouping &rest issues) + "Render ISSUES into a grouped view buffer (no help header, network stubbed)." + (let ((pearl-help-header nil)) + (cl-letf (((symbol-function 'pearl--team-states) (lambda (_team-id) nil))) + (pearl--build-org-content + issues (list :type 'view :name "By status" :id "v1" :group grouping))))) + ;;; --merge-issues-into-buffer (ert-deftest test-pearl-merge-updates-existing-in-place () @@ -123,6 +139,69 @@ (goto-char (point-min)) (should-not (re-search-forward "Beta" nil t))))) +;;; grouped-view merge (issueGrouping sections) + +(ert-deftest test-pearl-merge-grouped-update-preserves-level () + "Updating an issue in a grouped buffer re-renders it at level 3 (under its +group), not promoted to level 2 the way a flat append would render it." + (test-pearl-merge--in-org + (test-pearl-merge--grouped-buffer + "workflowState" + (test-pearl-merge--issue-st "a" "Alpha" "Todo" "unstarted") + (test-pearl-merge--issue-st "b" "Beta" "Done" "completed")) + (let ((counts (pearl--merge-issues-into-buffer + (list (test-pearl-merge--issue-st "a" "Alpha Renamed" "Todo" "unstarted") + (test-pearl-merge--issue-st "b" "Beta" "Done" "completed")) + "workflowState"))) + (should (= 2 (plist-get counts :updated))) + (goto-char (point-min)) + (should (re-search-forward "^\\*\\*\\* .*Alpha Renamed" nil t)) + ;; Not promoted to a level-2 heading. + (should-not (save-excursion (goto-char (point-min)) + (re-search-forward "^\\*\\* [A-Z].*Alpha Renamed" nil t)))))) + +(ert-deftest test-pearl-merge-grouped-new-issue-joins-existing-group () + "A new issue whose group already exists is inserted under that group's section +at level 3, not appended at the buffer end." + (test-pearl-merge--in-org + (test-pearl-merge--grouped-buffer + "workflowState" + (test-pearl-merge--issue-st "a" "Alpha" "Todo" "unstarted") + (test-pearl-merge--issue-st "b" "Beta" "Done" "completed")) + (let ((counts (pearl--merge-issues-into-buffer + (list (test-pearl-merge--issue-st "a" "Alpha" "Todo" "unstarted") + (test-pearl-merge--issue-st "b" "Beta" "Done" "completed") + (test-pearl-merge--issue-st "c" "Gamma" "Todo" "unstarted")) + "workflowState"))) + (should (= 1 (plist-get counts :added))) + ;; Gamma renders at level 3, inside the Todo section (before Done). + (goto-char (point-min)) + (let ((todo (re-search-forward "^\\*\\* Todo$" nil t)) + (gamma (re-search-forward "^\\*\\*\\* .*Gamma" nil t)) + (done (save-excursion (goto-char (point-min)) + (re-search-forward "^\\*\\* Done$" nil t)))) + (should (and todo gamma done)) + (should (< todo gamma)) + (should (< gamma done)))))) + +(ert-deftest test-pearl-merge-grouped-new-issue-creates-missing-group () + "A new issue whose group doesn't exist yet gets a fresh `** LABEL' section +created for it, with the issue at level 3 under it." + (test-pearl-merge--in-org + (test-pearl-merge--grouped-buffer + "workflowState" + (test-pearl-merge--issue-st "a" "Alpha" "Todo" "unstarted")) + (should-not (save-excursion (re-search-forward "^\\*\\* In Progress$" nil t))) + (let ((counts (pearl--merge-issues-into-buffer + (list (test-pearl-merge--issue-st "a" "Alpha" "Todo" "unstarted") + (test-pearl-merge--issue-st "d" "Delta" "In Progress" "started")) + "workflowState"))) + (should (= 1 (plist-get counts :added))) + (goto-char (point-min)) + (let ((grp (re-search-forward "^\\*\\* In Progress$" nil t))) + (should grp) + (should (re-search-forward "^\\*\\*\\* .*Delta" nil t)))))) + (ert-deftest test-pearl-merge-keeps-unpushed-edit-on-update () "An existing subtree with unpushed body edits is kept, not overwritten." (test-pearl-merge--in-org diff --git a/tests/test-pearl-output.el b/tests/test-pearl-output.el index 8b02fcb..f02df5f 100644 --- a/tests/test-pearl-output.el +++ b/tests/test-pearl-output.el @@ -70,6 +70,51 @@ (should (string-match-p "^#\\+title:" out)) (should-not (string-match-p "^\\*\\*\\* " out)))) +;;; --build-org-content grouping (issueGrouping -> Org sections) + +(ert-deftest test-pearl-build-org-content-flat-startup-show2levels () + "An ungrouped view folds to show2levels and renders issues at level 2." + (let* ((issues '((:id "1" :identifier "SE-1" :title "One" + :state (:name "Started" :type "started") :priority 0))) + (source '(:type view :name "Flat" :id "v1")) + (out (pearl--build-org-content issues source))) + (should (string-match-p "^#\\+STARTUP: show2levels$" out)) + (should (string-match-p "^\\*\\* STARTED " out)) + (should-not (string-match-p "^\\*\\*\\* " out)))) + +(ert-deftest test-pearl-build-org-content-grouped-sections-and-startup () + "A grouped view folds to show3levels, emits a level-2 group heading per group +in first-appearance order, and renders issues at level 3 under them." + (let* ((issues '((:id "1" :identifier "SE-1" :title "One" + :state (:name "Started" :type "started") :priority 0) + (:id "2" :identifier "SE-2" :title "Two" + :state (:name "Backlog" :type "backlog") :priority 0) + (:id "3" :identifier "SE-3" :title "Three" + :state (:name "Started" :type "started") :priority 0))) + (source '(:type view :name "By status" :id "v1" :group "workflowState")) + (out (pearl--build-org-content issues source))) + (should (string-match-p "^#\\+STARTUP: show3levels$" out)) + (should (string-match-p "^\\*\\* Started$" out)) + (should (string-match-p "^\\*\\* Backlog$" out)) + ;; Issues live at level 3 under the group headings. + (should (string-match-p "^\\*\\*\\* STARTED " out)) + ;; First-appearance order: SE-1 (Started) before SE-2 (Backlog). + (should (< (string-match "^\\*\\* Started$" out) + (string-match "^\\*\\* Backlog$" out))) + ;; The group heading carries no LINEAR-ID, so save/merge skip it. + (let ((started (string-match "^\\*\\* Started$" out)) + (next-id (string-match "LINEAR-ID:" out))) + (should started) + (should (< started next-id))))) + +(ert-deftest test-pearl-build-org-content-grouped-source-roundtrips () + "A grouped view's source (with :group) reads back from the rendered header." + (let* ((source '(:type view :name "By project" :id "v1" :group "project")) + (out (pearl--build-org-content '() source))) + (with-temp-buffer + (insert out) + (should (equal source (pearl--read-active-source)))))) + (ert-deftest test-pearl-build-org-content-title-uses-view-name-verbatim () "The view name in the file title matches Linear's capitalization verbatim, independent of `pearl-title-case-headings' (which governs issue headings only)." @@ -336,7 +381,7 @@ refresh prompt stubbed to return CHOICE. BUF and TMP are bound in BODY." (let ((merged nil)) (test-pearl-output--with-dirty-buffer ?m (cl-letf (((symbol-function 'pearl--merge-issues-into-buffer) - (lambda (_issues) (setq merged t) '(:updated 1 :added 0 :dropped 0 :skipped 0))) + (lambda (_issues &optional _grouping) (setq merged t) '(:updated 1 :added 0 :dropped 0 :skipped 0))) ((symbol-function 'pearl--update-source-header) #'ignore) ((symbol-function 'pearl--update-derived-todo-header) #'ignore) ((symbol-function 'pearl-highlight-comments) #'ignore) diff --git a/tests/test-pearl-refresh.el b/tests/test-pearl-refresh.el index d09c96c..199b346 100644 --- a/tests/test-pearl-refresh.el +++ b/tests/test-pearl-refresh.el @@ -93,7 +93,9 @@ (re-search-forward "Stale Title") (pearl-refresh-current-issue) (goto-char (point-min)) - (re-search-forward "^\\*\\* ") + ;; refresh preserves the issue's heading level (here level 3) rather than + ;; flattening it -- a grouped view's issues stay at level 3 on refresh + (re-search-forward "^\\*\\*\\* ") ;; heading + drawer reflect the remote (should (string-match-p "Refreshed Title" (thing-at-point 'line t))) (should (string= "In Progress" (org-entry-get nil "LINEAR-STATE-NAME"))) diff --git a/tests/test-pearl-views.el b/tests/test-pearl-views.el index 3675200..7c8ec80 100644 --- a/tests/test-pearl-views.el +++ b/tests/test-pearl-views.el @@ -325,6 +325,76 @@ the pick-source path showed a view's completed/canceled issues and ignored sort. ;; The favorite's own keys survive the enrichment. (should (string= "https://x" (plist-get rendered-source :url)))))) +;;; --view-grouping-active-p / --view-group-label / --group-issues + +(ert-deftest test-pearl-view-grouping-active-p-supported-types () + "The single-valued grouping dimensions are active in v1." + (dolist (g '("workflowState" "project" "assignee" "priority" "cycle")) + (should (pearl--view-grouping-active-p g)))) + +(ert-deftest test-pearl-view-grouping-active-p-deferred-and-none () + "Label-grouping (multi-valued, deferred), none, nil, and unknown values are +inactive, so the view renders flat." + (dolist (g '("label" "none" nil "someFutureGrouping")) + (should-not (pearl--view-grouping-active-p g)))) + +(ert-deftest test-pearl-view-group-label-by-dimension () + "Each supported grouping resolves an issue to its group label." + (let ((issue '(:state (:name "In Progress" :type "started") + :project (:name "Orchestration") + :assignee (:name "Eric") + :priority 1 + :cycle (:number 4 :name "Sprint 4")))) + (should (string= "In Progress" (pearl--view-group-label issue "workflowState"))) + (should (string= "Orchestration" (pearl--view-group-label issue "project"))) + (should (string= "Eric" (pearl--view-group-label issue "assignee"))) + (should (string= "Urgent" (pearl--view-group-label issue "priority"))) + (should (string= "Sprint 4" (pearl--view-group-label issue "cycle"))))) + +(ert-deftest test-pearl-view-group-label-missing-values () + "A missing dimension value gets a stable \"No X\" bucket label, matching how +Linear shows unset issues in a grouped view." + (let ((issue '(:priority 0))) + (should (string= "No status" (pearl--view-group-label issue "workflowState"))) + (should (string= "No project" (pearl--view-group-label issue "project"))) + (should (string= "No assignee" (pearl--view-group-label issue "assignee"))) + (should (string= "No cycle" (pearl--view-group-label issue "cycle"))) + (should (string= "None" (pearl--view-group-label issue "priority"))))) + +(ert-deftest test-pearl-view-group-label-cycle-number-fallback () + "A cycle with no name falls back to its number." + (should (string= "Cycle 7" + (pearl--view-group-label '(:cycle (:number 7)) "cycle")))) + +(ert-deftest test-pearl-group-issues-inactive-is-nil () + "An inactive grouping returns nil, signaling the caller to render flat." + (should-not (pearl--group-issues '((:id "a" :state (:name "Todo"))) "none")) + (should-not (pearl--group-issues '((:id "a" :state (:name "Todo"))) nil))) + +(ert-deftest test-pearl-group-issues-orders-by-first-appearance () + "Groups appear in the order their first issue does; issues keep their input +order within a group (the layer-1 sort is preserved inside each section)." + (let* ((issues '((:id "1" :state (:name "Started")) + (:id "2" :state (:name "Backlog")) + (:id "3" :state (:name "Started")) + (:id "4" :state (:name "Backlog")))) + (groups (pearl--group-issues issues "workflowState"))) + (should (equal '("Started" "Backlog") (mapcar #'car groups))) + (should (equal '("1" "3") (mapcar (lambda (i) (plist-get i :id)) + (cdr (assoc "Started" groups))))) + (should (equal '("2" "4") (mapcar (lambda (i) (plist-get i :id)) + (cdr (assoc "Backlog" groups))))))) + +;;; --view-node-prefs carries grouping + +(ert-deftest test-pearl-view-node-prefs-extracts-grouping () + "The view node's issueGrouping is read into :group alongside sort and +show-completed, so run/refresh can section the issues." + (let ((prefs (pearl--view-node-prefs + '((id . "v1") + (viewPreferencesValues (issueGrouping . "workflowState")))))) + (should (string= "workflowState" (plist-get prefs :group))))) + ;;; refresh-current-view, view branch (ert-deftest test-pearl-refresh-current-view-runs-view-source () |
