Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Andrés Moya 2024-11-07 13:49:11 +01:00
commit e9c32841a9
1048 changed files with 33672 additions and 54976 deletions

View file

@ -39,7 +39,7 @@
:pages []
:pages-index {}}
:workspace-libraries {}
:features/team #{"components/v2"}})
:features-team #{"components/v2"}})
(def ^:private idmap (atom {}))

View file

@ -20,7 +20,7 @@
:current-page-id nil
:workspace-data nil
:workspace-libraries {}
:features/team #{"components/v2"}})
:features-team #{"components/v2"}})
(defn- on-error
[cause]
@ -33,6 +33,7 @@
(let [state (-> initial-state
(assoc :current-file-id (:id file)
:current-page-id (cthf/current-page-id file)
:permissions {:can-edit true}
:workspace-file (dissoc file :data)
:workspace-data (:data file)))
store (ptk/store {:state state :on-error on-error})]

View file

@ -0,0 +1,42 @@
(ns frontend-tests.runner
(:require
[cljs.test :as t]
[frontend-tests.basic-shapes-test]
[frontend-tests.helpers-shapes-test]
[frontend-tests.logic.comp-remove-swap-slots-test]
[frontend-tests.logic.copying-and-duplicating-test]
[frontend-tests.logic.frame-guides-test]
[frontend-tests.logic.groups-test]
[frontend-tests.plugins.context-shapes-test]
[frontend-tests.tokens.logic.token-actions-test]
[frontend-tests.tokens.style-dictionary-test]
[frontend-tests.tokens.token-form-test]
[frontend-tests.tokens.token-test]
[frontend-tests.util-range-tree-test]
[frontend-tests.util-simple-math-test]
[frontend-tests.util-snap-data-test]))
(enable-console-print!)
(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
(if (cljs.test/successful? m)
(.exit js/process 0)
(.exit js/process 1)))
(defn init
[]
(t/run-tests
'frontend-tests.helpers-shapes-test
'frontend-tests.logic.comp-remove-swap-slots-test
'frontend-tests.logic.copying-and-duplicating-test
'frontend-tests.logic.frame-guides-test
'frontend-tests.logic.groups-test
'frontend-tests.plugins.context-shapes-test
'frontend-tests.util-range-tree-test
'frontend-tests.util-snap-data-test
'frontend-tests.util-simple-math-test
'frontend-tests.basic-shapes-test
;; 'frontend-tests.tokens.logic.token-actions-test
;; 'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-test
'frontend-tests.tokens.token-form-test))

View file

@ -0,0 +1,74 @@
(ns frontend-tests.tokens.helpers.state
(:require
[app.common.types.tokens-lib :as ctob]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn end
"Apply `attributes` that match `token` for `shape-ids`.
Optionally remove attributes from `attributes-to-remove`,
this is useful for applying a single attribute from an attributes set
while removing other applied tokens from this set."
[]
(ptk/reify ::end
ptk/WatchEvent
(watch [_ _ _]
(rx/empty))))
(defn end+
[]
(ptk/reify ::end+
ptk/WatchEvent
(watch [_ state _]
(->> (rx/from (-> (get-in state [:workspace-data :tokens-lib])
(ctob/get-active-themes-set-tokens)
(sd/resolve-tokens+)))
(rx/mapcat #(rx/of (end)))))))
(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 ::end))
;; 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)
stopper-s (if (fn? stopper)
(stopper stream)
(rx/filter #(= :the/end %) stream))]
(->> stream
(rx/take-until stopper-s)
(rx/last)
(rx/tap (fn [_]
(completed-cb @store)))
(rx/subs! (fn [_] (done))
(fn [cause]
(js/console.log "[error]:" cause))
(fn [_]
#_(js/console.log "[complete]"))))
(doseq [event (concat events [(end+)])]
(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,26 @@
(ns frontend-tests.tokens.helpers.tokens
(:require
[app.common.test-helpers.ids-map :as thi]
[app.common.types.tokens-lib :as ctob]
[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 name]
(some-> (get-in file [:data :tokens-lib])
(ctob/get-active-themes-set-tokens)
(get name)))
(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 (get-token file token-label)
applied-attributes (wtt/attributes-map attributes token)]
(update-in file [:data
:pages-index first-page-id
:objects shape-id
:applied-tokens]
merge applied-attributes)))

View file

@ -0,0 +1,407 @@
(ns frontend-tests.tokens.logic.token-actions-test
(:require
[app.common.logging :as log]
[app.common.test-helpers.compositions :as ctho]
[app.common.test-helpers.files :as cthf]
[app.common.test-helpers.shapes :as cths]
[app.common.types.tokens-lib :as ctob]
[app.main.ui.workspace.tokens.changes :as wtch]
[app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true]
[frontend-tests.helpers.pages :as thp]
[frontend-tests.helpers.state :as ths]
[frontend-tests.tokens.helpers.state :as tohs]
[frontend-tests.tokens.helpers.tokens :as toht]))
(t/use-fixtures :each
{:before (fn []
;; Ignore rxjs async errors
(log/set-level! "app.main.data.changes" :error)
(thp/reset-idmap!))})
(defn setup-file []
(cthf/sample-file :file-1 :page-label :page-1))
(def border-radius-token
{:name "borderRadius.sm"
:value "12"
:type :border-radius})
(def reference-border-radius-token
{:name "borderRadius.md"
:value "{borderRadius.sm} * 2"
:type :border-radius})
(defn setup-file-with-tokens
[& {:keys [rect-1 rect-2 rect-3]}]
(-> (setup-file)
(ctho/add-rect :rect-1 rect-1)
(ctho/add-rect :rect-2 rect-2)
(ctho/add-rect :rect-3 rect-3)
(assoc-in [:data :tokens-lib]
(-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
(ctob/set-active-themes #{"/Theme A"})
(ctob/add-set (ctob/make-token-set :name "Set A"))
(ctob/add-token-in-set "Set A" (ctob/make-token border-radius-token))
(ctob/add-token-in-set "Set A" (ctob/make-token reference-border-radius-token))))))
(t/deftest test-apply-token
(t/testing "applies token to shape and updates shape attributes to resolved value"
(t/async
done
(let [file (setup-file-with-tokens)
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 "borderRadius.md")
: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 (toht/get-token file' "borderRadius.md")
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')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-multiple-tokens
(t/testing "applying a token twice with the same attributes will override the previously applied tokens values"
(t/async
done
(let [file (setup-file-with-tokens)
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 "borderRadius.sm")
:on-update-shape wtch/update-shape-radius-all})
(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rx :ry}
:token (toht/get-token file "borderRadius.md")
: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 (toht/get-token file' "borderRadius.md")
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')) (:name token)))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token))))
(t/testing "shape radius got update to the resolved token value."
(t/is (= (:rx rect-1') 24))
(t/is (= (:ry rect-1') 24))))))))))
(t/deftest test-apply-token-overwrite
(t/testing "removes old token attributes and applies only single attribute"
(t/async
done
(let [file (setup-file-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [;; Apply "borderRadius.sm" to all border radius attributes
(wtch/apply-token {:attributes #{:rx :ry :r1 :r2 :r3 :r4}
:token (toht/get-token file "borderRadius.sm")
:shape-ids [(:id rect-1)]
:on-update-shape wtch/update-shape-radius-all})
;; Apply single `:r1` attribute to same shape
;; while removing other attributes from the border-radius set
;; but keep `:r4` for testing purposes
(wtch/apply-token {:attributes #{:r1}
:attributes-to-remove #{:rx :ry :r1 :r2 :r3}
:token (toht/get-token file "borderRadius.md")
:shape-ids [(:id rect-1)]
: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-sm (toht/get-token file' "borderRadius.sm")
token-md (toht/get-token file' "borderRadius.md")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "other border-radius attributes got removed"
(t/is (nil? (:rx (:applied-tokens rect-1')))))
(t/testing "r1 got applied with borderRadius.md"
(t/is (= (:r1 (:applied-tokens rect-1')) (:name token-md))))
(t/testing "while :r4 was kept with borderRadius.sm"
(t/is (= (:r4 (:applied-tokens rect-1')) (:name token-sm)))))))))))
(t/deftest test-apply-dimensions
(t/testing "applies dimensions token and updates the shapes width and height"
(t/async
done
(let [dimensions-token {:name "dimensions.sm"
:value "100"
:type :dimensions}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token dimensions-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "dimensions.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "dimensions.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-sizing
(t/testing "applies sizing token and updates the shapes width and height"
(t/async
done
(let [sizing-token {:name "sizing.sm"
:value "100"
:type :sizing}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token sizing-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:width :height}
:token (toht/get-token file "sizing.sm")
:on-update-shape wtch/update-shape-dimensions})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
token-target' (toht/get-token file' "sizing.sm")
rect-1' (cths/get-shape file' :rect-1)]
(t/testing "shape `:applied-tokens` got updated"
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:width (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:height (:applied-tokens rect-1')) (:name token-target'))))
(t/testing "shapes width and height got updated"
(t/is (= (:width rect-1') 100))
(t/is (= (:height rect-1') 100))))))))))
(t/deftest test-apply-opacity
(t/testing "applies opacity token and updates the shapes opacity"
(t/async
done
(let [opacity-float {:name "opacity.float"
:value "0.3"
:type :opacity}
opacity-percent {:name "opacity.percent"
:value "40%"
:type :opacity}
opacity-invalid {:name "opacity.invalid"
:value "100"
:type :opacity}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(-> %
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-float))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-percent))
(ctob/add-token-in-set "Set A" (ctob/make-token opacity-invalid)))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2)
rect-3 (cths/get-shape file :rect-3)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.float")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-2)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.percent")
:on-update-shape wtch/update-opacity})
(wtch/apply-token {:shape-ids [(:id rect-3)]
:attributes #{:opacity}
:token (toht/get-token file "opacity.invalid")
:on-update-shape wtch/update-opacity})]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-store new-state)
rect-1' (cths/get-shape file' :rect-1)
rect-2' (cths/get-shape file' :rect-2)
rect-3' (cths/get-shape file' :rect-3)
token-opacity-float (toht/get-token file' "opacity.float")
token-opacity-percent (toht/get-token file' "opacity.percent")
token-opacity-invalid (toht/get-token file' "opacity.invalid")]
(t/testing "float value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-1')) (:name token-opacity-float)))
(t/is (= (:opacity rect-1') 0.3)))
(t/testing "percentage value got translated to float and applied to opacity"
(t/is (= (:opacity (:applied-tokens rect-2')) (:name token-opacity-percent)))
(t/is (= (:opacity rect-2') 0.4)))
(t/testing "invalid opacity value got applied but did not change shape"
(t/is (= (:opacity (:applied-tokens rect-3')) (:name token-opacity-invalid)))
(t/is (nil? (:opacity rect-3')))))))))))
(t/deftest test-apply-rotation
(t/testing "applies rotation token and updates the shapes rotation"
(t/async
done
(let [rotation-token {:name "rotation.medium"
:value "120"
:type :rotation}
file (-> (setup-file-with-tokens)
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token rotation-token))))
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
events [(wtch/apply-token {:shape-ids [(:id rect-1)]
:attributes #{:rotation}
:token (toht/get-token file "rotation.medium")
:on-update-shape wtch/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' "rotation.medium")
rect-1' (cths/get-shape file' :rect-1)]
(t/is (some? (:applied-tokens rect-1')))
(t/is (= (:rotation (:applied-tokens rect-1')) (:name token-target')))
(t/is (= (:rotation rect-1') 120)))))))))
(t/deftest test-apply-stroke-width
(t/testing "applies stroke-width token and updates the shapes with stroke"
(t/async
done
(let [stroke-width-token {:name "stroke-width.sm"
:value "10"
:type :stroke-width}
file (-> (setup-file-with-tokens {:rect-1 {:strokes [{:stroke-alignment :inner,
:stroke-style :solid,
:stroke-color "#000000",
:stroke-opacity 1,
:stroke-width 5}]}})
(update-in [:data :tokens-lib]
#(ctob/add-token-in-set % "Set A" (ctob/make-token stroke-width-token))))
store (ths/setup-store file)
rect-with-stroke (cths/get-shape file :rect-1)
rect-without-stroke (cths/get-shape file :rect-2)
events [(wtch/apply-token {:shape-ids [(:id rect-with-stroke) (:id rect-without-stroke)]
:attributes #{:stroke-width}
:token (toht/get-token file "stroke-width.sm")
:on-update-shape wtch/update-stroke-width})]]
(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' "stroke-width.sm")
rect-with-stroke' (cths/get-shape file' :rect-1)
rect-without-stroke' (cths/get-shape file' :rect-2)]
(t/testing "token got applied to rect with stroke and shape stroke got updated"
(t/is (= (:stroke-width (:applied-tokens rect-with-stroke')) (:name token-target')))
(t/is (= (get-in rect-with-stroke' [:strokes 0 :stroke-width]) 10)))
(t/testing "token got applied to rect without stroke but shape didnt get updated"
(t/is (= (:stroke-width (:applied-tokens rect-without-stroke')) (:name token-target')))
(t/is (empty? (:strokes rect-without-stroke')))))))))))
(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-with-tokens)
store (ths/setup-store file)
rect-1 (cths/get-shape file :rect-1)
rect-2 (cths/get-shape file :rect-2)
events [(wtch/toggle-token {:shapes [rect-1 rect-2]
:token-type-props {:attributes #{:rx :ry}
:on-update-shape wtch/update-shape-radius-all}
:token (toht/get-token file "borderRadius.md")})]]
(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' "borderRadius.md")
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')) (:name token-2')))
(t/is (= (:rx (:applied-tokens rect-2')) (:name token-2')))
(t/is (= (:ry (:applied-tokens rect-1')) (:name token-2')))
(t/is (= (:ry (:applied-tokens rect-2')) (:name 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-with-tokens)
(toht/apply-token-to-shape :rect-1 "borderRadius.sm" #{:rx :ry})
(toht/apply-token-to-shape :rect-3 "borderRadius.md" #{: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 [(wtch/toggle-token {:shapes [rect-with-token rect-without-token rect-with-other-token]
:token (toht/get-token file "borderRadius.sm")
: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 removed"
(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-with-tokens)
(toht/apply-token-to-shape :rect-1 "borderRadius.md" #{:rx :ry})
(toht/apply-token-to-shape :rect-3 "borderRadius.md" #{: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 [(wtch/toggle-token {:shapes [rect-with-other-token-1 rect-without-token rect-with-other-token-2]
:token (toht/get-token file "borderRadius.sm")
: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' "borderRadius.sm")
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')) (:name target-token)))
(t/is (= (:rx (:applied-tokens rect-without-token')) (:name target-token)))
(t/is (= (:rx (:applied-tokens rect-with-other-token-2')) (:name target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-1')) (:name target-token)))
(t/is (= (:ry (:applied-tokens rect-without-token')) (:name target-token)))
(t/is (= (:ry (:applied-tokens rect-with-other-token-2')) (:name target-token)))))))))))

View file

@ -0,0 +1,115 @@
(ns frontend-tests.tokens.style-dictionary-test
(:require
[app.common.transit :as tr]
[app.common.types.tokens-lib :as ctob]
[app.main.ui.workspace.tokens.style-dictionary :as sd]
[beicon.v2.core :as rx]
[cljs.test :as t :include-macros true]
[promesa.core :as p]))
(t/deftest resolve-tokens-test
(t/async
done
(t/testing "resolves tokens using style-dictionary from a ids map"
(let [tokens (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :name "core"))
(ctob/add-token-in-set "core" (ctob/make-token {:name "borderRadius.sm"
:value "12px"
:type :border-radius}))
(ctob/add-token-in-set "core" (ctob/make-token {:value "{borderRadius.sm} * 2"
:name "borderRadius.md-with-dashes"
:type :border-radius}))
(ctob/get-all-tokens))]
(-> (sd/resolve-tokens+ tokens)
(p/finally
(fn [resolved-tokens]
(t/is (= 12 (get-in resolved-tokens ["borderRadius.sm" :resolved-value])))
(t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit])))
(t/is (= 24 (get-in resolved-tokens ["borderRadius.md-with-dashes" :resolved-value])))
(t/is (= "px" (get-in resolved-tokens ["borderRadius.md-with-dashes" :unit])))
(done))))))))
(t/deftest process-json-stream-test
(t/async
done
(t/testing "processes empty json string"
(let [json (-> {"core" {"color" {"$value" "red"
"$type" "color"}}}
(tr/encode-str {:type :json-verbose}))]
(->> (rx/of json)
(sd/process-json-stream)
(rx/subs! (fn [tokens-lib]
(t/is (instance? ctob/TokensLib tokens-lib))
(t/is (= "red" (-> (ctob/get-set tokens-lib "core")
(ctob/get-token "color")
(:value))))
(done))))))))
(t/deftest reference-errros-test
(t/testing "Extracts reference errors from StyleDictionary errors"
;; Using unicode for the white-space after "Error: " as some editors might remove it and its more visible
(t/is (=
["Some token references (2) could not be found."
""
"foo.value tries to reference missing, which is not defined."
"color.value tries to reference missing, which is not defined."]
(sd/reference-errors "Error:\u0020
Reference Errors:
Some token references (2) could not be found.
foo.value tries to reference missing, which is not defined.
color.value tries to reference missing, which is not defined.")))
(t/is (nil? (sd/reference-errors nil)))
(t/is (nil? (sd/reference-errors "none")))))
(t/deftest process-empty-json-stream-test
(t/async
done
(t/testing "processes empty json string"
(->> (rx/of "{}")
(sd/process-json-stream)
(rx/subs! (fn [tokens-lib]
(t/is (instance? ctob/TokensLib tokens-lib))
(done)))))))
(t/deftest process-invalid-json-stream-test
(t/async
done
(t/testing "fails on invalid json"
(->> (rx/of "{,}")
(sd/process-json-stream)
(rx/subs!
(fn []
(throw (js/Error. "Should be an error")))
(fn [err]
(t/is (= :error.import/json-parse-error (:error/code (ex-data err))))
(done)))))))
(t/deftest process-non-token-json-stream-test
(t/async
done
(t/testing "fails on non-token json"
(->> (rx/of "{\"foo\": \"bar\"}")
(sd/process-json-stream)
(rx/subs!
(fn []
(throw (js/Error. "Should be an error")))
(fn [err]
(t/is (= :error.import/invalid-json-data (:error/code (ex-data err))))
(done)))))))
(t/deftest process-missing-references-json-test
(t/async
done
(t/testing "fails on missing references in tokens"
(let [json (-> {"core" {"color" {"$value" "{missing}"
"$type" "color"}}}
(tr/encode-str {:type :json-verbose}))]
(->> (rx/of json)
(sd/process-json-stream)
(rx/subs!
(fn []
(throw (js/Error. "Should be an error")))
(fn [err]
(t/is (= :error.import/style-dictionary-reference-errors (:error/code (ex-data err))))
(done))))))))

View file

@ -0,0 +1,26 @@
;; 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 frontend-tests.tokens.token-form-test
(:require
[app.main.ui.workspace.tokens.form :as wtf]
[cljs.test :as t :include-macros true]
[malli.core :as m]))
(t/deftest test-valid-token-name-schema
;; Allow regular namespace token names
(t/is (some? (m/validate wtf/valid-token-name-schema "Foo")))
(t/is (some? (m/validate wtf/valid-token-name-schema "foo")))
(t/is (some? (m/validate wtf/valid-token-name-schema "FOO")))
(t/is (some? (m/validate wtf/valid-token-name-schema "Foo.Bar.Baz")))
;; Allow trailing tokens
(t/is (nil? (m/validate wtf/valid-token-name-schema "Foo.Bar.Baz....")))
;; Disallow multiple separator dots
(t/is (nil? (m/validate wtf/valid-token-name-schema "Foo..Bar.Baz")))
;; Disallow any special characters
(t/is (nil? (m/validate wtf/valid-token-name-schema "Hey Foo.Bar")))
(t/is (nil? (m/validate wtf/valid-token-name-schema "Hey😈Foo.Bar")))
(t/is (nil? (m/validate wtf/valid-token-name-schema "Hey%Foo.Bar"))))

View file

@ -0,0 +1,100 @@
;; 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 frontend-tests.tokens.token-test
(:require
[app.main.ui.workspace.tokens.token :as wtt]
[cljs.test :as t :include-macros true]))
(t/deftest test-parse-token-value
(t/testing "parses double from a token value"
(t/is (= {:value 100.1 :unit nil} (wtt/parse-token-value "100.1")))
(t/is (= {:value -9 :unit nil} (wtt/parse-token-value "-9"))))
(t/testing "trims white-space"
(t/is (= {:value -1.3 :unit nil} (wtt/parse-token-value " -1.3 "))))
(t/testing "parses unit: px"
(t/is (= {:value 70.3 :unit "px"} (wtt/parse-token-value " 70.3px "))))
(t/testing "parses unit: %"
(t/is (= {:value -10 :unit "%"} (wtt/parse-token-value "-10%"))))
(t/testing "parses unit: px")
(t/testing "returns nil for any invalid characters"
(t/is (nil? (wtt/parse-token-value " -1.3a "))))
(t/testing "doesnt accept invalid double"
(t/is (nil? (wtt/parse-token-value ".3")))))
(t/deftest remove-attributes-for-token-id
(t/testing "removes attributes matching the `token`, keeps other attributes"
(t/is (= {:ry "b"}
(wtt/remove-attributes-for-token #{:rx :ry} {:name "a"} {:rx "a" :ry "b"})))))
(t/deftest token-applied-test
(t/testing "matches passed token with `:token-attributes`"
(t/is (true? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "doesn't match empty token"
(t/is (nil? (wtt/token-applied? {} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "does't match passed token `:id`"
(t/is (nil? (wtt/token-applied? {:name "b"} {:applied-tokens {:x "a"}} #{:x}))))
(t/testing "doesn't match passed `:token-attributes`"
(t/is (nil? (wtt/token-applied? {:name "a"} {:applied-tokens {:x "a"}} #{:y})))))
(t/deftest shapes-ids-by-applied-attributes
(t/testing "Returns set of matched attributes that fit the applied token"
(let [attributes #{:x :y :z}
shape-applied-x {:id "shape-applied-x"
:applied-tokens {:x "1"}}
shape-applied-y {:id "shape-applied-y"
:applied-tokens {:y "1"}}
shape-applied-x-y {:id "shape-applied-x-y"
:applied-tokens {:x "1" :y "1"}}
shape-applied-none {:id "shape-applied-none"
:applied-tokens {}}
shape-applied-all {:id "shape-applied-all"
:applied-tokens {:x "1" :y "1" :z "1"}}
shape-ids (fn [& xs] (into #{} (map :id xs)))
shapes [shape-applied-x
shape-applied-y
shape-applied-x-y
shape-applied-all
shape-applied-none]
expected (wtt/shapes-ids-by-applied-attributes {:name "1"} shapes attributes)]
(t/is (= (:x expected) (shape-ids shape-applied-x
shape-applied-x-y
shape-applied-all)))
(t/is (= (:y expected) (shape-ids shape-applied-y
shape-applied-x-y
shape-applied-all)))
(t/is (= (:z expected) (shape-ids shape-applied-all)))
(t/is (true? (wtt/shapes-applied-all? expected (shape-ids shape-applied-all) attributes)))
(t/is (false? (wtt/shapes-applied-all? expected (apply shape-ids shapes) attributes)))
(shape-ids shape-applied-x
shape-applied-x-y
shape-applied-all))))
(t/deftest tokens-applied-test
(t/testing "is true when single shape matches the token and attributes"
(t/is (true? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "b"}}]
#{:x}))))
(t/testing "is false when no shape matches the token or attributes"
(t/is (nil? (wtt/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "b"}}
{:applied-tokens {:x "b"}}]
#{:x})))
(t/is (nil? (wtt/shapes-token-applied? {:name "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 token-name-path-exists?-test
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {"sm" {:name "sm"}}})))
(t/is (true? (wtt/token-name-path-exists? "border-radius" {"border-radius" {:name "sm"}})))
(t/is (true? (wtt/token-name-path-exists? "border-radius.sm" {"border-radius" {:name "sm"}})))
(t/is (true? (wtt/token-name-path-exists? "border-radius.sm.x" {"border-radius" {:name "sm"}})))
(t/is (false? (wtt/token-name-path-exists? "other" {"border-radius" {:name "sm"}})))
(t/is (false? (wtt/token-name-path-exists? "dark.border-radius.md" {"dark" {"border-radius" {"sm" {:name "sm"}}}}))))