mirror of
https://github.com/penpot/penpot.git
synced 2026-11-08 11:04:44 +00:00
A rollback can remove or change shapes the UI still refers to. - The selection drops shapes that are gone and the color picker that edited them closes. - The undo history is cut at the reverted action; it stays when no entry holds a reverted change. Undo and redo commits carry real undo changes and the history index they moved from, so a reverted Ctrl+Z puts the change and the index back. - An open path edition whose shape the rollback changed closes without saving the editor's stale copy. The v2 text editor loads the reverted text again; the WASM editor already showed it. - The Alt measurements look the hovered shape up in the current data. - Plugin setters, resize() and rotate() on a removed shape throw "Shape not found", whatever the plugin's throwValidationErrors flag.
182 lines
6.9 KiB
Clojure
182 lines
6.9 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.helpers.persistence
|
|
"Fixture and builders shared by the persistence tests: a store with
|
|
persistence initialized over a mocked transport, synthetic commits, and
|
|
one-page file data to roll back."
|
|
(:require
|
|
[app.common.types.shape :as cts]
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.changes :as dch]
|
|
[app.main.data.persistence :as dps]
|
|
[app.main.errors :as errors]
|
|
[app.main.repo :as rp]
|
|
[app.main.store :as st]
|
|
[app.main.worker :as mw]
|
|
[beicon.v2.core :as rx]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.mock :as mock]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(defn local-commit
|
|
"Builds a synthetic local commit event for `file-id`: a page edit as redo,
|
|
no undo, from the `:local` source."
|
|
[file-id]
|
|
(ptk/data-event ::dch/commit
|
|
{:id (uuid/next)
|
|
:file-id file-id
|
|
:file-revn 0
|
|
:file-vern 0
|
|
:source :local
|
|
:features #{}
|
|
:redo-changes [{:type :mod-page :id (uuid/next) :name "Edited"}]
|
|
:undo-changes []}))
|
|
|
|
(defn with-persistence
|
|
"Async fixture for the persistence tests: mocks the transport, flash, the
|
|
worker and the leave-page task set (`:tasks`), and runs `f` with
|
|
persistence initialized.
|
|
|
|
Evaluates to a promise resolving once `f` settles and teardown completes;
|
|
`await` it from an `^:async` test.
|
|
|
|
By default the transport responds through the `:response` subject. Pass a
|
|
`respond` function (`(fn [cmd params] observable)`) to drive the transport
|
|
response directly instead. Delivery through the subject is asynchronous
|
|
(`observe-on :async`): await each effect — via `wait-for` — before
|
|
asserting it."
|
|
[f & [respond]]
|
|
(let [file-id (uuid/next)
|
|
response (rx/subject)
|
|
failures (atom [])
|
|
requests (atom [])
|
|
tasks (atom #{})
|
|
store (ptk/store {:state {:current-file-id file-id
|
|
:permissions {:can-edit true}
|
|
:files {file-id {:id file-id :revn 0}}}
|
|
:on-error #(t/is false (str %))})]
|
|
(mock/with-mocks*
|
|
{rp/cmd! (mock/stub (fn [cmd params]
|
|
(swap! requests conj [cmd params])
|
|
(if respond
|
|
(respond cmd params)
|
|
(->> response (rx/take 1) (rx/observe-on :async)))))
|
|
errors/flash (fn [& {:keys [cause]}]
|
|
(swap! failures conj cause))
|
|
;; Index updates of applied changes go to a worker the tests lack
|
|
mw/ask! (mock/stub (fn [_] (rx/empty)))
|
|
st/ongoing-tasks tasks}
|
|
(try
|
|
(ptk/emit! store (dps/initialize-persistence))
|
|
(await (f {:file-id file-id :response response :failures failures
|
|
:requests requests :store store :tasks tasks}))
|
|
(finally
|
|
(rx/dispose! store)
|
|
(rx/end! response))))))
|
|
|
|
(defn set-attr
|
|
"A change that sets `attr` of shape `shape-id` to `val`."
|
|
[page-id shape-id attr val]
|
|
{:type :mod-obj :id shape-id :page-id page-id
|
|
:operations [{:type :set :attr attr :val val}]})
|
|
|
|
(defn rename
|
|
"A change that renames shape `shape-id` to `name`."
|
|
[page-id shape-id name]
|
|
(set-attr page-id shape-id :name name))
|
|
|
|
(defn attr-commit
|
|
"A local commit of `file-id` that sets `attr` of the shape from `from` to
|
|
`to`, with its own undo group."
|
|
[file-id page-id shape-id attr from to]
|
|
(ptk/data-event ::dch/commit
|
|
{:id (uuid/next)
|
|
:file-id file-id
|
|
:file-revn 0
|
|
:file-vern 0
|
|
:source :local
|
|
:features #{}
|
|
:undo-group (uuid/next)
|
|
:redo-changes [(set-attr page-id shape-id attr to)]
|
|
:undo-changes [(set-attr page-id shape-id attr from)]}))
|
|
|
|
(defn rename-commit
|
|
"A local commit of `file-id` that renames the shape from `from` to `to`,
|
|
with its own undo group."
|
|
[file-id page-id shape-id from to]
|
|
(attr-commit file-id page-id shape-id :name from to))
|
|
|
|
(defn remote-commit
|
|
"A peer's commit of `changes` to `file-id` at revision `revn`."
|
|
[file-id revn changes]
|
|
(dch/commit {:file-id file-id
|
|
:file-revn revn
|
|
:file-vern 0
|
|
:source :remote
|
|
:save-undo? false
|
|
:redo-changes changes
|
|
:undo-changes []}))
|
|
|
|
(defn file-data
|
|
"File data with one page holding one rectangle named `name`, with `attrs`
|
|
on top of its defaults."
|
|
[page-id shape-id name & {:as attrs}]
|
|
{:pages [page-id]
|
|
:pages-index {page-id {:id page-id
|
|
:name "Page"
|
|
:objects {uuid/zero (cts/setup-shape {:id uuid/zero :type :frame :shapes [shape-id]})
|
|
shape-id (merge (cts/setup-shape {:id shape-id :type :rect :name name
|
|
:parent-id uuid/zero :frame-id uuid/zero})
|
|
attrs)}}}})
|
|
|
|
(defn add-shape
|
|
"`data` with `shape` added as the last child of the root of `page-id`."
|
|
[data page-id shape]
|
|
(-> data
|
|
(assoc-in [:pages-index page-id :objects (:id shape)] shape)
|
|
(update-in [:pages-index page-id :objects uuid/zero :shapes] conj (:id shape))))
|
|
|
|
(defn open-file
|
|
"A state update that opens `page-id` of `file-id` with `data` loaded."
|
|
[file-id page-id data]
|
|
#(-> %
|
|
(assoc :current-page-id page-id)
|
|
(assoc-in [:files file-id :data] data)))
|
|
|
|
(defn open-file-with-library
|
|
"`open-file` that also loads library `lib-id` with `lib-data`."
|
|
[file-id lib-id page-id data lib-data]
|
|
(comp #(assoc-in % [:files lib-id] {:id lib-id :revn 0 :data lib-data})
|
|
(open-file file-id page-id data)))
|
|
|
|
(defn shape-attr
|
|
[store file-id page-id shape-id attr]
|
|
(get-in @store [:files file-id :data :pages-index page-id :objects shape-id attr]))
|
|
|
|
(defn shape-name
|
|
[store file-id page-id shape-id]
|
|
(shape-attr store file-id page-id shape-id :name))
|
|
|
|
(defn undo-entry
|
|
"The undo history entry `commit` would add."
|
|
[commit]
|
|
(let [{:keys [undo-group undo-changes redo-changes]} @commit]
|
|
{:undo-group undo-group
|
|
:undo-groups #{undo-group}
|
|
:undo-changes undo-changes
|
|
:redo-changes redo-changes
|
|
:tags #{}}))
|
|
|
|
(defn rejection
|
|
"A save answer that rejects `commit-ids`, the first one failing
|
|
validation."
|
|
[& commit-ids]
|
|
{:revn 0
|
|
:rejected {:commit-ids (vec commit-ids)
|
|
:failed-commit-id (first commit-ids)
|
|
:error {:type :validation :code :referential-integrity}}})
|