aboutsummaryrefslogtreecommitdiff
path: root/tests/test-pearl-filter.el
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test-pearl-filter.el')
-rw-r--r--tests/test-pearl-filter.el193
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