Merge remote-tracking branch 'origin/staging' into staging

This commit is contained in:
Andrey Antukh 2026-10-16 11:25:45 +02:00
commit 3e1334ce97
9 changed files with 429 additions and 49 deletions

View File

@ -16,6 +16,7 @@
[app.common.files.helpers :as cph]
[app.common.files.migrations :as fmig]
[app.common.geom.shapes :as gsh]
[app.common.path-names :as cpn]
[app.common.schema :as sm]
[app.common.svg :as csvg]
[app.common.time :as dt]
@ -102,7 +103,8 @@
(str/capital (d/name type)))))
:always
(update :name unique-name state)))
(-> (update :name cpn/clean-path)
(update :name unique-name state))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SCHEMAS

View File

@ -21,21 +21,24 @@
[app.common.geom.shapes.text :as gsht]
[app.common.logging :as l]
[app.common.math :as mth]
[app.common.path-names :as cpn]
[app.common.schema :as sm]
[app.common.svg :as csvg]
[app.common.types.color :as types.color]
[app.common.types.component :as ctk]
[app.common.types.components-list :as ctkl]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.fills :as types.fills]
[app.common.types.path :as path]
[app.common.types.path.segment :as path.segment]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.shadow :as ctss]
[app.common.types.shape.text :as ctst]
[app.common.types.text :as types.text]
[app.common.types.token :as ctt]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
@ -2169,7 +2172,7 @@
(if (some? token)
(update shape :applied-tokens
(fn [applied]
(-> (zipmap ctt/per-side-stroke-width-keys (repeat token))
(-> (zipmap cto/per-side-stroke-width-keys (repeat token))
(merge applied)
(dissoc :stroke-width))))
shape)))
@ -2219,6 +2222,66 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0033-normalize-main-instance-names"
[data _]
;; In penpot shape names always have "/" characters normalized with one space between and after,
;; to form paths. But this is not enforced and may be places that still admit names like
;; "Group/Name" or "Group / Name". With normal shapes this is not a problem. But for main
;; instances, the name of the root shape must match exactly the path+name of the component.
;; Here we normalize the names of all main instances, and a new file integrity validation has
;; been added to enforce this from now on.
;; Main instances in variants are a special case because they need to match the variant name.
(letfn [(update-shape [container shape-id]
(let [shape (ctn/get-shape container shape-id)
shape (cond
;; Variant container must have a normalized name
(ctk/is-variant-container? shape)
(let [name (:name shape)
clean-name (cpn/clean-path name)
clean-name (if (str/blank? clean-name)
"(empty)" ;; this may occur if the name contains only separators
clean-name)]
(if (= name clean-name)
shape
(assoc shape :name clean-name)))
;; Main instance inside a variant must have the same name as the container
(ctk/main-instance? shape)
(if (ctk/is-variant? shape)
(let [variant-container (ctn/get-shape container (:parent-id shape))]
(if (or (= (:name shape) (:name variant-container))
(nil? variant-container))
shape
(assoc shape :name (:name variant-container))))
;; Other main instances must have the same name as the path+name of the component
(let [component (ctkl/get-component data (:component-id shape) true)
expected-name (when component
(cpn/merge-path-item (:path component) (:name component)))]
(if (or (= expected-name (:name shape))
(nil? expected-name))
shape
(assoc shape :name expected-name))))
;; Any other shape does not need processing
:else shape)]
(reduce update-shape
(ctt/set-shape container shape)
(:shapes shape))))
(update-container
[container]
(let [root (ctn/get-container-root container)]
(if root
(update-shape container (:id root))
container)))]
(-> data
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(def available-migrations
(into (d/ordered-set)
["legacy-2"
@ -2308,4 +2371,5 @@
"0029-move-background-blur-out-of-blur"
"0030-remove-stroke-per-side-attr"
"0031-migrate-stroke-width-token-attr"
"0032-fix-children-not-in-parent"]))
"0032-fix-children-not-in-parent"
"0033-normalize-main-instance-names"]))

View File

