Files
logseq/src/test/frontend/handler/editor_test.cljs
2023-05-17 11:13:19 -04:00

265 lines
11 KiB
Clojure

(ns frontend.handler.editor-test
(:require [frontend.handler.editor :as editor]
[frontend.db :as db]
[clojure.test :refer [deftest is testing are use-fixtures]]
[datascript.core :as d]
[frontend.test.helper :as test-helper :refer [load-test-files]]
[frontend.db.model :as model]
[frontend.state :as state]
[frontend.util.cursor :as cursor]))
(use-fixtures :each {:before (fn []
;; Set current-repo explicitly since it's not the default
(state/set-current-repo! test-helper/test-db)
(test-helper/start-test-db!))
:after (fn []
(state/set-current-repo! nil)
(test-helper/destroy-test-db!))})
(deftest extract-nearest-link-from-text-test
(testing "Page, block and tag links"
(is (= "page1"
(editor/extract-nearest-link-from-text "[[page1]] [[page2]]" 0))
"Finds first page link correctly based on cursor position")
(is (= "page2"
(editor/extract-nearest-link-from-text "[[page1]] [[page2]]" 10))
"Finds second page link correctly based on cursor position")
(is (= "tag"
(editor/extract-nearest-link-from-text "#tag [[page1]]" 3))
"Finds tag correctly")
(is (= "61e057b9-f799-4532-9258-cfef6ce58370"
(editor/extract-nearest-link-from-text
"((61e057b9-f799-4532-9258-cfef6ce58370)) #todo" 5))
"Finds block correctly"))
(testing "Url links"
(is (= "https://github.com/logseq/logseq"
(editor/extract-nearest-link-from-text
"https://github.com/logseq/logseq is #awesome :)" 0 editor/url-regex))
"Finds url correctly")
(is (not= "https://github.com/logseq/logseq"
(editor/extract-nearest-link-from-text
"https://github.com/logseq/logseq is #awesome :)" 0))
"Doesn't find url if regex not passed")
(is (= "https://github.com/logseq/logseq"
(editor/extract-nearest-link-from-text
"[logseq](https://github.com/logseq/logseq) is #awesome :)" 0 editor/url-regex))
"Finds url in markdown link correctly"))
(is (= "https://github.com/logseq/logseq"
(editor/extract-nearest-link-from-text
"[[https://github.com/logseq/logseq][logseq]] is #awesome :)" 0 editor/url-regex))
"Finds url in org link correctly"))
(defn- set-marker
"Spied version of editor/set-marker"
[marker content format]
(let [actual-content (atom nil)]
(with-redefs [editor/save-block-if-changed! (fn [_ content]
(reset! actual-content content))]
(editor/set-marker {:block/marker marker :block/content content :block/format format})
@actual-content)))
(deftest set-marker-org
(are [marker content expect] (= expect (set-marker marker content :org))
"TODO" "TODO content" "DOING content"
"TODO" "** TODO content" "** DOING content"
"TODO" "## TODO content" "DOING ## TODO content"
"DONE" "DONE content" "content"))
(deftest set-marker-markdown
(are [marker content expect] (= expect (set-marker marker content :markdown))
"TODO" "TODO content" "DOING content"
"TODO" "## TODO content" "## DOING content"
"DONE" "DONE content" "content"))
(defn- keyup-handler
"Spied version of editor/keyup-handler"
[{:keys [value cursor-pos action commands]
;; Default to some commands matching which matches default behavior for most
;; completion scenarios
:or {commands [:fake-command]}}]
;; Reset editor action in order to test result
(state/set-editor-action! action)
;; Default cursor pos to end of line
(let [pos (or cursor-pos (count value))
input #js {:value value}]
(with-redefs [editor/get-matched-commands (constantly commands)
;; Ignore as none of its behaviors are tested
editor/default-case-for-keyup-handler (constantly nil)
cursor/pos (constantly pos)]
((editor/keyup-handler nil input nil)
#js {:key (subs value (dec (count value)))}
nil))))
(deftest keyup-handler-test
(testing "Command autocompletion"
(keyup-handler {:value "/b"
:action :commands
:commands [:fake-command]})
(is (= :commands (state/get-editor-action))
"Completion stays open if there is a matching command")
(keyup-handler {:value "/zz"
:action :commands
:commands []})
(is (= nil (state/get-editor-action))
"Completion closed if there no matching commands")
(keyup-handler {:value "/ " :action :commands})
(is (= nil (state/get-editor-action))
"Completion closed after a space follows /")
(keyup-handler {:value "/block " :action :commands})
(is (= :commands (state/get-editor-action))
"Completion stays open if space is part of the search term for /"))
(testing "Tag autocompletion"
(keyup-handler {:value "foo #b" :action :page-search-hashtag})
(is (= :page-search-hashtag (state/get-editor-action))
"Completion stays open for one tag")
(keyup-handler {:value "text # #bar"
:action :page-search-hashtag
:cursor-pos 6})
(is (= :page-search-hashtag (state/get-editor-action))
"Completion stays open when typing tag before another tag"))
;; Reset state
(state/set-editor-action! nil))
(defn- handle-last-input-handler
"Spied version of editor/handle-last-input"
[{:keys [value cursor-pos]}]
;; Reset editor action in order to test result
(state/set-editor-action! nil)
;; Default cursor pos to end of line
(let [pos (or cursor-pos (count value))]
(with-redefs [state/get-input (constantly #js {:value value})
cursor/pos (constantly pos)
cursor/move-cursor-backward (constantly nil) ;; ignore if called
cursor/get-caret-pos (constantly {})]
(editor/handle-last-input))))
(deftest handle-last-input-handler-test
(testing "Property autocompletion"
(handle-last-input-handler {:value "::"})
(is (= :property-search (state/get-editor-action))
"Autocomplete properties if only colons have been typed")
(handle-last-input-handler {:value "foo::bar\n::"})
(is (= :property-search (state/get-editor-action))
"Autocomplete properties if typing colons on a second line")
(handle-last-input-handler {:value "middle of line::"})
(is (= nil (state/get-editor-action))
"Don't autocomplete properties if typing colons in the middle of a line")
(handle-last-input-handler {:value "first \nfoo::bar"
:cursor-pos (dec (count "first "))})
(is (= nil (state/get-editor-action))
"Don't autocomplete properties if typing in a block where properties already exist"))
(testing "Command autocompletion"
(handle-last-input-handler {:value "/"})
(is (= :commands (state/get-editor-action))
"Command search if only / has been typed")
(handle-last-input-handler {:value "some words /"})
(is (= :commands (state/get-editor-action))
"Command search on start of new word")
(handle-last-input-handler {:value "a line\n/"})
(is (= :commands (state/get-editor-action))
"Command search on start of a new line")
(handle-last-input-handler {:value "https://"})
(is (= nil (state/get-editor-action))
"No command search in middle of a word")
(handle-last-input-handler {:value "#blah/"})
(is (= nil (state/get-editor-action))
"No command search after a tag search to allow for namespace completion"))
(testing "Tag autocompletion"
(handle-last-input-handler {:value "#"
:cursor-pos 1})
(is (= :page-search-hashtag (state/get-editor-action))
"Page search if only hashtag has been typed")
(handle-last-input-handler {:value "foo #"
:cursor-pos 5})
(is (= :page-search-hashtag (state/get-editor-action))
"Page search if hashtag has been typed at EOL")
(handle-last-input-handler {:value "#Some words"
:cursor-pos 1})
(is (= :page-search-hashtag (state/get-editor-action))
"Page search if hashtag is at start of line and there are existing words")
(handle-last-input-handler {:value "foo #"
:cursor-pos 5})
(is (= :page-search-hashtag (state/get-editor-action))
"Page search if hashtag is at EOL and after a space")
(handle-last-input-handler {:value "foo #bar"
:cursor-pos 5})
(is (= :page-search-hashtag (state/get-editor-action))
"Page search if hashtag is in middle of line and after a space")
(handle-last-input-handler {:value "String#" :cursor-pos 7})
(is (= nil (state/get-editor-action))
"No page search if hashtag has been typed at end of a word")
(handle-last-input-handler {:value "foo#bar" :cursor-pos 4})
(is (= nil (state/get-editor-action))
"No page search if hashtag is in middle of word")
(handle-last-input-handler {:value "`String#gsub and String#`"
:cursor-pos (dec (count "`String#gsub and String#`"))})
(is (= nil (state/get-editor-action))
"No page search within backticks"))
;; Reset state
(state/set-editor-action! nil))
(deftest save-block-aux!
(load-test-files [{:file/path "pages/page1.md"
:file/content "\n
- b1 #foo"}])
(testing "updating block's content changes content and preserves path-refs"
(let [conn (db/get-db test-helper/test-db false)
block (->> (d/q '[:find (pull ?b [* {:block/path-refs [:block/name]}])
:where [?b :block/content "b1 #foo"]]
@conn)
ffirst)
prev-path-refs (set (map :block/name (:block/path-refs block)))
_ (assert (= #{"page1" "foo"} prev-path-refs)
"block has expected :block/path-refs")
;; Use same options as edit-box-on-change!
_ (editor/save-block-aux! block "b12 #foo" {:skip-properties? true})
updated-block (d/pull @conn '[* {:block/path-refs [:block/name]}] [:block/uuid (:block/uuid block)])]
(is (= "b12 #foo" (:block/content updated-block)) "Content updated correctly")
(is (= prev-path-refs
(set (map :block/name (:block/path-refs updated-block))))
"Path-refs remain the same"))))
(deftest save-block
(testing "Saving blocks"
(test-helper/load-test-files [{:file/path "foo.md"
:file/content "# foo"}])
(let [repo test-helper/test-db
block-uuid (:block/uuid (model/get-block-by-page-name-and-block-route-name repo "foo" "foo"))
_ (let [_ (editor/save-block! repo block-uuid "# bar")
block (model/query-block-by-uuid block-uuid)
_ (is (= "# bar" (:block/content block)))])
_ (let [_ (editor/save-block! repo block-uuid "# foo" {:properties {:foo "bar"}})
block (model/query-block-by-uuid block-uuid)
_ (is (= "# foo\nfoo:: bar" (:block/content block)))])
_ (let [_ (editor/save-block! repo block-uuid "# bar")
block (model/query-block-by-uuid block-uuid)
_ (is (= "# bar" (:block/content block)))])])))