diff options
Diffstat (limited to 'tests/test-pearl-filter.el')
| -rw-r--r-- | tests/test-pearl-filter.el | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/tests/test-pearl-filter.el b/tests/test-pearl-filter.el new file mode 100644 index 0000000..6143311 --- /dev/null +++ b/tests/test-pearl-filter.el @@ -0,0 +1,193 @@ +;;; test-pearl-filter.el --- Tests for the issue filter DSL -*- 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 Layer 1 issue-filter DSL: `pearl--build-issue-filter' +;; (and its predicate helpers) and `pearl--validate-issue-filter'. All +;; pure -- no network. Each authoring key is checked in isolation, then in +;; combination (sibling clauses AND-ed), with `:state'/`:open' precedence and a +;; json-encode round-trip; validation covers the error cases. + +;;; Code: + +(require 'test-bootstrap (expand-file-name "test-bootstrap.el")) +(require 'json) + +;;; predicate helpers + +(ert-deftest test-pearl-filter-eq-helper () + "`--eq' wraps a value in an eq comparator." + (should (equal (pearl--eq "x") '(("eq" . "x")))) + (should (equal (pearl--eq t) '(("eq" . t))))) + +(ert-deftest test-pearl-filter-in-nin-helpers-make-vectors () + "`--in' / `--nin' encode their values as JSON arrays (vectors)." + (should (equal (pearl--in '("a" "b")) '(("in" . ["a" "b"])))) + (should (equal (pearl--nin '("a")) '(("nin" . ["a"]))))) + +;;; compile-priority + +(ert-deftest test-pearl-filter-compile-priority-symbol-and-int () + "Priority symbols map to numbers; integers pass through." + (should (= 1 (pearl--compile-priority 'urgent))) + (should (= 0 (pearl--compile-priority 'none))) + (should (= 4 (pearl--compile-priority 'low))) + (should (= 3 (pearl--compile-priority 3)))) + +;;; build-issue-filter -- single dimensions (Normal) + +(ert-deftest test-pearl-filter-assignee-me () + ":assignee :me compiles to assignee.isMe.eq true." + (should (equal (pearl--build-issue-filter '(:assignee :me)) + '(("assignee" ("isMe" ("eq" . t))))))) + +(ert-deftest test-pearl-filter-assignee-email () + ":assignee with an email compiles to assignee.email.eq." + (should (equal (pearl--build-issue-filter '(:assignee "x@y.com")) + '(("assignee" ("email" ("eq" . "x@y.com"))))))) + +(ert-deftest test-pearl-filter-open () + ":open t compiles to state.type nin the closed types." + (should (equal (pearl--build-issue-filter '(:open t)) + '(("state" ("type" ("nin" . ["completed" "canceled" "duplicate"]))))))) + +(ert-deftest test-pearl-filter-state-name () + ":state compiles to state.name.eq." + (should (equal (pearl--build-issue-filter '(:state "In Progress")) + '(("state" ("name" ("eq" . "In Progress"))))))) + +(ert-deftest test-pearl-filter-state-type-list () + ":state-type with a list compiles to state.type.in." + (should (equal (pearl--build-issue-filter '(:state-type ("started" "unstarted"))) + '(("state" ("type" ("in" . ["started" "unstarted"]))))))) + +(ert-deftest test-pearl-filter-state-type-single () + ":state-type with a bare string is wrapped into a one-element array." + (should (equal (pearl--build-issue-filter '(:state-type "started")) + '(("state" ("type" ("in" . ["started"]))))))) + +(ert-deftest test-pearl-filter-project-team-cycle () + ":project / :cycle compile to id.eq; :team to key.eq." + (should (equal (pearl--build-issue-filter '(:project "p-1")) + '(("project" ("id" ("eq" . "p-1")))))) + (should (equal (pearl--build-issue-filter '(:team "ENG")) + '(("team" ("key" ("eq" . "ENG")))))) + (should (equal (pearl--build-issue-filter '(:cycle "c-1")) + '(("cycle" ("id" ("eq" . "c-1"))))))) + +(ert-deftest test-pearl-filter-labels-any-of () + ":labels compiles to labels.some.name.in (carries any of the listed labels)." + (should (equal (pearl--build-issue-filter '(:labels ("bug" "p1"))) + '(("labels" ("some" ("name" ("in" . ["bug" "p1"])))))))) + +(ert-deftest test-pearl-filter-priority-symbol () + ":priority symbol compiles to priority.eq with the numeric value." + (should (equal (pearl--build-issue-filter '(:priority high)) + '(("priority" ("eq" . 2)))))) + +;;; precedence (:state / :state-type win over :open) + +(ert-deftest test-pearl-filter-explicit-state-beats-open () + "An explicit :state overrides :open." + (should (equal (pearl--build-issue-filter '(:open t :state "Done")) + '(("state" ("name" ("eq" . "Done"))))))) + +(ert-deftest test-pearl-filter-state-type-beats-open () + ":state-type overrides :open." + (should (equal (pearl--build-issue-filter '(:open t :state-type ("started"))) + '(("state" ("type" ("in" . ["started"]))))))) + +;;; composition (sibling clauses AND-ed) + ordering keys ignored + +(ert-deftest test-pearl-filter-composition-keeps-all-clauses () + "Multiple keys produce sibling clauses; :sort/:order don't affect the filter." + (let ((f (pearl--build-issue-filter + '(:assignee :me :open t :project "p-1" :labels ("bug") + :priority urgent :sort updated :order desc)))) + (should (assoc "assignee" f)) + (should (assoc "state" f)) + (should (assoc "project" f)) + (should (assoc "labels" f)) + (should (assoc "priority" f)) + ;; ordering keys are not part of the IssueFilter + (should-not (assoc "sort" f)) + (should-not (assoc "order" f)))) + +;;; boundary + +(ert-deftest test-pearl-filter-empty-plist-empty-filter () + "An empty plist compiles to an empty filter." + (should (null (pearl--build-issue-filter '())))) + +(ert-deftest test-pearl-filter-priority-zero-kept () + ":priority 0 (none) is kept, not treated as absent." + (should (equal (pearl--build-issue-filter '(:priority 0)) + '(("priority" ("eq" . 0)))))) + +;;; json-encode round-trip (proves the alist shape renders the right JSON) + +(ert-deftest test-pearl-filter-json-encodes-as-expected () + "The compiled filter json-encodes to the expected IssueFilter JSON." + (should (string= (json-encode (pearl--build-issue-filter '(:assignee :me :open t))) + (concat "{\"assignee\":{\"isMe\":{\"eq\":true}}," + "\"state\":{\"type\":{\"nin\":" + "[\"completed\",\"canceled\",\"duplicate\"]}}}")))) + +;;; validation (Error cases) + +(ert-deftest test-pearl-filter-validate-accepts-good-filter () + "A well-formed filter validates to t." + (should (eq t (pearl--validate-issue-filter + '(:assignee :me :open t :priority high :labels ("bug") :order desc))))) + +(ert-deftest test-pearl-filter-validate-rejects-unknown-key () + "An unknown key is a user-error." + (should-error (pearl--validate-issue-filter '(:bogus 1)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-odd-plist () + "A plist with an odd number of elements is a user-error." + (should-error (pearl--validate-issue-filter '(:open)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-bad-priority-symbol () + "An unrecognized priority symbol is a user-error." + (should-error (pearl--validate-issue-filter '(:priority huge)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-priority-out-of-range () + "A priority integer outside 0-4 is a user-error." + (should-error (pearl--validate-issue-filter '(:priority 9)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-bad-assignee () + "An :assignee that is neither :me nor a string is a user-error." + (should-error (pearl--validate-issue-filter '(:assignee 42)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-empty-string () + "An empty string for a value key is a user-error." + (should-error (pearl--validate-issue-filter '(:project "")) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-bad-order () + "An :order other than asc/desc is a user-error." + (should-error (pearl--validate-issue-filter '(:order sideways)) :type 'user-error)) + +(ert-deftest test-pearl-filter-validate-rejects-non-string-label () + "A non-string entry in :labels is a user-error." + (should-error (pearl--validate-issue-filter '(:labels ("bug" 7))) :type 'user-error)) + +(provide 'test-pearl-filter) +;;; test-pearl-filter.el ends here |
