🔧 Add tests and validations to variant functions

This commit is contained in:
Andrés Moya 2026-09-08 09:49:32 +02:00
parent 0cb10a55e2
commit 5c80841143
18 changed files with 1486 additions and 400 deletions

View File

@ -1113,12 +1113,13 @@
#?(:cljs
(defmethod process-change :validate-shapes
[data {:keys [page-id shape-ids context]} libraries]
(if libraries
(when libraries
(println "Validating shapes: \n"
" file-id:" (str (:id data)) "\n"
" page-id:" (str page-id) "\n"
" shape-ids:" (str shape-ids) "\n"
" context:" context)
(let [file {:data data :id uuid/zero}
(let [file {:id (:id data) :data data}
errors (reduce (fn [acc shape-id]
(if-let [page (ctpl/get-page data page-id)]
(let [page-errors (val/validate-shape shape-id file page libraries)]
@ -1132,8 +1133,7 @@
(ex/raise :type :validation
:code :referential-integrity
:hint (str "error on validating shapes: " context)
:details errors))
data))
:details errors))))
data))
;; === Operations

View File

@ -56,6 +56,10 @@
:undo-changes '()
:origin origin}))
(defn empty-changes?
[changes]
(empty? (:redo-changes changes)))
(defn set-save-undo?
[changes save-undo?]
(assoc changes :save-undo? save-undo?))
@ -1216,18 +1220,22 @@
(defn get-library-data
[changes]
(assert-library! changes)
(::library-data (meta changes)))
(defn get-objects
[changes]
(assert-objects! changes)
(dm/get-in (::file-data (meta changes)) [:pages-index uuid/zero :objects]))
(defn get-page
[changes]
(assert-page! changes)
(::page (meta changes)))
(defn get-page-id
[changes]
(assert-page-id! changes)
(::page-id (meta changes)))
(defn set-text-content

View File

@ -10,7 +10,6 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.files.variant :as cfv]
[app.common.path-names :as cpn]
[app.common.schema :as sm]
[app.common.types.component :as ctk]
@ -271,6 +270,8 @@
"Shape expected to be main instance"
shape file page))
(when-not (= (:component-file shape) (:id file))
(prn "shape" (:id shape) (:name shape) (:component-file shape))
(prn "file" (:id file) (:name file))
(report-error :component-main-external
"Main instance should refer to a component in the same file"
shape file page))
@ -570,7 +571,17 @@
objects (:objects page)
file-data (:data file)
first-child (get objects (first shapes))
prop-names (cfv/extract-properties-names first-child file-data)]
extract-properties-names
(fn [shape]
;; Get the names of the properties of the shape's component
(->> shape
(#(ctkl/get-component file-data (:component-id %) true))
:variant-properties
(map :name)))
prop-names (extract-properties-names first-child)]
(run! (fn [child-id]
(when-let [child (get objects child-id)]
(if (not (ctk/is-variant? child))
@ -584,7 +595,7 @@
(str/ffmt "Main instance in variant % should have the variant-id of the container but has %" (:id child) (:variant-id child))
child file page
:variant-id shape-id))
(when (not= prop-names (cfv/extract-properties-names child file-data))
(when (not= prop-names (extract-properties-names child))
(report-error :invalid-variant-properties
(str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names))
child file page

View File

@ -6,12 +6,15 @@
(ns app.common.files.variant
(:require
[app.common.data.macros :as dm]
[app.common.types.component :as ctc]
[app.common.types.components-list :as ctcl]
[app.common.types.components-list :as ctkl]
[app.common.types.variant :as ctv]))
(defn find-variant-components
"Find a list of the components that belongs to this variant-id"
"Find the components that belong to the variant container identified by `variant-id`,
preserving the order defined by the container's shapes.
Example return:
(<component1> <component2> ...)"
([data variant-id]
(let [page-id (->> data
:components
@ -22,22 +25,24 @@
objects (dm/get-in data [:pages-index page-id :objects])]
(find-variant-components data objects variant-id)))
([data objects variant-id]
(assert (or (uuid? variant-id) (nil? variant-id)))
;; We can't simply filter components, because we need to maintain the order
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))
reverse)))
(defn extract-properties-names
[shape data]
(->> shape
(#(ctcl/get-component data (:component-id %) true))
:variant-properties
(map :name)))
(let [container (get objects variant-id)]
(if (ctv/variant-container? container)
(->> (:shapes container)
(map #(dm/get-in objects [% :component-id]))
(map #(ctkl/get-component data % true))
reverse)
[]))))
(defn extract-properties-values
"Get a map of properties associated to their possible values"
"Get a map of variant property names to their distinct possible values,
collected from all components that belong to the variant container.
Example return:
[{:name 'Property 1' :value ('Value1' 'Value2')}]"
[data objects variant-id]
(assert (or (uuid? variant-id) (nil? variant-id)))
(->> (find-variant-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
@ -47,9 +52,13 @@
:value (->> v (map :value) distinct)}
mdata))))))
(defn get-variant-mains
[component data]
(assert (ctv/valid-variant-component? component) "expected valid component variant")
(defn- get-variant-mains
"Return the ids of the main instance shapes of the variant this component belongs to,
in the order they appear in the container.
Example return:
[<main-shape-a-id> <main-shape-b-id>]"
[data component]
(when-let [variant-id (:variant-id component)]
(let [page-id (:main-instance-page component)
objects (-> (dm/get-in data [:pages-index page-id])
@ -57,27 +66,20 @@
(dm/get-in objects [variant-id :shapes]))))
(defn is-secondary-variant?
[component data]
(let [shapes (get-variant-mains component data)]
"Return true if the component is a secondary variant in its variant container.
The primary variant is the last one in the container's children list.
Return false if the component is the primary variant or if it's not part of a variant."
[data component]
(let [shapes (get-variant-mains data component)]
(and (seq shapes)
(not= (:main-instance-id component) (last shapes)))))
(defn get-primary-variant
"Return the main instance of the primary variant (the last one) in the variant container."
[data component]
(let [page-id (:main-instance-page component)
objects (-> (dm/get-in data [:pages-index page-id])
(get :objects))
variant-id (:variant-id component)]
(->> (dm/get-in objects [variant-id :shapes])
(let [page-id (:main-instance-page component)
objects (-> (dm/get-in data [:pages-index page-id])
(get :objects))]
(->> (get-variant-mains data component)
peek
(get objects))))
(defn get-primary-component
[data component-id]
(when-let [component (ctcl/get-component data component-id)]
(if (ctc/is-variant? component)
(->> component
(get-primary-variant data)
:component-id
(ctcl/get-component data))
component)))

View File

@ -15,92 +15,145 @@
[cuerdas.core :as str]))
(defn generate-update-property-name
"Update the name of a variant property at position `pos` for all components in the variant.
The new name may be added a suffix to ensure it is unique among the other property names."
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)
props (-> related-components last :variant-properties)
prop-names (mapv :name props)
prop-names (concat (subvec prop-names 0 pos) (subvec prop-names (inc pos)))
new-name (ctv/update-number-in-repeated-item prop-names new-name)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
(fn [component]
(d/update-in-when component [:variant-properties pos] #(assoc % :name new-name)))
{:apply-changes-local-library? true}))
(if (or (nil? variant-id) (nil? pos))
changes
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
(if (empty? related-components)
changes
(let [props (-> related-components last :variant-properties)
num-props (count props)]
(if (or (< pos 0) (>= pos num-props))
changes
related-components)))
(let [prop-names (mapv :name props)
prop-names (concat (subvec prop-names 0 pos) (subvec prop-names (inc pos)))
new-name (ctv/update-number-in-repeated-item prop-names new-name)
changes (reduce (fn [changes component]
(pcb/update-component
changes (:id component)
(fn [component]
(d/update-in-when component [:variant-properties pos] #(assoc % :name new-name)))
{:apply-changes-local-library? true}))
changes
related-components)
ids-to-validate (map :main-instance-id related-components)
changes (pcb/validate-shapes changes
(pcb/get-page-id changes)
ids-to-validate
(str "generate-update-property-name: " variant-id
" pos: " pos " new-name: " new-name))]
changes)))))))
(defn generate-remove-property
"Remove the variant property at position `pos` for all components in the variant and their
respective main instances."
[changes variant-id pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)
props (-> related-components first :variant-properties)]
(if (and (seq props) (<= 0 pos) (< pos (count props)))
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (ctv/properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)
changes)))
(if (nil? pos)
changes
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)
props (-> related-components first :variant-properties)]
(if (and (seq props) (<= 0 pos) (< pos (count props)))
(let [changes (reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (ctv/properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)
ids-to-validate (map :main-instance-id related-components)
changes (pcb/validate-shapes changes
(pcb/get-page-id changes)
ids-to-validate
(str "generate-remove-property: " variant-id " pos: " pos))]
changes)
changes))))
(defn generate-update-property-value
"Update the value of the variant property at position 'pos' for the given component and its main instance."
[changes component-id pos value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
name (-> (:variant-properties component)
(update pos assoc :value value)
ctv/properties-to-name)]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
(if (or (nil? component-id) (nil? pos))
changes
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)]
(if (and component (< pos (count (:variant-properties component))))
(let [main-id (:main-instance-id component)
props (:variant-properties component)
name (-> props
(update pos assoc :value value)
ctv/properties-to-name)]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name))))
changes))))
(defn generate-set-variant-error
"Set an invalid property value as a :variant-error attribute in the main instance of the given component.
If value is nil, remove the :variant-error attribute."
[changes component-id value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)]
(-> changes
(pcb/update-shapes [main-id] (if (nil? value)
#(dissoc % :variant-error)
#(assoc % :variant-error value))))))
(if (nil? component-id)
changes
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)]
(if component
(let [main-id (:main-instance-id component)]
(-> changes
(pcb/update-shapes [main-id] (if (nil? value)
#(dissoc % :variant-error)
#(assoc % :variant-error value)))))
changes))))
(defn generate-reorder-variant-poperties
"Change the order of the variant properties for all components in the variant.
Move the property at the given position to some space between positions.
Clamp the positions to the valid range of property indexes."
[changes variant-id from-pos to-space-between-pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/reorder props from-pos to-space-between-pos)
main-id (:main-instance-id component)
name (ctv/properties-to-name props)]
(-> changes
(pcb/update-component (:id component)
#(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id]
#(assoc % :variant-name name)))))
changes
related-components)))
(if (or (nil? variant-id) (nil? from-pos) (nil? to-space-between-pos))
changes
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (cfv/find-variant-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props' (d/reorder props from-pos to-space-between-pos)
main-id (:main-instance-id component)
name (ctv/properties-to-name props')]
(if (not= props props')
(-> changes
(pcb/update-component (:id component)
#(assoc % :variant-properties props')
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id]
#(assoc % :variant-name name)))
changes)))
changes
related-components))))
(defn generate-add-new-property
"Add a new variant property to all components in the variant and their respective main instances.
The new property name may be given or it will be generated based on the next available property
number. In any case, it may be modified to be unique among the other property names.
The value may also be given, or else it will be an empty value, unless fill-values? is true, in
which case the value will be set to a default value based on the property number.
The editing? flag, if set, will be added to the metadata of the properties, for later use."
[changes variant-id & {:keys [fill-values? editing? property-name property-value]}]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
@ -158,10 +211,11 @@
(assoc :name new-name))))))
(defn generate-make-shapes-no-variant
"Extract some components from a variant, removing the variant-id and variant-name from the
main instances and the variant-id and variant-properties from the components."
[changes shapes]
(reduce generate-make-shape-no-variant changes shapes))
(defn- create-new-properties-from-variant
[shape min-props data container-name base-properties]
(let [component (ctcl/get-component data (:component-id shape) true)
@ -184,8 +238,9 @@
shape-name (ctv/remove-prefix (:name shape) container-name)]
(ctv/path-to-properties shape-name base-properties min-props)))
(defn generate-make-shapes-variant
"Introduce some components into a variant, adding the variant-id and variant-name to the
main instances and the variant-id and variant-properties to the components."
[changes shapes variant-container]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)

