editor_test.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. (ns frontend.handler.editor-test
  2. (:require [frontend.handler.editor :as editor]
  3. [frontend.db :as db]
  4. [clojure.test :refer [deftest is testing are use-fixtures]]
  5. [datascript.core :as d]
  6. [frontend.test.helper :as test-helper :refer [load-test-files]]
  7. [frontend.db.model :as model]
  8. [frontend.state :as state]
  9. [frontend.util.cursor :as cursor]))
  10. (use-fixtures :each test-helper/start-and-destroy-db)
  11. (deftest extract-nearest-link-from-text-test
  12. (testing "Page, block and tag links"
  13. (is (= "page1"
  14. (editor/extract-nearest-link-from-text "[[page1]] [[page2]]" 0))
  15. "Finds first page link correctly based on cursor position")
  16. (is (= "page2"
  17. (editor/extract-nearest-link-from-text "[[page1]] [[page2]]" 10))
  18. "Finds second page link correctly based on cursor position")
  19. (is (= "tag"
  20. (editor/extract-nearest-link-from-text "#tag [[page1]]" 3))
  21. "Finds tag correctly")
  22. (is (= "61e057b9-f799-4532-9258-cfef6ce58370"
  23. (editor/extract-nearest-link-from-text
  24. "((61e057b9-f799-4532-9258-cfef6ce58370)) #todo" 5))
  25. "Finds block correctly"))
  26. (testing "Url links"
  27. (is (= "https://github.com/logseq/logseq"
  28. (editor/extract-nearest-link-from-text
  29. "https://github.com/logseq/logseq is #awesome :)" 0 editor/url-regex))
  30. "Finds url correctly")
  31. (is (not= "https://github.com/logseq/logseq"
  32. (editor/extract-nearest-link-from-text
  33. "https://github.com/logseq/logseq is #awesome :)" 0))
  34. "Doesn't find url if regex not passed")
  35. (is (= "https://github.com/logseq/logseq"
  36. (editor/extract-nearest-link-from-text
  37. "[logseq](https://github.com/logseq/logseq) is #awesome :)" 0 editor/url-regex))
  38. "Finds url in markdown link correctly"))
  39. (is (= "https://github.com/logseq/logseq"
  40. (editor/extract-nearest-link-from-text
  41. "[[https://github.com/logseq/logseq][logseq]] is #awesome :)" 0 editor/url-regex))
  42. "Finds url in org link correctly"))
  43. (defn- set-marker
  44. "Spied version of editor/set-marker"
  45. [marker content format]
  46. (let [actual-content (atom nil)]
  47. (with-redefs [editor/save-block-if-changed! (fn [_ content]
  48. (reset! actual-content content))]
  49. (editor/set-marker {:block/marker marker :block/content content :block/format format})
  50. @actual-content)))
  51. (deftest set-marker-org
  52. (are [marker content expect] (= expect (set-marker marker content :org))
  53. "TODO" "TODO content" "DOING content"
  54. "TODO" "** TODO content" "** DOING content"
  55. "TODO" "## TODO content" "DOING ## TODO content"
  56. "DONE" "DONE content" "content"))
  57. (deftest set-marker-markdown
  58. (are [marker content expect] (= expect (set-marker marker content :markdown))
  59. "TODO" "TODO content" "DOING content"
  60. "TODO" "## TODO content" "## DOING content"
  61. "DONE" "DONE content" "content"))
  62. (defn- keyup-handler
  63. "Spied version of editor/keyup-handler"
  64. [{:keys [value cursor-pos action commands]
  65. ;; Default to some commands matching which matches default behavior for most
  66. ;; completion scenarios
  67. :or {commands [:fake-command]}}]
  68. ;; Reset editor action in order to test result
  69. (state/set-editor-action! action)
  70. ;; Default cursor pos to end of line
  71. (let [pos (or cursor-pos (count value))
  72. input #js {:value value}]
  73. (with-redefs [editor/get-matched-commands (constantly commands)
  74. ;; Ignore as none of its behaviors are tested
  75. editor/default-case-for-keyup-handler (constantly nil)
  76. cursor/pos (constantly pos)]
  77. ((editor/keyup-handler nil input nil)
  78. #js {:key (subs value (dec (count value)))}
  79. nil))))
  80. (deftest keyup-handler-test
  81. (testing "Command autocompletion"
  82. (keyup-handler {:value "/b"
  83. :action :commands
  84. :commands [:fake-command]})
  85. (is (= :commands (state/get-editor-action))
  86. "Completion stays open if there is a matching command")
  87. (keyup-handler {:value "/zz"
  88. :action :commands
  89. :commands []})
  90. (is (= nil (state/get-editor-action))
  91. "Completion closed if there no matching commands")
  92. (keyup-handler {:value "/ " :action :commands})
  93. (is (= nil (state/get-editor-action))
  94. "Completion closed after a space follows /")
  95. (keyup-handler {:value "/block " :action :commands})
  96. (is (= :commands (state/get-editor-action))
  97. "Completion stays open if space is part of the search term for /"))
  98. (testing "Tag autocompletion"
  99. (keyup-handler {:value "foo #b" :action :page-search-hashtag})
  100. (is (= :page-search-hashtag (state/get-editor-action))
  101. "Completion stays open for one tag")
  102. (keyup-handler {:value "text # #bar"
  103. :action :page-search-hashtag
  104. :cursor-pos 6})
  105. (is (= :page-search-hashtag (state/get-editor-action))
  106. "Completion stays open when typing tag before another tag"))
  107. ;; Reset state
  108. (state/set-editor-action! nil))
  109. (defn- handle-last-input-handler
  110. "Spied version of editor/handle-last-input"
  111. [{:keys [value cursor-pos]}]
  112. ;; Reset editor action in order to test result
  113. (state/set-editor-action! nil)
  114. ;; Default cursor pos to end of line
  115. (let [pos (or cursor-pos (count value))]
  116. (with-redefs [state/get-input (constantly #js {:value value})
  117. cursor/pos (constantly pos)
  118. cursor/move-cursor-backward (constantly nil) ;; ignore if called
  119. cursor/get-caret-pos (constantly {})]
  120. (editor/handle-last-input))))
  121. (deftest handle-last-input-handler-test
  122. (testing "Property autocompletion"
  123. (handle-last-input-handler {:value "::"})
  124. (is (= :property-search (state/get-editor-action))
  125. "Autocomplete properties if only colons have been typed")
  126. (handle-last-input-handler {:value "foo::bar\n::"})
  127. (is (= :property-search (state/get-editor-action))
  128. "Autocomplete properties if typing colons on a second line")
  129. (handle-last-input-handler {:value "middle of line::"})
  130. (is (= nil (state/get-editor-action))
  131. "Don't autocomplete properties if typing colons in the middle of a line")
  132. (handle-last-input-handler {:value "first \nfoo::bar"
  133. :cursor-pos (dec (count "first "))})
  134. (is (= nil (state/get-editor-action))
  135. "Don't autocomplete properties if typing in a block where properties already exist"))
  136. (testing "Command autocompletion"
  137. (handle-last-input-handler {:value "/"})
  138. (is (= :commands (state/get-editor-action))
  139. "Command search if only / has been typed")
  140. (handle-last-input-handler {:value "some words /"})
  141. (is (= :commands (state/get-editor-action))
  142. "Command search on start of new word")
  143. (handle-last-input-handler {:value "a line\n/"})
  144. (is (= :commands (state/get-editor-action))
  145. "Command search on start of a new line")
  146. (handle-last-input-handler {:value "https://"})
  147. (is (= nil (state/get-editor-action))
  148. "No command search in middle of a word")
  149. (handle-last-input-handler {:value "#blah/"})
  150. (is (= nil (state/get-editor-action))
  151. "No command search after a tag search to allow for namespace completion"))
  152. (testing "Tag autocompletion"
  153. (handle-last-input-handler {:value "#"
  154. :cursor-pos 1})
  155. (is (= :page-search-hashtag (state/get-editor-action))
  156. "Page search if only hashtag has been typed")
  157. (handle-last-input-handler {:value "foo #"
  158. :cursor-pos 5})
  159. (is (= :page-search-hashtag (state/get-editor-action))
  160. "Page search if hashtag has been typed at EOL")
  161. (handle-last-input-handler {:value "#Some words"
  162. :cursor-pos 1})
  163. (is (= :page-search-hashtag (state/get-editor-action))
  164. "Page search if hashtag is at start of line and there are existing words")
  165. (handle-last-input-handler {:value "foo #"
  166. :cursor-pos 5})
  167. (is (= :page-search-hashtag (state/get-editor-action))
  168. "Page search if hashtag is at EOL and after a space")
  169. (handle-last-input-handler {:value "foo #bar"
  170. :cursor-pos 5})
  171. (is (= :page-search-hashtag (state/get-editor-action))
  172. "Page search if hashtag is in middle of line and after a space")
  173. (handle-last-input-handler {:value "String#" :cursor-pos 7})
  174. (is (= nil (state/get-editor-action))
  175. "No page search if hashtag has been typed at end of a word")
  176. (handle-last-input-handler {:value "foo#bar" :cursor-pos 4})
  177. (is (= nil (state/get-editor-action))
  178. "No page search if hashtag is in middle of word")
  179. (handle-last-input-handler {:value "`String#gsub and String#`"
  180. :cursor-pos (dec (count "`String#gsub and String#`"))})
  181. (is (= nil (state/get-editor-action))
  182. "No page search within backticks"))
  183. ;; Reset state
  184. (state/set-editor-action! nil))
  185. (deftest save-block-aux!
  186. (load-test-files [{:file/path "pages/page1.md"
  187. :file/content "\n
  188. - b1 #foo"}])
  189. (testing "updating block's content changes content and preserves path-refs"
  190. (let [conn (db/get-db test-helper/test-db false)
  191. block (->> (d/q '[:find (pull ?b [* {:block/path-refs [:block/name]}])
  192. :where [?b :block/content "b1 #foo"]]
  193. @conn)
  194. ffirst)
  195. prev-path-refs (set (map :block/name (:block/path-refs block)))
  196. _ (assert (= #{"page1" "foo"} prev-path-refs)
  197. "block has expected :block/path-refs")
  198. ;; Use same options as edit-box-on-change!
  199. _ (editor/save-block-aux! block "b12 #foo" {:skip-properties? true})
  200. updated-block (d/pull @conn '[* {:block/path-refs [:block/name]}] [:block/uuid (:block/uuid block)])]
  201. (is (= "b12 #foo" (:block/content updated-block)) "Content updated correctly")
  202. (is (= prev-path-refs
  203. (set (map :block/name (:block/path-refs updated-block))))
  204. "Path-refs remain the same"))))
  205. (deftest save-block!
  206. (testing "Saving blocks with and without properties"
  207. (test-helper/load-test-files [{:file/path "foo.md"
  208. :file/content "# foo"}])
  209. (let [repo test-helper/test-db
  210. block-uuid (:block/uuid (model/get-block-by-page-name-and-block-route-name repo "foo" "foo"))]
  211. (editor/save-block! repo block-uuid "# bar")
  212. (is (= "# bar" (:block/content (model/query-block-by-uuid block-uuid))))
  213. (editor/save-block! repo block-uuid "# foo" {:properties {:foo "bar"}})
  214. (is (= "# foo\nfoo:: bar" (:block/content (model/query-block-by-uuid block-uuid))))
  215. (editor/save-block! repo block-uuid "# bar")
  216. (is (= "# bar" (:block/content (model/query-block-by-uuid block-uuid)))))))