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:
commit
2f17b79bef
12 changed files with 559 additions and 86 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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?))))]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}])]])]]))
|
||||
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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]
|
||||
|
|
49
frontend/test/token_tests/helpers/state.cljs
Normal file
49
frontend/test/token_tests/helpers/state.cljs
Normal 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)))
|
24
frontend/test/token_tests/helpers/tokens.cljs
Normal file
24
frontend/test/token_tests/helpers/tokens.cljs
Normal 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)))
|
274
frontend/test/token_tests/logic/token_actions_test.cljs
Normal file
274
frontend/test/token_tests/logic/token_actions_test.cljs
Normal 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)))))))))))
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue