✨ Test component validation, repair and library asset sync

Fill the unit test gaps in common and frontend component code:
- One test per component error code: break a file in one way, check
  the codes validation reports, repair it as the backend does (until
  valid, at most ten passes) and check what the repair changed.
  Pinned: F46 (a dangling ref in a copy child is never reported), F47
  and F48 (two repairs that never converge). :component-not-main,
  :should-be-component-root and :not-component-not-allowed have no
  test, as no file can produce them.
- Library color and typography sync must not touch copies. Both do
  (F3): the color sync touches :fill-group, the typography sync
  :content-group and :text-content-attribute, and a later main edit of
  that attribute then no longer reaches the copy. Pinned.
- Twenty live thf/dump-file calls in common tests go; they printed
  whole file trees on every run and asserted nothing.
- The disabled copy-nested-3 frontend test runs again: a stray
  backquote quoted its run-store call, so it never called done.
This commit is contained in:
alonso.torres 2026-10-09 16:45:15 +02:00
parent a9f27816e5
commit b0b2044b57
9 changed files with 555 additions and 63 deletions

View File

@ -44,6 +44,15 @@ For shape mutations, prefer production-path helpers such as `cls/generate-update
`thf/apply-changes` validates by default and usually gives the most useful invariant failure. Pass `:validate? false` only for intentionally malformed intermediate state.
## Validation and repair tests
`common-tests.files.repair-components-test` has one test per component error code: break a file with
the helpers (`ths/update-shape`, `thc/update-component`), assert the exact set of codes, then repair
as the backend does (validate + `cfr/repair-file` until valid, at most 10 passes, like
`app.srepl.procs.file-repair`) and assert the result. Repairs that never converge are pinned with
`tm/known-failure`. A deleted component's objects are reached with `ctkl/get-component data id true`
(`thc/get-component` skips deleted ones).
## Geometry setup caution
For geometry-sensitive tests, read `mem:common/geometry-invariants` before positioning shapes. Use geometry-preserving helpers or production change helpers rather than direct single-field edits.

View File

@ -860,6 +860,5 @@
(: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)))))

View File

@ -0,0 +1,365 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns common-tests.files.repair-components-test
"One test per component error code of `app.common.files.validate`: each
builds a file broken in one way, checks the codes validation reports,
repairs it as the backend does (validate and repair until valid, at most
ten passes) and checks the result.
Three codes have no test because no file can produce them: the checks that
report `:component-not-main`, `:should-be-component-root` and
`:not-component-not-allowed` only run on shapes that already satisfy them."
(:require
[app.common.files.changes-builder :as pcb]
[app.common.files.repair :as cfr]
[app.common.files.validate :as cfv]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.composable.core :as tm]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.file :as ctf]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
;; The backend repairs a file at most this many times (`app.srepl.procs.file-repair`).
(def ^:private max-passes 10)
(defn- codes
[errors]
(into #{} (map :code) errors))
(defn- repair
"Validate `file` and repair it until it validates, as the backend does.
Returns `[codes file' errors']`: the codes found first, the repaired file
and the errors left after the last pass (nil when it validates)."
([file] (repair file {}))
([file libraries]
(let [errors (cfv/validate-file file libraries)]
(loop [file file
errs errors
passes 0]
(if (or (nil? errs) (= passes max-passes))
[(codes errors) file errs]
(let [changes (cfr/repair-file file libraries errs)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)]
(recur file' (cfv/validate-file file' libraries) (inc passes))))))))
;; ---- Configurations
(defn- simple
"A component (:main-root with :main-child) and a copy (:copy-root with
:copy-child)."
[& {:as params}]
(-> (thf/sample-file :file1 :page-label :page1)
(tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root
(merge {:copy-root-params {:children-labels [:copy-child]}}
params))))
(defn- nested
"Component2 (:main2-root) nests a copy of component1 (:main2-nested-head);
its copy :copy2-root holds :copy2-nested-head."
[]
(-> (thf/sample-file :file1 :page-label :page1)
(tho/add-nested-component-with-copy :component1 :main1-root :main1-child
:component2 :main2-root :main2-nested-head
:copy2-root
:copy2-root-params {:children-labels [:copy2-nested-head :copy2-child]})))
(defn- two-nested
"Component2 (:main2-root) nests two copies of component1 (:nested-a,
:nested-b); its copy :copy2-root holds their copies :copy-a and :copy-b."
[]
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(tho/add-simple-component :component1 :main1-root :main1-child)
(tho/add-frame :main2-root)
(thc/instantiate-component :component1 :nested-a :parent-label :main2-root)
(thc/instantiate-component :component1 :nested-b :parent-label :main2-root)
(thc/make-component :component2 :main2-root)
(thc/instantiate-component :component2 :copy2-root))
objects (:objects (thf/current-page file))]
(doseq [[main-label copy-label] [[:nested-a :copy-a] [:nested-b :copy-b]]]
(thi/set-id! copy-label
(->> (get-in objects [(thi/id :copy2-root) :shapes])
(filter #(= (thi/id main-label) (get-in objects [% :shape-ref])))
(first))))
file))
(defn- reparent
"Move the shape `label` under the shape `parent-id`, keeping the links
consistent."
[file label parent-id]
(let [objects (:objects (thf/current-page file))
shape (get objects (thi/id label))
old-id (:parent-id shape)
parent (get objects parent-id)]
(-> file
(ths/update-shape-by-id old-id :shapes (filterv #(not= (:id shape) %) (:shapes (get objects old-id))))
(ths/update-shape-by-id parent-id :shapes (conj (vec (:shapes parent)) (:id shape)))
(ths/update-shape label :parent-id parent-id)
(ths/update-shape label :frame-id (if (= :frame (:type parent)) parent-id (:frame-id parent))))))
(defn- delete-main
"Delete the main `label`, which deletes its component."
[file label]
(let [page (thf/current-page file)
[_ changes] (cls/generate-delete-shapes (pcb/empty-changes nil (:id page))
file page (:objects page) #{(thi/id label)} {})]
(thf/apply-changes file changes :validate? false)))
(defn- update-deleted-object
"Apply `f` to the shape `label` stored in the deleted component
`component-label`."
[file component-label label f]
(ctf/update-file-data file
(fn [data]
(ctkl/update-component data (thi/id component-label)
#(update-in % [:objects (thi/id label)] f)))))
(defn- deleted-object
[file component-label label]
(get-in (ctkl/get-component (:data file) (thi/id component-label) true)
[:objects (thi/id label)]))
(defn- slot-group
[id]
#{(ctk/build-swap-slot-group id)})
;; ---- Main heads
(t/deftest component-main-external
;; the main points at a library that also has its component
(let [file (ths/update-shape (simple) :main-root :component-file (thi/new-id! :library))
library (assoc file :id (thi/id :library))
[found file' errors] (repair file {(:id library) library})]
(t/is (= #{:component-main-external} found))
(t/is (nil? errors))
(t/is (= (thi/id :file1) (:component-file (ths/get-shape file' :main-root))))))
(t/deftest component-not-found
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :component-id (uuid/next)))]
(t/is (= #{:component-not-found :ref-shape-not-found} found))
(t/is (nil? errors))
;; the copy is detached
(t/is (nil? (:component-id (ths/get-shape file' :copy-root))))))
(t/deftest invalid-main-instance-id
(let [[found file' errors] (repair (thc/update-component (simple) :component1 :main-instance-id (uuid/next)))]
(t/is (= #{:invalid-main-instance-id :ref-shape-not-found} found))
(t/is (nil? errors))
(t/is (= (thi/id :main-root) (:main-instance-id (thc/get-component file' :component1))))))
(t/deftest invalid-main-instance-page
(let [file (-> (simple)
(thf/add-sample-page :page2)
(thf/switch-to-page :page1)
(thc/update-component :component1 :main-instance-page (thi/id :page2)))
[found file' errors] (repair file)]
(t/is (= #{:invalid-main-instance-page :ref-shape-not-found} found))
(t/is (nil? errors))
(t/is (= (thi/id :page1) (:main-instance-page (thc/get-component file' :component1))))))
(t/deftest invalid-main-instance
;; the component names the copy as its main; the repair of
;; :invalid-main-instance does nothing, the main's own error fixes it
(let [[found file' errors] (repair (thc/update-component (simple) :component1 :main-instance-id (thi/id :copy-root)))]
(t/is (= #{:invalid-main-instance :invalid-main-instance-id :ref-shape-not-found} found))
(t/is (nil? errors))
(t/is (= (thi/id :main-root) (:main-instance-id (thc/get-component file' :component1))))))
(t/deftest shape-ref-in-main
(let [[found file' errors] (repair (ths/update-shape (simple) :main-root :shape-ref (thi/id :copy-root)))]
(t/is (= #{:shape-ref-in-main} found))
(t/is (nil? errors))
(t/is (nil? (:shape-ref (ths/get-shape file' :main-root))))))
;; ---- Shapes inside mains
(t/deftest component-main-flag-on-a-child
(let [[found file' errors] (repair (ths/update-shape (simple) :main-child :main-instance true))]
(t/is (= #{:component-main} found))
(t/is (nil? errors))
(t/is (nil? (:main-instance (ths/get-shape file' :main-child))))))
(t/deftest component-main-file-on-a-child
;; the repair only removes :main-instance, so the error never goes away
(let [[found _ errors] (repair (ths/update-shape (simple) :main-child :component-file (thi/id :file1)))]
(t/is (= #{:component-main} found))
(tm/known-failure "F47" nil #{:component-main} (codes errors))))
(t/deftest should-not-be-component-root
(let [[found file' errors] (repair (ths/update-shape (simple) :main-child :component-root true))]
(t/is (= #{:should-not-be-component-root} found))
(t/is (nil? errors))
(t/is (nil? (:component-root (ths/get-shape file' :main-child))))))
(t/deftest not-head-main-not-allowed
;; a main root without component is no longer a head; repair detaches the
;; main and then its copy
(let [[found file' errors] (repair (ths/update-shape (simple) :main-root :component-id nil))]
(t/is (= #{:not-head-main-not-allowed :ref-shape-is-not-head} found))
(t/is (nil? errors))
(t/is (nil? (:main-instance (ths/get-shape file' :main-root))))
(t/is (nil? (:shape-ref (ths/get-shape file' :copy-root))))))
;; ---- Copies
(t/deftest ref-shape-not-found
(let [[found file' errors] (repair (ths/update-shape (nested) :copy2-nested-head :shape-ref (uuid/next)))]
(t/is (= #{:ref-shape-not-found :missing-slot} found))
(t/is (nil? errors))
;; no matching shape: the nested copy is detached
(t/is (nil? (:shape-ref (ths/get-shape file' :copy2-nested-head))))))
(t/deftest ref-shape-not-found-in-a-copy-child
;; the reference of a non-head copy shape is never checked: the check reads
;; the library from the shape itself, and only heads have one
(let [errors (cfv/validate-file (ths/update-shape (simple) :copy-child :shape-ref (uuid/next)) {})]
(tm/known-failure "F46" #{:ref-shape-not-found} #{} (codes errors))))
(t/deftest ref-shape-is-head
;; the check reports no component, so the repair cannot make the shape a
;; head again
(let [file (-> (nested)
(ths/update-shape :copy2-nested-head :component-id nil)
(ths/update-shape :copy2-nested-head :component-file nil))
[found _ errors] (repair file)]
(t/is (= #{:ref-shape-is-head} found))
(tm/known-failure "F48" nil #{:ref-shape-is-head} (codes errors))))
(t/deftest ref-shape-is-not-head
;; a copy child made a head while the shape it references is not one
(let [file (-> (simple :main-child-params {:type :frame})
(ths/update-shape :copy-child :component-id (thi/id :component1))
(ths/update-shape :copy-child :component-file (thi/id :file1)))
[found file' errors] (repair file)]
(t/is (= #{:ref-shape-is-not-head :component-id-mismatch} found))
(t/is (nil? errors))
(t/is (not (ctk/instance-head? (ths/get-shape file' :copy-child))))))
(t/deftest component-id-mismatch
(let [[found file' errors] (repair (ths/update-shape (nested) :copy2-nested-head :component-id (thi/id :component2)))]
(t/is (= #{:component-id-mismatch} found))
(t/is (nil? errors))
(t/is (= (thi/id :component1) (:component-id (ths/get-shape file' :copy2-nested-head))))))
(t/deftest not-head-copy-not-allowed
(let [[found file' errors] (repair (reparent (simple) :copy-child uuid/zero))]
(t/is (= #{:not-head-copy-not-allowed} found))
(t/is (nil? errors))
(t/is (nil? (:shape-ref (ths/get-shape file' :copy-child))))))
(t/deftest instance-head-not-frame
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :type :rect))]
(t/is (= #{:instance-head-not-frame} found))
(t/is (nil? errors))
(t/is (= :frame (:type (ths/get-shape file' :copy-root))))))
(t/deftest invalid-text-touched
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-child :touched #{:text-content-text}))]
(t/is (= #{:invalid-text-touched} found))
(t/is (nil? errors))
(t/is (contains? (:touched (ths/get-shape file' :copy-child)) :content-group))))
;; ---- Nesting
(t/deftest root-main-not-allowed
(let [file (-> (simple)
(tho/add-simple-component :component2 :main2-root :main2-child)
(reparent :main-root (thi/id :main2-root)))
[found file' errors] (repair file)
main (ths/get-shape file' :main-root)]
(t/is (= #{:root-main-not-allowed :nested-main-not-allowed} found))
(t/is (nil? errors))
;; moved back to the page root, as a top main
(t/is (= uuid/zero (:parent-id main)))
(t/is (true? (:component-root main)))))
(t/deftest nested-main-not-allowed
(let [[found file' errors] (repair (ths/update-shape (simple) :main-root :component-root nil))]
(t/is (= #{:nested-main-not-allowed} found))
(t/is (nil? errors))
(t/is (true? (:component-root (ths/get-shape file' :main-root))))))
(t/deftest root-copy-not-allowed
(let [[found file' errors] (repair (reparent (simple) :copy-root (thi/id :main-root)))]
(t/is (= #{:root-copy-not-allowed} found))
(t/is (nil? errors))
;; it stays in the main, as a nested copy
(t/is (nil? (:component-root (ths/get-shape file' :copy-root))))))
(t/deftest nested-copy-not-allowed
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :component-root nil))]
(t/is (= #{:nested-copy-not-allowed} found))
(t/is (nil? errors))
(t/is (true? (:component-root (ths/get-shape file' :copy-root))))))
;; ---- Components
(t/deftest component-nil-objects-not-allowed
(let [[found file' errors] (repair (thc/update-component (simple) :component1 :objects nil))]
(t/is (= #{:component-nil-objects-not-allowed} found))
(t/is (nil? errors))
(t/is (not (contains? (thc/get-component file' :component1) :objects)))))
(t/deftest non-deleted-component-cannot-have-objects
(let [[found file' errors] (repair (thc/update-component (simple) :component1 :objects {(uuid/next) {}}))]
(t/is (= #{:non-deleted-component-cannot-have-objects} found))
(t/is (nil? errors))
(t/is (not (contains? (thc/get-component file' :component1) :objects)))))
(t/deftest shape-ref-cycle
(let [file (-> (two-nested)
(delete-main :main2-root)
(update-deleted-object :component2 :nested-a #(assoc % :shape-ref (:id %))))
[found file' errors] (repair file)]
(t/is (= #{:shape-ref-cycle} found))
(t/is (nil? errors))
(t/is (not= (thi/id :nested-a) (:shape-ref (deleted-object file' :component2 :nested-a))))))
;; ---- Swap slots
(t/deftest misplaced-slot
(let [[found file' errors] (repair (ths/update-shape (simple) :copy-root :touched (slot-group (uuid/next))))]
(t/is (= #{:misplaced-slot} found))
(t/is (nil? errors))
(t/is (nil? (ctk/get-swap-slot (ths/get-shape file' :copy-root))))))
(t/deftest duplicate-slot
(let [file (-> (two-nested)
(ths/update-shape :copy-a :touched (slot-group (thi/id :nested-a)))
(ths/update-shape :copy-b :touched (slot-group (thi/id :nested-a))))
[found file' errors] (repair file)]
(t/is (= #{:duplicate-slot} found))
(t/is (nil? errors))
(t/is (nil? (ctk/get-swap-slot (ths/get-shape file' :copy-b))))))
(t/deftest component-duplicate-slot
(let [file (-> (two-nested)
(delete-main :main2-root)
(update-deleted-object :component2 :nested-a #(assoc % :touched (slot-group (thi/id :nested-a))))
(update-deleted-object :component2 :nested-b #(assoc % :touched (slot-group (thi/id :nested-a)))))
[found file' errors] (repair file)]
(t/is (= #{:component-duplicate-slot} found))
(t/is (nil? errors))
(t/is (nil? (ctk/get-swap-slot (deleted-object file' :component2 :nested-b))))))
(t/deftest missing-slot
(let [file (-> (two-nested)
(tho/add-simple-component :component3 :main3-root :main3-child)
(tho/swap-component-in-shape :copy-a :component3 :new-shape-label :swapped)
(ths/update-shape :swapped :touched nil))
[found file' errors] (repair file)]
(t/is (= #{:missing-slot} found))
(t/is (nil? errors))
(t/is (= (thi/id :nested-a) (ctk/get-swap-slot (ths/get-shape file' :swapped))))))

View File

@ -133,7 +133,6 @@
;; ==== Check
;; It should the same as above, but in an external library.
(thf/dump-file file)
(t/is (ctk/instance-root? copy-h-board-with-ellipse))
(t/is (= (:shape-ref copy-h-board-with-ellipse) (thi/id :board-with-ellipse)))
(t/is (nil? (ctk/get-swap-slot copy-h-board-with-ellipse)))

View File

@ -0,0 +1,147 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS SUBSIDIARY SL
(ns common-tests.logic.library-asset-sync-test
"Library color and typography sync in component copies. The sync writes
the asset's new value into every shape that uses it, copies included.
That write is derived from the library, not authored on the copy, so it
must not touch the copy (`mem:common/component-sync-contract`), and later
main edits must still reach it."
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.composable.core :as tm]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.types.text :as txt]
[app.common.types.typographies-list :as cttl]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(defn- sync-file
"Apply the sync of `asset-type` (`:colors`, `:typographies` or
`:components`) for `asset-id` from the file to itself."
[file asset-type asset-id]
(let [changes (cll/generate-sync-file-changes (pcb/empty-changes)
nil
asset-type
(:id file)
asset-id
(:id file)
{(:id file) file}
(:id file))]
(thf/apply-changes file changes)))
(defn- update-shape
"Change the shape `label` with `f` through the production change path."
[file label f]
(let [page (thf/current-page file)
changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page))
#{(thi/id label)}
f
(:objects page)
{})]
(thf/apply-changes file changes)))
;; ---- Colors
(defn- color-fill
[color]
{:fill-color color
:fill-opacity 1
:fill-color-ref-id (thi/id :color1)
:fill-color-ref-file (thi/id :file1)})
(defn- component-using-color
"A component whose child is filled with the library color :color1
(#111111), and a copy of it."
[]
(-> (thf/sample-file :file1)
(ths/add-sample-library-color :color1 :color "#111111")
(tho/add-simple-component-with-copy :component1 :main-root :main-child :copy-root
:main-child-params {:fills [(color-fill "#111111")]}
:copy-root-params {:children-labels [:copy-child]})))
(defn- change-library-color
[file color]
(-> file
(assoc-in [:data :colors (thi/id :color1) :color] color)
(sync-file :colors (thi/id :color1))))
(t/deftest library-color-sync-reaches-the-copy-untouched
(let [file' (change-library-color (component-using-color) "#222222")
copy (ths/get-shape file' :copy-child)]
(t/is (= "#222222" (-> copy :fills first :fill-color)))
;; the sync writes the fill without ignore-touched
(tm/known-failure "F3" nil #{:fill-group} (:touched copy))))
(t/deftest main-fill-edit-reaches-the-copy-after-a-library-color-sync
(let [file' (-> (component-using-color)
(change-library-color "#222222")
(sync-file :components (thi/id :component1))
(update-shape :main-child #(assoc % :fills (ths/sample-fills-color :fill-color "#333333")))
(sync-file :components (thi/id :component1)))]
;; the touched fill blocks it
(tm/known-failure "F3" "#333333" "#222222" (-> (ths/get-shape file' :copy-child) :fills first :fill-color))))
;; ---- Typographies
(defn- typography-text
"A text \"hello\" styled, on every node, with the library typography
:typography1, as applying a typography in the app does."
[font-size]
(-> (txt/change-text (:content (ths/sample-shape :tmp :type :text)) "hello")
(->> (txt/transform-nodes txt/is-content-node?
#(assoc %
:font-size font-size
:typography-ref-id (thi/id :typography1)
:typography-ref-file (thi/id :file1))))))
(defn- component-using-typography
"A component whose child is a text styled with the library typography
:typography1 (font size 14), and a copy of it."
[]
(-> (thf/sample-file :file1)
(ths/add-sample-typography :typography1 :font-size "14")
(tho/add-frame-with-text :main-root :main-child "hello"
:child-params {:content (typography-text "14")})
(thc/make-component :component1 :main-root)
(thc/instantiate-component :component1 :copy-root :children-labels [:copy-child])))
(defn- font-size-of
"The font size of the first span of the text `shape`."
[shape]
(get-in shape [:content :children 0 :children 0 :children 0 :font-size]))
(defn- change-library-typography
[file font-size]
(-> file
(update :data cttl/update-typography (thi/id :typography1) assoc :font-size font-size)
(sync-file :typographies (thi/id :typography1))))
(t/deftest library-typography-sync-reaches-the-copy-untouched
(let [file' (change-library-typography (component-using-typography) "24")
copy (ths/get-shape file' :copy-child)]
(t/is (= "24" (font-size-of copy)))
;; the sync writes the content with a raw :set, which touches it
(tm/known-failure "F3" nil #{:content-group :text-content-attribute} (:touched copy))))
(t/deftest main-font-edit-reaches-the-copy-after-a-library-typography-sync
(let [file' (-> (component-using-typography)
(change-library-typography "24")
(sync-file :components (thi/id :component1))
(update-shape :main-child
(fn [shape]
(update shape :content
#(txt/transform-nodes txt/is-content-node? (fn [node] (assoc node :font-size "40")) %))))
(sync-file :components (thi/id :component1)))]
;; the touched text attributes block it
(tm/known-failure "F3" "40" "24" (font-size-of (ths/get-shape file' :copy-child)))))

View File

@ -43,7 +43,6 @@
file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})
copy01' (ths/get-shape file' :copy01)]
(thf/dump-file file :keys [:width])
;; The copy had width 5 before the switch
(t/is (= (:width copy01) 5))
;; The copy has width 15 after the switch
@ -108,7 +107,6 @@
file' (tho/swap-component-in-shape file :copy01 :c02 {:keep-touched? true})
copy01' (ths/get-shape file' :copy01)]
(thf/dump-file file :keys [:width])
;; The copy had width 25 before the switch
(t/is (= (:width copy01) 25))
;; The override is keept: The copy still has width 25 after the switch
@ -222,8 +220,6 @@
copy01' (ths/get-shape file' :copy01)]
(thf/dump-file file :keys [:width])
;; The copy had width 5 before the switch
(t/is (= (:width copy01) 5))
;; The copy has width 15 after the switch
@ -257,8 +253,6 @@
child01' (ths/get-shape file' :child01)]
(thf/dump-file file :keys [:width])
;; The copy had width 5 before the switch
(t/is (= (:width child01) 5))
;; The copy has width 15 after the switch
@ -305,8 +299,6 @@
copy01' (ths/get-shape file' :copy01)]
(thf/dump-file file :keys [:width :touched])
;; The copy had fill color before the switch
(t/is (= (:fills copy01) fills))
;; The copy still has fill color after the switch
@ -358,8 +350,6 @@
nested02' (ths/get-shape file' :nested02)]
(thf/dump-file file :keys [:width])
;; The copy had fill color before the switch
(t/is (= (:fills nested02) fills))
;; The copy still has fill color after the switch
@ -475,8 +465,6 @@
copy-both' (ths/get-shape file' :copy-both-2)
copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Clean copy
;; Before the switch:
@ -599,8 +587,6 @@
copy-both' (ths/get-shape file' :copy-both-2)
copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Clean copy
;; Before the switch:
@ -721,8 +707,6 @@
copy-both' (ths/get-shape file' :copy-both-2)
copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Clean copy
;; Before the switch:
@ -845,8 +829,6 @@
copy-both' (ths/get-shape file' :copy-both-2)
copy-both-t' (get-in page' [:objects (-> copy-both' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Clean copy
;; Before the switch:
@ -964,8 +946,6 @@
copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Copy structure clean
;; Before the switch, first line:
;; * font size 14
@ -1095,8 +1075,6 @@
copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Copy structure clean
;; Before the switch, first line:
;; * font size 14
@ -1222,8 +1200,6 @@
copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Copy structure clean
;; Before the switch, first line:
;; * font size 14
@ -1350,8 +1326,6 @@
copy-structure-mixed' (ths/get-shape file' :copy-structure-mixed-2)
copy-structure-mixed-t' (get-in page' [:objects (-> copy-structure-mixed' :shapes first)])]
(thf/dump-file file' {:keys [:name #_:content]})
;;;;;;;;;;; Copy structure clean
;; Before the switch, first line:
;; * font size 14
@ -1494,7 +1468,6 @@
copy02' (ths/get-shape file' :copy02)
copy-cp02' (ths/get-shape file' :copy-cp02)]
(thf/dump-file file')
;;copy-cp02 is a copy of external02
(t/is (= (:component-id copy-cp02) (:id external02)))
;;copy-01 had copy-cp02 as child
@ -1543,7 +1516,6 @@
copy02' (ths/get-shape file' :copy02)
copy-cp02' (ths/get-shape file' :copy-cp02)
copy-cp02-rect' (ths/get-shape-by-id file' (-> copy-cp02' :shapes first))]
(thf/dump-file file')
;;copy-cp02 is a copy of external02
(t/is (= (:component-id copy-cp02) (:id external02)))
;;copy-01 had copy-cp02 as child
@ -1611,7 +1583,6 @@
c02-in-copy' (ths/get-shape file' :c02-in-copy)
rect02' (get-in page' [:objects (-> c02-in-copy' :shapes first)])]
(thf/dump-file file :keys [:width :touched])
;; The rect had width 25 before the switch
(t/is (= (:width rect01) 25))
;; The rect still has width 25 after the switch

View File

@ -23,6 +23,7 @@
[common-tests.files-migrations-test]
[common-tests.files.comp-processors-test]
[common-tests.files.helpers-test]
[common-tests.files.repair-components-test]
[common-tests.files.repair-test]
[common-tests.files.shapes-builder-test]
[common-tests.files.tokens-test]
@ -66,6 +67,7 @@
[common-tests.logic.composable-sync-test]
[common-tests.logic.copying-and-duplicating-test]
[common-tests.logic.duplicated-pages-test]
[common-tests.logic.library-asset-sync-test]
[common-tests.logic.move-shapes-test]
[common-tests.logic.multiple-nesting-levels-test]
[common-tests.logic.swap-and-reset-test]
@ -128,6 +130,7 @@
'common-tests.files-migrations-test
'common-tests.files.comp-processors-test
'common-tests.files.helpers-test
'common-tests.files.repair-components-test
'common-tests.files.repair-test
'common-tests.files.shapes-builder-test
'common-tests.files.tokens-test
@ -171,6 +174,7 @@
'common-tests.logic.composable-sync-test
'common-tests.logic.copying-and-duplicating-test
'common-tests.logic.duplicated-pages-test
'common-tests.logic.library-asset-sync-test
'common-tests.logic.move-shapes-test
'common-tests.logic.multiple-nesting-levels-test
'common-tests.logic.swap-and-reset-test

View File

@ -367,7 +367,6 @@
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))

View File

@ -384,38 +384,37 @@
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 2)))))))
done)))
;; FIXME: this test does not calls done consistently, so it break all the test suite
#_(t/deftest copy-nested-3
(t/async done
(mock/with-mocks {uuid/next cthi/next-uuid}
(fn [done']
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
(t/deftest copy-nested-3
(t/async done
(mock/with-mocks {uuid/next cthi/next-uuid}
(fn [done']
(let [;; ==== Setup
file (setup-file)
store (ths/setup-store file)
;; ==== Action
;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
;; main and copy:
;; - Duplicate it two times, moving the duplicates to another page
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
`(ths/run-store
store done' events
(fn [new-state]
(let [file' (-> (ths/get-file-from-state new-state)
(cthf/switch-to-page :page-2))]
(cthf/validate-file! file')
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))
done)))
;; ==== Action
;; For each copy of Simple1 nested in a main or other copy, and the group inside Composed3
;; main and copy:
;; - Duplicate it two times, moving the duplicates to another page
events
(concat
(duplicate-copy-nested-and-group-out-of-the-main file :target-page-label :page-2)
;; - Change color of Simple1
(set-color-bottom-shape :frame-simple-1 file {:color "#111111"})
;; - Change color of the previous main
(set-color-bottom-shape :frame-composed-1 file {:color "#222222"})
(set-color-bottom-shape :group-3 file {:color "#333333"}))]
(ths/run-store
store done' events
(fn [new-state]
(let [file' (-> (ths/get-file-from-state new-state)
(cthf/switch-to-page :page-2))]
(cthf/validate-file! file')
;; Check that it's NOT PROPAGATED.
(t/is (= (count-shapes file' "rect-simple-1" "#111111") 10))
(t/is (= (count-shapes file' "rect-simple-1" "#222222") 4))
(t/is (= (count-shapes file' "rect-simple-1" "#333333") 0)))))))
done)))
(t/deftest duplicate-page-integrity-frame-group-component
;; This test covers the bug fixed in 2.9.0: duplicating a page with a mainInstance inside a group