diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 77e9af51b..73d46576f 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -57,6 +57,54 @@ #?(:cljs (instance? lkm/LinkedMap o) :clj (instance? LinkedMap o))) +(defn oassoc + [o & kvs] + (apply assoc (or o (ordered-map)) kvs)) + +(defn oassoc-in + [o [k & ks] v] + (if ks + (oassoc o k (oassoc-in (get o k) ks v)) + (oassoc o k v))) + +#_(defn oupdate-in + [m ks f & args] + (let [up (fn up [m ks f args] + (let [[k & ks] ks] + (if ks + (oassoc m k (up (get m k) ks f args)) + (oassoc m k (apply f (get m k) args)))))] + (up m ks f args))) + +(declare index-of) + +#_(defn oassoc-before + "Assoc a k v pair, in the order position just before the other key" + [o k v before-k] + (if-let [index (index-of (keys o) before-k)] + (-> (ordered-map) + (into (take index o)) + (assoc k v) + (into (drop index o))) + (oassoc o k v))) + +(defn oassoc-in-before + [o [old-k & old-ks] [k & ks] v] + (if-let [index (index-of (keys o) old-k)] + (let [new-v (if ks + (oassoc-in-before (get o k) old-ks ks v) + v)] + (if (= k old-k) + (-> (ordered-map) + (into (take index o)) + (assoc k new-v) + (into (drop (inc index) o))) + (-> (ordered-map) + (into (take index o)) + (assoc k new-v) + (into (drop index o))))) + (oassoc-in o (cons k ks) v))) + (defn vec2 "Creates a optimized vector compatible type of length 2 backed internally with MapEntry impl because it has faster access method @@ -564,6 +612,7 @@ new-elems (remove p? after)))) +;; TODO: remove this (defn addm-at-index "Insert an element in an ordered map at an arbitrary index" [coll index key element] diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 4de4d3ee8..e2a12733f 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -15,6 +15,86 @@ [cuerdas.core :as str] #?(:clj [app.common.fressian :as fres]))) +;; === Groups handling + +(def schema:groupable-item + [:map {:title "Groupable item"} + [:name :string]]) + +(def valid-groupable-item? + (sm/validator schema:groupable-item)) + +(defn split-path + "Decompose a string in the form 'one.two.three' into a vector of strings, removing spaces." + [path separator] + (let [xf (comp (map str/trim) + (remove str/empty?))] + (->> (str/split path separator) + (into [] xf)))) + +(defn join-path + "Regenerate a path as a string, from a vector." + [path separator] + (str/join separator path)) + +(defn group-item + "Add a group to the item name, in the form group.name." + [item group-name separator] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (update item :name #(str group-name separator %))) + +(defn ungroup-item + "Remove the first group from the item name." + [item separator] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (update item :name #(-> % + (split-path separator) + (rest) + (join-path separator)))) + +(defn get-path + "Get the groups part of the name as a vector. E.g. group.subgroup.name -> ['group' 'subrgoup']" + [item separator] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (split-path (:name item) separator)) + +(defn get-groups-str + "Get the groups part of the name. E.g. group.subgroup.name -> group.subrgoup" + [item separator] + (-> (get-path item separator) + (butlast) + (join-path separator))) + +(defn get-final-name + "Get the final part of the name. E.g. group.subgroup.name -> name" + [item separator] + (dm/assert! + "expected groupable item" + (valid-groupable-item? item)) + (-> (:name item) + (split-path separator) + (last))) + +(defn group? + "Check if a node of the grouping tree is a group or a final item." + [item] + (d/ordered-map? item)) + +(defn get-children + "Get all children of a group of a grouping tree. Each child is + a tuple [name item], where item " + [group] + (dm/assert! + "expected group node" + (group? group)) + (seq group)) + ;; === Token (defrecord Token [name type value description modified-at]) @@ -22,10 +102,10 @@ (def schema:token [:and [:map {:title "Token"} - [:name cto/token-name-ref] ;; not necessary to have uuid + [:name cto/token-name-ref] ;; not necessary to have uuid [:type [::sm/one-of cto/token-types]] [:value :any] - [:description [:maybe :string]] ;; defrecord always have the attributes, even with nil value + [:description [:maybe :string]] ;; defrecord always have the attributes, even with nil value [:modified-at ::sm/inst]] [:fn (partial instance? Token)]]) @@ -62,42 +142,56 @@ ITokenSet (add-token [_ token] (dm/assert! "expected valid token" (check-token! token)) - (TokenSet. name - description - (dt/now) - (assoc tokens (:name token) token))) + (let [path (split-path (:name token) ".")] + (TokenSet. name + description + (dt/now) + (d/oassoc-in tokens path token)))) (update-token [this token-name f] - (if-let [token (get tokens token-name)] - (let [token' (-> (make-token (f token)) - (assoc :modified-at (dt/now)))] - (check-token! token') - (TokenSet. name - description - (dt/now) - (if (= (:name token) (:name token')) - (assoc tokens (:name token') token') - (let [index (d/index-of (keys tokens) (:name token))] + (let [path (split-path token-name ".") + token (get-in tokens path)] + (if token + (let [token' (-> (make-token (f token)) + (assoc :modified-at (dt/now))) + path' (get-path token' ".")] + (check-token! token') + (TokenSet. name + description + (dt/now) + (if (= (:name token) (:name token')) + (d/oassoc-in tokens path token') (-> tokens - (dissoc (:name token)) - (d/addm-at-index index (:name token') token')))))) - this)) + (d/oassoc-in-before path path' token') + (d/dissoc-in path))))) + this))) (delete-token [_ token-name] - (TokenSet. name - description - (dt/now) - (dissoc tokens token-name))) + (let [path (split-path token-name ".")] + (TokenSet. name + description + (dt/now) + (d/dissoc-in tokens path)))) (get-tokens [_] - (vals tokens))) + (->> (tree-seq d/ordered-map? vals tokens) + (filter (partial instance? Token))))) + +(def schema:token-node + [:schema {:registry {::node [:or ::token + [:and + [:map-of {:gen/max 5} :string [:ref ::node]] + [:fn d/ordered-map?]]]}} + [:ref ::node]]) + +(sm/register! ::token-node schema:token-node) (def schema:token-set [:and [:map {:title "TokenSet"} [:name :string] [:description [:maybe :string]] [:modified-at ::sm/inst] - [:tokens [:and [:map-of {:gen/max 5} :string ::token] + [:tokens [:and [:map-of {:gen/max 5} :string ::token-node] [:fn d/ordered-map?]]]] [:fn (partial instance? TokenSet)]]) @@ -140,6 +234,7 @@ (update-set [_ set-name f] "modify a set in the ilbrary") (delete-set [_ set-name] "delete a set in the library") (set-count [_] "get the total number if sets in the library") + (get-set-tree [_] "get a nested tree of all sets in the library") (get-sets [_] "get an ordered sequence of all sets in the library") (get-set [_ set-name] "get one set looking for name") (get-set-group [_ set-group-path] "get the attributes of a set group")) @@ -215,6 +310,7 @@ (update-theme [_ theme-name f] "modify a theme in the ilbrary") (delete-theme [_ theme-name] "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-themes [_] "get an ordered sequence of all themes in the library") (get-theme [_ theme-name] "get one theme looking for name")) @@ -242,8 +338,6 @@ (toggle-set-in-theme [_ theme-name set-name] "toggle a set used / not used in a theme") (validate [_])) -(declare get-path) - (deftype TokensLib [sets set-groups themes] ;; NOTE: This is only for debug purposes, pending to properly ;; implement the toString and alternative printing. @@ -260,41 +354,50 @@ ITokenSets (add-set [_ token-set] (dm/assert! "expected valid token set" (check-token-set! token-set)) - (let [path (get-path token-set)] - (TokensLib. (assoc sets (:name token-set) token-set) + (let [path (get-path token-set "/") + groups-str (get-groups-str token-set "/")] + (TokensLib. (d/oassoc-in sets path token-set) (cond-> set-groups - (seq path) - (assoc path (make-token-set-group))) + (not (str/empty? groups-str)) + (assoc groups-str (make-token-set-group))) themes))) (update-set [this set-name f] - (if-let [set (get sets set-name)] - (let [set' (-> (make-token-set (f set)) - (assoc :modified-at (dt/now)))] - (check-token-set! set') - (TokensLib. (if (= (:name set) (:name set')) - (assoc sets (:name set') set') - (let [index (d/index-of (keys sets) (:name set))] + (let [path (split-path set-name "/") + set (get-in sets path)] + (if set + (let [set' (-> (make-token-set (f set)) + (assoc :modified-at (dt/now))) + path' (get-path set' "/")] + (check-token-set! set') + (TokensLib. (if (= (:name set) (:name set')) + (d/oassoc-in sets path set') (-> sets - (dissoc (:name set)) - (d/addm-at-index index (:name set') set')))) - set-groups ;; TODO update set-groups as needed - themes)) - this)) + (d/oassoc-in-before path path' set') + (d/dissoc-in path))) + set-groups ;; TODO update set-groups as needed + themes)) + this))) (delete-set [_ set-name] - (TokensLib. (dissoc sets set-name) - set-groups ;; TODO remove set-group if needed - themes)) + (let [path (split-path set-name "/")] + (TokensLib. (d/dissoc-in sets path) + set-groups ;; TODO remove set-group if needed + themes))) - (set-count [_] - (count sets)) + (get-set-tree [_] + sets) (get-sets [_] - (vals sets)) + (->> (tree-seq d/ordered-map? vals sets) + (filter (partial instance? TokenSet)))) + + (set-count [this] + (count (get-sets this))) (get-set [_ set-name] - (get sets set-name)) + (let [path (split-path set-name "/")] + (get-in sets path))) (get-set-group [_ set-group-path] (get set-groups set-group-path)) @@ -302,38 +405,47 @@ ITokenThemes (add-theme [_ token-theme] (dm/assert! "expected valid token theme" (check-token-theme! token-theme)) - (TokensLib. sets - set-groups - (assoc themes (:name token-theme) token-theme))) + (let [path (get-path token-theme "/")] + (TokensLib. sets + set-groups + (d/oassoc-in themes path token-theme)))) (update-theme [this theme-name f] - (if-let [theme (get themes theme-name)] - (let [theme' (-> (make-token-theme (f theme)) - (assoc :modified-at (dt/now)))] - (check-token-theme! theme') - (TokensLib. sets - set-groups - (if (= (:name theme) (:name theme')) - (assoc themes (:name theme') theme') - (let [index (d/index-of (keys themes) (:name theme))] + (let [path (split-path theme-name "/") + theme (get-in themes path)] + (if theme + (let [theme' (-> (make-token-theme (f theme)) + (assoc :modified-at (dt/now))) + path' (get-path theme' "/")] + (check-token-theme! theme') + (TokensLib. sets + set-groups + (if (= (:name theme) (:name theme')) + (d/oassoc-in themes path theme') (-> themes - (dissoc (:name theme)) - (d/addm-at-index index (:name theme') theme')))))) - this)) + (d/oassoc-in-before path path' theme') + (d/dissoc-in path))))) + this))) (delete-theme [_ theme-name] - (TokensLib. sets - set-groups - (dissoc themes theme-name))) + (let [path (split-path theme-name "/")] + (TokensLib. sets + set-groups + (d/dissoc-in themes path)))) - (theme-count [_] - (count themes)) + (get-theme-tree [_] + themes) (get-themes [_] - (vals themes)) + (->> (tree-seq d/ordered-map? vals themes) + (filter (partial instance? TokenTheme)))) + + (theme-count [this] + (count (get-themes this))) (get-theme [_ theme-name] - (get themes theme-name)) + (let [path (split-path theme-name "/")] + (get-in themes path))) ITokensLib (add-token-in-set [this set-name token] @@ -478,90 +590,3 @@ set-groups (fres/read-object! r) themes (fres/read-object! r)] (->TokensLib sets set-groups themes)))})) - -;; === Groups handling - -(def schema:groupable-item - [:map {:title "Groupable item"} - [:name :string]]) - -(def valid-groupable-item? - (sm/validator schema:groupable-item)) - -(defn split-path - "Decompose a string in the form 'one.two.three' into a vector of strings, removing spaces." - [path] - (let [xf (comp (map str/trim) - (remove str/empty?))] - (->> (str/split path ".") - (into [] xf)))) - -(defn join-path - "Regenerate a path as a string, from a vector." - [path-vec] - (str/join "." path-vec)) - -(defn group-item - "Add a group to the item name, in the form group.name." - [item group-name] - (dm/assert! - "expected groupable item" - (valid-groupable-item? item)) - (update item :name #(str group-name "." %))) - -(defn ungroup-item - "Remove the first group from the item name." - [item] - (dm/assert! - "expected groupable item" - (valid-groupable-item? item)) - (update item :name #(-> % - (split-path) - (rest) - (join-path)))) - -(defn get-path - "Get the groups part of the name. E.g. group.subgroup.name -> group.subrgoup" - [item] - (dm/assert! - "expected groupable item" - (valid-groupable-item? item)) - (-> (:name item) - (split-path) - (butlast) - (join-path))) - -(defn get-final-name - "Get the final part of the name. E.g. group.subgroup.name -> name" - [item] - (dm/assert! - "expected groupable item" - (valid-groupable-item? item)) - (-> (:name item) - (split-path) - (last))) - -(defn group-items - "Convert a sequence of items in a nested structure like this: - - {'': [itemA itemB] - 'group1': {'': [item1A item1B] - 'subgroup11': {'': [item11A item11B item11C]} - 'subgroup12': {'': [item12A]}} - 'group2': {'subgroup21': {'': [item21A]}}} - " - [items & {:keys [reverse-sort?]}] - (when (seq items) - (reduce (fn [groups item] - (let [pathv (split-path (:name item))] - (update-in groups - pathv - (fn [group] - (if group - (cons group item) - item))))) - (sorted-map-by (fn [key1 key2] ;; TODO: this does not work well with ordered-map - (if reverse-sort? ;; we need to think a bit more of this - (compare key2 key1) - (compare key1 key2)))) - items))) diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc index e96834c27..6a4418e83 100644 --- a/common/test/common_tests/types/tokens_lib_test.cljc +++ b/common/test/common_tests/types/tokens_lib_test.cljc @@ -130,10 +130,10 @@ (t/deftest add-token-set-with-group (let [tokens-lib (ctob/make-tokens-lib) - token-set (ctob/make-token-set :name "test-group.test-token-set") + token-set (ctob/make-token-set :name "test-group/test-token-set") tokens-lib' (ctob/add-set tokens-lib token-set) - set-group (ctob/get-set-group tokens-lib' "test-group")] + set-group (ctob/get-set-group tokens-lib' "test-group")] (t/is (= (:attr1 set-group) "one")) (t/is (= (:attr2 set-group) "two")))) @@ -423,8 +423,8 @@ (t/testing "grouping" (t/deftest split-and-join (let [name "group.subgroup.name" - path (ctob/split-path name) - name' (ctob/join-path path)] + path (ctob/split-path name ".") + name' (ctob/join-path path ".")] (t/is (= (first path) "group")) (t/is (= (second path) "subgroup")) (t/is (= (nth path 2) "name")) @@ -432,7 +432,7 @@ (t/deftest remove-spaces (let [name "group . subgroup . name" - path (ctob/split-path name)] + path (ctob/split-path name ".")] (t/is (= (first path) "group")) (t/is (= (second path) "subgroup")) (t/is (= (nth path 2) "name")))) @@ -441,82 +441,555 @@ (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) token2 (ctob/make-token :name "some group.token2" :type :boolean :value true) - token1' (ctob/group-item token1 "big group") - token2' (ctob/group-item token2 "big group") - token1'' (ctob/ungroup-item token1') - token2'' (ctob/ungroup-item token2')] + token1' (ctob/group-item token1 "big group" ".") + token2' (ctob/group-item token2 "big group" ".") + token1'' (ctob/ungroup-item token1' ".") + token2'' (ctob/ungroup-item token2' ".")] (t/is (= (:name token1') "big group.token1")) (t/is (= (:name token2') "big group.some group.token2")) (t/is (= (:name token1'') "token1")) (t/is (= (:name token2'') "some group.token2")))) - (t/deftest get-path + (t/deftest get-groups-str (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) token2 (ctob/make-token :name "some-group.token2" :type :boolean :value true) token3 (ctob/make-token :name "some-group.some-subgroup.token3" :type :boolean :value true)] - (t/is (= (ctob/get-path token1) "")) - (t/is (= (ctob/get-path token2) "some-group")) - (t/is (= (ctob/get-path token3) "some-group.some-subgroup")))) + (t/is (= (ctob/get-groups-str token1 ".") "")) + (t/is (= (ctob/get-groups-str token2 ".") "some-group")) + (t/is (= (ctob/get-groups-str token3 ".") "some-group.some-subgroup")))) (t/deftest get-final-name (let [token1 (ctob/make-token :name "token1" :type :boolean :value true) token2 (ctob/make-token :name "some-group.token2" :type :boolean :value true) token3 (ctob/make-token :name "some-group.some-subgroup.token3" :type :boolean :value true)] - (t/is (= (ctob/get-final-name token1) "token1")) - (t/is (= (ctob/get-final-name token2) "token2")) - (t/is (= (ctob/get-final-name token3) "token3")))) - - (t/deftest group-items - (let [tokens-lib (-> (ctob/make-tokens-lib) - (ctob/add-set (ctob/make-token-set :name "token-set1")) - (ctob/add-set (ctob/make-token-set :name "sgroup1.token-set2")) - (ctob/add-set (ctob/make-token-set :name "sgroup1.token-set3")) - (ctob/add-set (ctob/make-token-set :name "sgroup1.ssubgroup1.token-set4")) - (ctob/add-set (ctob/make-token-set :name "sgroup2.token-set5")) - (ctob/add-token-in-set "sgroup2.token-set5" - (ctob/make-token :name "tgroup1.tsubgroup1.token1" + (t/is (= (ctob/get-final-name token1 ".") "token1")) + (t/is (= (ctob/get-final-name token2 ".") "token2")) + (t/is (= (ctob/get-final-name token3 ".") "token3")))) + + (t/testing "grouped tokens" + (t/deftest grouped-tokens + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "token1" :type :boolean :value true)) - (ctob/add-token-in-set "sgroup2.token-set5" - (ctob/make-token :name "tgroup1.tsubgroup1.token2" + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.token2" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.token3" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.subgroup11.token4" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group2.token5" :type :boolean :value true))) - sets (ctob/get-sets tokens-lib) - set (ctob/get-set tokens-lib "sgroup2.token-set5") - tokens (ctob/get-tokens set) - set-groups (ctob/group-items sets) + set (ctob/get-set tokens-lib "test-token-set") + tokens-list (ctob/get-tokens set) - token-set1 (get set-groups "token-set1") - sgroup1 (get set-groups "sgroup1") - token-set2 (get sgroup1 "token-set2") - token-set3 (get sgroup1 "token-set3") - ssubgroup1 (get sgroup1 "ssubgroup1") - token-set4 (get ssubgroup1 "token-set4") - sgroup2 (get set-groups "sgroup2") - token-set5 (get sgroup2 "token-set5") + tokens-tree (:tokens set) + [node-token1 node-group1 node-group2] + (ctob/get-children tokens-tree) - token-groups (ctob/group-items tokens) - tgroup1 (get token-groups "tgroup1") - tsubgroup1 (get tgroup1 "tsubgroup1") - token1 (get tsubgroup1 "token1") - token2 (get tsubgroup1 "token2")] + [node-token2 node-token3 node-subgroup11] + (ctob/get-children (second node-group1)) - ;; {"sgroup1" - ;; {"token-set2" {:name "sgroup1.token-set2" ...} - ;; "token-set3" {:name "sgroup1.token-set3" ...} - ;; "ssubgroup1" - ;; {"token-set4" {:name "sgroup1.ssubgroup1.token-set4" ...}}} - ;; "sgroup2" - ;; {"token-set5" {:name "sgroup2.token-set5" ...}} - ;; "token-set1" {:name "token-set1" ...}} + [node-token4] + (ctob/get-children (second node-subgroup11)) - (t/is (= (:name token-set1) "token-set1")) - (t/is (= (:name token-set2) "sgroup1.token-set2")) - (t/is (= (:name token-set3) "sgroup1.token-set3")) - (t/is (= (:name token-set4) "sgroup1.ssubgroup1.token-set4")) - (t/is (= (:name token-set5) "sgroup2.token-set5")) + [node-token5] + (ctob/get-children (second node-group2))] - (t/is (= (:name token1) "tgroup1.tsubgroup1.token1")) - (t/is (= (:name token2) "tgroup1.tsubgroup1.token2"))))) + (t/is (= (count tokens-list) 5)) + (t/is (= (:name (nth tokens-list 0)) "token1")) + (t/is (= (:name (nth tokens-list 1)) "group1.token2")) + (t/is (= (:name (nth tokens-list 2)) "group1.token3")) + (t/is (= (:name (nth tokens-list 3)) "group1.subgroup11.token4")) + (t/is (= (:name (nth tokens-list 4)) "group2.token5")) + + (t/is (= (first node-token1) "token1")) + (t/is (= (ctob/group? (second node-token1)) false)) + (t/is (= (:name (second node-token1)) "token1")) + + (t/is (= (first node-group1) "group1")) + (t/is (= (ctob/group? (second node-group1)) true)) + (t/is (= (count (second node-group1)) 3)) + + (t/is (= (first node-token2) "token2")) + (t/is (= (ctob/group? (second node-token2)) false)) + (t/is (= (:name (second node-token2)) "group1.token2")) + + (t/is (= (first node-token3) "token3")) + (t/is (= (ctob/group? (second node-token3)) false)) + (t/is (= (:name (second node-token3)) "group1.token3")) + + (t/is (= (first node-subgroup11) "subgroup11")) + (t/is (= (ctob/group? (second node-subgroup11)) true)) + (t/is (= (count (second node-subgroup11)) 1)) + + (t/is (= (first node-token4) "token4")) + (t/is (= (ctob/group? (second node-token4)) false)) + (t/is (= (:name (second node-token4)) "group1.subgroup11.token4")) + + (t/is (= (first node-token5) "token5")) + (t/is (= (ctob/group? (second node-token5)) false)) + (t/is (= (:name (second node-token5)) "group2.token5")))) + + (t/deftest update-token-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-2" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-3" + :type :boolean + :value true))) + + tokens-lib' (-> tokens-lib + (ctob/update-token-in-set "test-token-set" "group1.test-token-2" + (fn [token] + (assoc token + :description "some description" + :value false)))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + group1' (get-in token-set' [:tokens "group1"]) + token (get-in token-set [:tokens "group1" "test-token-2"]) + token' (get-in token-set' [:tokens "group1" "test-token-2"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count group1') 2)) + (t/is (= (d/index-of (keys group1') "test-token-2") 0)) + (t/is (= (:name token') "group1.test-token-2")) + (t/is (= (:description token') "some description")) + (t/is (= (:value token') false)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) + (t/is (dt/is-after? (:modified-at token') (:modified-at token))))) + + (t/deftest rename-token-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-2" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-3" + :type :boolean + :value true))) + + tokens-lib' (-> tokens-lib + (ctob/update-token-in-set "test-token-set" "group1.test-token-2" + (fn [token] + (assoc token + :name "group1.updated-name")))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + group1' (get-in token-set' [:tokens "group1"]) + token (get-in token-set [:tokens "group1" "test-token-2"]) + token' (get-in token-set' [:tokens "group1" "updated-name"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count group1') 2)) + (t/is (= (d/index-of (keys group1') "updated-name") 0)) + (t/is (= (:name token') "group1.updated-name")) + (t/is (= (:description token') nil)) + (t/is (= (:value token') true)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) + (t/is (dt/is-after? (:modified-at token') (:modified-at token))))) + + (t/deftest move-token-of-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-2" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-3" + :type :boolean + :value true))) + + tokens-lib' (-> tokens-lib + (ctob/update-token-in-set "test-token-set" "group1.test-token-2" + (fn [token] + (assoc token + :name "group2.updated-name")))) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + group1' (get-in token-set' [:tokens "group1"]) + group2' (get-in token-set' [:tokens "group2"]) + token (get-in token-set [:tokens "group1" "test-token-2"]) + token' (get-in token-set' [:tokens "group2" "updated-name"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count group1') 1)) + (t/is (= (count group2') 1)) + (t/is (= (d/index-of (keys group2') "updated-name") 0)) + (t/is (= (:name token') "group2.updated-name")) + (t/is (= (:description token') nil)) + (t/is (= (:value token') true)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))) + (t/is (dt/is-after? (:modified-at token') (:modified-at token))))) + + (t/deftest delete-token-in-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "test-token-set")) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "test-token-1" + :type :boolean + :value true)) + (ctob/add-token-in-set "test-token-set" + (ctob/make-token :name "group1.test-token-2" + :type :boolean + :value true))) + tokens-lib' (-> tokens-lib + (ctob/delete-token-from-set "test-token-set" "group1.test-token-2")) + + token-set (ctob/get-set tokens-lib "test-token-set") + token-set' (ctob/get-set tokens-lib' "test-token-set") + token' (get-in token-set' [:tokens "group1" "test-token-2"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count (:tokens token-set')) 1)) + (t/is (nil? token')) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))) + + (t/testing "grouped sets" + (t/deftest grouped-sets + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set-1")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-2")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-3")) + (ctob/add-set (ctob/make-token-set :name "group1/subgroup11/token-set-4")) + (ctob/add-set (ctob/make-token-set :name "group2/token-set-5"))) + + sets-list (ctob/get-sets tokens-lib) + + sets-tree (ctob/get-set-tree tokens-lib) + + [node-set1 node-group1 node-group2] + (ctob/get-children sets-tree) + + [node-set2 node-set3 node-subgroup11] + (ctob/get-children (second node-group1)) + + [node-set4] + (ctob/get-children (second node-subgroup11)) + + [node-set5] + (ctob/get-children (second node-group2))] + + (t/is (= (count sets-list) 5)) + (t/is (= (:name (nth sets-list 0)) "token-set-1")) + (t/is (= (:name (nth sets-list 1)) "group1/token-set-2")) + (t/is (= (:name (nth sets-list 2)) "group1/token-set-3")) + (t/is (= (:name (nth sets-list 3)) "group1/subgroup11/token-set-4")) + (t/is (= (:name (nth sets-list 4)) "group2/token-set-5")) + + (t/is (= (first node-set1) "token-set-1")) + (t/is (= (ctob/group? (second node-set1)) false)) + (t/is (= (:name (second node-set1)) "token-set-1")) + + (t/is (= (first node-group1) "group1")) + (t/is (= (ctob/group? (second node-group1)) true)) + (t/is (= (count (second node-group1)) 3)) + + (t/is (= (first node-set2) "token-set-2")) + (t/is (= (ctob/group? (second node-set2)) false)) + (t/is (= (:name (second node-set2)) "group1/token-set-2")) + + (t/is (= (first node-set3) "token-set-3")) + (t/is (= (ctob/group? (second node-set3)) false)) + (t/is (= (:name (second node-set3)) "group1/token-set-3")) + + (t/is (= (first node-subgroup11) "subgroup11")) + (t/is (= (ctob/group? (second node-subgroup11)) true)) + (t/is (= (count (second node-subgroup11)) 1)) + + (t/is (= (first node-set4) "token-set-4")) + (t/is (= (ctob/group? (second node-set4)) false)) + (t/is (= (:name (second node-set4)) "group1/subgroup11/token-set-4")) + + (t/is (= (first node-set5) "token-set-5")) + (t/is (= (ctob/group? (second node-set5)) false)) + (t/is (= (:name (second node-set5)) "group2/token-set-5")))) + + (t/deftest update-set-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set-1")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-2")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-3")) + (ctob/add-set (ctob/make-token-set :name "group1/subgroup11/token-set-4")) + (ctob/add-set (ctob/make-token-set :name "group2/token-set-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-set "group1/token-set-2" + (fn [token-set] + (assoc token-set :description "some description")))) + + sets-tree (ctob/get-set-tree tokens-lib) + sets-tree' (ctob/get-set-tree tokens-lib') + group1' (get sets-tree' "group1") + token-set (get-in sets-tree ["group1" "token-set-2"]) + token-set' (get-in sets-tree' ["group1" "token-set-2"])] + + (t/is (= (ctob/set-count tokens-lib') 5)) + (t/is (= (count group1') 3)) + (t/is (= (d/index-of (keys group1') "token-set-2") 0)) + (t/is (= (:name token-set') "group1/token-set-2")) + (t/is (= (:description token-set') "some description")) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + + (t/deftest rename-set-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set-1")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-2")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-3")) + (ctob/add-set (ctob/make-token-set :name "group1/subgroup11/token-set-4")) + (ctob/add-set (ctob/make-token-set :name "group2/token-set-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-set "group1/token-set-2" + (fn [token-set] + (assoc token-set + :name "group1/updated-name")))) + + sets-tree (ctob/get-set-tree tokens-lib) + sets-tree' (ctob/get-set-tree tokens-lib') + group1' (get sets-tree' "group1") + token-set (get-in sets-tree ["group1" "token-set-2"]) + token-set' (get-in sets-tree' ["group1" "updated-name"])] + + (t/is (= (ctob/set-count tokens-lib') 5)) + (t/is (= (count group1') 3)) + (t/is (= (d/index-of (keys group1') "updated-name") 0)) + (t/is (= (:name token-set') "group1/updated-name")) + (t/is (= (:description token-set') nil)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + + (t/deftest move-set-of-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set-1")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-2")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-3")) + (ctob/add-set (ctob/make-token-set :name "group1/subgroup11/token-set-4")) + #_(ctob/add-set (ctob/make-token-set :name "group2/token-set-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-set "group1/token-set-2" + (fn [token-set] + (assoc token-set + :name "group2/updated-name")))) + + sets-tree (ctob/get-set-tree tokens-lib) + sets-tree' (ctob/get-set-tree tokens-lib') + group1' (get sets-tree' "group1") + group2' (get sets-tree' "group2") + token-set (get-in sets-tree ["group1" "token-set-2"]) + token-set' (get-in sets-tree' ["group2" "updated-name"])] + + (t/is (= (ctob/set-count tokens-lib') 4)) + (t/is (= (count group1') 2)) + (t/is (= (count group2') 1)) + (t/is (= (d/index-of (keys group2') "updated-name") 0)) + (t/is (= (:name token-set') "group2/updated-name")) + (t/is (= (:description token-set') nil)) + (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set))))) + + (t/deftest delete-set-in-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "token-set-1")) + (ctob/add-set (ctob/make-token-set :name "group1/token-set-2"))) + + tokens-lib' (-> tokens-lib + (ctob/delete-set "group1/token-set-2")) + + sets-tree' (ctob/get-set-tree tokens-lib') + token-set' (get-in sets-tree' ["group1" "token-set-2"])] + + (t/is (= (ctob/set-count tokens-lib') 1)) + (t/is (= (count sets-tree') 1)) + (t/is (nil? token-set'))))) + + (t/testing "grouped themes" + (t/deftest grouped-themes + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "token-theme-1")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-2")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-3")) + (ctob/add-theme (ctob/make-token-theme :name "group1/subgroup11/token-theme-4")) + (ctob/add-theme (ctob/make-token-theme :name "group2/token-theme-5"))) + + themes-list (ctob/get-themes tokens-lib) + + themes-tree (ctob/get-theme-tree tokens-lib) + + [node-theme1 node-group1 node-group2] + (ctob/get-children themes-tree) + + [node-theme2 node-theme3 node-subgroup11] + (ctob/get-children (second node-group1)) + + [node-theme4] + (ctob/get-children (second node-subgroup11)) + + [node-theme5] + (ctob/get-children (second node-group2))] + + (t/is (= (count themes-list) 5)) + (t/is (= (:name (nth themes-list 0)) "token-theme-1")) + (t/is (= (:name (nth themes-list 1)) "group1/token-theme-2")) + (t/is (= (:name (nth themes-list 2)) "group1/token-theme-3")) + (t/is (= (:name (nth themes-list 3)) "group1/subgroup11/token-theme-4")) + (t/is (= (:name (nth themes-list 4)) "group2/token-theme-5")) + + (t/is (= (first node-theme1) "token-theme-1")) + (t/is (= (ctob/group? (second node-theme1)) false)) + (t/is (= (:name (second node-theme1)) "token-theme-1")) + + (t/is (= (first node-group1) "group1")) + (t/is (= (ctob/group? (second node-group1)) true)) + (t/is (= (count (second node-group1)) 3)) + + (t/is (= (first node-theme2) "token-theme-2")) + (t/is (= (ctob/group? (second node-theme2)) false)) + (t/is (= (:name (second node-theme2)) "group1/token-theme-2")) + + (t/is (= (first node-theme3) "token-theme-3")) + (t/is (= (ctob/group? (second node-theme3)) false)) + (t/is (= (:name (second node-theme3)) "group1/token-theme-3")) + + (t/is (= (first node-subgroup11) "subgroup11")) + (t/is (= (ctob/group? (second node-subgroup11)) true)) + (t/is (= (count (second node-subgroup11)) 1)) + + (t/is (= (first node-theme4) "token-theme-4")) + (t/is (= (ctob/group? (second node-theme4)) false)) + (t/is (= (:name (second node-theme4)) "group1/subgroup11/token-theme-4")) + + (t/is (= (first node-theme5) "token-theme-5")) + (t/is (= (ctob/group? (second node-theme5)) false)) + (t/is (= (:name (second node-theme5)) "group2/token-theme-5")))) + + (t/deftest update-theme-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "token-theme-1")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-2")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-3")) + (ctob/add-theme (ctob/make-token-theme :name "group1/subgroup11/token-theme-4")) + (ctob/add-theme (ctob/make-token-theme :name "group2/token-theme-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-theme "group1/token-theme-2" + (fn [token-theme] + (assoc token-theme :description "some description")))) + + themes-tree (ctob/get-theme-tree tokens-lib) + themes-tree' (ctob/get-theme-tree tokens-lib') + group1' (get themes-tree' "group1") + token-theme (get-in themes-tree ["group1" "token-theme-2"]) + token-theme' (get-in themes-tree' ["group1" "token-theme-2"])] + + (t/is (= (ctob/theme-count tokens-lib') 5)) + (t/is (= (count group1') 3)) + (t/is (= (d/index-of (keys group1') "token-theme-2") 0)) + (t/is (= (:name token-theme') "group1/token-theme-2")) + (t/is (= (:description token-theme') "some description")) + (t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))) + + (t/deftest rename-theme-in-groups + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "token-theme-1")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-2")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-3")) + (ctob/add-theme (ctob/make-token-theme :name "group1/subgroup11/token-theme-4")) + (ctob/add-theme (ctob/make-token-theme :name "group2/token-theme-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-theme "group1/token-theme-2" + (fn [token-theme] + (assoc token-theme + :name "group1/updated-name")))) + + themes-tree (ctob/get-theme-tree tokens-lib) + themes-tree' (ctob/get-theme-tree tokens-lib') + group1' (get themes-tree' "group1") + token-theme (get-in themes-tree ["group1" "token-theme-2"]) + token-theme' (get-in themes-tree' ["group1" "updated-name"])] + + (t/is (= (ctob/theme-count tokens-lib') 5)) + (t/is (= (count group1') 3)) + (t/is (= (d/index-of (keys group1') "updated-name") 0)) + (t/is (= (:name token-theme') "group1/updated-name")) + (t/is (= (:description token-theme') nil)) + (t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))) + + (t/deftest move-theme-of-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "token-theme-1")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-2")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-3")) + (ctob/add-theme (ctob/make-token-theme :name "group1/subgroup11/token-theme-4")) + #_(ctob/add-theme (ctob/make-token-theme :name "group2/token-theme-5"))) + + tokens-lib' (-> tokens-lib + (ctob/update-theme "group1/token-theme-2" + (fn [token-theme] + (assoc token-theme + :name "group2/updated-name")))) + + themes-tree (ctob/get-theme-tree tokens-lib) + themes-tree' (ctob/get-theme-tree tokens-lib') + group1' (get themes-tree' "group1") + group2' (get themes-tree' "group2") + token-theme (get-in themes-tree ["group1" "token-theme-2"]) + token-theme' (get-in themes-tree' ["group2" "updated-name"])] + + (t/is (= (ctob/theme-count tokens-lib') 4)) + (t/is (= (count group1') 2)) + (t/is (= (count group2') 1)) + (t/is (= (d/index-of (keys group2') "updated-name") 0)) + (t/is (= (:name token-theme') "group2/updated-name")) + (t/is (= (:description token-theme') nil)) + (t/is (dt/is-after? (:modified-at token-theme') (:modified-at token-theme))))) + + (t/deftest delete-theme-in-group + (let [tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-theme (ctob/make-token-theme :name "token-theme-1")) + (ctob/add-theme (ctob/make-token-theme :name "group1/token-theme-2"))) + + tokens-lib' (-> tokens-lib + (ctob/delete-theme "group1/token-theme-2")) + + themes-tree' (ctob/get-theme-tree tokens-lib') + token-theme' (get-in themes-tree' ["group1" "token-theme-2"])] + + (t/is (= (ctob/theme-count tokens-lib') 1)) + (t/is (= (count themes-tree') 1)) + (t/is (nil? token-theme'))))))