mirror of
https://github.com/logseq/logseq.git
synced 2026-05-28 14:39:48 +00:00
fix(db-sync): rebind redo history tx-id for undo replay
This commit is contained in:
@@ -143,16 +143,14 @@
|
||||
(let [key (keyword "db-sync-sim" repo)]
|
||||
(d/listen! conn key
|
||||
(fn [tx-report]
|
||||
(db-sync/enqueue-local-tx! repo tx-report)
|
||||
(undo-redo/gen-undo-ops!
|
||||
repo
|
||||
(-> tx-report
|
||||
(assoc-in [:tx-meta :client-id] (:client-id @state/state))
|
||||
(update-in [:tx-meta :local-tx?]
|
||||
(fn [local-tx?]
|
||||
(if (nil? local-tx?)
|
||||
true
|
||||
local-tx?)))))))
|
||||
(let [tx-report' (-> tx-report
|
||||
(assoc-in [:tx-meta :client-id] (:client-id @state/state))
|
||||
(update-in [:tx-meta :local-tx?]
|
||||
(fn [local-tx?]
|
||||
(if (nil? local-tx?)
|
||||
true
|
||||
local-tx?))))]
|
||||
(db-sync/enqueue-local-tx! repo tx-report'))))
|
||||
(swap! listeners conj [conn key]))))
|
||||
(try
|
||||
(f)
|
||||
|
||||
@@ -1010,80 +1010,6 @@
|
||||
(finally
|
||||
(reset! ldb/*transact-invalid-callback prev-invalid-callback))))))))
|
||||
|
||||
(deftest undo-redo-insert-save-insert-save-indent-sequence-keeps-block-valid-test
|
||||
(testing "insert/save/insert/save/indent then undo-all/redo-all/undo keeps block 2 valid"
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:pages-and-blocks [{:page {:block/title "page 1"}
|
||||
:blocks []}]})
|
||||
client-ops-conn (d/create-conn client-op/schema-in-db)
|
||||
page-1 (db-test/find-page-by-title @conn "page 1")
|
||||
page-id (:db/id page-1)
|
||||
block-1-uuid (random-uuid)
|
||||
block-2-uuid (random-uuid)
|
||||
prev-invalid-callback @ldb/*transact-invalid-callback
|
||||
invalid-payload* (atom nil)]
|
||||
(with-datascript-conns conn client-ops-conn
|
||||
(fn []
|
||||
(d/listen! conn ::worker-undo-listener
|
||||
(fn [tx-report]
|
||||
(worker-undo-redo/gen-undo-ops! test-repo tx-report)))
|
||||
(reset! ldb/*transact-invalid-callback
|
||||
(fn [tx-report errors]
|
||||
(reset! invalid-payload* {:tx-meta (:tx-meta tx-report)
|
||||
:errors errors})))
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(try
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:insert-blocks [[{:block/uuid block-1-uuid
|
||||
:block/title ""}]
|
||||
page-id
|
||||
{:sibling? false
|
||||
:keep-uuid? true}]]]
|
||||
local-tx-meta)
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:save-block [{:block/uuid block-1-uuid
|
||||
:block/title "1"}
|
||||
nil]]]
|
||||
local-tx-meta)
|
||||
(let [block-1 (d/entity @conn [:block/uuid block-1-uuid])]
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:insert-blocks [[{:block/uuid block-2-uuid
|
||||
:block/title ""}]
|
||||
(:db/id block-1)
|
||||
{:sibling? true
|
||||
:keep-uuid? true}]]]
|
||||
local-tx-meta))
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:save-block [{:block/uuid block-2-uuid
|
||||
:block/title "2"}
|
||||
nil]]]
|
||||
local-tx-meta)
|
||||
(let [block-2 (d/entity @conn [:block/uuid block-2-uuid])]
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:indent-outdent-blocks [[(:db/id block-2)] true {}]]]
|
||||
local-tx-meta))
|
||||
|
||||
(loop []
|
||||
(when-not (= :frontend.worker.undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo))
|
||||
(recur)))
|
||||
(loop []
|
||||
(when-not (= :frontend.worker.undo-redo/empty-redo-stack
|
||||
(worker-undo-redo/redo test-repo))
|
||||
(recur)))
|
||||
(is (not= :frontend.worker.undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo)))
|
||||
(let [block-2 (d/entity @conn [:block/uuid block-2-uuid])]
|
||||
(is (some? block-2))
|
||||
(is (= "2" (:block/title block-2)))
|
||||
(is (= (:block/uuid page-1) (-> block-2 :block/page :block/uuid)))
|
||||
(is (= (:block/uuid page-1) (-> block-2 :block/parent :block/uuid))))
|
||||
(is (nil? @invalid-payload*))
|
||||
(finally
|
||||
(d/unlisten! conn ::worker-undo-listener)
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(reset! ldb/*transact-invalid-callback prev-invalid-callback))))))))
|
||||
|
||||
(deftest enqueue-local-tx-canonicalizes-batch-import-to-transact-test
|
||||
(testing "batch-import-edn local tx persists as canonical transact op"
|
||||
(let [{:keys [conn client-ops-conn]} (setup-parent-child)
|
||||
@@ -1126,11 +1052,13 @@
|
||||
:block/title "hello"} nil]]]
|
||||
local-tx-meta)
|
||||
(let [{:keys [tx-id]} (first (#'sync-apply/pending-txs test-repo))]
|
||||
(is (= true
|
||||
(:applied? (#'sync-apply/apply-history-action! test-repo
|
||||
tx-id
|
||||
true
|
||||
{:db-sync/tx-id tx-id}))))
|
||||
(let [{:keys [applied? history-tx-id]} (#'sync-apply/apply-history-action! test-repo
|
||||
tx-id
|
||||
true
|
||||
{:db-sync/tx-id tx-id})]
|
||||
(is (= true applied?))
|
||||
(is (uuid? history-tx-id))
|
||||
(is (not= tx-id history-tx-id)))
|
||||
(let [pending (#'sync-apply/pending-txs test-repo)]
|
||||
(is (= 2 (count pending)))
|
||||
(is (= 2 (count (distinct (map :tx-id pending)))))
|
||||
@@ -1138,6 +1066,59 @@
|
||||
(get-in (#'sync-apply/pending-tx-by-id test-repo tx-id)
|
||||
[:forward-outliner-ops 0 1 0 :block/title]))))))))))
|
||||
|
||||
(deftest apply-history-action-preserves-source-forward-inverse-ops-test
|
||||
(testing "undo/redo history actions should preserve source forward/inverse ops and create new tx rows"
|
||||
(let [{:keys [conn client-ops-conn child1]} (setup-parent-child)
|
||||
child-uuid (:block/uuid child1)]
|
||||
(with-datascript-conns conn client-ops-conn
|
||||
(fn []
|
||||
(outliner-op/apply-ops! conn
|
||||
[[:save-block [{:block/uuid child-uuid
|
||||
:block/title "hello"} nil]]]
|
||||
local-tx-meta)
|
||||
(let [{source-tx-id :tx-id} (first (#'sync-apply/pending-txs test-repo))]
|
||||
(let [{undo-applied? :applied?
|
||||
undo-history-tx-id :history-tx-id}
|
||||
(#'sync-apply/apply-history-action! test-repo
|
||||
source-tx-id
|
||||
true
|
||||
{})]
|
||||
(is (= true undo-applied?))
|
||||
(is (uuid? undo-history-tx-id))
|
||||
(is (not= source-tx-id undo-history-tx-id)))
|
||||
(let [source-pending (#'sync-apply/pending-tx-by-id test-repo source-tx-id)
|
||||
pending-after-undo (#'sync-apply/pending-txs test-repo)
|
||||
undo-pending (first (filter #(not= source-tx-id (:tx-id %)) pending-after-undo))]
|
||||
(is (= 2 (count pending-after-undo)))
|
||||
(is (some? undo-pending))
|
||||
(is (= "hello"
|
||||
(get-in source-pending [:forward-outliner-ops 0 1 0 :block/title])))
|
||||
(is (= "child 1"
|
||||
(get-in source-pending [:inverse-outliner-ops 0 1 0 :block/title])))
|
||||
(is (= "child 1"
|
||||
(get-in undo-pending [:forward-outliner-ops 0 1 0 :block/title])))
|
||||
(is (= "hello"
|
||||
(get-in undo-pending [:inverse-outliner-ops 0 1 0 :block/title]))))
|
||||
(let [{redo-applied? :applied?
|
||||
redo-history-tx-id :history-tx-id}
|
||||
(#'sync-apply/apply-history-action! test-repo
|
||||
source-tx-id
|
||||
false
|
||||
{})]
|
||||
(is (= true redo-applied?))
|
||||
(is (uuid? redo-history-tx-id))
|
||||
(is (not= source-tx-id redo-history-tx-id)))
|
||||
(let [source-pending (#'sync-apply/pending-tx-by-id test-repo source-tx-id)
|
||||
pending-after-redo (#'sync-apply/pending-txs test-repo)
|
||||
new-tx-ids (set (map :tx-id pending-after-redo))]
|
||||
(is (= 3 (count pending-after-redo)))
|
||||
(is (= 3 (count new-tx-ids)))
|
||||
(is (contains? new-tx-ids source-tx-id))
|
||||
(is (= "hello"
|
||||
(get-in source-pending [:forward-outliner-ops 0 1 0 :block/title])))
|
||||
(is (= "child 1"
|
||||
(get-in source-pending [:inverse-outliner-ops 0 1 0 :block/title]))))))))))
|
||||
|
||||
(deftest apply-history-action-semantic-op-must-not-fallback-to-raw-tx-test
|
||||
(testing "semantic history action should not fallback to raw tx replay"
|
||||
(let [{:keys [conn client-ops-conn child1]} (setup-parent-child)
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
(reset! worker-state/*client-ops-conns {test-repo client-ops-conn})
|
||||
(d/listen! conn ::gen-undo-ops
|
||||
(fn [tx-report]
|
||||
(db-sync/enqueue-local-tx! test-repo tx-report)
|
||||
(worker-undo-redo/gen-undo-ops! test-repo tx-report)))
|
||||
(db-sync/enqueue-local-tx! test-repo tx-report)))
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(try
|
||||
(f)
|
||||
@@ -47,27 +46,6 @@
|
||||
|
||||
(use-fixtures :each with-worker-conns)
|
||||
|
||||
(deftest gen-undo-ops-consumes-pending-editor-info-test
|
||||
(let [conn (worker-state/get-datascript-conn test-repo)
|
||||
block (db-test/find-block-by-content @conn "task")
|
||||
block-uuid (:block/uuid block)
|
||||
tx-report (d/with @conn
|
||||
[[:db/add (:db/id block) :block/title "updated task"]]
|
||||
(local-tx-meta
|
||||
{:outliner-op :save-block
|
||||
:outliner-ops [[:save-block [{:block/uuid block-uuid
|
||||
:block/title "updated task"} nil]]]}))
|
||||
editor-info {:block-uuid block-uuid
|
||||
:container-id 1
|
||||
:start-pos 0
|
||||
:end-pos 7}]
|
||||
(worker-undo-redo/set-pending-editor-info! test-repo editor-info)
|
||||
(worker-undo-redo/gen-undo-ops! test-repo tx-report)
|
||||
(let [op (last (get @worker-undo-redo/*undo-ops test-repo))]
|
||||
(is (= [::worker-undo-redo/record-editor-info editor-info]
|
||||
(first op)))
|
||||
(is (nil? (get @worker-undo-redo/*pending-editor-info test-repo))))))
|
||||
|
||||
(deftest worker-ui-state-roundtrip-test
|
||||
(let [ui-state-str "{:old-state {}, :new-state {:route-data {:to :page}}}"]
|
||||
(worker-undo-redo/record-ui-state! test-repo ui-state-str)
|
||||
@@ -130,6 +108,13 @@
|
||||
(second %))
|
||||
undo-op)))
|
||||
|
||||
(defn- latest-redo-history-data
|
||||
[]
|
||||
(let [redo-op (last (get @worker-undo-redo/*redo-ops test-repo))]
|
||||
(some #(when (= ::worker-undo-redo/db-transact (first %))
|
||||
(second %))
|
||||
redo-op)))
|
||||
|
||||
(deftest undo-missing-history-action-row-clears-history-test
|
||||
(testing "worker undo treats missing tx-id action row as unavailable and clears history"
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
@@ -153,6 +138,27 @@
|
||||
(is (= ::worker-undo-redo/empty-redo-stack redo-result))
|
||||
(is (= "v2" (:block/title (d/entity @conn [:block/uuid child-uuid]))))))))
|
||||
|
||||
(deftest undo-redo-rebinds-stack-to-latest-history-tx-id-test
|
||||
(testing "undo/redo pushes stack op with latest persisted history tx id"
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(let [conn (worker-state/get-datascript-conn test-repo)
|
||||
client-ops-conn (get @worker-state/*client-ops-conns test-repo)
|
||||
{:keys [child-uuid]} (seed-page-parent-child!)]
|
||||
(save-block-title! conn child-uuid "v1")
|
||||
(let [source-tx-id (:db-sync/tx-id (latest-undo-history-data))]
|
||||
(is (uuid? source-tx-id))
|
||||
(is (not= ::worker-undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo)))
|
||||
(let [redo-tx-id (:db-sync/tx-id (latest-redo-history-data))]
|
||||
(is (uuid? redo-tx-id))
|
||||
(is (= source-tx-id redo-tx-id))
|
||||
(is (not= ::worker-undo-redo/empty-redo-stack
|
||||
(worker-undo-redo/redo test-repo)))
|
||||
(let [undo-tx-id (:db-sync/tx-id (latest-undo-history-data))]
|
||||
(is (uuid? undo-tx-id))
|
||||
(is (not= source-tx-id undo-tx-id))
|
||||
(is (some? (d/entity @client-ops-conn [:db-sync/tx-id undo-tx-id])))))))))
|
||||
|
||||
(deftest undo-records-only-local-txs-test
|
||||
(testing "undo history records only local txs"
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
@@ -550,6 +556,85 @@
|
||||
(is (some? inserted-a))
|
||||
(is (some? inserted-b))))))
|
||||
|
||||
(deftest apply-template-repeated-undo-redo-uses-latest-history-tx-id-test
|
||||
(testing ":apply-template repeated undo/redo should always undo latest recreated blocks"
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(let [conn (worker-state/get-datascript-conn test-repo)
|
||||
{:keys [page-uuid]} (seed-page-parent-child!)
|
||||
page-id (:db/id (d/entity @conn [:block/uuid page-uuid]))
|
||||
template-root-uuid (random-uuid)
|
||||
template-a-uuid (random-uuid)
|
||||
template-b-uuid (random-uuid)
|
||||
empty-target-uuid (random-uuid)]
|
||||
(outliner-op/apply-ops!
|
||||
conn
|
||||
[[:insert-blocks [[{:block/uuid template-root-uuid
|
||||
:block/title "template 1"
|
||||
:block/tags #{:logseq.class/Template}}
|
||||
{:block/uuid template-a-uuid
|
||||
:block/title "a"
|
||||
:block/parent [:block/uuid template-root-uuid]}
|
||||
{:block/uuid template-b-uuid
|
||||
:block/title "b"
|
||||
:block/parent [:block/uuid template-a-uuid]}]
|
||||
page-id
|
||||
{:sibling? false
|
||||
:keep-uuid? true}]]]
|
||||
(local-tx-meta {:client-id "test-client"}))
|
||||
(outliner-op/apply-ops!
|
||||
conn
|
||||
[[:insert-blocks [[{:block/uuid empty-target-uuid
|
||||
:block/title ""}]
|
||||
page-id
|
||||
{:sibling? false
|
||||
:keep-uuid? true}]]]
|
||||
(local-tx-meta {:client-id "test-client"}))
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
(let [template-root (d/entity @conn [:block/uuid template-root-uuid])
|
||||
empty-target (d/entity @conn [:block/uuid empty-target-uuid])
|
||||
template-blocks (->> (ldb/get-block-and-children @conn template-root-uuid
|
||||
{:include-property-block? true})
|
||||
rest)
|
||||
blocks-to-insert (cons (assoc (first template-blocks)
|
||||
:logseq.property/used-template (:db/id template-root))
|
||||
(rest template-blocks))
|
||||
find-inserted-a-id (fn []
|
||||
(d/q '[:find ?b .
|
||||
:in $ ?template-uuid
|
||||
:where
|
||||
[?template :block/uuid ?template-uuid]
|
||||
[?b :logseq.property/used-template ?template]
|
||||
[?b :block/title "a"]]
|
||||
@conn
|
||||
template-root-uuid))]
|
||||
(outliner-op/apply-ops!
|
||||
conn
|
||||
[[:apply-template [(:db/id template-root)
|
||||
(:db/id empty-target)
|
||||
{:sibling? true
|
||||
:replace-empty-target? true
|
||||
:template-blocks blocks-to-insert}]]]
|
||||
(local-tx-meta {:client-id "test-client"}))
|
||||
(is (some? (find-inserted-a-id)))
|
||||
(is (not= ::worker-undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo)))
|
||||
(is (nil? (find-inserted-a-id)))
|
||||
(is (not= ::worker-undo-redo/empty-redo-stack
|
||||
(worker-undo-redo/redo test-repo)))
|
||||
(let [redo-1-a-id (find-inserted-a-id)]
|
||||
(is (some? redo-1-a-id))
|
||||
(is (not= ::worker-undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo)))
|
||||
(is (nil? (find-inserted-a-id)))
|
||||
(is (not= ::worker-undo-redo/empty-redo-stack
|
||||
(worker-undo-redo/redo test-repo)))
|
||||
(let [redo-2-a-id (find-inserted-a-id)]
|
||||
(is (some? redo-2-a-id))
|
||||
(is (not= redo-1-a-id redo-2-a-id))
|
||||
(is (not= ::worker-undo-redo/empty-undo-stack
|
||||
(worker-undo-redo/undo test-repo)))
|
||||
(is (nil? (find-inserted-a-id)))))))))
|
||||
|
||||
(deftest undo-history-records-forward-ops-for-save-block-test
|
||||
(testing "worker save-block history keeps semantic forward ops for redo replay"
|
||||
(worker-undo-redo/clear-history! test-repo)
|
||||
|
||||
Reference in New Issue
Block a user