From 5c80841143da1e6ed5ec32eb0a7cd09627200ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 8 Sep 2026 09:49:32 +0200 Subject: [PATCH] :wrench: Add tests and validations to variant functions --- common/src/app/common/files/changes.cljc | 8 +- .../src/app/common/files/changes_builder.cljc | 8 + common/src/app/common/files/validate.cljc | 17 +- common/src/app/common/files/variant.cljc | 72 +- .../app/common/logic/variant_properties.cljc | 195 ++++-- common/src/app/common/types/variant.cljc | 239 ++++++- .../test/common_tests/files/variant_test.cljc | 165 +++++ .../logic/comp_creation_test.cljc | 4 +- .../logic/variant_properties_test.cljc | 657 ++++++++++++++++++ .../common_tests/logic/variants_test.cljc | 224 +----- common/test/common_tests/runner.cljc | 4 + .../test/common_tests/types/variant_test.cljc | 255 ++++++- frontend/src/app/main/data/workspace.cljs | 28 +- .../src/app/main/data/workspace/variants.cljs | 2 +- .../src/app/main/ui/workspace/libraries.cljs | 2 +- .../sidebar/assets/file_library.cljs | 2 +- .../sidebar/options/menus/component.cljs | 2 +- frontend/src/app/plugins/library.cljs | 2 +- 18 files changed, 1486 insertions(+), 400 deletions(-) create mode 100644 common/test/common_tests/files/variant_test.cljc create mode 100644 common/test/common_tests/logic/variant_properties_test.cljc diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index e99dbe5c02..05dc838004 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -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 diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 06924ab4c7..dbaf45ae69 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -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 diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 280f468d10..9e25d86021 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -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 diff --git a/common/src/app/common/files/variant.cljc b/common/src/app/common/files/variant.cljc index 989a80a58b..02b434ead2 100644 --- a/common/src/app/common/files/variant.cljc +++ b/common/src/app/common/files/variant.cljc @@ -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: + ( ...)" ([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: + [ ]" + [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))) diff --git a/common/src/app/common/logic/variant_properties.cljc b/common/src/app/common/logic/variant_properties.cljc index d51fb07495..673e73cfe1 100644 --- a/common/src/app/common/logic/variant_properties.cljc +++ b/common/src/app/common/logic/variant_properties.cljc @@ -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) diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc index 8c9913b504..4515a29f68 100644 --- a/common/src/app/common/types/variant.cljc +++ b/common/src/app/common/types/variant.cljc @@ -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))))) diff --git a/common/test/common_tests/files/variant_test.cljc b/common/test/common_tests/files/variant_test.cljc new file mode 100644 index 0000000000..7c795d41c9 --- /dev/null +++ b/common/test/common_tests/files/variant_test.cljc @@ -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)))) diff --git a/common/test/common_tests/logic/comp_creation_test.cljc b/common/test/common_tests/logic/comp_creation_test.cljc index d28e5f3b31..69de2ad39d 100644 --- a/common/test/common_tests/logic/comp_creation_test.cljc +++ b/common/test/common_tests/logic/comp_creation_test.cljc @@ -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) diff --git a/common/test/common_tests/logic/variant_properties_test.cljc b/common/test/common_tests/logic/variant_properties_test.cljc new file mode 100644 index 0000000000..f73214236a --- /dev/null +++ b/common/test/common_tests/logic/variant_properties_test.cljc @@ -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))))) diff --git a/common/test/common_tests/logic/variants_test.cljc b/common/test/common_tests/logic/variants_test.cljc index c4cfc1b95a..4cd237a818 100644 --- a/common/test/common_tests/logic/variants_test.cljc +++ b/common/test/common_tests/logic/variants_test.cljc @@ -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 diff --git a/common/test/common_tests/runner.cljc b/common/test/common_tests/runner.cljc index 68e47e824e..35db81954b 100644 --- a/common/test/common_tests/runner.cljc +++ b/common/test/common_tests/runner.cljc @@ -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 diff --git a/common/test/common_tests/types/variant_test.cljc b/common/test/common_tests/types/variant_test.cljc index f477da5bd9..c9ba89cc9a 100644 --- a/common/test/common_tests/types/variant_test.cljc +++ b/common/test/common_tests/types/variant_test.cljc @@ -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"}])))) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b04bf5394b..362c6916c7 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/variants.cljs b/frontend/src/app/main/data/workspace/variants.cljs index 09f6003a9d..d0a7a203e3 100644 --- a/frontend/src/app/main/data/workspace/variants.cljs +++ b/frontend/src/app/main/data/workspace/variants.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 5c09d951ff..81fd28d852 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -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) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs index 8596dfd11a..3ebcf31c11 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.cljs @@ -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] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 8d46f46a3f..8c43bf0dc9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -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] diff --git a/frontend/src/app/plugins/library.cljs b/frontend/src/app/plugins/library.cljs index 998f8b0b5c..745711c706 100644 --- a/frontend/src/app/plugins/library.cljs +++ b/frontend/src/app/plugins/library.cljs @@ -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)))}