Merge pull request #245 from tokens-studio/use-token-name-ref

Use token name ref
This commit is contained in:
Florian Schrödl 2024-08-14 09:16:23 +02:00 committed by GitHub
commit c7d4db900e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 264 additions and 228 deletions

View file

@ -47,10 +47,12 @@
:string :string
:typography}) :typography})
(def token-name-ref :string)
(sm/register! ::token (sm/register! ::token
[:map {:title "Token"} [:map {:title "Token"}
[:id ::sm/uuid] [:id ::sm/uuid]
[:name :string] [:name token-name-ref]
[:type [::sm/one-of token-types]] [:type [::sm/one-of token-types]]
[:value :any] [:value :any]
[:description {:optional true} :string] [:description {:optional true} :string]
@ -58,48 +60,48 @@
(sm/register! ::border-radius (sm/register! ::border-radius
[:map [:map
[:rx {:optional true} ::sm/uuid] [:rx {:optional true} token-name-ref]
[:ry {:optional true} ::sm/uuid] [:ry {:optional true} token-name-ref]
[:r1 {:optional true} ::sm/uuid] [:r1 {:optional true} token-name-ref]
[:r2 {:optional true} ::sm/uuid] [:r2 {:optional true} token-name-ref]
[:r3 {:optional true} ::sm/uuid] [:r3 {:optional true} token-name-ref]
[:r4 {:optional true} ::sm/uuid]]) [:r4 {:optional true} token-name-ref]])
(def border-radius-keys (schema-keys ::border-radius)) (def border-radius-keys (schema-keys ::border-radius))
(sm/register! ::stroke-width (sm/register! ::stroke-width
[:map [:map
[:stroke-width {:optional true} ::sm/uuid]]) [:stroke-width {:optional true} token-name-ref]])
(def stroke-width-keys (schema-keys ::stroke-width)) (def stroke-width-keys (schema-keys ::stroke-width))
(sm/register! ::sizing (sm/register! ::sizing
[:map [:map
[:width {:optional true} ::sm/uuid] [:width {:optional true} token-name-ref]
[:height {:optional true} ::sm/uuid] [:height {:optional true} token-name-ref]
[:layout-item-min-w {:optional true} ::sm/uuid] [:layout-item-min-w {:optional true} token-name-ref]
[:layout-item-max-w {:optional true} ::sm/uuid] [:layout-item-max-w {:optional true} token-name-ref]
[:layout-item-min-h {:optional true} ::sm/uuid] [:layout-item-min-h {:optional true} token-name-ref]
[:layout-item-max-h {:optional true} ::sm/uuid]]) [:layout-item-max-h {:optional true} token-name-ref]])
(def sizing-keys (schema-keys ::sizing)) (def sizing-keys (schema-keys ::sizing))
(sm/register! ::opacity (sm/register! ::opacity
[:map [:map
[:opacity {:optional true} ::sm/uuid]]) [:opacity {:optional true} token-name-ref]])
(def opacity-keys (schema-keys ::opacity)) (def opacity-keys (schema-keys ::opacity))
(sm/register! ::spacing (sm/register! ::spacing
[:map [:map
[:row-gap {:optional true} ::sm/uuid] [:row-gap {:optional true} token-name-ref]
[:column-gap {:optional true} ::sm/uuid] [:column-gap {:optional true} token-name-ref]
[:p1 {:optional true} ::sm/uuid] [:p1 {:optional true} token-name-ref]
[:p2 {:optional true} ::sm/uuid] [:p2 {:optional true} token-name-ref]
[:p3 {:optional true} ::sm/uuid] [:p3 {:optional true} token-name-ref]
[:p4 {:optional true} ::sm/uuid] [:p4 {:optional true} token-name-ref]
[:x {:optional true} ::sm/uuid] [:x {:optional true} token-name-ref]
[:y {:optional true} ::sm/uuid]]) [:y {:optional true} token-name-ref]])
(def spacing-keys (schema-keys ::spacing)) (def spacing-keys (schema-keys ::spacing))
@ -113,7 +115,7 @@
(sm/register! ::rotation (sm/register! ::rotation
[:map [:map
[:rotation {:optional true} ::sm/uuid]]) [:rotation {:optional true} token-name-ref]])
(def rotation-keys (schema-keys ::rotation)) (def rotation-keys (schema-keys ::rotation))

View file

@ -16,6 +16,7 @@
[app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes :as dwsh]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.common :refer [workspace-shapes]] [app.main.ui.workspace.tokens.common :refer [workspace-shapes]]
[app.main.ui.workspace.tokens.token :as wtt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[clojure.data :as data] [clojure.data :as data]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -55,21 +56,21 @@
(first))] (first))]
shape)) shape))
(defn token-from-attributes [token-id attributes] (defn token-from-attributes [token attributes]
(->> (map (fn [attr] [attr token-id]) attributes) (->> (map (fn [attr] [attr (wtt/token-identifier token)]) attributes)
(into {}))) (into {})))
(defn unapply-token-id [shape attributes] (defn unapply-token-id [shape attributes]
(update shape :applied-tokens d/without-keys attributes)) (update shape :applied-tokens d/without-keys attributes))
(defn apply-token-id-to-attributes [{:keys [shape token-id attributes]}] (defn apply-token-to-attributes [{:keys [shape token attributes]}]
(let [token (token-from-attributes token-id attributes)] (let [token (token-from-attributes token attributes)]
(toggle-or-apply-token shape token))) (toggle-or-apply-token shape token)))
(defn apply-token-to-shape (defn apply-token-to-shape
[{:keys [shape token attributes] :as _props}] [{:keys [shape token attributes] :as _props}]
(let [applied-tokens (apply-token-id-to-attributes {:shape shape (let [applied-tokens (apply-token-to-attributes {:shape shape
:token-id (:id token) :token token
:attributes attributes})] :attributes attributes})]
(update shape :applied-tokens #(merge % applied-tokens)))) (update shape :applied-tokens #(merge % applied-tokens))))
@ -80,17 +81,6 @@
(apply-token-to-shape props) (apply-token-to-shape props)
shape)) shape))
(defn update-token-from-attributes
[{:keys [token-id shape-id attributes]}]
(ptk/reify ::update-token-from-attributes
ptk/WatchEvent
(watch [_ state _]
(let [shape (get-shape-from-state shape-id state)
applied-tokens (apply-token-id-to-attributes {:shape shape
:token-id token-id
:attributes attributes})]
(rx/of (update-shape shape-id {:applied-tokens applied-tokens}))))))
(defn get-token-data-from-token-id (defn get-token-data-from-token-id
[id] [id]
(let [workspace-data (deref refs/workspace-data)] (let [workspace-data (deref refs/workspace-data)]

View file

@ -300,7 +300,7 @@
(update-fn shape) (update-fn shape)
shape)) shape))
{:reg-objects? true {:reg-objects? true
:attrs [:rx :ry :r1 :r2 :r3 :r4]}))) :attrs [:rx :ry :r1 :r2 :r3 :r4 :applied-tokens]})))
on-switch-to-radius-1 on-switch-to-radius-1
(mf/use-fn (mf/use-fn

View file

@ -34,11 +34,10 @@
(watch [_ state _] (watch [_ state _]
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens]))) (->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
(rx/mapcat (rx/mapcat
(fn [sd-tokens] (fn [resolved-tokens]
(let [undo-id (js/Symbol) (let [undo-id (js/Symbol)
resolved-value (-> (get sd-tokens (:id token)) resolved-value (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value])
(wtt/resolve-token-value)) tokenized-attributes (wtt/attributes-map attributes token)]
tokenized-attributes (wtt/attributes-map attributes (:id token))]
(rx/of (rx/of
(dwu/start-undo-transaction undo-id) (dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape] (dwsh/update-shapes shape-ids (fn [shape]
@ -58,7 +57,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (rx/of
(let [remove-token #(when % (wtt/remove-attributes-for-token-id attributes (:id token) %))] (let [remove-token #(when % (wtt/remove-attributes-for-token attributes token %))]
(dwsh/update-shapes (dwsh/update-shapes
shape-ids shape-ids
(fn [shape] (fn [shape]

View file

@ -92,7 +92,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; When creating a new token we dont have a token name yet, ;; When creating a new token we dont have a token name yet,
;; so we use a temporary token name that hopefully doesn't clash with any of the users token names. ;; so we use a temporary token name that hopefully doesn't clash with any of the users token names.
token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value) token-name (if (str/empty? name-value) "__TOKEN_STUDIO_SYSTEM.TEMP" name-value)
token-references (sd/find-token-references input) token-references (wtt/find-token-references input)
direct-self-reference? (get token-references token-name)] direct-self-reference? (get token-references token-name)]
(cond (cond
empty-input? (p/rejected nil) empty-input? (p/rejected nil)
@ -104,7 +104,7 @@ Token names should only contain letters and digits separated by . characters.")}
(-> (sd/resolve-tokens+ new-tokens #_ {:debug? true}) (-> (sd/resolve-tokens+ new-tokens #_ {:debug? true})
(p/then (p/then
(fn [resolved-tokens] (fn [resolved-tokens]
(let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-id)] (let [{:keys [errors resolved-value] :as resolved-token} (get resolved-tokens token-name)]
(cond (cond
resolved-value (p/resolved resolved-token) resolved-value (p/resolved resolved-token)
(sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference) (sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference)

View file

@ -2,13 +2,11 @@
(:require (:require
["@tokens-studio/sd-transforms" :as sd-transforms] ["@tokens-studio/sd-transforms" :as sd-transforms]
["style-dictionary$default" :as sd] ["style-dictionary$default" :as sd]
[app.common.data :as d]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[cuerdas.core :as str] [cuerdas.core :as str]
[promesa.core :as p] [promesa.core :as p]
[rumext.v2 :as mf] [rumext.v2 :as mf]))
[shadow.resource]))
(def StyleDictionary (def StyleDictionary
"The global StyleDictionary instance used as an external library for now, "The global StyleDictionary instance used as an external library for now,
@ -23,13 +21,6 @@
;; Functions ------------------------------------------------------------------- ;; Functions -------------------------------------------------------------------
(defn find-token-references
"Finds token reference values in `value-string` and returns a set with all contained namespaces."
[value-string]
(some->> (re-seq #"\{([^}]*)\}" value-string)
(map second)
(into #{})))
(defn tokens->style-dictionary+ (defn tokens->style-dictionary+
"Resolves references and math expressions using StyleDictionary. "Resolves references and math expressions using StyleDictionary.
Returns a promise with the resolved dictionary." Returns a promise with the resolved dictionary."
@ -88,16 +79,16 @@
(resolve-sd-tokens+ config))] (resolve-sd-tokens+ config))]
(let [resolved-tokens (reduce (let [resolved-tokens (reduce
(fn [acc ^js cur] (fn [acc ^js cur]
(let [value (.-value cur) (let [id (uuid (.-uuid (.-id cur)))
resolved-value (d/parse-double (.-value cur)) origin-token (get tokens id)
original-value (-> cur .-original .-value) parsed-value (wtt/parse-token-value (.-value cur))
id (uuid (.-uuid (.-id cur))) resolved-token (if (not parsed-value)
missing-reference? (and (not resolved-value) (assoc origin-token :errors [:style-dictionary/missing-reference])
(re-find #"\{" value) (assoc origin-token
(= value original-value))] :resolved-value (:value parsed-value)
(cond-> (assoc-in acc [id :resolved-value] resolved-value) :resolved-unit (:unit parsed-value)))]
missing-reference? (update-in [id :errors] (fnil conj #{}) :style-dictionary/missing-reference)))) (assoc acc (wtt/token-identifier resolved-token) resolved-token)))
tokens sd-tokens)] {} sd-tokens)]
(when debug? (when debug?
(js/console.log "Resolved tokens" resolved-tokens)) (js/console.log "Resolved tokens" resolved-tokens))
resolved-tokens))) resolved-tokens)))
@ -148,20 +139,31 @@
(comment (comment
(defonce !output (atom nil)) (defonce !output (atom nil))
(-> @refs/workspace-tokens
(resolve-tokens+ {:debug? false})
(.then js/console.log))
(-> (resolve-workspace-tokens+ {:debug? true}) (-> (resolve-workspace-tokens+ {:debug? true})
(p/then #(reset! !output %))) (p/then #(reset! !output %)))
@!output
(->> @refs/workspace-tokens (->> @refs/workspace-tokens
(resolve-tokens+)) (resolve-tokens+)
(#(doto % js/console.log)))
(-> (->
(clj->js {"a" {:name "a" :value "5"} (clj->js {"a" {:name "a" :value "5"}
"b" {:name "b" :value "{a} * 2"}}) "b" {:name "b" :value "{a} * 2"}})
(#(resolve-sd-tokens+ % {:debug? true}))) (#(resolve-sd-tokens+ % {:debug? true})))
(defonce output (atom nil))
(require '[shadow.resource])
(let [example (-> (shadow.resource/inline "./data/example-tokens-set.json") (let [example (-> (shadow.resource/inline "./data/example-tokens-set.json")
(js/JSON.parse) (js/JSON.parse)
.-core)] .-core)]
(resolve-sd-tokens+ example {:debug? true})) (.then (resolve-sd-tokens+ example {:debug? true})
#(reset! output %)))
nil) nil)

View file

@ -4,6 +4,32 @@
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str])) [cuerdas.core :as str]))
(def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value."
#"^\s*(-?[0-9]+\.?[0-9]*)(px|%)?\s*$")
(defn parse-token-value
"Parses a resolved value and separates the unit from the value.
Returns a map of {:value `number` :unit `string`}."
[value]
(cond
(number? value) {:value value}
(string? value) (when-let [[_ value unit] (re-find parseable-token-value-regexp value)]
(when-let [parsed-value (d/parse-double value)]
{:value parsed-value
:unit unit}))))
(defn find-token-references
"Finds token reference values in `value-string` and returns a set with all contained namespaces."
[value-string]
(some->> (re-seq #"\{([^}]*)\}" value-string)
(map second)
(into #{})))
(defn token-identifier [{:keys [name] :as _token}]
name)
(defn resolve-token-value [{:keys [value resolved-value] :as _token}] (defn resolve-token-value [{:keys [value resolved-value] :as _token}]
(or (or
resolved-value resolved-value
@ -11,17 +37,17 @@
(defn attributes-map (defn attributes-map
"Creats an attributes map using collection of `attributes` for `id`." "Creats an attributes map using collection of `attributes` for `id`."
[attributes id] [attributes token]
(->> (map (fn [attr] {attr id}) attributes) (->> (map (fn [attr] [attr (token-identifier token)]) attributes)
(into {}))) (into {})))
(defn remove-attributes-for-token-id (defn remove-attributes-for-token
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`." "Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
[attributes token-id applied-tokens] [attributes token applied-tokens]
(let [attr? (set attributes)] (let [attr? (set attributes)]
(->> (remove (fn [[k v]] (->> (remove (fn [[k v]]
(and (attr? k) (and (attr? k)
(= v token-id))) (= v (token-identifier token))))
applied-tokens) applied-tokens)
(into {})))) (into {}))))
@ -29,7 +55,7 @@
"Test if `token` is applied to a `shape` on single `token-attribute`." "Test if `token` is applied to a `shape` on single `token-attribute`."
[token shape token-attribute] [token shape token-attribute]
(when-let [id (get-in shape [:applied-tokens token-attribute])] (when-let [id (get-in shape [:applied-tokens token-attribute])]
(= (:id token) id))) (= (token-identifier token) id)))
(defn token-applied? (defn token-applied?
"Test if `token` is applied to a `shape` with at least one of the one of the given `token-attributes`." "Test if `token` is applied to a `shape` with at least one of the one of the given `token-attributes`."

View file

@ -15,8 +15,8 @@
(defn apply-token-to-shape [file shape-label token-label attributes] (defn apply-token-to-shape [file shape-label token-label attributes]
(let [first-page-id (get-in file [:data :pages 0]) (let [first-page-id (get-in file [:data :pages 0])
shape-id (thi/id shape-label) shape-id (thi/id shape-label)
token-id (thi/id token-label) token (get-token file token-label)
applied-attributes (wtt/attributes-map attributes token-id)] applied-attributes (wtt/attributes-map attributes token)]
(update-in file [:data (update-in file [:data
:pages-index first-page-id :pages-index first-page-id
:objects shape-id :objects shape-id

View file

@ -5,6 +5,7 @@
[app.common.test-helpers.files :as cthf] [app.common.test-helpers.files :as cthf]
[app.common.test-helpers.shapes :as cths] [app.common.test-helpers.shapes :as cths]
[app.main.ui.workspace.tokens.changes :as wtch] [app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true] [cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp] [frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths] [frontend-tests.helpers.state :as ths]
@ -31,7 +32,32 @@
:type :border-radius}))) :type :border-radius})))
(t/deftest test-apply-token (t/deftest test-apply-token
(t/testing "applying a token twice with the same attributes will override the previous applied token" (t/testing "applies token to shape and updates shape attributes to resolved value"
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file :token-2)
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-2' (toht/get-token file' :token-2)
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2'))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-multiple-tokens
(t/testing "applying a token twice with the same attributes will override the previously applied tokens values"
(t/async (t/async
done done
(let [file (setup-file) (let [file (setup-file)
@ -51,11 +77,13 @@
(let [file' (ths/get-file-from-store new-state) (let [file' (ths/get-file-from-store new-state)
token-2' (toht/get-token file' :token-2) token-2' (toht/get-token file' :token-2)
rect-1' (cths/get-shape file' :rect-1)] rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2'))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24)) (t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))) (t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-token-overwrite (t/deftest test-apply-token-overwrite
(t/testing "removes old token attributes and applies only single attribute" (t/testing "removes old token attributes and applies only single attribute"
@ -87,32 +115,9 @@
(t/testing "other border-radius attributes got removed" (t/testing "other border-radius attributes got removed"
(t/is (nil? (:rx (:applied-tokens rect-1'))))) (t/is (nil? (:rx (:applied-tokens rect-1')))))
(t/testing "r1 got applied with :token-2" (t/testing "r1 got applied with :token-2"
(t/is (= (:r1 (:applied-tokens rect-1')) (:id token-2')))) (t/is (= (:r1 (:applied-tokens rect-1')) (wtt/token-identifier token-2'))))
(t/testing "while :r4 was kept" (t/testing "while :r4 was kept"
(t/is (= (:r4 (:applied-tokens rect-1')) (:id token-1')))))))))));))))))))))) (t/is (= (:r4 (:applied-tokens rect-1')) (wtt/token-identifier token-1')))))))))));)))))))))))
(t/deftest test-apply-border-radius
(t/testing "applies radius token and updates the shapes radius"
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file :token-2)
:on-update-shape wtch/update-shape-radius-all})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-2' (toht/get-token file' :token-2)
rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24)))))))))
(t/deftest test-apply-dimensions (t/deftest test-apply-dimensions
(t/testing "applies dimensions token and updates the shapes width and height" (t/testing "applies dimensions token and updates the shapes width and height"
@ -134,11 +139,13 @@
(let [file' (ths/get-file-from-store new-state) (let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' :token-target) token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)] rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100)) (t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))) (t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-sizing (t/deftest test-apply-sizing
(t/testing "applies sizing token and updates the shapes width and height" (t/testing "applies sizing token and updates the shapes width and height"
@ -160,11 +167,13 @@
(let [file' (ths/get-file-from-store new-state) (let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' :token-target) token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)] rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100)) (t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))) (t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-opacity (t/deftest test-apply-opacity
(t/testing "applies opacity token and updates the shapes opacity" (t/testing "applies opacity token and updates the shapes opacity"
@ -207,13 +216,13 @@
token-opacity-percent (toht/get-token file' :opacity-percent) token-opacity-percent (toht/get-token file' :opacity-percent)
token-opacity-invalid (toht/get-token file' :opacity-invalid)] token-opacity-invalid (toht/get-token file' :opacity-invalid)]
(t/testing "float value got translated to float and applied to opacity" (t/testing "float value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-1')) (:id token-opacity-float))) (t/is (= (:opacity (:applied-tokens rect-1')) (wtt/token-identifier token-opacity-float)))
(t/is (= (:opacity rect-1') 0.3))) (t/is (= (:opacity rect-1') 0.3)))
(t/testing "percentage value got translated to float and applied to opacity" (t/testing "percentage value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-2')) (:id token-opacity-percent))) (t/is (= (:opacity (:applied-tokens rect-2')) (wtt/token-identifier token-opacity-percent)))
(t/is (= (:opacity rect-2') 0.4))) (t/is (= (:opacity rect-2') 0.4)))
(t/testing "invalid opacity value got applied but did not change shape" (t/testing "invalid opacity value got applied but did not change shape"
(t/is (= (:opacity (:applied-tokens rect-3')) (:id token-opacity-invalid))) (t/is (= (:opacity (:applied-tokens rect-3')) (wtt/token-identifier token-opacity-invalid)))
(t/is (nil? (:opacity rect-3'))))))))))) (t/is (nil? (:opacity rect-3')))))))))))
(t/deftest test-apply-rotation (t/deftest test-apply-rotation
@ -237,7 +246,7 @@
token-target' (toht/get-token file' :token-target) token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)] rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rotation (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:rotation (:applied-tokens rect-1')) (wtt/token-identifier token-target')))
(t/is (= (:rotation rect-1') 120))))))))) (t/is (= (:rotation rect-1') 120)))))))))
(t/deftest test-apply-stroke-width (t/deftest test-apply-stroke-width
@ -267,10 +276,10 @@
rect-with-stroke' (cths/get-shape file' :rect-1) rect-with-stroke' (cths/get-shape file' :rect-1)
rect-without-stroke' (cths/get-shape file' :rect-2)] rect-without-stroke' (cths/get-shape file' :rect-2)]
(t/testing "token got applied to rect with stroke and shape stroke got updated" (t/testing "token got applied to rect with stroke and shape stroke got updated"
(t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:id token-target'))) (t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (wtt/token-identifier token-target')))
(t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10))) (t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10)))
(t/testing "token got applied to rect without stroke but shape didnt get updated" (t/testing "token got applied to rect without stroke but shape didnt get updated"
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:id token-target'))) (t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (wtt/token-identifier token-target')))
(t/is (empty? (:strokes rect-without-stroke'))))))))))) (t/is (empty? (:strokes rect-without-stroke')))))))))))
(t/deftest test-toggle-token-none (t/deftest test-toggle-token-none
@ -294,10 +303,10 @@
rect-2' (cths/get-shape file' :rect-2)] rect-2' (cths/get-shape file' :rect-2)]
(t/is (some? (:applied-tokens rect-1'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (some? (:applied-tokens rect-2'))) (t/is (some? (:applied-tokens rect-2')))
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:rx (:applied-tokens rect-2')) (:id token-2'))) (t/is (= (:rx (:applied-tokens rect-2')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-2')) (:id token-2'))) (t/is (= (:ry (:applied-tokens rect-2')) (wtt/token-identifier token-2')))
(t/is (= (:rx rect-1') 24)) (t/is (= (:rx rect-1') 24))
(t/is (= (:rx rect-2') 24))))))))) (t/is (= (:rx rect-2') 24)))))))))
@ -361,10 +370,10 @@
rect-with-other-token-2' (cths/get-shape file' :rect-3)] rect-with-other-token-2' (cths/get-shape file' :rect-3)]
(t/testing "token got applied to all shapes" (t/testing "token got applied to all shapes"
(t/is (= (:rx (:applied-tokens rect-with-other-token-1')) (:id target-token))) (t/is (= (:rx (:applied-tokens rect-with-other-token-1')) (wtt/token-identifier target-token)))
(t/is (= (:rx (:applied-tokens rect-without-token')) (:id target-token))) (t/is (= (:rx (:applied-tokens rect-without-token')) (wtt/token-identifier target-token)))
(t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (:id target-token))) (t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (wtt/token-identifier target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-1')) (:id target-token))) (t/is (= (:ry (:applied-tokens rect-with-other-token-1')) (wtt/token-identifier target-token)))
(t/is (= (:ry (:applied-tokens rect-without-token')) (:id target-token))) (t/is (= (:ry (:applied-tokens rect-without-token')) (wtt/token-identifier target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (:id target-token))))))))))) (t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (wtt/token-identifier target-token)))))))))))

View file

@ -1,20 +0,0 @@
;; 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 INC
(ns token-tests.style-dictionary-test
(:require
[app.main.ui.workspace.tokens.style-dictionary :as wtsd]
[cljs.test :as t :include-macros true]))
(t/deftest test-find-token-references
;; Return references
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo} + {bar}")))
;; Ignore non reference text
(t/is (= #{"foo.bar.baz"} (wtsd/find-token-references "{foo.bar.baz} + something")))
;; No references found
(t/is (nil? (wtsd/find-token-references "1 + 2")))
;; Edge-case: Ignore unmatched closing parens
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo}} + {bar}"))))

View file

@ -9,67 +9,95 @@
[app.main.ui.workspace.tokens.token :as wtt] [app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true])) [cljs.test :as t :include-macros true]))
(t/deftest test-parse-token-value
(t/testing "parses double from a token value"
(t/is (= {:value 100.1 :unit nil} (wtt/parse-token-value "100.1")))
(t/is (= {:value -9 :unit nil} (wtt/parse-token-value "-9"))))
(t/testing "trims white-space"
(t/is (= {:value -1.3 :unit nil} (wtt/parse-token-value " -1.3 "))))
(t/testing "parses unit: px"
(t/is (= {:value 70.3 :unit "px"} (wtt/parse-token-value " 70.3px "))))
(t/testing "parses unit: %"
(t/is (= {:value -10 :unit "%"} (wtt/parse-token-value "-10%"))))
(t/testing "parses unit: px")
(t/testing "returns nil for any invalid characters"
(t/is (nil? (wtt/parse-token-value " -1.3a "))))
(t/testing "doesnt accept invalid double"
(t/is (nil? (wtt/parse-token-value ".3")))))
(t/deftest find-token-references
(t/testing "finds references inside curly braces in a string"
(t/is (= #{"foo" "bar"} (wtt/find-token-references "{foo} + {bar}")))
(t/testing "ignores extra text"
(t/is (= #{"foo.bar.baz"} (wtt/find-token-references "{foo.bar.baz} + something"))))
(t/testing "ignores string without references"
(t/is (nil? (wtt/find-token-references "1 + 2"))))
(t/testing "handles edge-case for extra curly braces"
(t/is (= #{"foo" "bar"} (wtt/find-token-references "{foo}} + {bar}"))))))
(t/deftest remove-attributes-for-token-id (t/deftest remove-attributes-for-token-id
(t/testing "removes attributes matching the `token-id`, keeps other attributes" (t/testing "removes attributes matching the `token`, keeps other attributes"
(t/is (= {:ry :b} (t/is (= {:ry "b"}
(wtt/remove-attributes-for-token-id (wtt/remove-attributes-for-token #{:rx :ry} {:name "a"} {:rx "a" :ry "b"})))))
#{:rx :ry} :a {:rx :a :ry :b})))))
(t/deftest token-applied-test (t/deftest token-applied-test
(t/testing "matches passed token with `:token-attributes`" (t/testing "matches passed token with `:token-attributes`"
(t/is (true? (wtt/token-applied? {:id :a} {:applied-tokens {:x :a}} #{:x})))) (t/is (true? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "doesn't match empty token" (t/testing "doesn't match empty token"
(t/is (nil? (wtt/token-applied? {} {:applied-tokens {:x :a}} #{:x})))) (t/is (nil? (wtt/token-applied? {} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "does't match passed token `:id`" (t/testing "does't match passed token `:id`"
(t/is (nil? (wtt/token-applied? {:id :b} {:applied-tokens {:x :a}} #{:x})))) (t/is (nil? (wtt/token-applied? {:name "b"} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "doesn't match passed `:token-attributes`" (t/testing "doesn't match passed `:token-attributes`"
(t/is (nil? (wtt/token-applied? {:id :a} {:applied-tokens {:x :a}} #{:y}))))) (t/is (nil? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:y})))))
(t/deftest token-applied-attributes (t/deftest token-applied-attributes
(t/is (= #{:x} (wtt/token-applied-attributes {:id :a} (t/is (= #{:x} (wtt/token-applied-attributes {:name "a"}
{:applied-tokens {:x :a :y :b}} {:applied-tokens {:x "a" :y "b"}}
#{:x :missing})))) #{:x :missing}))))
(t/deftest shapes-ids-by-applied-attributes (t/deftest shapes-ids-by-applied-attributes
(t/testing "Returns set of matched attributes that fit the applied token" (t/testing "Returns set of matched attributes that fit the applied token"
(let [attributes #{:x :y :z} (let [attributes #{:x :y :z}
shape-applied-x {:id :shape-applied-x shape-applied-x {:id "shape-applied-x"
:applied-tokens {:x 1}} :applied-tokens {:x "1"}}
shape-applied-y {:id :shape-applied-y shape-applied-y {:id "shape-applied-y"
:applied-tokens {:y 1}} :applied-tokens {:y "1"}}
shape-applied-x-y {:id :shape-applied-x-y shape-applied-x-y {:id "shape-applied-x-y"
:applied-tokens {:x 1 :y 1}} :applied-tokens {:x "1" :y "1"}}
shape-applied-none {:id :shape-applied-none shape-applied-none {:id "shape-applied-none"
:applied-tokens {}} :applied-tokens {}}
shape-applied-all {:id :shape-applied-all shape-applied-all {:id "shape-applied-all"
:applied-tokens {:x 1 :y 1 :z 1}} :applied-tokens {:x "1" :y "1" :z "1"}}
ids-set (fn [& xs] (into #{} (map :id xs))) shape-ids (fn [& xs] (into #{} (map :id xs)))
shapes [shape-applied-x shapes [shape-applied-x
shape-applied-y shape-applied-y
shape-applied-x-y shape-applied-x-y
shape-applied-all shape-applied-all
shape-applied-none] shape-applied-none]
expected (wtt/shapes-ids-by-applied-attributes {:id 1} shapes attributes)] expected (wtt/shapes-ids-by-applied-attributes {:name "1"} shapes attributes)]
(t/is (= (:x expected) (ids-set shape-applied-x (t/is (= (:x expected) (shape-ids shape-applied-x
shape-applied-x-y shape-applied-x-y
shape-applied-all))) shape-applied-all)))
(t/is (= (:y expected) (ids-set shape-applied-y (t/is (= (:y expected) (shape-ids shape-applied-y
shape-applied-x-y shape-applied-x-y
shape-applied-all))) shape-applied-all)))
(t/is (= (:z expected) (ids-set shape-applied-all))) (t/is (= (:z expected) (shape-ids shape-applied-all)))
(t/is (true? (wtt/shapes-applied-all? expected (ids-set shape-applied-all) attributes))) (t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) attributes)))
(t/is (false? (wtt/shapes-applied-all? expected (apply ids-set shapes) attributes)))))) (t/is (false? (wtt/shapes-applied-all? expected (apply shape-ids shapes) attributes)))
(shape-ids shape-applied-x
shape-applied-x-y
shape-applied-all))))
(t/deftest tokens-applied-test (t/deftest tokens-applied-test
(t/testing "is true when single shape matches the token and attributes" (t/testing "is true when single shape matches the token and attributes"
(t/is (true? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}} (t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x :b}}] {:applied-tokens {:x "b"}}]
#{:x})))) #{:x}))))
(t/testing "is false when no shape matches the token or attributes" (t/testing "is false when no shape matches the token or attributes"
(t/is (nil? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :b}} (t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
{:applied-tokens {:x :b}}] {:applied-tokens {:x "b"}}]
#{:x}))) #{:x})))
(t/is (nil? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}} (t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x :a}}] {:applied-tokens {:x "a"}}]
#{:y}))))) #{:y})))))
(t/deftest name->path-test (t/deftest name->path-test