View File

@ -78,17 +78,41 @@
[properties]
(> (count properties) 1))
(defn variant-component?
[component]
(some? (:variant-id component)))
(defn variant-shape?
[shape]
(some? (:variant-id shape)))
(defn variant-container?
[shape]
(some? (:is-variant-container shape)))
(defn properties-to-name
"Transform the properties into a name, with the values separated by comma"
"Transform the properties into a name, with the values separated by comma, excluding the empty ones.
Example:
[{:name 'Property 1' :value 'Button'}
{:name 'Property 2' :value 'Primary'}] -> 'Button, Primary'"
[properties]
(assert (or (sequential? properties) (nil? properties)))
(->> properties
(map :value)
(remove str/empty?)
(str/join ", ")))
(defn next-property-number
"Returns the next property number, to avoid duplicates on the property names"
"Returns the next property number, to avoid duplicates on the property names.
Example:
[{:name 'Property 1' :value 'x'}
{:name 'Property 3' :value 'y'}] -> 4"
[properties]
(assert (or (sequential? properties) (nil? properties)))
(let [numbers (keep
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
properties)
@ -98,14 +122,29 @@
(inc (max max-num (count properties)))))
(defn add-new-property
"Adds a new property with generated name and provided value to the existing properties list."
"Adds a new property with generated name and provided value to the existing properties list.
Example:
[{:name 'Property 1' :value 'x'}] 'y' -> [{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}]"
[properties value]
(assert (or (sequential? properties) (nil? properties)))
(assert (or (string? value) (nil? value)))
(conj properties {:name (str property-prefix (next-property-number properties))
:value value}))
:value value}))
(defn add-new-properties
"Adds new properties with generated names and provided values to the existing properties list."
"Adds new properties with generated names and provided values to the existing properties list.
Example:
[{:name 'Property 1' :value 'x'}] ['a' 'b'] -> [{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'a'}
{:name 'Property 3' :value 'b'}]"
[properties values]
(assert (or (sequential? properties) (nil? properties)))
(assert (or (sequential? values) (nil? values)))
(let [next-prop-num (next-property-number properties)
xf (map-indexed (fn [i v]
{:name (str property-prefix (+ next-prop-num i))
@ -114,10 +153,21 @@
(defn path-to-properties
"From a list of properties and a name with path, assign each token of the
path as value of a different property"
path as value of a different property. It can add blank properties if
necessary, until the min-properties number is reached.
Example with min-properties=4:
'Button / Primary / Hover' -> [{:name 'Property 1' :value 'Button'}
{:name 'Property 2' :value 'Primary'}
{:name 'Property 3' :value 'Hover'}
{:name 'Property 4' :value ''}]"
([path properties]
(path-to-properties path properties 0))
([path properties min-properties]
(assert (or (string? path) (nil? path)))
(assert (or (sequential? properties) (nil? properties)))
(assert (int? min-properties))
(let [cpath (cpn/split-path path)
total-properties (max (count cpath) min-properties)
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
@ -127,8 +177,14 @@
(add-new-properties assigned remaining))))
(defn properties-map->formula
"Transforms a map of properties to a formula of properties omitting the empty ones"
"Transforms a map of properties to a formula of properties omitting the empty ones.
Example:
[{:name 'Property 1' :value 'Button'}
{:name 'Property 2' :value 'Primary'}] -> 'Property 1=Button, Property 2=Primary'"
[properties]
(assert (or (sequential? properties) (nil? properties)))
(->> properties
(keep (fn [{:keys [name value]}]
(when (not (str/blank? value))
@ -136,9 +192,15 @@
(str/join ", ")))
(defn properties-formula->map
"Transforms a formula of properties to a map of properties"
[s]
(->> (str/split s ",")
"Transforms a formula of properties to a map of properties.
Example:
'Property 1=Button, Property 2=Primary' -> [{:name 'Property 1' :value 'Button'}
{:name 'Property 2' :value 'Primary'}]"
[formula]
(assert (or (string? formula) (nil? formula)))
(->> (str/split formula ",")
(mapv #(str/split % "=" 2))
(filter (fn [[_ v]] (not (str/blank? v))))
(mapv (fn [[k v]]
@ -146,9 +208,15 @@
:value (str/trim v)}))))
(defn valid-properties-formula?
"Checks if a formula is valid"
[s]
(->> (str/split s ",")
"Checks if a formula is valid.
Example:
'Property 1=Button, Property 2=Primary' -> true
'Property 1=Button, Property 2' -> false"
[formula]
(assert (or (string? formula) (nil? formula)))
(->> (str/split formula ",")
(mapv #(str/split % "=" 2))
(every? #(and (= 2 (count %))
(not (str/blank? (first %)))
@ -156,20 +224,45 @@
(<= (count (second %)) property-max-length)))))
(defn find-properties-to-remove
"Compares two property maps to find which properties should be removed"
"Compares two property maps to find which properties should be removed.
Example:
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}]
[{:name 'Property 1' :value 'x'}] -> [{:name 'Property 2' :value 'y'}]"
[prev-properties upd-properties]
(assert (or (sequential? prev-properties) (nil? prev-properties)))
(assert (or (sequential? upd-properties) (nil? upd-properties)))
(let [upd-names (set (map :name upd-properties))]
(filterv #(not (contains? upd-names (:name %))) prev-properties)))
(defn find-properties-to-update
"Compares two property maps to find which properties should be updated"
"Compares two property maps to find which properties should be updated.
Example:
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}]
[{:name 'Property 1' :value 'new-x'}
{:name 'Property 2' :value 'y'}] -> [{:name 'Property 1' :value 'new-x'}]"
[prev-properties upd-properties]
(assert (or (sequential? prev-properties) (nil? prev-properties)))
(assert (or (sequential? upd-properties) (nil? upd-properties)))
(filterv #(some (fn [prop] (and (= (:name %) (:name prop))
(not= (:value %) (:value prop)))) prev-properties) upd-properties))
(defn find-properties-to-add
"Compares two property maps to find which properties should be added"
"Compares two property maps to find which properties should be added.
Example:
[{:name 'Property 1' :value 'x'}]
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}] -> [{:name 'Property 2' :value 'y'}]"
[prev-properties upd-properties]
(assert (or (sequential? prev-properties) (nil? prev-properties)))
(assert (or (sequential? upd-properties) (nil? upd-properties)))
(let [prev-names (set (map :name prev-properties))]
(filterv #(not (contains? prev-names (:name %))) upd-properties)))
@ -193,8 +286,15 @@
(defn update-number-in-repeated-item
"Add, keep or update a number in parentheses for a given item, if necessary, depending on the items
already present in a list, to avoid repetitions"
already present in a list, to avoid repetitions.
Example:
['Property'] 'Property' -> 'Property (1)'
['Property' 'Property (1)'] 'Property' -> 'Property (2)'"
[items item]
(assert (or (sequential? items) (nil? items)))
(assert (or (string? item) (nil? item)))
(let [names (group-numbers-by-base-name items)
[base num] (split-base-name-and-number item)
nums-taken (get names base #{})]
@ -204,8 +304,15 @@
(str base (when (pos? n) (str " (" n ")")))))))
(defn update-number-in-repeated-prop-names
"Add, keep or update a number for each prop name depending on the previous ones"
"Add, keep or update a number for each prop name depending on the previous ones.
Example:
[{:name 'Property' :value 'x'}
{:name 'Property' :value 'y'}] -> [{:name 'Property' :value 'x'}
{:name 'Property (1)' :value 'y'}]"
[properties]
(assert (or (sequential? properties) (nil? properties)))
(->> properties
(reduce (fn [acc prop]
(conj acc {:name (update-number-in-repeated-item (mapv :name acc) (:name prop))
@ -213,16 +320,30 @@
[])))
(defn find-index-for-property-name
"Finds the index of a name in a property map"
"Finds the index of a name in a property map.
Example:
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}] 'Property 2' -> 1"
[properties name]
(assert (or (sequential? properties) (nil? properties)))
(assert (or (string? name) (nil? name)))
(some (fn [[idx prop]]
(when (= (:name prop) name)
idx))
(map-indexed vector properties)))
(defn remove-prefix
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name"
"Removes the given prefix (with or without a trailing ' / ') from the beginning of the name.
Example:
'Button / Primary' 'Button' -> 'Primary'
'Button / Primary' 'Other' -> 'Button / Primary'"
[name prefix]
(assert (or (string? name) (nil? name)))
(assert (or (string? prefix) (nil? prefix)))
(let [long-name (str prefix " / ")]
(cond
(str/starts-with? name long-name)
@ -278,8 +399,20 @@
- Otherwise, assign p2's value to the first unused property in properties1. A property is considered used if:
- Its name exists in both properties1 and properties2, or
- Its value has already been updated during the merge.
- If no unused properties are available in properties1, append a new property with a default name and p2's value."
- If no unused properties are available in properties1, append a new property with a default name and p2's value.
Example:
[{:name 'Property 1' :value 'a'}
{:name 'Property 2' :value 'b'}]
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}
{:name 'Property 3' :value 'z'}] -> [{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}
{:name 'Property 3' :value 'z'}]"
[properties1 properties2]
(assert (or (sequential? properties1) (nil? properties1)))
(assert (or (sequential? properties2) (nil? properties2)))
(let [properties2 (remove #(str/empty? (:value %)) properties2)]
(-> (reduce
(fn [{:keys [properties used-pos]} prop]
@ -294,11 +427,21 @@
(defn compare-properties
"Compares vectors of properties keeping the value if it is the same for all
or setting a custom value where their values do not coincide"
or setting a custom value where their values do not coincide.
Example:
[[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'y'}]
[{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value 'z'}]] -> [{:name 'Property 1' :value 'x'}
{:name 'Property 2' :value nil}]"
([properties-list]
(compare-properties properties-list nil))
([properties-list distinct-mark]
(assert (or (sequential? properties-list) (nil? properties-list)))
(assert (or (string? distinct-mark) (nil? distinct-mark)))
(let [grouped (group-by :name (apply concat properties-list))
check-values (fn [values]
(let [vals (map :value values)]
@ -309,19 +452,19 @@
{:name name :value (check-values values)})
grouped))))
(defn same-variant?
"Determines if all elements belong to the same variant"
[components]
(let [variant-ids (distinct (map :variant-id components))
not-blank? (complement str/blank?)]
(and
(= 1 (count variant-ids))
(not-blank? (first variant-ids)))))
(defn distance
(defn properties-distance
"Computes a weighted distance between two property lists `properties1` and `properties2`.
Latter properties weight less that previous ones"
Latter properties weight less that previous ones.
Example:
[{:name 'type' :value 'primary'}
{:name 'status' :value 'default'}]
[{:name 'type' :value 'primary'}
{:name 'status' :value 'hover'}] -> 1.0"
[properties1 properties2]
(assert (or (sequential? properties1) (nil? properties1)))
(assert (or (sequential? properties2) (nil? properties2)))
(let [total-num-properties (count properties1)
xform (map-indexed
(fn [idx [p1 p2]]
@ -335,7 +478,11 @@
(defn variant-name-to-name
"Transforms a variant-name (its properties values) into a standard name:
the real name of the shape joined by the properties values separated by '/'"
the real name of the shape joined by the properties values separated by '/'.
Example:
{:name 'Button' :variant-name 'Primary, Hover'} -> 'Button / Primary / Hover'"
[variant]
(cpn/merge-path-item (:name variant) (str/replace (:variant-name variant) #", " " / ")))
@ -345,8 +492,13 @@
["true" "false"]])
(defn find-boolean-pair
"Given a vector, return a map that contains the boolean equivalency if the values match
with any of the boolean pairs. Returns nil if none match."
"Given a collection, return a map that contains the boolean equivalency if the values match
with any of the boolean pairs. Returns nil if none match.
Example:
['on' 'off'] -> {'on' true 'off' false}
['foo' 'bar'] -> nil"
[[a b :as v]]
(let [a' (-> a str/trim str/lower)
b' (-> b str/trim str/lower)]
@ -358,3 +510,18 @@
(= a' f)) {b true a false}
:else nil))
boolean-pairs))))
(defn same-variant?
"Determines if all elements belong to the same variant.
Example:
[{:variant-id 'abc'} {:variant-id 'abc'}] -> true
[{:variant-id 'abc'} {:variant-id 'def'}] -> false"
[components]
(assert (or (sequential? components) (nil? components)))
(let [variant-ids (distinct (map :variant-id components))
not-blank? (complement str/blank?)]
(and
(= 1 (count variant-ids))
(not-blank? (first variant-ids)))))

View File

@ -0,0 +1,165 @@
;; 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.variant-test
(:require
[app.common.files.variant :as fv]
[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.variants :as thv]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
;; ============================================================
;; find-variant-components
;; ============================================================
(t/deftest find-variant-components-empty
(let [file (thf/sample-file :file1)
data (:data file)
page (thf/current-page file)
objects (:objects page)]
(t/is (= (fv/find-variant-components data (uuid/next))
[]))
(t/is (= (fv/find-variant-components data objects (uuid/next))
[]))))
(t/deftest find-variant-components-non-variant
(let [file (-> (thf/sample-file :file1)
(tho/add-simple-component :c01 :m01 :s01))
data (:data file)
page (thf/current-page file)
objects (:objects page)]
(t/is (= (fv/find-variant-components data (thi/id :m01))
[]))
(t/is (= (fv/find-variant-components data objects (thi/id :m01))
[]))))
(t/deftest find-variant-components-normal
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
result (fv/find-variant-components data objects (thi/id :v01))]
(t/is (= (count result) 2))
(t/is (every? #(contains? % :id) result))
(t/is (every? #(contains? % :variant-id) result))))
(t/deftest find-variant-components-single-variant
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
result (fv/find-variant-components data objects (thi/id :v01))]
;; Verify the order is maintained (reversed from shapes order)
(t/is (= (:variant-id (first result)) (thi/id :v01)))
(t/is (= (:variant-id (second result)) (thi/id :v01)))))
;; ============================================================
;; extract-properties-values
;; ============================================================
(t/deftest extract-properties-values-empty
(let [file (thf/sample-file :file1)
data (:data file)
page (thf/current-page file)
objects (:objects page)]
(t/is (= (fv/extract-properties-values data objects (uuid/next))
[]))))
(t/deftest extract-properties-values-non-variant
(let [file (-> (thf/sample-file :file1)
(tho/add-simple-component :c01 :m01 :s01))
data (:data file)
page (thf/current-page file)
objects (:objects page)]
(t/is (= (fv/extract-properties-values data objects (thi/id :m01))
[]))))
(t/deftest extract-properties-values-normal
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
result (fv/extract-properties-values data objects (thi/id :v01))]
(t/is (seq result))
(t/is (every? #(contains? % :name) result))
(t/is (every? #(contains? % :value) result))
(t/is (= (:name (first result)) "Property 1"))
(t/is (= (set (:value (first result))) #{"Value1" "Value2"}))))
(t/deftest extract-properties-values-two-properties
(let [file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
data (:data file)
page (thf/current-page file)
objects (:objects page)
result (fv/extract-properties-values data objects (thi/id :v01))]
(t/is (= (count result) 2))
(t/is (= (set (map :name result)) #{"Property 1" "Property 2"}))))
;; ============================================================
;; is-secondary-variant?
;; ============================================================
(t/deftest is-secondary-variant-primary
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
component (thc/get-component file :c01)]
(t/is (not (fv/is-secondary-variant? data component)))))
(t/deftest is-secondary-variant-secondary
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
component (thc/get-component file :c02)]
(t/is (fv/is-secondary-variant? data component))))
(t/deftest is-secondary-variant-not-variant
(let [file (-> (thf/sample-file :file1)
(tho/add-simple-component :c01 :m01 :s01))
data (:data file)
component (thc/get-component file :c01)]
(t/is (not (fv/is-secondary-variant? data component)))))
(t/deftest is-secondary-variant-no-shapes
(let [file (thf/sample-file :file1)
data (:data file)
component {:id :comp :variant-id (thi/id :file1) :main-instance-page (thi/id :file1)}]
(t/is (not (fv/is-secondary-variant? data component)))))
;; ============================================================
;; get-primary-variant
;; ============================================================
(t/deftest get-primary-variant-nil
(let [file (thf/sample-file :file1)
data (:data file)]
(t/is (nil? (fv/get-primary-variant data nil)))))
(t/deftest get-primary-variant-empty
(let [file (thf/sample-file :file1)
data (:data file)
component {:id :comp :variant-id (thi/id :file1) :main-instance-page (thi/id :file1)}]
(t/is (nil? (fv/get-primary-variant data component)))))
(t/deftest get-primary-variant-normal
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
data (:data file)
component (thc/get-component file :c01)
result (fv/get-primary-variant data component)]
(t/is (some? result))
(t/is (contains? result :id))
(t/is (contains? result :component-id))))

View File

@ -424,7 +424,9 @@
file-deleted (thf/apply-changes file changes)
page-deleted (thf/current-page file-deleted)
changes (cll/generate-restore-component (pcb/empty-changes)
changes (cll/generate-restore-component (-> (pcb/empty-changes)
(pcb/with-library-data (:data file-deleted))
(pcb/with-objects (:objects page-deleted)))
(:data file-deleted)
(thi/id :component1)
(:id file-deleted)

View File

@ -0,0 +1,657 @@
;; 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.variant-properties-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.variant-properties :as clvp]
[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.uuid :as uuid]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
;; =============================================================================
;; generate-update-property-name tests
;; =============================================================================
(t/deftest test-update-property-name-no-op
(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)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; variant-id nil
(let [changes (clvp/generate-update-property-name base-changes nil 0 "NewName")]
(t/is (pcb/empty-changes? changes)))
;; variant-id non-existent
(let [changes (clvp/generate-update-property-name base-changes (uuid/next) 0 "NewName")]
(t/is (pcb/empty-changes? changes)))
;; pos nil
(let [changes (clvp/generate-update-property-name base-changes v-id nil "NewName")]
(t/is (pcb/empty-changes? changes)))
;; pos out of range (negative)
(let [changes (clvp/generate-update-property-name base-changes v-id -1 "NewName")]
(t/is (pcb/empty-changes? changes)))
;; pos out of range (too large)
(let [changes (clvp/generate-update-property-name base-changes v-id 100 "NewName")]
(t/is (pcb/empty-changes? changes)))))
(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-update-property-name-duplicate
(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)
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 "Property 2"))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
(t/is (= "Property 2 (1)" (-> comp01' :variant-properties first :name)))
(t/is (= "Property 2" (-> comp01' :variant-properties last :name)))
(t/is (= "Property 2 (1)" (-> comp02' :variant-properties first :name)))
(t/is (= "Property 2" (-> comp02' :variant-properties last :name)))))
;; =============================================================================
;; generate-remove-property tests
;; =============================================================================
(t/deftest test-remove-property-no-op
(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)
comp01 (thc/get-component file :c01)
orig-names (mapv :name (:variant-properties comp01))
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; variant-id nil
(let [changes (clvp/generate-remove-property base-changes nil 0)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= orig-names (mapv :name (:variant-properties comp01')))))
;; variant-id non-existent
(let [changes (clvp/generate-remove-property base-changes (uuid/next) 0)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= orig-names (mapv :name (:variant-properties comp01')))))
;; pos nil
(let [changes (clvp/generate-remove-property base-changes v-id nil)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= orig-names (mapv :name (:variant-properties comp01')))))
;; pos out of range (negative)
(let [changes (clvp/generate-remove-property base-changes v-id -1)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= orig-names (mapv :name (:variant-properties comp01')))))
;; pos out of range (too large)
(let [changes (clvp/generate-remove-property base-changes v-id 100)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= orig-names (mapv :name (:variant-properties comp01')))))))
(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"))))
;; =============================================================================
;; generate-update-property-value tests
;; =============================================================================
(t/deftest test-update-property-value-no-op
(let [file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
c01-id (:id comp01)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; component-id nil
(let [changes (clvp/generate-update-property-value base-changes nil 0 "NewVal")]
(t/is (pcb/empty-changes? changes)))
;; component-id non-existent
(let [changes (clvp/generate-update-property-value base-changes (uuid/next) 0 "NewVal")]
(t/is (pcb/empty-changes? changes)))
;; pos nil
(let [changes (clvp/generate-update-property-value base-changes c01-id nil "NewVal")]
(t/is (pcb/empty-changes? changes)))
;; pos out of range
(let [changes (clvp/generate-update-property-value base-changes c01-id 100 "NewVal")]
(t/is (pcb/empty-changes? changes)))))
(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-duplicate-value
(let [file (-> (thf/sample-file :file1)
(thv/add-variant-two-properties :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
c01-id (:id comp01)
c02-id (:id comp02)
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 c01-id 0 "SharedValue")
(clvp/generate-update-property-value c02-id 0 "SharedValue"))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
(t/is (= "SharedValue" (-> comp01' :variant-properties first :value)))
(t/is (= "SharedValue" (-> comp02' :variant-properties first :value)))))
;; =============================================================================
;; generate-set-variant-error tests
;; =============================================================================
(t/deftest test-set-variant-error-no-op
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; component-id nil
(let [changes (clvp/generate-set-variant-error base-changes nil "error")]
(t/is (pcb/empty-changes? changes)))
;; component-id non-existent
(let [changes (clvp/generate-set-variant-error base-changes (uuid/next) "error")]
(t/is (pcb/empty-changes? changes)))))
(t/deftest test-set-variant-error-set
(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)
c01-id (:id comp01)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-set-variant-error c01-id "MyError"))
file' (thf/apply-changes file changes)
main01' (ths/get-shape file' :m01)]
(t/is (= "MyError" (:variant-error main01')))))
(t/deftest test-set-variant-error-add-and-clear
(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)
c01-id (:id comp01)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-set-variant-error c01-id "SomeError")
(clvp/generate-set-variant-error c01-id nil))
file' (thf/apply-changes file changes)
main01' (ths/get-shape file' :m01)]
(t/is (nil? (:variant-error main01')))))
;; =============================================================================
;; generate-reorder-variant-poperties tests
;; =============================================================================
(t/deftest test-reorder-variant-properties-no-op
(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)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; variant-id nil
(let [changes (clvp/generate-reorder-variant-poperties base-changes nil 0 1)]
(t/is (pcb/empty-changes? changes)))
;; variant-id non-existent
(let [changes (clvp/generate-reorder-variant-poperties base-changes (uuid/next) 0 1)]
(t/is (pcb/empty-changes? changes)))
;; from-pos nil
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id nil 1)]
(t/is (pcb/empty-changes? changes)))
;; to-space-between-pos nil
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id 0 nil)]
(t/is (pcb/empty-changes? changes)))
;; from-pos negative is clamped to 0
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id -1 1)]
(t/is (pcb/empty-changes? changes)))
;; to-space-between-pos too large is clamped to last index
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id 1 100)]
(t/is (pcb/empty-changes? changes)))))
(t/deftest test-reorder-variant-properties-move
(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)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clvp/generate-reorder-variant-poperties v-id 0 2))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= "Property 2" (-> comp01' :variant-properties first :name)))
(t/is (= "Property 1" (-> comp01' :variant-properties last :name)))))
(t/deftest test-reorder-variant-properties-clamped-pos
(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)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id 0 100)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= "Property 2" (-> comp01' :variant-properties first :name)))
(t/is (= "Property 1" (-> comp01' :variant-properties last :name))))
(let [changes (clvp/generate-reorder-variant-poperties base-changes v-id 1 -1)
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)]
(t/is (= "Property 2" (-> comp01' :variant-properties first :name)))
(t/is (= "Property 1" (-> comp01' :variant-properties last :name))))))
;; =============================================================================
;; generate-add-new-property tests
;; =============================================================================
(t/deftest test-add-new-property-no-op
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; variant-id nil
(let [changes (clvp/generate-add-new-property base-changes nil)]
(t/is (pcb/empty-changes? changes)))
;; variant-id non-existent
(let [changes (clvp/generate-add-new-property base-changes (uuid/next))]
(t/is (pcb/empty-changes? changes)))))
(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-add-new-property-duplicate-name
(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)
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 "Property 1"}))
file' (thf/apply-changes file changes)
comp01' (thc/get-component file' :c01)
prop-names (mapv :name (:variant-properties comp01'))]
(t/is (= 2 (count prop-names)))
(t/is (= "Property 1" (first prop-names)))
(t/is (= "Property 1 (1)" (last prop-names)))))
;; =============================================================================
;; generate-make-shapes-no-variant tests
;; =============================================================================
(t/deftest test-make-shapes-no-variant-no-op
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))]
;; empty shapes list
(let [changes (clvp/generate-make-shapes-no-variant base-changes [])]
(t/is (pcb/empty-changes? changes)))
;; nil shapes list
(let [changes (clvp/generate-make-shapes-no-variant base-changes nil)]
(t/is (pcb/empty-changes? changes)))))
(t/deftest test-make-shapes-no-variant-remove
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
main01 (ths/get-shape file :m01)
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-no-variant [main01]))
file' (thf/apply-changes file changes :validate? false)
comp01' (thc/get-component file' :c01)
main01' (ths/get-shape file' :m01)]
(t/is (nil? (:variant-id comp01')))
(t/is (nil? (:variant-properties comp01')))
(t/is (nil? (:variant-id main01')))
(t/is (nil? (:variant-name main01')))))
;; =============================================================================
;; generate-make-shapes-variant tests
;; =============================================================================
(t/deftest test-make-shapes-variant-no-op
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
base-changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page)))
container (ths/get-shape file :v01)]
;; empty shapes list
(let [changes (clvp/generate-make-shapes-variant base-changes [] container)]
(t/is (pcb/empty-changes? changes)))
;; nil shapes list
(let [container (ths/get-shape file :v01)
changes (clvp/generate-make-shapes-variant base-changes nil container)]
(t/is (pcb/empty-changes? changes)))))
(t/deftest test-make-shapes-variant-convert
(let [file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
container (ths/get-shape file :v01)
v-id (:id container)
;; Add a non-variant component under the variant container
file (tho/add-simple-component file :c03 :m03 :sh03
:root-params {:parent-label :v01})
m03 (ths/get-shape file :m03)
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-make-shapes-variant [m03] container))
file' (thf/apply-changes file changes :validate? false)
c03' (thc/get-component file' :c03)
m03' (ths/get-shape file' :m03)]
(t/is (= v-id (:variant-id c03')))
(t/is (= v-id (:variant-id m03')))
(t/is (= "Property 1" (-> c03' :variant-properties first :name)))
(t/is (= "Frame1" (-> c03' :variant-properties first :value)))
(t/is (= "Frame1" (-> m03' :variant-name)))))

View File

@ -10,8 +10,6 @@
[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.test-helpers.components :as thc]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
@ -20,221 +18,6 @@
(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-duplicate-variant-container
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
@ -245,9 +28,6 @@
variant-container (ths/get-shape file :v01)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
@ -274,7 +54,6 @@
(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
@ -288,7 +67,7 @@
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-page page)
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(#(second (cls/generate-delete-shapes % #{m01-id m02-id} {}))))
@ -304,7 +83,6 @@
;; 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

View File

@ -23,6 +23,7 @@
[common-tests.files-migrations-test]
[common-tests.files.shapes-builder-test]
[common-tests.files.validate-test]
[common-tests.files.variant-test]
[common-tests.geom-align-test]
[common-tests.geom-bounds-layout-nil-test]
[common-tests.geom-bounds-map-test]
@ -89,6 +90,7 @@
[common-tests.types.token-test]
[common-tests.types.tokens-lib-test]
[common-tests.types.tokens-status-test]
[common-tests.types.variant-test]
[common-tests.undo-stack-test]
[common-tests.uuid-test]))
@ -104,6 +106,7 @@
'common-tests.files-migrations-0026-test
'common-tests.files-migrations-test
'common-tests.files.validate-test
'common-tests.files.variant-test
'common-tests.geom-align-test
'common-tests.geom-bounds-layout-nil-test
'common-tests.geom-bounds-map-test
@ -145,6 +148,7 @@
'common-tests.logic.token-test
'common-tests.logic.variants-switch-test
'common-tests.math-test
'common-tests.types.variant-test
'common-tests.media-test
'common-tests.path-names-test
'common-tests.record-test

View File

@ -7,10 +7,238 @@
(ns common-tests.types.variant-test
(:require
[app.common.types.variant :as ctv]
[app.common.uuid :as uuid]
[clojure.test :as t]))
(t/deftest variant-component
(t/is (not (ctv/variant-component? nil)))
(t/is (not (ctv/variant-component? {})))
(t/is (ctv/variant-component? {:variant-id (uuid/next)})))
(t/deftest variant-distance01
(t/deftest variant-shape
(t/is (not (ctv/variant-shape? nil)))
(t/is (not (ctv/variant-shape? {})))
(t/is (ctv/variant-shape? {:variant-id (uuid/next)})))
(t/deftest variant-container
(t/is (not (ctv/variant-container? nil)))
(t/is (not (ctv/variant-container? {})))
(t/is (ctv/variant-container? {:is-variant-container true})))
(t/deftest properties-to-name-test
(t/is (= "" (ctv/properties-to-name [])))
(t/is (= "" (ctv/properties-to-name nil)))
(t/is (= "Button, Primary" (ctv/properties-to-name [{:name "Property 1" :value "Button"}
{:name "Property 2" :value "Primary"}])))
(t/is (= "Button" (ctv/properties-to-name [{:name "Property 1" :value "Button"}
{:name "Property 2" :value ""}]))))
(t/deftest next-property-number-test
(t/is (= 1 (ctv/next-property-number [])))
(t/is (= 1 (ctv/next-property-number nil)))
(t/is (= 2 (ctv/next-property-number [{:name "Property 1" :value "x"}])))
(t/is (= 4 (ctv/next-property-number [{:name "Property 3" :value "x"}])))
(t/is (= 3 (ctv/next-property-number [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]))))
(t/deftest add-new-property-test
(t/is (= [{:name "Property 1" :value "x"}]
(ctv/add-new-property [] "x")))
(t/is (= [{:name "Property 1" :value "x"}]
(ctv/add-new-property nil "x")))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
(ctv/add-new-property [{:name "Property 1" :value "x"}] "y"))))
(t/deftest add-new-properties-test
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "b"}]
(ctv/add-new-properties [] ["a" "b"])))
(t/is (= '({:name "Property 2" :value "b"} {:name "Property 1" :value "a"})
(ctv/add-new-properties nil ["a" "b"])))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "a"} {:name "Property 3" :value "b"}]
(ctv/add-new-properties [{:name "Property 1" :value "x"}] ["a" "b"]))))
(t/deftest path-to-properties-test
(t/is (= [] (ctv/path-to-properties "" [])))
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "b"}]
(ctv/path-to-properties "a / b" nil)))
(t/is (= [{:name "Property 1" :value "Button"}
{:name "Property 2" :value "Primary"}
{:name "Property 3" :value "Hover"}]
(ctv/path-to-properties "Button / Primary / Hover" [])))
(t/is (= [{:name "Property 1" :value "Button"}
{:name "Property 2" :value "Primary"}
{:name "Property 3" :value "Hover"}
{:name "Property 4" :value ""}]
(ctv/path-to-properties "Button / Primary / Hover" [] 4)))
(t/is (= [{:name "Property 1" :value "Button"}
{:name "Property 2" :value "Primary"}]
(ctv/path-to-properties "Button / Primary" [{:name "Property 1" :value "old"}
{:name "Property 2" :value "old2"}]))))
(t/deftest properties-map->formula-test
(t/is (= "" (ctv/properties-map->formula [])))
(t/is (= "" (ctv/properties-map->formula nil)))
(t/is (= "Property 1=Button, Property 2=Primary"
(ctv/properties-map->formula [{:name "Property 1" :value "Button"}
{:name "Property 2" :value "Primary"}])))
(t/is (= "Property 1=Button"
(ctv/properties-map->formula [{:name "Property 1" :value "Button"}
{:name "Property 2" :value ""}]))))
(t/deftest properties-formula->map-test
(t/is (= [] (ctv/properties-formula->map "")))
(t/is (= [] (ctv/properties-formula->map nil)))
(t/is (= [{:name "Property 1" :value "Button"} {:name "Property 2" :value "Primary"}]
(ctv/properties-formula->map "Property 1=Button, Property 2=Primary")))
(t/is (= [{:name "Property 1" :value "Button"}]
(ctv/properties-formula->map "Property 1=Button, Property 2="))))
(t/deftest valid-properties-formula?-test
(t/is (= true (ctv/valid-properties-formula? "Property 1=Button, Property 2=Primary")))
(t/is (= false (ctv/valid-properties-formula? "")))
(t/is (= true (ctv/valid-properties-formula? nil)))
(t/is (= false (ctv/valid-properties-formula? "Property 1=Button, Property 2"))))
(t/deftest find-properties-to-remove-test
(t/is (= [] (ctv/find-properties-to-remove [] [])))
(t/is (= [] (ctv/find-properties-to-remove nil nil)))
(t/is (= [{:name "Property 3" :value "z"}]
(ctv/find-properties-to-remove [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}
{:name "Property 3" :value "z"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}])))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
(ctv/find-properties-to-remove [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 3" :value "z"}]))))
(t/deftest find-properties-to-update-test
(t/is (= [] (ctv/find-properties-to-update [] [])))
(t/is (= [] (ctv/find-properties-to-update nil nil)))
(t/is (= [{:name "Property 1" :value "new-x"}]
(ctv/find-properties-to-update [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 1" :value "new-x"}
{:name "Property 2" :value "y"}])))
(t/is (= [{:name "Property 1" :value "new-x"} {:name "Property 2" :value "new-y"}]
(ctv/find-properties-to-update [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 1" :value "new-x"}
{:name "Property 2" :value "new-y"}]))))
(t/deftest find-properties-to-add-test
(t/is (= [] (ctv/find-properties-to-add [] [])))
(t/is (= [] (ctv/find-properties-to-add nil nil)))
(t/is (= [{:name "Property 3" :value "z"}]
(ctv/find-properties-to-add [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}
{:name "Property 3" :value "z"}])))
(t/is (= [{:name "Property 2" :value "y"}]
(ctv/find-properties-to-add [{:name "Property 1" :value "x"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]))))
(t/deftest update-number-in-repeated-item-test
(t/is (= "Property" (ctv/update-number-in-repeated-item [] "Property")))
(t/is (= "Property" (ctv/update-number-in-repeated-item nil "Property")))
(t/is (= "Property (1)" (ctv/update-number-in-repeated-item ["Property"] "Property")))
(t/is (= "Property (2)" (ctv/update-number-in-repeated-item ["Property" "Property (1)"] "Property")))
(t/is (= "Property" (ctv/update-number-in-repeated-item ["Other"] "Property"))))
(t/deftest update-number-in-repeated-prop-names-test
(t/is (= [] (ctv/update-number-in-repeated-prop-names [])))
(t/is (= [] (ctv/update-number-in-repeated-prop-names nil)))
(t/is (= [{:name "Property" :value "x"}]
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}])))
(t/is (= [{:name "Property" :value "x"} {:name "Property (1)" :value "y"}]
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}
{:name "Property" :value "y"}])))
(t/is (= [{:name "Property" :value "x"} {:name "Property (1)" :value "y"} {:name "Property (2)" :value "z"}]
(ctv/update-number-in-repeated-prop-names [{:name "Property" :value "x"}
{:name "Property" :value "y"}
{:name "Property" :value "z"}]))))
(t/deftest find-index-for-property-name-test
(t/is (= nil (ctv/find-index-for-property-name [] "Property 1")))
(t/is (= nil (ctv/find-index-for-property-name nil "Property 1")))
(t/is (= 0 (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}] "Property 1")))
(t/is (= 1 (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}] "Property 2")))
(t/is (= nil (ctv/find-index-for-property-name [{:name "Property 1" :value "x"}] "Property 3"))))
(t/deftest remove-prefix-test
(t/is (= "name" (ctv/remove-prefix "name" "")))
(t/is (= "name" (ctv/remove-prefix "name" nil)))
(t/is (= "Primary" (ctv/remove-prefix "Button / Primary" "Button")))
(t/is (= "Primary" (ctv/remove-prefix "Button / Primary" "Button / ")))
(t/is (= "Button / Primary" (ctv/remove-prefix "Button / Primary" "Other"))))
(t/deftest merge-properties-test
(t/is (= [] (ctv/merge-properties [] [])))
(t/is (= [] (ctv/merge-properties nil nil)))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
(ctv/merge-properties [{:name "Property 1" :value "a"}
{:name "Property 2" :value "b"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}])))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"} {:name "Property 3" :value "z"}]
(ctv/merge-properties [{:name "Property 1" :value "a"}
{:name "Property 2" :value "b"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}
{:name "Property 3" :value "z"}])))
(t/is (= [{:name "Property 1" :value "a"} {:name "Property 2" :value "y"}]
(ctv/merge-properties [{:name "Property 1" :value "a"}
{:name "Property 2" :value "b"}]
[{:name "Property 2" :value "y"}]))))
(t/deftest compare-properties-test
(t/is (= [] (ctv/compare-properties [])))
(t/is (= [] (ctv/compare-properties nil)))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "y"}]
(ctv/compare-properties [[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]])))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value nil}]
(ctv/compare-properties [[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "z"}]])))
(t/is (= [{:name "Property 1" :value "x"} {:name "Property 2" :value "*"}]
(ctv/compare-properties [[{:name "Property 1" :value "x"}
{:name "Property 2" :value "y"}]
[{:name "Property 1" :value "x"}
{:name "Property 2" :value "z"}]]
"*"))))
(t/deftest variant-name-to-name-test
(t/is (= "Button / Primary / Hover" (ctv/variant-name-to-name {:name "Button" :variant-name "Primary, Hover"})))
(t/is (= "Button" (ctv/variant-name-to-name {:name "Button" :variant-name ""})))
(t/is (= "Button" (ctv/variant-name-to-name {:name "Button" :variant-name nil})))
(t/is (= "" (ctv/variant-name-to-name {:name "" :variant-name ""})))
(t/is (= nil (ctv/variant-name-to-name {:name nil :variant-name nil}))))
(t/deftest find-boolean-pair-test
(t/is (= {"on" true "off" false} (ctv/find-boolean-pair ["on" "off"])))
(t/is (= {"yes" true "no" false} (ctv/find-boolean-pair ["yes" "no"])))
(t/is (= {"true" true "false" false} (ctv/find-boolean-pair ["true" "false"])))
(t/is (= {"on" true "off" false} (ctv/find-boolean-pair ["off" "on"])))
(t/is (= {"ON" true "OFF" false} (ctv/find-boolean-pair ["ON" "OFF"])))
(t/is (= nil (ctv/find-boolean-pair ["foo" "bar"])))
(t/is (= nil (ctv/find-boolean-pair nil)))
(t/is (= nil (ctv/find-boolean-pair ["on"]))))
(t/deftest same-variant?-test
(t/is (= false (ctv/same-variant? [])))
(t/is (= false (ctv/same-variant? nil)))
(t/is (= true (ctv/same-variant? [{:variant-id "abc"}])))
(t/is (= true (ctv/same-variant? [{:variant-id "abc"} {:variant-id "abc"}])))
(t/is (= false (ctv/same-variant? [{:variant-id "abc"} {:variant-id "def"}])))
(t/is (= false (ctv/same-variant? [{:variant-id ""} {:variant-id ""}]))))
(t/deftest properties-distance01
;;c1: primary, default, rounded, blue, dark
;;c2: primary, hover, squared, blue, dark
;;c3: primary, default, squared, blue, light
@ -35,12 +263,11 @@
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "light"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)]
dist2 (ctv/properties-distance target props2)
dist3 (ctv/properties-distance target props3)]
(t/is (< dist3 dist2))))
(t/deftest variant-distance02
(t/deftest properties-distance02
;;c1: primary, default, rounded, blue, dark
;;c2: primary, hover, squared, red, dark
;;c3: secondary, hover, rounded, blue, dark
@ -65,11 +292,11 @@
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)]
dist2 (ctv/properties-distance target props2)
dist3 (ctv/properties-distance target props3)]
(t/is (< dist2 dist3))))
(t/deftest variant-distance03
(t/deftest properties-distance03
;;c1: primary, default, rounded, blue, dark
;;c2: secondary, default, rounded, blue, light
;;c3: secondary, hover, squared, blue, dark
@ -101,12 +328,18 @@
{:name "borders" :value "rounded"}
{:name "color" :value "blue"}
{:name "theme" :value "dark"}]
dist2 (ctv/distance target props2)
dist3 (ctv/distance target props3)
dist4 (ctv/distance target props4)]
dist2 (ctv/properties-distance target props2)
dist3 (ctv/properties-distance target props3)
dist4 (ctv/properties-distance target props4)]
(t/is (< dist2 dist4))
(t/is (< dist4 dist3))))
(t/deftest properties-distance04
(t/is (= 0 (ctv/properties-distance [] [])))
(t/is (= 0 (ctv/properties-distance nil nil)))
(t/is (= 0 (ctv/properties-distance [{:name "a" :value "x"}] [{:name "a" :value "x"}])))
(t/is (= 2.0 (ctv/properties-distance [{:name "a" :value "x"} {:name "b" :value "y"}] [{:name "a" :value "x"} {:name "b" :value "z"}]))))

View File

@ -1207,26 +1207,30 @@
(ptk/reify ::show-component-in-assets
ptk/WatchEvent
(watch [_ state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (cfv/get-primary-component fdata component-id)
cpath (:path component)
cpath (cpn/split-path cpath)
paths (map (fn [i] (cpn/join-path (take (inc i) cpath)))
(range (count cpath)))]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (ctkl/get-component fdata component-id)
primary-variant (cfv/get-primary-variant fdata component)
primary-component (ctkl/get-component fdata (:component-id primary-variant))
cpath (:path primary-component)
cpath (cpn/split-path cpath)
paths (map (fn [i] (cpn/join-path (take (inc i) cpath)))
(range (count cpath)))]
(rx/concat
(rx/from (map #(set-assets-group-open file-id :components % true) paths))
(rx/of (dcm/go-to-workspace :layout :assets)
(set-assets-section-open file-id :library true)
(set-assets-section-open file-id :components true)
(select-single-asset file-id (:id component) :components)))))
(select-single-asset file-id (:id primary-component) :components)))))
ptk/EffectEvent
(effect [_ state _]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (cfv/get-primary-component fdata component-id)
wrapper-id (str "component-shape-id-" (:id component))]
(let [file-id (:current-file-id state)
fdata (dsh/lookup-file-data state file-id)
component (ctkl/get-component fdata component-id)
primary-variant (cfv/get-primary-variant fdata component)
primary-component (ctkl/get-component fdata (:component-id primary-variant))
wrapper-id (str "component-shape-id-" (:id primary-component))]
(tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -803,7 +803,7 @@
(remove #(= (:id %) component-id))
(filter #(= (dm/get-in % [:variant-properties pos :value]) val))
(reverse))
nearest-comp (apply min-key #(ctv/distance target-props (:variant-properties %)) valid-comps)
nearest-comp (apply min-key #(ctv/properties-distance target-props (:variant-properties %)) valid-comps)
shape-parents (cfh/get-parents-with-self current-page-objects (:parent-id shape))
nearest-comp-children (cfh/get-children-with-self component-page-objects (:main-instance-id nearest-comp))
comps-nesting-loop? (seq? (cfh/components-nesting-loop? nearest-comp-children shape-parents))

View File

@ -58,7 +58,7 @@
graphics 0
typographies (count (:typographies data))
components (count (->> (ctkl/components-seq data)
(remove #(cfv/is-secondary-variant? % data))))
(remove #(cfv/is-secondary-variant? data %))))
tokens-lib (cfo/get-tokens-lib data)
tokens (if (some? tokens-lib) (count (ctob/get-all-tokens tokens-lib)) 0)
token-sets (if (some? tokens-lib) (count (ctob/get-sets tokens-lib)) 0)

View File

@ -345,7 +345,7 @@
(mf/with-memo [filters library]
(as-> (into [] (ctkl/components-seq library)) $
(cmm/apply-filters $ filters)
(remove #(cfv/is-secondary-variant? % library) $)))
(remove #(cfv/is-secondary-variant? library %) $)))
filtered-typographies
(mf/with-memo [filters typographies]

View File

@ -709,7 +709,7 @@
:components
vals
(remove #(true? (:deleted %)))
(remove #(cfv/is-secondary-variant? % current-lib-data))
(remove #(cfv/is-secondary-variant? current-lib-data %))
(map #(assoc % :full-name (cpn/merge-path-item-with-dot (:path %) (:name %)))))
count-variants (fn [component]

View File

@ -1075,7 +1075,7 @@
components (->> data
:components
(remove (comp :deleted second))
(remove (comp #(cfv/is-secondary-variant? % data) second))
(remove (comp #(cfv/is-secondary-variant? data %) second))
(map first)
(map #(lib-component-proxy plugin-id file-id %)))]
(apply array components)))}