mirror of
https://github.com/penpot/penpot.git
synced 2026-11-05 17:40:39 +00:00
A rollback reverts the local data with a rebase, which is not a commit, so the parts of the workspace that follow commits missed it: - Comment pins moved in the reverted action stayed where they were, on screen and on the server, and pins on a board the rollback removed kept pointing to it. They go back to the reverted position, and pins on a removed board move to the page root. - Focus mode stayed on with no shape left when the rollback removed the focused ones, and the next shape drawn turned it on again. It ends when its last shape goes, and the selection drops removed shapes. - Component thumbnails kept the rejected look. The thumbnails of the reverted mains are rendered and uploaded again, and an upload whose shapes changed during the render is skipped. - Under WASM, an open text editor reset to the reverted content but the canvas kept the old layout. It is laid out again. - A library sync whose save failed showed no update notice again once the release undid it. The notice is checked again after that rollback.
176 lines
8.3 KiB
Clojure
176 lines
8.3 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
|
|
|
(ns frontend-tests.data.persistence-commits-test
|
|
"Tests for the pure commit bookkeeping of persistence
|
|
(`app.main.data.persistence.commits`)."
|
|
(:require
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.persistence.commits :as pcm]
|
|
[cljs.test :as t :include-macros true]))
|
|
|
|
(defn- commit
|
|
[file-id undo-group]
|
|
{:id (uuid/next)
|
|
:file-id file-id
|
|
:file-revn 0
|
|
:file-vern 0
|
|
:features #{}
|
|
:source :local
|
|
:undo-group undo-group
|
|
:redo-changes [{:type :mod-page :id (uuid/next) :name "redo"}]
|
|
:undo-changes [{:type :mod-page :id (uuid/next) :name "undo"}]
|
|
:remote-seq 0})
|
|
|
|
(defn- queued
|
|
"Persistence state with `entries` queued, in order."
|
|
[& entries]
|
|
{:queue (into #queue [] (map :id) entries)
|
|
:index (into {} (map (juxt :id identity)) entries)})
|
|
|
|
(t/deftest a-queue-entry-keeps-only-what-a-save-needs
|
|
(let [file-id (uuid/next)
|
|
c1 (commit file-id (uuid/next))
|
|
c2 (commit file-id (uuid/next))
|
|
entry (pcm/queue-entry [c1 c2])]
|
|
(t/is (= (:id c1) (:id entry)))
|
|
(t/is (= file-id (:file-id entry)))
|
|
(t/is (not (contains? entry :redo-changes)))
|
|
(t/is (= [(:id c1) (:id c2)] (mapv :id (:commits entry))))
|
|
(t/is (not (contains? (first (:commits entry)) :source)))))
|
|
|
|
(t/deftest pending-commits-are-the-queued-then-the-buffered-of-one-file
|
|
(let [file-id (uuid/next)
|
|
other-id (uuid/next)
|
|
c1 (commit file-id nil)
|
|
c2 (commit other-id nil)
|
|
c3 (commit file-id nil)
|
|
pstate (assoc (queued (pcm/queue-entry [c1]) (pcm/queue-entry [c2]))
|
|
:buffer [c3])]
|
|
(t/is (= [(:id c1) (:id c3)] (mapv :id (pcm/pending-commits pstate file-id))))))
|
|
|
|
(t/deftest a-rollback-plan-reverts-whole-undo-groups-across-files
|
|
(let [file-id (uuid/next)
|
|
lib-id (uuid/next)
|
|
group (uuid/next)
|
|
kept (commit file-id (uuid/next))
|
|
bad (commit file-id (uuid/next))
|
|
lib-1 (commit lib-id (uuid/next))
|
|
lib-2 (commit lib-id (uuid/next))
|
|
linked (commit lib-id group)
|
|
later (commit file-id group)
|
|
saved (assoc (commit lib-id group) :file-id lib-id)
|
|
head (pcm/queue-entry [kept bad])
|
|
pstate (-> (queued head (pcm/queue-entry [lib-1 linked lib-2]))
|
|
(assoc :buffer [later]
|
|
:saved [saved]))
|
|
plan (pcm/rollback-plan pstate head {:commit-ids [(:id bad)]})]
|
|
(t/testing "this file reverts from the rejected commit on"
|
|
(t/is (= [(:id bad) (:id later)]
|
|
(mapv :id (get-in plan [:files file-id :reverted])))))
|
|
(t/testing "the library reverts from the commit that shares a group on"
|
|
(t/is (= [(:id linked) (:id lib-2)]
|
|
(mapv :id (get-in plan [:files lib-id :reverted]))))
|
|
(t/is (= [(:id lib-1)] (mapv :id (get-in plan [:files lib-id :redo])))))
|
|
(t/testing "saved commits of a reverted group are compensated"
|
|
(t/is (= [(:id saved)] (mapv :id (:compensate plan)))))
|
|
(t/is (= {:reverted 5 :files 2} (pcm/rollback-summary plan)))
|
|
|
|
(t/testing "the rolled back commits leave the queue and the buffer"
|
|
(let [pstate' (pcm/without-rolled-back pstate head plan)]
|
|
(t/is (= [(:id kept)] (mapv :id (get-in pstate' [:index (:id head) :commits]))))
|
|
(t/is (= [(:id lib-1)]
|
|
(mapv :id (get-in pstate' [:index (:id lib-1) :commits]))))
|
|
(t/is (empty? (:buffer pstate')))
|
|
(t/is (empty? (:saved pstate')))))))
|
|
|
|
(t/deftest an-entry-left-without-commits-leaves-the-queue
|
|
(let [file-id (uuid/next)
|
|
lib-id (uuid/next)
|
|
group (uuid/next)
|
|
bad (commit file-id group)
|
|
linked (commit lib-id group)
|
|
head (pcm/queue-entry [bad])
|
|
lib (pcm/queue-entry [linked])
|
|
pstate (queued head lib)
|
|
plan (pcm/rollback-plan pstate head {:commit-ids [(:id bad)]})
|
|
pstate' (pcm/without-rolled-back pstate head plan)]
|
|
(t/is (= [(:id head)] (vec (:queue pstate'))) "the in-flight entry stays until it is discarded")
|
|
(t/is (not (contains? (:index pstate') (:id lib))))))
|
|
|
|
(t/deftest compensation-undoes-newest-first
|
|
(let [c1 {:redo-changes [:r1] :undo-changes [:u1]}
|
|
c2 {:redo-changes [:r2] :undo-changes [:u2]}]
|
|
(t/is (= {:redo-changes [:u2 :u1] :undo-changes [:r1 :r2]}
|
|
(pcm/compensation-changes [c1 c2])))))
|
|
|
|
(t/deftest a-rollback-plan-names-the-index-before-the-oldest-reverted-undo
|
|
(let [file-id (uuid/next)
|
|
bad (commit file-id (uuid/next))
|
|
undo-1 (assoc (commit file-id (uuid/next)) :undo-index 4)
|
|
undo-2 (assoc (commit file-id (uuid/next)) :undo-index 3)
|
|
head (pcm/queue-entry [bad undo-1])
|
|
pstate (assoc (queued head) :buffer [undo-2])]
|
|
(t/is (= 4 (:undo-index (pcm/rollback-plan pstate head {:commit-ids [(:id bad) (:id undo-1)]}))))
|
|
(t/is (nil? (:undo-index (pcm/rollback-plan (queued (pcm/queue-entry [bad]))
|
|
(pcm/queue-entry [bad])
|
|
{:commit-ids [(:id bad)]}))))))
|
|
|
|
(t/deftest saved-commits-are-kept-while-a-pending-commit-shares-their-group
|
|
(let [file-id (uuid/next)
|
|
group (uuid/next)
|
|
shared (commit file-id group)
|
|
alone (commit file-id (uuid/next))
|
|
pending (commit file-id group)
|
|
old (commit file-id (uuid/next))
|
|
entry (pcm/queue-entry [shared alone])
|
|
pstate (pcm/keep-saved-commits {:queue #queue [] :index {} :buffer [pending] :saved [old]}
|
|
entry)]
|
|
(t/is (= [(:id shared)] (mapv :id (:saved pstate))))
|
|
(t/is (= file-id (:file-id (first (:saved pstate))))
|
|
"a saved commit carries the file it was saved to")))
|
|
|
|
(t/deftest remote-changes-older-than-the-oldest-pending-commit-are-forgotten
|
|
(let [file-id (uuid/next)
|
|
pending (assoc (commit file-id nil) :remote-seq 2)
|
|
pstate {:queue #queue [] :index {} :buffer [pending]
|
|
:remote-changes {file-id [{:seq 1 :changes [:old]} {:seq 3 :changes [:new]}]}}]
|
|
(t/is (= [{:seq 3 :changes [:new]}]
|
|
(get-in (pcm/prune-remote-changes pstate file-id) [:remote-changes file-id])))
|
|
(t/is (not (contains? (:remote-changes (pcm/prune-remote-changes (dissoc pstate :buffer) file-id))
|
|
file-id))
|
|
"a file with nothing pending keeps no remote changes")))
|
|
|
|
(t/deftest a-release-plan-reverts-every-pending-commit-of-the-file-and-its-groups
|
|
(let [file-id (uuid/next)
|
|
lib-id (uuid/next)
|
|
group (uuid/next)
|
|
edit (commit file-id (uuid/next))
|
|
linked (commit file-id group)
|
|
lib (commit lib-id group)
|
|
pstate (assoc (queued (pcm/queue-entry [edit linked]))
|
|
:buffer [lib])
|
|
plan (pcm/release-plan pstate file-id)]
|
|
(t/is (= [(:id edit) (:id linked)] (mapv :id (get-in plan [:files file-id :reverted]))))
|
|
(t/is (empty? (get-in plan [:files file-id :redo])))
|
|
(t/is (= [(:id lib)] (mapv :id (get-in plan [:files lib-id :reverted])))
|
|
"the rest of the undo group goes too")))
|
|
|
|
(t/deftest rollback-targets-name-the-files-pages-and-page-shapes-of-the-reverted-changes
|
|
(let [[f1 f2 p1 p2 p3 s1 s2 s3 s4 s5 c1] (repeatedly 11 uuid/next)
|
|
plan {:files {f1 {:reverted [{:redo-changes [{:type :add-obj :page-id p1 :id s1}
|
|
{:type :mov-objects :page-id p1 :shapes [s2 s3]}
|
|
{:type :mod-obj :component-id (uuid/next) :id s4}
|
|
{:type :mod-page :id p2}
|
|
{:type :set-comment-thread-position :page-id p1
|
|
:comment-thread-id c1}]}]}}
|
|
:compensate [{:file-id f2 :redo-changes [{:type :del-obj :page-id p3 :id s5}]}]}]
|
|
(t/is (= {:file-ids #{f1 f2}
|
|
:page-ids #{p1 p2 p3}
|
|
:shape-ids #{s1 s2 s3 s5}
|
|
:comment-thread-ids #{c1}}
|
|
(pcm/rollback-targets plan)))))
|