(ns logseq.e2e.commands-basic-test (:require [clojure.string :as string] [clojure.test :refer [deftest testing is use-fixtures]] [jsonista.core :as json] [logseq.e2e.api :refer [ls-api-call!]] [logseq.e2e.assert :as assert] [logseq.e2e.block :as b] [logseq.e2e.fixtures :as fixtures] [logseq.e2e.keyboard :as k] [logseq.e2e.locator :as loc] [logseq.e2e.util :as util] [wally.main :as w])) (use-fixtures :once fixtures/open-page) (use-fixtures :each fixtures/new-logseq-page fixtures/validate-graph) (def ^:private date-picker-day-selector ".ui__calendar [role='gridcell'] button, .ui__calendar button[role='gridcell']") (defn- focused-date-picker-day [] (json/read-value (w/eval-js (format "(() => { const active = document.activeElement; const dayButton = active?.closest?.(%s); return JSON.stringify({ focused: !!dayButton, text: dayButton?.textContent ?? null, label: dayButton?.getAttribute('aria-label') ?? dayButton?.textContent ?? null }); })()" (json/write-value-as-string date-picker-day-selector))) json/keyword-keys-object-mapper)) (defn- assert-date-picker-keyboard-navigation [command] (b/new-block (str command " keyboard test")) (util/input-command command) (w/wait-for date-picker-day-selector) (let [initial (focused-date-picker-day)] (is (:focused initial) (str command " should focus a calendar day when opened.")) (k/arrow-right) (let [right (focused-date-picker-day)] (is (:focused right) (str command " should keep calendar focus after ArrowRight.")) (is (not= (:label initial) (:label right)) (str command " should move focused date with ArrowRight.")) (k/arrow-left) (is (= (:label initial) (:label (focused-date-picker-day))) (str command " should move focused date back with ArrowLeft."))) (k/arrow-down) (let [down (focused-date-picker-day)] (is (:focused down) (str command " should keep calendar focus after ArrowDown.")) (is (not= (:label initial) (:label down)) (str command " should move focused date with ArrowDown.")) (k/arrow-up) (is (= (:label initial) (:label (focused-date-picker-day))) (str command " should move focused date back with ArrowUp."))) (k/enter) (if (= command "date picker") (w/wait-for-not-visible ".ui__calendar") (assert/assert-is-visible ".ui__calendar")))) (deftest command-trigger-test (testing "/command trigger popup" (b/new-block "b2") (util/press-seq " /") (w/wait-for "a.menu-link.chosen:has-text('Node reference')") (k/backspace) (w/wait-for-not-visible ".ui__popover-content"))) (deftest page-reference-test (testing "Page reference" (b/new-blocks ["b1" ""]) (util/input-command "Node reference") (util/press-seq "Another page") (k/enter) (is (= "[[Another page]]" (util/get-edit-content))) (util/exit-edit) (is (= "Another page" (util/get-text "a.page-ref"))))) (deftest block-reference-test (testing "Block reference" (b/new-blocks ["block test" ""]) (util/input-command "Node reference") (util/press-seq "block test") (util/wait-timeout 300) (k/enter) (is (string/includes? (util/get-edit-content) "[[")) (util/exit-edit) (is (= "block test" (util/get-text "a.page-ref"))))) (deftest link-test (testing "/link" (let [add-logseq-link (fn [] (util/press-seq "https://logseq.com") (k/tab) (util/press-seq "Logseq") (k/tab) (k/enter))] (b/new-block "") (util/input-command "link") (add-logseq-link) (is (= "[Logseq](https://logseq.com)" (util/get-edit-content))) (util/press-seq " some content ") (util/input-command "link") (add-logseq-link) (is (= (str "[Logseq](https://logseq.com)" " some content " "[Logseq](https://logseq.com)") (util/get-edit-content)))))) (deftest link-image-test (testing "/image link" (b/new-block "") (util/input-command "image link") (util/press-seq "https://logseq.com/test.png") (k/tab) (util/press-seq "Logseq") (k/tab) (k/enter) (is (= "![Logseq](https://logseq.com/test.png)" (util/get-edit-content))))) (deftest underline-test (testing "/underline" (b/new-block "") (util/input-command "underline") (is (= "" (util/get-edit-content))) (util/press-seq "test") (is (= "test" (util/get-edit-content))) (util/move-cursor-to-end))) (deftest code-block-test (testing "/code block" (b/new-block "") (util/input-command "code block") (w/wait-for ".CodeMirror") (util/wait-timeout 100) ;; create another block (k/shift+enter) (assert/assert-is-hidden ".ls-page-blocks .block-tags"))) (deftest math-block-test (testing "/math block" (b/new-block "") (util/input-command "math block") (util/press-seq "1 + 2 = 3") (util/exit-edit) (w/wait-for ".katex") (assert/assert-is-hidden ".ls-page-blocks .block-tags"))) (deftest quote-test (testing "/quote" (b/new-block "") (util/input-command "quote") (w/wait-for "div[data-node-type='quote']") (assert/assert-is-hidden ".ls-page-blocks .block-tags"))) (deftest quote-heading-test (testing "quote headings render consistently" (b/new-block "Property quote heading") (util/input-command "quote") (util/input-command "h1") (util/exit-edit) (b/new-block "# Markdown quote heading") (util/input-command "quote") (util/exit-edit) (assert/assert-is-visible "div[data-node-type='quote']:has(h1.block-title-wrap.as-heading:has-text('Property quote heading'))") (assert/assert-is-visible "div[data-node-type='quote']:has(h1.block-title-wrap.as-heading:has-text('Markdown quote heading'))") (b/jump-to-block "Markdown quote heading") (assert/assert-editor-mode) (is (= "Markdown quote heading" (util/get-edit-content))) (util/exit-edit) (b/new-block "Plain quote") (util/input-command "quote") (util/exit-edit) (assert/assert-is-visible "div[data-node-type='quote']:has(span.block-title-wrap:has-text('Plain quote'))"))) (deftest headings-test (testing "/heading" (dotimes [i 6] (let [heading (str "h" (inc i)) text (str heading " test ")] (b/new-block text) (util/input-command heading) (is (= text (util/get-edit-content))) (util/exit-edit) (w/wait-for heading))))) (deftest status-test (testing "task status commands" (let [status->icon {"Doing" "InProgress50" "In review" "InReview" "Canceled" "Cancelled"}] (doseq [status ["Backlog" "Todo" "Doing" "In review" "Done" "Canceled"]] (let [text (str status " test ")] (b/new-block text) (util/input-command status) (is (= text (util/get-edit-content))) (util/exit-edit) (k/esc) (w/wait-for (str ".ls-icon-" (get status->icon status status)))))))) (deftest priority-test (testing "task priority commands" (let [priority->icon {"No priority" "line-dashed"}] (doseq [priority ["No priority" "Low" "Medium" "High" "Urgent"]] (let [text (str priority " test ")] (b/new-block text) (util/input-command priority) (is (= text (util/get-edit-content))) (util/exit-edit) (w/wait-for (str ".ls-icon-" (get priority->icon priority (str "priorityLvl" priority))))))))) (deftest scheduled-deadline-test (testing "task scheduled and deadline commands" (doseq [command ["Scheduled" "Deadline"]] (fixtures/create-page) (let [text (str command " test ")] (b/new-block text) (util/input-command command) (k/enter) (assert/assert-editor-mode) (util/exit-edit) (is (= command (util/get-text ".property-k"))) (is (= "Today" (util/get-text ".ls-datetime a.page-ref"))))))) (deftest date-command-keyboard-navigation-test (testing "date commands focus the calendar and support keyboard navigation" (doseq [command ["date picker" "Scheduled" "Deadline"]] (fixtures/create-page) (assert-date-picker-keyboard-navigation command)))) ;; TODO: java "MMMM d, yyyy" vs js "MMM do, yyyy" (deftest date-time-test (testing "date time commands" (util/input-command "today") (let [text (util/get-edit-content)] (and (string/starts-with? text "[[") (string/ends-with? text "]]"))) (b/new-block "") (util/input-command "yesterday") (let [text (util/get-edit-content)] (and (string/starts-with? text "[[") (string/ends-with? text "]]"))) (b/new-block "") (util/input-command "tomorrow") (let [text (util/get-edit-content)] (and (string/starts-with? text "[[") (string/ends-with? text "]]"))) ;; FIXME: ;; (b/new-block "") ;; (util/input-command "time") ;; (let [text (util/get-edit-content) ;; t (tl/local-now)] ;; (is (= text (str (t/hour t) ":" (t/minute t))))) (b/new-block "") (util/input-command "date picker") (let [text (util/get-edit-content)] (and (string/starts-with? text "[[") (string/ends-with? text "]]"))))) (deftest number-list-test (testing "number list commands" (util/input-command "number list") (b/new-blocks ["a" "b" "c"]) (assert/assert-have-count "span.typed-list" 3) (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list"))) ;; double `enter` convert the next block to bullet block (k/enter) (assert/assert-have-count "span.typed-list" 4) (util/wait-timeout 60) (k/enter) (assert/assert-have-count "span.typed-list" 3) (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list"))))) (deftest number-children-test (testing "number children commands" (b/new-blocks ["a" "a1" "a2" "a3" "b"]) (k/arrow-up) (w/wait-for "textarea:text('a3')") (util/repeat-keyboard 3 "Shift+ArrowUp") (k/tab) (b/jump-to-block "a") (util/input-command "number children") (assert/assert-have-count "span.typed-list" 3) (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list"))))) (deftest query-test (testing "query" (b/new-blocks ["[[foo]] block" "[[foo]] another" ""]) (util/input-command "query") (let [btn (util/-query-last "button:text('filter')")] (w/click btn) (util/input "page reference") (w/click "a.menu-link:has-text('page reference')") (w/click (first (w/query "a.menu-link:has-text('foo')"))) (assert/assert-is-visible "div:text('Live query (2)')")))) (deftest query-view-membership-updates-live (testing "a mounted query view inserts and removes matching rows" (let [tag "live-query-membership" candidate-title "query membership candidate"] (b/new-blocks [(format "[[%s]] query seed" tag) candidate-title ""]) (let [candidate-uuid (.getAttribute (.first (w/-query (format ".ls-block[data-block-title='%s']" candidate-title))) "blockid") candidate-row (format ".custom-query-results :text('%s')" candidate-title)] (is (string? candidate-uuid)) (util/input-command "query") (w/click (util/-query-last "button:text('filter')")) (util/input "page reference") (w/click "a.menu-link:has-text('page reference')") (w/click (first (w/query (format "a.menu-link:has-text('%s')" tag)))) (w/wait-for "div:text('Live query (1)')") (ls-api-call! :editor.updateBlock candidate-uuid (format "[[%s]] %s" tag candidate-title)) (w/wait-for "div:text('Live query (2)')") (w/wait-for candidate-row) (ls-api-call! :editor.updateBlock candidate-uuid candidate-title) (w/wait-for "div:text('Live query (1)')") (w/wait-for-not-visible candidate-row))))) (deftest advanced-query-test (testing "query" (b/new-blocks ["[[bar]] block" "[[bar]] another" ""]) (util/input-command "advanced query") (w/click ".ls-query-setting") (w/click (.first (w/-query "pre.CodeMirror-line"))) (util/input "{:query [:find (pull ?b [*]) :where [?b :block/refs ?r] [?r :block/title \"bar\"]]}") (k/esc) (is (some? (w/find-one-by-text "div" "Live query (2)"))))) (deftest calculator-test (testing "calculator" (b/new-block "") (util/input-command "calculator") (util/input "1 + 2") (w/wait-for "div.extensions__code-calc-output-line") (is (= "3" (util/get-text "div.extensions__code-calc-output-line"))) (assert/assert-is-hidden ".ls-page-blocks .block-tags"))) (deftest template-test (testing "template" (b/new-block "template 1") (util/set-tag "Template" :hidden? true) (b/new-blocks ["block 1" "block 2" "block 3" "test"]) (k/arrow-up) (w/wait-for "textarea:text('block 3')") (util/repeat-keyboard 3 "Shift+ArrowUp") (k/tab) (b/jump-to-block "test") (util/input-command "template") (util/input "template 1") (w/wait-for "a.menu-link.chosen:has-text('template 1')") (k/enter) (doseq [text ["block 1" "block 2" "block 3"]] (assert/assert-have-count (loc/or (format ".ls-block .block-title-wrap:text('%s')" text) (format ".ls-block textarea:text('%s')" text)) 2)))) (deftest embed-html-test (testing "embed html" (b/new-block "") (util/input-command "embed html") (util/press-seq "
test
") (util/exit-edit) (is (= "test" (util/get-text "#embed-test"))))) (deftest embed-video-test (testing "embed video" (b/new-block "") (util/input-command "embed video") (util/press-seq "https://www.youtube.com/watch?v=7xTGNNLPyMI") (util/exit-edit) (w/wait-for "iframe"))) (deftest embed-tweet-test (testing "embed tweet" (b/new-block "") (util/input-command "embed tweet") (util/press-seq "https://x.com/logseq/status/1784914564083314839") (util/exit-edit) (w/wait-for "iframe"))) (deftest cloze-test (testing "cloze" (b/new-block "") (util/input-command "cloze") (util/press-seq "hidden answer") (util/exit-edit) (w/click "span.cloze") (w/wait-for "span.cloze-revealed")))