diff --git a/docs/dev-practices.md b/docs/dev-practices.md index a8e7b97d6a..72be4af40e 100644 --- a/docs/dev-practices.md +++ b/docs/dev-practices.md @@ -77,10 +77,13 @@ mistakes [as noted here](./contributing-to-translations.md#fix-mistakes). ## Testing +Even though we have a nightly release channel, it's hard for testing users (thanks to the brave users!) to notice all issues in a limited time, as Logseq is covering so many features (and the hell of combinations) +The only solution is automatic end-to-end tests - adding tests for GUI software is always painful but necessary We have unit and end to end tests. ### End to End Tests +https://github.com/logseq/logseq/pulls?q=E2E To run end to end tests ```sh @@ -95,11 +98,15 @@ If e2e failed after first running: - `rm -rdf /tmp/` - Windows: `rmdir /s %APPDATA%/Electron` (Reference: https://www.electronjs.org/de/docs/latest/api/app#appgetpathname) -If e2e tests fail, they can be debugged by examining a trace dump with [the +There's a `traceAll()` helper function to enable playwright trace file dump for specific test files https://github.com/logseq/logseq/pull/8332 + +If e2e tests fail in the file, they can be debugged by examining a trace dump with [the playwright trace -viewer](https://playwright.dev/docs/trace-viewer#recording-a-trace). Locally -this will get dumped into e2e-dump/. On CI the trace file will be under -Artifacts at the bottom of a run page e.g. +viewer](https://playwright.dev/docs/trace-viewer#recording-a-trace). + +Locally this will get dumped into e2e-dump/. + +On CI the trace file will be under Artifacts at the bottom of a run page e.g. https://github.com/logseq/logseq/actions/runs/3574600322. ### Unit Testing diff --git a/e2e-tests/editor.spec.ts b/e2e-tests/editor.spec.ts index 61f4193002..70afdef77d 100644 --- a/e2e-tests/editor.spec.ts +++ b/e2e-tests/editor.spec.ts @@ -596,3 +596,90 @@ test('should not erase typed text when expanding block quickly after typing #389 '' ) }) + +test('should keep correct undo and redo seq after indenting or outdenting the block #7615',async({page,block}) => { + await createRandomPage(page) + + await block.mustFill("foo") + + await page.keyboard.press("Enter") + await expect(page.locator('textarea >> nth=0')).toHaveText("") + await block.indent() + await block.mustFill("bar") + await expect(page.locator('textarea >> nth=0')).toHaveText("bar") + + if (IsMac) { + await page.keyboard.press('Meta+z') + } else { + await page.keyboard.press('Control+z') + } + // should undo "bar" input + await expect(page.locator('textarea >> nth=0')).toHaveText("") + if (IsMac) { + await page.keyboard.press('Shift+Meta+z') + } else { + await page.keyboard.press('Shift+Control+z') + } + // should redo "bar" input + await expect(page.locator('textarea >> nth=0')).toHaveText("bar") + await page.keyboard.press("Shift+Tab") + + await page.keyboard.press("Enter") + await expect(page.locator('textarea >> nth=0')).toHaveText("") + // swap input seq + await block.mustFill("baz") + await block.indent() + + if (IsMac) { + await page.keyboard.press('Meta+z') + } else { + await page.keyboard.press('Control+z') + } + // should undo indention + await expect(page.locator('textarea >> nth=0')).toHaveText("baz") + await page.keyboard.press("Shift+Tab") + + await page.keyboard.press("Enter") + await expect(page.locator('textarea >> nth=0')).toHaveText("") + // #7615 + await page.keyboard.type("aaa") + await block.indent() + await page.keyboard.type(" bbb") + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb") + if (IsMac) { + await page.keyboard.press('Meta+z') + } else { + await page.keyboard.press('Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa") + if (IsMac) { + await page.keyboard.press('Meta+z') + } else { + await page.keyboard.press('Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa") + if (IsMac) { + await page.keyboard.press('Meta+z') + } else { + await page.keyboard.press('Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("") + if (IsMac) { + await page.keyboard.press('Shift+Meta+z') + } else { + await page.keyboard.press('Shift+Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa") + if (IsMac) { + await page.keyboard.press('Shift+Meta+z') + } else { + await page.keyboard.press('Shift+Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa") + if (IsMac) { + await page.keyboard.press('Shift+Meta+z') + } else { + await page.keyboard.press('Shift+Control+z') + } + await expect(page.locator('textarea >> nth=0')).toHaveText("aaa bbb") +}) diff --git a/package.json b/package.json index b2873969af..c81be15b35 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "@capawesome/capacitor-background-task": "^2.0.0", "@excalidraw/excalidraw": "0.12.0", "@hugotomazi/capacitor-navigation-bar": "^2.0.0", - "@logseq/capacitor-file-sync": "0.0.17", + "@logseq/capacitor-file-sync": "0.0.18", "@logseq/react-tweet-embed": "1.3.1-1", "@sentry/react": "^6.18.2", "@sentry/tracing": "^6.18.2", diff --git a/resources/package.json b/resources/package.json index f72cf0cbeb..60aff570c4 100644 --- a/resources/package.json +++ b/resources/package.json @@ -37,7 +37,7 @@ "https-proxy-agent": "5.0.0", "@sentry/electron": "2.5.1", "posthog-js": "1.10.2", - "@logseq/rsapi": "0.0.59", + "@logseq/rsapi": "0.0.60", "electron-deeplink": "1.0.10", "abort-controller": "3.0.0", "fastify": "latest", diff --git a/src/electron/electron/handler.cljs b/src/electron/electron/handler.cljs index 137cdb0eea..d1d8ea0db2 100644 --- a/src/electron/electron/handler.cljs +++ b/src/electron/electron/handler.cljs @@ -1,34 +1,34 @@ (ns electron.handler "This ns starts the event handling for the electron main process and defines all the application-specific event types" - (:require ["electron" :refer [ipcMain dialog app autoUpdater shell]] - [cljs-bean.core :as bean] - ["fs" :as fs] - ["buffer" :as buffer] - ["fs-extra" :as fs-extra] - ["path" :as path] - ["os" :as os] - ["diff-match-patch" :as google-diff] - ["/electron/utils" :as js-utils] + (:require ["/electron/utils" :as js-utils] ["abort-controller" :as AbortController] - [electron.shell :as shell] - [electron.fs-watcher :as watcher] - [electron.configs :as cfgs] - [promesa.core :as p] - [clojure.string :as string] - [electron.utils :as utils] - [electron.logger :as logger] - [electron.state :as state] - [clojure.core.async :as async] - [electron.search :as search] - [electron.git :as git] - [electron.plugin :as plugin] - [electron.window :as win] - [electron.file-sync-rsapi :as rsapi] - [electron.backup-file :as backup-file] + ["buffer" :as buffer] + ["diff-match-patch" :as google-diff] + ["electron" :refer [app autoUpdater dialog ipcMain shell]] + ["fs" :as fs] + ["fs-extra" :as fs-extra] + ["os" :as os] + ["path" :as path] + [cljs-bean.core :as bean] [cljs.reader :as reader] + [clojure.core.async :as async] + [clojure.string :as string] + [electron.backup-file :as backup-file] + [electron.configs :as cfgs] + [electron.file-sync-rsapi :as rsapi] + [electron.find-in-page :as find] + [electron.fs-watcher :as watcher] + [electron.git :as git] + [electron.logger :as logger] + [electron.plugin :as plugin] + [electron.search :as search] [electron.server :as server] - [electron.find-in-page :as find])) + [electron.shell :as shell] + [electron.state :as state] + [electron.utils :as utils] + [electron.window :as win] + [promesa.core :as p])) (defmulti handle (fn [_window args] (keyword (first args)))) @@ -177,12 +177,34 @@ result (get (js->clj result) "filePaths")] (p/resolved (first result)))) -(defmethod handle :openDir [^js _window _messages] +(defn- pretty-print-js-error + "Converts file related JS Error messages to a human readable format. + Ex.: + Error: EACCES: permission denied, scandir '/tmp/test' + Permission denied for path: '/tmp/test' (Code: EACCES)" + [e] + (some->> + e + str + ;; Message parsed as "Error: $ERROR_CODE$: $REASON$, function $PATH$" + (re-matches #"(?:Error\: )(.+)(?:\: )(.+)(?:, \w+ )('.+')") + rest + (#(str (string/capitalize (second %)) " for path: " (nth % 2) " (Code: " (first %) ")")))) + +(defmethod handle :openDir [^js window _messages] (logger/info ::open-dir "open folder selection dialog") (p/let [path (open-dir-dialog)] (logger/debug ::open-dir {:path path}) (if path - (p/resolved (bean/->js (get-files path))) + (try + (p/resolved (bean/->js (get-files path))) + (catch js/Error e + (do + (utils/send-to-renderer window "notification" {:type "error" + :payload (str "Opening the specified directory failed.\n" + (or (pretty-print-js-error e) (str "Unexpected error: " e)))}) + (p/rejected e)))) + (p/rejected (js/Error "path empty"))))) (defmethod handle :getFiles [_window [_ path]] diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index a72a21fbd3..b8bc6ebf9b 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -1660,11 +1660,11 @@ [up?] (fn [event] (util/stop event) + (save-current-block!) (let [edit-block-id (:block/uuid (state/get-edit-block)) move-nodes (fn [blocks] (outliner-tx/transact! {:outliner-op :move-blocks} - (save-current-block!) (outliner-core/move-blocks-up-down! blocks up?)) (when-let [block-node (util/get-first-block-by-id (:block/uuid (first blocks)))] (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"})))] @@ -2139,10 +2139,10 @@ [node] (when-not (parent-is-page? node) (let [parent-node (tree/-get-parent node)] + (save-current-block!) (outliner-tx/transact! {:outliner-op :move-blocks :real-outliner-op :indent-outdent} - (save-current-block!) (outliner-core/move-blocks! [(:data node)] (:data parent-node) true))))) (defn- last-top-level-child? @@ -2666,6 +2666,7 @@ (defn indent-outdent [indent?] + (save-current-block!) (state/set-editor-op! :indent-outdent) (let [pos (some-> (state/get-input) cursor/pos) {:keys [block]} (get-state)] @@ -2674,8 +2675,7 @@ (outliner-tx/transact! {:outliner-op :move-blocks :real-outliner-op :indent-outdent} - (save-current-block!) - (outliner-core/indent-outdent-blocks! [block] indent?))) + (outliner-core/indent-outdent-blocks! [block] indent?))) (state/set-editor-op! :nil))) (defn keydown-tab-handler diff --git a/src/main/frontend/mobile/mobile_bar.cljs b/src/main/frontend/mobile/mobile_bar.cljs index 75931c4fbd..139bdc1223 100644 --- a/src/main/frontend/mobile/mobile_bar.cljs +++ b/src/main/frontend/mobile/mobile_bar.cljs @@ -99,4 +99,4 @@ (for [command commands] command)] [:div.toolbar-hide-keyboard - (command #(state/clear-edit!) "keyboard-show")]]))) + (command #(state/clear-edit!) {:icon "keyboard-show"})]]))) diff --git a/static/yarn.lock b/static/yarn.lock index c5f0006f12..25431e919b 100644 --- a/static/yarn.lock +++ b/static/yarn.lock @@ -392,41 +392,41 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@logseq/rsapi-darwin-arm64@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-arm64/-/rsapi-darwin-arm64-0.0.59.tgz#3b478c1e045246a536446b39ee0df6943403ec8f" - integrity sha512-mi7Z8UVocVGayCB2kKUgq6MGRNI7BX9EZeTnf7JWRLdfVZe4Rf3FDe1Kr/zlY5lO27KYsPzqLC/Pw6IkQtZsvQ== +"@logseq/rsapi-darwin-arm64@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-arm64/-/rsapi-darwin-arm64-0.0.60.tgz#5edd887b38dd4d26cae51d397ff0c6f3d96bec43" + integrity sha512-Bv0ck+pjHb0z6OaGbuIl4RNbKF1KRF281zDdpiVisdSByVQ6sCQ55lemvgDFgwlZWp3Bp6vyMTrfjlzeuP86ZQ== -"@logseq/rsapi-darwin-x64@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-x64/-/rsapi-darwin-x64-0.0.59.tgz#7a3d746d4807523dfe0197658d1f1266c66534f3" - integrity sha512-hDf5QUa+vQRHJ7p0OBAREWYOwHepUpDUEIbyXhSzTb7g5vsr5yBEOygnRt1Jt6a1DfadMPDdrl925AaUrkZAjA== +"@logseq/rsapi-darwin-x64@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi-darwin-x64/-/rsapi-darwin-x64-0.0.60.tgz#3c099b77f6e4fa5578a61de83e9907bfaf3ac3fa" + integrity sha512-iasBB89fB1h5kWAfrV2DQcynlUMKdR85GyruaVbJYspYp0SlkB+ZiR9vh5lkJQyNkMM8Y1hvLRh2ZTMAl+acWA== -"@logseq/rsapi-linux-arm64-gnu@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi-linux-arm64-gnu/-/rsapi-linux-arm64-gnu-0.0.59.tgz#58e83687912deae39e6e15e57093e558d80cf7ea" - integrity sha512-3kqeSsIjPgd+O/DvhsawJIXdIa4aPVxNtmQ/YJeyXV7Alx5jPY1kPaxKsZUl6h3qr8Zrr5/wTtg0pUS33BwHPg== +"@logseq/rsapi-linux-arm64-gnu@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi-linux-arm64-gnu/-/rsapi-linux-arm64-gnu-0.0.60.tgz#77e6ef67e87c975ca6a639da3a0b47a69afad8e7" + integrity sha512-op47HnJZUKYvrHsMFnlzkTd5uMeYCGkQXF1bYvYYgQrgREJyU2CzJHKKDoFZth13ahF8rjAccHeXhqg3TGccSg== -"@logseq/rsapi-linux-x64-gnu@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi-linux-x64-gnu/-/rsapi-linux-x64-gnu-0.0.59.tgz#5dd228df20befbd642044d99e0a41df86d9cdda5" - integrity sha512-iBTxAlQNKX4g4AMJa2l4tOlActXchIFf0tFrNkSWKmyW1mi2AyQ78eObl90F9H1OAHbziOhOs+zzu8nZvJBIJQ== +"@logseq/rsapi-linux-x64-gnu@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi-linux-x64-gnu/-/rsapi-linux-x64-gnu-0.0.60.tgz#fd96e37e902d9b4b031c603ec4fe1d06d9b2e594" + integrity sha512-LeM/PrGdVH3ix43erCVkKM0N5saXhJG7yxNl000/4bNAUkqdGOQ2Cnhy/TyekgZtMqblKjDl/1tJWboLA5jjLQ== -"@logseq/rsapi-win32-x64-msvc@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi-win32-x64-msvc/-/rsapi-win32-x64-msvc-0.0.59.tgz#3a44b310ad8fa725cd2d1cb6056dc5d66fa644b2" - integrity sha512-tupB5/uxcChl6PZO1Wb1g9w8MpiMArM9wu4d2b0KGTccOExKrMwZzKcusFfgWsCqwiIqF2ozBPVd8JKMnAd+yw== +"@logseq/rsapi-win32-x64-msvc@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi-win32-x64-msvc/-/rsapi-win32-x64-msvc-0.0.60.tgz#ed2d072df731c01681c4a24caf5a26baa5ff37a9" + integrity sha512-22/RRF/VUAb5flW2DLkG0RUMj+LujghSP9pMgv2zkyap104HG1+Wy8xO4j1DkqtpZnwVd2MCohVuHeimHn8IMA== -"@logseq/rsapi@0.0.59": - version "0.0.59" - resolved "https://registry.yarnpkg.com/@logseq/rsapi/-/rsapi-0.0.59.tgz#94b359237632279e2ccaf58fbbd563b0718307dc" - integrity sha512-m3Y4/k2VmyuLLrNo1EuSBOiNASXrZHtC8bsN2a/AyHFeFuCDThqiTNJ0g5VqvqeaQBBZBaIUawg3OKsmjkRHhQ== +"@logseq/rsapi@0.0.60": + version "0.0.60" + resolved "https://registry.yarnpkg.com/@logseq/rsapi/-/rsapi-0.0.60.tgz#45763b9988e558c0e9513826cf55069ea0a413c1" + integrity sha512-fn2tdNyx8XCvh100YG3yD5yBI+kjv0Hz9WeFKFMHb/8JujwS/MnZdM/IKDbw+JtDp/46TfXPXttLm6FQBa7O2w== optionalDependencies: - "@logseq/rsapi-darwin-arm64" "0.0.59" - "@logseq/rsapi-darwin-x64" "0.0.59" - "@logseq/rsapi-linux-arm64-gnu" "0.0.59" - "@logseq/rsapi-linux-x64-gnu" "0.0.59" - "@logseq/rsapi-win32-x64-msvc" "0.0.59" + "@logseq/rsapi-darwin-arm64" "0.0.60" + "@logseq/rsapi-darwin-x64" "0.0.60" + "@logseq/rsapi-linux-arm64-gnu" "0.0.60" + "@logseq/rsapi-linux-x64-gnu" "0.0.60" + "@logseq/rsapi-win32-x64-msvc" "0.0.60" "@malept/cross-spawn-promise@^1.0.0", "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" diff --git a/yarn.lock b/yarn.lock index d73a9c8a57..3091f53a36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -487,10 +487,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@logseq/capacitor-file-sync@0.0.17": - version "0.0.17" - resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.17.tgz#4bb9000c64aee8cc07d79069f5e3cbc0908b2613" - integrity sha512-B+VHtmH9tGNXiGcHDKNMY2Ype/YHPg+V+HaGYXqEEtzSVtw2QOe/RLkOZG+wKFhokSk3hNQyVd1/WdMGhdHS/Q== +"@logseq/capacitor-file-sync@0.0.18": + version "0.0.18" + resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-0.0.18.tgz#9e6c1386483fb693ce5fdd5b5a3fbb8627de40fc" + integrity sha512-tRcwc9OBh4oayhbMGj9iL+86jsUAT+Y76mLqsdYGsbKhck4Hv+YV3dZ0M9GUEFf18xE1pj6H7sB+qYGMd23X6w== "@logseq/react-tweet-embed@1.3.1-1": version "1.3.1-1"