mirror of
https://github.com/penpot/penpot.git
synced 2026-11-07 18:40:41 +00:00
- Ending a drag inside the frame the shapes already belong to sent an empty reparent save and bumped the revision. The move commits only when some shape changes parent. - When a user restored a version, other sessions reloaded the file with no notice. They now see a dialog naming who restored which version (by its name, else its date), saying that later changes are no longer in the file and, when they had changes not saved yet, that those were lost. - Exports drawn in the browser (PDF, SVG, and PNG when the WASM renderer draws it) and duplicating a file from the dashboard started at once, so an export could show changes a pending save then rejected, and a copy could miss queued edits. Both wait for the file's pending saves. A client export warns that it includes unsaved changes; a copy warns that it misses them.
146 lines
6.5 KiB
Clojure
146 lines
6.5 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.workspace-notifications-test
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.time :as ct]
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.changes :as dch]
|
|
[app.main.data.modal :as modal]
|
|
[app.main.data.notifications :as ntf]
|
|
[app.main.data.workspace :as-alias dw]
|
|
[app.main.data.workspace.notifications :as dwntf]
|
|
[app.util.i18n :as i18n]
|
|
[beicon.v2.core :as rx]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.async :as async]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(defn- file-change-msg
|
|
[extra]
|
|
(merge {:type :file-change
|
|
:profile-id (uuid/next)
|
|
:file-id (uuid/next)
|
|
:session-id (uuid/next)
|
|
:revn 1
|
|
:vern 0
|
|
:changes []}
|
|
extra))
|
|
|
|
(defn- ^:async watch-file-change
|
|
[msg]
|
|
(let [events (atom [])]
|
|
(await (async/observe (ptk/watch (dwntf/handle-file-change msg) {} (rx/empty))
|
|
:on-next #(swap! events conj %)))
|
|
@events))
|
|
|
|
(t/deftest ^:async repaired-file-change-applies-and-notifies
|
|
(let [events (await (watch-file-change (file-change-msg {:repaired true})))
|
|
types (mapv ptk/type events)
|
|
show (d/seek #(= ::ntf/show (ptk/type %)) events)
|
|
state (ptk/update show {})]
|
|
(t/is (= [::dch/commit ::ntf/show] types))
|
|
(t/is (= :info (get-in state [:notification :level])))
|
|
(t/is (nil? (get-in state [:notification :timeout]))
|
|
"the toast stays until the user closes it")
|
|
(t/is (= (i18n/tr "workspace.notifications.file-repaired")
|
|
(get-in state [:notification :content])))))
|
|
|
|
(t/deftest ^:async ordinary-file-change-does-not-notify
|
|
(let [events (await (watch-file-change (file-change-msg {})))]
|
|
(t/is (= [::dch/commit] (mapv ptk/type events)))))
|
|
|
|
(defn- restored-events
|
|
"Events the watch of `handle-file-restored` emits for `msg` on `state`."
|
|
[state msg]
|
|
(let [events (atom [])]
|
|
(-> (async/observe (ptk/watch (dwntf/handle-file-restored msg) state nil)
|
|
:on-next #(swap! events conj %))
|
|
(.then (fn [_] @events)))))
|
|
|
|
(defn- shown-modal
|
|
[events]
|
|
(some #(when (= ::modal/show-modal (ptk/type %))
|
|
(::modal/modal (ptk/update % {})))
|
|
events))
|
|
|
|
;; Scenario: a peer restores an earlier version of the open file. The file
|
|
;; reloads and a modal names the peer. Proves: the reload is not silent.
|
|
(t/deftest ^:async a-peer-restore-reloads-and-names-the-peer
|
|
(let [file-id (uuid/next)
|
|
session-id (uuid/next)
|
|
profile-id (uuid/next)
|
|
state {:current-file-id file-id
|
|
:files {file-id {:id file-id :vern 1}}
|
|
:workspace-presence {session-id {:profile-id profile-id}}
|
|
:profiles {profile-id {:id profile-id :fullname "Ada"}}}
|
|
events (await (restored-events state {:type :file-restored
|
|
:file-id file-id
|
|
:session-id session-id
|
|
:vern 2}))
|
|
modal (shown-modal events)]
|
|
(t/is (some #(= ::dw/reload-current-file (ptk/type %)) events))
|
|
(t/is (= :file-restored (:type modal)))
|
|
(t/is (= "Ada" (get-in modal [:props :fullname])))
|
|
(t/is (false? (get-in modal [:props :unsaved])))))
|
|
|
|
;; Scenario: a peer restores a version they named, while this tab has a
|
|
;; rename not saved yet and the restorer has no presence. Proves: the modal
|
|
;; names the restorer by profile and the version by its label, and says the
|
|
;; unsaved rename is lost.
|
|
(t/deftest ^:async a-peer-restore-names-the-version-and-the-lost-edits
|
|
(let [file-id (uuid/next)
|
|
profile-id (uuid/next)
|
|
created-at (ct/now)
|
|
commit-id (uuid/next)
|
|
state {:current-file-id file-id
|
|
:files {file-id {:id file-id :vern 1}}
|
|
:profiles {profile-id {:id profile-id :fullname "Ada"}}
|
|
:persistence {:queue [commit-id]
|
|
:index {commit-id {:id commit-id
|
|
:file-id file-id
|
|
:file-vern 1
|
|
:commits [{:redo-changes [{:type :mod-page}]}]}}}}
|
|
events (await (restored-events state {:type :file-restored
|
|
:file-id file-id
|
|
:session-id (uuid/next)
|
|
:profile-id profile-id
|
|
:label "Hand-off v2"
|
|
:created-at created-at
|
|
:vern 2}))
|
|
props (:props (shown-modal events))]
|
|
(t/is (= "Ada" (:fullname props)))
|
|
(t/is (= "Hand-off v2" (:label props)))
|
|
(t/is (= created-at (:created-at props)))
|
|
(t/is (true? (:unsaved props)))))
|
|
|
|
;; Scenario: the restoring session left before its message arrived, so it
|
|
;; has no presence. Proves: the modal still shows, without a name.
|
|
(t/deftest ^:async a-restore-from-an-unknown-session-shows-no-name
|
|
(let [file-id (uuid/next)
|
|
state {:current-file-id file-id
|
|
:files {file-id {:id file-id :vern 1}}}
|
|
events (await (restored-events state {:type :file-restored
|
|
:file-id file-id
|
|
:session-id (uuid/next)
|
|
:vern 2}))
|
|
modal (shown-modal events)]
|
|
(t/is (= :file-restored (:type modal)))
|
|
(t/is (nil? (get-in modal [:props :fullname])))))
|
|
|
|
;; Scenario: the file already shows the restored version. Proves: no reload
|
|
;; and no modal for a version the user already sees.
|
|
(t/deftest ^:async a-restore-of-the-loaded-version-does-nothing
|
|
(let [file-id (uuid/next)
|
|
state {:current-file-id file-id
|
|
:files {file-id {:id file-id :vern 2}}}
|
|
events (await (restored-events state {:type :file-restored
|
|
:file-id file-id
|
|
:session-id (uuid/next)
|
|
:vern 2}))]
|
|
(t/is (empty? events))))
|