🔧 Add separated TokenStatus to store status apart of TokensLib

This commit is contained in:
Andrés Moya 2026-06-05 14:44:09 +02:00
parent b748f51fb9
commit ceb682daf2
28 changed files with 1617 additions and 307 deletions

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.schema :as sm]
@ -29,6 +30,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as ctt]
[app.common.types.variant :as ctv]
@ -397,6 +399,13 @@
[:id ::sm/uuid]
[:attrs [:maybe ctob/schema:token-theme-attrs]]]]
[:set-tokens-status
[:map {:title "SetTokensStatus"}
[:type [:= :set-tokens-status]]
[:theme-ids [:set ::sm/uuid]]
[:set-ids [:set ::sm/uuid]]]]
;; TODO deprecate this once everyone uses set-tokens-status
[:set-active-token-themes
[:map {:title "SetActiveTokenThemes"}
[:type [:= :set-active-token-themes]]
@ -425,9 +434,9 @@
[:before-group [:maybe :boolean]]]]
[:set-base-font-size
[:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]]
[:base-font-size :string]]]
[:map {:title "ModBaseFontSize"}
[:type [:= :set-base-font-size]]
[:base-font-size :string]]]
[:set-tokens-file
[:map {:title "SetTokensFile"}
@ -993,73 +1002,78 @@
(defmethod process-change :set-token
[data {:keys [set-id token-id attrs]}]
(let [data (ctf/ensure-tokens-lib data)]
(update data :tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-token lib' set-id token-id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-token lib' set-id token-id)
(not (ctob/get-token lib' set-id token-id))
(ctob/add-token lib' set-id (ctob/make-token attrs))
(not (ctob/get-token lib' set-id token-id))
(ctob/add-token lib' set-id (ctob/make-token attrs))
:else
(ctob/update-token lib' set-id token-id
(fn [prev-token]
(ctob/make-token (merge prev-token attrs)))))))))
:else
(ctob/update-token lib' set-id token-id
(fn [prev-token]
(ctob/make-token (merge prev-token attrs)))))))))
(defmethod process-change :set-token-set
[data {:keys [id attrs]}]
(let [data (ctf/ensure-tokens-lib data)]
(update data :tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-set lib' id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-set lib' id)
(not (ctob/get-set lib' id))
(ctob/add-set lib' (ctob/make-token-set attrs))
(not (ctob/get-set lib' id))
(ctob/add-set lib' (ctob/make-token-set attrs))
:else
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
:else
(ctob/update-set lib' id (fn [_] (ctob/make-token-set attrs))))))))
(defmethod process-change :set-token-theme
[data {:keys [id attrs]}]
(let [data (ctf/ensure-tokens-lib data)]
(update data :tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-theme lib' id)
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib
(fn [lib']
(cond
(not attrs)
(ctob/delete-theme lib' id)
(not (ctob/get-theme lib' id))
(ctob/add-theme lib' (ctob/make-token-theme attrs))
(not (ctob/get-theme lib' id))
(ctob/add-theme lib' (ctob/make-token-theme attrs))
:else
(ctob/update-theme lib'
id
(fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme attrs)))))))))
:else
(ctob/update-theme lib'
id
(fn [prev-token-theme]
(ctob/make-token-theme (merge prev-token-theme attrs)))))))))
(defmethod process-change :set-tokens-status
[data {:keys [theme-ids set-ids]}]
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-status ctos/set-tokens-status theme-ids set-ids)))
(defmethod process-change :set-active-token-themes
[data {:keys [theme-paths]}]
(-> (ctf/ensure-tokens-lib data)
(update :tokens-lib ctob/set-active-themes theme-paths)))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/set-active-themes theme-paths)))
(defmethod process-change :rename-token-set-group
[data {:keys [set-group-path set-group-fname]}]
(-> (ctf/ensure-tokens-lib data)
(update :tokens-lib ctob/rename-set-group set-group-path set-group-fname)))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/rename-set-group set-group-path set-group-fname)))
(defmethod process-change :move-token-set
[data {:keys [from-path to-path before-path before-group] :as changes}]
(-> (ctf/ensure-tokens-lib data)
(update :tokens-lib ctob/move-set from-path to-path before-path before-group)))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/move-set from-path to-path before-path before-group)))
(defmethod process-change :move-token-set-group
[data {:keys [from-path to-path before-path before-group]}]
(-> (ctf/ensure-tokens-lib data)
(update :tokens-lib ctob/move-set-group from-path to-path before-path before-group)))
(-> (cfo/ensure-tokens-lib data)
(cfo/update-tokens-lib ctob/move-set-group from-path to-path before-path before-group)))
;; === Design Tokens configuration
@ -1069,12 +1083,11 @@
(defmethod process-change :set-tokens-file
[data {:keys [library-id]}]
(assoc data :tokens-file library-id))
(cfo/set-tokens-file data library-id))
;; === Operations
(def decode-shape-attrs
(def decode-shape-attrs
(sm/decoder cts/schema:shape-attrs sm/json-transformer))
(defmethod process-operation :assign
@ -1243,3 +1256,22 @@
(defmethod frames-changed :default
[_ _]
nil)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Design Tokens changes detection
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private tokens-lib-change-types
"Set of change types that modify the tokens library."
#{:set-tokens-lib
:set-token
:set-token-set
:set-token-theme
:rename-token-set-group
:move-token-set
:move-token-set-group})
(defn tokens-lib-changed?
"Check if a commit contains changes that modify the tokens library."
[changes]
(some #(tokens-lib-change-types (:type %)) changes))

View File

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.files.changes :as cfc]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@ -22,6 +23,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape.layout :as ctl]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]))
@ -1020,6 +1022,21 @@
:attrs (datafy prev-theme)})
(apply-changes-local))))
(defn set-tokens-status
([changes tokens-status]
(assert-library! changes)
(assert (ctos/tokens-status? tokens-status))
(let [theme-ids (ctos/get-active-theme-ids tokens-status)
set-ids (ctos/get-active-set-ids tokens-status)
library-data (::library-data (meta changes))
prev-tokens-status (cfo/get-tokens-status library-data)
prev-theme-ids (ctos/get-active-theme-ids prev-tokens-status)
prev-set-ids (ctos/get-active-set-ids prev-tokens-status)]
(-> changes
(update :redo-changes conj {:type :set-tokens-status :theme-ids theme-ids :set-ids set-ids})
(update :undo-changes conj {:type :set-tokens-status :theme-ids prev-theme-ids :set-ids prev-set-ids})
(apply-changes-local)))))
(defn set-active-token-themes
[changes active-theme-paths]
(assert-library! changes)

View File

@ -13,6 +13,7 @@
[app.common.files.comp-processors :as cfcp]
[app.common.files.defaults :as cfd]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.rect :as grc]
@ -1874,6 +1875,13 @@
(update :pages-index d/update-vals update-container)
(d/update-when :components d/update-vals update-container))))
(defmethod migrate-data "0025-separate-tokens-status"
[data _]
(if-let [tokens-lib (:tokens-lib data)]
(assoc data :tokens-status
(cfo/make-tokens-status-from-lib tokens-lib))
data))
(def available-migrations
(into (d/ordered-set)
["legacy-2"
@ -1955,4 +1963,5 @@
"0021-fix-shape-svg-attrs"
"0022-normalize-component-root-and-resync"
"0023-repair-token-themes-with-inexistent-sets"
"0024b-fix-stroke-cap-placement"]))
"0024b-fix-stroke-cap-placement"
"0025-separate-tokens-status"]))

View File

