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

This commit is contained in:
Akshay Gupta
2024-08-20 17:02:00 +05:30
14 changed files with 335 additions and 258 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,22 +56,22 @@
(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))))
(defn maybe-apply-token-to-shape (defn maybe-apply-token-to-shape
@@ -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

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

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]
@@ -137,6 +136,9 @@
(zipmap (repeat value)))] (zipmap (repeat value)))]
{:layout-gap layout-gap})) {: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] (defn update-layout-spacing [value shape-ids attributes]
(ptk/reify ::update-layout-spacing (ptk/reify ::update-layout-spacing
ptk/WatchEvent ptk/WatchEvent
@@ -149,15 +151,6 @@
(rx/of (rx/of
(dwsl/update-layout layout-shape-ids layout-attributes)))))) (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] (defn update-shape-position [value shape-ids attributes]
(ptk/reify ::update-shape-position (ptk/reify ::update-shape-position
ptk/WatchEvent ptk/WatchEvent

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,15 @@
(: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, "Initiates the global StyleDictionary instance with transforms
as the package would need webpack to be bundled, from tokens-studio used to parse and resolved token values."
because shadow-cljs doesn't support some of the more modern bundler features."
(do (do
(sd-transforms/registerTransforms sd) (sd-transforms/registerTransforms sd)
(.registerFormat sd #js {:name "custom/json" (.registerFormat sd #js {:name "custom/json"
@@ -23,13 +20,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 +78,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 +138,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,31 +32,58 @@
: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 (t/async
done done
(let [file (setup-file) (let [file (setup-file)
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)] events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry} :attributes #{:rx :ry}
:token (toht/get-token file :token-1) :token (toht/get-token file :token-2)
:on-update-shape wtch/update-shape-radius-all}) :on-update-shape wtch/update-shape-radius-all})]]
(wtch/apply-token {:shape-ids [(:id rect-1)] (tohs/run-store-async
:attributes #{:rx :ry} store done events
:token (toht/get-token file :token-2) (fn [new-state]
:on-update-shape wtch/update-shape-radius-all})]] (let [file' (ths/get-file-from-store new-state)
(tohs/run-store-async token-2' (toht/get-token file' :token-2)
store done events rect-1' (cths/get-shape file' :rect-1)]
(fn [new-state] (t/testing "shape `:applied-tokens` got updated"
(let [file' (ths/get-file-from-store new-state) (t/is (some? (:applied-tokens rect-1')))
token-2' (toht/get-token file' :token-2) (t/is (= (:rx (:applied-tokens rect-1')) (wtt/token-identifier token-2')))
rect-1' (cths/get-shape file' :rect-1)] (t/is (= (:ry (:applied-tokens rect-1')) (wtt/token-identifier token-2'))))
(t/is (some? (:applied-tokens rect-1'))) (t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:rx rect-1') 24))
(t/is (= (:ry (:applied-tokens rect-1')) (:id token-2'))) (t/is (= (:ry rect-1') 24))))))))))
(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/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,58 +115,37 @@
(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"
(t/async (t/async
done done
(let [file (-> (setup-file) (let [file (-> (setup-file)
(toht/add-token :token-target {:value "100" (toht/add-token :token-target {:value "100"
:name "dimensions.sm" :name "dimensions.sm"
:type :dimensions})) :type :dimensions}))
store (ths/setup-store file) store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1) rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)] events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height} :attributes #{:width :height}
:token (toht/get-token file :token-target) :token (toht/get-token file :token-target)
:on-update-shape wtch/update-shape-dimensions})]] :on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async (tohs/run-store-async
store done events store done events
(fn [new-state] (fn [new-state]
(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/is (some? (:applied-tokens rect-1'))) (t/testing "shape `:applied-tokens` got updated"
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier token-target')))
(t/is (= (:width rect-1') 100)) (t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier token-target'))))
(t/is (= (:height rect-1') 100))))))))) (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/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/is (some? (:applied-tokens rect-1'))) (t/testing "shape `:applied-tokens` got updated"
(t/is (= (:width (:applied-tokens rect-1')) (:id token-target'))) (t/is (some? (:applied-tokens rect-1')))
(t/is (= (:height (:applied-tokens rect-1')) (:id token-target'))) (t/is (= (:width (:applied-tokens rect-1')) (wtt/token-identifier token-target')))
(t/is (= (:width rect-1') 100)) (t/is (= (:height (:applied-tokens rect-1')) (wtt/token-identifier token-target'))))
(t/is (= (:height rect-1') 100))))))))) (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/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 +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 (ns token-tests.style-dictionary-test
(:require (:require
[app.main.ui.workspace.tokens.style-dictionary :as wtsd] [app.main.ui.workspace.tokens.style-dictionary :as sd]
[cljs.test :as t :include-macros true])) [cljs.test :as t :include-macros true]
[promesa.core :as p]))
(t/deftest test-find-token-references (def border-radius-token
;; Return references {:id #uuid "8c868278-7c8d-431b-bbc9-7d8f15c8edb9"
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo} + {bar}"))) :value "12px"
;; Ignore non reference text :name "borderRadius.sm"
(t/is (= #{"foo.bar.baz"} (wtsd/find-token-references "{foo.bar.baz} + something"))) :type :border-radius})
;; No references found
(t/is (nil? (wtsd/find-token-references "1 + 2"))) (def reference-border-radius-token
;; Edge-case: Ignore unmatched closing parens {:id #uuid "b9448d78-fd5b-4e3d-aa32-445904063f5b"
(t/is (= #{"foo" "bar"} (wtsd/find-token-references "{foo}} + {bar}")))) :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] [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