0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-21 06:02:32 -05:00

Merge branch 'token-studio-develop' into token-sets-ui

This commit is contained in:
Akshay Gupta 2024-08-20 17:02:00 +05:30
commit 1a3184d327
No known key found for this signature in database
14 changed files with 335 additions and 258 deletions

View file

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

View file

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

View file

@ -29,6 +29,8 @@
[app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.editable-select :refer [editable-select]]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token-types :as wtty]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
@ -985,18 +987,25 @@
(mf/use-fn
(mf/deps ids)
(fn [type prop value]
(let [token-value (wtc/maybe-resolve-token-value value)
val (or token-value (mth/finite value 0))]
(let [token-identifier (wtt/token-identifier value)
val (or token-identifier (mth/finite value 0))
on-update-shape wtch/update-layout-padding]
(cond
(and (= type :simple) (= prop :p1))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}
:applied-tokens {:padding-p1 (if token-value (:id value) nil)
:padding-p3 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
:attributes #{:p1 :p3}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p1 :p3})))
(and (= type :simple) (= prop :p2))
(st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}
:applied-tokens {:padding-p2 (if token-value (:id value) nil)
:padding-p4 (if token-value (:id value) nil)}}))
(if token-identifier
(st/emit! (wtch/apply-token {:shape-ids ids
:attributes #{:p2 :p4}
:token value
:on-update-shape on-update-shape}))
(st/emit! (on-update-shape value ids #{:p2 :p4})))
(some? prop)
(st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))))

View file

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

View file

@ -34,11 +34,10 @@
(watch [_ state _]
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
(rx/mapcat
(fn [sd-tokens]
(fn [resolved-tokens]
(let [undo-id (js/Symbol)
resolved-value (-> (get sd-tokens (:id token))
(wtt/resolve-token-value))
tokenized-attributes (wtt/attributes-map attributes (:id token))]
resolved-value (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value])
tokenized-attributes (wtt/attributes-map attributes token)]
(rx/of
(dwu/start-undo-transaction undo-id)
(dwsh/update-shapes shape-ids (fn [shape]
@ -58,7 +57,7 @@
ptk/WatchEvent
(watch [_ _ _]
(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
shape-ids
(fn [shape]
@ -137,6 +136,9 @@
(zipmap (repeat value)))]
{:layout-gap layout-gap}))
(defn update-layout-padding [value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat value))}))
(defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing
ptk/WatchEvent
@ -149,15 +151,6 @@
(rx/of
(dwsl/update-layout layout-shape-ids layout-attributes))))))
(defn update-layout-spacing-column [value shape-ids]
(ptk/reify ::update-layout-spacing-column
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(for [shape-id shape-ids]
(let [layout-update {:layout-gap {:column-gap value :row-gap value}}]
(dwsl/update-layout [shape-id] layout-update)))))))
(defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position
ptk/WatchEvent

View file

@ -81,8 +81,7 @@
(concat [all-action] single-actions)))
(defn spacing-attribute-actions [{:keys [token selected-shapes] :as context-data}]
(let [on-update-shape (fn [resolved-value shape-ids attrs]
(dwsl/update-layout shape-ids {:layout-padding (zipmap attrs (repeat resolved-value))}))
(let [on-update-shape-padding wtch/update-layout-padding
padding-attrs {:p1 "Top"
:p2 "Right"
:p3 "Bottom"
@ -105,7 +104,7 @@
:shape-ids shape-ids}]
(if all-selected?
(st/emit! (wtch/unapply-token props))
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape))))))}
(st/emit! (wtch/apply-token (assoc props :on-update-shape on-update-shape-padding))))))}
{:title "Horizontal"
:selected? horizontal-padding-selected?
:action (fn []
@ -116,7 +115,7 @@
horizontal-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove horizontal-attributes))
:else (wtch/apply-token (assoc props
:attributes horizontal-attributes
:on-update-shape on-update-shape)))]
:on-update-shape on-update-shape-padding)))]
(st/emit! event)))}
{:title "Vertical"
:selected? vertical-padding-selected?
@ -128,7 +127,7 @@
vertical-padding-selected? (wtch/apply-token (assoc props :attributes-to-remove vertical-attributes))
:else (wtch/apply-token (assoc props
:attributes vertical-attributes
:on-update-shape on-update-shape)))]
:on-update-shape on-update-shape-padding)))]
(st/emit! event)))}]
single-padding-items (->> padding-attrs
(map (fn [[attr title]]
@ -149,7 +148,7 @@
all-selected? (-> (assoc props :attributes-to-remove all-padding-attrs)
(wtch/apply-token))
selected? (wtch/unapply-token props)
:else (-> (assoc props :on-update-shape on-update-shape)
:else (-> (assoc props :on-update-shape on-update-shape-padding)
(wtch/apply-token)))]
(st/emit! event))}))))
gap-items (all-or-sepearate-actions {:attribute-labels {:column-gap "Column Gap"

View file

@ -14,6 +14,7 @@
width: 100%;
padding: $s-8;
border-radius: $br-8;
position: relative;
cursor: pointer;
background: transparent;

View file

@ -8,14 +8,15 @@
(:require-macros [app.main.style :as stl])
(:require
["lodash.debounce" :as debounce]
[app.main.ui.workspace.tokens.update :as wtu]
[app.common.data :as d]
[app.main.data.modal :as modal]
[app.main.data.tokens :as dt]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.tokens.common :as tokens.common]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt]
[app.main.ui.workspace.tokens.update :as wtu]
[app.util.dom :as dom]
[cuerdas.core :as str]
[malli.core :as m]
@ -92,7 +93,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; 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.
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)]
(cond
empty-input? (p/rejected nil)
@ -104,7 +105,7 @@ Token names should only contain letters and digits separated by . characters.")}
(-> (sd/resolve-tokens+ new-tokens #_ {:debug? true})
(p/then
(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
resolved-value (p/resolved resolved-token)
(sd/missing-reference-error? errors) (p/rejected :error/token-missing-reference)
@ -141,14 +142,15 @@ Token names should only contain letters and digits separated by . characters.")}
(mf/defc form
{::mf/wrap-props false}
[{:keys [token token-type] :as _args}]
(let [tokens (sd/use-resolved-workspace-tokens)
(let [tokens (mf/deref refs/workspace-tokens)
resolved-tokens (sd/use-resolved-tokens tokens)
token-path (mf/use-memo
(mf/deps (:name token))
#(wtt/token-name->path (:name token)))
tokens-tree (mf/use-memo
(mf/deps token-path tokens)
(mf/deps token-path resolved-tokens)
(fn []
(-> (wtt/token-names-tree tokens)
(-> (wtt/token-names-tree resolved-tokens)
;; Allow setting editing token to it's own path
(d/dissoc-in token-path))))
@ -177,7 +179,7 @@ Token names should only contain letters and digits separated by . characters.")}
;; Value
value-ref (mf/use-var (:value token))
token-resolve-result (mf/use-state (get-in tokens [(:id token) :resolved-value]))
token-resolve-result (mf/use-state (get-in resolved-tokens [(wtt/token-identifier token) :resolved-value]))
set-resolve-value (mf/use-callback
(fn [token-or-err]
(let [v (cond
@ -219,7 +221,7 @@ Token names should only contain letters and digits separated by . characters.")}
(not valid-description-field?))
on-submit (mf/use-callback
(mf/deps validate-name validate-descripion token tokens)
(mf/deps validate-name validate-descripion token resolved-tokens)
(fn [e]
(dom/prevent-default e)
;; We have to re-validate the current form values before submitting
@ -236,7 +238,7 @@ Token names should only contain letters and digits separated by . characters.")}
(validate-token-value+ {:input final-value
:name-value final-name
:token token
:tokens tokens})])
:tokens resolved-tokens})])
(p/finally (fn [result err]
;; The result should be a vector of all resolved validations
;; We do not handle the error case as it will be handled by the components validations

View file

@ -2,18 +2,15 @@
(:require
["@tokens-studio/sd-transforms" :as sd-transforms]
["style-dictionary$default" :as sd]
[app.common.data :as d]
[app.main.refs :as refs]
[app.main.ui.workspace.tokens.token :as wtt]
[cuerdas.core :as str]
[promesa.core :as p]
[rumext.v2 :as mf]
[shadow.resource]))
[rumext.v2 :as mf]))
(def StyleDictionary
"The global StyleDictionary instance used as an external library for now,
as the package would need webpack to be bundled,
because shadow-cljs doesn't support some of the more modern bundler features."
"Initiates the global StyleDictionary instance with transforms
from tokens-studio used to parse and resolved token values."
(do
(sd-transforms/registerTransforms sd)
(.registerFormat sd #js {:name "custom/json"
@ -23,13 +20,6 @@
;; 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+
"Resolves references and math expressions using StyleDictionary.
Returns a promise with the resolved dictionary."
@ -88,16 +78,16 @@
(resolve-sd-tokens+ config))]
(let [resolved-tokens (reduce
(fn [acc ^js cur]
(let [value (.-value cur)
resolved-value (d/parse-double (.-value cur))
original-value (-> cur .-original .-value)
id (uuid (.-uuid (.-id cur)))
missing-reference? (and (not resolved-value)
(re-find #"\{" value)
(= value original-value))]
(cond-> (assoc-in acc [id :resolved-value] resolved-value)
missing-reference? (update-in [id :errors] (fnil conj #{}) :style-dictionary/missing-reference))))
tokens sd-tokens)]
(let [id (uuid (.-uuid (.-id cur)))
origin-token (get tokens id)
parsed-value (wtt/parse-token-value (.-value cur))
resolved-token (if (not parsed-value)
(assoc origin-token :errors [:style-dictionary/missing-reference])
(assoc origin-token
:resolved-value (:value parsed-value)
:resolved-unit (:unit parsed-value)))]
(assoc acc (wtt/token-identifier resolved-token) resolved-token)))
{} sd-tokens)]
(when debug?
(js/console.log "Resolved tokens" resolved-tokens))
resolved-tokens)))
@ -148,20 +138,31 @@
(comment
(defonce !output (atom nil))
(-> @refs/workspace-tokens
(resolve-tokens+ {:debug? false})
(.then js/console.log))
(-> (resolve-workspace-tokens+ {:debug? true})
(p/then #(reset! !output %)))
@!output
(->> @refs/workspace-tokens
(resolve-tokens+))
(resolve-tokens+)
(#(doto % js/console.log)))
(->
(clj->js {"a" {:name "a" :value "5"}
"b" {:name "b" :value "{a} * 2"}})
(#(resolve-sd-tokens+ % {:debug? true})))
(defonce output (atom nil))
(require '[shadow.resource])
(let [example (-> (shadow.resource/inline "./data/example-tokens-set.json")
(js/JSON.parse)
.-core)]
(resolve-sd-tokens+ example {:debug? true}))
(.then (resolve-sd-tokens+ example {:debug? true})
#(reset! output %)))
nil)

View file

@ -4,6 +4,32 @@
[clojure.set :as set]
[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}]
(or
resolved-value
@ -11,17 +37,17 @@
(defn attributes-map
"Creats an attributes map using collection of `attributes` for `id`."
[attributes id]
(->> (map (fn [attr] {attr id}) attributes)
[attributes token]
(->> (map (fn [attr] [attr (token-identifier token)]) attributes)
(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`."
[attributes token-id applied-tokens]
[attributes token applied-tokens]
(let [attr? (set attributes)]
(->> (remove (fn [[k v]]
(and (attr? k)
(= v token-id)))
(= v (token-identifier token))))
applied-tokens)
(into {}))))
@ -29,7 +55,7 @@
"Test if `token` is applied to a `shape` on single `token-attribute`."
[token shape token-attribute]
(when-let [id (get-in shape [:applied-tokens token-attribute])]
(= (:id token) id)))
(= (token-identifier token) id)))
(defn token-applied?
"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]
(let [first-page-id (get-in file [:data :pages 0])
shape-id (thi/id shape-label)
token-id (thi/id token-label)
applied-attributes (wtt/attributes-map attributes token-id)]
token (get-token file token-label)
applied-attributes (wtt/attributes-map attributes token)]
(update-in file [:data
:pages-index first-page-id
:objects shape-id

View file

@ -5,6 +5,7 @@
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.shapes :as cths]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
@ -31,31 +32,58 @@
:type :border-radius})))
(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-1)
:on-update-shape wtch/update-shape-radius-all})
(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)))))))))
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
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-1)
:on-update-shape wtch/update-shape-radius-all})
(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-token-overwrite
(t/testing "removes old token attributes and applies only single attribute"
@ -87,58 +115,37 @@
(t/testing "other border-radius attributes got removed"
(t/is (nil? (:rx (:applied-tokens rect-1')))))
(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/is (= (:r4 (:applied-tokens rect-1')) (:id 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/is (= (:r4 (:applied-tokens rect-1')) (wtt/token-identifier token-1')))))))))));)))))))))))
(t/deftest test-apply-dimensions
(t/testing "applies dimensions token and updates the shapes width and height"
(t/async
done
(let [file (-> (setup-file)
(toht/add-token :token-target {:value "100"
:name "dimensions.sm"
:type :dimensions}))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file :token-target)
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100)))))))))
done
(let [file (-> (setup-file)
(toht/add-token :token-target {:value "100"
:name "dimensions.sm"
:type :dimensions}))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file :token-target)
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier 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 (= (:height rect-1') 100))))))))))
(t/deftest test-apply-sizing
(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)
token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target')))
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100)))))))))
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier 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 (= (:height rect-1') 100))))))))))
(t/deftest test-apply-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-invalid (toht/get-token file' :opacity-invalid)]
(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/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/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/deftest test-apply-rotation
@ -237,7 +246,7 @@
token-target' (toht/get-token file' :token-target)
rect-1' (cths/get-shape file' :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/deftest test-apply-stroke-width
@ -267,10 +276,10 @@
rect-with-stroke' (cths/get-shape file' :rect-1)
rect-without-stroke' (cths/get-shape file' :rect-2)]
(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/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/deftest test-toggle-token-none
@ -294,10 +303,10 @@
rect-2' (cths/get-shape file' :rect-2)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (some? (:applied-tokens rect-2')))
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2')))
(t/is (= (:rx (:applied-tokens rect-2')) (:id token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2')))
(t/is (= (:ry (:applied-tokens rect-2')) (:id token-2')))
(t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:rx (:applied-tokens rect-2')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
(t/is (= (:ry (:applied-tokens rect-2')) (wtt/token-identifier token-2')))
(t/is (= (:rx rect-1') 24))
(t/is (= (:rx rect-2') 24)))))))))
@ -361,10 +370,10 @@
rect-with-other-token-2' (cths/get-shape file' :rect-3)]
(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-without-token')) (:id target-token)))
(t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (: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')) (wtt/token-identifier 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-without-token')) (:id target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (: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')) (wtt/token-identifier target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (wtt/token-identifier target-token)))))))))))

View file

@ -1,20 +1,37 @@
;; 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]))
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[cljs.test :as t :include-macros true]
[promesa.core :as p]))
(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}"))))
(def border-radius-token
{:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9"
:value "12px"
:name "borderRadius.sm"
:type :border-radius})
(def reference-border-radius-token
{:id #uuid "b9448d78-fd5b-4e3d-aa32-445904063f5b"
:value "{borderRadius.sm} * 2"
:name "borderRadius.md-with-dashes"
:type :border-radius})
(def tokens {(:id border-radius-token) border-radius-token
(:id reference-border-radius-token) reference-border-radius-token})
(t/deftest resolve-tokens-test
(t/async
done
(t/testing "resolves tokens using style-dictionary"
(-> (sd/resolve-tokens+ tokens)
(p/finally (fn [resolved-tokens]
(let [expected-tokens {"borderRadius.sm"
(assoc border-radius-token
:resolved-value 12
:resolved-unit "px")
"borderRadius.md-with-dashes"
(assoc reference-border-radius-token
:resolved-value 24
:resolved-unit "px")}]
(t/is (= expected-tokens resolved-tokens))
(done))))))))

View file

@ -9,67 +9,95 @@
[app.main.ui.workspace.tokens.token :as wtt]
[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/testing "removes attributes matching the `token-id`, keeps other attributes"
(t/is (= {:ry :b}
(wtt/remove-attributes-for-token-id
#{:rx :ry} :a {:rx :a :ry :b})))))
(t/testing "removes attributes matching the `token`, keeps other attributes"
(t/is (= {:ry "b"}
(wtt/remove-attributes-for-token #{:rx :ry} {:name "a"} {:rx "a" :ry "b"})))))
(t/deftest token-applied-test
(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/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/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/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/is (= #{:x} (wtt/token-applied-attributes {:id :a}
{:applied-tokens {:x :a :y :b}}
(t/is (= #{:x} (wtt/token-applied-attributes {:name "a"}
{:applied-tokens {:x "a" :y "b"}}
#{:x :missing}))))
(t/deftest shapes-ids-by-applied-attributes
(t/testing "Returns set of matched attributes that fit the applied token"
(let [attributes #{:x :y :z}
shape-applied-x {:id :shape-applied-x
:applied-tokens {:x 1}}
shape-applied-y {:id :shape-applied-y
:applied-tokens {:y 1}}
shape-applied-x-y {:id :shape-applied-x-y
:applied-tokens {:x 1 :y 1}}
shape-applied-none {:id :shape-applied-none
shape-applied-x {:id "shape-applied-x"
:applied-tokens {:x "1"}}
shape-applied-y {:id "shape-applied-y"
:applied-tokens {:y "1"}}
shape-applied-x-y {:id "shape-applied-x-y"
:applied-tokens {:x "1" :y "1"}}
shape-applied-none {:id "shape-applied-none"
:applied-tokens {}}
shape-applied-all {:id :shape-applied-all
:applied-tokens {:x 1 :y 1 :z 1}}
ids-set (fn [& xs] (into #{} (map :id xs)))
shape-applied-all {:id "shape-applied-all"
:applied-tokens {:x "1" :y "1" :z "1"}}
shape-ids (fn [& xs] (into #{} (map :id xs)))
shapes [shape-applied-x
shape-applied-y
shape-applied-x-y
shape-applied-all
shape-applied-none]
expected (wtt/shapes-ids-by-applied-attributes {:id 1} shapes attributes)]
(t/is (= (:x expected) (ids-set shape-applied-x
shape-applied-x-y
shape-applied-all)))
(t/is (= (:y expected) (ids-set shape-applied-y
shape-applied-x-y
shape-applied-all)))
(t/is (= (:z expected) (ids-set shape-applied-all)))
(t/is (true? (wtt/shapes-applied-all? expected (ids-set shape-applied-all) attributes)))
(t/is (false? (wtt/shapes-applied-all? expected (apply ids-set shapes) attributes))))))
expected (wtt/shapes-ids-by-applied-attributes {:name "1"} shapes attributes)]
(t/is (= (:x expected) (shape-ids shape-applied-x
shape-applied-x-y
shape-applied-all)))
(t/is (= (:y expected) (shape-ids shape-applied-y
shape-applied-x-y
shape-applied-all)))
(t/is (= (:z expected) (shape-ids shape-applied-all)))
(t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) 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/testing "is true when single shape matches the token and attributes"
(t/is (true? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}}
{:applied-tokens {:x :b}}]
(t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "b"}}]
#{:x}))))
(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}}
{:applied-tokens {:x :b}}]
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
{:applied-tokens {:x "b"}}]
#{:x})))
(t/is (nil? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}}
{:applied-tokens {:x :a}}]
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "a"}}]
#{:y})))))
(t/deftest name->path-test