@ -9,13 +9,18 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.i18n :refer [tr]]
[app.common.logging :as log]
[app.common.schema :as sm]
[app.common.types.token :as cto]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.set :as set]
[cuerdas.core :as str]
[malli.core :as m]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HIGH LEVEL SCHEMAS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -165,8 +170,8 @@
(some (fn [[token-name _]]
(not (ctob/token-name-path-exists? token-name tokens-tree)))
new-tokens))))]])
(defn find-refs [value]
(prn value)
(defn- find-refs [value]
(cond
(string? value)
(cto/find-token-value-references value)
@ -345,6 +350,8 @@
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Token
(def parseable-token-value-regexp
"Regexp that can be used to parse a number value out of resolved token value.
This regexp also trims whitespace around the value."
@ -416,3 +423,128 @@
;; FIXME: this should be precalculated ?
(defn is-reference? [token]
(str/includes? (:value token) "{"))
;; Tokens lib in file data
(defn make-tokens-status-from-lib
"Make a TokensStatus from a TokensLib, activating the themes and sets
marked as active in the library (to migrate from legacy files)."
[tokens-lib]
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(let [active-theme-ids (into #{}
(comp (map :id)
(filter #(not= % ctob/hidden-theme-id)))
(ctob/get-active-themes tokens-lib))
active-set-ids (into #{}
(comp (map #(ctob/get-set-by-name tokens-lib %))
(remove nil?)
(map ctob/get-id))
(ctob/get-active-themes-set-names tokens-lib))]
(ctos/make-tokens-status :active-theme-ids active-theme-ids
:active-set-ids active-set-ids)))
(defn ensure-tokens-lib
"Ensure file-data has a :tokens-lib or :tokens-file, and also a :tokens-status, creating them if necessary."
[file-data]
(cond-> file-data
(nil? (:tokens-file file-data))
(update :tokens-lib #(or % (ctob/make-tokens-lib)))
:always
(update :tokens-status #(or % (ctos/make-tokens-status)))))
(defn get-tokens-file
[file-data]
(:tokens-file file-data))
(defn set-tokens-file
[file-data tokens-file]
(assoc file-data :tokens-file tokens-file))
(defn get-tokens-lib
[file-data]
(:tokens-lib file-data))
(defn get-tokens-status
[file-data]
(:tokens-status file-data))
(defn update-tokens-lib
"Update the tokens-lib inside file-data through a callback function.
The function will receive the tokens lib and the rest of args."
[file-data f & args]
(d/update-when file-data :tokens-lib #(apply f % args)))
(defn update-tokens-status
"Update the tokens-status inside file-data through a callback function.
The function will receive the tokens status and the rest of args."
[file-data f & args]
(d/update-when file-data :tokens-status #(apply f % args)))
;; Tokens status with tokens lib
(defn- calculate-active-sets
"Obtain the set-ids that needs to be active for a particular set of theme ids"
[active-theme-ids tokens-lib]
(let [active-themes (map #(ctob/get-theme tokens-lib %) active-theme-ids)
active-set-names (reduce set/union #{} (map :sets active-themes))
active-sets (map #(ctob/get-set-by-name tokens-lib %) active-set-names)
active-set-ids (into #{} (map ctob/get-id) active-sets)]
active-set-ids))
(defn get-active-themes
"Return an ordered sequence of active themes"
[tokens-status tokens-lib]
(->> (ctob/get-themes tokens-lib)
(filter #(ctos/theme-active? tokens-status (ctob/get-id %)))))
(defn activate-theme
"Activate a theme and all its sets"
[tokens-status tokens-lib id]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? id) "expected valid theme id")
(if-not (ctos/theme-active? tokens-status id)
(if-let [theme (ctob/get-theme tokens-lib id)]
(let [group-themes (into #{} (ctob/get-themes-in-group tokens-lib (:group theme)))
active-theme-ids (ctos/get-active-theme-ids tokens-status)
active-theme-ids' (-> (set/difference active-theme-ids group-themes)
(conj id))
active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)]
(ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids'))
tokens-status)
tokens-status))
(defn deactivate-theme
"Deactivate a theme and all its sets that are not in other active themes"
[tokens-status tokens-lib id]
(assert (ctos/tokens-status? tokens-status) "expected valid tokens-status")
(assert (ctob/tokens-lib? tokens-lib) "expected valid tokens-lib")
(assert (uuid? id) "expected valid theme id")
(if (ctos/theme-active? tokens-status id)
(let [active-theme-ids' (disj (ctos/get-active-theme-ids tokens-status) id)
active-set-ids' (calculate-active-sets active-theme-ids' tokens-lib)]
(ctos/set-tokens-status tokens-status active-theme-ids' active-set-ids'))
tokens-status))
(defn sync-tokens-status-with-lib
"Synchronizes tokens status with the current tokens lib:
- Delete any theme or set that no longer exists in the lib."
[tokens-status tokens-lib]
(let [active-theme-ids (ctos/get-active-theme-ids tokens-status)
valid-theme-ids (into #{}
(filter #(some? (ctob/get-theme tokens-lib %)))
active-theme-ids)
active-set-ids (ctos/get-active-set-ids tokens-status)
valid-set-ids (into #{}
(filter #(some? (ctob/get-set tokens-lib %)))
active-set-ids)]
(if (or (not= active-theme-ids valid-theme-ids)
(not= active-set-ids valid-set-ids))
(do
(log/info :hint "syncing token status"
:removed-themes (count (set/difference active-theme-ids valid-theme-ids))
:removed-sets (count (set/difference active-set-ids valid-set-ids)))
(ctos/set-tokens-status tokens-status valid-theme-ids valid-set-ids))
tokens-status)))

View File

@ -7,7 +7,9 @@
(ns app.common.logic.tokens
(:require
[app.common.files.changes-builder :as pcb]
[app.common.types.tokens-lib :as ctob]))
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]))
(defn- generate-update-active-sets
"Copy the active sets from the currently active themes and move them
@ -40,32 +42,45 @@
[changes tokens-lib set-name]
(generate-update-active-sets changes tokens-lib #(ctob/toggle-set % set-name)))
(defn- generate-update-active-token-theme
"Change the active state of a theme in `tokens-lib`. If after the change there is
any active theme other than the hidden one, deactivate the hidden theme."
[changes tokens-lib update-fn]
(let [active-token-themes (some-> tokens-lib
(update-fn)
(ctob/get-active-theme-paths))
active-token-themes' (if (= active-token-themes #{ctob/hidden-theme-path})
active-token-themes
(disj active-token-themes ctob/hidden-theme-path))]
(pcb/set-active-token-themes changes active-token-themes')))
(defn generate-set-active-token-theme
"Activate or deactivate a token theme in `tokens-lib`."
[changes tokens-lib id active?]
;; ================== nuevo
(defn generate-activate-theme
[changes tokens-status tokens-lib id]
(let [tokens-status' (cfo/activate-theme tokens-status tokens-lib id)]
(if (not= tokens-status tokens-status')
(pcb/set-tokens-status changes tokens-status')
changes)))
(defn generate-deactivate-theme
[changes tokens-status tokens-lib id]
(let [tokens-status' (cfo/deactivate-theme tokens-status tokens-lib id)]
(if (not= tokens-status tokens-status')
(pcb/set-tokens-status changes tokens-status')
changes)))
(defn generate-set-theme-status
[changes tokens-status tokens-lib id active?]
(if active?
(generate-update-active-token-theme changes tokens-lib
#(ctob/activate-theme % id))
(generate-update-active-token-theme changes tokens-lib
#(ctob/deactivate-theme % id))))
(generate-activate-theme changes tokens-status tokens-lib id)
(generate-deactivate-theme changes tokens-status tokens-lib id)))
(defn generate-toggle-token-theme
"Toggle the active state of a token theme in `tokens-lib`."
[changes tokens-lib id]
(generate-update-active-token-theme changes tokens-lib
#(ctob/toggle-theme-active % id)))
(defn generate-toggle-theme
[changes tokens-status tokens-lib id]
(let [active? (ctos/theme-active? tokens-status id)]
(if active?
(generate-deactivate-theme changes tokens-status tokens-lib id)
(generate-activate-theme changes tokens-status tokens-lib id))))
(defn generate-sync-tokens-status-with-lib
"Synchronizes tokens status with the current tokens lib"
[changes tokens-status tokens-lib]
(let [tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(if (not= tokens-status tokens-status')
(pcb/set-tokens-status changes tokens-status')
changes)))
;; ======= fin nuevo
(defn toggle-token-set-group
"Toggle a token set group at `group-path` in `tokens-lib` for a `tokens-lib-theme`."
@ -178,4 +193,4 @@
(reduce (fn [changes set]
(pcb/set-token-set changes (ctob/get-id set) nil))
changes
sets)))
sets)))

View File

@ -7,6 +7,7 @@
(ns app.common.test-helpers.tokens
(:require
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.shapes :as ths]
[app.common.types.container :as ctn]
@ -19,15 +20,32 @@
(defn get-tokens-lib
[file]
(:tokens-lib (ctf/file-data file)))
(-> file (ctf/file-data) (cfo/get-tokens-lib)))
(defn get-tokens-status
[file]
(-> file (ctf/file-data) (cfo/get-tokens-status)))
(defn add-tokens-lib
"Ensure the file has a tokens-lib and a tokens-status in its data, creating empty ones if not"
[file]
(ctf/update-file-data file ctf/ensure-tokens-lib))
(ctf/update-file-data file cfo/ensure-tokens-lib))
(defn update-tokens-lib
"Modify the tokens-lib of a file "
[file f]
(ctf/update-file-data file #(update % :tokens-lib f)))
(ctf/update-file-data file #(cfo/update-tokens-lib % f)))
(defn update-tokens-status
[file f]
(ctf/update-file-data file #(cfo/update-tokens-status % f)))
(defn sample-file-with-tokens
[& {:keys [lib-fn status-fn file-id] :or {lib-fn identity status-fn identity file-id :file1}}]
(-> (thf/sample-file file-id)
(add-tokens-lib)
(update-tokens-lib lib-fn)
(update-tokens-status status-fn)))
(defn get-token
[file set-id token-id]

View File

@ -29,6 +29,7 @@
[app.common.types.shape-tree :as ctst]
[app.common.types.text :as txt]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.types.typographies-list :as ctyl]
[app.common.types.typography :as cty]
[app.common.uuid :as uuid]
@ -86,7 +87,9 @@
[:components {:optional true} schema:components]
[:typographies {:optional true} schema:typographies]
[:plugin-data {:optional true} schema:plugin-data]
[:tokens-lib {:optional true} ctob/schema:tokens-lib]])
[:tokens-file {:optional true} ::sm/uuid] ;; The tokens-lib may be in this file or in an external library
[:tokens-lib {:optional true} ctob/schema:tokens-lib]
[:tokens-status {:optional true} ctos/schema:tokens-status]])
(def schema:file
"A schema for validate a file data structure; data is optional
@ -196,11 +199,6 @@
(check-file file)))
(defn ensure-tokens-lib
"Ensure file-data has a :tokens-lib key, creating one if necessary."
[file-data]
(update file-data :tokens-lib #(or % (ctob/make-tokens-lib))))
;; Helpers
(defn file-data
@ -311,6 +309,7 @@
(update-objects-tree container f)))))
;; Asset helpers
(defn find-component-file
[file libraries component-file]
(if (and (some? file) (= component-file (:id file)))

View File

@ -755,11 +755,15 @@
(def ^:private theme-separator "/")
(defn- join-theme-path [group name]
(cpn/join-path [group name] :separator theme-separator :with-spaces? false))
(defn- join-theme-path [group name with-spaces?]
(let [path (if (and (str/empty? group) with-spaces?) [name] [group name])]
(cpn/join-path path :separator theme-separator :with-spaces? with-spaces?)))
(defn get-theme-path [theme]
(join-theme-path (:group theme) (:name theme)))
(defn get-theme-path
([theme]
(get-theme-path theme false))
([theme with-spaces?]
(join-theme-path (:group theme) (:name theme) with-spaces?)))
(defn split-theme-path [path]
(cpn/split-group-name path
@ -767,7 +771,7 @@
:with-spaces? false))
(def hidden-theme-path
(join-theme-path hidden-theme-group hidden-theme-name))
(join-theme-path hidden-theme-group hidden-theme-name false))
;; === TokenThemes (collection)
@ -778,7 +782,9 @@
(delete-theme [_ id] "delete a theme in the library")
(theme-count [_] "get the total number if themes in the library")
(get-theme-tree [_] "get a nested tree of all themes in the library")
(get-theme-tree-no-hidden [_] "get a nested tree of all themes in the library except the hidden theme")
(get-themes [_] "get an ordered sequence of all themes in the library")
(get-themes-in-group [_ group] "get an ordered sequence of the themes in the group")
(get-theme [_ id] "get one theme looking for id")
(get-theme-by-name [_ group name] "get one theme looking for group and name")
(get-theme-groups [_] "get a sequence of group names by order")
@ -1177,7 +1183,7 @@ Will return a value that matches this schema:
(d/dissoc-in [group name])))
(if same-path?
active-themes
(disj active-themes (join-theme-path group name)))))))
(disj active-themes (join-theme-path group name false)))))))
this))
(delete-theme [this id]
@ -1186,12 +1192,15 @@ Will return a value that matches this schema:
(if theme
(TokensLib. sets
(d/dissoc-in themes [group name])
(disj active-themes (join-theme-path group name)))
(disj active-themes (join-theme-path group name false)))
this)))
(get-theme-tree [_]
themes)
(get-theme-tree-no-hidden [_]
(d/dissoc-in themes [hidden-theme-group hidden-theme-name]))
(get-theme-groups [_]
(into [] (comp
(map key)
@ -1202,6 +1211,10 @@ Will return a value that matches this schema:
(->> (tree-seq d/ordered-map? vals themes)
(filter (partial instance? TokenTheme))))
(get-themes-in-group [_ group]
(->> (get themes group)
(map (comp get-id val))))
(theme-count [this]
(count (get-themes this)))

View File

@ -0,0 +1,147 @@
;; 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 Sucursal en España SL
(ns app.common.types.tokens-status
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as c.json])
[app.common.schema :as sm]
[app.common.schema.generators :as sg]
[app.common.transit :as t]
[clojure.core.protocols :as cp]
[clojure.datafy :refer [datafy]]
[clojure.pprint :as pp]))
;; TokensStatus datatype contains the activation status of the themes and sets
;; in a tokens library.
(defprotocol ITokensStatus
(get-active-theme-ids [_] "Return a clojure set of active theme ids")
(get-active-set-ids [_] "Return a clojure set of active set ids")
(theme-active? [_ theme-id] "Check if a theme is active")
(set-active? [_ set-id] "Check if a set is active")
(set-tokens-status [_ theme-ids set-ids] "Set the activation status of the themes and sets"))
(deftype TokensStatus [active-theme-ids active-set-ids]
cp/Datafiable
(datafy [_]
{:active-theme-ids active-theme-ids
:active-set-ids active-set-ids})
#?@(:clj
[c.json/JSONWriter
(-write [this writter options]
(c.json/-write (datafy this) writter options))])
ITokensStatus
(get-active-theme-ids [_]
active-theme-ids)
(get-active-set-ids [_]
active-set-ids)
(theme-active? [_ theme-id]
(assert (uuid? theme-id))
(contains? active-theme-ids theme-id))
(set-active? [_ set-id]
(assert (uuid? set-id))
(contains? active-set-ids set-id))
(set-tokens-status [_ theme-ids set-ids]
(assert (set? theme-ids))
(assert (set? set-ids))
(TokensStatus. theme-ids set-ids)))
;; === Helper & Predicate ===
(defn map->TokensStatus
[{:keys [active-theme-ids active-set-ids]}]
(TokensStatus. active-theme-ids active-set-ids))
(defn tokens-status?
[o]
(instance? TokensStatus o))
;; === Schemas, Check functions & Constructor ===
(declare make-tokens-status)
(def schema:tokens-status-attrs
[:map {:title "TokensStatus"}
[:active-theme-ids {:optional true} [:set {:gen/max 5} ::sm/uuid]]
[:active-set-ids {:optional true} [:set {:gen/max 5} ::sm/uuid]]])
(def schema:tokens-status
[:and {:gen/gen (->> (sg/generator schema:tokens-status-attrs)
(sg/fmap #(make-tokens-status %)))}
[:fn tokens-status?]])
(def ^:private check-tokens-status-attrs
(sm/check-fn schema:tokens-status-attrs
:hint "expected valid params for tokens-status"))
(def check-tokens-status
(sm/check-fn schema:tokens-status
:hint "expected valid tokens-status"))
(defn make-tokens-status
[& {:as attrs}]
(-> attrs
(update :active-theme-ids #(or % #{}))
(update :active-set-ids #(or % #{}))
(check-tokens-status-attrs)
(map->TokensStatus)))
;; === Pretty-print for debugging ===
(defmethod pp/simple-dispatch TokensStatus [^TokensStatus obj]
(.write *out* "#penpot/tokens-status ")
(pp/pprint-newline :miser)
(pp/pprint (datafy obj)))
#?(:clj
(do
(defmethod print-method TokensStatus
[^TokensStatus this ^java.io.Writer w]
(.write w "#penpot/tokens-status ")
(print-method (datafy this) w))
(defmethod print-dup TokensStatus
[^TokensStatus this ^java.io.Writer w]
(print-method this w)))
:cljs
(extend-type TokensStatus
cljs.core/IPrintWithWriter
(-pr-writer [this writer opts]
(-write writer "#penpot/tokens-status ")
(-pr-writer (datafy this) writer opts))
cljs.core/IEncodeJS
(-clj->js [this]
(clj->js (datafy this)))))
;; === Transit serialization ===
(t/add-handlers!
{:id "penpot/tokens-status"
:class TokensStatus
:wfn datafy
:rfn #(make-tokens-status %)})
;; === Fressian serialization ===
#?(:clj
(fres/add-handlers!
{:name "penpot/tokens-status/v1"
:class TokensStatus
:wfn (fn [n w o]
(fres/write-tag! w n 1)
(fres/write-object! w (datafy o)))
:rfn (fn [r]
(let [obj (fres/read-object! r)]
(make-tokens-status obj)))}))

View File

@ -7,6 +7,12 @@
(ns common-tests.files.tokens-test
(:require
[app.common.files.tokens :as cfo]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tht]
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.test :as t]))
(t/deftest test-parse-token-value
@ -92,3 +98,343 @@
(t/is (nil? (cfo/shapes-token-applied? {:name "a"} [{:applied-tokens {:x "a"}}
{:applied-tokens {:x "a"}}]
#{:y})))))
;; Tokens lib
(t/deftest test-ensure-tokens-lib
(t/testing "ensure-tokens-lib should add a tokens-lib and tokens-status to the file data if they are missing, and should not modify them if they already exist"
(let [file (thf/sample-file :file1)
file-data (ctf/file-data file)
file-data' (cfo/ensure-tokens-lib file-data)]
(t/is (contains? file-data' :tokens-lib))
(t/is (ctob/tokens-lib? (:tokens-lib file-data')))
(t/is (contains? file-data' :tokens-status))
(t/is (ctos/tokens-status? (:tokens-status file-data'))))))
(t/deftest test-update-tokens-lib
(t/testing "update when there is no tokens-lib has no effect"
(let [file (thf/sample-file :file1)
file' (cfo/update-tokens-lib file #(t/is false "This should not be called"))]
(t/is (= file file'))))
(t/testing "update a tokens-lib applies the changes correctly"
(let [file (-> (thf/sample-file :file1)
(tht/add-tokens-lib))
file' (ctf/update-file-data file
#(cfo/update-tokens-lib
%
ctob/add-theme
(ctob/make-token-theme :id (thi/new-id! :theme1)
:name "theme 1")))
tokens-lib' (tht/get-tokens-lib file')]
(t/is (= 2 (ctob/theme-count tokens-lib'))) ;; Count the hidden theme
(t/is (ctob/token-theme? (ctob/get-theme tokens-lib' (thi/id :theme1)))))))
(t/deftest test-update-tokens-status
(t/testing "update when there is no tokens-status has no effect"
(let [file (thf/sample-file :file1)
file' (cfo/update-tokens-status file #(t/is false "This should not be called"))]
(t/is (= file file'))))
(t/testing "update a tokens-status applies the changes correctly"
(let [file (-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib
(fn [tokens-lib]
(-> tokens-lib
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme1) :name "theme"))))))
file' (ctf/update-file-data file
#(cfo/update-tokens-status
%
ctos/set-tokens-status
#{(thi/id :theme1)}
#{}))
tokens-status' (tht/get-tokens-status file')]
(t/is (= 1 (count (ctos/get-active-theme-ids tokens-status'))))
(t/is (ctos/theme-active? tokens-status' (thi/id :theme1)))
(t/is (= 0 (count (ctos/get-active-set-ids tokens-status')))))))
;; Tokens status with tokens lib
;; This is a private function but it deserves specific tests for all use cases
(def calculate-active-sets #'app.common.files.tokens/calculate-active-sets)
(t/deftest test-calculate-active-sets
(t/testing "returns union of sets from all active themes"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-c) :name "set-c"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a" "set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:group ""
:sets #{"set-c"})))]
(t/is (= #{(thi/id :set-a) (thi/id :set-b) (thi/id :set-c)}
(calculate-active-sets #{(thi/id :theme-1) (thi/id :theme-2)} tokens-lib)))))
(t/testing "deduplicates sets shared by multiple active themes"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a" "set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:group ""
:sets #{"set-b"})))]
(t/is (= #{(thi/id :set-a) (thi/id :set-b)}
(calculate-active-sets #{(thi/id :theme-1) (thi/id :theme-2)} tokens-lib)))))
(t/testing "returns empty set when active-theme-ids is empty"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"})))]
(t/is (= #{}
(calculate-active-sets #{} tokens-lib)))))
(t/testing "ignores active themes that do not exist in the lib"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"})))]
(t/is (= #{(thi/id :set-a)}
(calculate-active-sets #{(thi/id :theme-1) (thi/new-id! :nonexistent)} tokens-lib)))))
(t/testing "ignores set names that do not exist in the lib"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a" "nonexistent-set"})))]
(t/is (contains? (calculate-active-sets #{(thi/id :theme-1)} tokens-lib) (thi/id :set-a)))
(t/is (= 2 (count (calculate-active-sets #{(thi/id :theme-1)} tokens-lib))))))
(t/testing "returns empty set when themes have no sets"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{})))]
(t/is (= #{}
(calculate-active-sets #{(thi/id :theme-1)} tokens-lib)))))
(t/testing "returns empty set when there are no themes"
(let [tokens-lib (-> (ctob/make-tokens-lib))]
(t/is (= #{}
(calculate-active-sets #{} tokens-lib))))))
(t/deftest test-get-active-themes
(t/testing "returns only themes that are active in the status"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group ""))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2) :name "theme-2" :group ""))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-3) :name "theme-3" :group "")))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/id :theme-3)}
:active-set-ids #{})]
(t/is (= 2 (count (cfo/get-active-themes tokens-status tokens-lib))))
(let [active-ids (into #{} (map ctob/get-id) (cfo/get-active-themes tokens-status tokens-lib))]
(t/is (contains? active-ids (thi/id :theme-1)))
(t/is (contains? active-ids (thi/id :theme-3)))
(t/is (not (contains? active-ids (thi/id :theme-2)))))))
(t/testing "returns empty sequence when no themes are active"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group "")))
tokens-status (ctos/make-tokens-status :active-theme-ids #{}
:active-set-ids #{})]
(t/is (empty? (cfo/get-active-themes tokens-status tokens-lib)))))
(t/testing "returns empty sequence when tokens-lib has no themes"
(let [tokens-lib (ctob/make-tokens-lib)
tokens-status (ctos/make-tokens-status)]
(t/is (empty? (cfo/get-active-themes tokens-status tokens-lib)))))
(t/testing "active theme ids in status that do not exist in lib are silently ignored"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1) :name "theme-1" :group "")))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/new-id! :nonexistent)}
:active-set-ids #{})]
(t/is (= 1 (count (cfo/get-active-themes tokens-status tokens-lib)))))))
(t/deftest test-activate-theme
(t/testing "activating a theme sets it as active"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status)
tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-1))]
(t/is (ctos/theme-active? tokens-status' (thi/id :theme-1)))))
(t/testing "activating a theme deactivates other themes in the same group"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group "colors"
:sets #{"set-a"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:group "colors"
:sets #{"set-b"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)}
:active-set-ids #{(thi/id :set-a)})
tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-2))]
(t/is (ctos/theme-active? tokens-status' (thi/id :theme-2)))
(t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1))))))
(t/testing "themes in different groups can be active simultaneously"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group "colors"
:sets #{"set-a"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:group "spacing"
:sets #{"set-b"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)}
:active-set-ids #{(thi/id :set-a)})
tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-2))]
(t/is (ctos/theme-active? tokens-status' (thi/id :theme-1)))
(t/is (ctos/theme-active? tokens-status' (thi/id :theme-2)))))
(t/testing "activating an already-active theme returns status unchanged"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)}
:active-set-ids #{(thi/id :set-a)})
tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/id :theme-1))]
(t/is (identical? tokens-status tokens-status'))))
(t/testing "activating a non-existent theme returns status unchanged"
(let [tokens-lib (ctob/make-tokens-lib)
tokens-status (ctos/make-tokens-status)
tokens-status' (cfo/activate-theme tokens-status tokens-lib (thi/new-id! :nonexistent))]
(t/is (identical? tokens-status tokens-status')))))
(t/deftest test-deactivate-theme
(t/testing "deactivating an active theme removes it from active themes"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1)}
:active-set-ids #{(thi/id :set-a)})
tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))]
(t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1))))))
(t/testing "deactivating a theme removes sets not used by remaining active themes"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a) :name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b) :name "set-b"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group ""
:sets #{"set-a"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:group ""
:sets #{"set-b"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/id :theme-1) (thi/id :theme-2)}
:active-set-ids #{(thi/id :set-a) (thi/id :set-b)})
tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))]
(t/is (not (ctos/theme-active? tokens-status' (thi/id :theme-1))))
(t/is (ctos/theme-active? tokens-status' (thi/id :theme-2)))))
(t/testing "deactivating an already-inactive theme returns status unchanged"
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:group "")))
tokens-status (ctos/make-tokens-status)
tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/id :theme-1))]
(t/is (identical? tokens-status tokens-status'))))
(t/testing "deactivating a non-existent theme returns status unchanged"
(let [tokens-lib (ctob/make-tokens-lib)
tokens-status (ctos/make-tokens-status)
tokens-status' (cfo/deactivate-theme tokens-status tokens-lib (thi/new-id! :nonexistent))]
(t/is (identical? tokens-status tokens-status')))))
(t/deftest test-sync-tokens-status-with-lib
(t/testing "removes theme ids that no longer exist in the lib"
(let [theme-1-id (thi/new-id! :theme-1)
set-a-id (thi/new-id! :set-a)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id theme-1-id
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id (thi/new-id! :removed-theme)}
:active-set-ids #{set-a-id})
tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(t/is (= #{theme-1-id} (ctos/get-active-theme-ids tokens-status')))
(t/is (ctos/theme-active? tokens-status' theme-1-id))))
(t/testing "removes set ids that no longer exist in the lib"
(let [theme-1-id (thi/new-id! :theme-1)
set-a-id (thi/new-id! :set-a)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id theme-1-id
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id}
:active-set-ids #{set-a-id (thi/new-id! :removed-set)})
tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(t/is (= #{set-a-id} (ctos/get-active-set-ids tokens-status')))))
(t/testing "returns same status object when everything is valid"
(let [theme-1-id (thi/new-id! :theme-1)
set-a-id (thi/new-id! :set-a)
tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id set-a-id :name "set-a"))
(ctob/add-theme (ctob/make-token-theme :id theme-1-id
:name "theme-1"
:group ""
:sets #{"set-a"})))
tokens-status (ctos/make-tokens-status :active-theme-ids #{theme-1-id}
:active-set-ids #{set-a-id})
tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(t/is (identical? tokens-status tokens-status'))))
(t/testing "returns same status object when both theme and set ids are empty"
(let [tokens-lib (ctob/make-tokens-lib)
tokens-status (ctos/make-tokens-status)
tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(t/is (identical? tokens-status tokens-status'))))
(t/testing "removes all themes and sets when lib is empty"
(let [tokens-lib (ctob/make-tokens-lib)
tokens-status (ctos/make-tokens-status :active-theme-ids #{(thi/new-id! :old-theme)}
:active-set-ids #{(thi/new-id! :old-set)})
tokens-status' (cfo/sync-tokens-status-with-lib tokens-status tokens-lib)]
(t/is (= #{} (ctos/get-active-theme-ids tokens-status')))
(t/is (= #{} (ctos/get-active-set-ids tokens-status'))))))

View File

@ -12,24 +12,20 @@
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tht]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(defn- setup-file [lib-fn]
(-> (thf/sample-file :file1)
(tht/add-tokens-lib)
(tht/update-tokens-lib lib-fn)))
(t/deftest generate-toggle-token-set-test
(t/testing "toggling an active set will switch to hidden theme without user sets"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar"))
@ -45,11 +41,12 @@
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling an inactive set will switch to hidden theme without user sets"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar"}))
(ctob/set-active-themes #{"/theme"})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set (tht/get-tokens-lib file) "foo/bar"))
@ -65,10 +62,11 @@
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling an set with hidden theme already active will toggle set in hidden theme"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-hidden-theme))
(ctob/set-active-themes #{ctob/hidden-theme-path})))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-theme (ctob/make-hidden-theme))
(ctob/set-active-themes #{ctob/hidden-theme-path})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
@ -85,10 +83,11 @@
(t/deftest set-token-theme-test
(t/testing "delete token theme"
(let [theme-id (uuid/next)
file (setup-file #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "foo"
:group "main"))))
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "foo"
:group "main"))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme theme-id nil))
@ -107,7 +106,7 @@
theme (ctob/make-token-theme :id theme-id
:name "foo"
:group "main")
file (setup-file identity)
file (tht/sample-file-with-tokens)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-theme theme-id theme))
@ -126,7 +125,8 @@
prev-theme (ctob/make-token-theme :id theme-id
:name prev-theme-name
:group "main")
file (setup-file #(ctob/add-theme % prev-theme))
file (tht/sample-file-with-tokens
:lib-fn #(ctob/add-theme % prev-theme))
new-theme-name "foo1"
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
@ -149,9 +149,10 @@
:group "main")
set-name "bar-set"
token-set (ctob/make-token-set :name set-name)
file (setup-file #(-> %
(ctob/add-theme theme)
(ctob/add-set token-set)))
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme theme)
(ctob/add-set token-set)))
theme' (assoc theme :sets #{set-name})
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
@ -169,13 +170,14 @@
(let [set-name "foo"
set-id (uuid/next)
token-id (uuid/next)
file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name set-name))
(ctob/add-token set-id (ctob/make-token {:name "to.delete.color.red"
:id token-id
:value "red"
:type :color}))))
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name set-name))
(ctob/add-token set-id (ctob/make-token {:name "to.delete.color.red"
:id token-id
:value "red"
:type :color}))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-id token-id nil))
@ -194,8 +196,9 @@
token (ctob/make-token {:name "to.add.color.red"
:value "red"
:type :color})
file (setup-file #(-> % (ctob/add-set (ctob/make-token-set :id set-id
:name set-name))))
file (tht/sample-file-with-tokens
:lib-fn #(-> % (ctob/add-set (ctob/make-token-set :id set-id
:name set-name))))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-id (:id token) token))
@ -217,10 +220,11 @@
token (-> prev-token
(assoc :name "color.red.changed")
(assoc :value "blue"))
file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name set-name))
(ctob/add-token set-id prev-token)))
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name set-name))
(ctob/add-token set-id prev-token)))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token set-id (:id prev-token) token))
@ -237,7 +241,8 @@
(t/testing "delete token set"
(let [set-name "foo"
set-id (uuid/next)
file (setup-file #(ctob/add-set % (ctob/make-token-set :id set-id :name set-name)))
file (tht/sample-file-with-tokens
:lib-fn #(ctob/add-set % (ctob/make-token-set :id set-id :name set-name)))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-id nil))
@ -254,7 +259,7 @@
(let [set-name "foo"
set-id (uuid/next)
token-set (ctob/make-token-set :id set-id :name set-name)
file (setup-file identity)
file (tht/sample-file-with-tokens)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(pcb/set-token-set set-id token-set))
@ -271,7 +276,8 @@
(let [set-name "foo"
set-id (uuid/next)
token-set (ctob/make-token-set :id set-id :name set-name)
file (setup-file #(-> (ctob/add-set % token-set)))
file (tht/sample-file-with-tokens
:lib-fn #(-> (ctob/add-set % token-set)))
new-set-name "foo1"
changes (-> (pcb/empty-changes)
@ -292,12 +298,13 @@
(t/deftest generate-toggle-token-set-group-test
(t/testing "toggling set group with no active sets inside will activate all child sets"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child"))
(ctob/add-theme (ctob/make-token-theme :name "theme"))
(ctob/set-active-themes #{"/theme"})))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child"))
(ctob/add-theme (ctob/make-token-theme :name "theme"))
(ctob/set-active-themes #{"/theme"})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-token-set-group (tht/get-tokens-lib file) ["foo" "bar"]))
@ -313,13 +320,14 @@
(t/is (= #{"/theme"} (ctob/get-active-theme-paths undo-lib)))))
(t/testing "toggling set group with partially active sets inside will deactivate all child sets"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar/baz"}))
(ctob/set-active-themes #{"/theme"})))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz/baz-child"))
(ctob/add-theme (ctob/make-token-theme :name "theme"
:sets #{"foo/bar/baz"}))
(ctob/set-active-themes #{"/theme"})))
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
@ -337,9 +345,10 @@
(t/deftest generate-move-token-set-test
(t/testing "Ignore dropping set to the same position:"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar/baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar/baz"))))
drop (partial clt/generate-move-token-set (pcb/empty-changes) (tht/get-tokens-lib file))]
(t/testing "on top of identical"
(t/is (= (pcb/empty-changes)
@ -359,10 +368,11 @@
(t/testing "Reorder sets when dropping next to a set:"
(t/testing "at top"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))
(ctob/add-set (ctob/make-token-set :name "baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))
(ctob/add-set (ctob/make-token-set :name "baz"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1
:to-index 0
@ -378,10 +388,11 @@
(t/is (= (ctob/get-set-names lib) undo-sets)))))
(t/testing "at bottom"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))
(ctob/add-set (ctob/make-token-set :name "baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))
(ctob/add-set (ctob/make-token-set :name "baz"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0
:to-index 2
@ -397,9 +408,10 @@
(t/is (= (ctob/get-set-names lib) undo-sets)))))
(t/testing "dropping out of set group"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 1
:to-index 0
@ -415,9 +427,10 @@
(t/is (= (ctob/get-set-names lib) undo-sets)))))
(t/testing "into set group"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 2
:to-index 1
@ -434,9 +447,10 @@
(t/testing "edge-cases:"
(t/testing "prevent overriding set to identical path"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "foo"))))
lib (tht/get-tokens-lib file)]
(t/is (thrown?
#?(:cljs js/Error :clj Exception)
@ -452,9 +466,10 @@
#"move token set error: path exists"))))
(t/testing "dropping below collapsed group doesnt add as child"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "foo/bar"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "foo/bar"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set (pcb/empty-changes) lib {:from-index 0
:to-index 1
@ -472,9 +487,10 @@
(t/deftest generate-move-token-group-test
(t/testing "Ignore dropping set group to the same position"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar/baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo"))
(ctob/add-set (ctob/make-token-set :name "bar/baz"))))
drop (partial clt/generate-move-token-set-group (pcb/empty-changes) (tht/get-tokens-lib file))]
(t/testing "on top of identical"
(t/is (= (pcb/empty-changes)
@ -494,10 +510,11 @@
(t/testing "Move set groups"
(t/testing "to top"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar/bar"))
(ctob/add-set (ctob/make-token-set :name "baz/baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar/bar"))
(ctob/add-set (ctob/make-token-set :name "baz/baz"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 2
:to-index 0
@ -514,9 +531,10 @@
(t/is (= (ctob/get-set-names lib) undo-sets)))))
(t/testing "to bottom"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0
:to-index 2
@ -533,9 +551,10 @@
(t/is (= (ctob/get-set-names lib) undo-sets)))))
(t/testing "into set group"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar/bar"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/foo"))
(ctob/add-set (ctob/make-token-set :name "bar/bar"))))
lib (tht/get-tokens-lib file)
changes (clt/generate-move-token-set-group (pcb/empty-changes) lib {:from-index 0
:to-index 2
@ -552,9 +571,10 @@
(t/testing "edge-cases:"
(t/testing "prevent overriding set to identical path"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/identical/foo"))
(ctob/add-set (ctob/make-token-set :name "identical/bar"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/identical/foo"))
(ctob/add-set (ctob/make-token-set :name "identical/bar"))))
lib (tht/get-tokens-lib file)]
(t/is (thrown?
#?(:cljs js/Error :clj Exception)
@ -564,8 +584,9 @@
#"move token set error: path exists"))))
(t/testing "prevent dropping parent to child"
(let [file (setup-file #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))))
(let [file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "foo/bar/baz"))))
lib (tht/get-tokens-lib file)]
(t/is (thrown?
#?(:cljs js/Error :clj Exception)
@ -573,3 +594,211 @@
:to-index 1
:position :bot})
#"move token set error: parent-to-child"))))))))
(t/deftest generate-activate-theme-test
(t/testing "activating an inactive theme creates changes"
(let [set-id (uuid/next)
theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id
:name "s1"))
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{"s1"}))))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-activate-theme tokens-status tokens-lib theme-id))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')
file'' (thf/apply-undo-changes file' changes)
tokens-status'' (tht/get-tokens-status file'')]
;; Redo: theme and set are active
(t/is (ctos/theme-active? tokens-status' theme-id))
(t/is (ctos/set-active? tokens-status' set-id))
;; Undo: theme and set are inactive
(t/is (not (ctos/theme-active? tokens-status'' theme-id)))
(t/is (not (ctos/set-active? tokens-status'' set-id)))))
(t/testing "activating an already-active theme returns same changes"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{"s1"})))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-activate-theme tokens-status tokens-lib theme-id))]
(t/is (= [] (:redo-changes changes)))))
(t/testing "activating a non-existent theme returns same changes"
(let [non-existent-id (uuid/next)
file (tht/sample-file-with-tokens)
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-activate-theme tokens-status tokens-lib non-existent-id))]
(t/is (= [] (:redo-changes changes))))))
(t/deftest generate-deactivate-theme-test
(t/testing "deactivating an active theme creates changes"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{})))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-deactivate-theme tokens-status tokens-lib theme-id))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')
file'' (thf/apply-undo-changes file' changes)
tokens-status'' (tht/get-tokens-status file'')]
;; Redo: theme is inactive
(t/is (not (ctos/theme-active? tokens-status' theme-id)))
;; Undo: theme is active again
(t/is (ctos/theme-active? tokens-status'' theme-id))))
(t/testing "deactivating an inactive theme returns same changes"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :name "s1"))
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{"s1"}))))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-deactivate-theme tokens-status tokens-lib theme-id))]
(t/is (= [] (:redo-changes changes))))))
(t/deftest generate-set-theme-status-test
(t/testing "setting active? to true activates the theme"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{}))))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-set-theme-status tokens-status tokens-lib theme-id true))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')]
(t/is (ctos/theme-active? tokens-status' theme-id))))
(t/testing "setting active? to false deactivates the theme"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{})))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-set-theme-status tokens-status tokens-lib theme-id false))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')]
(t/is (not (ctos/theme-active? tokens-status' theme-id))))))
(t/deftest generate-toggle-theme-test
(t/testing "toggling an active theme deactivates it"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{})))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-theme tokens-status tokens-lib theme-id))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')
file'' (thf/apply-undo-changes file' changes)
tokens-status'' (tht/get-tokens-status file'')]
;; Redo: theme is inactive
(t/is (not (ctos/theme-active? tokens-status' theme-id)))
;; Undo: theme is active again
(t/is (ctos/theme-active? tokens-status'' theme-id))))
(t/testing "toggling an inactive theme activates it"
(let [theme-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{}))))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-toggle-theme tokens-status tokens-lib theme-id))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')
file'' (thf/apply-undo-changes file' changes)
tokens-status'' (tht/get-tokens-status file'')]
;; Redo: theme is active
(t/is (ctos/theme-active? tokens-status' theme-id))
;; Undo: theme is inactive
(t/is (not (ctos/theme-active? tokens-status'' theme-id))))))
(t/deftest generate-sync-tokens-status-with-lib-test
(t/testing "sync removes stale theme and set ids from status"
(let [stale-theme-id (uuid/next)
stale-set-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn identity
:status-fn #(ctos/set-tokens-status % #{stale-theme-id} #{stale-set-id}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib))
file' (thf/apply-changes file changes)
tokens-status' (tht/get-tokens-status file')
file'' (thf/apply-undo-changes file' changes)
tokens-status'' (tht/get-tokens-status file'')]
;; Redo: stale ids are removed
(t/is (= #{} (ctos/get-active-theme-ids tokens-status')))
(t/is (= #{} (ctos/get-active-set-ids tokens-status')))
;; Undo: stale ids are restored
(t/is (= #{stale-theme-id} (ctos/get-active-theme-ids tokens-status'')))
(t/is (= #{stale-set-id} (ctos/get-active-set-ids tokens-status'')))))
(t/testing "sync with status already in sync returns same changes"
(let [theme-id (uuid/next)
set-id (uuid/next)
file (tht/sample-file-with-tokens
:lib-fn #(-> %
(ctob/add-set (ctob/make-token-set :id set-id :name "s1"))
(ctob/add-theme (ctob/make-token-theme :id theme-id
:name "t1"
:sets #{"s1"})))
:status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id}))
tokens-status (tht/get-tokens-status file)
tokens-lib (tht/get-tokens-lib file)
changes (-> (pcb/empty-changes)
(pcb/with-library-data (:data file))
(clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib))]
(t/is (= [] (:redo-changes changes))))))

View File

@ -247,6 +247,16 @@
theme' (ctob/toggle-set theme nil)]
(t/is (= (:sets theme') #{})))))
(t/deftest get-theme-path
(let [theme1 (ctob/make-token-theme :name "theme1")
theme2 (ctob/make-token-theme :group "group1" :name "theme2")]
(t/is (= "/theme1" (ctob/get-theme-path theme1)))
(t/is (= "/theme1" (ctob/get-theme-path theme1 false)))
(t/is (= "theme1" (ctob/get-theme-path theme1 true)))
(t/is (= "group1/theme2" (ctob/get-theme-path theme2)))
(t/is (= "group1/theme2" (ctob/get-theme-path theme2 false)))
(t/is (= "group1 / theme2" (ctob/get-theme-path theme2 true)))))
(t/deftest make-tokens-lib
(let [tokens-lib (ctob/make-tokens-lib)]
(t/is (= (ctob/set-count tokens-lib) 0))))

View File

@ -7,9 +7,10 @@
(ns common-tests.types.tokens-migrations-test
(:require
[app.common.data :as d]
[app.common.files.tokens :as cfo]
[app.common.test-helpers.ids-map :as thi]
[app.common.time :as ct]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
@ -79,4 +80,34 @@
(t/is (= (ctob/get-description theme1') (ctob/get-description theme1)))
(t/is (= (ctob/get-id theme2') (ctob/get-id theme2)))
(t/is (= (ctob/get-name theme2') (ctob/get-name theme2)))
(t/is (= (ctob/get-description theme2') (ctob/get-description theme2))))))
(t/is (= (ctob/get-description theme2') (ctob/get-description theme2))))))
(t/deftest make-tokens-status-from-tokens-lib
(let [tokens-lib (-> (ctob/make-tokens-lib)
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-a)
:name "set-a"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-b)
:name "set-b"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-c)
:name "set-c"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-d)
:name "set-d"))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-1)
:name "theme-1"
:sets #{"set-a" "set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-2)
:name "theme-2"
:sets #{"set-b"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-3)
:name "theme-3"
:sets #{"set-c" "set-d"}))
(ctob/set-active-themes #{"/theme-1" "/theme-2"}))
tokens-status (cfo/make-tokens-status-from-lib tokens-lib)]
(t/is (ctos/tokens-status? tokens-status))
(t/is (ctos/check-tokens-status tokens-status))
(t/is (= (count (ctos/get-active-theme-ids tokens-status)) 2))
(t/is (ctos/theme-active? tokens-status (thi/id :theme-1)))
(t/is (ctos/theme-active? tokens-status (thi/id :theme-2)))
(t/is (= 2 (count (ctos/get-active-set-ids tokens-status))))
(t/is (ctos/set-active? tokens-status (thi/id :set-a)))
(t/is (ctos/set-active? tokens-status (thi/id :set-b)))))

View 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 Sucursal en España SL
(ns common-tests.types.tokens-status-test
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
[app.common.transit :as tr]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[clojure.datafy :refer [datafy]]
[clojure.test :as t]))
(t/deftest make-tokens-status
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-tokens-status :active-theme-ids #{theme-id}
:active-set-ids #{set-id})]
(t/is (ctos/tokens-status? status))
(t/is (ctos/check-tokens-status status))
(t/is (= 1 (count (ctos/get-active-theme-ids status))))
(t/is (ctos/theme-active? status theme-id))
(t/is (= 1 (count (ctos/get-active-set-ids status))))
(t/is (ctos/set-active? status set-id))))
(t/deftest make-tokens-status-defaults
(let [status (ctos/make-tokens-status)]
(t/is (ctos/tokens-status? status))
(t/is (ctos/check-tokens-status status))
(t/is (= 0 (count (ctos/get-active-theme-ids status))))
(t/is (= 0 (count (ctos/get-active-set-ids status))))))
(t/deftest make-invalid-tokens-status
(t/testing "non-set for active-themes"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception)
#"expected valid params for tokens-status"
(ctos/make-tokens-status :active-theme-ids []))))
(t/testing "non-uuid in active-sets"
(t/is (thrown-with-msg? #?(:cljs js/Error :clj Exception)
#"expected valid params for tokens-status"
(ctos/make-tokens-status :active-set-ids #{"not-a-uuid"})))))
(t/deftest set-tokens-status
(let [theme1-id (uuid/next)
theme2-id (uuid/next)
theme3-id (uuid/next)
set1-id (uuid/next)
set2-id (uuid/next)
set3-id (uuid/next)
status (-> (ctos/make-tokens-status {:active-theme-ids #{theme3-id}
:active-set-ids #{set3-id}})
(ctos/set-tokens-status #{theme1-id theme2-id} #{set1-id set2-id}))]
(t/is (= #{theme1-id theme2-id} (ctos/get-active-theme-ids status)))
(t/is (= #{set1-id set2-id} (ctos/get-active-set-ids status)))))
(t/deftest datafy-tokens-status
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-tokens-status :active-theme-ids #{theme-id}
:active-set-ids #{set-id})
result (datafy status)]
(t/is (map? result))
(t/is (not (ctos/tokens-status? result)))
(t/is (= (:active-theme-ids result) #{theme-id}))
(t/is (= (:active-set-ids result) #{set-id}))))
(t/deftest transit-serialization
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-tokens-status :active-theme-ids #{theme-id}
:active-set-ids #{set-id})
encoded (tr/encode-str status)
status' (tr/decode-str encoded)]
(t/is (ctos/tokens-status? status'))
(t/is (= (datafy status') (datafy status)))))
#?(:clj
(t/deftest fressian-serialization
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-tokens-status :active-theme-ids #{theme-id}
:active-set-ids #{set-id})
encoded (fres/encode status)
status' (fres/decode encoded)]
(t/is (ctos/tokens-status? status'))
(t/is (= (datafy status') (datafy status))))))
#?(:clj
(t/deftest json-serialization
(let [theme-id (uuid/next)
set-id (uuid/next)
status (ctos/make-tokens-status :active-theme-ids #{theme-id}
:active-set-ids #{set-id})
json-str (json/write-str status)
parsed (json/read-str json-str :key-fn keyword)]
(t/is (map? parsed))
(t/is (= [(str theme-id)] (:active-theme-ids parsed)))
(t/is (= [(str set-id)] (:active-set-ids parsed))))))

View File

@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.files.tokens :as cfo]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
@ -37,12 +38,21 @@
([state file-id]
(dm/get-in state [:files file-id :data])))
(defn lookup-tokens-lib
(defn lookup-tokens-file-data
[state]
(let [current-file-data (lookup-file-data state)
tokens-file-id (or (:tokens-file current-file-data) (:id current-file-data))
tokens-file-data (lookup-file-data state tokens-file-id)]
(:tokens-lib tokens-file-data)))
tokens-file-id (or (cfo/get-tokens-file current-file-data) (:id current-file-data))]
(lookup-file-data state tokens-file-id)))
(defn lookup-tokens-lib
[state]
(let [tokens-file-data (lookup-tokens-file-data state)]
(cfo/get-tokens-lib tokens-file-data)))
(defn lookup-tokens-status
[state]
(let [current-file-data (lookup-file-data state)] ;; Tokens status is always in the current file
(cfo/get-tokens-status current-file-data)))
(defn get-page
[fdata page-id]

View File

@ -12,10 +12,12 @@
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.files.shapes-helpers :as cfsh]
[app.common.files.tokens :as cfo]
[app.common.geom.point :as gpt]
[app.common.logging :as log]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.logic.tokens :as clo]
[app.common.logic.variants :as clv]
[app.common.path-names :as cpn]
[app.common.time :as ct]
@ -1483,6 +1485,47 @@
(rx/take-until stopper-s)))))))
(defn sync-tokens-status-with-lib
[]
(ptk/reify ::sync-tokens-status-with-lib
ptk/WatchEvent
(watch [_ state _]
(let [tokens-lib (dsh/lookup-tokens-lib state)
tokens-status (dsh/lookup-tokens-status state)]
(when (and tokens-lib tokens-status)
(let [data (dsh/lookup-file-data state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clo/generate-sync-tokens-status-with-lib tokens-status tokens-lib))]
(rx/of (dch/commit-changes changes))))))))
(defn watch-token-changes
"Watch the state for changes that affect the tokens library. If a change is detected,
launches a sync-tokens-status event so the tokens-status is kept in sync with the library."
[]
(ptk/reify ::watch-token-changes
ptk/WatchEvent
(watch [_ _ stream]
(let [stopper-s
(->> stream
(rx/map ptk/type)
(rx/filter (fn [event-type]
(or (= ::dwpg/finalize-page event-type)
(= ::watch-token-changes event-type)))))
changes-s
(->> stream
(rx/filter dch/commit?)
(rx/map deref)
(rx/filter #(= :local (:source %)))
(rx/observe-on :async))]
(->> changes-s
(rx/filter (comp ch/tokens-lib-changed? :changes))
(rx/debounce 5000)
(rx/map (fn [_] (sync-tokens-status-with-lib)))
(rx/take-until stopper-s))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Backend interactions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1528,6 +1571,21 @@
(map #(assoc % :library-of file-id))
(d/index-by :id))))))
(defn- initialize-tokens-status
[library-id]
(ptk/reify ::initialize-tokens-status
ptk/WatchEvent
(watch [it state _]
(let [library (dm/get-in state [:files library-id])
library-data (ctf/file-data library)]
(when (some? (cfo/get-tokens-lib library-data))
(when-let [tokens-status (cfo/get-tokens-status library-data)]
(let [changes (-> (pcb/empty-changes it)
(pcb/with-library-data library-data)
(pcb/set-tokens-status tokens-status))]
(rx/of
(dch/commit-changes changes)))))))))
(defn- load-library-file
[file-id library-id]
(ptk/reify ::load-library-file
@ -1537,8 +1595,10 @@
(rx/merge
(->> (rp/cmd! :get-file {:id library-id :features features})
(rx/merge-map fpmap/resolve-file)
(rx/map (fn [file]
(libraries-fetched file-id [file]))))
(rx/mapcat (fn [file]
(rx/of
(libraries-fetched file-id [file])
(initialize-tokens-status library-id)))))
(->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"})
(rx/map (fn [thumbnails]
(fn [state]

View File

@ -114,6 +114,7 @@
(rx/of (dwth/watch-state-changes file-id page-id)))
(rx/of (dwl/watch-component-changes))
(rx/of (dwl/watch-token-changes))
(let [profile (:profile state)
props (get profile :props)]

View File

@ -267,11 +267,12 @@
(ptk/reify ::set-token-theme-active
ptk/WatchEvent
(watch [_ state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-set-active-token-theme tokens-lib id active?))]
(let [data (dsh/lookup-file-data state)
tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes)
(pcb/with-library-data data)
(clt/generate-set-theme-status tokens-status tokens-lib id active?))]
(rx/of (dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))
@ -281,11 +282,12 @@
(ptk/reify ::toggle-token-theme-active
ptk/WatchEvent
(watch [it state _]
(let [data (dsh/lookup-file-data state)
tokens-lib (get-tokens-lib state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(clt/generate-toggle-token-theme tokens-lib id))]
(let [data (dsh/lookup-tokens-file-data state)
tokens-status (dsh/lookup-tokens-status state)
tokens-lib (dsh/lookup-tokens-lib state)
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(clt/generate-toggle-theme tokens-status tokens-lib id))]
(rx/of
(dch/commit-changes changes)
(dwtp/propagate-workspace-tokens))))))

View File

@ -460,6 +460,9 @@
(def tokens-lib
(l/derived dsh/lookup-tokens-lib st/state))
(def tokens-status
(l/derived dsh/lookup-tokens-status st/state))
(def workspace-token-theme-groups
(l/derived (d/nilf ctob/get-theme-groups) tokens-lib))

View File

@ -25,6 +25,8 @@
(def libraries (mf/create-context nil))
(def design-tokens (mf/create-context nil))
(def token-inputs (mf/create-context nil))
(def tokens-lib (mf/create-context nil))
(def tokens-status (mf/create-context nil))
(def current-scroll (mf/create-context nil))
(def current-zoom (mf/create-context nil))

View File

@ -22,7 +22,7 @@
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as muc]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
@ -175,7 +175,7 @@
(mf/with-memo []
(mf/html [:> collapse-button* {}]))]
[:> (mf/provider muc/sidebar) {:value :left}
[:> (mf/provider ctx/sidebar) {:value :left}
[:aside {:ref parent-ref
:id "left-sidebar-aside"
:data-testid "left-sidebar"
@ -316,8 +316,8 @@
(mf/with-memo [active-tokens]
(delay (ctob/group-by-type active-tokens)))]
[:> (mf/provider muc/sidebar) {:value :right}
[:> (mf/provider muc/active-tokens-by-type) {:value active-tokens-by-type}
[:> (mf/provider ctx/sidebar) {:value :right}
[:> (mf/provider ctx/active-tokens-by-type) {:value active-tokens-by-type}
[:aside
{:class (stl/css-case :right-settings-bar true
:not-expand (not can-be-expanded?)
@ -362,6 +362,9 @@
(let [tokens-lib
(mf/deref refs/tokens-lib)
tokens-status
(mf/deref refs/tokens-status)
active-tokens
(mf/with-memo [tokens-lib]
(if tokens-lib
@ -393,25 +396,26 @@
resolved-active-tokens-force-set
(sd/use-resolved-tokens* active-tokens-force-set)]
[:*
(if (:collapse-left-sidebar layout)
[:> collapsed-button*]
[:> left-sidebar* {:layout layout
[:> (mf/provider ctx/tokens-lib) {:value tokens-lib}
[:> (mf/provider ctx/tokens-status) {:value tokens-status}
(if (:collapse-left-sidebar layout)
[:> collapsed-button*]
[:> left-sidebar* {:layout layout
:file file
:page-id page-id
:tokens-lib tokens-lib
:active-tokens active-tokens-force-set
:resolved-active-tokens (if tokenscript?
tokenscript-resolved-active-tokens-force-set
resolved-active-tokens-force-set)}])
[:> right-sidebar* {:section section
:selected selected
:drawing-tool drawing-tool
:layout layout
:file file
:file-id file-id
:page-id page-id
:tokens-lib tokens-lib
:active-tokens active-tokens-force-set
:resolved-active-tokens (if tokenscript?
tokenscript-resolved-active-tokens-force-set
resolved-active-tokens-force-set)}])
[:> right-sidebar* {:section section
:selected selected
:drawing-tool drawing-tool
:layout layout
:file file
:file-id file-id
:page-id page-id
:tokens-lib tokens-lib
:active-tokens (if tokenscript?
tokenscript-resolved-active-tokens
resolved-active-tokens)}]]))
:active-tokens (if tokenscript?
tokenscript-resolved-active-tokens
resolved-active-tokens)}]]]))

View File

@ -26,21 +26,17 @@
(st/emit! (dwtl/toggle-token-set-group path)))
(mf/defc sets-list*
[{:keys [tokens-lib selected new-path edition-id]}]
[{:keys [selected new-path edition-id]}]
(let [token-sets
(let [tokens-lib
(mf/use-ctx ctx/tokens-lib)
token-sets
(some-> tokens-lib (ctob/get-set-tree))
can-edit?
(mf/use-ctx ctx/can-edit?)
token-set-active?
(mf/use-fn
(mf/deps tokens-lib)
(fn [name]
(when tokens-lib
(ctob/token-set-active? tokens-lib name))))
token-set-group-active?
(mf/use-fn
(fn [group-path]
@ -66,7 +62,6 @@
{:tokens-lib tokens-lib
:token-sets token-sets
:is-token-set-active token-set-active?
:is-token-set-group-active token-set-group-active?
:on-select on-select-token-set-click

View File

@ -10,6 +10,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.store :as st]
[app.main.ui.context :as ctx]
@ -313,7 +314,6 @@
[{:keys [is-draggable
selected
is-token-set-group-active
is-token-set-active
on-start-edition
on-reset-edition
on-edit-submit-set
@ -326,7 +326,9 @@
new-path
edition-id]}]
(let [collapsed-paths* (mf/use-state #{})
(let [tokens-status (mf/use-ctx ctx/tokens-status)
collapsed-paths* (mf/use-state #{})
collapsed-paths (deref collapsed-paths*)
collapsed?
@ -418,7 +420,7 @@
:set token-set
:label (peek path)
:is-editing (= edition-id id)
:is-active (is-token-set-active (ctob/get-name token-set))
:is-active (ctos/set-active? tokens-status id)
:is-selected (= selected id)
:is-draggable is-draggable
:is-new false
@ -440,7 +442,6 @@
selected
on-update-token-set
on-update-token-set-group
is-token-set-active
is-token-set-group-active
on-create-token-set
on-toggle-token-set
@ -453,7 +454,6 @@
edition-id]}]
(assert (fn? is-token-set-group-active) "expected a function for `is-token-set-group-active` prop")
(assert (fn? is-token-set-active) "expected a function for `is-token-set-active` prop")
(let [theme-modal? (= origin "theme-modal")
can-edit? (mf/use-ctx ctx/can-edit?)
@ -492,7 +492,6 @@
:token-sets token-sets
:selected selected
:on-select on-select
:is-token-set-active is-token-set-active
:is-token-set-group-active is-token-set-group-active
:on-toggle-set on-toggle-token-set
:on-toggle-set-group on-toggle-token-set-group

View File

@ -19,6 +19,7 @@
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.combobox :refer [combobox*]]
@ -450,12 +451,14 @@
::mf/register modal/components
::mf/register-as :tokens/themes}
[]
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)
:data-testid "token-theme-update-create-modal"}
[:> icon-button* {:class (stl/css :close-btn)
:on-click modal/hide!
:aria-label (tr "labels.close")
:variant "action"
:icon i/close}]
[:> themes-modal-body*]]])
(let [tokens-status (mf/deref refs/tokens-status)]
[:> (mf/provider ctx/tokens-status) {:value tokens-status}
[:div {:class (stl/css :modal-overlay)}
[:div {:class (stl/css :modal-dialog)
:data-testid "token-theme-update-create-modal"}
[:> icon-button* {:class (stl/css :close-btn)
:on-click modal/hide!
:aria-label (tr "labels.close")
:variant "action"
:icon i/close}]
[:> themes-modal-body*]]]]))

View File

@ -8,13 +8,16 @@
(:require-macros [app.main.style :as stl])
(:require
[app.common.data.macros :as dm]
[app.common.files.tokens :as cfo]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.ds.foundations.typography.text :refer [text*]]
[app.main.ui.hooks :as hooks]
@ -24,17 +27,16 @@
[rumext.v2 :as mf]))
(mf/defc themes-list*
[{:keys [themes active-theme-paths on-close is-grouped]}]
[{:keys [themes tokens-status on-close is-grouped]}]
(when (seq themes)
[:ul {:class (stl/css :theme-options)}
(for [[_ {:keys [id name] :as theme}] themes
:let [theme-id (ctob/get-theme-path theme)
selected? (get active-theme-paths theme-id)
:let [selected? (ctos/theme-active? tokens-status id)
select-theme (fn [e]
(dom/stop-propagation e)
(st/emit! (dwtl/toggle-token-theme-active id))
(on-close))]]
[:li {:key theme-id
[:li {:key (str "theme-" id)
:role "option"
:aria-selected selected?
:class (stl/css-case
@ -53,42 +55,52 @@
(modal/show! :tokens/themes {}))
(mf/defc theme-options*
[{:keys [active-theme-paths themes on-close]}]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
:active-theme-paths active-theme-paths
:on-close on-close
:is-grouped true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click open-tokens-theme-modal}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]])
[{:keys [tokens-lib tokens-status on-close]}]
(let [themes
(mf/with-memo [tokens-lib]
(ctob/get-theme-tree-no-hidden tokens-lib))]
[:ul {:class (stl/css :theme-options :custom-select-dropdown)
:role "listbox"}
(for [[group themes] themes]
[:li {:key group
:aria-labelledby (dm/str group "-label")
:role "group"}
(when (seq group)
[:> text* {:as "span" :typography "headline-small" :class (stl/css :group) :id (dm/str (str/kebab group) "-label") :title group} group])
[:> themes-list* {:themes themes
:tokens-status tokens-status
:on-close on-close
:is-grouped true}]])
[:li {:class (stl/css :separator)
:aria-hidden true}]
[:li {:class (stl/css-case :checked-element true
:checked-element-button true)
:role "option"
:on-click open-tokens-theme-modal}
[:> text* {:as "span" :typography "body-small"} (tr "workspace.tokens.edit-themes")]
[:> icon* {:icon-id i/arrow-right :aria-hidden true}]]]))
(mf/defc theme-selector*
[{:keys []}]
(let [;; Store
active-theme-paths (mf/deref refs/workspace-active-theme-paths-no-hidden)
active-themes-count (count active-theme-paths)
themes (mf/deref refs/workspace-token-theme-tree-no-hidden)
tokens-lib (mf/use-ctx ctx/tokens-lib)
tokens-status (mf/use-ctx ctx/tokens-status)
active-themes
(mf/with-memo [tokens-lib tokens-status]
(cfo/get-active-themes tokens-status tokens-lib))
active-themes-count
(mf/with-memo [active-themes]
(count active-themes))
can-edit? (:can-edit (deref refs/permissions))
;; Data
current-label (cond
(> active-themes-count 1) (tr "workspace.tokens.active-themes" active-themes-count)
(= active-themes-count 1) (some->> (first active-theme-paths)
(ctob/split-theme-path)
(remove empty?)
(str/join " / "))
(= active-themes-count 1) (-> (first active-themes)
(ctob/get-theme-path true))
:else (tr "workspace.tokens.no-active-theme"))
;; State
@ -140,7 +152,7 @@
[:& dropdown {:show is-open?
:on-close on-close-dropdown}
[:> theme-options* {:active-theme-paths active-theme-paths
:themes themes
[:> theme-options* {:tokens-lib tokens-lib
:tokens-status tokens-status
:on-close on-close-dropdown}]]])
container))]))

View File

@ -51,6 +51,7 @@
[frontend-tests.tokens.logic.token-actions-test]
[frontend-tests.tokens.logic.token-data-test]
[frontend-tests.tokens.logic.token-remapping-test]
[frontend-tests.tokens.logic.tokens-status-test]
[frontend-tests.tokens.style-dictionary-test]
[frontend-tests.tokens.token-errors-test]
[frontend-tests.tokens.workspace-tokens-remap-test]
@ -127,6 +128,7 @@
'frontend-tests.tokens.logic.token-remapping-test
'frontend-tests.tokens.style-dictionary-test
'frontend-tests.tokens.token-errors-test
'frontend-tests.tokens.logic.tokens-status-test
'frontend-tests.tokens.workspace-tokens-remap-test
'frontend-tests.ui.comments-clustering-test
'frontend-tests.ui.comments-position-modifier-test

View File

@ -103,21 +103,21 @@
sets (ctob/get-sets token-lib)]
(t/testing "Token lib contains one set"
(t/is (= (count sets) 1))))))))
(t/is (= (count sets) 1)))))))))
(t/deftest delete-set
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
events [(dwtl/delete-token-set (cthi/id :test-token-set))]]
(t/deftest delete-set
(t/async
done
(let [file (setup-file-with-token-lib)
store (ths/setup-store file)
events [(dwtl/delete-token-set (cthi/id :test-token-set))]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
sets' (ctob/get-sets tokens-lib')]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
tokens-lib' (toht/get-tokens-lib file')
sets' (ctob/get-sets tokens-lib')]
(t/testing "Set has been deleted"
(t/is (= (count sets') 0))))))))))
(t/testing "Set has been deleted"
(t/is (= (count sets') 0)))))))))

View File

@ -0,0 +1,118 @@
(ns frontend-tests.tokens.logic.tokens-status-test
(:require
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.tokens :as tho]
[app.common.types.tokens-lib :as ctob]
[app.common.types.tokens-status :as ctos]
[app.main.data.workspace.tokens.library-edit :as dwtl]
[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]))
(t/use-fixtures :each
{:before thp/reset-idmap!})
(defn- setup-file
"Create a file with several token themes and token sets, some active and
some inactive. Returns the file map with :tokens-lib and :tokens-status
in its :data."
[]
(-> (tho/sample-file-with-tokens
:lib-fn #(-> %
;; Add token sets
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-active)
:name "set-active"))
(ctob/add-set (ctob/make-token-set :id (thi/new-id! :set-inactive)
:name "set-inactive"))
;; Add themes
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-active)
:name "theme-active"
:group "group-1"
:sets #{"set-active"}))
(ctob/add-theme (ctob/make-token-theme :id (thi/new-id! :theme-inactive)
:name "theme-inactive"
:group "group-1"
:sets #{"set-inactive"})))
:status-fn #(-> %
(ctos/set-tokens-status #{(thi/id :theme-active)}
#{(thi/id :set-active)})))))
(defn- get-tokens-status [file]
(get-in file [:data :tokens-status]))
(t/deftest test-set-token-theme-active-deactivate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-active)
set-id (thi/id :set-active)
events [(dwtl/set-token-theme-active theme-id false)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (not (ctos/theme-active? status theme-id))
"theme should be inactive after deactivation")
(t/is (not (ctos/set-active? status set-id))
"set should be inactive when its theme is deactivated")))))))
(t/deftest test-set-token-theme-active-activate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-inactive)
set-id (thi/id :set-inactive)
events [(dwtl/set-token-theme-active theme-id true)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (ctos/theme-active? status theme-id)
"theme should be active after activation")
(t/is (ctos/set-active? status set-id)
"set should be active when its theme is activated")))))))
(t/deftest test-toggle-token-theme-active-deactivate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-active)
set-id (thi/id :set-active)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (not (ctos/theme-active? status theme-id))
"active theme should become inactive after toggle")
(t/is (not (ctos/set-active? status set-id))
"set should be inactive when its theme is toggled off")))))))
(t/deftest test-toggle-token-theme-active-activate
(t/async
done
(let [file (setup-file)
store (ths/setup-store file)
theme-id (thi/id :theme-inactive)
set-id (thi/id :set-inactive)
events [(dwtl/toggle-token-theme-active theme-id)]]
(tohs/run-store-async
store done events
(fn [new-state]
(let [file' (ths/get-file-from-state new-state)
status (get-tokens-status file')]
(t/is (ctos/theme-active? status theme-id)
"inactive theme should become active after toggle")
(t/is (ctos/set-active? status set-id)
"set should be active when its theme is toggled on")))))))