penpot/common/test/common_tests/logic/variants_test.cljc
Alonso Torres 4fb0ea9eee
🐛 Give unique values to same-name components combined as variants (#12881)
When components whose name matches the variant container are moved into
it, removing the container name leaves an empty value. Combining several
components named the same produced "Value 1" for the first variant and
empty values for the rest, so variants shared the same combination.

Assign the first unused "Value N" to the last property when all computed
values are blank, tracking values given within the same batch.

Combine moves the remaining mains into the container one at a time, in
the combine order, so the generated values follow that order instead of
the layer order.

Closes #12878
2026-11-10 09:16:08 +01:00

725 lines
30 KiB
Clojure

;; 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.variants-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.logic.variant-properties :as clvp]
[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]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-update-property-name
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-name v-id 0 "NewName1")
(clvp/generate-update-property-name v-id 1 "NewName2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp01' :variant-properties last :name) "NewName2"))
(t/is (= (-> comp02' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp02' :variant-properties last :name) "NewName2"))))
(t/deftest test-add-new-property-without-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) ""))))
(t/deftest test-add-new-property-with-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:fill-values? true}))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) "Value 1"))
(t/is (= (-> comp02' :variant-properties last :value) "Value 2"))))
(t/deftest test-add-new-property-with-the-same-value
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:property-value "Value 1"}))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) "Value 1"))
(t/is (= (-> comp02' :variant-properties last :value) "Value 1"))))
(t/deftest test-remove-property
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id))
file (thf/apply-changes file changes)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-remove-property v-id 0))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 2))
(t/is (= (count (:variant-properties comp01')) 1))
(t/is (= (count (:variant-properties comp02)) 2))
(t/is (= (count (:variant-properties comp02')) 1))
(t/is (= (-> comp01' :variant-properties first :name) "Property 2"))))
(t/deftest test-update-property-value
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 "NewValue1")
(clvp/generate-update-property-value (:id comp02) 0 "NewValue2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :value) "NewValue1"))
(t/is (= (-> comp02' :variant-properties first :value) "NewValue2"))))
(t/deftest test-update-property-value-sanitizes
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
before (-> comp01 :variant-properties first :value)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 "a=b,"))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
main' (ths/get-shape file' :m01)
noop (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 42))
file-noop (thf/apply-changes file noop)
comp-noop (thc/get-component file-noop :c01)
cleared (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 "=,="))
file-cleared (thf/apply-changes file cleared)
comp-cleared (thc/get-component file-cleared :c01)
main-cleared (ths/get-shape file-cleared :m01)
nilled (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 nil))
file-nilled (thf/apply-changes file nilled)
comp-nilled (thc/get-component file-nilled :c01)]
(t/is (= "ab" (-> comp01' :variant-properties first :value)))
(t/is (= "ab" (:variant-name main')))
(t/is (= before (-> comp-noop :variant-properties first :value)))
(t/is (= "" (-> comp-cleared :variant-properties first :value)))
(t/is (not (str/includes? (str (:variant-name main-cleared)) "=")))
(t/is (= "" (-> comp-nilled :variant-properties first :value)))))
(t/deftest test-update-property-name-sanitizes-and-noops
(let [file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
before (-> (thc/get-component file :c01) :variant-properties first :name)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-name v-id 0 "Size=Big"))
file' (thf/apply-changes file changes)
noop (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-name v-id 0 "=,="))
file-noop (thf/apply-changes file noop)]
(t/is (= "SizeBig" (-> (thc/get-component file' :c01) :variant-properties first :name)))
(t/is (= before (-> (thc/get-component file-noop :c01) :variant-properties first :name)))))
(t/deftest test-add-new-property-sanitizes
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
before-count (-> (thc/get-component file :c01) :variant-properties count)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:property-name "Size=Big"
:property-value "a=b,"}))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
noop (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:property-name "=,="}))
file-noop (thf/apply-changes file noop)]
(t/is (= "SizeBig" (-> comp01' :variant-properties last :name)))
(t/is (= "ab" (-> comp01' :variant-properties last :value)))
(t/is (= before-count
(-> (thc/get-component file-noop :c01) :variant-properties count)))))
(t/deftest test-add-new-property-blank-value-does-not-trail-separator
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
main (ths/get-shape file :m01)
before (:variant-name main)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-add-new-property v-id {:property-name "Size"
:property-value "=,="}))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
main' (ths/get-shape file' :m01)]
(t/is (= before (:variant-name main')))
(t/is (not (str/ends-with? (str (:variant-name main')) ", ")))
(t/is (= "" (-> comp01' :variant-properties last :value)))
(t/is (= "Size" (-> comp01' :variant-properties last :name)))))
(t/deftest test-update-property-value-from-formula-strips-equal-in-value
;; Option A / M2: formula parse may leave `=` inside a value; write path strips it.
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
parsed (ctv/properties-formula->map "border=yes color=yes")
value (-> parsed first :value)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-update-property-value (:id comp01) 0 value))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
main' (ths/get-shape file' :m01)]
(t/is (= "yes color=yes" value))
(t/is (= "yes coloryes" (-> comp01' :variant-properties first :value)))
(t/is (= "yes coloryes" (:variant-name main')))
(t/is (not= "yes color=yes" (-> comp01' :variant-properties first :value)))))
(t/deftest test-make-shapes-variant-sanitizes-component-name-as-value
;; create-new-properties-from-variant adds (:name component) as a value when
;; moving a variant into a differently named container. Production always
;; pairs generate-make-shapes-variant with change-parent.
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02)
(thv/add-variant :v02 :c03 :m03 :c04 :m04)
(thc/update-component :c03 {:name "Big=red"}))
page (thf/current-page file)
container (ths/get-shape file :v01)
shape (ths/get-shape file :m03)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-make-shapes-variant [shape] container)
(pcb/change-parent (:id container) [shape] 0))
file' (thf/apply-changes file changes)
values (->> (thc/get-component file' :c03)
:variant-properties
(map :value)
set)]
(t/is (contains? values "Bigred"))))
(t/deftest test-make-shapes-variant-same-name-components-get-unique-values
;; Mirrors combine-as-variants with components that share the container
;; name: the first main defines the container, the rest are moved into it.
(let [file (-> (thf/sample-file :file1)
(tho/add-frame :v01 {:name "Tag"})
(tho/add-simple-component :c01 :m01 :r01
:root-params {:parent-label :v01 :name "Tag"})
(tho/add-simple-component :c02 :m02 :r02
:root-params {:name "Tag"})
(tho/add-simple-component :c03 :m03 :r03
:root-params {:name "Tag"}))
page (thf/current-page file)
define (-> (pcb/empty-changes nil (:id page))
(pcb/with-objects (:objects page))
(pcb/with-library-data (:data file))
(clv/generate-define-variant-container
(thi/id :v01) (thi/id :m01) (thi/id :c01) "Tag"))
file (thf/apply-changes file define)
page (thf/current-page file)
container (ths/get-shape file :v01)
shapes [(ths/get-shape file :m02) (ths/get-shape file :m03)]
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-make-shapes-variant shapes container)
(pcb/change-parent (:id container) shapes 0))
file' (thf/apply-changes file changes)
values (mapv #(-> (thc/get-component file' %) :variant-properties first :value)
[:c01 :c02 :c03])]
(t/is (= ["Value 1" "Value 2" "Value 3"] values))
(t/is (= "Value 2" (:variant-name (ths/get-shape file' :m02))))
(t/is (= "Value 3" (:variant-name (ths/get-shape file' :m03))))))
(t/deftest test-duplicate-variant-container
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
variant-container (ths/get-shape file :v01)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(cll/generate-duplicate-changes objects ;; objects
page ;; page
#{(:id variant-container)} ;; ids
(gpt/point 0 0) ;; delta
{(:id file) file} ;; libraries
(:data file) ;; library-data
(:id file))) ;; file-id
;; ==== Get
file' (thf/apply-changes file changes)
data' (:data file')
page' (thf/current-page file')
objects' (:objects page')]
;; ==== Check
(thf/validate-file! file')
(t/is (= (count (:components data)) 2))
(t/is (= (count (:components data')) 4))
(t/is (= (count objects) 4))
(t/is (= (count objects') 7))))
(t/deftest test-delete-variant
;; When a variant container becomes empty, it id automatically deleted
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
container (ths/get-shape file :v01)
m01-id (-> (ths/get-shape file :m01) :id)
m02-id (-> (ths/get-shape file :m02) :id)
page (thf/current-page file)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(#(second (cls/generate-delete-shapes % #{m01-id m02-id} {}))))
file' (thf/apply-changes file changes)
;; ==== Get
container' (ths/get-shape file' :v01)]
;; ==== Check
;; The variant containew was not nil before the deletion
(t/is (not (nil? container)))
;; The variant containew is nil after the deletion
(t/is (nil? container'))))
(t/deftest test-instantiate-component-over-variant-container
;; When a component is instantiated at a position over a variant container,
;; the new copy must not become a child of the container (its children can
;; only be variant mains)
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
container (ths/get-shape file :v01)
page (thf/current-page file)
;; ==== Action
;; Instantiate at a position inside the variant container, without
;; an explicit parent, so the destiny frame is chosen by position
[new-shape changes]
(cll/generate-instantiate-component (-> (pcb/empty-changes nil (:id page))
(pcb/with-objects (:objects page)))
(:objects page)
(:id file)
(thi/id :c01)
(gpt/point (:x container) (:y container))
page
{(:id file) file})
file' (thf/apply-changes file changes)
;; ==== Get
new-shape' (ths/get-shape-by-id file' (:id new-shape))]
;; ==== Check
(thf/validate-file! file')
(t/is (some? new-shape'))
(t/is (not= (:parent-id new-shape') (:id container)))))
(defn- two-property-file-with-other-page
[]
(-> (thf/sample-file :file1 :page-label :page1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02)
(thf/add-sample-page :page2)))
(t/deftest test-update-property-value-skips-when-main-not-in-objects
(let [file (two-property-file-with-other-page)
other (thf/current-page file)
comp01 (thc/get-component file :c01)
main (ths/get-shape file :m01 :page-label :page1)
before (:variant-properties comp01)
label (:variant-name main)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id other))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects other))
(clvp/generate-update-property-value (:id comp01) 1 "crítica"))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
main' (ths/get-shape file' :m01 :page-label :page1)]
(thf/validate-file! file')
(t/is (empty? (:redo-changes changes)))
(t/is (= before (:variant-properties comp01')))
(t/is (= label (:variant-name main')))))
(t/deftest test-update-property-value-writes-main-page-and-chains
(let [file (two-property-file-with-other-page)
mains (thf/get-page file :page1)
comp01 (thc/get-component file :c01)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id mains))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects mains))
(clvp/generate-update-property-value (:id comp01) 0 "active")
(clvp/generate-update-property-value (:id comp01) 1 "crítica"))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
main' (ths/get-shape file' :m01 :page-label :page1)
props' (:variant-properties comp01')]
(thf/validate-file! file')
(t/is (= "active" (-> props' first :value)))
(t/is (= "crítica" (-> props' second :value)))
(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)]
(-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-rename-variant (thi/id :v01) name))))
(defn- redo-ids
[changes change-type]
(into #{}
(comp (filter #(= change-type (:type %)))
(map :id))
(:redo-changes changes)))
(t/deftest test-rename-variant-one-vector
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
board (ths/get-shape file :v01)
main1 (ths/get-shape file :m01)
main2 (ths/get-shape file :m02)
comp1 (thc/get-component file :c01)
comp2 (thc/get-component file :c02)
changes (rename-variant-changes file "Group / Button")
file' (thf/apply-changes file changes)
board' (ths/get-shape file' :v01)
main1' (ths/get-shape file' :m01)
main2' (ths/get-shape file' :m02)
comp1' (thc/get-component file' :c01)
comp2' (thc/get-component file' :c02)
restored (thf/apply-undo-changes file' changes)]
(t/is (= #{(thi/id :v01) (thi/id :m01) (thi/id :m02)}
(redo-ids changes :mod-obj)))
(t/is (= #{(thi/id :c01) (thi/id :c02)}
(redo-ids changes :mod-component)))
(t/is (= "Group / Button" (:name board') (:name main1') (:name main2')))
(t/is (= "Group" (:path comp1') (:path comp2')))
(t/is (= "Button" (:name comp1') (:name comp2')))
(t/is (= "Group / Button"
(cpn/merge-path-item (:path comp1') (:name comp1'))
(cpn/merge-path-item (:path comp2') (:name comp2'))))
(t/is (= (:id board) (:variant-id comp1') (:variant-id comp2')))
(t/is (= (:variant-name main1) (:variant-name main1')))
(t/is (= (:variant-name main2) (:variant-name main2')))
(t/is (= (:variant-properties comp1) (:variant-properties comp1')))
(t/is (= (:variant-properties comp2) (:variant-properties comp2')))
(t/is (= (:name board) (:name (ths/get-shape restored :v01))))
(t/is (= (:name main1) (:name (ths/get-shape restored :m01))))
(t/is (= (:name main2) (:name (ths/get-shape restored :m02))))
(t/is (= (:path comp1) (:path (thc/get-component restored :c01))))
(t/is (= (:name comp1) (:name (thc/get-component restored :c01))))
(t/is (= (:path comp2) (:path (thc/get-component restored :c02))))
(t/is (= (:name comp2) (:name (thc/get-component restored :c02))))))
(t/deftest test-rename-variant-slash-only-name
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
changes (rename-variant-changes file "///")
file' (thf/apply-changes file changes)
comp1' (thc/get-component file' :c01)
comp2' (thc/get-component file' :c02)]
(t/is (= "(empty)"
(:name (ths/get-shape file' :v01))
(:name (ths/get-shape file' :m01))
(:name (ths/get-shape file' :m02))
(:name comp1')
(:name comp2')))
(t/is (= "" (:path comp1') (:path comp2')))
(t/is (= "(empty)"
(cpn/merge-path-item (:path comp1') (:name comp1'))
(cpn/merge-path-item (:path comp2') (:name comp2'))))
(t/is (some? (:variant-id comp1')))
(t/is (some? (:variant-id comp2')))))