@ -7,6 +7,7 @@
(ns app.common.files.repair
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
@ -215,6 +216,20 @@
(pcb/with-library-data file-data)
(pcb/update-component (:component-id shape) repair-component))))
(defmethod repair-error :invalid-main-instance-name
[_ {:keys [shape page-id args] :as error} file-data _]
(let [repair-shape
(fn [shape]
;; Change the name to match the component
(let [component-name (:component-name args)]
(log/debug :hint (str " -> rename shape " (:id shape) " to " component-name))
(assoc shape :name component-name)))]
(log/debug :hint "repairing shape :invalid-main-instance-name" :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 :invalid-main-instance
[_ {:keys [shape page-id] :as error} file-data _]
(let [repair-shape
@ -729,6 +744,42 @@
(pcb/with-file-data file-data)
(pcb/update-shapes [parent-id] repair-fn))))
(defmethod repair-error :variant-container-bad-name
[_ {:keys [shape page-id args]} file-data _]
(let [clean-name (:clean-name args)
repair-shape
(fn [shape]
(log/debug :hint " -> set :name of shape" :id (:id shape) :name clean-name)
(assoc shape :name clean-name))
repair-component
(fn [component]
(let [[path name] (cpn/split-group-name clean-name)]
(log/debug :hint " -> set :path and :name of component" :id (:id component) :path path :name name)
(assoc component :path path :name name)))
all-shape-ids
(cons (:id shape) (:shapes shape))
objects
(dm/get-in file-data [:pages-index page-id :objects])
all-main-instances
(map (d/getf objects) (:shapes shape))
all-component-ids
(map :component-id all-main-instances)]
(log/debug :hint "repairing shape :variant-container-bad-name" :id (:id shape) :name (:name shape) :page-id page-id)
(as-> (pcb/empty-changes nil page-id) $
(pcb/with-file-data $ file-data)
(pcb/with-library-data $ file-data)
(pcb/update-shapes $ all-shape-ids repair-shape)
(reduce #(pcb/update-component %1 %2 repair-component)
$
all-component-ids))))
(defmethod repair-error :variant-main-bad-name
[_ {:keys [shape page-id args]} file-data _]
(let [repair-fn
@ -771,14 +822,22 @@
(fn [shape]
(let [variant-id (:variant-id args)]
;; Set the desired variant-id
(log/debug :hint (str " -> set variant-id to " variant-id))
(assoc shape
:variant-id variant-id)))]
(log/debug :hint (str " -> set variant-id in shape to " variant-id))
(assoc shape :variant-id variant-id)))
repair-component
(fn [component]
(let [variant-id (:variant-id args)]
;; Set the desired variant-id
(log/debug :hint (str " -> set variant-id in component to " variant-id))
(assoc component :variant-id variant-id)))]
(log/debug :hint "repairing shape :variant-component-bad-id" :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))))
(pcb/with-library-data file-data)
(pcb/update-shapes [(:id shape)] repair-shape)
(pcb/update-component (:component-id shape) repair-component))))
(defmethod repair-error :missing-tokens-status
[_ _ file-data _]

View File

@ -41,6 +41,7 @@
:duplicate-slot
:invalid-main-instance-id
:invalid-main-instance-page
:invalid-main-instance-name
:invalid-main-instance
:invalid-parent
:component-main
@ -70,6 +71,7 @@
:invalid-variant-properties
:variant-not-main
:parent-not-variant
:variant-container-bad-name
:variant-main-bad-name
:variant-main-bad-variant-name
:variant-component-bad-name
@ -302,7 +304,14 @@
shape file page)
(report-error :invalid-main-instance-page
(str/ffmt "Main instance page of component % is not valid" (:component-id shape))
shape file page))))))))
shape file page))))
(when-not (ctk/is-variant? component) ;; Variants are checked elsewhere
(let [component-name (cpn/merge-path-item (:path component) (:name component))]
(when-not (= (:name shape) component-name)
(report-error :invalid-main-instance-name
(str/ffmt "Main instance % has an invalid name" (:id shape))
shape file page
:component-name component-name))))))))
(defn- check-component-not-main-head
"Validate shape is a not-main instance head, component
@ -570,14 +579,25 @@
(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
-the name of the container should be normalized for paths (separator / with one space at each side)."
[shape file page]
(let [shape-id (:id shape)
shapes (:shapes shape)
objects (:objects page)
file-data (:data file)
first-child (get objects (first shapes))
prop-names (cfv/extract-properties-names first-child file-data)]
(let [shape-id (:id shape)
container-name (:name shape)
shapes (:shapes shape)
objects (:objects page)
file-data (:data file)
first-child (get objects (first shapes))
prop-names (cfv/extract-properties-names first-child file-data)
clean-path (cpn/clean-path container-name)
clean-path (if (str/blank? clean-path)
"(empty)" ;; this may occur if the name contains only separators
clean-path)]
(when-not (= container-name clean-path)
(report-error :variant-container-bad-name
(str/ffmt "Variant container % has a bad name %" shape-id container-name)
shape file page
:clean-name clean-path))
(run! (fn [child-id]
(when-let [child (get objects child-id)]
(if (not (ctk/is-variant? child))
@ -598,6 +618,11 @@
:prop-names prop-names))))))
shapes)))
(defn- renamed-container?
[container]
(and (some? container)
(not= (:name container) (cpn/clean-path (:name container)))))
(defn- check-variant
"Shape is a variant, so
-it should be a main component
@ -622,21 +647,23 @@
(str/ffmt "Variant % has an invalid variant-name" (:id shape))
shape file page
:variant-name variant-name))
(when-not (= (:name parent) (:name shape))
(report-error :variant-main-bad-name
(str/ffmt "Main instance inside variant % has an invalid name" (:id shape))
shape file page
:variant-name (:name parent)))
(when-not (= (:name parent) (cpn/merge-path-item (:path component) (:name component)))
(report-error :variant-component-bad-name
(str/ffmt "Component % has an invalid name" (:id shape))
shape file page
:variant-container-name (:name parent)))
;; If the container is going to be renamed all main and components will be renamed, too
(when-not (renamed-container? parent)
(when-not (= (:name parent) (:name shape))
(report-error :variant-main-bad-name
(str/ffmt "Main instance inside variant % has an invalid name" (:id shape))
shape file page
:variant-name (:name parent)))
(when-not (= (:name parent) (cpn/merge-path-item (:path component) (:name component)))
(report-error :variant-component-bad-name
(str/ffmt "Component % has an invalid name" (:id shape))
shape file page
:variant-container-name (:name parent))))
(when-not (= (:variant-id component) (:variant-id shape))
(report-error :variant-component-bad-id
(str/ffmt "Variant % has adifferent variant-id than its component" (:id shape))
(str/ffmt "Variant % has a :variant-id that does not point to the container" (:id shape))
shape file page
:variant-id (:variant-id component)))))
:variant-id (:parent-id shape)))))
(defn- check-shape
"Validate referential integrity and semantic coherence of

View File

@ -62,7 +62,6 @@
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value2"}]}))))
(defn add-variant-with-copy
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label component-copy-label]
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
@ -77,8 +76,6 @@
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property 1" :value "Value2"}]}))))
(defn add-variant-with-text
[file variant-label component1-label root1-label component2-label root2-label child1-label child2-label text1 text2
& {:keys [text1-params text2-params]}]

View File

@ -15,6 +15,7 @@
[app.common.files.repair :as cfr]
[app.common.files.validate :as cfv]
[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]
@ -24,6 +25,48 @@
(t/use-fixtures :each thi/test-fixture)
(t/deftest repair-invalid-main-instance-name
(t/testing "detect and repair a main instance whose name doesn't match the component's name"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(tho/add-simple-component-with-copy :component1 :root1 :child1 :copy1)
(tho/add-simple-component-with-copy :component2 :root2 :child2 :copy2)
;; Update component paths and names
(thc/update-component :component1 {:path "Group / Subgroup" :name "Component1"})
(thc/update-component :component2 {:path "Group / Subgroup" :name "Component2"})
;; Update component1 shapes name to have path structure
(ths/update-shape :root1 :name "Group / Subgroup / Component1")
(ths/update-shape :copy1 :name "Group / Subgroup / Component1")
;; Update component2's name to have unnormalized path
;; Note that the copy name is allowed to be different
(ths/update-shape :root2 :name "Group/Subgroup / Component2")
(ths/update-shape :copy2 :name "Group/Subgroup / Component2"))
errors (cfv/validate-file file {})
changes (cfr/repair-file file {} errors)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)
errors' (cfv/validate-file file' {})
root1' (ths/get-shape file' :root1)
child1' (ths/get-shape file' :child1)
copy1' (ths/get-shape file' :copy1)
root2' (ths/get-shape file' :root2)
child2' (ths/get-shape file' :child2)
copy2' (ths/get-shape file' :copy2)]
(t/is (= 1 (count errors)))
(t/is (= :invalid-main-instance-name (:code (first errors))))
(t/is (nil? errors'))
(t/is (= "Group / Subgroup / Component1" (:name root1')))
(t/is (= "Rect1" (:name child1')))
(t/is (= "Group / Subgroup / Component1" (:name copy1')))
(t/is (= "Group / Subgroup / Component2" (:name root2')))
(t/is (= "Rect1" (:name child2')))
(t/is (= "Group/Subgroup / Component2" (:name copy2'))))))
(t/deftest repair-main-instance-not-a-variant
(t/testing "detect and repair a variant component whose root shape is not a variant"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
@ -44,7 +87,7 @@
(t/is (= (thi/id :variant1) (:variant-id root1'))))))
(t/deftest repair-invalid-variant-id-variant-component-bad-id
(t/testing "detect and repair a variant component whose variant id does not match the container's id"
(t/testing "detect and repair a variant main whose variant id does not match the container's id"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
(ths/update-shape :root1 :variant-id (uuid/next)))
@ -61,7 +104,25 @@
(t/is (= :variant-component-bad-id (:code (second errors))))
(t/is (nil? errors'))
(t/is (= (thi/id :variant1) (:variant-id root1'))))))
(t/is (= (thi/id :variant1) (:variant-id root1')))))
(t/testing "detect and repair a variant component whose variant id does not match the container's id"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
(thc/update-component :component1 {:variant-id (uuid/next)}))
errors (cfv/validate-file file {})
changes (cfr/repair-file file {} errors)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)
errors' (cfv/validate-file file' {})
component1' (thc/get-component file' :component1)]
(t/is (= 1 (count errors)))
(t/is (= :variant-component-bad-id (:code (first errors))))
(t/is (nil? errors'))
(t/is (= (thi/id :variant1) (:variant-id component1'))))))
(t/deftest repair-invalid-variant-properties
(t/testing "detect and repair a second variant component whose properties do not match the first variant component's properties"
@ -161,23 +222,60 @@
(t/is (true? (:is-variant-container container'))))))
(t/deftest repair-variant-container-bad-name
(t/testing "detect and repair a variant container who has an unnormalized name"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
;; Variant container has a name that is not normalized and thus it cannot
;; match the component names, that are separated in path and group.
(ths/update-shape :variant1 :name "Group / Subgroup/Component")
(ths/update-shape :root1 :name "Group / Subgroup/Component")
(ths/update-shape :root2 :name "Group / Subgroup/Component")
(thc/update-component :component1 {:path "Group / Subgroup" :name "Component"})
(thc/update-component :component2 {:path "Group/Subgroup" :name "Component"}))
errors (cfv/validate-file file {})
changes (cfr/repair-file file {} errors)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)
errors' (cfv/validate-file file' {})
var1' (ths/get-shape file' :variant1)
root1' (ths/get-shape file' :root1)
comp1' (thc/get-component file' :component1)
root2' (ths/get-shape file' :root2)
comp2' (thc/get-component file' :component2)]
(t/is (= 1 (count errors)))
(t/is (= :variant-container-bad-name (:code (first errors))))
(t/is (nil? errors'))
(t/is (= "Group / Subgroup / Component" (:name var1')))
(t/is (= "Group / Subgroup / Component" (:name root1')))
(t/is (= "Group / Subgroup" (:path comp1')))
(t/is (= "Component" (:name comp1')))
(t/is (= "Group / Subgroup / Component" (:name root2')))
(t/is (= "Group / Subgroup" (:path comp2')))
(t/is (= "Component" (:name comp2'))))))
(t/deftest repair-variant-main-bad-name
(t/testing "detect and repair a main instance whose name doesn't match the variant container's name"
(let [file (-> (thf/sample-file :file1 :page-label :page1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
;; Change root1's name so it doesn't match the container
;; Change component's name so it doesn't match the container
(thc/update-component :component1 :name "WrongName")
(ths/update-shape :root1 :name "WrongName"))
errors (cfv/validate-file file {})
changes (cfr/repair-file file {} errors)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)
errors' (cfv/validate-file file' {})
root1' (ths/get-shape file' :root1)]
root1' (ths/get-shape file' :root1)
comp1' (thc/get-component file' :component1)]
(t/is (= 1 (count errors)))
(t/is (= 2 (count errors)))
(t/is (= :variant-main-bad-name (:code (first errors))))
(t/is (= :variant-component-bad-name (:code (second errors))))
(t/is (nil? errors'))
(t/is (= "Board" (:name root1'))))))
(t/is (= "Board" (:name root1')))
(t/is (= "Board" (:name comp1'))))))
(t/deftest repair-variant-main-bad-variant-name
(t/testing "detect and repair a variant shape whose :variant-name doesn't match the component's properties"
@ -211,23 +309,28 @@
(ths/update-shape :variant1 :name "Group / Subgroup / Component")
(ths/update-shape :root1 :name "Group / Subgroup / Component")
(ths/update-shape :root2 :name "Group / Subgroup / Component")
;; Update component paths and names
(thc/update-component :component1 {:path "Group / Subgroup" :name "Component"})
(thc/update-component :component2 {:path "Group / Subgroup" :name "Component"})
;; Break component1's name
(thc/update-component :component1 {:name "WrongName"}))
;; Break component2's name
(thc/update-component :component2 {:name "WrongName"}))
errors (cfv/validate-file file {})
changes (cfr/repair-file file {} errors)
file' (thf/apply-changes file {:redo-changes changes} :validate? false)
errors' (cfv/validate-file file' {})
comp1' (thc/get-component file' :component1)]
root1' (ths/get-shape file' :root1)
comp1' (thc/get-component file' :component1)
root2' (ths/get-shape file' :root2)
comp2' (thc/get-component file' :component2)]
(t/is (= 1 (count errors)))
(t/is (= :variant-component-bad-name (:code (first errors))))
(t/is (nil? errors'))
(t/is (= "Group / Subgroup / Component" (:name root1')))
(t/is (= "Group / Subgroup" (:path comp1')))
(t/is (= "Component" (:name comp1'))))))
(t/is (= "Component" (:name comp1')))
(t/is (= "Group / Subgroup / Component" (:name root2')))
(t/is (= "Group / Subgroup" (:path comp2')))
(t/is (= "Component" (:name comp2'))))))
(t/deftest repair-child-not-in-parent
(t/testing "detect and repair shapes whose parent exists but does not list them"

View File

@ -70,3 +70,20 @@
(t/is (= "Image" (:name media)))
(t/is (= 128 (:width media)))
(t/is (= 64 (:height media)))))
(t/deftest add-shape-normalizes-path-name
(let [file-id (uuid/next)
page-id (uuid/next)
shape-id (uuid/next)
state (-> (fb/create-state)
(fb/add-file {:id file-id :name "Test file"})
(fb/add-page {:id page-id :name "Page 1"})
(fb/add-shape {:id shape-id
:type :frame
:name "Group/Subgroup / Name"
:x 0
:y 0
:width 10
:height 10}))
shape (fb/get-shape state shape-id)]
(t/is (= "Group / Subgroup / Name" (:name shape)))))

View File

@ -9,12 +9,20 @@
[app.common.data :as d]
[app.common.files.migrations :as cfm]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.components :as thk]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.variants :as thv]
[app.common.types.file :as ctf]
[app.common.types.shape :as cts]
[app.common.types.token :as ctt]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(defmethod cfm/migrate-data "test/1" [data _] (update data :sum inc))
(defmethod cfm/migrate-data "test/2" [data _] (update data :sum inc))
(defmethod cfm/migrate-data "test/3" [data _] (update data :sum inc))
@ -647,3 +655,98 @@
(t/is (contains? (:migrations file') migration-id) "migration id persisted")
(t/is (= 4 (count (get-in file' [:data :pages-index page-id :objects shape-id :points])))
"path points repaired before schema validation")))
(t/deftest migration-0033-normalize-main-instance-diff-from-component
(let [migration-id "0033-normalize-main-instance-names"
file (-> (thf/sample-file :file1)
(tho/add-simple-component :component1 :root1 :child1
:component-params {:path "Group / Subgroup"
:name "Name"}
:root-params {:name "Group/Subgroup / Name"})
(tho/add-simple-component :component2 :root2 :child2
:component-params {:path "Group / Subgroup"
:name "Other Name"}
:root-params {:name "Group / Subgroup / Other Name"})
(ths/add-sample-shape :shape1 :name "Other Group/Other Subgroup / Other Name"))
data (ctf/file-data file)
data' (cfm/migrate-data data migration-id)
file' (assoc file :data data')
root1' (ths/get-shape file' :root1)
component1' (thk/get-component file' :component1)
root2' (ths/get-shape file' :root2)
component2' (thk/get-component file' :component2)
shape1' (ths/get-shape file' :shape1)]
(t/is (= "Group / Subgroup" (:path component1')))
(t/is (= "Name" (:name component1')))
(t/is (= "Group / Subgroup / Name" (:name root1')))
(t/is (= "Group / Subgroup" (:path component2')))
(t/is (= "Other Name" (:name component2')))
(t/is (= "Group / Subgroup / Other Name" (:name root2')))
(t/is (= "Other Group/Other Subgroup / Other Name" (:name shape1')))))
(t/deftest migration-0033-normalize-variant-instance-diff-from-container
(let [migration-id "0033-normalize-main-instance-names"
file (-> (thf/sample-file :file1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
(ths/update-shape :variant1 :name "Group / Subgroup / Component")
(ths/update-shape :root1 :name "Group / Subgroup / Component")
(ths/update-shape :root2 :name "Group / Subgroup/Component") ;; Bad path
(thk/update-component :component1 {:path "Group / Subgroup" :name "Component"})
(thk/update-component :component2 {:path "Group / Subgroup" :name "Component"})
(thk/instantiate-component :component2 :copy2)
(ths/update-shape :copy2 :name "Group / Subgroup/Component")) ;; Allowed name in copies
data (ctf/file-data file)
data' (cfm/migrate-data data migration-id)
file' (assoc file :data data')
variant1' (ths/get-shape file' :variant1)
root1' (ths/get-shape file' :root1)
component1' (thk/get-component file' :component1)
root2' (ths/get-shape file' :root2)
component2' (thk/get-component file' :component2)
copy2' (ths/get-shape file' :copy2)]
(t/is (= "Group / Subgroup / Component" (:name variant1')))
(t/is (= "Group / Subgroup / Component" (:name root1')))
(t/is (= "Group / Subgroup / Component" (:name root2')))
(t/is (= "Group / Subgroup" (:path component1')))
(t/is (= "Group / Subgroup" (:path component2')))
(t/is (= "Component" (:name component1')))
(t/is (= "Component" (:name component2')))
(t/is (= "Group / Subgroup/Component" (:name copy2')))))
(t/deftest migration-0033-normalize-variant-name-not-normalized
(let [migration-id "0033-normalize-main-instance-names"
file (-> (thf/sample-file :file1)
(thv/add-variant :variant1 :component1 :root1 :component2 :root2)
(ths/update-shape :variant1 :name "Group/Subgroup / Component")
(ths/update-shape :root1 :name "Group/Subgroup / Component")
(ths/update-shape :root2 :name "Group/Subgroup / Component") ;; Bad path
(thk/update-component :component1 {:path "Group / Subgroup" :name "Component"})
(thk/update-component :component2 {:path "Group / Subgroup" :name "Component"})
(thk/instantiate-component :component2 :copy2)
(ths/update-shape :copy2 :name "Group / Subgroup/Component")) ;; Allowed name in copies
data (ctf/file-data file)
data' (cfm/migrate-data data migration-id)
file' (assoc file :data data')
variant1' (ths/get-shape file' :variant1)
root1' (ths/get-shape file' :root1)
component1' (thk/get-component file' :component1)
root2' (ths/get-shape file' :root2)
component2' (thk/get-component file' :component2)
copy2' (ths/get-shape file' :copy2)]
(t/is (= "Group / Subgroup / Component" (:name variant1')))
(t/is (= "Group / Subgroup / Component" (:name root1')))
(t/is (= "Group / Subgroup / Component" (:name root2')))
(t/is (= "Group / Subgroup" (:path component1')))
(t/is (= "Group / Subgroup" (:path component2')))
(t/is (= "Component" (:name component1')))
(t/is (= "Component" (:name component2')))
(t/is (= "Group / Subgroup/Component" (:name copy2')))))

View File

@ -19,6 +19,7 @@
[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.types.shape-tree :as ctst]
[app.common.uuid :as uuid]
[clojure.test :as t]))
@ -250,18 +251,25 @@
component (thc/get-component file :component1)
;; ==== Action
changes (cll/generate-rename-component (pcb/empty-changes)
(:id component)
"Test component after"
(:data file))
changes (-> (pcb/empty-changes nil (thf/current-page-id file))
(pcb/with-file-data (ctf/file-data file))
(cll/generate-rename-component
(:id component)
"Test component after"
(:data file))
(pcb/update-shapes
[(thi/id :main1-root)]
#(assoc % :name "Test component after")))
file' (thf/apply-changes file changes)
;; ==== Get
component' (thc/get-component file' :component1)]
component' (thc/get-component file' :component1)
main1-root' (ths/get-shape file' :main1-root)]
;; ==== Check
(t/is (= (:name component') "Test component after"))))
(t/is (= (:name component') "Test component after"))
(t/is (= (:name main1-root') "Test component after"))))
(t/deftest test-duplicate-component
(let [;; ==== Setup