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

Merge pull request #216 from tokens-studio/ux-improvements

UX improvements
This commit is contained in:
Florian Schrödl 2024-07-08 13:53:10 +02:00 committed by GitHub
commit 2f17b79bef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 559 additions and 86 deletions

View file

@ -5,9 +5,7 @@
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.2.2",
"browserslist": [
"defaults"
],
"browserslist": ["defaults"],
"type": "module",
"repository": {
"type": "git",
@ -29,6 +27,7 @@
"token-test:compile": "clojure -M:dev:shadow-cljs compile test-esm --config-merge '{:autorun false}'",
"token-test:run": "bun target/tests-esm.cjs",
"token-test:watch": "clojure -M:dev:shadow-cljs watch test-esm",
"token-test:nodemon": "nodemon --watch ./target/tests-esm.cjs --exec 'bun run token-test:run'",
"token-test": "yarn run token-test:compile && yarn run token-test:run",
"translations:validate": "node ./scripts/validate-translations.js",
"translations:find-unused": "node ./scripts/find-unused-translations.js",

View file

@ -163,7 +163,9 @@
:output-to "target/tests-esm.cjs"
:output-dir "target/test-esm"
:ns-regexp "^token-tests.*-test$"
:autorun true
:autorun false
:devtools {:http-port 3460}
:compiler-options
{:output-feature-set :es2020

View file

@ -18,6 +18,18 @@ If possible add video here from PR as well
## Changes
### 2024-07-05 - UX Improvements when applying tokens
[Link to PR](https://github.com/tokens-studio/tokens-studio-for-penpot/compare/token-studio-develop...ux-improvements?body=&expand=1)
- When unapplying tokens, the shape doesn't change anymore
- Multi Select behavior according to [Specs](https://github.com/tokens-studio/obsidian-docs/blob/31f0d7f98ff5ac922970f3009fe877cc02d6d0cd/Products/TS%20for%20Penpot/Specs/Token%20State%20Specs.md)
- Undo for applying tokens and change the shape is now one undo step
(before applying a token created multiple undo steps)
[Video](https://github.com/tokens-studio/tokens-studio-for-penpot/assets/1898374/01d9d429-cab1-41cd-a3ff-495003edd3e8
)
### 2024-07-01 - Disallow creating tokens at existing paths
Disallow creating tokens at an existing path.

View file

@ -22,6 +22,7 @@
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.token :as wtt]
[app.util.dom :as dom]
[app.util.timers :as timers]
[clojure.set :as set]
@ -212,7 +213,7 @@
(defn additional-actions [{:keys [token-id token-type selected-shapes] :as context-data}]
(let [attributes->actions (fn [update-fn coll]
(for [{:keys [attributes] :as item} coll]
(let [selected? (wtc/tokens-applied? {:id token-id} selected-shapes attributes)]
(let [selected? (wtt/shapes-token-applied? {:id token-id} selected-shapes attributes)]
(assoc item
:action #(update-fn context-data attributes)
:selected? selected?))))]

View file

@ -9,36 +9,25 @@
[app.common.data :as d :refer [ordered-map]]
[app.common.types.shape.radius :as ctsr]
[app.common.types.token :as ctt]
[app.libs.file-builder :as fb]
[app.main.data.tokens :as dt]
[app.main.data.workspace :as udw]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt]
[app.util.dom :as dom]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
[potok.v2.core :as ptk]
[promesa.core :as p]))
;; Helpers ---------------------------------------------------------------------
(defn token-applied?
"Test if `token` is applied to a `shape` with the given `token-attributes`."
[token shape token-attributes]
(let [{:keys [id]} token
applied-tokens (get shape :applied-tokens {})]
(some (fn [attr]
(= (get applied-tokens attr) id))
token-attributes)))
(defn tokens-applied?
"Test if `token` is applied to to any of `shapes` with the given `token-attributes`."
[token shapes token-attributes]
(some #(token-applied? token % token-attributes) shapes))
(defn resolve-token-value [{:keys [value resolved-value] :as token}]
(or
resolved-value
@ -53,38 +42,124 @@
(->> (vals tokens)
(group-by :type)))
(defn tokens-name-map
"Convert tokens into a map with their `:name` as the key.
E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}"
[tokens]
(->> (map (fn [{:keys [name] :as token}] [name token]) tokens)
(into {})))
(defn tokens-name-map-for-type
"Convert tokens with `token-type` into a map with their `:name` as the key.
E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}"
[token-type tokens]
(-> (group-tokens-by-type tokens)
(get token-type [])
(tokens-name-map)))
(defn tokens-name-map->select-options [{:keys [shape tokens attributes selected-attributes]}]
(->> (tokens-name-map tokens)
(->> (wtt/token-names-map tokens)
(map (fn [[_k {:keys [name] :as item}]]
(cond-> (assoc item :label name)
(token-applied? item shape (or selected-attributes attributes)) (assoc :selected? true))))))
(wtt/token-applied? item shape (or selected-attributes attributes)) (assoc :selected? true))))))
;; Update functions ------------------------------------------------------------
;; Shape Update Functions ------------------------------------------------------
(defn update-shape-radius [value shape-ids]
(dch/update-shapes shape-ids
(fn [shape]
(when (ctsr/has-radius? shape)
(ctsr/set-radius-1 shape value)))
{:reg-objects? true
:attrs ctt/border-radius-keys}))
(defn update-shape-dimensions [value shape-ids]
(ptk/reify ::update-shape-dimensions
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(dwt/update-dimensions shape-ids :width value)
(dwt/update-dimensions shape-ids :height value)))))
(defn update-opacity [value shape-ids]
(dch/update-shapes shape-ids #(assoc % :opacity value)))
(defn update-stroke-width
[value shape-ids]
(dch/update-shapes shape-ids (fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value)))))
(defn update-rotation [value shape-ids]
(ptk/reify ::update-shape-dimensions
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(udw/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value)))))
(defn update-layout-spacing-column [value shape-ids]
(ptk/reify ::update-layout-spacing-column
ptk/WatchEvent
(watch [_ state _]
(rx/concat
(for [shape-id shape-ids]
(let [shape (dt/get-shape-from-state shape-id state)
layout-direction (:layout-flex-dir shape)
layout-update (if (or (= layout-direction :row-reverse) (= layout-direction :row))
{:layout-gap {:column-gap value}}
{:layout-gap {:row-gap value}})]
(dwsl/update-layout [shape-id] layout-update)))))))
;; Events ----------------------------------------------------------------------
(defn apply-token
[{:keys [attributes shape-ids token on-update-shape] :as _props}]
(ptk/reify ::apply-token
ptk/WatchEvent
(watch [_ state _]
(->> (rx/from (sd/resolve-tokens+ (get-in state [:workspace-data :tokens])))
(rx/mapcat
(fn [sd-tokens]
(let [undo-id (js/Symbol)
resolved-value (-> (get sd-tokens (:id token))
(resolve-token-value))
tokenized-attributes (wtt/attributes-map attributes (:id token))]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/update-shapes shape-ids (fn [shape]
(update shape :applied-tokens merge tokenized-attributes)))
(when on-update-shape
(on-update-shape resolved-value shape-ids attributes))
(dwu/commit-undo-transaction undo-id)))))))))
(defn unapply-token
"Removes `attributes` that match `token` for `shape-ids`.
Doesn't update shape attributes."
[{:keys [attributes token shape-ids] :as _props}]
(ptk/reify ::unapply-token
ptk/WatchEvent
(watch [_ _ _]
(rx/of
(let [remove-token #(when % (wtt/remove-attributes-for-token-id attributes (:id token) %))]
(dch/update-shapes
shape-ids
(fn [shape]
(update shape :applied-tokens remove-token))))))))
(defn toggle-token
[{:keys [token-type-props token shapes] :as _props}]
(ptk/reify ::on-toggle-token
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [attributes on-update-shape]} token-type-props
unapply-tokens? (wtt/shapes-token-applied? token shapes (:attributes token-type-props))
shape-ids (map :id shapes)]
(if unapply-tokens?
(rx/of
(unapply-token {:attributes attributes
:token token
:shape-ids shape-ids}))
(rx/of
(apply-token {:attributes attributes
:token token
:shape-ids shape-ids
:on-update-shape on-update-shape})))))))
(defn on-apply-token [{:keys [token token-type-props selected-shapes] :as _props}]
(let [{:keys [attributes on-apply on-update-shape]
:or {on-apply dt/update-token-from-attributes}} token-type-props
shape-ids (->> selected-shapes
(eduction
(remove #(tokens-applied? token % attributes))
(remove #(wtt/shapes-token-applied? token % attributes))
(map :id)))]
(p/let [sd-tokens (sd/resolve-workspace-tokens+ {:debug? true})]
(let [resolved-token (get sd-tokens (:id token))
resolved-token-value (resolve-token-value resolved-token)]
@ -94,45 +169,6 @@
:attributes attributes}))
(on-update-shape resolved-token-value shape-ids attributes))))))
(defn update-shape-radius [value shape-ids]
(st/emit!
(dch/update-shapes shape-ids
(fn [shape]
(when (ctsr/has-radius? shape)
(ctsr/set-radius-1 shape value)))
{:reg-objects? true
:attrs ctt/border-radius-keys})))
(defn update-shape-dimensions [value shape-ids]
(st/emit!
(dwt/update-dimensions shape-ids :width value)
(dwt/update-dimensions shape-ids :height value)))
(defn update-opacity [value shape-ids]
(st/emit!
(dch/update-shapes shape-ids #(assoc % :opacity value))))
(defn update-stroke-width
[value shape-ids]
(st/emit!
(dch/update-shapes shape-ids (fn [shape]
(when (seq (:strokes shape))
(assoc-in shape [:strokes 0 :stroke-width] value))))))
(defn update-rotation [value shape-ids]
(st/emit! (udw/trigger-bounding-box-cloaking shape-ids)
(udw/increase-rotation shape-ids value)))
(defn update-layout-spacing-column [value shape-ids]
(doseq [shape-id shape-ids]
(let [shape (dt/get-shape-from-state shape-id @st/state)
layout-direction (:layout-flex-dir shape)
layout-update (if (or (= layout-direction :row-reverse) (= layout-direction :row))
{:layout-gap {:column-gap value}}
{:layout-gap {:row-gap value}})]
(st/emit!
(dwsl/update-layout [shape-id] layout-update)))))
;; JSON export functions -------------------------------------------------------
(defn encode-tokens

View file

@ -16,6 +16,7 @@
[app.main.ui.workspace.sidebar.assets.common :as cmm]
[app.main.ui.workspace.tokens.core :as wtc]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[app.main.ui.workspace.tokens.token :as wtt]
[app.util.dom :as dom]
[cuerdas.core :as str]
[okulary.core :as l]
@ -98,9 +99,10 @@
(mf/deps selected-shapes token-type-props)
(fn [event token]
(dom/stop-propagation event)
(wtc/on-apply-token {:token token
:token-type-props token-type-props
:selected-shapes selected-shapes})))
(st/emit!
(wtc/toggle-token {:token token
:shapes selected-shapes
:token-type-props token-type-props}))))
tokens-count (count tokens)]
[:div {:on-click on-toggle-open-click}
[:& cmm/asset-section {:icon (mf/fnc icon-wrapper [_]
@ -121,7 +123,7 @@
[:& token-pill
{:key (:id token)
:token token
:highlighted? (wtc/tokens-applied? token selected-shapes attributes)
:highlighted? (wtt/shapes-token-applied? token selected-shapes attributes)
:on-click #(on-token-pill-click % token)
:on-context-menu #(on-context-menu % token)}])]])]]))

View file

@ -53,7 +53,7 @@
"Resolves references and math expressions using StyleDictionary.
Returns a promise with the resolved dictionary."
[tokens & {:keys [debug?] :as config}]
(let [performance-start (js/window.performance.now)
(let [performance-start (js/performance.now)
sd (tokens->style-dictionary+ tokens config)]
(when debug?
(js/console.log "StyleDictionary" sd))
@ -61,7 +61,7 @@
(.buildAllPlatforms "json")
(.catch js/console.error)
(.then (fn [^js resp]
(let [performance-end (js/window.performance.now)
(let [performance-end (js/performance.now)
duration-ms (- performance-end performance-start)
resolved-tokens (.-allTokens resp)]
(when debug?

View file

@ -2,6 +2,36 @@
(:require
[cuerdas.core :as str]))
(defn attributes-map
"Creats an attributes map using collection of `attributes` for `id`."
[attributes id]
(->> (map (fn [attr] {attr id}) attributes)
(into {})))
(defn remove-attributes-for-token-id
"Removes applied tokens with `token-id` for the given `attributes` set from `applied-tokens`."
[attributes token-id applied-tokens]
(let [attr? (set attributes)]
(->> (remove (fn [[k v]]
(and (attr? k)
(= v token-id)))
applied-tokens)
(into {}))))
(defn token-applied?
"Test if `token` is applied to a `shape` with the given `token-attributes`."
[token shape token-attributes]
(let [{:keys [id]} token
applied-tokens (get shape :applied-tokens {})]
(some (fn [attr]
(= (get applied-tokens attr) id))
token-attributes)))
(defn shapes-token-applied?
"Test if `token` is applied to to any of `shapes` with the given `token-attributes`."
[token shapes token-attributes]
(some #(token-applied? token % token-attributes) shapes))
(defn token-name->path
"Splits token-name into a path vector split by `.` characters.
@ -21,6 +51,14 @@
{:path (seq path)
:selector selector}))
(defn token-names-map
"Convert tokens into a map with their `:name` as the key.
E.g.: {\"sm\" {:token-type :border-radius :id #uuid \"000\" ...}}"
[tokens]
(->> (map (fn [{:keys [name] :as token}] [name token]) tokens)
(into {})))
(defn token-names-tree
"Convert tokens into a nested tree with their `:name` as the path."
[tokens]

View file

@ -0,0 +1,49 @@
(ns token-tests.helpers.state
(:require
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn stop-on
"Helper function to be used with async version of run-store.
Will stop the execution after event with `event-type` has completed."
[event-type]
(fn [stream]
(->> stream
#_(rx/tap #(prn (ptk/type %)))
(rx/filter #(ptk/type? event-type %)))))
(def stop-on-send-update-indices
"Stops on `send-update-indices` function being called, which should be the last function of an event chain."
(stop-on :app.main.data.workspace.changes/send-update-indices))
;; Support for async events in tests
;; https://chat.kaleidos.net/penpot-partners/pl/tz1yoes3w3fr9qanxqpuhoz3ch
(defn run-store
"Async version of `frontend-tests.helpers.state/run-store`."
([store done events completed-cb]
(run-store store done events completed-cb nil))
([store done events completed-cb stopper]
(let [stream (ptk/input-stream store)]
(->> stream
(rx/take-until (if stopper
(stopper stream)
(rx/filter #(= :the/end %) stream)))
(rx/last)
(rx/tap (fn []
(completed-cb @store)))
(rx/subs! (fn [_] (done))
(fn [cause]
(js/console.log "[error]:" cause))
(fn [_]
#_(js/console.log "[complete]"))))
(doall (for [event events]
(ptk/emit! store event)))
(ptk/emit! store :the/end))))
(defn run-store-async
"Helper version of `run-store` that automatically stops on the `send-update-indices` event"
([store done events completed-cb]
(run-store store done events completed-cb stop-on-send-update-indices))
([store done events completed-cb stop-on]
(run-store store done events completed-cb stop-on)))

View file

@ -0,0 +1,24 @@
(ns token-tests.helpers.tokens
(:require
[app.common.test-helpers.ids-map :as thi]
[app.main.ui.workspace.tokens.token :as wtt]))
(defn add-token [state label params]
(let [id (thi/new-id! label)
token (assoc params :id id)]
(update-in state [:data :tokens] assoc id token)))
(defn get-token [file label]
(let [id (thi/id label)]
(get-in file [:data :tokens id])))
(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)]
(update-in file [:data
:pages-index first-page-id
:objects shape-id
:applied-tokens]
merge applied-attributes)))

View file

@ -0,0 +1,274 @@
(ns token-tests.logic.token-actions-test
(:require
[app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.shapes :as cths]
[app.main.ui.workspace.tokens.core :as wtc]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[token-tests.helpers.state :as tohs]
[token-tests.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
(defn- setup-file
[]
(-> (cthf/sample-file :file-1 :page-label :page-1)
(ctho/add-rect :rect-1 {})
(ctho/add-rect :rect-2 {})
(ctho/add-rect :rect-3 {})
(toht/add-token :token-1 {:value "12"
:name "borderRadius.sm"
:type :border-radius})
(toht/add-token :token-2 {:value "{borderRadius.sm} * 2"
:name "borderRadius.md"
:type :border-radius})))
(t/deftest test-apply-token
(t/testing "applying a token twice with the same attributes will override")
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file :token-1)
:on-update-shape wtc/update-shape-radius})
(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file :token-2)
:on-update-shape wtc/update-shape-radius})]]
(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-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 [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file :token-2)
:on-update-shape wtc/update-shape-radius})]]
(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/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 [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file :token-target)
:on-update-shape wtc/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))))))))
(t/deftest test-apply-sizing
(t/testing "applies sizing token and updates the shapes width and height")
(t/async
done
(let [file (-> (setup-file)
(toht/add-token :token-target {:value "100"
:name "sizing.sm"
:type :sizing}))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file :token-target)
:on-update-shape wtc/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))))))))
(t/deftest test-apply-opacity
(t/testing "applies opacity token and updates the shapes opacity")
(t/async
done
(let [file (-> (setup-file)
(toht/add-token :token-target {:value "0.5"
:name "opacity.medium"
:type :opacity}))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:opacity}
:token (toht/get-token file :token-target)
:on-update-shape wtc/update-opacity})]]
(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 (= (:opacity (:applied-tokens rect-1')) (:id token-target')))
;; TODO Fix opacity shape update not working?
#_(t/is (= (:opacity rect-1') 0.5))))))))
(t/deftest test-apply-rotation
(t/testing "applies rotation token and updates the shapes rotation")
(t/async
done
(let [file (-> (setup-file)
(toht/add-token :token-target {:value "120"
:name "rotation.medium"
:type :rotation}))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtc/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rotation}
:token (toht/get-token file :token-target)
:on-update-shape wtc/update-rotation})]]
(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 (= (:rotation (:applied-tokens rect-1')) (:id token-target')))
(t/is (= (:rotation rect-1') 120))))))))
(t/deftest test-toggle-token-none
(t/testing "should apply token to all selected items, where no item has the token applied"
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2)
events [(wtc/toggle-token {:shapes [rect-1 rect-2]
:token-type-props {:attributes #{:rx :ry}
:on-update-shape wtc/update-shape-radius}
:token (toht/get-token file :token-2)})]]
(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)
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 rect-1') 24))
(t/is (= (:rx rect-2') 24)))))))))
(t/deftest test-toggle-token-mixed
(t/testing "should unapply given token if one of the selected items has the token applied while keeping other tokens with some attributes"
(t/async
done
(let [file (-> (setup-file)
(toht/apply-token-to-shape :rect-1 :token-1 #{:rx :ry})
(toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry}))
store (ths/setup-store file)
rect-with-token (cths/get-shape file :rect-1)
rect-without-token (cths/get-shape file :rect-2)
rect-with-other-token (cths/get-shape file :rect-3)
events [(wtc/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token]
:token (toht/get-token file :token-1)
:token-type-props {:attributes #{:rx :ry}}})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
rect-with-token' (cths/get-shape file' :rect-1)
rect-without-token' (cths/get-shape file' :rect-2)
rect-with-other-token' (cths/get-shape file' :rect-3)]
(t/testing "rect-with-token got the token remove"
(t/is (nil? (:rx (:applied-tokens rect-with-token'))))
(t/is (nil? (:ry (:applied-tokens rect-with-token')))))
(t/testing "rect-without-token didn't get updated"
(t/is (= (:applied-tokens rect-without-token') (:applied-tokens rect-without-token))))
(t/testing "rect-with-other-token didn't get updated"
(t/is (= (:applied-tokens rect-with-other-token') (:applied-tokens rect-with-other-token)))))))))))
(t/deftest test-toggle-token-apply-to-all
(t/testing "should apply token to all if none of the shapes has it applied"
(t/async
done
(let [file (-> (setup-file)
(toht/apply-token-to-shape :rect-1 :token-2 #{:rx :ry})
(toht/apply-token-to-shape :rect-3 :token-2 #{:rx :ry}))
store (ths/setup-store file)
rect-with-other-token-1 (cths/get-shape file :rect-1)
rect-without-token (cths/get-shape file :rect-2)
rect-with-other-token-2 (cths/get-shape file :rect-3)
events [(wtc/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2]
:token (toht/get-token file :token-1)
:token-type-props {:attributes #{:rx :ry}}})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
target-token (toht/get-token file' :token-1)
rect-with-other-token-1' (cths/get-shape file' :rect-1)
rect-without-token' (cths/get-shape file' :rect-2)
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 (= (: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)))))))))))

View file

@ -9,11 +9,47 @@
[app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true]))
(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/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/testing "doesn't match empty token"
(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/testing "doesn't match passed `:token-attributes`"
(t/is (nil? (wtt/token-applied? {:id :a} {:applied-tokens {:x :a}} #{:y})))))
(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}}]
#{: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}}]
#{:x})))
(t/is (nil? (wtt/shapes-token-applied? {:id :a} [{:applied-tokens {:x :a}}
{:applied-tokens {:x :a}}]
#{:y})))))
(t/deftest name->path-test
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo.bar.baz")))
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz")))
(t/is (= ["foo" "bar" "baz"] (wtt/token-name->path "foo..bar.baz...."))))
(t/deftest tokens-name-map-test
(t/testing "creates a a names map from tokens"
(t/is (= {"border-radius.sm" {:name "border-radius.sm", :value "10"}
"border-radius.md" {:name "border-radius.md", :value "20"}}
(wtt/token-names-map [{:name "border-radius.sm" :value "10"}
{:name "border-radius.md" :value "20"}])))))
(t/deftest tokens-name-tree-test
(t/is (= {"foo"
{"bar"