feat(query-dsl): add unit tests

This commit is contained in:
Tienson Qin
2020-12-28 00:44:09 +08:00
parent 3a4a4eeba9
commit 1ce76bbcb6
11 changed files with 758 additions and 143 deletions

View File

@@ -3,8 +3,10 @@
(defonce test-db "test-db")
(defn wrap-setup!
[f]
(conn/start! nil test-db)
(f)
(defn start-test-db!
[]
(conn/start! nil test-db))
(defn destroy-test-db!
[]
(conn/destroy-all!))

View File

@@ -1,6 +1,6 @@
(ns frontend.db.model-test
(:require [frontend.db.model :as model]
[frontend.db.config :refer [wrap-setup! test-db]]
[frontend.db.config :refer [test-db] :as config]
[datascript.core :as d]
[frontend.db-schema :as schema]
[frontend.handler.repo :as repo-handler]
@@ -67,5 +67,8 @@
1 (count a-ref-blocks)
["b" "c"] alias-names)))
(use-fixtures :each wrap-setup!)
(use-fixtures :each
{:before config/start-test-db!
:after config/destroy-test-db!})
#_(cljs.test/test-ns 'frontend.db.model-test)

View File

@@ -1,7 +1,545 @@
(ns frontend.db.query-dsl-test
(:require [frontend.db.query-dsl :as query-dsl]
(:require [frontend.db.query-dsl :as dsl]
[frontend.db :as db]
[frontend.db.conn :as conn]
[frontend.db.config :refer [test-db] :as config]
[datascript.core :as d]
[frontend.db-schema :as schema]
[frontend.handler.repo :as repo-handler]
[cljs.test :refer [deftest is are testing]]))
[cljs.test :refer [deftest is are testing use-fixtures]]))
;; TODO: quickcheck
;; 1. generate query filters
;; 2. find illegal queries which can't be executed by datascript
;; 3. find filters combinations which might break the current query implementation
(defn import-test-data!
[]
(let [files [{:file/path "journals/2020_12_26.md"
:file/content "---
title: Dec 26th, 2020
tags: [[page-tag-1]], page-tag-2
parent: [[child page 1]]
---
## DONE 26-b1 [[page 1]]
:PROPERTIES:
:created_at: 1608968448113
:last_modified_at: 1608968448113
:prop_a: val_a
:prop_c: [[page a]], [[page b]], [[page c]]
:END:
## LATER 26-b2-modified-later [[page 2]] #tag1
:PROPERTIES:
:created_at: 1608968448114
:last_modified_at: 1608968448120
:prop_b: val_b
:END:
## DONE [#A] 26-b3 [[page 1]]
:PROPERTIES:
:created_at: 1608968448115
:last_modified_at: 1608968448115
:END:
"}
{:file/path "journals/2020_12_27.md"
:file/content "---
title: Dec 27th, 2020
tags: page-tag-2, [[page-tag-3]]
parent: [[child page 1]], child page 2
---
## NOW [#A] b1 [[page 1]]
:PROPERTIES:
:created_at: 1609052958714
:last_modified_at: 1609052958714
:END:
## LATER [#B] b2-modified-later [[page 2]]
:PROPERTIES:
:created_at: 1609052959376
:last_modified_at: 1609052974285
:END:
## b3 [[page 1]]
:PROPERTIES:
:created_at: 1609052959954
:last_modified_at: 1609052959954
:prop_a: val_a
:END:
## b4 [[page 2]]
:PROPERTIES:
:created_at: 1609052961569
:last_modified_at: 1609052961569
:END:
## b5
:PROPERTIES:
:created_at: 1609052963089
:last_modified_at: 1609052963089
:END:"}
{:file/path "journals/2020_12_28.md"
:file/content "---
title: Dec 28th, 2020
parent: child page 2
---
## 28-b1 [[page 1]]
:PROPERTIES:
:created_at: 1609084800000
:last_modified_at: 1609084800000
:END:
## 28-b2-modified-later [[page 2]]
:PROPERTIES:
:created_at: 1609084800001
:last_modified_at: 1609084800020
:END:
## 28-b3 [[page 1]]
:PROPERTIES:
:created_at: 1609084800002
:last_modified_at: 1609084800002
:END:
"}]]
(repo-handler/parse-files-and-load-to-db! test-db files {:re-render? false})))
(def parse (partial dsl/parse test-db))
(defn- q
[s]
(db/clear-query-state!)
(let [parse-result (parse s)]
{:query (:query parse-result)
:result (dsl/query test-db s)}))
(defn q-count
[s]
(let [{:keys [query result]} (q s)]
{:query query
:count (if result
(count @result)
0)}))
(defonce empty-result {:query nil :result nil})
(deftest test-parse
[]
(testing "nil or blank strings should be ignored"
(are [x y] (= (q x) y)
nil empty-result
"" empty-result
" " empty-result))
(testing "Non exists page should be ignored"
(are [x y] (= (q x) y)
"[[page-not-exist]]" empty-result
"[[another-page-not-exist]]" empty-result))
(testing "Single page query"
(are [x y] (= (q-count x) y)
"[[page 1]]"
{:query '[[?b :block/ref-pages [:page/name "page 1"]]]
:count 6}
"[[page 2]]"
{:query '[[?b :block/ref-pages [:page/name "page 2"]]]
:count 4}))
(testing "Block properties query"
(are [x y] (= (q-count x) y)
"(property prop_a val_a)"
{:query '[[?b :block/properties ?p]
[(get ?p "prop_a") ?v]
(or
[(= ?v "val_a")]
[(contains? ?v "val_a")])]
:count 2}
"(property prop_b val_b)"
{:query '[[?b :block/properties ?p]
[(get ?p "prop_b") ?v]
(or
[(= ?v "val_b")]
[(contains? ?v "val_b")])]
:count 1}
"(and (property prop_b val_b))"
{:query '[[?b :block/properties ?p]
[(get ?p "prop_b") ?v]
(or
[(= ?v "val_b")]
[(contains? ?v "val_b")])]
:count 1}
"(and (property prop_c \"page c\"))"
{:query '[[?b :block/properties ?p]
[(get ?p "prop_c") ?v]
(or
[(= ?v "page c")]
[(contains? ?v "page c")])]
:count 1}
;; TODO: optimize
"(and (property prop_c \"page c\") (property prop_c \"page b\"))"
{:query '([?b :block/properties ?p]
[(get ?p "prop_c") ?v]
(or [(= ?v "page c")] [(contains? ?v "page c")])
(or [(= ?v "page b")] [(contains? ?v "page b")]))
:count 1}
"(or (property prop_c \"page c\") (property prop_b val_b))"
{:query '(or
(and [?b :block/properties ?p]
[(get ?p "prop_c") ?v]
(or [(= ?v "page c")] [(contains? ?v "page c")]))
(and [?b :block/properties ?p]
[(get ?p "prop_b") ?v]
(or [(= ?v "val_b")] [(contains? ?v "val_b")])))
:count 2}))
(testing "TODO queries"
(are [x y] (= (q-count x) y)
"(todo now)"
{:query '[[?b :block/marker ?marker]
[(contains? #{"NOW"} ?marker)]]
:count 1}
"(todo NOW)"
{:query '[[?b :block/marker ?marker]
[(contains? #{"NOW"} ?marker)]]
:count 1}
"(todo later)"
{:query '[[?b :block/marker ?marker]
[(contains? #{"LATER"} ?marker)]]
:count 2}
"(todo now later)"
{:query '[[?b :block/marker ?marker]
[(contains? #{"NOW" "LATER"} ?marker)]]
:count 3}
"(todo [now later])"
{:query '[[?b :block/marker ?marker]
[(contains? #{"NOW" "LATER"} ?marker)]]
:count 3}))
(testing "Priority queries"
(are [x y] (= (q-count x) y)
"(priority A)"
{:query '[[?b :block/priority ?priority]
[(contains? #{"A"} ?priority)]]
:count 2}
"(priority a)"
{:query '[[?b :block/priority ?priority]
[(contains? #{"A"} ?priority)]]
:count 2}
"(priority a b)"
{:query '[[?b :block/priority ?priority]
[(contains? #{"A" "B"} ?priority)]]
:count 3}
"(priority [a b])"
{:query '[[?b :block/priority ?priority]
[(contains? #{"A" "B"} ?priority)]]
:count 3}
"(priority a b c)"
{:query '[[?b :block/priority ?priority]
[(contains? #{"A" "B" "C"} ?priority)]]
:count 3}))
(testing "all-page-tags queries"
(are [x y] (= (q-count x) y)
"(all-page-tags)"
{:query '[[?t :tag/name ?tag]
[?p :page/name ?tag]]
:count 3}))
(testing "page-tags queries"
(are [x y] (= (q-count x) y)
"(page-tags [[page-tag-1]])"
{:query '[[?p :page/tags ?t]
[?t :tag/name ?tag]
[(contains? #{"page-tag-1"} ?tag)]]
:count 1}
"(page-tags page-tag-2)"
{:query '[[?p :page/tags ?t]
[?t :tag/name ?tag]
[(contains? #{"page-tag-2"} ?tag)]]
:count 2}
"(page-tags page-tag-1 page-tag-2)"
{:query '[[?p :page/tags ?t]
[?t :tag/name ?tag]
[(contains? #{"page-tag-1" "page-tag-2"} ?tag)]]
:count 2}
"(page-tags page-TAG-1 page-tag-2)"
{:query '[[?p :page/tags ?t]
[?t :tag/name ?tag]
[(contains? #{"page-tag-1" "page-tag-2"} ?tag)]]
:count 2}
"(page-tags [page-tag-1 page-tag-2])"
{:query '[[?p :page/tags ?t]
[?t :tag/name ?tag]
[(contains? #{"page-tag-1" "page-tag-2"} ?tag)]]
:count 2}))
(testing "page-property queries"
(are [x y] (= (q-count x) y)
"(page-property parent [[child page 1]])"
{:query '[[?p :page/properties ?prop]
[(get ?prop :parent) ?v]
(or
[(= ?v "child page 1")]
[(contains? ?v "child page 1")])]
:count 2}
"(page-property parent \"child page 1\")"
{:query '[[?p :page/properties ?prop]
[(get ?prop :parent) ?v]
(or
[(= ?v "child page 1")]
[(contains? ?v "child page 1")])]
:count 2}
"(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
{:query '([?p :page/properties ?prop]
[(get ?prop :parent) ?v]
(or [(= ?v "child page 1")] [(contains? ?v "child page 1")])
(or [(= ?v "child page 2")] [(contains? ?v "child page 2")]))
:count 1}
"(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
{:query '(or (and
[?p :page/properties ?prop]
[(get ?prop :parent) ?v]
(or [(= ?v "child page 1")] [(contains? ?v "child page 1")]))
(and
[?p :page/properties ?prop]
[(get ?prop :parent) ?v]
(or [(= ?v "child page 2")] [(contains? ?v "child page 2")])))
:count 3}))
;; boolean queries
(testing "AND queries"
(are [x y] (= (q-count x) y)
"(and [[tag1]] [[page 2]])"
{:query '([?b :block/ref-pages [:page/name "tag1"]]
[?b :block/ref-pages [:page/name "page 2"]])
:count 1})
(are [x y] (= (q-count x) y)
"(and [[tag1]] [[page 2]])"
{:query '([?b :block/ref-pages [:page/name "tag1"]]
[?b :block/ref-pages [:page/name "page 2"]])
:count 1}))
(testing "OR queries"
(are [x y] (= (q-count x) y)
"(or [[tag1]] [[page 2]])"
{:query '(or
(and [?b :block/ref-pages [:page/name "tag1"]])
(and [?b :block/ref-pages [:page/name "page 2"]]))
:count 4}))
(testing "NOT queries"
(are [x y] (= (q-count x) y)
"(not [[page 1]])"
{:query '([?b :block/uuid]
(not
[?b :block/ref-pages [:page/name "page 1"]]))
:count 8}))
(testing "Between query"
(are [x y] (= (q-count x) y)
"(and (todo now later done) (between -7d +7d))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/page ?p]
[?p :page/journal? true]
[?p :page/journal-day ?d]
[(>= ?d 20201221)]
[(<= ?d 20210104)])
:count 5}
"(and (todo now later done) (between -7d 7d))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/page ?p]
[?p :page/journal? true]
[?p :page/journal-day ?d]
[(>= ?d 20201221)]
[(<= ?d 20210104)])
:count 5}
;; disorder
"(and (todo now later done) (between +7d -7d))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/page ?p]
[?p :page/journal? true]
[?p :page/journal-day ?d]
[(>= ?d 20201221)]
[(<= ?d 20210104)])
:count 5}
;; between with journal pages
"(and (todo now later done) (between [[Dec 27th, 2020]] [[Dec 28th, 2020]]))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/page ?p]
[?p :page/journal? true]
[?p :page/journal-day ?d]
[(>= ?d 20201227)]
[(<= ?d 20201228)])
:count 2}
;; between with created_at
"(and (todo now later done) (between created_at +7d -7d))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/properties ?p]
[(get ?p "created_at") ?v]
[(>= ?v 1608480000000)]
[(< ?v 1609689600000)])
:count 5}
;; between with last_modified_at
"(and (todo now later done) (between last_modified_at +7d -7d))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
[?b :block/properties ?p]
[(get ?p "last_modified_at") ?v]
[(>= ?v 1608480000000)]
[(< ?v 1609689600000)])
:count 5}))
(testing "Nested boolean queries"
(are [x y] (= (q-count x) y)
"(and (todo done) (not [[page 1]]))"
{:query '([?b :block/marker ?marker]
[(contains? #{"DONE"} ?marker)]
(not [?b :block/ref-pages [:page/name "page 1"]]))
:count 0})
(are [x y] (= (q-count x) y)
"(and (todo now later) (or [[page 1]] [[page 2]]))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER"} ?marker)]
(or (and [?b :block/ref-pages [:page/name "page 1"]])
(and [?b :block/ref-pages [:page/name "page 2"]])))
:count 3})
(are [x y] (= (q-count x) y)
"(not (and (todo now later) (or [[page 1]] [[page 2]])))"
{:query '([?b :block/uuid]
(not
[?b :block/marker ?marker]
[(contains? #{"NOW" "LATER"} ?marker)]
(or
(and [?b :block/ref-pages [:page/name "page 1"]])
(and [?b :block/ref-pages [:page/name "page 2"]]))))
:count 11})
;; FIXME: not working
;; (are [x y] (= (q-count x) y)
;; "(or (priority a) (not (priority a)))"
;; {:query '(or
;; (and [?b :block/priority ?priority] [(contains? #{"A"} ?priority)])
;; (and (not [?b :block/priority ?priority]
;; [(contains? #{"A"} ?priority)])))
;; :count 5})
(are [x y] (= (q-count x) y)
"(and (todo now later done) (or [[page 1]] (not [[page 1]])))"
{:query '([?b :block/marker ?marker]
[(contains? #{"NOW" "LATER" "DONE"} ?marker)]
(or
(and [?b :block/ref-pages [:page/name "page 1"]])
(and (not [?b :block/ref-pages [:page/name "page 1"]]))))
:count 5}))
(testing "sort-by (created_at defaults to desc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by created_at))")
:result
deref
(map #(get-in % [:block/properties "created_at"])))]
(is (= result
'(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
(testing "sort-by (created_at desc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by created_at desc))")
:result
deref
(map #(get-in % [:block/properties "created_at"])))]
(is (= result
'(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
(testing "sort-by (created_at asc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by created_at asc))")
:result
deref
(map #(get-in % [:block/properties "created_at"])))]
(is (= result
'(1608968448113 1608968448114 1608968448115 1609052958714 1609052959376)))))
(testing "sort-by (last_modified_at defaults to desc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by last_modified_at))")
:result
deref
(map #(get-in % [:block/properties "last_modified_at"])))]
(is (= result
'(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
(testing "sort-by (last_modified_at desc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by last_modified_at desc))")
:result
deref
(map #(get-in % [:block/properties "last_modified_at"])))]
(is (= result
'(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
(testing "sort-by (last_modified_at desc)"
(db/clear-query-state!)
(let [result (->> (q "(and (todo now later done)
(sort-by last_modified_at asc))")
:result
deref
(map #(get-in % [:block/properties "last_modified_at"])))]
(is (= result
'(1608968448113 1608968448115 1608968448120 1609052958714 1609052974285))))))
(use-fixtures :once
{:before (fn []
(config/start-test-db!)
(import-test-data!))
:after config/destroy-test-db!})
#_(cljs.test/test-ns 'frontend.db.query-dsl-test)
(comment
(require '[clojure.pprint :as pprint])
(config/start-test-db!)
(import-test-data!)
(dsl/query test-db "(all-page-tags)")
;; (or (priority a) (not (priority a)))
;; FIXME: Error: Insufficient bindings: #{?priority} not bound in [(contains? #{"A"} ?priority)]
(pprint/pprint
(d/q
'[:find (pull ?b [*])
:where
[?b :block/uuid]
(or (and [?b :block/priority ?priority] [(contains? #{"A"} ?priority)])
(not [?b :block/priority #{"A"}]
[(contains? #{"A"} ?priority)]))]
(frontend.db/get-conn test-db))))