aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-normalize.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-24 13:44:34 -0500
committerCraig Jennings <c@cjennings.net>2026-05-24 13:44:34 -0500
commitb081d62276378b3168c92c06153fd59db0589535 (patch)
tree9be7f7d22e0c9b4a73432fe744c09bb456c671a9 /tests/test-pearl-normalize.el
downloadpearl-b081d62276378b3168c92c06153fd59db0589535.tar.gz
pearl-b081d62276378b3168c92c06153fd59db0589535.zip
feat: pearl — manage Linear issues from org-mode
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.
Diffstat (limited to 'tests/test-pearl-normalize.el')
-rw-r--r--tests/test-pearl-normalize.el138
1 files changed, 138 insertions, 0 deletions
diff --git a/tests/test-pearl-normalize.el b/tests/test-pearl-normalize.el
new file mode 100644
index 0000000..78874ab
--- /dev/null
+++ b/tests/test-pearl-normalize.el
@@ -0,0 +1,138 @@
+;;; test-pearl-normalize.el --- Tests for API model normalization -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2026 Craig Jennings
+
+;; Author: Craig Jennings <c@cjennings.net>
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Tests for the normalizers that flatten raw json-read Linear responses into
+;; internal plists. Driven by the shared fixtures so the renderer-facing
+;; contract is locked: vectors become lists, absent/`:json-false' fields become
+;; nil, and a null comment author falls back to the bot/external actor.
+
+;;; Code:
+
+(require 'test-bootstrap (expand-file-name "test-bootstrap.el"))
+(require 'testutil-fixtures (expand-file-name "testutil-fixtures.el"))
+
+;;; node-list
+
+(ert-deftest test-pearl-normalize-node-list-vector-to-list ()
+ "A connection's nodes vector is returned as a list."
+ (should (equal '(1 2 3) (pearl--node-list '((nodes . [1 2 3]))))))
+
+(ert-deftest test-pearl-normalize-node-list-empty-and-missing ()
+ "An empty or missing nodes connection yields an empty list."
+ (should (null (pearl--node-list '((nodes . [])))))
+ (should (null (pearl--node-list '((pageInfo . nil))))))
+
+;;; normalize-issue -- fully populated
+
+(ert-deftest test-pearl-normalize-issue-full ()
+ "A full issue normalizes every field, flattening nested objects."
+ (let ((i (pearl--normalize-issue (testutil-linear-fixture-issue-full))))
+ (should (string= "ENG-42" (plist-get i :identifier)))
+ (should (string= "Fix the thing" (plist-get i :title)))
+ (should (= 2 (plist-get i :priority)))
+ (should (string= "In Progress" (plist-get (plist-get i :state) :name)))
+ (should (string= "started" (plist-get (plist-get i :state) :type)))
+ (should (string= "Craig" (plist-get (plist-get i :assignee) :name)))
+ (should (string= "ENG" (plist-get (plist-get i :team) :key)))
+ (should (string= "Platform" (plist-get (plist-get i :project) :name)))
+ (should (string= "Cycle 12" (plist-get (plist-get i :cycle) :name)))
+ ;; labels: vector of nodes -> list of (:id :name) plists
+ (should (equal '("bug" "backend")
+ (mapcar (lambda (l) (plist-get l :name)) (plist-get i :labels))))))
+
+;;; normalize-issue -- null / missing optional fields
+
+(ert-deftest test-pearl-normalize-issue-null-fields ()
+ "Absent or null optional fields normalize to nil, not an error."
+ (let ((i (pearl--normalize-issue (testutil-linear-fixture-issue-null-fields))))
+ (should (string= "ENG-7" (plist-get i :identifier)))
+ (should (null (plist-get i :description)))
+ (should (null (plist-get i :assignee)))
+ (should (null (plist-get i :project)))
+ (should (null (plist-get i :cycle)))
+ (should (null (plist-get i :labels)))
+ ;; state is still present
+ (should (string= "Todo" (plist-get (plist-get i :state) :name)))))
+
+(ert-deftest test-pearl-normalize-issue-nil-input ()
+ "Normalizing nil yields nil."
+ (should (null (pearl--normalize-issue nil))))
+
+(ert-deftest test-pearl-normalize-issue-omits-comments-when-absent ()
+ "An issue fetched without comments has a nil :comments, not an empty list."
+ (let ((i (pearl--normalize-issue (testutil-linear-fixture-issue-full))))
+ (should (null (plist-get i :comments)))))
+
+;;; normalize-comment -- author fallback
+
+(ert-deftest test-pearl-normalize-comment-user-author ()
+ "A comment with a user takes the user's name as author."
+ (let* ((raw (car (pearl--node-list
+ (cdr (assoc 'comments (testutil-linear-fixture-issue-with-comments))))))
+ (c (pearl--normalize-comment raw)))
+ (should (string= "Alice" (plist-get c :author)))
+ (should (string= "First comment" (plist-get c :body)))))
+
+(ert-deftest test-pearl-normalize-comment-null-user-falls-back-to-bot ()
+ "A comment with a null user falls back to the bot actor's name.
+
+`Comment.user' is null for integration/bot comments, so the renderer must not
+assume a user is present."
+ (let ((c (pearl--normalize-comment
+ '((id . "cm-bot") (body . "Deployed") (createdAt . "2026-05-20T00:00:00Z")
+ (user) (botActor . ((name . "GitHub")))))))
+ (should (string= "GitHub" (plist-get c :author)))))
+
+(ert-deftest test-pearl-normalize-comment-null-user-no-actor-nil ()
+ "A null user with no bot or external actor leaves :author nil.
+The renderer is responsible for showing a placeholder; the normalizer reports
+the absence honestly rather than inventing a name."
+ (let ((c (pearl--normalize-comment '((id . "cm-x") (body . "x") (user)))))
+ (should (null (plist-get c :author)))))
+
+(ert-deftest test-pearl-normalize-comment-bot-without-name-default ()
+ "A bot actor with no name falls back to the literal \"automation\"."
+ (let ((c (pearl--normalize-comment
+ '((id . "cm-b") (body . "x") (user) (botActor . ((id . "b1")))))))
+ (should (string= "automation" (plist-get c :author)))))
+
+;;; normalize-custom-view
+
+(ert-deftest test-pearl-normalize-custom-view-personal ()
+ "A personal (shared=false) workspace-wide view: :shared nil, :team nil."
+ (let* ((views (cdr (assoc 'nodes (assoc 'customViews
+ (assoc 'data (testutil-linear-fixture-custom-views))))))
+ (cv (pearl--normalize-custom-view (elt views 0))))
+ (should (string= "My open work" (plist-get cv :name)))
+ (should (null (plist-get cv :shared)))
+ (should (null (plist-get cv :team)))
+ (should (string= "Craig" (plist-get (plist-get cv :owner) :name)))))
+
+(ert-deftest test-pearl-normalize-custom-view-shared-with-team ()
+ "A shared team view: :shared t and a normalized :team plist."
+ (let* ((views (cdr (assoc 'nodes (assoc 'customViews
+ (assoc 'data (testutil-linear-fixture-custom-views))))))
+ (cv (pearl--normalize-custom-view (elt views 1))))
+ (should (eq t (plist-get cv :shared)))
+ (should (string= "ENG" (plist-get (plist-get cv :team) :key)))))
+
+(provide 'test-pearl-normalize)
+;;; test-pearl-normalize.el ends here