🐛 Fix Creating or combining a variant set renames the component first

This commit is contained in:
Pablo Alba 2026-11-03 10:42:59 +01:00 committed by Pablo Alba
parent eeda9c1d18
commit 7666d7d500
4 changed files with 214 additions and 34 deletions

View File

@ -38,6 +38,24 @@
(pcb/update-shapes changes shape-ids #(assoc % :name stored-name))
components)))
(defn generate-define-variant-container
"One change vector. `name` is the string stored on the board.
The component path and name are `split-group-name` of that string.
Library data is refreshed before the first property is added."
[changes variant-id main-id component-id name]
(let [[path item-name] (cpn/split-group-name name)]
(-> changes
(pcb/update-shapes [variant-id]
#(assoc % :is-variant-container true :name name))
(pcb/update-shapes [main-id]
#(assoc % :name name :variant-id variant-id))
(pcb/update-component component-id
#(assoc % :variant-id variant-id
:path path
:name item-name)
{:apply-changes-local-library? true})
(clvp/generate-add-new-property variant-id {:fill-values? true}))))
(defn generate-add-new-variant
[changes shape variant-id new-component-id new-shape-id prop-num]
(let [data (pcb/get-library-data changes)

View File

@ -14,10 +14,12 @@
[app.common.logic.variants :as clv]
[app.common.path-names :as cpn]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.compositions :as tho]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.variants :as thv]
[app.common.types.component :as ctk]
[app.common.types.variant :as ctv]
[clojure.test :as t]
[cuerdas.core :as str]))
@ -566,6 +568,48 @@
(t/is (= (ctv/properties-to-name props') (:variant-name main')))
(t/is (= "active, crítica" (:variant-name main')))))
(defn- post-artboard-alpha-file
"A normal frame holding one main named Alpha — before the container flag."
[]
(-> (thf/sample-file :file1)
(tho/add-frame :v01 {:name "Board"})
(tho/add-simple-component :c01 :m01 :r01
:root-params {:parent-label :v01 :name "Alpha"})))
(t/deftest test-define-variant-container-renames-component-in-same-vector
(let [file (post-artboard-alpha-file)
page (thf/current-page file)
variant-id (thi/id :v01)
main-id (thi/id :m01)
component-id (:id (thc/get-component file :c01))
changes (-> (pcb/empty-changes nil (:id page))
(pcb/with-objects (:objects page))
(pcb/with-library-data (:data file))
(clv/generate-define-variant-container
variant-id main-id component-id "Component"))
file' (thf/apply-changes file changes)
container' (ths/get-shape file' :v01)
main' (ths/get-shape file' :m01)
comp' (thc/get-component file' :c01)
restored (thf/apply-undo-changes file' changes)
container-rest (ths/get-shape restored :v01)
main-rest (ths/get-shape restored :m01)
comp-rest (thc/get-component restored :c01)]
(t/is (ctk/is-variant-container? container'))
(t/is (= "Component"
(:name container')
(:name main')
(cpn/merge-path-item (:path comp') (:name comp'))))
(t/is (= variant-id (:variant-id main') (:variant-id comp')))
(t/is (= (ctv/properties-to-name (:variant-properties comp'))
(:variant-name main')))
(t/is (not (ctk/is-variant-container? container-rest)))
(t/is (= "Alpha" (:name main-rest)))
(t/is (= "Alpha" (cpn/merge-path-item (:path comp-rest) (:name comp-rest))))
(t/is (nil? (:variant-id main-rest)))
(t/is (nil? (:variant-id comp-rest)))))
(defn- rename-variant-changes
[file name]
(let [page (thf/current-page file)]

View File

@ -304,21 +304,6 @@
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id)))))))))
(defn generate-define-variant-container
"One commit that turns an existing frame into a valid variant set:
container flag and name, the main's variant-id, the component's
variant-id (refreshing library data), then the first property."
[changes variant-id main-id component-id name]
(-> changes
(pcb/update-shapes [variant-id]
#(assoc % :is-variant-container true :name name))
(pcb/update-shapes [main-id]
#(assoc % :name name :variant-id variant-id))
(pcb/update-component component-id
#(assoc % :variant-id variant-id)
{:apply-changes-local-library? true})
(clvp/generate-add-new-property variant-id {:fill-values? true})))
(defn- define-variant-container
"Read page objects after the artboard exists, then commit the defining
variant fields in one change."
@ -332,7 +317,8 @@
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/with-library-data data)
(generate-define-variant-container variant-id main-id component-id name))]
(clv/generate-define-variant-container
variant-id main-id component-id name))]
(rx/of (dch/commit-changes changes))))))
(defn- focus-property
@ -340,7 +326,10 @@
(ptk/reify ::focus-property
ptk/EffectEvent
(effect [_ _ _]
(dom/focus! (dom/get-element (str "variant-prop-" variant-id "-0"))))))
;; Unit tests run without a DOM; skip rather than throw into the store.
(when (exists? js/document)
(some-> (dom/get-element (str "variant-prop-" variant-id "-0"))
(dom/focus!))))))
(defn- resposition-and-resize-variant
"Resize the variant container, and move the shape (that is a variant) to the right"
@ -468,9 +457,6 @@
(rx/of
(dwu/start-undo-transaction undo-id)
(when (not= name (:name main))
(dwl/rename-component component-id name))
;; Create the frame while it is still a normal board
(dwsh/create-artboard-from-shapes [main-instance-id] variant-id nil nil nil delta flex?)
(cl/remove-all-fills variant-vec {:color clr/black :opacity 1})

View File

@ -8,6 +8,7 @@
(:require
[app.common.files.changes-builder :as pcb]
[app.common.files.variant :as cfv]
[app.common.logic.variants :as clv]
[app.common.path-names :as cpn]
[app.common.test-helpers.components :as cthc]
[app.common.test-helpers.compositions :as tho]
@ -181,6 +182,20 @@
(tho/add-simple-component :c02 :m02 :r02
:root-params {:name "Beta" :x 150 :y 0 :width 100 :height 80})))
(defn- two-mains-shared-prefix-file
[]
(-> (cthf/sample-file :file1)
(tho/add-simple-component :c01 :m01 :r01
:root-params {:name "UI / Alpha" :x 0 :y 0 :width 100 :height 80})
(tho/add-simple-component :c02 :m02 :r02
:root-params {:name "UI / Beta" :x 150 :y 0 :width 100 :height 80})))
(defn- path-named-main-file
[]
(-> (cthf/sample-file :file1)
(tho/add-simple-component :c01 :m01 :r01
:root-params {:name "Group / Button"})))
(defn- two-mains-on-page1-with-page2
"Two mains on :page1; the open page is :page2 (Assets combine while elsewhere)."
[]
@ -201,6 +216,10 @@
(tho/add-simple-component :c01 :m01 :r01
:root-params {:parent-label :v01 :name "Set"})))
(defn- component-full-name
[component]
(cpn/merge-path-item (:path component) (:name component)))
(defn- page-objects
[file]
(:objects (cthf/current-page file)))
@ -258,7 +277,7 @@
changes (-> (pcb/empty-changes nil (:id page))
(pcb/with-objects (:objects page))
(pcb/with-library-data (:data file))
(dwv/generate-define-variant-container
(clv/generate-define-variant-container
variant-id main-id component-id name))
file' (cthf/apply-changes file changes)
container' (cths/get-shape file' :v01)
@ -271,7 +290,7 @@
(t/is (= variant-id (:variant-id comp')))
(t/is (some? (:variant-id comp')))
(t/is (= (ctv/properties-to-name (:variant-properties comp'))
(:variant-name main'))))))
(:variant-name main')))))
(t/deftest transform-in-variant-validates-after-every-commit
(t/async
@ -320,6 +339,54 @@
(t/is (= variant-id (:variant-id comp')))))
after-commits-idle))))
(t/deftest transform-in-variant-path-name-validates-after-every-commit
(t/async
done
(let [file (path-named-main-file)
main-id (cthi/id :m01)
variant-id (uuid/next)
snapshots (atom [])
store (ths/setup-store file)]
(watch-applied-commits store snapshots)
(ths/run-store
store
done
[(dwv/transform-in-variant main-id variant-id nil [] false true true)]
(fn [state]
(let [snaps @snapshots
flagged (first-flagged snaps variant-id)
file' (ths/get-file-from-state state)
objects' (page-objects file')
container' (get objects' variant-id)
main' (get objects' main-id)
comp' (cthc/get-component file' :c01)
before-flag (take-while
#(not (ctk/is-variant-container?
(get (page-objects (:file %)) variant-id)))
snaps)]
(assert-valid-snapshots snaps)
(t/is (some? flagged) "a commit must set :is-variant-container")
(doseq [snap before-flag]
(let [ffile (:file snap)
fmain (get (page-objects ffile) main-id)
fcomp (cthc/get-component ffile :c01)]
(t/is (= "Group / Button" (:name fmain)))
(t/is (= "Group / Button" (component-full-name fcomp)))))
(when flagged
(let [ffile (:file flagged)
fobjs (page-objects ffile)
fcont (get fobjs variant-id)
fmain (get fobjs main-id)
fcomp (cthc/get-component ffile :c01)]
(t/is (= "Group" (:name fcont) (:name fmain) (component-full-name fcomp)))
(t/is (= variant-id (:variant-id fmain) (:variant-id fcomp)))
(t/is (= (ctv/properties-to-name (:variant-properties fcomp))
(:variant-name fmain)))))
(t/is (ctk/is-variant-container? container'))
(t/is (= "Group" (:name container') (:name main') (component-full-name comp')))
(t/is (= variant-id (:variant-id main') (:variant-id comp')))))
after-commits-idle))))
(t/deftest add-new-variant-on-existing-set-still-validates
(t/async
done
@ -363,19 +430,37 @@
done
[(dwv/combine-as-variants [id1 id2] {:variant-id variant-id :trigger "test"})]
(fn [state]
(let [snaps @snapshots
file' (ths/get-file-from-state state)
objects (page-objects file')
container (get objects variant-id)
m01' (get objects id1)
m02' (get objects id2)
c01' (cthc/get-component file' :c01)
c02' (cthc/get-component file' :c02)
names1 (mapv :name (:variant-properties c01'))
names2 (mapv :name (:variant-properties c02'))
ordered (mapv :main-instance-id
(cfv/find-variant-components (:data file') objects variant-id))]
(let [snaps @snapshots
file' (ths/get-file-from-state state)
objects (page-objects file')
container (get objects variant-id)
m01' (get objects id1)
m02' (get objects id2)
c01' (cthc/get-component file' :c01)
c02' (cthc/get-component file' :c02)
names1 (mapv :name (:variant-properties c01'))
names2 (mapv :name (:variant-properties c02'))
ordered (mapv :main-instance-id
(cfv/find-variant-components (:data file') objects variant-id))
before-flag (take-while
#(not (ctk/is-variant-container?
(get (page-objects (:file %)) variant-id)))
snaps)
flagged (first-flagged snaps variant-id)]
(assert-valid-snapshots snaps)
(doseq [snap before-flag]
(let [ffile (:file snap)
fmain (get (page-objects ffile) id1)
fcomp (cthc/get-component ffile :c01)]
(t/is (= "Alpha" (:name fmain)))
(t/is (= "Alpha" (component-full-name fcomp)))))
(when flagged
(let [ffile (:file flagged)
fobjs (page-objects ffile)
fcont (get fobjs variant-id)
fmain (get fobjs id1)
fcomp (cthc/get-component ffile :c01)]
(t/is (= "Component" (:name fcont) (:name fmain) (component-full-name fcomp)))))
(doseq [{:keys [file]} snaps]
(let [objs (page-objects file)
cont (get objs variant-id)
@ -392,6 +477,53 @@
(t/is (= variant-id (:parent-id m02')))))
after-commits-idle))))
(t/deftest combine-as-variants-shared-prefix-validates-after-every-commit
(t/async
done
(let [file (two-mains-shared-prefix-file)
id1 (cthi/id :m01)
id2 (cthi/id :m02)
variant-id (uuid/next)
snapshots (atom [])
store (ths/setup-store file)]
(watch-applied-commits store snapshots)
(ths/run-store
store
done
[(dwv/combine-as-variants [id1 id2] {:variant-id variant-id :trigger "test"})]
(fn [state]
(let [snaps @snapshots
file' (ths/get-file-from-state state)
objects (page-objects file')
container (get objects variant-id)
m01' (get objects id1)
m02' (get objects id2)
c01' (cthc/get-component file' :c01)
c02' (cthc/get-component file' :c02)
flagged (first-flagged snaps variant-id)]
(assert-valid-snapshots snaps)
(doseq [{:keys [file]} snaps]
(let [objs (page-objects file)
cont (get objs variant-id)
kids (set (:shapes cont))]
(when (contains? kids id2)
(t/is (ctk/is-variant-container? cont)
"other mains must not enter the frame before it is a variant container"))))
(when flagged
(let [ffile (:file flagged)
fobjs (page-objects ffile)
fcont (get fobjs variant-id)
fmain (get fobjs id1)
fcomp (cthc/get-component ffile :c01)]
(t/is (= "UI" (:name fcont) (:name fmain) (component-full-name fcomp)))))
(t/is (ctk/is-variant-container? container))
(t/is (= "UI" (:name container) (:name m01') (:name m02')
(component-full-name c01') (component-full-name c02')))
(t/is (= variant-id (:variant-id m01') (:variant-id m02')
(:variant-id c01') (:variant-id c02')))
(t/is (= variant-id (:parent-id m01') (:parent-id m02')))))
after-commits-idle))))
(t/deftest combine-as-variants-from-another-page-validates-after-every-commit
(t/async
done