mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 14:00:37 +00:00
✨ Test the file and container helpers and the library events
The component helpers of types/file and types/container and the library events of the workspace had the lowest coverage left. - common-tests.types.file-test: the export detach, the reuse of the library page, the original ref of a swap, deleted components and used-in?. Pinned: F52 (a library copy nested in a local component loses its ref in every copy), F56 (the original ref of a swap is nil when the mains are on different pages) and F57 (library color refs inside :fills survive the export detach). - common-tests.types.container-test: the placement rules for moves and pastes (copies, mains, variant containers, deleted components, component loops) and small helper branches. A plain shape moved into its own child passes the rules (F58); relocate-shapes drops it first. - frontend-tests.data.workspace-libraries-test: the events on several shapes at once, each one undo entry; update of a library main; a blank rename; placing with an id ref. - workspace-library-updates-test: how a file learns that a library changed, and the sync dialog with its update and dismiss, with the RPC mocked. - workspace-library-links-test: linking, unlinking and sharing.
This commit is contained in:
parent
1ffda32211
commit
20062c003b
@ -96,6 +96,7 @@
|
||||
[common-tests.types.color-test]
|
||||
[common-tests.types.components-test]
|
||||
[common-tests.types.container-test]
|
||||
[common-tests.types.file-test]
|
||||
[common-tests.types.fill-test]
|
||||
[common-tests.types.font-test]
|
||||
[common-tests.types.modifiers-test]
|
||||
@ -203,6 +204,7 @@
|
||||
'common-tests.types.color-test
|
||||
'common-tests.types.components-test
|
||||
'common-tests.types.container-test
|
||||
'common-tests.types.file-test
|
||||
'common-tests.types.fill-test
|
||||
'common-tests.types.font-test
|
||||
'common-tests.types.modifiers-test
|
||||
|
||||
@ -6,11 +6,22 @@
|
||||
|
||||
(ns common-tests.types.container-test
|
||||
(:require
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.test-helpers.variants :as thv]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctc]
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Helpers
|
||||
;; ---------------------------------------------------------------------------
|
||||
@ -154,3 +165,208 @@
|
||||
shape-b (make-shape id-b :parent-id id-a)
|
||||
objects (objects-map shape-a shape-b)]
|
||||
(t/is (nil? (ctc/find-component-main objects shape-a false))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Placement rules: where shapes may go when moved or pasted
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defn- placement-file
|
||||
"A board with a frame, a group and a rect inside; components :c1 and :c2,
|
||||
a copy of :c1; and two variant containers, :v01 with mains :vm1 and :vm2,
|
||||
:v02 with mains :vm3 and :vm4."
|
||||
[]
|
||||
(-> (thf/sample-file :file)
|
||||
(tho/add-frame :board)
|
||||
(tho/add-frame :inner :parent-label :board)
|
||||
(tho/add-group :group :parent-label :board)
|
||||
(tho/add-rect :rect :parent-label :board)
|
||||
(tho/add-simple-component :c1 :main1 :main1-child)
|
||||
(tho/add-simple-component :c2 :main2 :main2-child)
|
||||
(thc/instantiate-component :c1 :copy1 :children-labels [:copy1-child])
|
||||
(thv/add-variant :v01 :vc1 :vm1 :vc2 :vm2)
|
||||
(thv/add-variant :v02 :vc3 :vm3 :vc4 :vm4)))
|
||||
|
||||
(defn- page-objects
|
||||
[file]
|
||||
(:objects (thf/current-page file)))
|
||||
|
||||
(defn- label
|
||||
[id]
|
||||
(if (= id uuid/zero) :root (thi/label id)))
|
||||
|
||||
(defn- place
|
||||
"The labels of the parent and the frame that the shapes `labels` go to
|
||||
when moved, or pasted with `pasting?`, into `target`."
|
||||
[file target labels & {:keys [pasting? libraries objects]}]
|
||||
(let [objects (or objects (page-objects file))
|
||||
children (map #(ths/get-shape file %) labels)]
|
||||
(mapv label (ctc/find-valid-parent-and-frame-ids
|
||||
(thi/id target) objects children pasting?
|
||||
(or libraries {(:id file) file})))))
|
||||
|
||||
(defn- delete-component
|
||||
[file component-label]
|
||||
(assoc-in file [:data :components (thi/id component-label) :deleted] true))
|
||||
|
||||
(t/deftest placement-of-plain-shapes
|
||||
(let [file (placement-file)]
|
||||
(t/is (= [:board :board] (place file :board [:main1])))
|
||||
(t/testing "inside a group the frame is the group's frame"
|
||||
(t/is (= [:group :board] (place file :group [:rect]))))
|
||||
(t/testing "the three-argument arity moves, without libraries"
|
||||
(t/is (= [:inner :inner]
|
||||
(mapv label (ctc/find-valid-parent-and-frame-ids
|
||||
(thi/id :inner) (page-objects file)
|
||||
[(ths/get-shape file :rect)])))))))
|
||||
|
||||
(t/deftest placement-inside-a-copy
|
||||
(let [file (placement-file)]
|
||||
(t/testing "nothing goes into a copy or a shape inside it"
|
||||
(t/is (= [:root :root] (place file :copy1 [:rect])))
|
||||
(t/is (= [:root :root] (place file :copy1-child [:rect]))))
|
||||
(t/testing "a shape may stay in the copy it already is in"
|
||||
(t/is (= [:copy1 :copy1] (place file :copy1 [:copy1-child]))))
|
||||
(t/testing "but a pasted one is a new shape"
|
||||
(t/is (= [:root :root] (place file :copy1 [:copy1-child] :pasting? true))))))
|
||||
|
||||
(t/deftest placement-of-a-main
|
||||
(let [file (placement-file)]
|
||||
(t/testing "a main is not moved into another main, nor inside one"
|
||||
(t/is (= [:root :root] (place file :main2 [:main1])))
|
||||
(t/is (= [:root :root] (place file :main2-child [:main1]))))
|
||||
(t/testing "a plain shape may go into a main"
|
||||
(t/is (= [:main2 :main2] (place file :main2 [:rect]))))
|
||||
(t/testing "a pasted main becomes a copy, so it may go into a main"
|
||||
(t/is (= [:main2 :main2] (place file :main2 [:main1] :pasting? true))))
|
||||
(t/testing "unless its component is deleted: then it is pasted as a main"
|
||||
(let [libraries {(:id file) (delete-component file :c1)}]
|
||||
(t/is (= [:root :root] (place file :main2 [:main1] :pasting? true :libraries libraries)))))
|
||||
(t/testing "a cut main, no longer in the page, follows the same rule"
|
||||
(let [objects (dissoc (page-objects file) (thi/id :main1) (thi/id :main1-child))
|
||||
libraries {(:id file) (delete-component file :c1)}]
|
||||
(t/is (= [:main2 :main2] (place file :main2 [:main1] :pasting? true :objects objects)))
|
||||
(t/is (= [:root :root] (place file :main2 [:main1] :pasting? true
|
||||
:objects objects :libraries libraries)))))))
|
||||
|
||||
(t/deftest placement-of-a-variant-container
|
||||
(let [file (placement-file)]
|
||||
(t/is (= [:board :board] (place file :board [:v01])))
|
||||
(t/testing "not inside another variant container"
|
||||
(t/is (= [:root :root] (place file :v02 [:v01]))))
|
||||
(t/testing "not inside a main, even pasted"
|
||||
(t/is (= [:root :root] (place file :main2 [:v01] :pasting? true))))))
|
||||
|
||||
(t/deftest placement-inside-a-variant-container
|
||||
(let [file (placement-file)]
|
||||
(t/testing "only mains go into a variant container"
|
||||
(t/is (= [:root :root] (place file :v01 [:rect])))
|
||||
(t/is (= [:v01 :v01] (place file :v01 [:main1]))))
|
||||
(t/testing "a pasted variant does not go inside a sibling variant"
|
||||
(t/is (= [:v01 :v01] (place file :vm2 [:vm1] :pasting? true))))
|
||||
(t/testing "but it may go inside a variant of another container"
|
||||
(t/is (= [:vm3 :vm3] (place file :vm3 [:vm1] :pasting? true))))))
|
||||
|
||||
(t/deftest placement-that-makes-a-component-loop
|
||||
(let [file (placement-file)]
|
||||
(t/testing "a copy does not go inside its own main"
|
||||
(t/is (= [:root :root] (place file :main1 [:copy1] :pasting? true)))
|
||||
(t/is (= [:main2 :main2] (place file :main2 [:copy1] :pasting? true))))
|
||||
(t/testing "a main does not go inside itself, even pasted"
|
||||
(t/is (= [:root :root] (place file :main1-child [:main1])))
|
||||
(t/is (= [:root :root] (place file :main1-child [:main1] :pasting? true))))
|
||||
(t/testing "a plain shape inside its own child is let through (`relocate-shapes` drops it)"
|
||||
(t/is (= [:inner :inner] (place file :inner [:board]))))))
|
||||
|
||||
(t/deftest placement-with-a-validation-cache
|
||||
(let [file (placement-file)
|
||||
objects (page-objects file)
|
||||
children [(ths/get-shape file :main1)]
|
||||
libs {(:id file) file}
|
||||
cache (ctc/parent-validation-cache objects children libs)]
|
||||
(t/is (= [:root :root]
|
||||
(mapv label (ctc/find-valid-parent-and-frame-ids
|
||||
(thi/id :main2) objects children false libs cache))))
|
||||
(t/is (= [:board :board]
|
||||
(mapv label (ctc/find-valid-parent-and-frame-ids
|
||||
(thi/id :board) objects children false libs cache))))))
|
||||
|
||||
(t/deftest valid-shape-for-component
|
||||
(let [file (placement-file)
|
||||
objects (page-objects file)
|
||||
valid? #(ctc/valid-shape-for-component? objects (ths/get-shape file %))]
|
||||
(t/is (valid? :rect))
|
||||
(t/is (valid? :board))
|
||||
(t/testing "a main can't be inside another main"
|
||||
(t/is (not (valid? :main1-child))))
|
||||
(t/testing "a main can't hold another main"
|
||||
(t/is (not (valid? :v01))))
|
||||
(t/testing "a main can't be inside a copy"
|
||||
(t/is (not (valid? :copy1-child))))))
|
||||
|
||||
(t/deftest first-valid-parent
|
||||
(let [file (placement-file)
|
||||
objects (page-objects file)
|
||||
first-valid #(label (:id (ctc/get-first-valid-parent objects (thi/id %))))]
|
||||
(t/is (= :rect (first-valid :rect)))
|
||||
(t/is (= :vm1 (first-valid :vm1)))
|
||||
(t/testing "copies and variant containers are skipped"
|
||||
(t/is (= :root (first-valid :copy1-child)))
|
||||
(t/is (= :root (first-valid :v01))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Heads, containers and instances
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(t/deftest head-shape-of-nothing-and-of-the-root
|
||||
(let [objects (page-objects (placement-file))]
|
||||
(t/is (nil? (ctc/get-head-shape objects nil)))
|
||||
(t/is (nil? (ctc/get-head-shape objects (get objects uuid/zero))))
|
||||
(t/is (= [] (ctc/get-child-heads objects (uuid/next))))))
|
||||
|
||||
(t/deftest children-in-instance-stop-at-a-nested-copy
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-nested-component :c1 :main1 :main1-child
|
||||
:c2 :main2 :nested-head))
|
||||
objects (page-objects file)
|
||||
labels #(set (map (comp label :id) (ctc/get-children-in-instance objects (thi/id %))))]
|
||||
(t/is (= #{:main1 :main1-child} (labels :main1)))
|
||||
(t/is (= #{:main2} (labels :main2)))))
|
||||
|
||||
(t/deftest container-of-a-component
|
||||
(let [file (placement-file)
|
||||
container (ctc/get-container (:data file) :component (thi/id :c1))]
|
||||
(t/is (ctc/component? container))
|
||||
(t/is (not (ctc/page? container)))
|
||||
(t/is (= (thi/id :main1) (:main-instance-id container)))))
|
||||
|
||||
(t/deftest container-root
|
||||
(let [root-id (uuid/next)
|
||||
root (assoc (make-shape root-id) :parent-id nil)
|
||||
child (make-shape (uuid/next) :parent-id root-id)]
|
||||
(t/is (= root (ctc/get-container-root {:objects (objects-map root child)})))))
|
||||
|
||||
(t/deftest swap-keep-attrs-keep-auto-sizing-of-a-layout
|
||||
(let [shape (make-shape (uuid/next)
|
||||
:type :frame
|
||||
:layout :flex
|
||||
:layout-item-h-sizing :auto
|
||||
:layout-item-v-sizing :auto
|
||||
:layout-item-margin {:m1 10})
|
||||
fixed (assoc shape
|
||||
:layout-item-h-sizing :fix
|
||||
:layout-item-v-sizing :fix)]
|
||||
(t/is (= [:auto :auto nil]
|
||||
((juxt :layout-item-h-sizing :layout-item-v-sizing :layout-item-margin)
|
||||
(ctc/remove-swap-keep-attrs shape))))
|
||||
(t/is (= [nil nil]
|
||||
((juxt :layout-item-h-sizing :layout-item-v-sizing)
|
||||
(ctc/remove-swap-keep-attrs fixed))))))
|
||||
|
||||
(t/deftest component-instance-with-default-options
|
||||
(let [file (placement-file)
|
||||
component (ctkl/get-component (:data file) (thi/id :c1))
|
||||
[root shapes] (ctc/make-component-instance (thf/current-page file) component
|
||||
(:data file) (gpt/point 0 0))]
|
||||
(t/is (= (thi/id :c1) (:component-id root)))
|
||||
(t/is (not (ctk/main-instance? root)))
|
||||
(t/is (= 2 (count shapes)))))
|
||||
|
||||
293
common/test/common_tests/types/file_test.cljc
Normal file
293
common/test/common_tests/types/file_test.cljc
Normal file
@ -0,0 +1,293 @@
|
||||
;; 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 common-tests.types.file-test
|
||||
"Component helpers of `app.common.types.file`: the export detach of
|
||||
external references, the library page that absorbed components go to,
|
||||
the original ref of a swapped shape, and the lookups on deleted
|
||||
components."
|
||||
(:require
|
||||
[app.common.files.validate :as cfv]
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.types.pages-list :as ctpl]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
|
||||
(def ^:private link-attrs
|
||||
[:component-id :component-file :shape-ref :component-root])
|
||||
|
||||
(defn- links
|
||||
[file label]
|
||||
(select-keys (ths/get-shape file label) link-attrs))
|
||||
|
||||
(defn- validation-errors
|
||||
[file]
|
||||
(set (map :code (cfv/validate-file file {}))))
|
||||
|
||||
(defn- library-with-component
|
||||
[]
|
||||
(-> (thf/sample-file :library :is-shared true)
|
||||
(tho/add-simple-component :lib-component :lib-main :lib-main-child)))
|
||||
|
||||
;; ----- Export detach
|
||||
|
||||
(t/deftest detach-external-references-drops-library-links
|
||||
(let [library (library-with-component)
|
||||
file (-> (thf/sample-file :file)
|
||||
(tho/add-simple-component-with-copy :component :main :main-child
|
||||
:copy
|
||||
:copy-root-params {:children-labels [:copy-child]})
|
||||
(thc/instantiate-component :lib-component :lib-copy
|
||||
:library library
|
||||
:children-labels [:lib-copy-child]))
|
||||
file' (ctf/detach-external-references file (:id file))]
|
||||
(t/testing "a copy of a library component becomes plain shapes"
|
||||
(t/is (= {} (links file' :lib-copy)))
|
||||
(t/is (= {} (links file' :lib-copy-child))))
|
||||
(t/testing "a copy of a local component keeps its links"
|
||||
(t/is (= (links file :copy) (links file' :copy)))
|
||||
(t/is (= (links file :copy-child) (links file' :copy-child))))
|
||||
(t/is (= #{} (validation-errors file')))))
|
||||
|
||||
(t/deftest detach-external-references-keeps-a-nested-library-copy-in-place
|
||||
(let [library (library-with-component)
|
||||
file (-> (thf/sample-file :file)
|
||||
(tho/add-frame :main {:name "Frame2"})
|
||||
(thc/instantiate-component :lib-component :nested-head
|
||||
:library library
|
||||
:parent-label :main)
|
||||
(thc/make-component :component :main)
|
||||
(thc/instantiate-component :component :copy
|
||||
:children-labels [:copy-nested-head]))
|
||||
file' (ctf/detach-external-references file (:id file))]
|
||||
(t/testing "the nested library copy in the main becomes plain shapes"
|
||||
(t/is (= {} (links file' :nested-head))))
|
||||
(t/testing "its copy keeps the ref to the main's shape, which is local"
|
||||
(tm/known-failure "F52"
|
||||
{:shape-ref (thi/id :nested-head)}
|
||||
{}
|
||||
(links file' :copy-nested-head)))
|
||||
(t/testing "the validation does not see the lost ref"
|
||||
(t/is (= #{} (validation-errors file'))))))
|
||||
|
||||
;; Fill and typography refs on the shape and on the text nodes themselves
|
||||
;; (the older format), next to a fill ref inside `:fills`.
|
||||
(defn- file-with-color-refs
|
||||
[library-id]
|
||||
(let [file (thf/sample-file :file)]
|
||||
(-> file
|
||||
(ths/add-sample-shape :rect
|
||||
:type :rect
|
||||
:fill-color-ref-id (thi/id :color)
|
||||
:fill-color-ref-file library-id
|
||||
:stroke-color-ref-id (thi/id :color)
|
||||
:stroke-color-ref-file library-id
|
||||
:fills [{:fill-color "#abcdef"
|
||||
:fill-opacity 1
|
||||
:fill-color-ref-id (thi/id :color)
|
||||
:fill-color-ref-file library-id}])
|
||||
(ths/add-sample-shape :text
|
||||
:type :text
|
||||
:content {:type "root"
|
||||
:children [{:type "paragraph-set"
|
||||
:children [{:type "paragraph"
|
||||
:children [{:text "library"
|
||||
:fill-color-ref-id (thi/id :color)
|
||||
:fill-color-ref-file library-id
|
||||
:typography-ref-id (thi/id :typography)
|
||||
:typography-ref-file library-id}
|
||||
{:text "local"
|
||||
:typography-ref-id (thi/id :typography)
|
||||
:typography-ref-file (:id file)}]}]}]}))))
|
||||
|
||||
(defn- text-leaves
|
||||
[file]
|
||||
(->> (get-in (ths/get-shape file :text) [:content :children 0 :children 0 :children])
|
||||
(mapv #(select-keys % [:fill-color-ref-file :typography-ref-file]))))
|
||||
|
||||
(t/deftest detach-external-references-drops-library-color-refs
|
||||
(let [library-id (thi/new-id! :library)
|
||||
file (file-with-color-refs library-id)
|
||||
file' (ctf/detach-external-references file (:id file))
|
||||
rect' (ths/get-shape file' :rect)]
|
||||
(t/is (= {} (select-keys rect' [:fill-color-ref-file :stroke-color-ref-file])))
|
||||
(t/is (= [{} {:typography-ref-file (:id file)}] (text-leaves file')))
|
||||
(t/testing "a fill ref inside `:fills`"
|
||||
(tm/known-failure "F57" nil library-id (-> rect' :fills first :fill-color-ref-file)))))
|
||||
|
||||
;; ----- Asset usage
|
||||
|
||||
(t/deftest component-used-in-a-file
|
||||
(let [library (library-with-component)
|
||||
file (-> (thf/sample-file :file)
|
||||
(thc/instantiate-component :lib-component :lib-copy :library library))
|
||||
unused (-> (thf/sample-file :other)
|
||||
(tho/add-rect :rect))
|
||||
lib-c (thc/get-component library :lib-component)]
|
||||
(t/is (ctf/used-in? (:data file) (:id library) lib-c :component))
|
||||
(t/is (not (ctf/used-in? (:data unused) (:id library) lib-c :component)))))
|
||||
|
||||
;; ----- Library page for absorbed components
|
||||
|
||||
(t/deftest absorbed-components-reuse-the-library-page
|
||||
(let [library1 (-> (thf/sample-file :library1 :is-shared true)
|
||||
(tho/add-simple-component :component1 :main1 :main1-child))
|
||||
library2 (-> (thf/sample-file :library2 :is-shared true)
|
||||
(tho/add-simple-component :component2 :main2 :main2-child))
|
||||
file (-> (thf/sample-file :file)
|
||||
(thc/instantiate-component :component1 :copy1 :library library1)
|
||||
(thc/instantiate-component :component2 :copy2 :library library2))
|
||||
file' (ctf/update-file-data file
|
||||
#(-> %
|
||||
(ctf/absorb-assets (:data library1))
|
||||
(ctf/absorb-assets (:data library2))))
|
||||
data' (ctf/file-data file')
|
||||
pages' (ctpl/pages-seq data')
|
||||
roots (->> (ctpl/get-page data' (:id (second pages')))
|
||||
(ctn/shapes-seq)
|
||||
(filter ctk/main-instance?)
|
||||
(sort-by :y))]
|
||||
(t/is (= ["Page 1" "Main components"] (map :name pages')))
|
||||
(t/is (= 2 (count roots)))
|
||||
(t/testing "the second component goes below the first, a grid gap apart"
|
||||
(let [[first-root second-root] roots]
|
||||
(t/is (<= (+ (:y first-root) (:height first-root) 50) (:y second-root)))))
|
||||
(t/is (= #{} (validation-errors file')))))
|
||||
|
||||
;; ----- Original ref of a swapped shape
|
||||
|
||||
(defn- swap-at-second-level
|
||||
"Swaps, in a copy of :c3, the copy of :c1 nested two levels down
|
||||
(:c3 > :c2 > :c1) for :c4. Returns the file with the new shape as
|
||||
:swapped."
|
||||
[file]
|
||||
(let [copy-c2 (first (:shapes (ths/get-shape file :copy3)))
|
||||
copy-c1 (first (:shapes (ths/get-shape-by-id file copy-c2)))]
|
||||
(thi/set-id! :copy-c1 copy-c1)
|
||||
(-> file
|
||||
(tho/add-simple-component :c4 :main4 :main4-child)
|
||||
(tho/swap-component-in-shape :copy-c1 :c4 :new-shape-label :swapped))))
|
||||
|
||||
(defn- ref-id-for-swapped
|
||||
[file]
|
||||
(ctf/find-ref-id-for-swapped (ths/get-shape file :swapped)
|
||||
(thf/current-page file)
|
||||
{(:id file) file}))
|
||||
|
||||
(t/deftest ref-id-for-swapped-goes-up-the-ref-chain
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-two-levels-nested-component-with-copy
|
||||
:c1 :main1 :main1-child
|
||||
:c2 :main2 :nested-head1
|
||||
:c3 :main3 :nested-head2 :nested-subhead2
|
||||
:copy3)
|
||||
(swap-at-second-level))]
|
||||
(t/is (= (thi/id :nested-head1) (ctk/get-swap-slot (ths/get-shape file :swapped))))
|
||||
(t/is (= (thi/id :nested-subhead2) (ref-id-for-swapped file)))))
|
||||
|
||||
(t/deftest ref-id-for-swapped-with-mains-on-other-pages
|
||||
(let [file (-> (thf/sample-file :file :page-label :page1)
|
||||
(tho/add-nested-component :c1 :main1 :main1-child
|
||||
:c2 :main2 :nested-head1)
|
||||
(thf/add-sample-page :page2)
|
||||
(tho/add-frame :main3 {:name "Frame3"})
|
||||
(thc/instantiate-component :c2 :nested-head2
|
||||
:parent-label :main3
|
||||
:children-labels [:nested-subhead2])
|
||||
(thc/make-component :c3 :main3)
|
||||
(thc/instantiate-component :c3 :copy3)
|
||||
(swap-at-second-level))]
|
||||
(t/is (= (thi/id :nested-head1) (ctk/get-swap-slot (ths/get-shape file :swapped))))
|
||||
(tm/known-failure "F56" (thi/id :nested-subhead2) nil (ref-id-for-swapped file))))
|
||||
|
||||
;; ----- Deleted components
|
||||
|
||||
(defn- file-with-deleted-component
|
||||
[]
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-simple-component :component :main :main-child))]
|
||||
(ctf/update-file-data file #(ctf/delete-component % (thi/id :component) false nil))))
|
||||
|
||||
(t/deftest lookups-on-a-deleted-component
|
||||
(let [file (file-with-deleted-component)
|
||||
data (ctf/file-data file)
|
||||
component (thc/get-component file :component :include-deleted? true)]
|
||||
(t/is (:deleted component))
|
||||
(t/is (= (thi/id :main) (:id (ctf/get-component-root data component))))
|
||||
(t/is (= #{(thi/id :main) (thi/id :main-child)}
|
||||
(set (map :id (ctf/get-component-shapes data component)))))
|
||||
(t/testing "a shape of the component, with the component as its container"
|
||||
(let [child (ctf/get-component-shape data component (thi/id :main-child)
|
||||
:with-context? true)]
|
||||
(t/is (= (thi/id :main-child) (:id child)))
|
||||
(t/is (= :component (:type (:container (meta child)))))))
|
||||
(t/testing "loading the objects again leaves the component as it is"
|
||||
(t/is (= component (ctf/load-component-objects data component))))))
|
||||
|
||||
;; ----- Containers and ref chains
|
||||
|
||||
(t/deftest update-container-of-a-component
|
||||
(let [file (file-with-deleted-component)
|
||||
component (thc/get-component file :component :include-deleted? true)
|
||||
container (ctn/make-container component :component)
|
||||
data' (ctf/update-container (ctf/file-data file) container
|
||||
#(assoc % :name "Renamed"))]
|
||||
(t/is (= "Renamed" (:name (thc/get-component {:data data'} :component
|
||||
:include-deleted? true))))))
|
||||
|
||||
(t/deftest update-objects-tree-updates-and-removes
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-frame-with-child :frame :child)
|
||||
(tho/add-rect :rect))
|
||||
container (ctn/make-container (thf/current-page file) :page)
|
||||
result (ctf/update-objects-tree
|
||||
container
|
||||
(fn [shape]
|
||||
(condp = (:id shape)
|
||||
(thi/id :frame) {:result :update
|
||||
:updated-shape (assoc shape :name "Updated")}
|
||||
(thi/id :rect) {:result :remove}
|
||||
{:result :keep})))]
|
||||
(t/is (= "Updated" (:name (ctst/get-shape result (thi/id :frame)))))
|
||||
(t/is (some? (ctst/get-shape result (thi/id :child))))
|
||||
(t/is (nil? (ctst/get-shape result (thi/id :rect))))
|
||||
(t/testing "an unknown result"
|
||||
(t/is (thrown? #?(:clj Exception :cljs :default)
|
||||
(ctf/update-objects-tree container (constantly {:result :other})))))))
|
||||
|
||||
(t/deftest ref-chain-without-the-target
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-simple-component-with-copy :component :main :main-child
|
||||
:copy
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
page (thf/current-page file)
|
||||
libraries {(:id file) file}
|
||||
chain (ctf/get-ref-chain-until-target-ref page libraries
|
||||
(ths/get-shape file :copy-child)
|
||||
(ths/get-shape file :copy))]
|
||||
(t/testing "the chain runs to the main and stops there"
|
||||
(t/is (= [(thi/id :copy-child) (thi/id :main-child)] (map :id chain))))))
|
||||
|
||||
(t/deftest near-match-with-context
|
||||
(let [file (-> (thf/sample-file :file)
|
||||
(tho/add-simple-component-with-copy :component :main :main-child
|
||||
:copy
|
||||
:copy-root-params {:children-labels [:copy-child]}))
|
||||
match (ctf/find-near-match file (thf/current-page file) {(:id file) file}
|
||||
(ths/get-shape file :copy-child)
|
||||
:with-context? true)]
|
||||
(t/is (= (thi/id :main-child) (:id match)))
|
||||
(t/is (some? (:container (meta match))))))
|
||||
220
frontend/test/frontend_tests/data/workspace_libraries_test.cljs
Normal file
220
frontend/test/frontend_tests/data/workspace_libraries_test.cljs
Normal file
@ -0,0 +1,220 @@
|
||||
;; 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-libraries-test
|
||||
"The library events that act on several shapes or components at once, each
|
||||
as one undo entry, and the branches of the single-shape events that the
|
||||
composable cases do not reach: creating or placing a component with an
|
||||
id ref, a blank rename and updating a main that lives in a linked
|
||||
library."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.test-helpers.components :as cthc]
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.shapes :as dwsh]
|
||||
[app.main.data.workspace.undo :as dwu]
|
||||
[app.main.store :as st]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.composable-tests.interpreter :as ftm]))
|
||||
|
||||
(t/use-fixtures :each ftm/fixture)
|
||||
|
||||
(def ^:private main-color "#111111")
|
||||
(def ^:private override "#222222")
|
||||
|
||||
(defn- two-components
|
||||
"Components :c1 and :c2, two copies of :c1 (:copy-a, :copy-b) and two plain
|
||||
rects."
|
||||
[]
|
||||
(-> (cthf/sample-file :file1)
|
||||
(ctho/add-simple-component :c1 :main1 :main1-child
|
||||
:child-params {:fills (cths/sample-fills-color :fill-color main-color)})
|
||||
(ctho/add-simple-component :c2 :main2 :main2-child)
|
||||
(cthc/instantiate-component :c1 :copy-a :children-labels [:copy-a-child])
|
||||
(cthc/instantiate-component :c1 :copy-b :children-labels [:copy-b-child])
|
||||
(ctho/add-rect :rect-a)
|
||||
(ctho/add-rect :rect-b)))
|
||||
|
||||
(defn- install-file!
|
||||
[file]
|
||||
(ftm/install! (tm/make-situation file)))
|
||||
|
||||
(defn- shape
|
||||
[label]
|
||||
(cths/get-shape (ftm/current-file) label))
|
||||
|
||||
(defn- shape-by-id
|
||||
[id]
|
||||
(cths/get-shape-by-id (ftm/current-file) id))
|
||||
|
||||
(defn- data
|
||||
[]
|
||||
(:data (ftm/current-file)))
|
||||
|
||||
(defn- undo-entries
|
||||
[]
|
||||
(count (get-in @st/state [:workspace-undo :items])))
|
||||
|
||||
(defn- step
|
||||
"Run `events` and wait until their work drains."
|
||||
[& events]
|
||||
(ftm/await-step (vec events)))
|
||||
|
||||
(defn- select
|
||||
[& labels]
|
||||
(dws/select-shapes (into (d/ordered-set) (map cthi/id) labels)))
|
||||
|
||||
(defn- linked?
|
||||
[label]
|
||||
(some? (:component-id (shape label))))
|
||||
|
||||
(defn- fill
|
||||
[label]
|
||||
(-> (shape label) :fills first :fill-color))
|
||||
|
||||
(defn- set-fill
|
||||
[& labels]
|
||||
(dwsh/update-shapes (into #{} (map cthi/id) labels)
|
||||
#(assoc % :fills (cths/sample-fills-color :fill-color override))))
|
||||
|
||||
(defn- component-count
|
||||
[]
|
||||
(count (ctkl/components-seq (data))))
|
||||
|
||||
(t/deftest ^:async detach-components-is-one-undo-entry
|
||||
(install-file! (two-components))
|
||||
(let [before (undo-entries)]
|
||||
(t/is (= :settled (await (step (dwl/detach-components [(cthi/id :copy-a) (cthi/id :copy-b)])))))
|
||||
(t/is (not (linked? :copy-a)))
|
||||
(t/is (not (linked? :copy-b)))
|
||||
(t/is (= (inc before) (undo-entries)))
|
||||
(await (step dwu/undo))
|
||||
(t/is (linked? :copy-a))
|
||||
(t/is (linked? :copy-b))))
|
||||
|
||||
(t/deftest ^:async detach-selected-components
|
||||
(install-file! (two-components))
|
||||
(t/testing "nothing happens when a selected shape is inside a copy"
|
||||
(await (step (select :copy-a :copy-b-child) dwl/detach-selected-components))
|
||||
(t/is (linked? :copy-a))
|
||||
(t/is (some? (:shape-ref (shape :copy-b-child)))))
|
||||
(t/testing "the selected copies are detached"
|
||||
(await (step (select :copy-a :copy-b) dwl/detach-selected-components))
|
||||
(t/is (not (linked? :copy-a)))
|
||||
(t/is (not (linked? :copy-b)))))
|
||||
|
||||
(t/deftest ^:async reset-components-is-one-undo-entry
|
||||
(install-file! (two-components))
|
||||
(await (step (set-fill :copy-a-child :copy-b-child)))
|
||||
(t/is (= override (fill :copy-a-child)))
|
||||
(let [before (undo-entries)]
|
||||
(await (step (dwl/reset-components [(cthi/id :copy-a) (cthi/id :copy-b)])))
|
||||
(t/is (= main-color (fill :copy-a-child)))
|
||||
(t/is (= main-color (fill :copy-b-child)))
|
||||
(t/is (= (inc before) (undo-entries)))
|
||||
(await (step dwu/undo))
|
||||
(t/is (= override (fill :copy-a-child)))
|
||||
(t/is (= override (fill :copy-b-child)))))
|
||||
|
||||
(defn- copies-of
|
||||
[component-label]
|
||||
(->> (vals (:objects (cthf/current-page (ftm/current-file))))
|
||||
(filter #(and (ctk/instance-head? %)
|
||||
(not (ctk/main-instance? %))
|
||||
(= (cthi/id component-label) (:component-id %))))))
|
||||
|
||||
(t/deftest ^:async multi-swap-is-one-undo-entry
|
||||
(install-file! (two-components))
|
||||
(let [before (undo-entries)]
|
||||
(await (step (dwl/component-multi-swap [(shape :copy-a) (shape :copy-b)]
|
||||
(:id (ftm/current-file))
|
||||
(cthi/id :c2))))
|
||||
(t/is (= 0 (count (copies-of :c1))))
|
||||
(t/is (= 2 (count (copies-of :c2))))
|
||||
(t/is (= (inc before) (undo-entries)))
|
||||
(await (step dwu/undo))
|
||||
(t/is (= 2 (count (copies-of :c1))))
|
||||
(t/is (= 0 (count (copies-of :c2))))))
|
||||
|
||||
(t/deftest ^:async add-multiple-components-is-one-undo-entry
|
||||
(install-file! (two-components))
|
||||
(let [before (undo-entries)]
|
||||
(await (step (select :rect-a :rect-b) (dwl/add-multiple-components)))
|
||||
(t/is (= 4 (component-count)))
|
||||
(t/is (= (inc before) (undo-entries)))
|
||||
(await (step dwu/undo))
|
||||
(t/is (= 2 (component-count)))))
|
||||
|
||||
(t/deftest ^:async add-multiple-components-skips-copies
|
||||
(install-file! (two-components))
|
||||
(await (step (select :rect-a :copy-a-child) (dwl/add-multiple-components)))
|
||||
(t/is (= 2 (component-count))))
|
||||
|
||||
(t/deftest ^:async add-component-sets-the-id-ref
|
||||
(install-file! (two-components))
|
||||
(let [id-ref (atom nil)]
|
||||
(await (step (dwl/add-component id-ref [(cthi/id :rect-a)])))
|
||||
(t/is (some? (ctkl/get-component (data) @id-ref)))))
|
||||
|
||||
(t/deftest ^:async duplicate-component-makes-a-new-main
|
||||
(install-file! (two-components))
|
||||
(await (step (dwl/duplicate-component (:id (ftm/current-file)) (cthi/id :c1))))
|
||||
(let [new-component (->> (ctkl/components-seq (data))
|
||||
(remove #(#{(cthi/id :c1) (cthi/id :c2)} (:id %)))
|
||||
first)]
|
||||
(t/is (= 3 (component-count)))
|
||||
(t/is (some? (shape-by-id (:main-instance-id new-component))))))
|
||||
|
||||
(t/deftest ^:async restore-components-is-one-undo-entry
|
||||
(install-file! (two-components))
|
||||
(let [file-id (:id (ftm/current-file))]
|
||||
(await (step (dwl/delete-component {:id (cthi/id :c1)})
|
||||
(dwl/delete-component {:id (cthi/id :c2)})))
|
||||
(t/is (= 0 (component-count)))
|
||||
(let [before (undo-entries)]
|
||||
(await (step (dwl/restore-components {(cthi/id :c1) file-id
|
||||
(cthi/id :c2) file-id})))
|
||||
(t/is (= 2 (component-count)))
|
||||
(t/is (= (inc before) (undo-entries))))))
|
||||
|
||||
(t/deftest ^:async update-component-of-a-library-main
|
||||
(let [library (-> (cthf/sample-file :library :is-shared true)
|
||||
(ctho/add-simple-component :lib-c :lib-main :lib-main-child))
|
||||
file (-> (cthf/sample-file :file1)
|
||||
(cthc/instantiate-component :lib-c :copy
|
||||
:library library
|
||||
:children-labels [:copy-child]))]
|
||||
(ftm/install! (-> (tm/make-situation file)
|
||||
(tm/with-aux-files {(:id library) library})))
|
||||
(await (step (set-fill :copy-child)))
|
||||
(await (step (dwl/update-component (cthi/id :copy))))
|
||||
(let [library' (get-in @st/state [:files (:id library)])]
|
||||
(t/is (= override (-> (cths/get-shape library' :lib-main-child) :fills first :fill-color))))
|
||||
(t/testing "a shape that is not a head is left as it is"
|
||||
(await (step (dwl/update-component (cthi/id :copy-child))))
|
||||
(t/is (= override (fill :copy-child))))))
|
||||
|
||||
(t/deftest ^:async rename-component-to-a-blank-name
|
||||
(install-file! (two-components))
|
||||
(let [before (:name (ctkl/get-component (data) (cthi/id :c1)))]
|
||||
(await (step (dwl/rename-component (cthi/id :c1) " ")))
|
||||
(t/is (= before (:name (ctkl/get-component (data) (cthi/id :c1)))))))
|
||||
|
||||
(t/deftest ^:async instantiate-component-sets-the-id-ref
|
||||
(install-file! (two-components))
|
||||
(let [id-ref (atom nil)]
|
||||
(await (step (dwl/instantiate-component (:id (ftm/current-file)) (cthi/id :c2)
|
||||
(gpt/point 0 0) {:id-ref id-ref :origin "plugin"})))
|
||||
(t/is (= (cthi/id :c2) (:component-id (shape-by-id @id-ref))))))
|
||||
@ -0,0 +1,189 @@
|
||||
;; 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-library-links-test
|
||||
"Linking a library to the file and unlinking it, with the libraries it
|
||||
brings along, and publishing the file as a shared library. The server is
|
||||
mocked."
|
||||
(:require
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.variants :as cthv]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.repo :as rp]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/use-fixtures :each {:before cthi/reset-idmap!})
|
||||
|
||||
(defn- rpc
|
||||
"A mock of `rp/cmd!` that answers each command from `responses`
|
||||
(command -> value, or fn of the params returning an observable), and
|
||||
records the calls in `calls`."
|
||||
[calls responses]
|
||||
(mock/stub
|
||||
(fn [cmd params & _]
|
||||
(swap! calls conj [cmd params])
|
||||
(let [response (get responses cmd)]
|
||||
(if (fn? response)
|
||||
(response params)
|
||||
(rx/of response))))))
|
||||
|
||||
(defn- commands
|
||||
[calls]
|
||||
(set (map first @calls)))
|
||||
|
||||
(defn- merge-state
|
||||
"An event that merges `m` into the state, for what the workspace loads
|
||||
before these events run."
|
||||
[m]
|
||||
(ptk/reify ::merge-state
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(merge-with merge state m))))
|
||||
|
||||
(defn- files
|
||||
[]
|
||||
{:file (cthf/sample-file :file1)
|
||||
:library (-> (cthf/sample-file :library :is-shared true)
|
||||
(ctho/add-simple-component :lib-c :lib-main :lib-main-child))
|
||||
:dependency (-> (cthf/sample-file :dependency :is-shared true)
|
||||
(ctho/add-simple-component :dep-c :dep-main :dep-main-child))})
|
||||
|
||||
(defn- linked-to
|
||||
[state file-id]
|
||||
(->> (vals (:files state))
|
||||
(filter #(= file-id (:library-of %)))
|
||||
(map :id)
|
||||
set))
|
||||
|
||||
(t/deftest link-loads-the-library-and-its-dependencies
|
||||
(t/async
|
||||
done
|
||||
(let [{:keys [file library dependency]} (files)
|
||||
by-id (into {} (map (juxt :id identity)) [library dependency])
|
||||
calls (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (rpc calls {:link-file-to-library [(:id dependency)]
|
||||
:get-file #(rx/of (get by-id (:id %)))
|
||||
:get-file-object-thumbnails {"thumbnail-id" "thumbnail-uri"}
|
||||
:get-library-usage {:used-in 3}})}
|
||||
(fn [inner-done]
|
||||
(ths/run-store
|
||||
(ths/setup-store file) inner-done
|
||||
[(merge-state {:shared-files {(:id library) {:library-summary {:variants [{} {}]}}}})
|
||||
(dwl/link-file-to-library (:id file) (:id library))]
|
||||
(fn [state]
|
||||
(t/is (= #{(:id library) (:id dependency)} (linked-to state (:id file))))
|
||||
(t/is (= "thumbnail-uri" (get-in state [:thumbnails "thumbnail-id" :uri])))
|
||||
(t/testing "a library with variants asks how much it is used"
|
||||
(t/is (contains? (commands calls) :get-library-usage))))))
|
||||
done))))
|
||||
|
||||
(t/deftest link-skips-libraries-already-loaded
|
||||
(t/async
|
||||
done
|
||||
(let [{:keys [file library dependency]} (files)
|
||||
calls (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (rpc calls {:link-file-to-library [(:id dependency)]
|
||||
:get-file #(rx/of library)
|
||||
:get-file-object-thumbnails {}})}
|
||||
(fn [inner-done]
|
||||
(ths/run-store
|
||||
(ths/setup-store file {:libraries [dependency]}) inner-done
|
||||
[(dwl/link-file-to-library (:id file) (:id library))]
|
||||
(fn [_]
|
||||
(t/is (= [(:id library)]
|
||||
(->> @calls
|
||||
(filter #(= :get-file (first %)))
|
||||
(map (comp :id second)))))
|
||||
(t/is (not (contains? (commands calls) :get-library-usage))))))
|
||||
done))))
|
||||
|
||||
(t/deftest link-that-would-make-a-loop
|
||||
(t/async
|
||||
done
|
||||
(let [{:keys [file library]} (files)
|
||||
calls (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (rpc calls {:link-file-to-library
|
||||
(fn [_] (rx/throw (ex-info "loop" {:code :circular-library-reference})))})}
|
||||
(fn [inner-done]
|
||||
(ths/run-store
|
||||
(ths/setup-store file) inner-done
|
||||
[(dwl/link-file-to-library (:id file) (:id library))]
|
||||
(fn [state]
|
||||
(t/is (= :error (get-in state [:notification :level])))
|
||||
(t/is (empty? (linked-to state (:id file)))))))
|
||||
done))))
|
||||
|
||||
(t/deftest unlink-drops-the-library-and-what-it-brought
|
||||
(t/async
|
||||
done
|
||||
(let [{:keys [file library dependency]} (files)
|
||||
calls (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (rpc calls {:unlink-file-from-library nil
|
||||
:get-file-libraries []})}
|
||||
(fn [inner-done]
|
||||
(ths/run-store
|
||||
(ths/setup-store file) inner-done
|
||||
[(dwl/libraries-fetched (:id file) [library dependency])
|
||||
(dwl/unlink-file-from-library (:id file) (:id library))]
|
||||
(fn [state]
|
||||
(t/is (empty? (linked-to state (:id file))))
|
||||
(t/is (some? (get-in state [:files (:id file)]))))))
|
||||
done))))
|
||||
|
||||
(t/deftest cleanup-keeps-the-libraries-still-linked
|
||||
(t/async
|
||||
done
|
||||
(let [{:keys [file library dependency]} (files)]
|
||||
(ths/run-store
|
||||
(ths/setup-store file) done
|
||||
[(dwl/libraries-fetched (:id file) [library dependency])
|
||||
(dwl/cleanup-unlinked-libraries (:id file) [library])]
|
||||
(fn [state]
|
||||
(t/is (= #{(:id library)} (linked-to state (:id file)))))))))
|
||||
|
||||
(defn- share
|
||||
[file shared? check]
|
||||
(fn [done]
|
||||
(let [calls (atom [])]
|
||||
(mock/with-mocks
|
||||
{rp/cmd! (rpc calls {:set-file-shared nil})}
|
||||
(fn [inner-done]
|
||||
(ths/run-store
|
||||
(ths/setup-store file) inner-done
|
||||
[(dwl/set-file-shared (:id file) shared?)]
|
||||
#(check % @calls)))
|
||||
done))))
|
||||
|
||||
(t/deftest share-a-file-with-variants
|
||||
(t/async
|
||||
done
|
||||
(let [file (-> (cthf/sample-file :file1)
|
||||
(cthv/add-variant :v01 :c01 :m01 :c02 :m02))]
|
||||
((share file true
|
||||
(fn [state calls]
|
||||
(t/is (true? (get-in state [:files (:id file) :is-shared])))
|
||||
(t/is (= [[:set-file-shared {:id (:id file) :is-shared true}]] calls))))
|
||||
done))))
|
||||
|
||||
(t/deftest unshare-a-file
|
||||
(t/async
|
||||
done
|
||||
(let [file (cthf/sample-file :file1)]
|
||||
((share file false
|
||||
(fn [state calls]
|
||||
(t/is (false? (get-in state [:files (:id file) :is-shared])))
|
||||
(t/is (= [[:set-file-shared {:id (:id file) :is-shared false}]] calls))))
|
||||
done))))
|
||||
@ -0,0 +1,203 @@
|
||||
;; 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-library-updates-test
|
||||
"How a file learns that a linked library changed and offers to sync: the
|
||||
library change pushed by the server, the check of which libraries need a
|
||||
sync, the sync dialog and its update and dismiss actions, and the sync
|
||||
itself from an external library."
|
||||
(:require
|
||||
[app.common.test-helpers.components :as cthc]
|
||||
[app.common.test-helpers.composable.core :as tm]
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.store :as st]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.composable-tests.interpreter :as ftm]
|
||||
[frontend-tests.helpers.mock :as mock]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(t/use-fixtures :each ftm/fixture)
|
||||
|
||||
(def ^:private old-color "#111111")
|
||||
(def ^:private new-color "#222222")
|
||||
|
||||
(def ^:private synced-at (js/Date. 1000))
|
||||
(def ^:private component-changed-at (js/Date. 2000))
|
||||
(def ^:private library-changed-at (js/Date. 3000))
|
||||
|
||||
(defn- library-and-file
|
||||
"A library whose component :lib-c changed after the file last synced
|
||||
with it, and a file with a copy of :lib-c. With `changed?`, the main
|
||||
child got a fill after the copy was made, so a sync changes the copy."
|
||||
[changed?]
|
||||
(let [library (-> (cthf/sample-file :library :is-shared true)
|
||||
(ctho/add-simple-component :lib-c :lib-main :lib-main-child
|
||||
:child-params {:fills (cths/sample-fills-color
|
||||
:fill-color old-color)})
|
||||
;; `ctkl/update-component` would stamp the current date
|
||||
(ctf/update-file-data
|
||||
#(assoc-in % [:components (cthi/id :lib-c) :modified-at] component-changed-at))
|
||||
(assoc :synced-at synced-at
|
||||
:modified-at library-changed-at))
|
||||
file (-> (cthf/sample-file :file1)
|
||||
(cthc/instantiate-component :lib-c :copy
|
||||
:library library
|
||||
:children-labels [:copy-child])
|
||||
(ctho/add-rect :rect))]
|
||||
[(cond-> library
|
||||
changed?
|
||||
(cths/update-shape :lib-main-child :fills (cths/sample-fills-color :fill-color new-color)))
|
||||
file]))
|
||||
|
||||
(defn- install-with-library!
|
||||
[changed?]
|
||||
(let [[library file] (library-and-file changed?)]
|
||||
(ftm/install! (-> (tm/make-situation file)
|
||||
(tm/with-aux-files {(:id library) library})))
|
||||
library))
|
||||
|
||||
(defn- live-library
|
||||
[]
|
||||
(get-in @st/state [:files (cthi/id :library)]))
|
||||
|
||||
(defn- copy-fill
|
||||
[]
|
||||
(-> (cths/get-shape (ftm/current-file) :copy-child) :fills first :fill-color))
|
||||
|
||||
(defn- sleep
|
||||
[ms]
|
||||
(js/Promise. (fn [resolve] (js/setTimeout resolve ms))))
|
||||
|
||||
(defn- await-until
|
||||
"Wait up to a second for `ready?`."
|
||||
[ready?]
|
||||
(ftm/await-step [] 1000 ready?))
|
||||
|
||||
(defn- notification
|
||||
[]
|
||||
(:notification @st/state))
|
||||
|
||||
(defn- rpc-commands
|
||||
[]
|
||||
(set (map :cmd @mock/rpc-calls)))
|
||||
|
||||
;; ----- Which libraries need a sync
|
||||
|
||||
(t/deftest assets-need-sync
|
||||
(let [[library file] (library-and-file true)
|
||||
data (:data file)]
|
||||
(t/testing "a library changed after the last sync, with a used component changed too"
|
||||
(t/is (= [{:shape-id (cthi/id :copy) :asset-id (cthi/id :lib-c)
|
||||
:asset-type :component :container-id (cthf/current-page-id file)}]
|
||||
(vec (dwl/assets-need-sync library data)))))
|
||||
(t/testing "the file itself"
|
||||
(t/is (nil? (dwl/assets-need-sync library (assoc data :id (:id library))))))
|
||||
(t/testing "a library not changed since the last sync"
|
||||
(t/is (nil? (dwl/assets-need-sync (assoc library :synced-at (js/Date. 4000)) data))))
|
||||
(t/testing "changes before the date the user dismissed the updates"
|
||||
(t/is (nil? (dwl/assets-need-sync library data (js/Date. 4000)))))
|
||||
(t/testing "a used component that did not change"
|
||||
(t/is (empty? (dwl/assets-need-sync (assoc library :synced-at (js/Date. 2500)) data))))))
|
||||
|
||||
;; ----- The sync dialog
|
||||
|
||||
(defn- sync-dialog-shown?
|
||||
[]
|
||||
(= :sync-dialog (:tag (notification))))
|
||||
|
||||
(t/deftest ^:async sync-dialog-updates-the-copies
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{rp/cmd! mock/rpc-cmd-mock}
|
||||
(install-with-library! true)
|
||||
(t/is (= :settled (await (ftm/await-step [(dwl/notify-sync-file)] 1000 sync-dialog-shown?))))
|
||||
((-> (notification) :accept :callback))
|
||||
(await (await-until #(= new-color (copy-fill))))
|
||||
(t/is (= new-color (copy-fill)))
|
||||
(t/is (not= :visible (:status (notification))))
|
||||
(t/is (not= synced-at (:synced-at (live-library))))
|
||||
(t/is (contains? (rpc-commands) :update-file-library-sync-status)))))
|
||||
|
||||
(t/deftest ^:async sync-dialog-dismiss-ignores-the-changes
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{rp/cmd! mock/rpc-cmd-mock}
|
||||
(install-with-library! true)
|
||||
(await (ftm/await-step [(dwl/notify-sync-file)] 1000 sync-dialog-shown?))
|
||||
((-> (notification) :cancel :callback))
|
||||
(t/is (some? (:ignore-sync-until (ftm/current-file))))
|
||||
(t/is (contains? (rpc-commands) :ignore-file-library-sync-status))
|
||||
(t/is (= old-color (copy-fill)))
|
||||
(t/testing "no dialog for the same changes afterwards"
|
||||
(st/emit! (dwl/notify-sync-file))
|
||||
(await (sleep 50))
|
||||
(t/is (not (sync-dialog-shown?)))))))
|
||||
|
||||
(t/deftest ^:async no-sync-dialog-when-the-sync-changes-nothing
|
||||
(install-with-library! false)
|
||||
(st/emit! (dwl/notify-sync-file))
|
||||
(await (sleep 50))
|
||||
(t/is (not (sync-dialog-shown?))))
|
||||
|
||||
(defn- view-only
|
||||
[]
|
||||
(ptk/reify ::view-only
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:permissions :can-edit] false))))
|
||||
|
||||
(t/deftest ^:async no-sync-dialog-without-edit-permission
|
||||
(install-with-library! true)
|
||||
(st/emit! (view-only) (dwl/notify-sync-file))
|
||||
(await (sleep 50))
|
||||
(t/is (not (sync-dialog-shown?))))
|
||||
|
||||
;; ----- Changes pushed by the server
|
||||
|
||||
(defn- rename-change
|
||||
[library]
|
||||
{:type :mod-obj
|
||||
:page-id (cthf/current-page-id library)
|
||||
:id (cthi/id :lib-main-child)
|
||||
:operations [{:type :set :attr :name :val "Renamed"}]})
|
||||
|
||||
(t/deftest ^:async library-change-from-the-server
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{dwl/fetch-library-thumbnails (mock/stub (fn [_] (ptk/data-event ::no-thumbnails)))}
|
||||
(let [library (install-with-library! true)]
|
||||
(await (ftm/await-step [(dwl/ext-library-changed (:id library) (js/Date. 5000) 7
|
||||
[(rename-change library)])]))
|
||||
(let [library' (live-library)]
|
||||
(t/is (= 7 (:revn library')))
|
||||
(t/is (= (js/Date. 5000) (:modified-at library')))
|
||||
(t/is (= "Renamed" (:name (cths/get-shape library' :lib-main-child)))))))))
|
||||
|
||||
;; ----- Sync from an external library
|
||||
|
||||
(t/deftest ^:async sync-from-an-external-library
|
||||
(await
|
||||
(mock/with-mocks*
|
||||
{rp/cmd! mock/rpc-cmd-mock}
|
||||
(let [library (install-with-library! true)]
|
||||
(await (ftm/await-step [(dwl/set-updating-library true)
|
||||
(dwl/sync-file (:id (ftm/current-file)) (:id library))]))
|
||||
(t/is (= new-color (copy-fill)))
|
||||
(t/is (not (:updating-library @st/state)))
|
||||
(t/is (not= synced-at (:synced-at (live-library))))
|
||||
(t/is (contains? (rpc-commands) :update-file-library-sync-status))))))
|
||||
|
||||
(t/deftest ^:async set-updating-library
|
||||
(await (ftm/await-step [(dwl/set-updating-library true)]))
|
||||
(t/is (true? (:updating-library @st/state)))
|
||||
(await (ftm/await-step [(dwl/set-updating-library false)]))
|
||||
(t/is (nil? (:updating-library @st/state))))
|
||||
@ -32,6 +32,9 @@
|
||||
[frontend-tests.data.workspace-context-menu-test]
|
||||
[frontend-tests.data.workspace-edit-watchers-test]
|
||||
[frontend-tests.data.workspace-interactions-test]
|
||||
[frontend-tests.data.workspace-libraries-test]
|
||||
[frontend-tests.data.workspace-library-links-test]
|
||||
[frontend-tests.data.workspace-library-updates-test]
|
||||
[frontend-tests.data.workspace-mcp-test]
|
||||
[frontend-tests.data.workspace-media-test]
|
||||
[frontend-tests.data.workspace-pages-test]
|
||||
@ -194,6 +197,9 @@
|
||||
'frontend-tests.data.workspace-context-menu-test
|
||||
'frontend-tests.data.workspace-edit-watchers-test
|
||||
'frontend-tests.data.workspace-interactions-test
|
||||
'frontend-tests.data.workspace-libraries-test
|
||||
'frontend-tests.data.workspace-library-links-test
|
||||
'frontend-tests.data.workspace-library-updates-test
|
||||
'frontend-tests.data.workspace-mcp-test
|
||||
'frontend-tests.data.workspace-media-test
|
||||
'frontend-tests.data.workspace-pages-test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user