mirror of
https://github.com/penpot/penpot.git
synced 2025-06-20 14:16:59 +02:00
🔧 Refactor token json file import/export
This commit is contained in:
parent
3ee3ee2059
commit
5e8929e504
11 changed files with 686 additions and 615 deletions
|
@ -11,14 +11,10 @@
|
|||
[app.common.files.tokens :as cft]
|
||||
[app.common.logging :as l]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.transit :as t]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.tinycolor :as tinycolor]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.warnings :as wtw]
|
||||
[app.main.store :as st]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -249,116 +245,6 @@
|
|||
#(get tokens (sd-token-name %))
|
||||
(StyleDictionary. (assoc default-config :log {:verbosity "verbose"}))))
|
||||
|
||||
;; === Import
|
||||
|
||||
(defn- decode-single-set-json
|
||||
"Decodes parsed json containing single token set and converts to library"
|
||||
[this set-name tokens]
|
||||
(assert (map? tokens) "expected a map data structure for `data`")
|
||||
|
||||
(ctob/add-set this (ctob/make-token-set :name (ctob/normalize-set-name set-name)
|
||||
:tokens (ctob/flatten-nested-tokens-json tokens ""))))
|
||||
|
||||
(defn- decode-single-set-legacy-json
|
||||
"Decodes parsed legacy json containing single token set and converts to library"
|
||||
[this set-name tokens]
|
||||
(assert (map? tokens) "expected a map data structure for `data`")
|
||||
(decode-single-set-json this set-name (ctob/legacy-nodes->dtcg-nodes tokens)))
|
||||
|
||||
(defn- reference-errors
|
||||
"Extracts reference errors from StyleDictionary."
|
||||
[err]
|
||||
(let [[header-1 header-2 & errors] (str/split err "\n")]
|
||||
(when (and
|
||||
(= header-1 "Error: ")
|
||||
(= header-2 "Reference Errors:"))
|
||||
errors)))
|
||||
|
||||
(defn name-error
|
||||
"Extracts name error out of malli schema error during import."
|
||||
[err]
|
||||
(let [schema-error (some-> (ex-data err)
|
||||
(get-in [:app.common.schema/explain :errors])
|
||||
(first))
|
||||
name-error? (= (:in schema-error) [:name])]
|
||||
(when name-error?
|
||||
(wte/error-ex-info :error.import/invalid-token-name (:value schema-error) err))))
|
||||
|
||||
|
||||
(defn- group-by-value [m]
|
||||
(reduce (fn [acc [k v]]
|
||||
(update acc v conj k)) {} m))
|
||||
|
||||
(defn- tokens-of-unknown-type-warning [unknown-tokens]
|
||||
(let [type->tokens (group-by-value unknown-tokens)]
|
||||
(ntf/show {:content (tr "workspace.tokens.unknown-token-type")
|
||||
:detail (->> (for [[token-type tokens] type->tokens]
|
||||
(tr "workspace.tokens.unknown-token-type-section" token-type (count tokens)))
|
||||
(str/join "\n"))
|
||||
:type :toast
|
||||
:level :info})))
|
||||
|
||||
(defn parse-json [data]
|
||||
(try
|
||||
(t/decode-str data)
|
||||
(catch js/Error e
|
||||
(throw (wte/error-ex-info :error.import/json-parse-error data e)))))
|
||||
|
||||
(defn decode-json-data [data file-name]
|
||||
(let [single-set? (ctob/single-set? data)
|
||||
json-format (ctob/get-json-format data)
|
||||
unknown-tokens (ctob/get-tokens-of-unknown-type
|
||||
data
|
||||
""
|
||||
(= json-format :json-format/dtcg))]
|
||||
{:tokens-lib
|
||||
(try
|
||||
(cond
|
||||
(and single-set?
|
||||
(= :json-format/legacy json-format))
|
||||
(decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) file-name data)
|
||||
|
||||
(and single-set?
|
||||
(= :json-format/dtcg json-format))
|
||||
(decode-single-set-json (ctob/ensure-tokens-lib nil) file-name data)
|
||||
|
||||
(= :json-format/legacy json-format)
|
||||
(ctob/decode-legacy-json (ctob/ensure-tokens-lib nil) data)
|
||||
|
||||
:else
|
||||
(ctob/decode-dtcg-json (ctob/ensure-tokens-lib nil) data))
|
||||
|
||||
(catch js/Error e
|
||||
(let [err (or (name-error e)
|
||||
(wte/error-ex-info :error.import/invalid-json-data data e))]
|
||||
(throw err))))
|
||||
:unknown-tokens unknown-tokens}))
|
||||
|
||||
(defn process-json-stream
|
||||
([data-stream]
|
||||
(process-json-stream nil data-stream))
|
||||
([params data-stream]
|
||||
(let [{:keys [file-name]} params]
|
||||
(->> data-stream
|
||||
(rx/map parse-json)
|
||||
(rx/map #(decode-json-data % file-name))
|
||||
(rx/mapcat (fn [{:keys [tokens-lib unknown-tokens]}]
|
||||
(when unknown-tokens
|
||||
(st/emit! (tokens-of-unknown-type-warning unknown-tokens)))
|
||||
(try
|
||||
(->> (ctob/get-all-tokens tokens-lib)
|
||||
(resolve-tokens-with-errors)
|
||||
(rx/map (fn [_]
|
||||
tokens-lib))
|
||||
(rx/catch (fn [sd-error]
|
||||
(let [reference-errors (reference-errors sd-error)]
|
||||
;; We allow reference errors for the users to resolve in the ui and throw on any other errors
|
||||
(if reference-errors
|
||||
(rx/of tokens-lib)
|
||||
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error)))))))
|
||||
(catch js/Error e
|
||||
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e))))))))))
|
||||
|
||||
;; === Hooks
|
||||
|
||||
(defonce !tokens-cache (atom nil))
|
||||
|
|
134
frontend/src/app/main/data/workspace/tokens/import_export.cljs
Normal file
134
frontend/src/app/main/data/workspace/tokens/import_export.cljs
Normal file
|
@ -0,0 +1,134 @@
|
|||
;; 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 app.main.data.workspace.tokens.import-export
|
||||
(:require
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.json :as json]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.store :as st]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn- extract-reference-errors
|
||||
"Extracts reference errors from errors produced by StyleDictionary."
|
||||
[err]
|
||||
(let [[header-1 header-2 & errors] (str/split err "\n")]
|
||||
(when (and
|
||||
(= header-1 "Error: ")
|
||||
(= header-2 "Reference Errors:"))
|
||||
errors)))
|
||||
|
||||
(defn- extract-name-error
|
||||
"Extracts name error out of malli schema error during import."
|
||||
[err]
|
||||
(let [schema-error (some-> (ex-data err)
|
||||
(get-in [:app.common.schema/explain :errors])
|
||||
(first))
|
||||
name-error? (= (:in schema-error) [:name])]
|
||||
(when name-error?
|
||||
(wte/error-ex-info :error.import/invalid-token-name (:value schema-error) err))))
|
||||
|
||||
(defn- group-by-value [m]
|
||||
(reduce (fn [acc [k v]]
|
||||
(update acc v conj k)) {} m))
|
||||
|
||||
(defn- show-unknown-types-warning [unknown-tokens]
|
||||
(let [type->tokens (group-by-value unknown-tokens)]
|
||||
(ntf/show {:content (tr "workspace.tokens.unknown-token-type")
|
||||
:detail (->> (for [[token-type tokens] type->tokens]
|
||||
(tr "workspace.tokens.unknown-token-type-section" token-type (count tokens)))
|
||||
(str/join "\n"))
|
||||
:type :toast
|
||||
:level :info})))
|
||||
|
||||
(defn- decode-json
|
||||
[json-string]
|
||||
(try
|
||||
(json/decode json-string {:key-fn identity})
|
||||
(catch js/Error e
|
||||
(throw (wte/error-ex-info :error.import/json-parse-error json-string e)))))
|
||||
|
||||
(defn- parse-decoded-json
|
||||
[decoded-json file-name]
|
||||
(try
|
||||
{:tokens-lib (ctob/parse-decoded-json decoded-json file-name)
|
||||
:unknown-tokens (ctob/get-tokens-of-unknown-type decoded-json)}
|
||||
(catch js/Error e
|
||||
(let [err (or (extract-name-error e)
|
||||
(wte/error-ex-info :error.import/invalid-json-data decoded-json e))]
|
||||
(throw err)))))
|
||||
|
||||
(defn- validate-library
|
||||
"Resolve tokens in the library and search for errors. Reference errors are ignored, since
|
||||
it can be resolved by the user in the UI. All the other errors are thrown as exceptions."
|
||||
[{:keys [tokens-lib unknown-tokens]}]
|
||||
(when unknown-tokens
|
||||
(st/emit! (show-unknown-types-warning unknown-tokens)))
|
||||
(try
|
||||
(->> (ctob/get-all-tokens tokens-lib)
|
||||
(sd/resolve-tokens-with-errors)
|
||||
(rx/map (fn [_]
|
||||
tokens-lib))
|
||||
(rx/catch (fn [sd-error]
|
||||
(let [reference-errors (extract-reference-errors sd-error)]
|
||||
(if reference-errors
|
||||
(rx/of tokens-lib)
|
||||
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error sd-error sd-error)))))))
|
||||
(catch js/Error e
|
||||
(throw (wte/error-ex-info :error.import/style-dictionary-unknown-error "" e)))))
|
||||
|
||||
(defn- drop-parent-directory
|
||||
[path]
|
||||
(->> (cfh/split-path path)
|
||||
(rest)
|
||||
(str/join "/")))
|
||||
|
||||
(defn- remove-path-extension
|
||||
[path]
|
||||
(-> (str/split path ".")
|
||||
(butlast)
|
||||
(str/join)))
|
||||
|
||||
(defn- file-path->set-name
|
||||
[path]
|
||||
(-> path
|
||||
(drop-parent-directory)
|
||||
(remove-path-extension)))
|
||||
|
||||
(defn import-file-stream
|
||||
[file-path file-text]
|
||||
(let [file-name (remove-path-extension file-path)]
|
||||
(->> file-text
|
||||
(rx/map decode-json)
|
||||
(rx/map #(parse-decoded-json % file-name))
|
||||
(rx/mapcat validate-library))))
|
||||
|
||||
(defn import-directory-stream
|
||||
[file-stream]
|
||||
(->> file-stream
|
||||
(rx/map (fn [[file-path file-text]]
|
||||
(let [set-name (file-path->set-name file-path)]
|
||||
(try
|
||||
{set-name (decode-json file-text)}
|
||||
(catch js/Error e
|
||||
;; Ignore files with json parse errors
|
||||
{:path file-path :error e})))))
|
||||
(rx/reduce (fn [merged-json decoded-json]
|
||||
(if (:error decoded-json)
|
||||
merged-json
|
||||
(conj merged-json decoded-json)))
|
||||
{})
|
||||
(rx/map (fn [merged-json]
|
||||
(parse-decoded-json (if (= 1 (count merged-json))
|
||||
(val (first merged-json))
|
||||
merged-json)
|
||||
(ffirst merged-json))))
|
||||
(rx/mapcat validate-library)))
|
|
@ -7,12 +7,11 @@
|
|||
(ns app.main.ui.workspace.tokens.modals.import
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.main.data.event :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[app.main.data.workspace.tokens.errors :as wte]
|
||||
[app.main.data.workspace.tokens.errors :as dwte]
|
||||
[app.main.data.workspace.tokens.import-export :as dwti]
|
||||
[app.main.data.workspace.tokens.library-edit :as dwtl]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.ds.buttons.button :refer [button*]]
|
||||
|
@ -29,23 +28,8 @@
|
|||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- drop-parent-directory [path]
|
||||
(->> (cfh/split-path path)
|
||||
(rest)
|
||||
(str/join "/")))
|
||||
|
||||
(defn- remove-path-extension [path]
|
||||
(-> (str/split path ".")
|
||||
(butlast)
|
||||
(str/join)))
|
||||
|
||||
(defn- file-path->set-name
|
||||
[path]
|
||||
(-> path
|
||||
(drop-parent-directory)
|
||||
(remove-path-extension)))
|
||||
|
||||
(defn- on-import-stream [tokens-lib-stream]
|
||||
(defn- on-stream-imported
|
||||
[tokens-lib-stream]
|
||||
(rx/sub!
|
||||
tokens-lib-stream
|
||||
(fn [lib]
|
||||
|
@ -53,9 +37,8 @@
|
|||
(dwtl/import-tokens-lib lib))
|
||||
(modal/hide!))
|
||||
(fn [err]
|
||||
(js/console.error err)
|
||||
(st/emit! (ntf/show {:content (wte/humanize-errors [(ex-data err)])
|
||||
:detail (wte/detail-errors [(ex-data err)])
|
||||
(st/emit! (ntf/show {:content (dwte/humanize-errors [(ex-data err)])
|
||||
:detail (dwte/detail-errors [(ex-data err)])
|
||||
:type :toast
|
||||
:level :error})))))
|
||||
|
||||
|
@ -81,44 +64,28 @@
|
|||
type (.-type file)]
|
||||
(or
|
||||
(= type "application/json")
|
||||
(str/ends-with? name ".json")))))
|
||||
;; Read files as text, ignore files with json parse errors
|
||||
(map (fn [file]
|
||||
(->> (wapi/read-file-as-text file)
|
||||
(rx/mapcat (fn [json]
|
||||
(let [path (.-webkitRelativePath file)]
|
||||
(rx/of
|
||||
(try
|
||||
{(file-path->set-name path) (sd/parse-json json)}
|
||||
(catch js/Error e
|
||||
{:path path :error e}))))))))))]
|
||||
|
||||
(->> (apply rx/merge files)
|
||||
(rx/reduce (fn [acc cur]
|
||||
(if (:error cur)
|
||||
acc
|
||||
(conj acc cur)))
|
||||
{})
|
||||
(rx/map #(sd/decode-json-data (if (= 1 (count %))
|
||||
(val (first %))
|
||||
%)
|
||||
(ffirst %)))
|
||||
(rx/map :tokens-lib)
|
||||
(on-import-stream))
|
||||
(str/ends-with? name ".json"))))))]
|
||||
(->> (rx/from files)
|
||||
(rx/mapcat (fn [file]
|
||||
(->> (wapi/read-file-as-text file)
|
||||
(rx/map (fn [file-text]
|
||||
[(.-webkitRelativePath file)
|
||||
file-text])))))
|
||||
(dwti/import-directory-stream)
|
||||
(on-stream-imported))
|
||||
|
||||
(-> (mf/ref-val dir-input-ref)
|
||||
(dom/set-value! "")))))
|
||||
|
||||
on-import
|
||||
on-import-file
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [file (-> (dom/get-target event)
|
||||
(dom/get-files)
|
||||
(first))
|
||||
file-name (remove-path-extension (.-name file))]
|
||||
(first))]
|
||||
(->> (wapi/read-file-as-text file)
|
||||
(sd/process-json-stream {:file-name file-name})
|
||||
(on-import-stream))
|
||||
(dwti/import-file-stream (.-name file))
|
||||
(on-stream-imported))
|
||||
|
||||
(-> (mf/ref-val file-input-ref)
|
||||
(dom/set-value! "")))))]
|
||||
|
@ -142,7 +109,7 @@
|
|||
:ref file-input-ref
|
||||
:style {:display "none"}
|
||||
:accept ".json"
|
||||
:on-change on-import}]
|
||||
:on-change on-import-file}]
|
||||
[:input {:type "file"
|
||||
:ref dir-input-ref
|
||||
:style {:display "none"}
|
||||
|
|
|
@ -381,7 +381,7 @@
|
|||
(fn []
|
||||
(st/emit! (ptk/data-event ::ev/event {::ev/name "export-tokens"}))
|
||||
(let [tokens-json (some-> (deref refs/tokens-lib)
|
||||
(ctob/encode-dtcg)
|
||||
(ctob/export-dtcg-json)
|
||||
(json/encode :key-fn identity))]
|
||||
(->> (wapi/create-blob (or tokens-json "{}") "application/json")
|
||||
(dom/trigger-download "tokens.json")))))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[frontend-tests.logic.frame-guides-test]
|
||||
[frontend-tests.logic.groups-test]
|
||||
[frontend-tests.plugins.context-shapes-test]
|
||||
[frontend-tests.tokens.import-export-test]
|
||||
[frontend-tests.tokens.logic.token-actions-test]
|
||||
[frontend-tests.tokens.logic.token-data-test]
|
||||
[frontend-tests.tokens.style-dictionary-test]
|
||||
|
@ -40,5 +41,6 @@
|
|||
'frontend-tests.basic-shapes-test
|
||||
'frontend-tests.tokens.logic.token-actions-test
|
||||
'frontend-tests.tokens.logic.token-data-test
|
||||
'frontend-tests.tokens.import-export-test
|
||||
'frontend-tests.tokens.style-dictionary-test
|
||||
'frontend-tests.tokens.token-form-test))
|
||||
|
|
101
frontend/test/frontend_tests/tokens/import_export_test.cljs
Normal file
101
frontend/test/frontend_tests/tokens/import_export_test.cljs
Normal file
|
@ -0,0 +1,101 @@
|
|||
;; 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.import-export-test
|
||||
(:require
|
||||
[app.common.json :as json]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.workspace.tokens.import-export :as dwti]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(t/deftest import-file-stream-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "import simple color token value"
|
||||
(let [json (-> {"core" {"color" {"$value" "red"
|
||||
"$type" "color"}}
|
||||
"$metadata" {"tokenSetOrder" ["core"]}}
|
||||
(json/encode {:type :json-verbose}))]
|
||||
(->> (rx/of json)
|
||||
(dwti/import-file-stream "core")
|
||||
(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-errors-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."]
|
||||
(#'dwti/extract-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? (#'dwti/extract-reference-errors nil))) ;; #' is used to access private functions
|
||||
(t/is (nil? (#'dwti/extract-reference-errors "none")))))
|
||||
|
||||
(t/deftest import-empty-json-stream-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "fails on empty json string"
|
||||
(->> (rx/of "{}")
|
||||
(dwti/import-file-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 import-invalid-json-stream-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "fails on invalid json"
|
||||
(->> (rx/of "{,}")
|
||||
(dwti/import-file-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 import-non-token-json-stream-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "fails on non-token json"
|
||||
(->> (rx/of "{\"foo\": \"bar\"}")
|
||||
(dwti/import-file-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 import-missing-references-json-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "allows missing references in tokens"
|
||||
(let [json (-> {"core" {"color" {"$value" "{missing}"
|
||||
"$type" "color"}}
|
||||
"$metadata" {"tokenSetOrder" ["core"]}}
|
||||
(json/encode {:type :json-verbose}))]
|
||||
(->> (rx/of json)
|
||||
(dwti/import-file-stream "")
|
||||
(rx/subs! (fn [tokens-lib]
|
||||
(t/is (instance? ctob/TokensLib tokens-lib))
|
||||
(t/is (= "{missing}" (:value (ctob/get-token-in-set tokens-lib "core" "color"))))
|
||||
(done))))))))
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
(ns frontend-tests.tokens.style-dictionary-test
|
||||
(:require
|
||||
[app.common.transit :as tr]
|
||||
[app.common.types.tokens-lib :as ctob]
|
||||
[app.main.data.style-dictionary :as sd]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -51,114 +50,3 @@
|
|||
(t/is (= :error.token/number-too-large
|
||||
(get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code])))
|
||||
(done))))))))
|
||||
|
||||
(t/deftest process-json-stream-test
|
||||
(t/async
|
||||
done
|
||||
(t/testing "process simple color token value"
|
||||
(let [json (-> {"core" {"color" {"$value" "red"
|
||||
"$type" "color"}}
|
||||
"$metadata" {"tokenSetOrder" ["core"]}}
|
||||
(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 "allows missing references in tokens"
|
||||
(let [json (-> {"core" {"color" {"$value" "{missing}"
|
||||
"$type" "color"}}
|
||||
"$metadata" {"tokenSetOrder" ["core"]}}
|
||||
(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 (= "{missing}" (:value (ctob/get-token-in-set tokens-lib "core" "color"))))
|
||||
(done))))))))
|
||||
|
||||
(t/deftest single-set-legacy-json-decoding
|
||||
(let [decode-single-set-legacy-json #'sd/decode-single-set-legacy-json
|
||||
json {"color" {"red" {"100" {"value" "red"
|
||||
"type" "color"
|
||||
"description" ""}}}}
|
||||
lib (decode-single-set-legacy-json (ctob/ensure-tokens-lib nil) "single_set" json)
|
||||
get-set-token (fn [set-name token-name]
|
||||
(some-> (ctob/get-set lib set-name)
|
||||
(ctob/get-token token-name)))]
|
||||
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
|
||||
(t/testing "token added"
|
||||
(t/is (some? (get-set-token "single_set" "color.red.100"))))))
|
||||
|
||||
(t/deftest single-set-dtcg-json-decoding
|
||||
(let [decode-single-set-json #'sd/decode-single-set-json
|
||||
json (-> {"color" {"red" {"100" {"$value" "red"
|
||||
"$type" "color"
|
||||
"$description" ""}}}})
|
||||
lib (decode-single-set-json (ctob/ensure-tokens-lib nil) "single_set" json)
|
||||
get-set-token (fn [set-name token-name]
|
||||
(some-> (ctob/get-set lib set-name)
|
||||
(ctob/get-token token-name)))]
|
||||
(t/is (= '("single_set") (ctob/get-ordered-set-names lib)))
|
||||
(t/testing "token added"
|
||||
(t/is (some? (get-set-token "single_set" "color.red.100"))))))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue