plugins_basic_test.clj 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. (ns logseq.e2e.plugins-basic-test
  2. (:require
  3. [clojure.set :as set]
  4. [clojure.test :refer [deftest testing is use-fixtures]]
  5. [logseq.e2e.api :refer [ls-api-call!]]
  6. [logseq.e2e.assert :as assert]
  7. [logseq.e2e.fixtures :as fixtures]
  8. [logseq.e2e.keyboard :as k]
  9. [logseq.e2e.page :as page]
  10. [logseq.e2e.util :as util]
  11. [wally.main :as w]
  12. [wally.repl :as repl]))
  13. (use-fixtures :once fixtures/open-page)
  14. (use-fixtures :each fixtures/new-logseq-page)
  15. (defn ->plugin-ident
  16. [property-name]
  17. (str ":plugin.property._test_plugin/" property-name))
  18. (defonce ^:private *property-idx (atom 0))
  19. (defn- new-property
  20. []
  21. (str "p" (swap! *property-idx inc)))
  22. (defn- assert-api-ls-block!
  23. ([ret] (assert-api-ls-block! ret 1))
  24. ([ret-or-uuid count]
  25. (let [uuid' (or (get ret-or-uuid "uuid") ret-or-uuid)]
  26. (is (string? uuid'))
  27. (assert/assert-have-count (str "#ls-block-" uuid') count)
  28. uuid')))
  29. (deftest editor-apis-test
  30. (testing "editor related apis"
  31. (page/new-page "test-block-apis")
  32. (ls-api-call! :ui.showMsg "hello world" "info")
  33. (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-apis" "append-block-in-page-0")
  34. ret1 (ls-api-call! :editor.appendBlockInPage "append-block-in-current-page-0")
  35. uuid' (assert-api-ls-block! ret)]
  36. (assert-api-ls-block! ret1)
  37. (-> (ls-api-call! :editor.insertBlock uuid' "insert-0")
  38. (assert-api-ls-block!))
  39. (ls-api-call! :editor.updateBlock uuid' "append-but-updated-0")
  40. (k/esc)
  41. (w/wait-for ".block-title-wrap:text('append-but-updated-0')")
  42. (ls-api-call! :editor.removeBlock uuid')
  43. (assert-api-ls-block! uuid' 0))))
  44. (deftest block-properties-test
  45. (testing "block properties related apis"
  46. (page/new-page "test-block-properties-apis")
  47. (let [ret (ls-api-call! :editor.appendBlockInPage "test-block-properties-apis" "block-in-page-0" {:properties {:p1 1}})
  48. uuid' (assert-api-ls-block! ret)
  49. prop1 (ls-api-call! :editor.getBlockProperty uuid' "p1")
  50. props1 (ls-api-call! :editor.getBlockProperties uuid' "p1")
  51. props2 (ls-api-call! :editor.getPageProperties "test-block-properties-apis")]
  52. (w/wait-for ".property-k:text('p1')")
  53. (is (= 1 (get prop1 "value")))
  54. (is (= (get prop1 "ident") ":plugin.property._test_plugin/p1"))
  55. (is (= 1 (get props1 ":plugin.property._test_plugin/p1")))
  56. (is (= ["Page"] (get props2 ":block/tags")))
  57. (ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2")
  58. (ls-api-call! :editor.upsertBlockProperty uuid' "p3" true)
  59. (ls-api-call! :editor.upsertBlockProperty uuid' "p4" {:a 1, :b [2, 3]})
  60. (let [prop2 (ls-api-call! :editor.getBlockProperty uuid' "p2")
  61. prop3 (ls-api-call! :editor.getBlockProperty uuid' "p3")
  62. prop4 (ls-api-call! :editor.getBlockProperty uuid' "p4")]
  63. (w/wait-for ".property-k:text('p2')")
  64. (is (= "p2" (get prop2 "value")))
  65. (is (true? prop3))
  66. (is (= prop4 {"a" 1, "b" [2 3]})))
  67. (ls-api-call! :editor.removeBlockProperty uuid' "p4")
  68. ;; wait for react re-render
  69. (util/wait-timeout 16)
  70. (is (nil? (w/find-one-by-text ".property-k" "p4")))
  71. (ls-api-call! :editor.upsertBlockProperty uuid' "p3" false)
  72. (ls-api-call! :editor.upsertBlockProperty uuid' "p2" "p2-updated")
  73. (w/wait-for ".block-title-wrap:text('p2-updated')")
  74. (let [props (ls-api-call! :editor.getBlockProperties uuid')]
  75. (is (= (get props ":plugin.property._test_plugin/p3") false))
  76. (is (= (get props ":plugin.property._test_plugin/p2") "p2-updated"))))))
  77. (deftest property-upsert-test
  78. (testing "property with default settings"
  79. (let [p (new-property)]
  80. (ls-api-call! :editor.upsertProperty p)
  81. (let [property (ls-api-call! :editor.getProperty p)]
  82. (is (= "default" (get property "type")))
  83. (is (= ":db.cardinality/one" (get property "cardinality"))))))
  84. (testing "property with specified cardinality && type"
  85. (let [p (new-property)]
  86. (ls-api-call! :editor.upsertProperty p {:type "number"
  87. :cardinality "one"})
  88. (let [property (ls-api-call! :editor.getProperty p)]
  89. (is (= "number" (get property "type")))
  90. (is (= ":db.cardinality/one" (get property "cardinality")))))
  91. (let [p (new-property)]
  92. (ls-api-call! :editor.upsertProperty p {:type "number"
  93. :cardinality "many"})
  94. (let [property (ls-api-call! :editor.getProperty p)]
  95. (is (= "number" (get property "type")))
  96. (is (= ":db.cardinality/many" (get property "cardinality"))))
  97. (ls-api-call! :editor.upsertProperty p {:type "default"})
  98. (let [property (ls-api-call! :editor.getProperty p)]
  99. (is (= "default" (get property "type"))))))
  100. ;; TODO: How to test against eval-js errors on playwright?
  101. #_(testing ":checkbox property doesn't allow :many cardinality"
  102. (let [p (new-property)]
  103. (ls-api-call! :editor.upsertProperty p {:type "checkbox"
  104. :cardinality "many"}))))
  105. (deftest property-related-test
  106. (testing "properties management related apis"
  107. (dorun
  108. (map-indexed
  109. (fn [idx property-type]
  110. (let [property-name (str "p" idx)
  111. _ (ls-api-call! :editor.upsertProperty property-name {:type property-type})
  112. property (ls-api-call! :editor.getProperty property-name)]
  113. (is (= (get property "ident") (str ":plugin.property._test_plugin/" property-name)))
  114. (is (= (get property "type") property-type))
  115. (ls-api-call! :editor.removeProperty property-name)
  116. (is (nil? (ls-api-call! :editor.getProperty property-name)))))
  117. ["default" "number" "date" "datetime" "checkbox" "url" "node" "json" "string"]))))
  118. (deftest insert-block-with-properties
  119. (testing "insert block with properties"
  120. (let [page "insert-block-properties-test"
  121. _ (page/new-page page)
  122. ;; :checkbox, :number, :url, :json can be inferred and default to :default, but not for :page
  123. b1 (ls-api-call! :editor.insertBlock page "b1" {:properties {"x1" true
  124. "x2" "https://logseq.com"
  125. "x3" 1
  126. "x4" [1]
  127. "x5" {:foo "bar"}
  128. "x6" "Page x"
  129. "x7" ["Page y" "Page z"]
  130. "x8" "some content"}
  131. :schema {"x6" {:type "page"}
  132. "x7" {:type "page"}}})]
  133. (is (true? (get b1 (->plugin-ident "x1"))))
  134. (is (= "https://logseq.com" (-> (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x2")))
  135. (get "title"))))
  136. (is (= 1 (-> (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x3")))
  137. (get ":logseq.property/value"))))
  138. (is (= 1 (-> (ls-api-call! :editor.getBlock (first (get b1 (->plugin-ident "x4"))))
  139. (get ":logseq.property/value"))))
  140. (is (= "{\"foo\":\"bar\"}" (get b1 (->plugin-ident "x5"))))
  141. (let [page-x (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x6")))]
  142. (is (= "page x" (get page-x "name"))))
  143. (is (= ["page y" "page z"] (map #(-> (ls-api-call! :editor.getBlock %)
  144. (get "name")) (get b1 (->plugin-ident "x7")))))
  145. (let [x8-block-value (ls-api-call! :editor.getBlock (get b1 (->plugin-ident "x8")))]
  146. (is (= "some content" (get x8-block-value "title")))
  147. (is (some? (get x8-block-value "page")))))))
  148. (deftest update-block-with-properties
  149. (testing "update block with properties"
  150. (let [page "update-block-properties-test"
  151. _ (page/new-page page)
  152. block (ls-api-call! :editor.insertBlock page "b1")
  153. _ (ls-api-call! :editor.updateBlock (get block "uuid")
  154. "b1-new-content"
  155. {:properties {"y1" true
  156. "y2" "https://logseq.com"
  157. "y3" 1
  158. "y4" [1]
  159. "y5" {:foo "bar"}
  160. "y6" "Page x"
  161. "y7" ["Page y" "Page z"]
  162. "y8" "some content"}
  163. :schema {"y6" {:type "page"}
  164. "y7" {:type "page"}}})
  165. b1 (ls-api-call! :editor.getBlock (get block "uuid"))]
  166. (is (true? (get b1 (->plugin-ident "y1"))))
  167. (is (= "https://logseq.com" (-> (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y2") "id"]))
  168. (get "title"))))
  169. (is (= 1 (-> (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y3") "id"]))
  170. (get ":logseq.property/value"))))
  171. (is (= 1 (-> (ls-api-call! :editor.getBlock (get (first (get b1 (->plugin-ident "y4"))) "id"))
  172. (get ":logseq.property/value"))))
  173. (is (= "{\"foo\":\"bar\"}" (get b1 (->plugin-ident "y5"))))
  174. (let [page-x (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y6") "id"]))]
  175. (is (= "page x" (get page-x "name"))))
  176. (is (= ["page y" "page z"] (map #(-> (ls-api-call! :editor.getBlock %)
  177. (get "name"))
  178. (map #(get % "id") (get b1 (->plugin-ident "y7"))))))
  179. (let [y8-block-value (ls-api-call! :editor.getBlock (get-in b1 [(->plugin-ident "y8") "id"]))]
  180. (is (= "some content" (get y8-block-value "title")))
  181. (is (some? (get y8-block-value "page")))))))
  182. (deftest insert-batch-blocks-test
  183. (testing "insert batch blocks"
  184. (let [page "insert batch blocks"
  185. _ (page/new-page page)
  186. page-uuid (get (ls-api-call! :editor.getBlock page) "uuid")
  187. result (ls-api-call! :editor.insertBatchBlock page-uuid
  188. [{:content "b1"
  189. :children [{:content "b1.1"
  190. :children [{:content "b1.1.1"}
  191. {:content "b1.1.2"}]}
  192. {:content "b1.2"}]}
  193. {:content "b2"}])
  194. contents (util/get-page-blocks-contents)]
  195. (is (= contents ["b1" "b1.1" "b1.1.1" "b1.1.2" "b1.2" "b2"]))
  196. (is (= (map #(get % "title") result) ["b1" "b1.1" "b1.1.1" "b1.1.2" "b1.2" "b2"]))))
  197. (testing "insert batch blocks with properties"
  198. (let [page "insert batch blocks with properties"
  199. _ (page/new-page page)
  200. page-uuid (get (ls-api-call! :editor.getBlock page) "uuid")
  201. result (ls-api-call! :editor.insertBatchBlock page-uuid
  202. [{:content "b1"
  203. :children [{:content "b1.1"
  204. :children [{:content "b1.1.1"
  205. :properties {"z3" "Page 1"
  206. "z4" ["Page 2" "Page 3"]}}
  207. {:content "b1.1.2"}]}
  208. {:content "b1.2"}]
  209. :properties {"z1" "test"
  210. "z2" true}}
  211. {:content "b2"}]
  212. {:schema {"z3" "page"
  213. "z4" "page"}})
  214. contents (util/get-page-blocks-contents)]
  215. (is (= contents
  216. ["b1" "test" "b1.1" "b1.1.1" "Page 1" "Page 2" "Page 3" "b1.1.2" "b1.2" "b2"]))
  217. (is (true? (get (first result) (->plugin-ident "z2")))))))
  218. (deftest create-page-test
  219. (testing "create page"
  220. (let [result (ls-api-call! :editor.createPage "Test page 1")]
  221. (is (= "Test page 1" (get result "title")))
  222. (is
  223. (=
  224. ":logseq.class/Page"
  225. (-> (ls-api-call! :editor.getBlock (first (get result "tags")))
  226. (get "ident"))))))
  227. (testing "create page with properties"
  228. (let [result (ls-api-call! :editor.createPage "Test page 2"
  229. {:px1 "test"
  230. :px2 1
  231. :px3 "Page 1"
  232. :px4 ["Page 2" "Page 3"]}
  233. {:schema {:px3 {:type "page"}
  234. :px4 {:type "page"}}})
  235. page (ls-api-call! :editor.getBlock "Test page 2")]
  236. (is (= "Test page 2" (get result "title")))
  237. (is
  238. (=
  239. ":logseq.class/Page"
  240. (-> (ls-api-call! :editor.getBlock (first (get result "tags")))
  241. (get "ident"))))
  242. ;; verify properties
  243. (is (= "test" (-> (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px1") "id"]))
  244. (get "title"))))
  245. (is (= 1 (-> (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px2") "id"]))
  246. (get ":logseq.property/value"))))
  247. (let [page-1 (ls-api-call! :editor.getBlock (get-in page [(->plugin-ident "px3") "id"]))]
  248. (is (= "page 1" (get page-1 "name"))))
  249. (is (= ["page 2" "page 3"] (map #(-> (ls-api-call! :editor.getBlock %)
  250. (get "name"))
  251. (map #(get % "id") (get page (->plugin-ident "px4"))))))))
  252. (testing "create tag page"
  253. (let [result (ls-api-call! :editor.createPage "Tag new"
  254. {}
  255. {:class true})]
  256. (is
  257. (=
  258. ":logseq.class/Tag"
  259. (-> (ls-api-call! :editor.getBlock (first (get result "tags")))
  260. (get "ident")))))))
  261. (deftest get-all-tags-test
  262. (testing "get_all_tags"
  263. (let [result (ls-api-call! :editor.get_all_tags)
  264. built-in-tags #{":logseq.class/Template"
  265. ":logseq.class/Query"
  266. ":logseq.class/Math-block"
  267. ":logseq.class/Task"
  268. ":logseq.class/Code-block"
  269. ":logseq.class/Card"
  270. ":logseq.class/Quote-block"
  271. ":logseq.class/Cards"}]
  272. (is (set/subset? built-in-tags (set (map #(get % "ident") result)))))))
  273. (deftest get-all-properties-test
  274. (testing "get_all_properties"
  275. (let [result (ls-api-call! :editor.get_all_properties)]
  276. (is (>= (count result) 94)))))
  277. (deftest get-tag-objects-test
  278. (testing "get_tag_objects"
  279. (let [page "tag objects test"
  280. _ (page/new-page page)
  281. _ (ls-api-call! :editor.insertBlock page "task 1"
  282. {:properties {"logseq.property/status" "Doing"}})
  283. result (ls-api-call! :editor.get_tag_objects "logseq.class/Task")]
  284. (is (= (count result) 1))
  285. (is (= "task 1" (get (first result) "title"))))))
  286. (deftest create-and-get-tag-test
  287. (testing "create and get tag with title or ident"
  288. (let [title "book1"
  289. title-ident (str :plugin.class._test_plugin/book1)
  290. tag1 (ls-api-call! :editor.createTag title)
  291. tag2 (ls-api-call! :editor.getTag title)
  292. tag3 (ls-api-call! :editor.getTag title-ident)
  293. tag4 (ls-api-call! :editor.getTag (get tag1 "uuid"))]
  294. (is (= (get tag1 "ident") title-ident) "create tag with title from test as plugin")
  295. (is (= (get tag2 "ident") title-ident) "get tag with title")
  296. (is (= (get tag3 "title") title) "get tag with ident")
  297. (is (= (get tag4 "title") title) "get tag with uuid")))
  298. (testing "create tag with tagProperties"
  299. (let [tag-props [{:name "prop1"}
  300. {:name "prop2" :schema {:type "number"}}
  301. {:name "prop3" :schema {:type "checkbox"}}]
  302. tag (ls-api-call! :editor.createTag "tag-with-props" {:tagProperties tag-props})
  303. props (get tag ":logseq.property.class/properties")]
  304. (is (= 3 (count props)) "tag has 3 properties")))
  305. (testing "add and remove tag extends"
  306. (let [tag1 (ls-api-call! :editor.createTag "tag1")
  307. tag2 (ls-api-call! :editor.createTag "tag2")
  308. tag3 (ls-api-call! :editor.createTag "tag3")
  309. id1 (get tag1 "id")
  310. id2 (get tag2 "id")
  311. id3 (get tag3 "id")
  312. _ (ls-api-call! :editor.addTagExtends id1 id2)
  313. tag1 (ls-api-call! :editor.getTag id1)]
  314. (is (= (get tag1 ":logseq.property.class/extends") [id2]) "tag1 extends tag2 with db id")
  315. (let [_ (ls-api-call! :editor.addTagExtends id1 id3)
  316. tag1 (ls-api-call! :editor.getTag id1)]
  317. (is (= (get tag1 ":logseq.property.class/extends") [id2 id3]) "tag1 extends tag2,tag3 with db ids")))))
  318. (deftest get-tags-by-name-test
  319. (testing "get tags by exact name"
  320. (let [tag-name "product"
  321. tag1 (ls-api-call! :editor.createTag tag-name)
  322. result (ls-api-call! :editor.getTagsByName tag-name)]
  323. (is (= 1 (count result)) "should return exactly one tag")
  324. (is (= (get tag1 "uuid") (get (first result) "uuid")) "should return the created tag")
  325. (is (= (get tag1 "title") (get (first result) "title")) "tag title should match")))
  326. (testing "get tags by name is case-insensitive"
  327. (let [tag-name "TestTag123"
  328. _ (ls-api-call! :editor.createTag tag-name)
  329. result-lower (ls-api-call! :editor.getTagsByName "testtag123")
  330. result-upper (ls-api-call! :editor.getTagsByName "TESTTAG123")
  331. result-mixed (ls-api-call! :editor.getTagsByName "TeStTaG123")]
  332. (is (= 1 (count result-lower)) "should find tag with lowercase search")
  333. (is (= 1 (count result-upper)) "should find tag with uppercase search")
  334. (is (= 1 (count result-mixed)) "should find tag with mixed case search")
  335. (is (= (get (first result-lower) "uuid") (get (first result-upper) "uuid")) "all searches should return same tag")
  336. (is (= (get (first result-lower) "uuid") (get (first result-mixed) "uuid")) "all searches should return same tag")))
  337. (testing "get tags by name returns empty array for non-existent tag"
  338. (let [result (ls-api-call! :editor.getTagsByName "NonExistentTag12345")]
  339. (is (empty? result) "should return empty array for non-existent tag")))
  340. (testing "get tags by name filters out non-tag pages"
  341. (let [page-name "regular-page"
  342. _ (page/new-page page-name)
  343. result (ls-api-call! :editor.getTagsByName page-name)]
  344. (is (empty? result) "should not return regular pages, only tags")))
  345. (testing "get tags by name with multiple tags having similar names"
  346. (let [_tag1 (ls-api-call! :editor.createTag "category")
  347. _tag2 (ls-api-call! :editor.createTag "Category")
  348. result (ls-api-call! :editor.getTagsByName "category")]
  349. ;; Due to case-insensitive name normalization, both tags should be the same
  350. (is (>= (count result) 1) "should return at least one tag")
  351. ;; Verify the result contains valid tag structure
  352. (is (string? (get (first result) "uuid")) "returned tag should have uuid")
  353. (is (string? (get (first result) "title")) "returned tag should have title"))))