mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🔧 Add more validations for components, to avoid some crashes (#7820)
* 🔧 Validate only after propagation in tests * 💄 Enhance some component sync traces * 🔧 Add fake uuid generator for debugging * 🐛 Remove old feature of advancing references when reset changes Since long time ago, we only allow to reset changes in the top copy shape. In this case the near and the remote shapes are the same, so the advance-ref has no effect. * 🐛 Fix some bugs and add validations, repair and migrations Also added several utilities to debug and to create scripts that processes files * 🐛 Fix misplaced parenthesis passing propagate-fn to wrong function The :propagate-fn keyword argument was incorrectly placed inside the ths/get-shape call instead of being passed to tho/reset-overrides. This caused reset-overrides to never propagate component changes, making the test not validate what it intended. * 🐛 Accept and forward :include-deleted? in find-near-match Callers were passing :include-deleted? true but the parameter was not in the destructuring, so it was silently ignored and the function always hardcoded true. This made the API misleading and would cause incorrect behavior if called with :include-deleted? false. * 💄 Use set/union alias instead of fully-qualified clojure.set/union The namespace already requires [clojure.set :as set], so use the alias for consistency. * 🐛 Add tests for reset-overrides with and without propagate-fn Add two focused tests to comp_reset_test to cover the propagate-fn path in reset-overrides: - test-reset-with-propagation-updates-copies: verifies that resetting an override on a nested copy inside a main and supplying propagate-fn causes the canonical color to appear in all downstream copies. - test-reset-without-propagation-does-not-update-copies: regression guard for the misplaced-parenthesis bug; confirms that omitting propagate-fn leaves copies with the overridden value because the component sync never runs. --------- Co-authored-by: Andrey Antukh <niwi@niwi.nz>
This commit is contained in:
parent
909427d442
commit
a3ea9fbecb
115
common/src/app/common/files/comp_processors.cljc
Normal file
115
common/src/app/common/files/comp_processors.cljc
Normal file
@ -0,0 +1,115 @@
|
||||
;; 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
|
||||
|
||||
(ns app.common.files.comp-processors
|
||||
"Repair, migration or transformation utilities for components."
|
||||
(:require
|
||||
[app.common.logging :as log]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]))
|
||||
|
||||
(log/set-level! :warn)
|
||||
|
||||
(defn remove-unneeded-objects-in-components
|
||||
"Some components have an :objects attribute, despite not being deleted. This removes it.
|
||||
It also adds an empty :objects if it's deleted and does not have it."
|
||||
[file-data]
|
||||
(ctf/update-components
|
||||
file-data
|
||||
(fn [component]
|
||||
(if (:deleted component)
|
||||
(if (nil? (:objects component))
|
||||
(do
|
||||
(log/warn :msg "Adding empty :objects to deleted component"
|
||||
:component-id (:id component)
|
||||
:component-name (:name component)
|
||||
:file-id (:id file-data))
|
||||
(assoc component :objects {}))
|
||||
component)
|
||||
(if (contains? component :objects)
|
||||
(do
|
||||
(log/warn :msg "Removing :objects from non-deleted component"
|
||||
:component-id (:id component)
|
||||
:component-name (:name component)
|
||||
:file-id (:id file-data))
|
||||
(dissoc component :objects))
|
||||
component)))))
|
||||
|
||||
(defn fix-missing-swap-slots
|
||||
"Locate shapes that have been swapped (i.e. their shape-ref does not point to the near match) but
|
||||
they don't have a swap slot. In this case, add one pointing to the near match."
|
||||
[file-data libraries]
|
||||
(ctf/update-all-shapes
|
||||
file-data
|
||||
(fn [shape]
|
||||
(if (ctk/subcopy-head? shape)
|
||||
(let [container (:container (meta shape))
|
||||
file {:id (:id file-data) :data file-data}
|
||||
near-match (ctf/find-near-match file container libraries shape :include-deleted? true :with-context? false)]
|
||||
(if (and (some? near-match)
|
||||
(not= (:shape-ref shape) (:id near-match))
|
||||
(nil? (ctk/get-swap-slot shape)))
|
||||
(let [updated-shape (ctk/set-swap-slot shape (:id near-match))]
|
||||
(log/warn :msg "Adding missing swap slot to shape"
|
||||
:shape-id (:id shape)
|
||||
:shape-name (:name shape)
|
||||
:swap-slot (:id near-match)
|
||||
:file-id (:id file)
|
||||
:container-id (:id container)
|
||||
:container-type (:type container))
|
||||
{:result :update :updated-shape updated-shape})
|
||||
{:result :keep}))
|
||||
{:result :keep}))))
|
||||
|
||||
(defn sync-component-id-with-ref-shape
|
||||
"Ensure that all copies heads have the same component id and file as the referenced shape.
|
||||
There may be bugs that cause them to get out of sync."
|
||||
[file-data libraries]
|
||||
(letfn [(sync-one-iteration
|
||||
[file-data libraries]
|
||||
(ctf/update-all-shapes
|
||||
file-data
|
||||
(fn [shape]
|
||||
(if (and (ctk/subcopy-head? shape) (nil? (ctk/get-swap-slot shape)))
|
||||
(let [container (:container (meta shape))
|
||||
file {:id (:id file-data) :data file-data}
|
||||
ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true :with-context? true})]
|
||||
(if (and (some? ref-shape)
|
||||
(or (not= (:component-id shape) (:component-id ref-shape))
|
||||
(not= (:component-file shape) (:component-file ref-shape))))
|
||||
(let [shape' (cond-> shape
|
||||
(some? (:component-id ref-shape))
|
||||
(assoc :component-id (:component-id ref-shape))
|
||||
|
||||
(nil? (:component-id ref-shape))
|
||||
(dissoc :component-id)
|
||||
|
||||
(some? (:component-file ref-shape))
|
||||
(assoc :component-file (:component-file ref-shape))
|
||||
|
||||
(nil? (:component-file ref-shape))
|
||||
(dissoc :component-file))]
|
||||
(log/warn :msg "Syncing component id and file with ref shape"
|
||||
:shape-id (:id shape)
|
||||
:shape-name (:name shape)
|
||||
:component-id (:component-id shape')
|
||||
:component-file (:component-file shape')
|
||||
:ref-shape-id (:id ref-shape)
|
||||
:file-id (:id file)
|
||||
:container-id (:id container)
|
||||
:container-type (:type container))
|
||||
{:result :update :updated-shape shape'})
|
||||
{:result :keep}))
|
||||
{:result :keep}))))]
|
||||
;; If a copy inside a main is updated, we need to repeat the process for the change to be
|
||||
;; propagated to all copies.
|
||||
(loop [current-data file-data
|
||||
iteration 0]
|
||||
(let [next-data (sync-one-iteration current-data libraries)]
|
||||
(if (or (= current-data next-data)
|
||||
(> iteration 20)) ;; safety bound
|
||||
next-data
|
||||
(recur next-data (inc iteration)))))))
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.files.changes :as cpc]
|
||||
[app.common.files.comp-processors :as cfcp]
|
||||
[app.common.files.defaults :as cfd]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
@ -1786,6 +1787,24 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0018-remove-unneeded-objects-from-components"
|
||||
[data _]
|
||||
(cfcp/remove-unneeded-objects-in-components data))
|
||||
|
||||
(defmethod migrate-data "0019-fix-missing-swap-slots"
|
||||
[data _]
|
||||
(let [libraries (if (:libs data)
|
||||
(deref (:libs data))
|
||||
{})]
|
||||
(cfcp/fix-missing-swap-slots data libraries)))
|
||||
|
||||
(defmethod migrate-data "0020-sync-component-id-with-near-main"
|
||||
[data _]
|
||||
(let [libraries (if (:libs data)
|
||||
(deref (:libs data))
|
||||
{})]
|
||||
(cfcp/sync-component-id-with-ref-shape data libraries)))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@ -1860,4 +1879,7 @@
|
||||
"0015-fix-text-attrs-blank-strings"
|
||||
"0015-clean-shadow-color"
|
||||
"0016-copy-fills-from-position-data-to-text-node"
|
||||
"0017-fix-layout-flex-dir"]))
|
||||
"0017-fix-layout-flex-dir"
|
||||
"0018-remove-unneeded-objects-from-components"
|
||||
"0019-fix-missing-swap-slots"
|
||||
"0020-sync-component-id-with-near-main"]))
|
||||
|
||||
@ -334,6 +334,31 @@
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-id-mismatch
|
||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
(fn [shape]
|
||||
; Set the component-id and component-file to the ones of the near main
|
||||
(log/debug :hint (str " -> set component-id to " (:component-id args)))
|
||||
(log/debug :hint (str " -> set component-file to " (:component-file args)))
|
||||
(cond-> shape
|
||||
(some? (:component-id args))
|
||||
(assoc :component-id (:component-id args))
|
||||
|
||||
(nil? (:component-id args))
|
||||
(dissoc :component-id)
|
||||
|
||||
(some? (:component-file args))
|
||||
(assoc :component-file (:component-file args))
|
||||
|
||||
(nil? (:component-file args))
|
||||
(dissoc :component-file)))]
|
||||
|
||||
(log/dbg :hint "repairing shape :component-id-mismatch" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||
(-> (pcb/empty-changes nil page-id)
|
||||
(pcb/with-file-data file-data)
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :ref-shape-is-head
|
||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||
(let [repair-shape
|
||||
@ -501,7 +526,7 @@
|
||||
(pcb/update-shapes [(:id shape)] repair-shape))))
|
||||
|
||||
(defmethod repair-error :component-nil-objects-not-allowed
|
||||
[_ {:keys [shape] :as error} file-data _]
|
||||
[_ {component :shape} file-data _] ; in this error the :shape argument is the component
|
||||
(let [repair-component
|
||||
(fn [component]
|
||||
;; Remove the objects key, or set it to {} if the component is deleted
|
||||
@ -513,10 +538,26 @@
|
||||
(log/debug :hint " -> remove :objects")
|
||||
(dissoc component :objects))))]
|
||||
|
||||
(log/dbg :hint "repairing component :component-nil-objects-not-allowed" :id (:id shape) :name (:name shape))
|
||||
(log/dbg :hint "repairing component :component-nil-objects-not-allowed" :id (:id component) :name (:name component))
|
||||
(-> (pcb/empty-changes nil)
|
||||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component (:id shape) repair-component))))
|
||||
(pcb/update-component (:id component) repair-component))))
|
||||
|
||||
(defmethod repair-error :non-deleted-component-cannot-have-objects
|
||||
[_ {component :shape} file-data _] ; in this error the :shape argument is the component
|
||||
(let [repair-component
|
||||
(fn [component]
|
||||
; Remove the :objects field
|
||||
(if-not (:deleted component)
|
||||
(do
|
||||
(log/debug :hint " -> remove :objects")
|
||||
(dissoc component :objects))
|
||||
component))]
|
||||
|
||||
(log/dbg :hint "repairing component :non-deleted-component-cannot-have-objects" :id (:id component) :name (:name component))
|
||||
(-> (pcb/empty-changes nil)
|
||||
(pcb/with-library-data file-data)
|
||||
(pcb/update-component (:id component) repair-component))))
|
||||
|
||||
(defmethod repair-error :invalid-text-touched
|
||||
[_ {:keys [shape page-id] :as error} file-data _]
|
||||
|
||||
@ -51,6 +51,7 @@
|
||||
:ref-shape-is-head
|
||||
:ref-shape-is-not-head
|
||||
:shape-ref-in-main
|
||||
:component-id-mismatch
|
||||
:root-main-not-allowed
|
||||
:nested-main-not-allowed
|
||||
:root-copy-not-allowed
|
||||
@ -59,6 +60,7 @@
|
||||
:not-head-copy-not-allowed
|
||||
:not-component-not-allowed
|
||||
:component-nil-objects-not-allowed
|
||||
:non-deleted-component-cannot-have-objects
|
||||
:instance-head-not-frame
|
||||
:invalid-text-touched
|
||||
:misplaced-slot
|
||||
@ -326,6 +328,20 @@
|
||||
:component-file (:component-file ref-shape)
|
||||
:component-id (:component-id ref-shape)))))
|
||||
|
||||
(defn- check-ref-component-id
|
||||
"Validate that if the copy has not been swapped, the component-id and component-file are
|
||||
the same as in the referenced shape in the near main."
|
||||
[shape file page libraries]
|
||||
(when (nil? (ctk/get-swap-slot shape))
|
||||
(when-let [ref-shape (ctf/find-ref-shape file page libraries shape :include-deleted? true)]
|
||||
(when (or (not= (:component-id shape) (:component-id ref-shape))
|
||||
(not= (:component-file shape) (:component-file ref-shape)))
|
||||
(report-error :component-id-mismatch
|
||||
"Nested copy component-id and component-file must be the same as the near main"
|
||||
shape file page
|
||||
:component-id (:component-id ref-shape)
|
||||
:component-file (:component-file ref-shape))))))
|
||||
|
||||
(defn- check-empty-swap-slot
|
||||
"Validate that this shape does not have any swap slot."
|
||||
[shape file page]
|
||||
@ -350,6 +366,19 @@
|
||||
"This shape has children with the same swap slot"
|
||||
shape file page)))
|
||||
|
||||
(defn- check-required-swap-slot
|
||||
"Validate that the shape has swap-slot if it's a subinstance head and the ref shape is not the
|
||||
matching shape by position in the near main."
|
||||
[shape file page libraries]
|
||||
(let [near-match (ctf/find-near-match file page libraries shape :include-deleted? true :with-context? false)]
|
||||
(when (and (some? near-match)
|
||||
(not= (:shape-ref shape) (:id near-match))
|
||||
(nil? (ctk/get-swap-slot shape)))
|
||||
(report-error :missing-slot
|
||||
"Shape has been swapped, should have swap slot"
|
||||
shape file page
|
||||
:swap-slot (or (ctk/get-swap-slot near-match) (:id near-match))))))
|
||||
|
||||
(defn- check-valid-touched
|
||||
"Validate that the text touched flags are coherent."
|
||||
[shape file page]
|
||||
@ -418,6 +447,8 @@
|
||||
(check-component-not-main-head shape file page libraries)
|
||||
(check-component-not-root shape file page)
|
||||
(check-valid-touched shape file page)
|
||||
(check-ref-component-id shape file page libraries)
|
||||
(check-required-swap-slot shape file page libraries)
|
||||
;; We can have situations where the nested copy and the ancestor copy come from different libraries and some of them have been dettached
|
||||
;; so we only validate the shape-ref if the ancestor is from a valid library
|
||||
(when library-exists
|
||||
@ -458,8 +489,7 @@
|
||||
(defn- check-variant-container
|
||||
"Shape is a variant container, so:
|
||||
-all its children should be variants with variant-id equals to the shape-id
|
||||
-all the components should have the same properties
|
||||
"
|
||||
-all the components should have the same properties"
|
||||
[shape file page]
|
||||
(let [shape-id (:id shape)
|
||||
shapes (:shapes shape)
|
||||
@ -648,6 +678,13 @@
|
||||
"Component main not allowed inside other component"
|
||||
main-instance file component-page))))
|
||||
|
||||
(defn- check-not-objects
|
||||
[component file]
|
||||
(when (d/not-empty? (:objects component))
|
||||
(report-error :non-deleted-component-cannot-have-objects
|
||||
"A non-deleted component cannot have shapes inside"
|
||||
component file nil)))
|
||||
|
||||
(defn- check-component
|
||||
"Validate semantic coherence of a component. Report all errors found."
|
||||
[component file]
|
||||
@ -656,7 +693,8 @@
|
||||
"Objects list cannot be nil"
|
||||
component file nil))
|
||||
(when-not (:deleted component)
|
||||
(check-main-inside-main component file))
|
||||
(check-main-inside-main component file)
|
||||
(check-not-objects component file))
|
||||
(when (:deleted component)
|
||||
(check-component-duplicate-swap-slot component file)
|
||||
(check-ref-cycles component file))
|
||||
@ -674,8 +712,6 @@
|
||||
;; PUBLIC API: VALIDATION FUNCTIONS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare check-swap-slots)
|
||||
|
||||
(defn validate-file
|
||||
"Validate full referential integrity and semantic coherence on file data.
|
||||
|
||||
@ -686,8 +722,6 @@
|
||||
|
||||
(doseq [page (filter :id (ctpl/pages-seq data))]
|
||||
(check-shape uuid/zero file page libraries)
|
||||
(when (str/includes? (:name file) "check-swap-slot")
|
||||
(check-swap-slots uuid/zero file page libraries))
|
||||
(->> (get-orphan-shapes page)
|
||||
(run! #(check-shape % file page libraries))))
|
||||
|
||||
@ -728,40 +762,3 @@
|
||||
:hint "error on validating file referential integrity"
|
||||
:file-id (:id file)
|
||||
:details errors)))
|
||||
|
||||
(declare compare-slots)
|
||||
|
||||
;; Optional check to look for missing swap slots.
|
||||
;; Search for copies that do not point the shape-ref to the near component but don't have swap slot
|
||||
;; (looking for position relative to the parent, in the copy and the main).
|
||||
;;
|
||||
;; This check cannot be generally enabled, because files that have been migrated from components v1
|
||||
;; may have copies with shapes that do not match by position, but have not been swapped. So we enable
|
||||
;; it for specific files only. To activate the check, you need to add the string "check-swap-slot" to
|
||||
;; the name of the file.
|
||||
(defn- check-swap-slots
|
||||
[shape-id file page libraries]
|
||||
(let [shape (ctst/get-shape page shape-id)]
|
||||
(if (and (ctk/instance-root? shape) (ctk/in-component-copy? shape))
|
||||
(let [ref-shape (ctf/find-ref-shape file page libraries shape :include-deleted? true :with-context? true)
|
||||
container (:container (meta ref-shape))]
|
||||
(when (some? ref-shape)
|
||||
(compare-slots shape ref-shape file page container)))
|
||||
(doall (for [child-id (:shapes shape)]
|
||||
(check-swap-slots child-id file page libraries))))))
|
||||
|
||||
(defn- compare-slots
|
||||
[shape-copy shape-main file container-copy container-main]
|
||||
(if (and (not= (:shape-ref shape-copy) (:id shape-main))
|
||||
(nil? (ctk/get-swap-slot shape-copy)))
|
||||
(report-error :missing-slot
|
||||
"Shape has been swapped, should have swap slot"
|
||||
shape-copy file container-copy
|
||||
:swap-slot (or (ctk/get-swap-slot shape-main) (:id shape-main)))
|
||||
(when (nil? (ctk/get-swap-slot shape-copy))
|
||||
(let [children-id-pairs (d/zip-all (:shapes shape-copy) (:shapes shape-main))]
|
||||
(doall (for [[child-copy-id child-main-id] children-id-pairs]
|
||||
(let [child-copy (ctst/get-shape container-copy child-copy-id)
|
||||
child-main (ctst/get-shape container-main child-main-id)]
|
||||
(when (and (some? child-copy) (some? child-main))
|
||||
(compare-slots child-copy child-main file container-copy container-main)))))))))
|
||||
|
||||
@ -333,7 +333,7 @@
|
||||
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> promote to root")
|
||||
(assoc % :component-root true)))
|
||||
|
||||
:always
|
||||
(some? (ctk/get-swap-slot shape))
|
||||
; First level subinstances of a detached component can't have swap-slot
|
||||
(pcb/update-shapes [shape-id] #(do (log/trace :msg " -> remove swap-slot")
|
||||
(ctk/remove-swap-slot %)))
|
||||
@ -364,7 +364,7 @@
|
||||
(let [ref-shape (ctf/find-ref-shape file container libraries shape {:include-deleted? true})]
|
||||
(cond-> changes
|
||||
(some? (:shape-ref ref-shape))
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (advanced)")
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (advanced to " (:shape-ref ref-shape) ")"))
|
||||
(assoc % :shape-ref (:shape-ref ref-shape))))
|
||||
|
||||
;; When advancing level, the normal touched groups (not swap slots) of the
|
||||
@ -374,16 +374,18 @@
|
||||
(pcb/update-shapes
|
||||
[(:id shape)]
|
||||
#(do (log/trace :msg " (merge touched)")
|
||||
(log/trace :msg (str " (ref-shape: " (:id ref-shape) ")"))
|
||||
(log/trace :msg (str " (ref touched: " (:touched ref-shape) ")"))
|
||||
(assoc % :touched
|
||||
(clojure.set/union (:touched shape)
|
||||
(ctk/normal-touched-groups ref-shape)))))
|
||||
(set/union (:touched shape)
|
||||
(ctk/normal-touched-groups ref-shape)))))
|
||||
|
||||
;; Swap slot must also be copied if the current shape has not any,
|
||||
;; except if this is the first level subcopy.
|
||||
(and (some? (ctk/get-swap-slot ref-shape))
|
||||
(nil? (ctk/get-swap-slot shape))
|
||||
(not= (:id shape) shape-id))
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg " (got swap-slot)")
|
||||
(pcb/update-shapes [(:id shape)] #(do (log/trace :msg (str " (got swap-slot " (ctk/get-swap-slot ref-shape) ")"))
|
||||
(ctk/set-swap-slot % (ctk/get-swap-slot ref-shape))))
|
||||
|
||||
;; If we can't get the ref-shape (e.g. it's in an external library not linked),
|
||||
@ -771,14 +773,6 @@
|
||||
;; is different than the one in the near component (Shape-2-2-1)
|
||||
;; but it's not touched.
|
||||
|
||||
(defn- redirect-shaperef ;;Set the :shape-ref of a shape pointing to the :id of its remote-shape
|
||||
([container libraries shape]
|
||||
(redirect-shaperef nil nil shape (ctf/find-remote-shape container libraries shape)))
|
||||
([_ _ shape remote-shape]
|
||||
(if (some? (:shape-ref shape))
|
||||
(assoc shape :shape-ref (:id remote-shape))
|
||||
shape)))
|
||||
|
||||
(defn generate-sync-shape-direct
|
||||
"Generate changes to synchronize one shape that is the root of a component
|
||||
instance, and all its children, from the given component."
|
||||
@ -790,18 +784,12 @@
|
||||
component (ctkl/get-component library (:component-id shape-inst) true)]
|
||||
(if (and (ctk/in-component-copy? shape-inst)
|
||||
(or (ctf/direct-copy? shape-inst component container nil libraries) reset?)) ; In a normal sync, we don't want to sync remote mains, only direct/near
|
||||
(let [redirect-shaperef (partial redirect-shaperef container libraries)
|
||||
|
||||
shape-main (when component
|
||||
(let [shape-main (when component
|
||||
(if reset?
|
||||
;; the reset is against the ref-shape, not against the original shape of the component
|
||||
(ctf/find-ref-shape file container libraries shape-inst)
|
||||
(ctf/get-ref-shape library component shape-inst)))
|
||||
|
||||
shape-inst (if reset?
|
||||
(redirect-shaperef shape-inst shape-main)
|
||||
shape-inst)
|
||||
|
||||
initial-root? (:component-root shape-inst)
|
||||
|
||||
root-inst shape-inst
|
||||
@ -819,8 +807,8 @@
|
||||
root-inst
|
||||
root-main
|
||||
reset?
|
||||
initial-root?
|
||||
redirect-shaperef)
|
||||
initial-root?)
|
||||
|
||||
;; If the component is not found, because the master component has been
|
||||
;; deleted or the library unlinked, do nothing.
|
||||
changes))
|
||||
@ -844,7 +832,7 @@
|
||||
nil))))))
|
||||
|
||||
(defn- generate-sync-shape-direct-recursive
|
||||
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root? redirect-shaperef]
|
||||
[changes container shape-inst component library file libraries shape-main root-inst root-main reset? initial-root?]
|
||||
(shape-log :debug (:id shape-inst) container
|
||||
:msg "Sync shape direct recursive"
|
||||
:shape-inst (str (:name shape-inst) " " (pretty-uuid (:id shape-inst)))
|
||||
@ -891,9 +879,6 @@
|
||||
children-inst (vec (ctn/get-direct-children container shape-inst))
|
||||
children-main (vec (ctn/get-direct-children component-container shape-main))
|
||||
|
||||
children-inst (if reset?
|
||||
(map #(redirect-shaperef %) children-inst) children-inst)
|
||||
|
||||
only-inst (fn [changes child-inst]
|
||||
(shape-log :trace (:id child-inst) container
|
||||
:msg "Only inst"
|
||||
@ -942,8 +927,7 @@
|
||||
root-inst
|
||||
root-main
|
||||
reset?
|
||||
initial-root?
|
||||
redirect-shaperef))
|
||||
initial-root?))
|
||||
|
||||
swapped (fn [changes child-inst child-main]
|
||||
(shape-log :trace (:id child-inst) container
|
||||
@ -1008,16 +992,13 @@
|
||||
the values in the shape and all its children."
|
||||
[changes file libraries container shape-id]
|
||||
(shape-log :debug shape-id container :msg "Sync shape inverse" :shape (str shape-id))
|
||||
(let [redirect-shaperef (partial redirect-shaperef container libraries)
|
||||
shape-inst (ctn/get-shape container shape-id)
|
||||
(let [shape-inst (ctn/get-shape container shape-id)
|
||||
library (dm/get-in libraries [(:component-file shape-inst) :data])
|
||||
component (ctkl/get-component library (:component-id shape-inst))
|
||||
|
||||
shape-main (when component
|
||||
(ctf/find-remote-shape container libraries shape-inst))
|
||||
|
||||
shape-inst (redirect-shaperef shape-inst shape-main)
|
||||
|
||||
initial-root? (:component-root shape-inst)
|
||||
|
||||
root-inst shape-inst
|
||||
@ -1038,12 +1019,11 @@
|
||||
shape-main
|
||||
root-inst
|
||||
root-main
|
||||
initial-root?
|
||||
redirect-shaperef)
|
||||
initial-root?)
|
||||
changes)))
|
||||
|
||||
(defn- generate-sync-shape-inverse-recursive
|
||||
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root? redirect-shaperef]
|
||||
[changes container shape-inst component library file libraries shape-main root-inst root-main initial-root?]
|
||||
(shape-log :trace (:id shape-inst) container
|
||||
:msg "Sync shape inverse recursive"
|
||||
:shape (str (:name shape-inst))
|
||||
@ -1100,8 +1080,6 @@
|
||||
children-main (mapv #(ctn/get-shape component-container %)
|
||||
(:shapes shape-main))
|
||||
|
||||
children-inst (map #(redirect-shaperef %) children-inst)
|
||||
|
||||
only-inst (fn [changes child-inst]
|
||||
(add-shape-to-main changes
|
||||
child-inst
|
||||
@ -1130,8 +1108,7 @@
|
||||
child-main
|
||||
root-inst
|
||||
root-main
|
||||
initial-root?
|
||||
redirect-shaperef))
|
||||
initial-root?))
|
||||
|
||||
swapped (fn [changes child-inst child-main]
|
||||
(shape-log :trace (:id child-inst) container
|
||||
@ -1773,6 +1750,23 @@
|
||||
(pcb/update-shapes changes [(:id dest-shape)] ctk/unhead-shape {:ignore-touched true})
|
||||
changes))
|
||||
|
||||
(defn- check-swapped-main
|
||||
[changes dest-shape origin-shape]
|
||||
;; Only for direct updates (from main to copy). Check if the main shape
|
||||
;; has been swapped. If so, the new component-id and component-file must
|
||||
;; be put into the copy.
|
||||
(if (and (= (:shape-ref dest-shape) (:id origin-shape))
|
||||
(ctk/instance-head? dest-shape)
|
||||
(ctk/instance-head? origin-shape)
|
||||
(or (not= (:component-id dest-shape) (:component-id origin-shape))
|
||||
(not= (:component-file dest-shape) (:component-file origin-shape))))
|
||||
(pcb/update-shapes changes [(:id dest-shape)]
|
||||
#(assoc %
|
||||
:component-id (:component-id origin-shape)
|
||||
:component-file (:component-file origin-shape))
|
||||
{:ignore-touched true})
|
||||
changes))
|
||||
|
||||
(defn- update-attrs
|
||||
"The main function that implements the attribute sync algorithm. Copy
|
||||
attributes that have changed in the origin shape to the dest shape.
|
||||
@ -1816,6 +1810,8 @@
|
||||
:always
|
||||
(check-detached-main dest-shape origin-shape)
|
||||
:always
|
||||
(check-swapped-main dest-shape origin-shape)
|
||||
:always
|
||||
(generate-update-tokens container dest-shape origin-shape touched omit-touched? nil))
|
||||
|
||||
(let [sync-group
|
||||
|
||||
@ -177,8 +177,11 @@
|
||||
(thc/instantiate-component component-label copy-root-label copy-root-params)))
|
||||
|
||||
(defn add-nested-component
|
||||
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params]}]
|
||||
[file
|
||||
component1-label main1-root-label main1-child-label
|
||||
component2-label main2-root-label nested-head-label
|
||||
& {:keys [component1-params root1-params main1-child-params
|
||||
component2-params main2-root-params nested-head-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
@ -204,8 +207,13 @@
|
||||
component2-params)))
|
||||
|
||||
(defn add-nested-component-with-copy
|
||||
[file component1-label main1-root-label main1-child-label component2-label main2-root-label nested-head-label copy2-root-label
|
||||
& {:keys [component1-params root1-params main1-child-params component2-params main2-root-params nested-head-params copy2-root-params]}]
|
||||
[file
|
||||
component1-label main1-root-label main1-child-label
|
||||
component2-label main2-root-label nested-head-label
|
||||
copy2-root-label
|
||||
& {:keys [component1-params root1-params main1-child-params
|
||||
component2-params main2-root-params nested-head-params
|
||||
copy2-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
@ -232,6 +240,102 @@
|
||||
:nested-head-params nested-head-params)
|
||||
(thc/instantiate-component component2-label copy2-root-label copy2-root-params)))
|
||||
|
||||
(defn add-two-levels-nested-component
|
||||
[file
|
||||
component1-label main1-root-label main1-child-label
|
||||
component2-label main2-root-label nested-head1-label
|
||||
component3-label main3-root-label nested-head2-label nested-subhead2-label
|
||||
& {:keys [component1-params root1-params main1-child-params
|
||||
component2-params main2-root-params nested-head1-params
|
||||
component3-params main3-root-params nested-head2-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head1-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main1-child-label
|
||||
;;
|
||||
;; {:main3-root-label} [:name Frame3] # [Component :component3-label]
|
||||
;; :nested-head2-label [:name Frame2] @--> [Component :component2-label] :main2-root-label
|
||||
;; :nested-subhead2-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main1-child-label
|
||||
(-> file
|
||||
(add-simple-component component1-label
|
||||
main1-root-label
|
||||
main1-child-label
|
||||
:component-params component1-params
|
||||
:root-params root1-params
|
||||
:child-params main1-child-params)
|
||||
(add-frame main2-root-label (merge {:name "Frame2"}
|
||||
main2-root-params))
|
||||
(thc/instantiate-component component1-label
|
||||
nested-head1-label
|
||||
(assoc nested-head1-params
|
||||
:parent-label main2-root-label))
|
||||
(thc/make-component component2-label
|
||||
main2-root-label
|
||||
component2-params)
|
||||
(add-frame main3-root-label (merge {:name "Frame3"}
|
||||
main3-root-params))
|
||||
(thc/instantiate-component component2-label
|
||||
nested-head2-label
|
||||
(assoc nested-head2-params
|
||||
:parent-label main3-root-label
|
||||
:children-labels [nested-subhead2-label]))
|
||||
(thc/make-component component3-label
|
||||
main3-root-label
|
||||
component3-params)))
|
||||
|
||||
(defn add-two-levels-nested-component-with-copy
|
||||
[file
|
||||
component1-label main1-root-label main1-child-label
|
||||
component2-label main2-root-label nested-head1-label
|
||||
component3-label main3-root-label nested-head2-label nested-subhead2-label
|
||||
copy2-root-label
|
||||
& {:keys [component1-params root1-params main1-child-params
|
||||
component2-params main2-root-params nested-head1-params
|
||||
component3-params main3-root-params nested-head2-params
|
||||
copy2-root-params]}]
|
||||
;; Generated shape tree:
|
||||
;; {:main1-root-label} [:name Frame1] # [Component :component1-label]
|
||||
;; :main1-child-label [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root-label} [:name Frame2] # [Component :component2-label]
|
||||
;; :nested-head1-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main1-child-label
|
||||
;;
|
||||
;; {:main3-root-label} [:name Frame3] # [Component :component3-label]
|
||||
;; :nested-head2-label [:name Frame2] @--> [Component :component2-label] :main2-root-label
|
||||
;; :nested-subhead2-label [:name Frame1] @--> [Component :component1-label] :main1-root-label
|
||||
;; <no-label> [:name Rect1] ---> :main1-child-label
|
||||
;;
|
||||
;; :copy2-label [:name Frame3] #--> [Component :component3-label] :main3-root-label
|
||||
;; <no-label> [:name Frame2] @--> [Component :component2-label] :nested-head2-label
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1-label] :nested-subhead2-label
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> file
|
||||
(add-two-levels-nested-component component1-label
|
||||
main1-root-label
|
||||
main1-child-label
|
||||
component2-label
|
||||
main2-root-label
|
||||
nested-head1-label
|
||||
component3-label
|
||||
main3-root-label
|
||||
nested-head2-label
|
||||
nested-subhead2-label
|
||||
:component1-params component1-params
|
||||
:root1-params root1-params
|
||||
:main1-child-params main1-child-params
|
||||
:component2-params component2-params
|
||||
:main2-root-params main2-root-params
|
||||
:nested-head1-params nested-head1-params
|
||||
:component3-params component3-params
|
||||
:main3-root-params main3-root-params
|
||||
:nested-head2-params nested-head2-params)
|
||||
(thc/instantiate-component component3-label copy2-root-label copy2-root-params)))
|
||||
|
||||
;; ----- Getters
|
||||
|
||||
(defn bottom-shape-by-id
|
||||
@ -274,15 +378,18 @@
|
||||
file-id
|
||||
{file-id file}
|
||||
file-id))]
|
||||
(thf/apply-changes file changes)))
|
||||
(thf/apply-changes file changes :validate? false)))
|
||||
|
||||
(defn swap-component
|
||||
(defn swap-component-
|
||||
"Swap the specified shape by the component specified by component-tag"
|
||||
[file shape component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label]}]
|
||||
[file shape component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label library]}]
|
||||
(let [page (if page-label
|
||||
(thf/get-page file page-label)
|
||||
(thf/current-page file))
|
||||
libraries {(:id file) file}
|
||||
libraries (cond-> {(:id file) file}
|
||||
(some? library)
|
||||
(assoc (:id library) library))
|
||||
library (or library file)
|
||||
|
||||
orig-shapes (when keep-touched? (cfh/get-children-with-self (:objects page) (:id shape)))
|
||||
|
||||
@ -290,10 +397,10 @@
|
||||
(cll/generate-component-swap (pcb/empty-changes)
|
||||
(:objects page)
|
||||
shape
|
||||
(:data file)
|
||||
(:data library)
|
||||
page
|
||||
libraries
|
||||
(-> (thc/get-component file component-tag)
|
||||
(-> (thc/get-component library component-tag)
|
||||
:id)
|
||||
0
|
||||
nil
|
||||
@ -305,26 +412,36 @@
|
||||
[changes nil])
|
||||
|
||||
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(when new-shape-label
|
||||
(thi/rm-id! (:id new-shape))
|
||||
(thi/set-id! new-shape-label (:id new-shape)))
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
(defn swap-component-in-shape [file shape-tag component-tag & {:keys [page-label propagate-fn]}]
|
||||
(swap-component file (ths/get-shape file shape-tag :page-label page-label) component-tag :page-label page-label :propagate-fn propagate-fn))
|
||||
(defn swap-component-in-shape
|
||||
[file shape-tag component-tag & {:keys [page-label propagate-fn keep-touched? new-shape-label library]}]
|
||||
(swap-component- file (ths/get-shape file shape-tag :page-label page-label)
|
||||
component-tag
|
||||
:page-label page-label
|
||||
:propagate-fn propagate-fn
|
||||
:keep-touched? keep-touched?
|
||||
:new-shape-label new-shape-label
|
||||
:library library))
|
||||
|
||||
(defn swap-component-in-first-child [file shape-tag component-tag & {:keys [page-label propagate-fn]}]
|
||||
(defn swap-component-in-first-child
|
||||
[file shape-tag component-tag & {:keys [page-label propagate-fn library]}]
|
||||
(let [first-child-id (->> (ths/get-shape file shape-tag :page-label page-label)
|
||||
:shapes
|
||||
first)]
|
||||
(swap-component file
|
||||
(ths/get-shape-by-id file first-child-id :page-label page-label)
|
||||
component-tag
|
||||
:page-label page-label
|
||||
:propagate-fn propagate-fn)))
|
||||
(swap-component- file
|
||||
(ths/get-shape-by-id file first-child-id :page-label page-label)
|
||||
component-tag
|
||||
:page-label page-label
|
||||
:propagate-fn propagate-fn
|
||||
:library library)))
|
||||
|
||||
(defn update-color
|
||||
"Update the first fill color for the shape identified by shape-tag"
|
||||
@ -339,9 +456,10 @@
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color color)))
|
||||
(:objects page)
|
||||
{})
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
(defn update-bottom-color
|
||||
@ -357,9 +475,10 @@
|
||||
(assoc shape :fills (ths/sample-fills-color :fill-color color)))
|
||||
(:objects page)
|
||||
{})
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
(defn reset-overrides [file shape & {:keys [page-label propagate-fn]}]
|
||||
@ -374,9 +493,10 @@
|
||||
{file-id file}
|
||||
(ctn/make-container container :page)
|
||||
(:id shape)))
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
(defn reset-overrides-in-first-child [file shape-tag & {:keys [page-label propagate-fn]}]
|
||||
@ -398,9 +518,10 @@
|
||||
#{(-> (ths/get-shape file shape-tag :page-label page-label)
|
||||
:id)}
|
||||
{})
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
(defn duplicate-shape [file shape-tag & {:keys [page-label propagate-fn]}]
|
||||
@ -419,8 +540,9 @@
|
||||
(:id file)) ;; file-id
|
||||
(cll/generate-duplicate-changes-update-indices (:objects page) ;; objects
|
||||
#{(:id shape)}))
|
||||
file' (thf/apply-changes file changes)]
|
||||
file' (thf/apply-changes file changes :validate? (not propagate-fn))]
|
||||
(if propagate-fn
|
||||
(propagate-fn file')
|
||||
(-> (propagate-fn file')
|
||||
(thf/validate-file!))
|
||||
file')))
|
||||
|
||||
|
||||
@ -54,12 +54,14 @@
|
||||
([file] (validate-file! file {}))
|
||||
([file libraries]
|
||||
(cfv/validate-file-schema! file)
|
||||
(cfv/validate-file! file libraries)))
|
||||
(cfv/validate-file! file libraries)
|
||||
file))
|
||||
|
||||
(defn apply-changes
|
||||
[file changes]
|
||||
[file changes & {:keys [validate?] :or {validate? true}}]
|
||||
(let [file' (ctf/update-file-data file #(cfc/process-changes % (:redo-changes changes) true))]
|
||||
(validate-file! file')
|
||||
(when validate?
|
||||
(validate-file! file'))
|
||||
file'))
|
||||
|
||||
(defn apply-undo-changes
|
||||
|
||||
@ -82,6 +82,18 @@
|
||||
(:id page)
|
||||
#(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
|
||||
|
||||
(defn update-shape-by-id
|
||||
[file shape-id attr val & {:keys [page-label]}]
|
||||
(let [page (if page-label
|
||||
(thf/get-page file page-label)
|
||||
(thf/current-page file))
|
||||
shape (ctst/get-shape page shape-id)]
|
||||
(update file :data
|
||||
(fn [file-data]
|
||||
(ctpl/update-page file-data
|
||||
(:id page)
|
||||
#(ctst/set-shape % (ctn/set-shape-attr shape attr val)))))))
|
||||
|
||||
(defn update-shape-text
|
||||
[file shape-label attr val & {:keys [page-label]}]
|
||||
(let [page (if page-label
|
||||
|
||||
@ -163,11 +163,15 @@
|
||||
Note that design tokens also are involved, although they go by an alternate
|
||||
route and thus they are not part of :sync-attrs.
|
||||
Also when detaching a nested copy it also needs to trigger a synchronization,
|
||||
even though :shape-ref is not a synced attribute per se"
|
||||
even though :shape-ref, :component-id or :component-file are not synced
|
||||
attributes per se."
|
||||
[attr]
|
||||
(or (contains? sync-attrs attr)
|
||||
(= :shape-ref attr)
|
||||
(= :applied-tokens attr)))
|
||||
(= :applied-tokens attr)
|
||||
(= :component-id attr)
|
||||
(= :component-file attr)
|
||||
(= :component-root attr)))
|
||||
|
||||
(defn instance-root?
|
||||
"Check if this shape is the head of a top instance."
|
||||
|
||||
@ -60,6 +60,9 @@
|
||||
(some? objects)
|
||||
(assoc :objects objects)
|
||||
|
||||
(nil? objects)
|
||||
(dissoc :objects)
|
||||
|
||||
(some? modified-at)
|
||||
(assoc :modified-at modified-at)
|
||||
|
||||
|
||||
@ -55,6 +55,10 @@
|
||||
[page-or-component type]
|
||||
(assoc page-or-component :type type))
|
||||
|
||||
(defn unmake-container
|
||||
[container]
|
||||
(dissoc container :type))
|
||||
|
||||
(defn page?
|
||||
[container]
|
||||
(= (:type container) :page))
|
||||
|
||||
@ -204,7 +204,8 @@
|
||||
|
||||
(defn update-file-data
|
||||
[file f]
|
||||
(update file :data f))
|
||||
(when file
|
||||
(update file :data f)))
|
||||
|
||||
(defn containers-seq
|
||||
"Generate a sequence of all pages and all components, wrapped as containers"
|
||||
@ -225,6 +226,85 @@
|
||||
(ctpl/update-page file-data (:id container) f)
|
||||
(ctkl/update-component file-data (:id container) f)))
|
||||
|
||||
(defn update-pages
|
||||
"Update all pages inside the file"
|
||||
[file-data f]
|
||||
(update file-data :pages-index d/update-vals
|
||||
(fn [page]
|
||||
(-> page
|
||||
(ctn/make-container :page)
|
||||
(f)
|
||||
(ctn/unmake-container)))))
|
||||
|
||||
(defn update-components
|
||||
"Update all components inside the file"
|
||||
[file-data f]
|
||||
(d/update-when file-data :components d/update-vals
|
||||
(fn [component]
|
||||
(-> component
|
||||
(ctn/make-container :component)
|
||||
(f)
|
||||
(ctn/unmake-container)))))
|
||||
|
||||
(defn update-containers
|
||||
"Update all pages and components inside the file"
|
||||
[file-data f]
|
||||
(-> file-data
|
||||
(update-pages f)
|
||||
(update-components f)))
|
||||
|
||||
(defn update-objects-tree
|
||||
"Do a depth-first traversal of the shapes in a container, doing different kinds of updates.
|
||||
The function f receives a shape with a context metadata with the container.
|
||||
It must return a map with the following keys:
|
||||
- :result -> :keep, :update or :remove
|
||||
- :updated-shape -> the updated shape if result is :update"
|
||||
[container f]
|
||||
(letfn [(update-shape-recursive
|
||||
[container shape-id]
|
||||
(let [shape (ctst/get-shape container shape-id)]
|
||||
(when (not shape)
|
||||
(throw (ex-info "Shape not found" {:shape-id shape-id})))
|
||||
(let [shape (with-meta shape {:container container})
|
||||
|
||||
{:keys [result updated-shape]} (f shape)
|
||||
|
||||
container'
|
||||
(case result
|
||||
:keep
|
||||
container
|
||||
|
||||
:update
|
||||
(ctst/set-shape container updated-shape)
|
||||
|
||||
:remove
|
||||
(ctst/delete-shape container shape-id true)
|
||||
|
||||
(throw (ex-info "Invalid result from update function" {:result result})))]
|
||||
|
||||
(if (= result :remove)
|
||||
container'
|
||||
(reduce update-shape-recursive
|
||||
container'
|
||||
(:shapes shape))))))]
|
||||
|
||||
(let [root-id (if (ctn/page? container)
|
||||
uuid/zero
|
||||
(:main-instance-id container))]
|
||||
|
||||
(if-not (empty? (:objects container))
|
||||
(update-shape-recursive container root-id)
|
||||
container))))
|
||||
|
||||
(defn update-all-shapes
|
||||
"Update all shapes in the file data, using the update-objects-tree function for each container"
|
||||
[file-data f]
|
||||
(when file-data
|
||||
(update-containers
|
||||
file-data
|
||||
(fn [container]
|
||||
(update-objects-tree container f)))))
|
||||
|
||||
;; Asset helpers
|
||||
(defn find-component-file
|
||||
[file libraries component-file]
|
||||
@ -328,6 +408,27 @@
|
||||
(get-ref-shape (:data component-file) component shape :with-context? with-context?))))]
|
||||
(some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape))))
|
||||
|
||||
(defn find-near-match
|
||||
"Locate the shape that occupies the same position in the near main component.
|
||||
This will be the ref-shape except if the shape is a copy subhead that has been
|
||||
swapped. In this case, the near match will be the ref-shape that was before
|
||||
the swap."
|
||||
[file container libraries shape & {:keys [include-deleted? with-context?] :or {include-deleted? false with-context? false}}]
|
||||
(let [parent-shape (ctst/get-shape container (:parent-id shape))
|
||||
parent-ref-shape (when parent-shape
|
||||
(find-ref-shape file container libraries parent-shape :include-deleted? include-deleted? :with-context? true))
|
||||
ref-container (when parent-ref-shape
|
||||
(:container (meta parent-ref-shape)))
|
||||
shape-index (when parent-shape
|
||||
(d/index-of (:shapes parent-shape) (:id shape)))
|
||||
near-match-id (when (and parent-ref-shape shape-index)
|
||||
(get (:shapes parent-ref-shape) shape-index))
|
||||
near-match (when near-match-id
|
||||
(cond-> (ctst/get-shape ref-container near-match-id)
|
||||
with-context?
|
||||
(with-meta (meta parent-ref-shape))))]
|
||||
near-match))
|
||||
|
||||
(defn advance-shape-ref
|
||||
"Get the shape-ref of the near main of the shape, recursively repeated as many times
|
||||
as the given levels."
|
||||
|
||||
@ -16,8 +16,6 @@
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]))
|
||||
|
||||
|
||||
;; FIXME: the order of arguments seems arbitrary, container should be a first artgument
|
||||
(defn add-shape
|
||||
"Insert a shape in the tree, at the given index below the given parent or frame.
|
||||
Update the parent as needed."
|
||||
|
||||
@ -60,8 +60,9 @@
|
||||
:cljs (uuid (impl/v4))))
|
||||
|
||||
(defn custom
|
||||
([a] #?(:clj (UUID. 0 a) :cljs (uuid (impl/custom 0 a))))
|
||||
([b a] #?(:clj (UUID. b a) :cljs (uuid (impl/custom b a)))))
|
||||
"Generate a uuid using directly the given number (specified as one or two long integers)"
|
||||
([low] #?(:clj (UUID. 0 low) :cljs (uuid (impl/custom 0 low))))
|
||||
([high low] #?(:clj (UUID. high low) :cljs (uuid (impl/custom high low)))))
|
||||
|
||||
(def zero (uuid "00000000-0000-0000-0000-000000000000"))
|
||||
|
||||
@ -137,6 +138,22 @@
|
||||
(+ (clojure.lang.Murmur3/hashLong a)
|
||||
(clojure.lang.Murmur3/hashLong b)))))
|
||||
|
||||
;; Fake uuids generator
|
||||
(def ^:private fake-ids (atom 0))
|
||||
|
||||
(defn reset-fake!
|
||||
"Reset the fake uuid counter to 0, for reproducible results across tests."
|
||||
[]
|
||||
(reset! fake-ids 0))
|
||||
|
||||
(defn next-fake
|
||||
"When you need predictable uuids, for example when debugging a failing test, wrap the code with
|
||||
(with-redefs [uuid/next uuid/next-fake]
|
||||
...tested code...)"
|
||||
[]
|
||||
(-> (swap! fake-ids inc)
|
||||
(custom)))
|
||||
|
||||
;; Commented code used for debug
|
||||
;; #?(:cljs
|
||||
;; (defn ^:export test-uuid
|
||||
|
||||
787
common/test/common_tests/files/comp_processors_test.cljc
Normal file
787
common/test/common_tests/files/comp_processors_test.cljc
Normal file
@ -0,0 +1,787 @@
|
||||
;; 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
|
||||
|
||||
(ns common-tests.files.comp-processors-test
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.files.comp-processors :as cfcp]
|
||||
[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.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.file :as ctf]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest test-remove-unneeded-objects-in-components
|
||||
|
||||
(t/testing "nil file should return nil"
|
||||
(let [file nil
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
|
||||
(t/is (nil? file'))))
|
||||
|
||||
(t/testing "empty file should not need any action"
|
||||
(let [file (thf/sample-file :file1)
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file without components should not need any action"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-child :frame1 :shape1))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with non deleted components should not need any action"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with deleted components should not need any action"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1)
|
||||
(tho/delete-shape :frame1))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with non deleted components with :objects nil should remove it"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1)
|
||||
(thc/update-component :component1 {:objects nil}))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:components
|
||||
{(thi/id :component1)
|
||||
{}}}}]
|
||||
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with non deleted components with :objects should remove it"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1)
|
||||
(thc/update-component :component1 {:objects {:sample 777}}))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:components
|
||||
{(thi/id :component1)
|
||||
{:objects
|
||||
[{:sample 777} nil]}}}}]
|
||||
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with deleted components without :objects should add an empty one"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1)
|
||||
(tho/delete-shape :frame1)
|
||||
(ctf/update-file-data
|
||||
(fn [file-data]
|
||||
(ctkl/update-component file-data (thi/id :component1) #(dissoc % :objects)))))
|
||||
|
||||
file' (ctf/update-file-data file cfcp/remove-unneeded-objects-in-components)
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:components
|
||||
{(thi/id :component1)
|
||||
{:objects
|
||||
[nil {}]}}}}]
|
||||
|
||||
(t/is (= expected-diff diff)))))
|
||||
|
||||
(t/deftest test-fix-missing-swap-slots
|
||||
|
||||
(t/testing "nil file should return nil"
|
||||
(let [file nil
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
(t/is (nil? file'))))
|
||||
|
||||
(t/testing "empty file should not need any action"
|
||||
(let [file (thf/sample-file :file1)
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file without components should not need any action"
|
||||
(let [file
|
||||
;; :frame1 [:name Frame1]
|
||||
;; :child1 [:name Rect1]
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-child :frame1 :shape1))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with nested not swapped components should not need any action"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-head
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:copy2-root))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with a normally swapped copy should not need any action"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; {swap-slot :nested-head}
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-first-child :copy2-root :component3))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with a swapped nested copy in a main should not need any action"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; {swap-slot :nested-head}
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :nested-head
|
||||
;; <no-label> [:name Rect3] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-shape :nested-head :component3
|
||||
:propagate-fn #(tho/propagate-component-changes % :component2)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with a swapped copy with broken slot should have it repaired"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; NO SWAP SLOT
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-first-child :copy2-root :component3)
|
||||
(ths/update-shape :copy2-nested-head :touched nil))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:touched
|
||||
[nil
|
||||
#{(ctk/build-swap-slot-group (str (thi/id :nested-head)))}]}}}}}}]
|
||||
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with a swapped copy inside a main with broken slot has no effect since it cannot be distinguished"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; NO SWAP SLOT
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :nested-head
|
||||
;; <no-label> [:name Rect3] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-shape :nested-head :component3
|
||||
:propagate-fn #(tho/propagate-component-changes % :component2))
|
||||
(ths/update-shape :nested-head :touched nil))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with a two levels nested copy in a main swapped with broken slot should have it repaired"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main4-root} [:name Frame4] # [Component :component4]
|
||||
;; :main4-child [:name Rect4]
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
|
||||
;; :nested-subhead2 [:name Frame4] @--> [Component :component4] :main4-root
|
||||
;; NO SWAP SLOT
|
||||
;; <no-label> [:name Rect4] ---> :main4-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
|
||||
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
|
||||
;; <no-label> [:name Frame4] @--> [Component :component4] :nested-subhead2
|
||||
;; <no-label> [:name Rect4] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head1
|
||||
:component3 :main3-root :nested-head2 :nested-subhead2
|
||||
:copy2-root)
|
||||
(tho/add-simple-component :component4 :main4-root :main4-child
|
||||
:root-params {:name "Frame4"}
|
||||
:child-params {:name "Rect4"})
|
||||
(tho/swap-component-in-shape :nested-subhead2 :component4
|
||||
:propagate-fn #(tho/propagate-component-changes % :component3))
|
||||
(ths/update-shape :nested-subhead2 :touched nil))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :nested-subhead2)
|
||||
{:touched
|
||||
[nil
|
||||
#{(ctk/build-swap-slot-group (str (thi/id :nested-head1)))}]}}}}}}]
|
||||
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "when components are in external libraries, the fix still works well"
|
||||
(let [library1
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; :nested2-child [:name Rect1] ---> :main1-child
|
||||
(-> (thf/sample-file :library1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested2-head
|
||||
:nested-head-params {:children-labels [:nested2-child]}))
|
||||
library2
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main4-root} [:name Frame4] # [Component :component4]
|
||||
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
|
||||
;; :nested4-child [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :library2)
|
||||
(tho/add-nested-component :component3 :main3-root :main3-child
|
||||
:component4 :main4-root :nested4-head
|
||||
:root1-params {:name "Frame3"}
|
||||
:main1-child-params {:name "Rect3"}
|
||||
:main2-root-params {:name "Frame4"}
|
||||
:nested-head-params {:children-labels [:nested4-child]}))
|
||||
|
||||
file
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
|
||||
;; NO SWAP SLOT
|
||||
;; <no-label> [:name Frame3] @--> :nested4-head
|
||||
;; <no-label> [:name Rect3] ---> :nested4-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]
|
||||
:library library1)
|
||||
(tho/swap-component-in-first-child :copy2 :component4 :library library2)
|
||||
(ths/update-shape :copy2-nested-head :touched nil))
|
||||
|
||||
libraries {(:id library1) library1
|
||||
(:id library2) library2}
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/fix-missing-swap-slots % libraries))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:touched
|
||||
[nil
|
||||
#{(ctk/build-swap-slot-group (str (thi/id :nested2-head)))}]}}}}}}]
|
||||
|
||||
(t/is (= expected-diff diff)))))
|
||||
|
||||
(t/deftest test-sync-component-id-with-ref-shape
|
||||
|
||||
(t/testing "nil file should return nil"
|
||||
(let [file nil
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
|
||||
(t/is (nil? file'))))
|
||||
|
||||
(t/testing "empty file should not need any action"
|
||||
(let [file (thf/sample-file :file1)
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file without components should not need any action"
|
||||
(let [file
|
||||
;; :frame1 [:name Frame1]
|
||||
;; :child1 [:name Rect1]
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-child :frame1 :shape1))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
|
||||
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with valid normal components should not need any action"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
|
||||
;; :nested-subhead2 [:name Frame1] @--> [Component :component1] :nested-head1
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
;;
|
||||
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
|
||||
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head1
|
||||
:component3 :main3-root :nested-head2 :nested-subhead2
|
||||
:copy2-root))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
|
||||
|
||||
#_(thf/dump-file file') ;; Uncomment to debug
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with valid swapped components should not need any action"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-head
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
;;
|
||||
;; :copy3-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy3-nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; {swap-slot :nested-head}
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:copy2-root)
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(thc/instantiate-component :component2 :copy3-root :children-labels [:copy3-nested-head])
|
||||
(tho/swap-component-in-first-child :copy3-root :component3))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))]
|
||||
|
||||
#_(thf/dump-file file') ;; Uncomment to debug
|
||||
(t/is (empty? (d/map-diff file file')))))
|
||||
|
||||
(t/testing "file with a non swapped copy with broken component id/file should have it repaired"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame1] @--> [Component <bad>] :nested-head ## <- BAD component-id
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
;;
|
||||
;; :copy3-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy3-nested-head [:name Frame1] @--> [Component <bad>] :nested-head ## <- BAD component-file
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(thc/instantiate-component :component2 :copy3-root :children-labels [:copy3-nested-head])
|
||||
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
|
||||
(ths/update-shape :copy3-nested-head :component-file (thi/new-id! :some-other-file)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:component-id
|
||||
[(thi/id :some-other-id) (thi/id :component1)]}
|
||||
(thi/id :copy3-nested-head)
|
||||
{:component-file
|
||||
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
|
||||
|
||||
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with a copy of a swapped main with broken component id/file should have it repaired"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; {swap-slot :nested-head}
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component: <bad>] :nested-head ## <- BAD component-id/file
|
||||
;; <no-label> [:name Rect3] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-shape :nested-head :component3
|
||||
:propagate-fn #(tho/propagate-component-changes % :component2))
|
||||
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
|
||||
(ths/update-shape :copy2-nested-head :component-file (thi/new-id! :some-other-file)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:component-id
|
||||
[(thi/id :some-other-id) (thi/id :component3)]
|
||||
:component-file
|
||||
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
|
||||
|
||||
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with multiple copies of same component should sync all"
|
||||
(let [file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component :component1 :frame1 :shape1)
|
||||
(thc/instantiate-component :component1 :copy1-root :children-labels [:copy1-child])
|
||||
(thc/instantiate-component :component1 :copy2-root :children-labels [:copy2-child])
|
||||
(ths/update-shape :copy1-child :component-id (thi/new-id! :wrong-id1))
|
||||
(ths/update-shape :copy2-child :component-id (thi/new-id! :wrong-id2)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
|
||||
diff (d/map-diff file file')]
|
||||
|
||||
;; Both copies should be corrected
|
||||
(t/is (contains? diff :data))
|
||||
(t/is (contains? (get-in diff [:data :pages-index]) (thf/current-page-id file)))))
|
||||
|
||||
(t/testing "file with a copy root with broken component id/file cannot be repaired. But it's propagated to copies."
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component <bad>] :main1-root ## <- BAD component-id/file
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; :copy2-root [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame1] @--> [Component :component1] :nested-head
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head)
|
||||
(thc/instantiate-component :component2 :copy2-root :children-labels [:copy2-nested-head])
|
||||
(ths/update-shape :nested-head :component-id (thi/new-id! :some-other-id))
|
||||
(ths/update-shape :nested-head :component-file (thi/new-id! :some-other-file)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:component-id
|
||||
[(thi/id :component1) (thi/id :some-other-id)]
|
||||
:component-file
|
||||
[(thi/id :file1) (thi/id :some-other-file)]}}}}}}]
|
||||
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with a 2nd nested copy inside a main with broken component/id should have it repaired, and propagated to copies"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :nested-head2 [:name Frame2] @--> [Component :component2] :main2-root
|
||||
;; :nested-subhead2 [:name Frame1] @--> [Component <bad>] :nested-head1 ## <- BAD component-id/file
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
;;
|
||||
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
|
||||
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head1
|
||||
:component3 :main3-root :nested-head2 :nested-subhead2
|
||||
:copy2-root)
|
||||
(ths/update-shape :nested-subhead2 :component-id (thi/new-id! :some-other-id))
|
||||
(ths/update-shape :nested-subhead2 :component-file (thi/new-id! :some-other-file)))
|
||||
|
||||
copy2-root (ths/get-shape file :copy2-root)
|
||||
copy2-root-child1 (ths/get-shape-by-id file (first (:shapes copy2-root)))
|
||||
copy2-root-child2 (ths/get-shape-by-id file (first (:shapes copy2-root-child1)))
|
||||
file (-> file
|
||||
(ths/update-shape-by-id (:id copy2-root-child2) :component-id (thi/id :some-other-id))
|
||||
(ths/update-shape-by-id (:id copy2-root-child2) :component-file (thi/id :some-other-file)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :nested-subhead2)
|
||||
{:component-id
|
||||
[(thi/id :some-other-id) (thi/id :component1)]
|
||||
:component-file
|
||||
[(thi/id :some-other-file) (thi/id :file1)]}
|
||||
(:id copy2-root-child2)
|
||||
{:component-id
|
||||
[(thi/id :some-other-id) (thi/id :component1)]
|
||||
:component-file
|
||||
[(thi/id :some-other-file) (thi/id :file1)]}}}}}}]
|
||||
|
||||
#_(ctf/dump-tree file' (thf/current-page-id file') {(:id file') file'} {:show-ids true}) ;; Uncomment to debug
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "when components are in external libraries, the fix still works well"
|
||||
(let [library1
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested2-head [:name Frame4] @--> [Component :component4] :main4-root
|
||||
;; {swap-slot :nested2-head}
|
||||
;; :nested4-head [:name Frame3] @--> [Component: component3] :main3-root
|
||||
;; :nested4-child [:name Rect3] ---> :nested4-child
|
||||
(-> (thf/sample-file :library1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested2-head
|
||||
:nested-head-params {:children-labels [:nested2-child]}))
|
||||
library2
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main4-root} [:name Frame4] # [Component :component4]
|
||||
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
|
||||
;; :nested4-child [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :library2)
|
||||
(tho/add-nested-component :component3 :main3-root :main3-child
|
||||
:component4 :main4-root :nested4-head
|
||||
:root1-params {:name "Frame3"}
|
||||
:main1-child-params {:name "Rect3"}
|
||||
:main2-root-params {:name "Frame4"}
|
||||
:nested-head-params {:children-labels [:nested4-child]}))
|
||||
|
||||
library1
|
||||
(tho/swap-component-in-shape library1 :nested2-head :component4 :library library2)
|
||||
|
||||
file
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame4] @--> [Component <bad>] :main4-root ## <- BAD component-id/file
|
||||
;; <no-label> [:name Frame3] @--> :nested4-head
|
||||
;; <no-label> [:name Rect3] ---> :nested4-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]
|
||||
:library library1)
|
||||
(ths/update-shape :copy2-nested-head :component-id (thi/new-id! :some-other-id))
|
||||
(ths/update-shape :copy2-nested-head :component-file (thi/new-id! :some-other-file)))
|
||||
|
||||
libraries {(:id library1) library1
|
||||
(:id library2) library2}
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % libraries))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(thi/id :copy2-nested-head)
|
||||
{:component-id
|
||||
[(thi/id :some-other-id) (thi/id :component4)]
|
||||
:component-file
|
||||
[(thi/id :some-other-file) (thi/id :library2)]}}}}}}]
|
||||
|
||||
#_(thf/dump-file library2) ;; Uncomment to debug
|
||||
(t/is (= expected-diff diff))))
|
||||
|
||||
(t/testing "file with several broken ids should propagate to all copies"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head1 [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; <no-label> [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :nested-head2 [:name Frame2] @--> [Component <bad>] :main2-root ## <- BAD component-id
|
||||
;; :nested-subhead2 [:name Frame1] @--> [Component <bad>] :nested-head1 ## <- BAD component-id
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
;;
|
||||
;; :copy2-root [:name Frame3] #--> [Component :component3] :main3-root
|
||||
;; <no-label> [:name Frame2] @--> [Component :component2] :nested-head2
|
||||
;; <no-label> [:name Frame1] @--> [Component :component1] :nested-subhead2
|
||||
;; <no-label> [:name Rect1] ---> <no-label>
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-two-levels-nested-component-with-copy :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head1
|
||||
:component3 :main3-root :nested-head2 :nested-subhead2
|
||||
:copy2-root)
|
||||
;; Corrupt both levels
|
||||
(ths/update-shape :nested-head2 :component-id (thi/new-id! :wrong-comp2))
|
||||
(ths/update-shape :nested-subhead2 :component-id (thi/new-id! :wrong-comp3)))
|
||||
|
||||
file' (ctf/update-file-data file #(cfcp/sync-component-id-with-ref-shape % {}))
|
||||
copy2-root (ths/get-shape file' :copy2-root)
|
||||
copy2-root-child1 (ths/get-shape-by-id file' (first (:shapes copy2-root)))
|
||||
copy2-root-child2 (ths/get-shape-by-id file' (first (:shapes copy2-root-child1)))
|
||||
|
||||
diff (d/map-diff file file')
|
||||
|
||||
expected-diff {:data
|
||||
{:pages-index
|
||||
{(thf/current-page-id file)
|
||||
{:objects
|
||||
{(:id copy2-root-child1)
|
||||
{:component-id [(thi/id :component2) (thi/id :wrong-comp2)]}
|
||||
(:id copy2-root-child2)
|
||||
{:component-id [(thi/id :component1) (thi/id :wrong-comp3)]}}}}}}]
|
||||
|
||||
(thf/dump-file file') ;; Uncomment to debug
|
||||
(t/is (= expected-diff diff)))))
|
||||
|
||||
@ -465,9 +465,10 @@
|
||||
page
|
||||
{(:id file) file}
|
||||
(thi/id :nested-h-ellipse))
|
||||
file' (-> (thf/apply-changes file changes)
|
||||
file' (-> (thf/apply-changes file changes :validate? false)
|
||||
(tho/propagate-component-changes :c-board-with-ellipse)
|
||||
(tho/propagate-component-changes :c-big-board))
|
||||
(tho/propagate-component-changes :c-big-board)
|
||||
(thf/validate-file!))
|
||||
|
||||
;; ==== Get
|
||||
nested2-h-ellipse (ths/get-shape file' :nested-h-ellipse)
|
||||
|
||||
@ -349,4 +349,73 @@
|
||||
(t/is (= (:fill-color fill') "#FFFFFF"))
|
||||
(t/is (= (:fill-opacity fill') 1))
|
||||
(t/is (= (:touched copy2-root') nil))
|
||||
(t/is (= (:touched copy2-child') nil))))
|
||||
(t/is (= (:touched copy2-child') nil))))
|
||||
|
||||
(t/deftest test-reset-with-propagation-updates-copies
|
||||
;; When a nested copy inside a main component has an override and we
|
||||
;; reset it passing a propagate-fn, the reset must be propagated to
|
||||
;; all copies of that component so they reflect the canonical color.
|
||||
(let [;; ==== Setup
|
||||
file
|
||||
(-> (thf/sample-file :file1)
|
||||
;; component1: main1-root / main1-child (fill "#aabbcc")
|
||||
;; component2: main2-root contains nested-head (instance of component1)
|
||||
;; copy2-root: copy of component2
|
||||
(tho/add-nested-component-with-copy
|
||||
:component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:copy2-root
|
||||
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
|
||||
:copy2-root-params {:children-labels [:copy2-nested-head]}))
|
||||
|
||||
propagate-fn (fn [f]
|
||||
(-> f
|
||||
(tho/propagate-component-changes :component1)
|
||||
(tho/propagate-component-changes :component2)))
|
||||
|
||||
;; ==== Action – override the nested-head color, then reset it with propagation
|
||||
file'
|
||||
(-> file
|
||||
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
|
||||
(tho/reset-overrides (ths/get-shape file :nested-head) :propagate-fn propagate-fn))
|
||||
|
||||
;; ==== Get
|
||||
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
|
||||
|
||||
;; ==== Check
|
||||
;; After reset + propagation the copy should mirror the canonical color
|
||||
(t/is (= copy2-bottom-color "#aabbcc"))))
|
||||
|
||||
(t/deftest test-reset-without-propagation-does-not-update-copies
|
||||
;; This is the regression test for the misplaced-parenthesis bug: when
|
||||
;; propagate-fn is NOT passed to reset-overrides the copies of the component
|
||||
;; must still hold the overridden value because the component sync never ran.
|
||||
(let [;; ==== Setup
|
||||
file
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component-with-copy
|
||||
:component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:copy2-root
|
||||
:main1-child-params {:fills (ths/sample-fills-color :fill-color "#aabbcc")}
|
||||
:copy2-root-params {:children-labels [:copy2-nested-head]}))
|
||||
|
||||
propagate-fn (fn [f]
|
||||
(-> f
|
||||
(tho/propagate-component-changes :component1)
|
||||
(tho/propagate-component-changes :component2)))
|
||||
|
||||
;; ==== Action – override the nested-head color, then reset WITHOUT propagation
|
||||
file'
|
||||
(-> file
|
||||
(tho/update-bottom-color :nested-head "#fabada" :propagate-fn propagate-fn)
|
||||
;; Reset without propagate-fn: the component definition is updated but
|
||||
;; the change is never pushed to the copy.
|
||||
(tho/reset-overrides (ths/get-shape file :nested-head)))
|
||||
|
||||
;; ==== Get
|
||||
copy2-bottom-color (tho/bottom-fill-color file' :copy2-root)]
|
||||
|
||||
;; ==== Check
|
||||
;; Without propagation the copy still reflects the overridden color
|
||||
(t/is (= copy2-bottom-color "#fabada"))))
|
||||
@ -64,9 +64,8 @@
|
||||
|
||||
(reset-all-overrides [file]
|
||||
(-> file
|
||||
(tho/reset-overrides-in-first-child :frame-board-1 :page-label :page-1)
|
||||
(tho/reset-overrides-in-first-child :copy-board-1 :page-label :page-2)
|
||||
(propagate-all-component-changes)))
|
||||
(tho/reset-overrides-in-first-child :frame-board-1 :page-label :page-1 :propagate-fn propagate-all-component-changes)
|
||||
(tho/reset-overrides-in-first-child :copy-board-1 :page-label :page-2 :propagate-fn propagate-all-component-changes)))
|
||||
|
||||
(fill-colors [file]
|
||||
[(tho/bottom-fill-color file :frame-ellipse-1 :page-label :page-1)
|
||||
|
||||
@ -6,20 +6,11 @@
|
||||
|
||||
(ns common-tests.logic.multiple-nesting-levels-test
|
||||
(:require
|
||||
[app.common.files.changes :as ch]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.pprint :as pp]
|
||||
[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.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
@ -56,10 +47,9 @@
|
||||
|
||||
(reset-all-overrides [file]
|
||||
(-> file
|
||||
(tho/reset-overrides (ths/get-shape file :copy-simple-1))
|
||||
(tho/reset-overrides (ths/get-shape file :copy-frame-composed-1))
|
||||
(tho/reset-overrides (ths/get-shape file :composed-1-composed-2-copy))
|
||||
(propagate-all-component-changes)))
|
||||
(tho/reset-overrides (ths/get-shape file :copy-simple-1) :propagate-fn propagate-all-component-changes)
|
||||
(tho/reset-overrides (ths/get-shape file :copy-frame-composed-1) :propagate-fn propagate-all-component-changes)
|
||||
(tho/reset-overrides (ths/get-shape file :composed-1-composed-2-copy) :propagate-fn propagate-all-component-changes)))
|
||||
|
||||
(fill-colors [file]
|
||||
[(tho/bottom-fill-color file :frame-simple-1)
|
||||
|
||||
@ -6,20 +6,12 @@
|
||||
|
||||
(ns common-tests.logic.swap-as-override-test
|
||||
(:require
|
||||
[app.common.files.changes :as ch]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.logic.libraries :as cll]
|
||||
[app.common.logic.shapes :as cls]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.data :as d]
|
||||
[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.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.file :as ctf]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
@ -27,23 +19,40 @@
|
||||
(defn- setup []
|
||||
(-> (thf/sample-file :file1)
|
||||
|
||||
(tho/add-simple-component :component-1 :frame-component-1 :child-component-1 :child-params {:name "child-component-1" :type :rect :fills (ths/sample-fills-color :fill-color "#111111")})
|
||||
(tho/add-simple-component :component-2 :frame-component-2 :child-component-2 :child-params {:name "child-component-2" :type :rect :fills (ths/sample-fills-color :fill-color "#222222")})
|
||||
(tho/add-simple-component :component-3 :frame-component-3 :child-component-3 :child-params {:name "child-component-3" :type :rect :fills (ths/sample-fills-color :fill-color "#333333")})
|
||||
(tho/add-simple-component :component-1 :frame-component-1 :child-component-1
|
||||
:root-params {:name "component-1"}
|
||||
:child-params {:name "child-component-1"
|
||||
:type :rect
|
||||
:fills (ths/sample-fills-color :fill-color "#111111")})
|
||||
(tho/add-simple-component :component-2 :frame-component-2 :child-component-2
|
||||
:root-params {:name "component-2"}
|
||||
:child-params {:name "child-component-2"
|
||||
:type :rect
|
||||
:fills (ths/sample-fills-color :fill-color "#222222")})
|
||||
(tho/add-simple-component :component-3 :frame-component-3 :child-component-3
|
||||
:root-params {:name "component-3"}
|
||||
:child-params {:name "child-component-3"
|
||||
:type :rect
|
||||
:fills (ths/sample-fills-color :fill-color "#333333")})
|
||||
|
||||
(tho/add-frame :frame-icon-and-text)
|
||||
(thc/instantiate-component :component-1 :copy-component-1 :parent-label :frame-icon-and-text :children-labels [:component-1-icon-and-text])
|
||||
(tho/add-frame :frame-icon-and-text :name "copy-component-1")
|
||||
(thc/instantiate-component :component-1 :copy-component-1
|
||||
:parent-label :frame-icon-and-text
|
||||
:children-labels [:component-1-icon-and-text])
|
||||
(ths/add-sample-shape :text
|
||||
{:type :text
|
||||
:name "icon+text"
|
||||
:parent-label :frame-icon-and-text})
|
||||
(thc/make-component :icon-and-text :frame-icon-and-text)
|
||||
|
||||
(tho/add-frame :frame-panel)
|
||||
(thc/instantiate-component :icon-and-text :copy-icon-and-text :parent-label :frame-panel :children-labels [:icon-and-text-panel])
|
||||
(tho/add-frame :frame-panel :name "icon-and-text")
|
||||
(thc/instantiate-component :icon-and-text :copy-icon-and-text
|
||||
:parent-label :frame-panel
|
||||
:children-labels [:icon-and-text-panel])
|
||||
(thc/make-component :panel :frame-panel)
|
||||
|
||||
(thc/instantiate-component :panel :copy-panel :children-labels [:copy-icon-and-text-panel])))
|
||||
(thc/instantiate-component :panel :copy-panel
|
||||
:children-labels [:copy-icon-and-text-panel])))
|
||||
|
||||
(defn- propagate-all-component-changes [file]
|
||||
(-> file
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
copy (ths/get-shape file :copy01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy :circle {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :circle {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy' (ths/get-shape file' :copy02)]
|
||||
;; Both copies have the same id
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy01' (ths/get-shape file' :copy02)]
|
||||
(thf/dump-file file :keys [:width])
|
||||
@ -61,7 +61,7 @@
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -100,7 +100,7 @@
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy01' (ths/get-shape file' :copy02)]
|
||||
(thf/dump-file file :keys [:width])
|
||||
@ -137,7 +137,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -180,7 +180,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -257,25 +257,19 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
;; Override font size on copy-font-size
|
||||
file (update-attr file :copy-font-size-t font-size-path-0 "25")
|
||||
copy-font-size (ths/get-shape file :copy-font-size)
|
||||
copy-font-size-t (ths/get-shape file :copy-font-size-t)
|
||||
|
||||
;; Override text on copy-text
|
||||
file (update-attr file :copy-text-t text-path-0 "text overriden")
|
||||
copy-text (ths/get-shape file :copy-text)
|
||||
copy-text-t (ths/get-shape file :copy-text-t)
|
||||
|
||||
;; Override both on copy-both
|
||||
file (update-attr file :copy-both-t font-size-path-0 "25")
|
||||
file (update-attr file :copy-both-t text-path-0 "text overriden")
|
||||
copy-both (ths/get-shape file :copy-both)
|
||||
copy-both-t (ths/get-shape file :copy-both-t)
|
||||
|
||||
|
||||
@ -283,10 +277,10 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-clean' (ths/get-shape file' :copy-clean-2)
|
||||
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
|
||||
@ -387,25 +381,19 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
;; Override font size on copy-font-size
|
||||
file (update-attr file :copy-font-size-t font-size-path-0 "25")
|
||||
copy-font-size (ths/get-shape file :copy-font-size)
|
||||
copy-font-size-t (ths/get-shape file :copy-font-size-t)
|
||||
|
||||
;; Override text on copy-text
|
||||
file (update-attr file :copy-text-t text-path-0 "text overriden")
|
||||
copy-text (ths/get-shape file :copy-text)
|
||||
copy-text-t (ths/get-shape file :copy-text-t)
|
||||
|
||||
;; Override both on copy-both
|
||||
file (update-attr file :copy-both-t font-size-path-0 "25")
|
||||
file (update-attr file :copy-both-t text-path-0 "text overriden")
|
||||
copy-both (ths/get-shape file :copy-both)
|
||||
copy-both-t (ths/get-shape file :copy-both-t)
|
||||
|
||||
|
||||
@ -413,10 +401,10 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-clean' (ths/get-shape file' :copy-clean-2)
|
||||
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
|
||||
@ -515,25 +503,19 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
;; Override font size on copy-font-size
|
||||
file (update-attr file :copy-font-size-t font-size-path-0 "25")
|
||||
copy-font-size (ths/get-shape file :copy-font-size)
|
||||
copy-font-size-t (ths/get-shape file :copy-font-size-t)
|
||||
|
||||
;; Override text on copy-text
|
||||
file (update-attr file :copy-text-t text-path-0 "text overriden")
|
||||
copy-text (ths/get-shape file :copy-text)
|
||||
copy-text-t (ths/get-shape file :copy-text-t)
|
||||
|
||||
;; Override both on copy-both
|
||||
file (update-attr file :copy-both-t font-size-path-0 "25")
|
||||
file (update-attr file :copy-both-t text-path-0 "text overriden")
|
||||
copy-both (ths/get-shape file :copy-both)
|
||||
copy-both-t (ths/get-shape file :copy-both-t)
|
||||
|
||||
|
||||
@ -541,10 +523,10 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-clean' (ths/get-shape file' :copy-clean-2)
|
||||
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
|
||||
@ -645,25 +627,19 @@
|
||||
|
||||
|
||||
;; The copy clean has no overrides
|
||||
|
||||
|
||||
copy-clean (ths/get-shape file :copy-clean)
|
||||
copy-clean-t (ths/get-shape file :copy-clean-t)
|
||||
|
||||
;; Override font size on copy-font-size
|
||||
file (update-attr file :copy-font-size-t font-size-path-0 "25")
|
||||
copy-font-size (ths/get-shape file :copy-font-size)
|
||||
copy-font-size-t (ths/get-shape file :copy-font-size-t)
|
||||
|
||||
;; Override text on copy-text
|
||||
file (update-attr file :copy-text-t text-path-0 "text overriden")
|
||||
copy-text (ths/get-shape file :copy-text)
|
||||
copy-text-t (ths/get-shape file :copy-text-t)
|
||||
|
||||
;; Override both on copy-both
|
||||
file (update-attr file :copy-both-t font-size-path-0 "25")
|
||||
file (update-attr file :copy-both-t text-path-0 "text overriden")
|
||||
copy-both (ths/get-shape file :copy-both)
|
||||
copy-both-t (ths/get-shape file :copy-both-t)
|
||||
|
||||
|
||||
@ -671,10 +647,10 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-clean :c02 {:new-shape-label :copy-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-font-size :c02 {:new-shape-label :copy-font-size-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-text :c02 {:new-shape-label :copy-text-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-both :c02 {:new-shape-label :copy-both-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-clean' (ths/get-shape file' :copy-clean-2)
|
||||
copy-clean-t' (get-in page' [:objects (-> copy-clean' :shapes first)])
|
||||
@ -774,14 +750,12 @@
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
;; both lines with the same attrs
|
||||
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
|
||||
(change-structure :copy-structure-unif-t))
|
||||
copy-structure-unif (ths/get-shape file :copy-structure-unif)
|
||||
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
@ -789,7 +763,6 @@
|
||||
file (-> (change-structure file :copy-structure-mixed-t)
|
||||
(update-attr :copy-structure-mixed-t font-size-path-0 "35")
|
||||
(update-attr :copy-structure-mixed-t font-size-path-1 "40"))
|
||||
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
|
||||
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
|
||||
|
||||
|
||||
@ -797,9 +770,9 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
|
||||
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
|
||||
@ -908,14 +881,12 @@
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
;; both lines with the same attrs
|
||||
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
|
||||
(change-structure :copy-structure-unif-t))
|
||||
copy-structure-unif (ths/get-shape file :copy-structure-unif)
|
||||
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
@ -923,7 +894,6 @@
|
||||
file (-> (change-structure file :copy-structure-mixed-t)
|
||||
(update-attr :copy-structure-mixed-t font-size-path-0 "35")
|
||||
(update-attr :copy-structure-mixed-t font-size-path-1 "40"))
|
||||
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
|
||||
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
|
||||
|
||||
|
||||
@ -931,9 +901,9 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
|
||||
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
|
||||
@ -1038,14 +1008,12 @@
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
;; both lines with the same attrs
|
||||
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
|
||||
(change-structure :copy-structure-unif-t))
|
||||
copy-structure-unif (ths/get-shape file :copy-structure-unif)
|
||||
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
@ -1053,7 +1021,6 @@
|
||||
file (-> (change-structure file :copy-structure-mixed-t)
|
||||
(update-attr :copy-structure-mixed-t font-size-path-0 "35")
|
||||
(update-attr :copy-structure-mixed-t font-size-path-1 "40"))
|
||||
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
|
||||
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
|
||||
|
||||
|
||||
@ -1061,9 +1028,9 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
|
||||
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
|
||||
@ -1169,14 +1136,12 @@
|
||||
|
||||
|
||||
file (change-structure file :copy-structure-clean-t)
|
||||
copy-structure-clean (ths/get-shape file :copy-structure-clean)
|
||||
copy-structure-clean-t (ths/get-shape file :copy-structure-clean-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
;; both lines with the same attrs
|
||||
file (-> (update-attr file :copy-structure-unif-t font-size-path-0 "25")
|
||||
(change-structure :copy-structure-unif-t))
|
||||
copy-structure-unif (ths/get-shape file :copy-structure-unif)
|
||||
copy-structure-unif-t (ths/get-shape file :copy-structure-unif-t)
|
||||
|
||||
;; Duplicate a text line in copy-structure-clean, updating
|
||||
@ -1184,7 +1149,6 @@
|
||||
file (-> (change-structure file :copy-structure-mixed-t)
|
||||
(update-attr :copy-structure-mixed-t font-size-path-0 "35")
|
||||
(update-attr :copy-structure-mixed-t font-size-path-1 "40"))
|
||||
copy-structure-mixed (ths/get-shape file :copy-structure-mixed)
|
||||
copy-structure-mixed-t (ths/get-shape file :copy-structure-mixed-t)
|
||||
|
||||
|
||||
@ -1192,9 +1156,9 @@
|
||||
|
||||
|
||||
file' (-> file
|
||||
(tho/swap-component copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
(tho/swap-component-in-shape :copy-structure-clean :c02 {:new-shape-label :copy-structure-clean-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-unif :c02 {:new-shape-label :copy-structure-unif-2 :keep-touched? true})
|
||||
(tho/swap-component-in-shape :copy-structure-mixed :c02 {:new-shape-label :copy-structure-mixed-2 :keep-touched? true}))
|
||||
page' (thf/current-page file')
|
||||
copy-structure-clean' (ths/get-shape file' :copy-structure-clean-2)
|
||||
copy-structure-clean-t' (get-in page' [:objects (-> copy-structure-clean' :shapes first)])
|
||||
@ -1290,7 +1254,6 @@
|
||||
:children-labels [:copy-cp01]))
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
copy-cp01 (ths/get-shape file :copy-cp01)
|
||||
copy-cp01-rect-id (-> copy-cp01 :shapes first)
|
||||
|
||||
@ -1309,7 +1272,7 @@
|
||||
|
||||
;; ==== Action
|
||||
;; Switch :c01 for :c02
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
copy02 (ths/get-shape file' :copy02)
|
||||
copy-cp02' (ths/get-shape-by-id file' (-> copy02 :shapes first))
|
||||
copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]
|
||||
@ -1337,17 +1300,16 @@
|
||||
:children-labels [:copy-cp01]))
|
||||
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
copy-cp01 (ths/get-shape file :copy-cp01)
|
||||
external02 (thc/get-component file :external02)
|
||||
|
||||
;; On :c01, swap the copy of :external01 for a copy of :external02
|
||||
file (-> file
|
||||
(tho/swap-component copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
|
||||
(tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
|
||||
copy-cp02 (ths/get-shape file :copy-cp02)
|
||||
|
||||
;; ==== Action
|
||||
;; Switch :c01 for :c02
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
copy-cp02' (ths/get-shape file' :copy-cp02)]
|
||||
@ -1376,12 +1338,11 @@
|
||||
|
||||
page (thf/current-page file)
|
||||
copy01 (ths/get-shape file :copy01)
|
||||
copy-cp01 (ths/get-shape file :copy-cp01)
|
||||
external02 (thc/get-component file :external02)
|
||||
|
||||
;; On :c01, swap the copy of :external01 for a copy of :external02
|
||||
file (-> file
|
||||
(tho/swap-component copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
|
||||
(tho/swap-component-in-shape :copy-cp01 :external02 {:new-shape-label :copy-cp02 :keep-touched? false}))
|
||||
copy-cp02 (ths/get-shape file :copy-cp02)
|
||||
copy-cp02-rect-id (-> copy-cp02 :shapes first)
|
||||
|
||||
@ -1396,7 +1357,7 @@
|
||||
|
||||
;; ==== Action
|
||||
;; Switch :c01 for :c02
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
copy-cp02' (ths/get-shape file' :copy-cp02)
|
||||
@ -1463,7 +1424,7 @@
|
||||
;; ==== Action
|
||||
|
||||
|
||||
file' (tho/swap-component file c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :c01-in-copy :c02 {:new-shape-label :c02-in-copy :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
c02-in-copy' (ths/get-shape file' :c02-in-copy)
|
||||
@ -1515,7 +1476,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1564,7 +1525,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1613,7 +1574,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1660,7 +1621,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1714,7 +1675,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1763,7 +1724,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1812,7 +1773,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1859,7 +1820,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1910,7 +1871,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -1956,7 +1917,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2023,7 +1984,7 @@
|
||||
text01 (get-in page [:objects (:id text01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2055,7 +2016,7 @@
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; ==== Action - Try to switch to a component with different shape type
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2098,7 +2059,7 @@
|
||||
path01 (get-in page [:objects (:id path01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2146,7 +2107,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2190,7 +2151,7 @@
|
||||
rect01 (get-in page [:objects (-> copy01 :shapes first)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2243,7 +2204,7 @@
|
||||
old-position-data (:position-data text01)
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2306,7 +2267,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2357,7 +2318,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2411,7 +2372,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2468,7 +2429,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2532,7 +2493,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2588,7 +2549,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2653,7 +2614,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
@ -2710,7 +2671,7 @@
|
||||
rect01 (get-in page [:objects (:id rect01)])
|
||||
|
||||
;; ==== Action
|
||||
file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
file' (tho/swap-component-in-shape file :copy01 :c02 {:new-shape-label :copy02 :keep-touched? true})
|
||||
|
||||
page' (thf/current-page file')
|
||||
copy02' (ths/get-shape file' :copy02)
|
||||
|
||||
@ -6,9 +6,13 @@
|
||||
|
||||
(ns common-tests.types.components-test
|
||||
(:require
|
||||
[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.types.component :as ctk]
|
||||
[app.common.types.file :as ctf]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/use-fixtures :each thi/test-fixture)
|
||||
@ -39,3 +43,357 @@
|
||||
(t/is (= (ctk/get-swap-slot s4) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
|
||||
(t/is (= (ctk/get-swap-slot s5) #uuid "9cc181fa-5eef-8084-8004-7bb2ab45fd1f"))
|
||||
(t/is (nil? (ctk/get-swap-slot s6)))))
|
||||
|
||||
(t/deftest test-find-near-match
|
||||
|
||||
(t/testing "shapes not in a component have no near match"
|
||||
(let [file
|
||||
;; :frame1 [:name Frame1]
|
||||
;; :child1 [:name Rect1]
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-frame-with-child :frame1 :shape1))
|
||||
|
||||
page (thf/current-page file)
|
||||
|
||||
frame1 (ths/get-shape file :frame1)
|
||||
shape1 (ths/get-shape file :shape1)
|
||||
|
||||
near-match1 (ctf/find-near-match file page {} frame1)
|
||||
near-match2 (ctf/find-near-match file page {} shape1)]
|
||||
|
||||
(t/is (nil? near-match1))
|
||||
(t/is (nil? near-match2))))
|
||||
|
||||
(t/testing "shapes in a copy get the ref-shape"
|
||||
(let [file
|
||||
;; {:main-root} [:name Frame1] # [Component :component1]
|
||||
;; :main-child1 [:name Rect1]
|
||||
;; :main-child2 [:name Rect2]
|
||||
;; :main-child3 [:name Rect3]
|
||||
;;
|
||||
;; :copy-root [:name Frame1] #--> [Component :component1] :main-root
|
||||
;; <no-label> [:name Rect1] ---> :main-child1
|
||||
;; <no-label> [:name Rect2] ---> :main-child2
|
||||
;; <no-label> [:name Rect3] ---> :main-child3
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-component-with-many-children-and-copy :component1
|
||||
:main-root [:main-child1 :main-child2 :main-child3]
|
||||
:copy-root))
|
||||
|
||||
page (thf/current-page file)
|
||||
|
||||
main-root (ths/get-shape file :main-root)
|
||||
main-child1 (ths/get-shape file :main-child1)
|
||||
main-child2 (ths/get-shape file :main-child2)
|
||||
main-child3 (ths/get-shape file :main-child3)
|
||||
copy-root (ths/get-shape file :copy-root)
|
||||
copy-child1 (ths/get-shape-by-id file (nth (:shapes copy-root) 0))
|
||||
copy-child2 (ths/get-shape-by-id file (nth (:shapes copy-root) 1))
|
||||
copy-child3 (ths/get-shape-by-id file (nth (:shapes copy-root) 2))
|
||||
|
||||
near-main-root (ctf/find-near-match file page {} main-root)
|
||||
near-main-child1 (ctf/find-near-match file page {} main-child1)
|
||||
near-main-child2 (ctf/find-near-match file page {} main-child2)
|
||||
near-main-child3 (ctf/find-near-match file page {} main-child3)
|
||||
near-copy-root (ctf/find-near-match file page {} copy-root)
|
||||
near-copy-child1 (ctf/find-near-match file page {} copy-child1)
|
||||
near-copy-child2 (ctf/find-near-match file page {} copy-child2)
|
||||
near-copy-child3 (ctf/find-near-match file page {} copy-child3)]
|
||||
|
||||
(t/is (nil? near-main-root))
|
||||
(t/is (nil? near-main-child1))
|
||||
(t/is (nil? near-main-child2))
|
||||
(t/is (nil? near-main-child3))
|
||||
(t/is (nil? near-copy-root))
|
||||
(t/is (= (:id near-copy-child1) (thi/id :main-child1)))
|
||||
(t/is (= (:id near-copy-child2) (thi/id :main-child2)))
|
||||
(t/is (= (:id near-copy-child3) (thi/id :main-child3)))))
|
||||
|
||||
(t/testing "shapes in nested not swapped copies get the ref-shape"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; :nested-child [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame1] @--> [Component :component1] :nested-head
|
||||
;; :copy2-nested-child [:name Rect1] ---> :nested-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:nested-head-params {:children-labels [:nested-child]})
|
||||
(thc/instantiate-component :component2 :copy2
|
||||
:children-labels [:copy2-nested-head :copy2-nested-child]))
|
||||
|
||||
page (thf/current-page file)
|
||||
|
||||
main1-root (ths/get-shape file :main1-root)
|
||||
main1-child (ths/get-shape file :main1-child)
|
||||
main2-root (ths/get-shape file :main2-root)
|
||||
nested-head (ths/get-shape file :nested-head)
|
||||
nested-child (ths/get-shape file :nested-child)
|
||||
copy2 (ths/get-shape file :copy2)
|
||||
copy2-nested-head (ths/get-shape file :copy2-nested-head)
|
||||
copy2-nested-child (ths/get-shape file :copy2-nested-child)
|
||||
|
||||
near-main1-root (ctf/find-near-match file page {} main1-root)
|
||||
near-main1-child (ctf/find-near-match file page {} main1-child)
|
||||
near-main2-root (ctf/find-near-match file page {} main2-root)
|
||||
near-nested-head (ctf/find-near-match file page {} nested-head)
|
||||
near-nested-child (ctf/find-near-match file page {} nested-child)
|
||||
near-copy2 (ctf/find-near-match file page {} copy2)
|
||||
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
|
||||
near-copy2-nested-child (ctf/find-near-match file page {} copy2-nested-child)]
|
||||
|
||||
(t/is (nil? near-main1-root))
|
||||
(t/is (nil? near-main1-child))
|
||||
(t/is (nil? near-main2-root))
|
||||
(t/is (nil? near-nested-head))
|
||||
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
|
||||
(t/is (nil? near-copy2))
|
||||
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
|
||||
(t/is (= (:id near-copy2-nested-child) (thi/id :nested-child)))))
|
||||
|
||||
(t/testing "shapes in swapped copies get the swap slot"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; :nested-child [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame3] @--> [Component :component3] :main3-root
|
||||
;; {swap-slot :nested-head}
|
||||
;; <no-label> [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested-head
|
||||
:nested-head-params {:children-labels [:nested-child]})
|
||||
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
|
||||
(tho/add-simple-component :component3 :main3-root :main3-child
|
||||
:root-params {:name "Frame3"}
|
||||
:child-params {:name "Rect3"})
|
||||
(tho/swap-component-in-first-child :copy2 :component3))
|
||||
|
||||
page (thf/current-page file)
|
||||
|
||||
main1-root (ths/get-shape file :main1-root)
|
||||
main1-child (ths/get-shape file :main1-child)
|
||||
main2-root (ths/get-shape file :main2-root)
|
||||
nested-head (ths/get-shape file :nested-head)
|
||||
nested-child (ths/get-shape file :nested-child)
|
||||
copy2 (ths/get-shape file :copy2)
|
||||
copy2-nested-head (ths/get-shape file :copy2-nested-head)
|
||||
copy2-nested-child (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
|
||||
|
||||
near-main1-root (ctf/find-near-match file page {} main1-root)
|
||||
near-main1-child (ctf/find-near-match file page {} main1-child)
|
||||
near-main2-root (ctf/find-near-match file page {} main2-root)
|
||||
near-nested-head (ctf/find-near-match file page {} nested-head)
|
||||
near-nested-child (ctf/find-near-match file page {} nested-child)
|
||||
near-copy2 (ctf/find-near-match file page {} copy2)
|
||||
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
|
||||
near-copy2-nested-child (ctf/find-near-match file page {} copy2-nested-child)]
|
||||
|
||||
(t/is (nil? near-main1-root))
|
||||
(t/is (nil? near-main1-child))
|
||||
(t/is (nil? near-main2-root))
|
||||
(t/is (nil? near-nested-head))
|
||||
(t/is (= (:id near-nested-child) (thi/id :main1-child)))
|
||||
(t/is (nil? near-copy2))
|
||||
(t/is (= (:id near-copy2-nested-head) (thi/id :nested-head)))
|
||||
(t/is (= (:id near-copy2-nested-child) (thi/id :main3-child)))))
|
||||
|
||||
(t/testing "shapes in second level nested copies under swapped get the shape in the new main"
|
||||
(let [file
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; :nested2-child [:name Rect1] ---> :main1-child
|
||||
;;
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main4-root} [:name Frame4] # [Component :component4]
|
||||
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
|
||||
;; :nested4-child [:name Rect3] ---> :main3-child
|
||||
;;
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
|
||||
;; {swap-slot :nested2-head}
|
||||
;; <no-label> [:name Frame3] @--> :nested4-head
|
||||
;; <no-label> [:name Rect3] ---> :nested4-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested2-head
|
||||
:nested-head-params {:children-labels [:nested2-child]})
|
||||
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head])
|
||||
(tho/add-nested-component :component3 :main3-root :main3-child
|
||||
:component4 :main4-root :nested4-head
|
||||
:root1-params {:name "Frame3"}
|
||||
:main1-child-params {:name "Rect3"}
|
||||
:main2-root-params {:name "Frame4"}
|
||||
:nested-head-params {:children-labels [:nested4-child]})
|
||||
(tho/swap-component-in-first-child :copy2 :component4))
|
||||
|
||||
page (thf/current-page file)
|
||||
|
||||
main1-root (ths/get-shape file :main1-root)
|
||||
main1-child (ths/get-shape file :main1-child)
|
||||
main2-root (ths/get-shape file :main2-root)
|
||||
nested2-head (ths/get-shape file :nested2-head)
|
||||
nested2-child (ths/get-shape file :nested2-child)
|
||||
main3-root (ths/get-shape file :main3-root)
|
||||
main3-child (ths/get-shape file :main3-child)
|
||||
main4-root (ths/get-shape file :main4-root)
|
||||
nested4-head (ths/get-shape file :nested4-head)
|
||||
nested4-child (ths/get-shape file :nested4-child)
|
||||
copy2 (ths/get-shape file :copy2)
|
||||
copy2-nested-head (ths/get-shape file :copy2-nested-head)
|
||||
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
|
||||
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
|
||||
|
||||
near-main1-root (ctf/find-near-match file page {} main1-root)
|
||||
near-main1-child (ctf/find-near-match file page {} main1-child)
|
||||
near-main2-root (ctf/find-near-match file page {} main2-root)
|
||||
near-nested2-head (ctf/find-near-match file page {} nested2-head)
|
||||
near-nested2-child (ctf/find-near-match file page {} nested2-child)
|
||||
near-main3-root (ctf/find-near-match file page {} main3-root)
|
||||
near-main3-child (ctf/find-near-match file page {} main3-child)
|
||||
near-main4-root (ctf/find-near-match file page {} main4-root)
|
||||
near-nested4-head (ctf/find-near-match file page {} nested4-head)
|
||||
near-nested4-child (ctf/find-near-match file page {} nested4-child)
|
||||
near-copy2 (ctf/find-near-match file page {} copy2)
|
||||
near-copy2-nested-head (ctf/find-near-match file page {} copy2-nested-head)
|
||||
near-copy2-nested4-head (ctf/find-near-match file page {} copy2-nested4-head)
|
||||
near-copy2-nested4-child (ctf/find-near-match file page {} copy2-nested4-child)]
|
||||
|
||||
(t/is (nil? near-main1-root))
|
||||
(t/is (nil? near-main1-child))
|
||||
(t/is (nil? near-main2-root))
|
||||
(t/is (nil? near-nested2-head))
|
||||
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
|
||||
(t/is (nil? near-main3-root))
|
||||
(t/is (nil? near-main3-child))
|
||||
(t/is (nil? near-main4-root))
|
||||
(t/is (nil? near-nested4-head))
|
||||
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
|
||||
(t/is (nil? near-copy2))
|
||||
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
|
||||
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
|
||||
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child)))))
|
||||
|
||||
(t/testing "component in external libraries still work well"
|
||||
(let [library1
|
||||
;; {:main1-root} [:name Frame1] # [Component :component1]
|
||||
;; :main1-child [:name Rect1]
|
||||
;;
|
||||
;; {:main2-root} [:name Frame2] # [Component :component2]
|
||||
;; :nested2-head [:name Frame1] @--> [Component :component1] :main1-root
|
||||
;; :nested2-child [:name Rect1] ---> :main1-child
|
||||
(-> (thf/sample-file :library1)
|
||||
(tho/add-nested-component :component1 :main1-root :main1-child
|
||||
:component2 :main2-root :nested2-head
|
||||
:nested-head-params {:children-labels [:nested2-child]}))
|
||||
library2
|
||||
;; {:main3-root} [:name Frame3] # [Component :component3]
|
||||
;; :main3-child [:name Rect3]
|
||||
;;
|
||||
;; {:main4-root} [:name Frame4] # [Component :component4]
|
||||
;; :nested4-head [:name Frame3] @--> [Component :component1] :main3-root
|
||||
;; :nested4-child [:name Rect3] ---> :main3-child
|
||||
(-> (thf/sample-file :library2)
|
||||
(tho/add-nested-component :component3 :main3-root :main3-child
|
||||
:component4 :main4-root :nested4-head
|
||||
:root1-params {:name "Frame3"}
|
||||
:main1-child-params {:name "Rect3"}
|
||||
:main2-root-params {:name "Frame4"}
|
||||
:nested-head-params {:children-labels [:nested4-child]}))
|
||||
|
||||
file
|
||||
;; :copy2 [:name Frame2] #--> [Component :component2] :main2-root
|
||||
;; :copy2-nested-head [:name Frame4] @--> [Component :component4] :main4-root
|
||||
;; {swap-slot :nested2-head}
|
||||
;; <no-label> [:name Frame3] @--> :nested4-head
|
||||
;; <no-label> [:name Rect3] ---> :nested4-child
|
||||
(-> (thf/sample-file :file1)
|
||||
(thc/instantiate-component :component2 :copy2 :children-labels [:copy2-nested-head]
|
||||
:library library1)
|
||||
(tho/swap-component-in-first-child :copy2 :component4 :library library2))
|
||||
|
||||
page-library1 (thf/current-page library1)
|
||||
page-library2 (thf/current-page library2)
|
||||
page-file (thf/current-page file)
|
||||
libraries {(:id library1) library1
|
||||
(:id library2) library2}
|
||||
|
||||
main1-root (ths/get-shape library1 :main1-root)
|
||||
main1-child (ths/get-shape library1 :main1-child)
|
||||
main2-root (ths/get-shape library1 :main2-root)
|
||||
nested2-head (ths/get-shape library1 :nested2-head)
|
||||
nested2-child (ths/get-shape library1 :nested2-child)
|
||||
main3-root (ths/get-shape library2 :main3-root)
|
||||
main3-child (ths/get-shape library2 :main3-child)
|
||||
main4-root (ths/get-shape library2 :main4-root)
|
||||
nested4-head (ths/get-shape library2 :nested4-head)
|
||||
nested4-child (ths/get-shape library2 :nested4-child)
|
||||
copy2 (ths/get-shape file :copy2)
|
||||
copy2-nested-head (ths/get-shape file :copy2-nested-head)
|
||||
copy2-nested4-head (ths/get-shape-by-id file (first (:shapes copy2-nested-head)))
|
||||
copy2-nested4-child (ths/get-shape-by-id file (first (:shapes copy2-nested4-head)))
|
||||
|
||||
near-main1-root (ctf/find-near-match file page-file libraries main1-root)
|
||||
near-main1-child (ctf/find-near-match file page-file libraries main1-child)
|
||||
near-main2-root (ctf/find-near-match file page-file libraries main2-root)
|
||||
near-nested2-head (ctf/find-near-match library1 page-library1 libraries nested2-head)
|
||||
near-nested2-child (ctf/find-near-match library1 page-library1 libraries nested2-child)
|
||||
near-main3-root (ctf/find-near-match file page-file libraries main3-root)
|
||||
near-main3-child (ctf/find-near-match file page-file libraries main3-child)
|
||||
near-main4-root (ctf/find-near-match file page-file libraries main4-root)
|
||||
near-nested4-head (ctf/find-near-match library2 page-library2 libraries nested4-head)
|
||||
near-nested4-child (ctf/find-near-match library2 page-library2 libraries nested4-child)
|
||||
near-copy2 (ctf/find-near-match file page-file libraries copy2)
|
||||
near-copy2-nested-head (ctf/find-near-match file page-file libraries copy2-nested-head)
|
||||
near-copy2-nested4-head (ctf/find-near-match file page-file libraries copy2-nested4-head)
|
||||
near-copy2-nested4-child (ctf/find-near-match file page-file libraries copy2-nested4-child)]
|
||||
|
||||
(thf/dump-file library1 :keys [:name :swap-slot-label] :show-refs? true)
|
||||
(t/is (some? main1-root))
|
||||
(t/is (some? main1-child))
|
||||
(t/is (some? main2-root))
|
||||
(t/is (some? nested2-head))
|
||||
(t/is (some? nested2-child))
|
||||
(t/is (some? main3-root))
|
||||
(t/is (some? main3-child))
|
||||
(t/is (some? main4-root))
|
||||
(t/is (some? nested4-head))
|
||||
(t/is (some? nested4-child))
|
||||
(t/is (some? copy2))
|
||||
(t/is (some? copy2-nested-head))
|
||||
(t/is (some? copy2-nested4-head))
|
||||
(t/is (some? copy2-nested4-child))
|
||||
|
||||
(t/is (nil? near-main1-root))
|
||||
(t/is (nil? near-main1-child))
|
||||
(t/is (nil? near-main2-root))
|
||||
(t/is (nil? near-nested2-head))
|
||||
(t/is (= (:id near-nested2-child) (thi/id :main1-child)))
|
||||
(t/is (nil? near-main3-root))
|
||||
(t/is (nil? near-main3-child))
|
||||
(t/is (nil? near-main4-root))
|
||||
(t/is (nil? near-nested4-head))
|
||||
(t/is (= (:id near-nested4-child) (thi/id :main3-child)))
|
||||
(t/is (nil? near-copy2))
|
||||
(t/is (= (:id near-copy2-nested-head) (thi/id :nested2-head)))
|
||||
(t/is (= (:id near-copy2-nested4-head) (thi/id :nested4-head)))
|
||||
(t/is (= (:id near-copy2-nested4-child) (thi/id :nested4-child))))))
|
||||
|
||||
@ -23,10 +23,11 @@
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
;; Change this to :info :debug or :trace to debug this module
|
||||
(log/set-level! :info)
|
||||
(log/set-level! :warn)
|
||||
|
||||
(def page-change?
|
||||
#{:add-page :mod-page :del-page :mov-page})
|
||||
|
||||
(def update-layout-attr?
|
||||
#{:hidden})
|
||||
|
||||
@ -207,6 +208,7 @@
|
||||
|
||||
;; Prevent commit changes by a viewer team member (it really should never happen)
|
||||
(when (:can-edit permissions)
|
||||
(log/trace :hint "commit-changes" :redo-changes redo-changes)
|
||||
(let [selected (dm/get-in state [:workspace-local :selected])]
|
||||
(rx/of (-> params
|
||||
(assoc :undo-group undo-group)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user