mirror of
https://github.com/penpot/penpot.git
synced 2026-09-19 10:26:14 +00:00
190 lines
9.5 KiB
Clojure
190 lines
9.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 INC Sucursal en España SL
|
|
|
|
(ns frontend-tests.data.workspace-versions-test
|
|
(:require
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.changes :as dch]
|
|
[app.main.data.persistence :as dps]
|
|
[app.main.data.workspace :as dw]
|
|
[app.main.data.workspace.pages :as dwpg]
|
|
[app.main.data.workspace.versions :as versions]
|
|
[app.main.refs :as refs]
|
|
[app.main.repo :as rp]
|
|
[app.util.timers :as tm]
|
|
[beicon.v2.core :as rx]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.mock :as mock]
|
|
[potok.v2.core :as ptk]))
|
|
|
|
(t/deftest version-creation-rejects-save-failure-and-timeout
|
|
(doseq [failure [:error :timeout]
|
|
operation [:create :plugin-create]]
|
|
(t/testing (str operation " after " failure)
|
|
(let [file-id (uuid/next)
|
|
resolved (atom [])
|
|
rejected (atom [])
|
|
requests (atom [])
|
|
state {:current-file-id file-id}
|
|
event (case operation
|
|
:create (versions/create-version)
|
|
:plugin-create (versions/create-version-from-plugins
|
|
file-id "Version"
|
|
#(swap! resolved conj %) #(swap! rejected conj %)))]
|
|
(with-redefs [refs/persistence (atom {:status (if (= failure :error) :error :saving)
|
|
:error {:type :persistence :code :save-permission-denied}})
|
|
rp/cmd! (mock/stub (fn [cmd _]
|
|
(swap! requests conj cmd)
|
|
(rx/of nil)))
|
|
rx/timeout (mock/stub (fn [_ fallback source]
|
|
(if (= failure :timeout) fallback source)))]
|
|
(->> (ptk/watch event state (rx/empty))
|
|
(rx/subs! (fn [_]) #(swap! rejected conj %)))
|
|
(t/is (empty? @requests) "Do not create a version without saving")
|
|
(t/is (empty? @resolved) "Do not resolve the plugin promise on failure")
|
|
(t/is (= 1 (count @rejected)))
|
|
(t/is (= (if (= failure :error) :save-permission-denied :save-timeout)
|
|
(:code (ex-data (first @rejected))))))))))
|
|
|
|
(t/deftest leaving-the-workspace-clears-the-preview-flags
|
|
(let [team-id (uuid/next)
|
|
file-id (uuid/next)
|
|
other-id (uuid/next)
|
|
output (atom [])
|
|
state (-> {:current-file-id file-id
|
|
:current-team-id team-id
|
|
:permissions {:can-edit true}
|
|
:files {file-id {:id file-id :revn 0 :vern 0}}
|
|
:workspace-global {:read-only? true :preview-id (uuid/next)}}
|
|
(as-> $ (ptk/update (dw/finalize-workspace team-id file-id) $))
|
|
(assoc :current-file-id other-id)
|
|
(assoc-in [:files other-id] {:id other-id :revn 0 :vern 0}))]
|
|
(t/is (nil? (get-in state [:workspace-global :preview-id])))
|
|
(when-let [result (ptk/watch (dch/commit-changes {:redo-changes [] :undo-changes []})
|
|
state (rx/empty))]
|
|
(->> result (rx/subs! #(swap! output conj %))))
|
|
(t/is (= 1 (count @output))
|
|
"Editing must work again on the next file opened in the tab")))
|
|
|
|
(t/deftest strict-persistence-wait-rejects-a-real-timeout
|
|
(t/async done
|
|
(mock/with-mocks
|
|
{refs/persistence (atom {:status :saving})}
|
|
(fn [finish]
|
|
(->> (dps/wait-persisted-or-error 0)
|
|
(rx/subs! (fn [_]
|
|
(t/is false "The stalled save must not succeed")
|
|
(finish))
|
|
(fn [cause]
|
|
(t/is (= :save-timeout (:code (ex-data cause))))
|
|
(finish)))))
|
|
done)))
|
|
|
|
(t/deftest strict-persistence-wait-observes-a-later-failure
|
|
(let [pstate (atom {:status :saving})
|
|
rejected (atom [])
|
|
resolved (atom [])]
|
|
(with-redefs [refs/persistence pstate]
|
|
(let [subscription (->> (dps/wait-persisted-or-error)
|
|
(rx/subs! #(swap! resolved conj %) #(swap! rejected conj %)))]
|
|
(try
|
|
(reset! pstate {:status :error :error {:code :missing-commit}})
|
|
(t/is (empty? @resolved))
|
|
(t/is (= :missing-commit (:code (ex-data (first @rejected)))))
|
|
(finally
|
|
(rx/dispose! subscription)))))))
|
|
|
|
(t/deftest failed-restore-returns-to-the-live-file-before-reporting
|
|
;; The plugin promise is rejected on a later tick, so every assertion about
|
|
;; the rejection runs once the event loop has drained.
|
|
(t/async done
|
|
(let [deferred (atom [])]
|
|
(doseq [operation [:ui :plugin]
|
|
failure [:saving :timeout :restore]
|
|
preview? [true false]]
|
|
(let [label (str operation " after " failure ", preview=" preview?)
|
|
file-id (uuid/next)
|
|
version-id (uuid/next)
|
|
backup {:id file-id :revn 10}
|
|
snapshot {:id file-id :revn 5}
|
|
rejected (atom [])
|
|
resolved (atom [])
|
|
requests (atom [])
|
|
initialized (atom [])
|
|
at-reject (atom nil)
|
|
store (ptk/store {:state (cond-> {:current-file-id file-id
|
|
:files {file-id backup}}
|
|
preview?
|
|
(assoc :files {file-id snapshot}
|
|
:workspace-global {:read-only? true :preview-id version-id}
|
|
:workspace-versions {:backup backup}))
|
|
:on-error #(swap! rejected conj %)})
|
|
reject (fn [cause]
|
|
(reset! at-reject
|
|
{:file (get-in @store [:files file-id])
|
|
:preview-id (get-in @store [:workspace-global :preview-id])})
|
|
(swap! rejected conj cause))
|
|
event (if (= operation :ui)
|
|
(#'versions/restore-version version-id)
|
|
(versions/restore-version-from-plugin
|
|
file-id version-id #(swap! resolved conj %) reject))]
|
|
(t/testing label
|
|
(with-redefs [refs/persistence (atom {:status (case failure
|
|
:saving :error :timeout :saving :saved)})
|
|
rp/cmd! (mock/stub (fn [cmd _]
|
|
(swap! requests conj cmd)
|
|
(rx/throw (ex-info "Restore failed" {:type :internal}))))
|
|
rx/timeout (mock/stub (fn [_ fallback source]
|
|
(if (= failure :timeout) fallback source)))
|
|
dwpg/initialize-page (mock/stub
|
|
(fn [_ _]
|
|
(fn [state]
|
|
(swap! initialized conj (get-in state [:files file-id]))
|
|
state)))]
|
|
(ptk/emit! store event)
|
|
(t/is (empty? @resolved))
|
|
(t/is (= (if (= failure :restore) [:restore-file-snapshot] []) @requests)
|
|
"Do not restore a version without saving")
|
|
(t/is (= backup (get-in @store [:files file-id])))
|
|
(t/is (nil? (get-in @store [:workspace-global :read-only?])))
|
|
(t/is (nil? (get-in @store [:workspace-global :preview-id])))
|
|
(t/is (nil? (get-in @store [:workspace-versions :backup])))
|
|
(t/is (= (if preview? [backup] []) @initialized))))
|
|
(swap! deferred conj
|
|
(fn []
|
|
(t/testing label
|
|
(t/is (= 1 (count @rejected)))
|
|
(when (= operation :plugin)
|
|
(t/is (= {:file backup :preview-id nil} @at-reject)
|
|
"The live file is back before the plugin promise rejects")))
|
|
(rx/dispose! store)))))
|
|
(tm/schedule 50 (fn []
|
|
(doseq [check @deferred] (check))
|
|
(done))))))
|
|
|
|
(t/deftest successful-restore-clears-preview-only-after-loading
|
|
(let [file-id (uuid/next)
|
|
state {:current-file-id file-id
|
|
:current-team-id (uuid/next)
|
|
:current-page-id (uuid/next)
|
|
:workspace-global {:read-only? true :preview-id (uuid/next)}
|
|
:workspace-versions {:backup {:id file-id}}}
|
|
stream (rx/subject)
|
|
events (atom [])
|
|
cleanup #(filter (ptk/type? ::versions/clear-preview-state) @events)
|
|
sub (->> (ptk/watch (#'versions/initialize-version) state stream)
|
|
(rx/subs! #(swap! events conj %)))]
|
|
(try
|
|
(t/is (empty? (cleanup)))
|
|
(rx/push! stream (ptk/data-event ::dw/bundle-fetched nil))
|
|
(t/is (= 1 (count (cleanup))))
|
|
(let [result (ptk/update (first (cleanup)) state)]
|
|
(t/is (nil? (get-in result [:workspace-global :read-only?])))
|
|
(t/is (nil? (get-in result [:workspace-global :preview-id])))
|
|
(t/is (nil? (get-in result [:workspace-versions :backup]))))
|
|
(finally
|
|
(rx/dispose! sub)))))
|