;;; test-pearl-views.el --- Tests for Linear Custom Views -*- lexical-binding: t; -*- ;; Copyright (C) 2026 Craig Jennings ;; Author: Craig Jennings ;; 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 . ;;; Commentary: ;; Tests for reading and running Linear Custom Views: the cached views list, ;; the server-side `--query-view-async' run, `pearl-run-view', the view ;; branch of refresh, and opening the active view in the browser. HTTP is ;; stubbed. ;;; Code: (require 'test-bootstrap (expand-file-name "test-bootstrap.el")) (require 'testutil-request (expand-file-name "testutil-request.el")) (require 'cl-lib) ;;; --query-view-async (ert-deftest test-pearl-query-view-async-extracts-issues () "Running a view extracts the server-side issue nodes into an ok result." (testutil-linear-with-response '((data (customView (issues (nodes . [((id . "i1") (identifier . "ENG-1") (title . "A"))]) (pageInfo (hasNextPage . :json-false) (endCursor . nil)))))) (let (result) (pearl--query-view-async "view-1" (lambda (r) (setq result r))) (should (eq 'ok (pearl--query-result-status result))) (should (= 1 (length (pearl--query-result-issues result))))))) ;;; --custom-views (cached) (ert-deftest test-pearl-custom-views-caches () "The views list is fetched once and served from cache." (let ((pearl-api-key "test-key") (pearl--cache-views nil) (calls 0)) (cl-letf (((symbol-function 'request) (lambda (_url &rest args) (cl-incf calls) (funcall (plist-get args :success) :data '((data (customViews (nodes . [((id . "v1") (name . "My View") (url . "https://x"))]) (pageInfo (hasNextPage . :json-false))))))))) (let ((views (pearl--custom-views))) (should (= 1 (length views))) (pearl--custom-views) (should (= 1 calls)))))) ;;; run-view (ert-deftest test-pearl-run-view-renders-with-view-source () "Running a view resolves its id and renders with a view-typed source." (let ((ran-id nil) (rendered-source nil)) (cl-letf (((symbol-function 'pearl--custom-views) (lambda (&optional _force) '(((id . "v1") (name . "My View") (url . "https://linear.app/view/v1"))))) ((symbol-function 'pearl--query-view-async) (lambda (id cb) (setq ran-id id) (funcall cb (pearl--make-query-result 'ok :issues nil)))) ((symbol-function 'pearl--render-query-result) (lambda (_result source) (setq rendered-source source)))) (pearl-run-view "My View") (should (string= "v1" ran-id)) (should (eq 'view (plist-get rendered-source :type))) (should (string= "v1" (plist-get rendered-source :id))) (should (string= "https://linear.app/view/v1" (plist-get rendered-source :url)))))) ;;; refresh-current-view, view branch (ert-deftest test-pearl-refresh-current-view-runs-view-source () "Refresh on a view source calls the view query, not the filter query." (let ((view-ran nil) (source '(:type view :name "My View" :id "v1" :url "https://x"))) (with-temp-buffer (insert (format "#+title: Linear — My View\n#+LINEAR-SOURCE: %s\n\n" (prin1-to-string source))) (org-mode) (cl-letf (((symbol-function 'pearl--query-view-async) (lambda (id cb) (setq view-ran id) (funcall cb (pearl--make-query-result 'ok :issues nil)))) ((symbol-function 'pearl--merge-query-result) (lambda (&rest _) nil))) (pearl-refresh-current-view) (should (string= "v1" view-ran)))))) ;;; open-current-view-in-linear (ert-deftest test-pearl-open-current-view-visits-url () "Opening the active view visits the source's url." (let ((visited nil) (source '(:type view :name "My View" :id "v1" :url "https://linear.app/view/v1"))) (with-temp-buffer (insert (format "#+LINEAR-SOURCE: %s\n" (prin1-to-string source))) (org-mode) (cl-letf (((symbol-function 'browse-url) (lambda (u &rest _) (setq visited u)))) (pearl-open-current-view-in-linear) (should (string= "https://linear.app/view/v1" visited)))))) (ert-deftest test-pearl-open-current-view-no-url-errors () "Opening a non-view or url-less source signals a user error." (let ((source '(:type filter :name "My open issues" :filter (:assignee :me)))) (with-temp-buffer (insert (format "#+LINEAR-SOURCE: %s\n" (prin1-to-string source))) (org-mode) (should-error (pearl-open-current-view-in-linear) :type 'user-error)))) ;;; the view query fetches comments too (ert-deftest test-pearl-view-issues-query-requests-comments () "The Custom View query selects comments, so a view-populated list shows them." (should (string-match-p "comments[[:space:]]*{[[:space:]]*nodes" pearl--view-issues-query))) (provide 'test-pearl-views) ;;; test-pearl-views.el ends here