mirror of
https://github.com/penpot/penpot.git
synced 2026-11-09 11:30:54 +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.
503 lines
26 KiB
Clojure
503 lines
26 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.exports-assets-test
|
|
(:require
|
|
[app.common.uuid :as uuid]
|
|
[app.main.data.exports.assets :as de]
|
|
[app.main.data.exports.wasm :as wasm.exports]
|
|
[app.main.data.persistence :as dwp]
|
|
[app.main.errors :as errors]
|
|
[app.main.repo :as repo]
|
|
[app.main.store :as st]
|
|
[app.main.worker :as mw]
|
|
[app.render-wasm.api :as wasm.api]
|
|
[app.util.dom :as dom]
|
|
[app.util.i18n :as i18n]
|
|
[app.util.webapi :as wapi]
|
|
[app.util.websocket :as ws]
|
|
[beicon.v2.core :as rx]
|
|
[cljs.test :as t :include-macros true]
|
|
[frontend-tests.helpers.async :as async]
|
|
[frontend-tests.helpers.events :as the]
|
|
[frontend-tests.helpers.mock :as mock]
|
|
[potok.v2.core :as ptk]))
|
|
(def ^:private export {:id (uuid/next)
|
|
:object-id (uuid/next)
|
|
:type :png
|
|
:suffix ""
|
|
:scale 1})
|
|
|
|
(defn- export-with-name
|
|
[name]
|
|
(merge export {:name name}))
|
|
|
|
(defn- test-state
|
|
[]
|
|
{:profile-id (:id export)
|
|
:ws-conn nil})
|
|
|
|
(defn- wasm-state
|
|
[]
|
|
(assoc (test-state) :features #{"render-wasm/v1"}))
|
|
|
|
(t/deftest sanitize-filename-strips-reserved-chars-and-dedupes
|
|
(t/is (= "a_b.png"
|
|
(wasm.exports/sanitize-filename {:name "a/b" :suffix "" :type :png} #{})))
|
|
(t/is (= "board-2.pdf"
|
|
(wasm.exports/sanitize-filename {:name "board" :suffix "" :type :pdf}
|
|
#{"board.pdf"}))))
|
|
|
|
(t/deftest normalize-export-preserves-existing-name
|
|
(t/is (= (export-with-name "Layer 1")
|
|
(de/normalize-export (export-with-name "Layer 1")))))
|
|
|
|
(t/deftest normalize-export-replaces-nil-name-with-object-id
|
|
(t/is (= (export-with-name (str (:object-id export)))
|
|
(de/normalize-export (assoc export :name nil)))))
|
|
|
|
(t/deftest normalize-export-replaces-empty-name-with-object-id
|
|
(t/is (= (export-with-name (str (:object-id export)))
|
|
(de/normalize-export (assoc export :name "")))))
|
|
|
|
(t/deftest request-simple-export-sends-normalized-export
|
|
(t/async done
|
|
(let [export (export-with-name "")
|
|
observed (atom nil)]
|
|
(mock/with-mocks {repo/cmd! (mock/stub (fn [_ params]
|
|
(reset! observed params)
|
|
(rx/of {:filename "export.png"
|
|
:mtype "image/png"
|
|
:uri "blob:export"})))
|
|
dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/of ::force-persisted)))
|
|
dom/trigger-download-uri (mock/stub (fn [& _] nil))}
|
|
(fn [done']
|
|
(let [completed (fn [_state]
|
|
(t/is (= (export-with-name (str (:object-id export)))
|
|
(-> @observed :exports first))))]
|
|
(ptk/emit! (the/prepare-store (test-state) done' completed)
|
|
(de/request-simple-export {:export export})
|
|
:the/end)))
|
|
done))))
|
|
|
|
(t/deftest request-multiple-export-sends-normalized-enabled-exports
|
|
(t/async done
|
|
(let [exports [{:id "enabled-1"
|
|
:object-id "enabled-1"
|
|
:shape {:id "enabled-1"}
|
|
:type :png
|
|
:suffix ""
|
|
:scale 1
|
|
:enabled true
|
|
:name ""}]
|
|
observed (atom nil)]
|
|
(mock/with-mocks {repo/cmd! (mock/stub (fn [_ params]
|
|
(reset! observed params)
|
|
(rx/of {:id (:id export)})))
|
|
ws/get-rcv-stream (mock/stub (fn [_] (rx/empty)))
|
|
st/ongoing-tasks (atom #{})}
|
|
(fn [done']
|
|
(let [completed (fn [_state]
|
|
(t/is (= [{:id "enabled-1"
|
|
:object-id "enabled-1"
|
|
:shape {:id "enabled-1"}
|
|
:type :png
|
|
:suffix ""
|
|
:scale 1
|
|
:enabled true
|
|
:name "enabled-1"}]
|
|
(:exports @observed))))]
|
|
(ptk/emit! (the/prepare-store (test-state) done' completed)
|
|
(de/request-multiple-export {:exports exports})
|
|
:the/end)))
|
|
done))))
|
|
|
|
(defn- export-store
|
|
"A store for the export tests that records store errors in `store-errors`."
|
|
[state store-errors]
|
|
(ptk/store {:state state :on-error #(swap! store-errors conj %)}))
|
|
|
|
(defn- unsaved-state
|
|
"Test state whose save queue still holds a change of `file-id`."
|
|
[file-id]
|
|
(let [commit-id (uuid/next)]
|
|
(assoc (test-state)
|
|
:persistence {:queue (conj #queue [] commit-id)
|
|
:index {commit-id {:id commit-id :file-id file-id}}})))
|
|
|
|
;; Scenario: the exporter fails a single export. Proves: the user gets a
|
|
;; toast and stays in the workspace; the failure is not raised to the global
|
|
;; error handler, which would replace the workspace with an error page.
|
|
(t/deftest ^:async failed-simple-export-shows-a-toast-instead-of-raising
|
|
(let [store-errors (atom [])
|
|
flashed (atom [])
|
|
store (export-store (test-state) store-errors)]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [_ _]
|
|
(rx/throw (ex-info "exporter failed"
|
|
{:type :server-error
|
|
:code :unhandled}))))
|
|
dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/empty)))
|
|
errors/flash (fn [& {:keys [hint type]}]
|
|
(swap! flashed conj [hint type]))
|
|
i18n/tr (mock/stub #(str "translated:" %))}
|
|
(ptk/emit! store (de/request-simple-export {:export export}))
|
|
(await (async/wait-for #(seq @flashed) "the failure is shown"))
|
|
(await (async/settle))
|
|
(t/is (= [["translated:errors.export-failed" :unhandled]] @flashed)
|
|
"an exporter defect reports as unhandled")
|
|
(t/is (empty? @store-errors) "nothing reaches the global error handler")
|
|
(t/is (nil? (:export @store)) "the export state is cleared")))))
|
|
|
|
;; Scenario: the export request of a multiple export fails. Proves: the user
|
|
;; gets a toast, and the leave-page warning the export turned on is released
|
|
;; even though no progress message will ever arrive.
|
|
(t/deftest ^:async failed-multiple-export-shows-a-toast-and-releases-the-leave-warning
|
|
(let [store-errors (atom [])
|
|
flashed (atom [])
|
|
tasks (atom #{})
|
|
store (export-store (test-state) store-errors)]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [_ _]
|
|
(rx/throw (ex-info "exporter failed"
|
|
{:type :server-error}))))
|
|
dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/empty)))
|
|
ws/get-rcv-stream (mock/stub (fn [_] (rx/subject)))
|
|
st/ongoing-tasks tasks
|
|
errors/flash (fn [& {:keys [hint]}] (swap! flashed conj hint))
|
|
i18n/tr (mock/stub #(str "translated:" %))}
|
|
(ptk/emit! store (de/request-multiple-export {:exports [(assoc export :enabled true)]}))
|
|
(await (async/wait-for #(seq @flashed) "the failure is shown"))
|
|
(await (async/wait-for #(not (contains? @tasks :export))
|
|
"the leave-page warning is released"))
|
|
(t/is (= ["translated:errors.export-failed"] @flashed))
|
|
(t/is (empty? @store-errors) "nothing reaches the global error handler")))))
|
|
|
|
;; Scenario: an export starts while its file has a change not saved yet.
|
|
;; Proves: the user is warned that the export may miss it, and the export
|
|
;; runs anyway.
|
|
(t/deftest ^:async export-warns-when-the-file-has-unsaved-changes
|
|
(let [file-id (uuid/next)
|
|
downloaded (atom false)
|
|
store (export-store (unsaved-state file-id) (atom []))]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [_ _]
|
|
(rx/of {:filename "export.png"
|
|
:mtype "image/png"
|
|
:uri "blob:export"})))
|
|
dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/empty)))
|
|
dom/trigger-download-uri (mock/stub (fn [& _] (reset! downloaded true)))
|
|
i18n/tr (mock/stub #(str "translated:" %))}
|
|
(ptk/emit! store (de/request-simple-export {:export (assoc export :file-id file-id)}))
|
|
(await (async/wait-for #(true? @downloaded) "the export runs anyway"))
|
|
(t/is (= "translated:workspace.export.unsaved-changes"
|
|
(get-in @store [:notification :content])))
|
|
(t/is (= :warning (get-in @store [:notification :level])))))))
|
|
|
|
;; Scenario: an export starts with every change of its file saved. Proves:
|
|
;; no warning is shown.
|
|
(t/deftest ^:async export-shows-no-warning-when-the-file-is-saved
|
|
(let [downloaded (atom false)
|
|
store (export-store (test-state) (atom []))]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [_ _]
|
|
(rx/of {:filename "export.png"
|
|
:mtype "image/png"
|
|
:uri "blob:export"})))
|
|
dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/empty)))
|
|
dom/trigger-download-uri (mock/stub (fn [& _] (reset! downloaded true)))}
|
|
(ptk/emit! store (de/request-simple-export {:export (assoc export :file-id (uuid/next))}))
|
|
(await (async/wait-for #(true? @downloaded) "the export runs"))
|
|
(await (async/settle))
|
|
(t/is (nil? (:notification @store)))))))
|
|
|
|
;; Scenario: WASM is on and the user exports one shape. Proves: the worker
|
|
;; builds a single blob (no ZIP); the exporter is not called.
|
|
;; Stub `export-target`: without a module it is nil; routing still stays
|
|
;; local (never the server API under wasm).
|
|
(t/deftest ^:async wasm-simple-export-stays-local
|
|
(let [cmd-calls (atom [])
|
|
worker-kind (atom nil)
|
|
downloaded (atom nil)
|
|
store (export-store (wasm-state) (atom []))
|
|
page-id (uuid/next)
|
|
file-id (uuid/next)
|
|
one (assoc export :enabled true :name "layer"
|
|
:page-id page-id :file-id file-id)]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [cmd params]
|
|
(swap! cmd-calls conj [cmd params])
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
wasm.api/export-target (mock/stub (constantly :client))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [msg]
|
|
(reset! worker-kind (:kind msg))
|
|
(rx/of {:type :done
|
|
:filename "layer.png"
|
|
:mtype "image/png"
|
|
:blob (js/Blob.)})))
|
|
wapi/create-uri (mock/stub (constantly "blob:png"))
|
|
dom/trigger-download-uri (mock/stub (fn [filename mtype uri]
|
|
(reset! downloaded [filename mtype uri])))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-simple-export {:export one}))
|
|
(await (async/wait-for #(some? @downloaded) "the file downloads"))
|
|
(t/is (empty? @cmd-calls) "no exporter RPC")
|
|
(t/is (= :single @worker-kind))
|
|
(t/is (= ["layer.png" "image/png" "blob:png"] @downloaded))))))
|
|
|
|
;; Scenario: WASM says the raster is too big for the GPU (legacy `:exporter`
|
|
;; code). Proves: still local worker download, no server RPC — CPU raster
|
|
;; runs inside the worker via `export-bytes`.
|
|
(t/deftest ^:async wasm-oversized-for-gpu-stays-local
|
|
(let [cmd-calls (atom [])
|
|
worker-kind (atom nil)
|
|
downloaded (atom nil)
|
|
store (export-store (wasm-state) (atom []))
|
|
one (assoc export :enabled true :name "huge")]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [cmd params]
|
|
(swap! cmd-calls conj [cmd params])
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
wasm.api/export-target (mock/stub (constantly :exporter))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [msg]
|
|
(reset! worker-kind (:kind msg))
|
|
(rx/of {:type :done
|
|
:filename "huge.png"
|
|
:mtype "image/png"
|
|
:blob (js/Blob.)})))
|
|
wapi/create-uri (mock/stub (constantly "blob:png"))
|
|
dom/trigger-download-uri (mock/stub (fn [filename mtype uri]
|
|
(reset! downloaded [filename mtype uri])))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-simple-export {:export one}))
|
|
(await (async/wait-for #(some? @downloaded) "the file downloads"))
|
|
(t/is (empty? @cmd-calls) "no exporter RPC")
|
|
(t/is (= :single @worker-kind))
|
|
(t/is (= ["huge.png" "image/png" "blob:png"] @downloaded))))))
|
|
|
|
;; Scenario: WASM reports true `:too-large` (beyond raster limits). Proves:
|
|
;; toast and no RPC — the exporter would fail the same way.
|
|
(t/deftest ^:async wasm-true-too-large-shows-toast
|
|
(let [cmd-calls (atom [])
|
|
store (export-store (wasm-state) (atom []))
|
|
one (assoc export :enabled true :name "impossible")]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [cmd params]
|
|
(swap! cmd-calls conj [cmd params])
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
wasm.api/export-target (mock/stub (constantly :too-large))
|
|
i18n/tr (mock/stub #(str "translated:" %))}
|
|
(ptk/emit! store (de/request-simple-export {:export one}))
|
|
(await (async/wait-for #(some? (:notification @store)) "the toast shows"))
|
|
(t/is (empty? @cmd-calls) "no exporter RPC")
|
|
(t/is (= "translated:errors.export-too-large"
|
|
(get-in @store [:notification :content])))
|
|
(t/is (= :error (get-in @store [:notification :level])))))))
|
|
|
|
;; Scenario: WASM is on and the user exports several shapes. Proves: nothing
|
|
;; hits the exporter; the worker builds a ZIP and the client downloads it.
|
|
(t/deftest ^:async wasm-multiple-export-stays-local
|
|
(let [cmd-calls (atom [])
|
|
worker-cmd (atom nil)
|
|
downloaded (atom nil)
|
|
store (export-store (wasm-state) (atom []))
|
|
page-id (uuid/next)
|
|
file-id (uuid/next)
|
|
exports [(assoc export :enabled true :name "a" :page-id page-id :file-id file-id)
|
|
(assoc export :id (uuid/next)
|
|
:object-id (uuid/next)
|
|
:page-id page-id
|
|
:file-id file-id
|
|
:enabled true
|
|
:name "b")]]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [cmd params]
|
|
(swap! cmd-calls conj [cmd params])
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [msg]
|
|
(reset! worker-cmd (:cmd msg))
|
|
(rx/of {:type :done
|
|
:filename "page.zip"
|
|
:mtype "application/zip"
|
|
:blob (js/Blob.)})))
|
|
wapi/create-uri (mock/stub (constantly "blob:zip"))
|
|
dom/trigger-download-uri (mock/stub (fn [filename mtype uri]
|
|
(reset! downloaded [filename mtype uri])))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-multiple-export {:exports exports :name "page"}))
|
|
(await (async/wait-for #(some? @downloaded) "the zip downloads"))
|
|
(t/is (empty? @cmd-calls) "no exporter RPC")
|
|
(t/is (= :exports/run-local @worker-cmd))
|
|
(t/is (= ["page.zip" "application/zip" "blob:zip"] @downloaded))))))
|
|
|
|
;; Scenario: WASM is on and the user exports boards as PDF. Proves: worker
|
|
;; multi-page PDF, no exporter call.
|
|
(t/deftest ^:async wasm-export-frames-stays-local
|
|
(let [cmd-calls (atom [])
|
|
worker-kind (atom nil)
|
|
downloaded (atom nil)
|
|
store (export-store (wasm-state) (atom []))
|
|
page-id (uuid/next)
|
|
file-id (uuid/next)
|
|
exports [(assoc export :enabled true :name "board-1"
|
|
:page-id page-id :file-id file-id)
|
|
(assoc export :id (uuid/next)
|
|
:object-id (uuid/next)
|
|
:page-id page-id
|
|
:file-id file-id
|
|
:enabled true
|
|
:name "board-2")]]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [cmd params]
|
|
(swap! cmd-calls conj [cmd params])
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [msg]
|
|
(reset! worker-kind (:kind msg))
|
|
(rx/of {:type :progress :done 1 :total 2}
|
|
{:type :progress :done 2 :total 2}
|
|
{:type :done
|
|
:filename "page.pdf"
|
|
:mtype "application/pdf"
|
|
:blob (js/Blob.)})))
|
|
wapi/create-uri (mock/stub (constantly "blob:pdf"))
|
|
dom/trigger-download-uri (mock/stub (fn [filename mtype uri]
|
|
(reset! downloaded [filename mtype uri])))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-multiple-export {:exports exports
|
|
:cmd :export-frames
|
|
:name "page"}))
|
|
(await (async/wait-for #(some? @downloaded) "the pdf downloads"))
|
|
(t/is (empty? @cmd-calls) "no exporter RPC")
|
|
(t/is (= :pdf-frames @worker-kind))
|
|
(t/is (= ["page.pdf" "application/pdf" "blob:pdf"] @downloaded))))))
|
|
|
|
;; Scenario: the user cancels a local WASM multi-export. Proves: the widget
|
|
;; stays cancelled and late worker :progress/:done do not download or flip
|
|
;; status back to running.
|
|
(t/deftest ^:async wasm-local-cancel-ignores-late-worker-messages
|
|
(let [downloaded (atom nil)
|
|
cancel-cmds (atom [])
|
|
worker-bus (rx/subject)
|
|
store (export-store (wasm-state) (atom []))
|
|
page-id (uuid/next)
|
|
file-id (uuid/next)
|
|
exports [(assoc export :enabled true :name "a"
|
|
:page-id page-id :file-id file-id)
|
|
(assoc export :id (uuid/next)
|
|
:object-id (uuid/next)
|
|
:page-id page-id
|
|
:file-id file-id
|
|
:enabled true
|
|
:name "b")]]
|
|
(await
|
|
(mock/with-mocks*
|
|
{repo/cmd! (mock/stub (fn [_ _]
|
|
(rx/throw (ex-info "exporter must not run" {}))))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [_] worker-bus))
|
|
mw/emit! (mock/stub (fn [msg]
|
|
(swap! cancel-cmds conj msg)))
|
|
wapi/create-uri (mock/stub (constantly "blob:zip"))
|
|
dom/trigger-download-uri (mock/stub (fn [filename mtype uri]
|
|
(reset! downloaded [filename mtype uri])))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-multiple-export {:exports exports :name "page"}))
|
|
(await (async/wait-for #(= "running" (get-in @store [:export :status]))
|
|
"export is running"))
|
|
(rx/push! worker-bus {:type :progress :done 1 :total 2})
|
|
(await (async/wait-for #(= 1 (get-in @store [:export :progress]))
|
|
"first progress lands"))
|
|
(ptk/emit! store (de/cancel-export))
|
|
(await (async/wait-for #(= "cancelled" (get-in @store [:export :status]))
|
|
"export is cancelled"))
|
|
(rx/push! worker-bus {:type :progress :done 2 :total 2})
|
|
(rx/push! worker-bus {:type :done
|
|
:filename "page.zip"
|
|
:mtype "application/zip"
|
|
:blob (js/Blob.)})
|
|
(await (async/settle))
|
|
(t/is (= "cancelled" (get-in @store [:export :status])))
|
|
(t/is (nil? @downloaded) "late :done does not download")
|
|
(t/is (= :exports/cancel (:cmd (first @cancel-cmds))))
|
|
(rx/end! worker-bus)))))
|
|
|
|
(t/deftest collect-export-objects-keeps-only-roots-and-descendants
|
|
(let [page-id (uuid/next)
|
|
file-id (uuid/next)
|
|
root (uuid/next)
|
|
child (uuid/next)
|
|
other (uuid/next)
|
|
objects {root {:id root :parent-id uuid/zero :shapes [child]}
|
|
child {:id child :parent-id root :shapes []}
|
|
other {:id other :parent-id uuid/zero :shapes []}}
|
|
state {:files {file-id {:id file-id
|
|
:data {:pages-index
|
|
{page-id {:id page-id
|
|
:objects objects}}}}}}
|
|
exports [{:object-id root :page-id page-id :file-id file-id}]
|
|
selected (de/collect-export-objects state exports)]
|
|
(t/is (= #{root child} (set (keys selected))))
|
|
(t/is (not (contains? selected other)))))
|
|
|
|
;; Scenario: a shape is drawn in the browser while its file has a change not
|
|
;; saved yet. Proves: the export waits for the save first, and the user is
|
|
;; told the export includes changes the server does not have.
|
|
(t/deftest ^:async client-export-waits-for-the-save-and-warns-of-unsaved-changes
|
|
(let [file-id (uuid/next)
|
|
calls (atom [])
|
|
store (export-store (assoc (unsaved-state file-id) :features #{"render-wasm/v1"})
|
|
(atom []))]
|
|
(await
|
|
(mock/with-mocks*
|
|
{dwp/force-persist-files-and-wait (mock/stub (fn [file-ids _]
|
|
(swap! calls conj [:wait file-ids])
|
|
(rx/empty)))
|
|
wasm.api/export-target (mock/stub (constantly :client))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [_]
|
|
(swap! calls conj [:export])
|
|
(rx/empty)))
|
|
i18n/tr (mock/stub #(str "translated:" %))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-simple-export {:export (assoc export :file-id file-id)}))
|
|
(await (async/wait-for #(= 2 (count @calls)) "the export runs"))
|
|
(t/is (= [[:wait #{file-id}] [:export]] @calls)
|
|
"the export starts once the save is done")
|
|
(t/is (= "translated:workspace.export.unsaved-changes-included"
|
|
(get-in @store [:notification :content])))))))
|
|
|
|
;; Scenario: a shape is drawn in the browser with every change saved.
|
|
;; Proves: no warning is shown.
|
|
(t/deftest ^:async client-export-shows-no-warning-when-the-file-is-saved
|
|
(let [exported (atom false)
|
|
store (export-store (wasm-state) (atom []))]
|
|
(await
|
|
(mock/with-mocks*
|
|
{dwp/force-persist-files-and-wait (mock/stub (fn [_ _] (rx/empty)))
|
|
wasm.api/export-target (mock/stub (constantly :client))
|
|
de/collect-export-objects (mock/stub (fn [_ _] {}))
|
|
mw/ask-many! (mock/stub (fn [_]
|
|
(reset! exported true)
|
|
(rx/empty)))
|
|
st/ongoing-tasks (atom #{})}
|
|
(ptk/emit! store (de/request-simple-export {:export (assoc export :file-id (uuid/next))}))
|
|
(await (async/wait-for #(true? @exported) "the export runs"))
|
|
(await (async/settle))
|
|
(t/is (nil? (:notification @store)))))))
|