diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 5c6e8f881b..57b6082eff 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -983,7 +983,7 @@ [changes tokens-lib] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-tokens-lib (get library-data :tokens-lib)] + prev-tokens-lib (cfo/get-tokens-lib library-data)] (-> changes (update :redo-changes conj {:type :set-tokens-lib :tokens-lib tokens-lib}) (update :undo-changes conj {:type :set-tokens-lib :tokens-lib prev-tokens-lib}) @@ -992,7 +992,7 @@ (defn set-token [changes set-id token-id token] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token (some-> (get library-data :tokens-lib) + prev-token (some-> (cfo/get-tokens-lib library-data) (ctob/get-token set-id token-id))] (-> changes (update :redo-changes conj {:type :set-token @@ -1009,7 +1009,7 @@ [changes id token-set] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token-set (some-> (get library-data :tokens-lib) + prev-token-set (some-> (cfo/get-tokens-lib library-data) (ctob/get-set id))] (-> changes (update :redo-changes conj {:type :set-token-set @@ -1024,7 +1024,7 @@ [changes id new-name] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-token-set (some-> (get library-data :tokens-lib) + prev-token-set (some-> (cfo/get-tokens-lib library-data) (ctob/get-set id))] (-> changes (update :redo-changes conj {:type :set-token-set @@ -1038,7 +1038,7 @@ (defn set-token-theme [changes id theme] (assert-library! changes) (let [library-data (::library-data (meta changes)) - prev-theme (some-> (get library-data :tokens-lib) + prev-theme (some-> (cfo/get-tokens-lib library-data) (ctob/get-theme id))] (-> changes (update :redo-changes conj {:type :set-token-theme diff --git a/common/src/app/common/files/tokens.cljc b/common/src/app/common/files/tokens.cljc index 518912fc86..ba004a716b 100644 --- a/common/src/app/common/files/tokens.cljc +++ b/common/src/app/common/files/tokens.cljc @@ -490,8 +490,8 @@ is external (in this case the own library is inactive, but still exists)." [file-data] (let [tokens-lib (:tokens-lib file-data)] - (and (some? tokens-lib) - (not (ctob/empty-lib? tokens-lib))))) + (and (some? tokens-lib) + (not (ctob/empty-lib? tokens-lib))))) (defn tokens-provider? "Returns true if the file MAY become a tokens source. This is if the file has tokens and has diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc index de3ad14163..6b7d7a7172 100644 --- a/common/src/app/common/logic/tokens.cljc +++ b/common/src/app/common/logic/tokens.cljc @@ -105,64 +105,88 @@ "Create changes for dropping a token set or token set. Throws for impossible moves." [changes tokens-lib params] - (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] - (pcb/move-token-set changes params) + (if tokens-lib + (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] + (pcb/move-token-set changes params) + changes) changes)) (defn generate-move-token-set-group "Create changes for dropping a token set or token set group. Throws for impossible moves" [changes tokens-lib params] - (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] - (pcb/move-token-set-group changes params) + (if tokens-lib + (if-let [params (calculate-move-token-set-or-set-group tokens-lib params)] + (pcb/move-token-set-group changes params) + changes) changes)) (defn generate-delete-token-set-group "Create changes for deleting a token set group." [changes tokens-lib path] - (let [sets (ctob/get-sets-at-path tokens-lib path)] - (reduce (fn [changes set] - (pcb/set-token-set changes (ctob/get-id set) nil)) - changes - sets))) + (if tokens-lib + (let [sets (ctob/get-sets-at-path tokens-lib path)] + (reduce (fn [changes set] + (pcb/set-token-set changes (ctob/get-id set) nil)) + changes + sets)) + changes)) ;; Tokens Status (defn- update-tokens-status [changes tokens-status update-fn & args] - (let [tokens-status' (apply update-fn tokens-status args)] - (if (not= tokens-status tokens-status') - (pcb/set-tokens-status changes tokens-status') - changes))) + (if tokens-status + (let [tokens-status' (apply update-fn tokens-status args)] + (if (not= tokens-status tokens-status') + (pcb/set-tokens-status changes tokens-status') + changes)) + changes)) (defn generate-activate-theme [changes tokens-status tokens-lib id] - (update-tokens-status changes tokens-status cfo/activate-theme tokens-lib id)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/activate-theme tokens-lib id) + changes)) (defn generate-deactivate-theme [changes tokens-status tokens-lib id] - (update-tokens-status changes tokens-status cfo/deactivate-theme tokens-lib id)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/deactivate-theme tokens-lib id) + changes)) (defn generate-set-theme-status [changes tokens-status tokens-lib id active?] - (update-tokens-status changes tokens-status cfo/set-theme-active tokens-lib id active?)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/set-theme-active tokens-lib id active?) + changes)) (defn generate-toggle-theme [changes tokens-status tokens-lib id] - (update-tokens-status changes tokens-status cfo/toggle-theme-active tokens-lib id)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/toggle-theme-active tokens-lib id) + changes)) (defn generate-set-enabled-token-set [changes tokens-status tokens-lib id enabled?] - (update-tokens-status changes tokens-status cfo/set-set-active tokens-lib id enabled?)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/set-set-active tokens-lib id enabled?) + changes)) (defn generate-toggle-token-set [changes tokens-status tokens-lib id] - (update-tokens-status changes tokens-status cfo/toggle-set-active tokens-lib id)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/toggle-set-active tokens-lib id) + changes)) (defn generate-toggle-token-set-group [changes tokens-status tokens-lib group-path] - (update-tokens-status changes tokens-status cfo/toggle-set-group-active tokens-lib group-path)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/toggle-set-group-active tokens-lib group-path) + changes)) (defn generate-sync-tokens-status-with-lib [changes tokens-status tokens-lib] - (update-tokens-status changes tokens-status cfo/sync-tokens-status-with-lib tokens-lib)) + (if tokens-lib + (update-tokens-status changes tokens-status cfo/sync-tokens-status-with-lib tokens-lib) + changes)) diff --git a/common/test/common_tests/files/tokens_test.cljc b/common/test/common_tests/files/tokens_test.cljc index 3d2fe863d8..93b87a12e1 100644 --- a/common/test/common_tests/files/tokens_test.cljc +++ b/common/test/common_tests/files/tokens_test.cljc @@ -1312,3 +1312,168 @@ 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')))))) + +;; Mismatched tokens-status and tokens-lib +;; +;; The tokens-status may contain IDs for themes and sets that do not exist in +;; the tokens-lib (e.g. when the status comes from the tokens lib of a different source). +;; All functions must handle this gracefully: return a reasonable result, never +;; throw an exception. + +(t/deftest test-mismatched-status-lib + (let [real-theme-1 (thi/new-id! :real-theme-1) + real-theme-2 (thi/new-id! :real-theme-2) + real-set-a (thi/new-id! :real-set-a) + real-set-b (thi/new-id! :real-set-b) + real-set-c (thi/new-id! :real-set-c) + invalid-theme-id (thi/new-id! :invalid-theme) + invalid-set-id (thi/new-id! :invalid-set) + + tokens-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id real-set-a :name "real-set-a")) + (ctob/add-set (ctob/make-token-set :id real-set-b :name "real-set-b")) + (ctob/add-set (ctob/make-token-set :id real-set-c :name "real-set-c")) + (ctob/add-token real-set-a (ctob/make-token :name "spacing" :type :spacing :value "8px")) + (ctob/add-token real-set-b (ctob/make-token :name "radius" :type :border-radius :value "4px")) + (ctob/add-theme (ctob/make-token-theme :id real-theme-1 + :name "theme-1" + :group "colors" + :sets #{"real-set-a"})) + (ctob/add-theme (ctob/make-token-theme :id real-theme-2 + :name "theme-2" + :group "colors" + :sets #{"real-set-b" "real-set-c"}))) + + ;; Status has one valid and one invalid theme, one valid and one invalid set + tokens-status (ctos/make-tokens-status :active-theme-ids #{real-theme-1 invalid-theme-id} + :active-set-ids #{real-set-a invalid-set-id})] + + (t/testing "activate-theme with non-existent theme id returns status unchanged" + (let [status' (cfo/activate-theme tokens-status tokens-lib invalid-theme-id)] + (t/is (identical? tokens-status status')))) + + (t/testing "activate-theme with valid theme works despite invalid ids in status" + (let [status' (cfo/activate-theme tokens-status tokens-lib real-theme-2)] + (t/is (ctos/theme-active? status' real-theme-2)) + (t/is (not (ctos/theme-active? status' real-theme-1))) + (t/is (ctos/theme-active? status' invalid-theme-id)) + (t/is (contains? (ctos/get-active-set-ids status') real-set-b)) + (t/is (contains? (ctos/get-active-set-ids status') real-set-c)))) + + (t/testing "deactivate-theme with non-existent theme id recalculates sets" + (let [status' (cfo/deactivate-theme tokens-status tokens-lib invalid-theme-id)] + (t/is (not (ctos/theme-active? status' invalid-theme-id))) + (t/is (ctos/theme-active? status' real-theme-1)) + (t/is (contains? (ctos/get-active-set-ids status') real-set-a)))) + + (t/testing "toggle-theme-active with non-existent theme id deactivates it" + (let [status' (cfo/toggle-theme-active tokens-status tokens-lib invalid-theme-id)] + (t/is (not (ctos/theme-active? status' invalid-theme-id))) + (t/is (ctos/theme-active? status' real-theme-1)))) + + (t/testing "toggle-theme-active with valid inactive theme activates it" + (let [status' (cfo/toggle-theme-active tokens-status tokens-lib real-theme-2)] + (t/is (ctos/theme-active? status' real-theme-2)) + (t/is (contains? (ctos/get-active-set-ids status') real-set-b)) + (t/is (contains? (ctos/get-active-set-ids status') real-set-c)))) + + (t/testing "set-theme-active false with non-existent theme recalculates sets" + (let [status' (cfo/set-theme-active tokens-status tokens-lib invalid-theme-id false)] + (t/is (not (ctos/theme-active? status' invalid-theme-id))) + (t/is (contains? (ctos/get-active-set-ids status') real-set-a)))) + + (t/testing "set-theme-active true with non-existent theme returns status unchanged" + (let [status' (cfo/set-theme-active tokens-status tokens-lib invalid-theme-id true)] + (t/is (identical? tokens-status status')))) + + (t/testing "get-active-themes filters out non-existent theme ids" + (let [active-themes (cfo/get-active-themes tokens-status tokens-lib)] + (t/is (= 1 (count active-themes))) + (t/is (= real-theme-1 (ctob/get-id (first active-themes)))))) + + (t/testing "get-active-sets filters out non-existent set ids" + (let [active-sets (cfo/get-active-sets tokens-status tokens-lib)] + (t/is (= 1 (count active-sets))) + (t/is (= real-set-a (ctob/get-id (first active-sets)))))) + + (t/testing "set-set-active with non-existent set id returns status unchanged" + (t/is (identical? tokens-status (cfo/set-set-active tokens-status tokens-lib invalid-set-id true))) + (t/is (identical? tokens-status (cfo/set-set-active tokens-status tokens-lib invalid-set-id false)))) + + (t/testing "activate-set with non-existent set id returns status unchanged" + (t/is (identical? tokens-status (cfo/activate-set tokens-status tokens-lib invalid-set-id)))) + + (t/testing "deactivate-set with non-existent set id returns status unchanged" + (t/is (identical? tokens-status (cfo/deactivate-set tokens-status tokens-lib invalid-set-id)))) + + (t/testing "toggle-set-active with non-existent set id returns status unchanged" + (t/is (identical? tokens-status (cfo/toggle-set-active tokens-status tokens-lib invalid-set-id)))) + + (t/testing "sets-at-path-all-active? with invalid set ids in status don't cause errors" + ;; real-set-a is at [] and is active (plus invalid-set-id); real-set-b, real-set-c at [] inactive + (t/is (= :partial (cfo/sets-at-path-all-active? tokens-status tokens-lib []))) + ;; All sets at [] active (with invalid id still in status) + (let [all-active-status (ctos/make-tokens-status :active-theme-ids #{} + :active-set-ids #{real-set-a real-set-b real-set-c invalid-set-id})] + (t/is (= :all (cfo/sets-at-path-all-active? all-active-status tokens-lib []))))) + + (t/testing "toggle-set-group-active with invalid set ids in status don't cause errors" + ;; Status has real-set-a + invalid-set-id active -> :partial at [] + ;; Toggling :partial deactivates all at [], invalid-set-id remains + (let [status' (cfo/toggle-set-group-active tokens-status tokens-lib [])] + (t/is (not (ctos/set-active? status' real-set-a))) + (t/is (not (ctos/set-active? status' real-set-b))) + (t/is (not (ctos/set-active? status' real-set-c))) + (t/is (ctos/set-active? status' invalid-set-id))) + ;; All sets at [] active + invalid-set-id -> :all at [] + ;; Toggling :all deactivates all at [], invalid-set-id remains + (let [all-active (ctos/make-tokens-status :active-theme-ids #{} + :active-set-ids #{real-set-a real-set-b real-set-c invalid-set-id}) + status' (cfo/toggle-set-group-active all-active tokens-lib [])] + (t/is (not (ctos/set-active? status' real-set-a))) + (t/is (not (ctos/set-active? status' real-set-b))) + (t/is (not (ctos/set-active? status' real-set-c))) + (t/is (ctos/set-active? status' invalid-set-id)))) + + (t/testing "get-tokens-in-active-sets with invalid set ids returns tokens from valid sets only" + (let [tokens (cfo/get-tokens-in-active-sets tokens-status tokens-lib)] + (t/is (= 1 (count tokens))) + (t/is (contains? tokens "spacing")) + (t/is (not (contains? tokens "radius"))))) + + (t/testing "get-tokens-in-active-sets-force with invalid set ids and valid force set" + (let [tokens (cfo/get-tokens-in-active-sets-force tokens-status tokens-lib real-set-b)] + (t/is (= 2 (count tokens))) + (t/is (contains? tokens "spacing")) + (t/is (contains? tokens "radius")))) + + (t/testing "get-tokens-in-active-sets-force with non-existent force set id" + (let [tokens (cfo/get-tokens-in-active-sets-force tokens-status tokens-lib invalid-set-id)] + (t/is (= 1 (count tokens))) + (t/is (contains? tokens "spacing")))) + + (t/testing "all-invalid status: every id is non-existent in lib" + (let [all-invalid (ctos/make-tokens-status :active-theme-ids #{invalid-theme-id} + :active-set-ids #{invalid-set-id})] + ;; get-active-themes returns empty + (t/is (empty? (cfo/get-active-themes all-invalid tokens-lib))) + ;; get-active-sets returns empty + (t/is (empty? (cfo/get-active-sets all-invalid tokens-lib))) + ;; get-tokens-in-active-sets returns empty + (t/is (empty? (cfo/get-tokens-in-active-sets all-invalid tokens-lib))) + ;; get-tokens-in-active-sets-force returns only force set tokens + (let [tokens (cfo/get-tokens-in-active-sets-force all-invalid tokens-lib real-set-a)] + (t/is (= 1 (count tokens))) + (t/is (contains? tokens "spacing"))) + ;; activate-theme for non-existent id is no-op + (t/is (identical? all-invalid (cfo/activate-theme all-invalid tokens-lib invalid-theme-id))) + ;; deactivate-theme removes non-existent id and recalculates (empty) sets + (let [status' (cfo/deactivate-theme all-invalid tokens-lib invalid-theme-id)] + (t/is (not (ctos/theme-active? status' invalid-theme-id))) + (t/is (= #{} (ctos/get-active-set-ids status')))) + ;; toggle-theme-active toggles non-existent id (deactivates) + (let [status' (cfo/toggle-theme-active all-invalid tokens-lib invalid-theme-id)] + (t/is (not (ctos/theme-active? status' invalid-theme-id)))) + ;; set-theme-active false removes non-existent id + (let [status' (cfo/set-theme-active all-invalid tokens-lib invalid-theme-id false)] + (t/is (not (ctos/theme-active? status' invalid-theme-id)))))))) diff --git a/common/test/common_tests/logic/token_test.cljc b/common/test/common_tests/logic/token_test.cljc index 0d1c462e2f..feca1b1c2c 100644 --- a/common/test/common_tests/logic/token_test.cljc +++ b/common/test/common_tests/logic/token_test.cljc @@ -888,3 +888,155 @@ (clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) + +;; Nil-safety: all public functions must be no-op with nil tokens-lib or tokens-status + +(t/deftest nil-tokens-lib-generate-move-token-set-test + (t/testing "generate-move-token-set is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes)] + (t/is (= changes + (clt/generate-move-token-set changes nil {:from-index 0 + :to-index 1 + :position :top})))))) + +(t/deftest nil-tokens-lib-generate-move-token-set-group-test + (t/testing "generate-move-token-set-group is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes)] + (t/is (= changes + (clt/generate-move-token-set-group changes nil {:from-index 0 + :to-index 1 + :position :top})))))) + +(t/deftest nil-tokens-lib-generate-delete-token-set-group-test + (t/testing "generate-delete-token-set-group is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes)] + (t/is (= changes + (clt/generate-delete-token-set-group changes nil ["group"])))))) + +(t/deftest nil-tokens-status-generate-activate-theme-test + (t/testing "generate-activate-theme is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-activate-theme changes nil tokens-lib (uuid/next))))))) + +(t/deftest nil-tokens-lib-generate-activate-theme-test + (t/testing "generate-activate-theme is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-activate-theme changes tokens-status nil (uuid/next))))))) + +(t/deftest nil-tokens-status-generate-deactivate-theme-test + (t/testing "generate-deactivate-theme is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-deactivate-theme changes nil tokens-lib (uuid/next))))))) + +(t/deftest nil-tokens-lib-generate-deactivate-theme-test + (t/testing "generate-deactivate-theme is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-deactivate-theme changes tokens-status nil (uuid/next))))))) + +(t/deftest nil-tokens-status-generate-set-theme-status-test + (t/testing "generate-set-theme-status is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-set-theme-status changes nil tokens-lib (uuid/next) true)))))) + +(t/deftest nil-tokens-lib-generate-set-theme-status-test + (t/testing "generate-set-theme-status is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-set-theme-status changes tokens-status nil (uuid/next) true)))))) + +(t/deftest nil-tokens-status-generate-toggle-theme-test + (t/testing "generate-toggle-theme is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-toggle-theme changes nil tokens-lib (uuid/next))))))) + +(t/deftest nil-tokens-lib-generate-toggle-theme-test + (t/testing "generate-toggle-theme is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-toggle-theme changes tokens-status nil (uuid/next))))))) + +(t/deftest nil-tokens-status-generate-set-enabled-token-set-test + (t/testing "generate-set-enabled-token-set is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-set-enabled-token-set changes nil tokens-lib (uuid/next) true)))))) + +(t/deftest nil-tokens-lib-generate-set-enabled-token-set-test + (t/testing "generate-set-enabled-token-set is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-set-enabled-token-set changes tokens-status nil (uuid/next) true)))))) + +(t/deftest nil-tokens-status-generate-toggle-token-set-test + (t/testing "generate-toggle-token-set is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-toggle-token-set changes nil tokens-lib (uuid/next))))))) + +(t/deftest nil-tokens-lib-generate-toggle-token-set-test + (t/testing "generate-toggle-token-set is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-toggle-token-set changes tokens-status nil (uuid/next))))))) + +(t/deftest nil-tokens-status-generate-toggle-token-set-group-test + (t/testing "generate-toggle-token-set-group is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-toggle-token-set-group changes nil tokens-lib ["foo" "bar"])))))) + +(t/deftest nil-tokens-lib-generate-toggle-token-set-group-test + (t/testing "generate-toggle-token-set-group is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-toggle-token-set-group changes tokens-status nil ["foo" "bar"])))))) + +(t/deftest nil-tokens-status-generate-sync-tokens-status-with-lib-test + (t/testing "generate-sync-tokens-status-with-lib is no-op with nil tokens-status" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-lib (tht/get-tokens-lib file)] + (t/is (= changes + (clt/generate-sync-tokens-status-with-lib changes nil tokens-lib)))))) + +(t/deftest nil-tokens-lib-generate-sync-tokens-status-with-lib-test + (t/testing "generate-sync-tokens-status-with-lib is no-op with nil tokens-lib" + (let [changes (pcb/empty-changes) + file (tht/sample-file-with-tokens) + tokens-status (tht/get-tokens-status file)] + (t/is (= changes + (clt/generate-sync-tokens-status-with-lib changes tokens-status nil)))))) diff --git a/frontend/playwright/data/workspace/get-file-layout-stroke-token-json b/frontend/playwright/data/workspace/get-file-layout-stroke-token-json index b690e5505c..2975acf029 100644 --- a/frontend/playwright/data/workspace/get-file-layout-stroke-token-json +++ b/frontend/playwright/data/workspace/get-file-layout-stroke-token-json @@ -134,6 +134,18 @@ "~:components-v2": true, "~:base-font-size": "16px" }, + "~:tokens-status": { + "~#penpot/tokens-status": { + "~:active-theme-ids": { + "~#set": [] + }, + "~:active-set-ids": { + "~#set": [ + "~u7c1a66f7-5186-8060-8007-6ce67f8f2592" + ] + } + } + }, "~:tokens-lib": { "~#penpot/tokens-lib": { "~:sets": { diff --git a/frontend/playwright/data/workspace/get-file-token-tooltip.json b/frontend/playwright/data/workspace/get-file-token-tooltip.json index 55a35d9d60..2474f4df79 100644 --- a/frontend/playwright/data/workspace/get-file-token-tooltip.json +++ b/frontend/playwright/data/workspace/get-file-token-tooltip.json @@ -53,6 +53,18 @@ } } }, + "~:tokens-status": { + "~#penpot/tokens-status": { + "~:active-theme-ids": { + "~#set": [] + }, + "~:active-set-ids": { + "~#set": [ + "~uf71719b3-63a1-8157-8008-40346917de8c" + ] + } + } + }, "~:tokens-lib": { "~#penpot/tokens-lib": { "~:sets": { @@ -373,7 +385,7 @@ } } }, - "~:id": "~u55608328-aed3-807a-8008-41341f2f7247", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:options": { "~:components-v2": true, "~:base-font-size": "16px" diff --git a/frontend/playwright/data/workspace/get-file-tokens-all-types.json b/frontend/playwright/data/workspace/get-file-tokens-all-types.json index 75700e00fa..17584ba7df 100644 --- a/frontend/playwright/data/workspace/get-file-tokens-all-types.json +++ b/frontend/playwright/data/workspace/get-file-tokens-all-types.json @@ -29,7 +29,7 @@ "~:revn": 3, "~:modified-at": "~m1779204621124", "~:vern": 0, - "~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:is-shared": false, "~:migrations": { "~#ordered-set": [ @@ -130,11 +130,23 @@ "~:name": "Page 1" } }, - "~:id": "~u9fb430ed-e1e9-81bc-8008-0b7ae978d9c4", + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", "~:options": { "~:components-v2": true, "~:base-font-size": "16px" }, + "~:tokens-status": { + "~#penpot/tokens-status": { + "~:active-theme-ids": { + "~#set": [] + }, + "~:active-set-ids": { + "~#set": [ + "~u1dc5cafc-6d57-808d-8008-0b7af8180cbd" + ] + } + } + }, "~:tokens-lib": { "~#penpot/tokens-lib": { "~:sets": { diff --git a/frontend/playwright/data/workspace/get-file-typography-tokens.json b/frontend/playwright/data/workspace/get-file-typography-tokens.json index 8e3497af70..0377a574a3 100644 --- a/frontend/playwright/data/workspace/get-file-typography-tokens.json +++ b/frontend/playwright/data/workspace/get-file-typography-tokens.json @@ -54,6 +54,19 @@ "~:components-v2": true, "~:base-font-size": "16px" }, + "~:tokens-status": { + "~#penpot/tokens-status": { + "~:active-theme-ids": { + "~#set": [] + }, + "~:active-set-ids": { + "~#set": [ + "~u7239ff52-c1d9-8014-8006-ae7ad49690e3", + "~u97915939-ccca-808f-8006-aeb3c391e0f4" + ] + } + } + }, "~:tokens-lib": { "~#penpot/tokens-lib": { "~:sets": { diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 6e6e16de6a..b2d79480a4 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -25,7 +25,6 @@ [app.main.data.style-dictionary :as sd] [app.main.data.tinycolor :as tinycolor] [app.main.data.tokenscript :as ts] - ;; [app.main.data.workspace :as udw] [app.main.data.workspace.colors :as wdc] [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] @@ -93,14 +92,13 @@ (ptk/reify ::update-shape-rotation ptk/WatchEvent (watch [_ _ _] - ;; TODOstatus see a way of avoiding the circular dependency of using here main.data.workspace - #_(when (number? value) + (when (number? value) (rx/of - (udw/trigger-bounding-box-cloaking shape-ids) - (udw/increase-rotation shape-ids value nil - {:page-id page-id - :ignore-touched true - :no-wasm? true}))))))) + (dwtr/trigger-bounding-box-cloaking shape-ids) + (dwtr/increase-rotation shape-ids value nil + {:page-id page-id + :ignore-touched true + :no-wasm? true}))))))) (defn update-stroke-width ([value shape-ids attributes] (update-stroke-width value shape-ids attributes nil)) diff --git a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs index b380ef3c21..f31a6b2ee8 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -350,7 +350,8 @@ tokens-lib (dsh/lookup-tokens-lib state) suffix (tr "workspace.tokens.duplicate-suffix")] - (when-let [token-set (ctob/duplicate-set id tokens-lib {:suffix suffix})] + (when-let [token-set (when tokens-lib + (ctob/duplicate-set id tokens-lib {:suffix suffix}))] (when id-ref (reset! id-ref (ctob/get-id token-set))) (let [changes (-> (pcb/empty-changes it) (pcb/with-library-data data) @@ -604,21 +605,22 @@ (lookup-token-set state set-id) (lookup-token-set state)) data (dsh/lookup-file-data state) - token (-> (dsh/lookup-tokens-lib state) - (ctob/get-token (ctob/get-id token-set) id)) - token' (->> (merge token params) - (into {}) - (ctob/make-token)) - token-type (:type token) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token (ctob/get-id token-set) - id - token'))] - (toggle-token-path (str (name token-type) "." (:name token))) - (rx/of (dch/commit-changes changes) - (ev/event (-> {::ev/name "edit-token" :type token-type} - (merge (meta it)))))))))) + tokens-lib (dsh/lookup-tokens-lib state)] + (when (and tokens-lib token-set) + (let [token (ctob/get-token tokens-lib (ctob/get-id token-set) id) + token' (->> (merge token params) + (into {}) + (ctob/make-token)) + token-type (:type token) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token (ctob/get-id token-set) + id + token'))] + (toggle-token-path (str (name token-type) "." (:name token))) + (rx/of (dch/commit-changes changes) + (ev/event (-> {::ev/name "edit-token" :type token-type} + (merge (meta it)))))))))))) (defn bulk-update-tokens [set-id token-ids type old-path new-path & {:keys [undo-group]}] @@ -658,19 +660,20 @@ ptk/WatchEvent (watch [it state _] (let [data (dsh/lookup-file-data state) + tokens-lib (dsh/lookup-tokens-lib state) token-set (if set-id (lookup-token-set state set-id) - (lookup-token-set state)) - token (-> (dsh/lookup-tokens-lib state) - (ctob/get-token (ctob/get-id token-set) token-id)) - token-type (:type token) + (lookup-token-set state))] + (when (and tokens-lib token-set) + (let [token (ctob/get-token tokens-lib (ctob/get-id token-set) token-id) + token-type (:type token) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/set-token set-id token-id nil))] - (rx/of (dch/commit-changes changes) - (ev/event (-> {::ev/name "delete-token" :type token-type} - (merge (meta it))))))))) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/set-token set-id token-id nil))] + (rx/of (dch/commit-changes changes) + (ev/event (-> {::ev/name "delete-token" :type token-type} + (merge (meta it))))))))))) (defn bulk-delete-tokens [set-id token-ids] diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 31348bb2d2..c88cdb401d 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -177,7 +177,7 @@ (when hint-name [:li {:class (stl/css :hint-line)} "(" (tr "workspace.libraries.connected-through") " " - [:span {:class (stl/css :hint-library-name)} hint-name ] + [:span {:class (stl/css :hint-library-name)} hint-name] ")"])])) (mf/defc sample-library-entry* @@ -359,29 +359,29 @@ {::mf/private true} [{:keys [is-shared linked-libraries shared-libraries on-change-tab]}] (let [file-id (mf/use-ctx ctx/current-file-id) - + ;; The summary of the current/local library ;; NOTE: we only need a snapshot of current library local-library (deref refs/workspace-data) summary (get-library-summary local-library) empty-library? (empty-library? summary) - + selected (h/use-shared-state mdc/colorpalette-selected-broadcast-key :recent) dependencies (mf/with-memo [shared-libraries] (into {} (map (juxt :id :library-file-ids) (vals shared-libraries)))) - + library-names (mf/with-memo [shared-libraries] (into {} (map (fn [{:keys [id name]}] [id name]) (vals shared-libraries)))) - + find-connected-to (mf/use-fn (mf/deps dependencies) (fn [library-id] (->> dependencies (keep (fn [[k v]] (when (contains? v library-id) k)))))) - + linked-libraries (mf/with-memo [linked-libraries find-connected-to library-names] (let [libs @@ -413,11 +413,11 @@ (sort-by-name) (mapcat (fn [{:keys [id] :as library}] (cons library (get nested-by-parent id []))))))) - + linked-libraries-ids (mf/with-memo [linked-libraries] (into #{} d/xf:map-id linked-libraries)) - + unlink-library (mf/use-fn (mf/deps file-id local-library) @@ -433,7 +433,7 @@ ;; we must reset it to the local library. (when (cfo/effective-tokens-source? local-library library-id) (st/emit! (dwtl/set-tokens-source (:id local-library))))))) - + import-tokens (mf/use-fn (mf/deps file-id) @@ -444,7 +444,7 @@ (st/emit! (modal/show :tokens/import-from-library {:file-id file-id :library-id library-id}))))) - + set-as-tokens-source (mf/use-fn (fn [event] @@ -452,18 +452,18 @@ (dom/get-data "library-id") (uuid/parse))] (st/emit! (dwtl/set-tokens-source library-id))))) - + on-delete-accept (mf/use-fn (mf/deps file-id) #(st/emit! (dwl/set-file-shared file-id false) (modal/show :libraries-dialog {:file-id file-id}))) - + on-delete-cancel (mf/use-fn (mf/deps file-id) #(st/emit! (modal/show :libraries-dialog {:file-id file-id}))) - + publish (mf/use-fn (mf/deps file-id) @@ -481,7 +481,7 @@ :on-cancel cancel-publish})) (publish-library)) (dom/blur! input-node)))) - + unpublish (mf/use-fn (mf/deps file-id) @@ -493,8 +493,8 @@ :on-accept on-delete-accept :on-cancel on-delete-cancel :count-libraries 1})))) - - go-to-shared + + go-to-shared (mf/use-fn (mf/deps on-change-tab) (fn [_] diff --git a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs index 41d1532359..0004502661 100644 --- a/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/sets/lists.cljs @@ -251,7 +251,7 @@ (fn [event] (dom/stop-propagation event) (when (fn? on-toggle) - (on-toggle (ctob/get-name set))))) + (on-toggle (ctob/get-id set))))) on-edit-submit' (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/tokens/tokens_source.scss b/frontend/src/app/main/ui/workspace/tokens/tokens_source.scss index 6dddcd3d85..ece7e1a7c3 100644 --- a/frontend/src/app/main/ui/workspace/tokens/tokens_source.scss +++ b/frontend/src/app/main/ui/workspace/tokens/tokens_source.scss @@ -37,4 +37,4 @@ border: $b-1 solid var(--color-foreground-secondary); padding: var(--sp-xxs) px2rem(6); border-radius: $br-6; -} \ No newline at end of file +} diff --git a/frontend/test/frontend_tests/logic/components_and_tokens.cljs b/frontend/test/frontend_tests/logic/components_and_tokens.cljs index 5ce23f5836..3e0bc8f579 100644 --- a/frontend/test/frontend_tests/logic/components_and_tokens.cljs +++ b/frontend/test/frontend_tests/logic/components_and_tokens.cljs @@ -5,16 +5,13 @@ ;; Copyright (c) KALEIDOS SUBSIDIARY SL (ns frontend-tests.logic.components-and-tokens (:require - [app.common.files.tokens :as cfo] [app.common.geom.point :as geom] [app.common.math :as mth] [app.common.test-helpers.components :as cthc] [app.common.test-helpers.compositions :as ctho] - [app.common.test-helpers.files :as cthf] [app.common.test-helpers.ids-map :as cthi] [app.common.test-helpers.shapes :as cths] [app.common.test-helpers.tokens :as ctht] - [app.common.types.file :as ctf] [app.common.types.tokens-lib :as ctob] [app.common.types.tokens-status :as ctos] [app.main.data.helpers :as dsh] diff --git a/frontend/test/frontend_tests/plugins/tokens_test.cljs b/frontend/test/frontend_tests/plugins/tokens_test.cljs index f91b22f1dc..9dd5631f84 100644 --- a/frontend/test/frontend_tests/plugins/tokens_test.cljs +++ b/frontend/test/frontend_tests/plugins/tokens_test.cljs @@ -240,6 +240,7 @@ set-id (cthi/new-id! :set) dup-id (cthi/new-id! :dup)] (with-redefs [u/locate-tokens-lib (constantly nil) + u/check-editable-tokens (constantly nil) dwtl/duplicate-token-set (mock/stub (fn [id {:keys [id-ref]}] (t/is (= set-id id)) @@ -257,6 +258,7 @@ set-id (cthi/new-id! :set) captured (atom [])] (with-redefs [u/locate-tokens-lib (constantly nil) + u/check-editable-tokens (constantly nil) u/locate-token-theme (fn [_file _theme] (ctob/make-token-theme :id theme-id @@ -281,6 +283,7 @@ token-id (cthi/new-id! :token) captured (atom nil)] (with-redefs [u/locate-tokens-lib (constantly nil) + u/check-editable-tokens (constantly nil) u/locate-token (constantly {:id token-id :name "font.primary" :type :font-family @@ -357,6 +360,7 @@ (with-redefs [u/locate-tokens-lib (constantly nil) u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/check-editable-tokens (constantly nil) u/not-valid (fn [_ code value] (swap! invalid conj [code value])) dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) st/emit! (fn ([event] (swap! emitted conj event) nil) @@ -378,6 +382,7 @@ (with-redefs [u/locate-tokens-lib (constantly nil) u/locate-token-set (fn [_ id] (when (= id set-id) token-set)) u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/check-editable-tokens (constantly nil) u/not-valid (fn [_ code value] (swap! invalid conj [code value])) dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) st/emit! (fn ([event] (swap! emitted conj event) nil) @@ -395,16 +400,17 @@ theme (ctob/make-token-theme :id theme-id :group "mode" :name "Light") emitted (atom []) invalid (atom [])] - (with-redefs [u/locate-tokens-lib (constantly nil) - u/locate-token-set (constantly nil) - u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) - u/not-valid (fn [_ code value] (swap! invalid conj [code value])) - u/handle-error (fn [plugin-id] - (fn [cause] - (u/not-valid plugin-id :error (str cause)))) - dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) - st/emit! (fn ([event] (swap! emitted conj event) nil) - ([event & _] (swap! emitted conj event) nil))] + (with-redefs [u/locate-tokens-lib (constantly nil) + u/locate-token-set (constantly nil) + u/throw-validation-errors? (constantly true) + u/locate-token-theme (fn [_ id] (when (= id theme-id) theme)) + u/check-editable-tokens (constantly nil) + u/handle-error (fn [plugin-id] + (fn [cause] + (u/not-valid plugin-id :error (str cause)))) + dwtl/update-token-theme (fn [id theme] {:id id :theme theme}) + st/emit! (fn ([event] (swap! emitted conj event) nil) + ([event & _] (swap! emitted conj event) nil))] (let [theme-proxy (ptok/token-theme-proxy plugin-id file-id theme-id)] (try (.addSet theme-proxy 42) (catch :default e (swap! invalid conj e))) (try (.removeSet theme-proxy nil) (catch :default e (swap! invalid conj e))) diff --git a/frontend/test/frontend_tests/tokens/helpers/state.cljs b/frontend/test/frontend_tests/tokens/helpers/state.cljs index 6175926687..e03b15b099 100644 --- a/frontend/test/frontend_tests/tokens/helpers/state.cljs +++ b/frontend/test/frontend_tests/tokens/helpers/state.cljs @@ -32,12 +32,14 @@ (ptk/reify ::end+ ptk/WatchEvent (watch [_ state _] - (let [data (dsh/lookup-file-data state)] - (->> (cfo/get-tokens-in-active-sets - (cfo/get-tokens-status data) - (cfo/get-tokens-lib data)) - (sd/resolve-tokens) - (rx/mapcat #(rx/of (end)))))))) + (let [data (dsh/lookup-file-data state) + tokens-status (cfo/get-tokens-status data) + tokens-lib (cfo/get-tokens-lib data)] + (if (and tokens-status tokens-lib) + (->> (cfo/get-tokens-in-active-sets tokens-status tokens-lib) + (sd/resolve-tokens) + (rx/mapcat #(rx/of (end)))) + (rx/of (end))))))) (defn stop-on "Helper function to be used with async version of run-store. diff --git a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs index 8fd5bc83ef..ee2d94a31f 100644 --- a/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs +++ b/frontend/test/frontend_tests/tokens/logic/token_data_test.cljs @@ -33,7 +33,35 @@ :page-label :page-1 :lib-fn #(ctob/add-set % (ctob/make-token-set :id (cthi/new-id! :test-token-set) :name "Set A")) - :status-fn #(ctos/set-tokens-status % #{} #{cthi/id :test-token-set}))) + :status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :test-token-set)}))) + +(defn setup-file-with-token-lib-and-theme + [theme-id] + (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set) + :name "Set A")) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "Theme A" + :group "default"))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{(cthi/id :test-token-set)}))) + +(defn setup-file-with-token-lib-and-token + [] + (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :test-token-set) + :name "Set A")) + (ctob/add-token (cthi/id :test-token-set) + (ctob/make-token :id (cthi/new-id! :color.primary) + :name "color.primary" + :type :color + :value "#000000"))) + :status-fn #(ctos/set-tokens-status % #{} #{(cthi/id :test-token-set)}))) (t/deftest add-set (t/async @@ -54,6 +82,20 @@ (t/is (= (count sets') 2)) (t/is (some? set-b'))))))))) +(t/deftest add-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/create-token-set (ctob/make-token-set :name "Set A"))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + (t/deftest rename-set (t/async done @@ -75,6 +117,21 @@ (t/is (= (count sets') 1)) (t/is (some? set-a'))))))))) +(t/deftest rename-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + set-a (ctob/make-token-set :name "Set A") + events [(dwtl/rename-token-set set-a "Set A updated")]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + (t/deftest duplicate-set (t/async done @@ -109,6 +166,20 @@ (t/testing "Token lib contains one set" (t/is (= (count sets) 1))))))))) +(t/deftest duplicate-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/duplicate-token-set (uuid/next))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + (t/deftest delete-set (t/async done @@ -126,6 +197,20 @@ (t/testing "Set has been deleted" (t/is (= (count sets') 0))))))))) +(t/deftest delete-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/delete-token-set (uuid/next))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + (t/deftest set-tokens-source (t/async done @@ -153,3 +238,660 @@ (t/testing "tokens-source is restored to nil" (t/is (nil? (:tokens-source file-data'')))))) (tohs/stop-on ::dwu/undo))))) + +(t/deftest set-tokens-source-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + library-id (uuid/next)] + + (tohs/run-store-async + store done + [(dwtl/set-tokens-source library-id)] + (fn [new-state] + (let [file-data' (-> (ths/get-file-from-state new-state) :data)] + (t/testing "tokens-source is set even without tokens-lib" + (t/is (= library-id (:tokens-source file-data')))))))))) + +;; ========================================================================== +;; Token Themes +;; ========================================================================== + +(t/deftest create-token-theme + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id + :name "Theme B" + :group "default"))]] + + (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') + theme' (ctob/get-theme tokens-lib' theme-id)] + + (t/testing "Theme has been created" + (t/is (some? theme')) + (t/is (= "Theme B" (:name theme')))))))))) + +(t/deftest create-token-theme-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id + :name "Theme A" + :group "default"))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest update-token-theme + (t/async + done + (let [theme-id (uuid/next) + file (setup-file-with-token-lib-and-theme theme-id) + store (ths/setup-store file) + events [(dwtl/update-token-theme theme-id + (ctob/make-token-theme :id theme-id + :name "Theme A renamed" + :group "default"))]] + + (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') + theme' (ctob/get-theme tokens-lib' theme-id)] + + (t/testing "Theme has been renamed" + (t/is (= "Theme A renamed" (:name theme')))))))))) + +(t/deftest update-token-theme-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/update-token-theme theme-id + (ctob/make-token-theme :id theme-id + :name "Theme A" + :group "default"))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest set-token-theme-active + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/create-token-theme (ctob/make-token-theme :id theme-id + :name "Theme B" + :group "default")) + (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) + tokens-status (ctho/get-tokens-status file')] + + (t/testing "Theme has been activated" + (t/is (ctos/theme-active? tokens-status theme-id))))))))) + +(t/deftest set-token-theme-active-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/set-token-theme-active theme-id true)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest toggle-token-theme-active + (t/async + done + (let [theme-id (uuid/next) + file (setup-file-with-token-lib-and-theme theme-id) + store (ths/setup-store file) + 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) + tokens-status (ctho/get-tokens-status file')] + + (t/testing "Theme has been deactivated" + (t/is (not (ctos/theme-active? tokens-status theme-id)))))))))) + +(t/deftest toggle-token-theme-active-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/toggle-token-theme-active theme-id)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest delete-token-theme + (t/async + done + (let [theme-id (uuid/next) + file (setup-file-with-token-lib-and-theme theme-id) + store (ths/setup-store file) + events [(dwtl/delete-token-theme theme-id)]] + + (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') + theme' (ctob/get-theme tokens-lib' theme-id)] + + (t/testing "Theme has been deleted" + (t/is (nil? theme'))))))))) + +(t/deftest delete-token-theme-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + theme-id (uuid/next) + events [(dwtl/delete-token-theme theme-id)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +;; ========================================================================== +;; Token Set Operations +;; ========================================================================== + +(t/deftest set-enabled-token-set + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + events [(dwtl/set-enabled-token-set (cthi/id :test-token-set) true)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + tokens-status (ctho/get-tokens-status file')] + + (t/testing "Set has been enabled" + (t/is (ctos/set-active? tokens-status (cthi/id :test-token-set)))))))))) + +(t/deftest set-enabled-token-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/set-enabled-token-set (uuid/next) true)]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest toggle-token-set + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + events [(dwtl/toggle-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-status (ctho/get-tokens-status file')] + + (t/testing "Set has been toggled (deactivated)" + (t/is (not (ctos/set-active? tokens-status (cthi/id :test-token-set))))))))))) + +(t/deftest toggle-token-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/toggle-token-set (uuid/next))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest toggle-token-set-group + (t/async + done + (let [file (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "group/set-a")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b) + :name "group/set-b"))) + :status-fn #(ctos/set-tokens-status % #{} #{})) + store (ths/setup-store file) + events [(dwtl/toggle-token-set-group ["group"])]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file' (ths/get-file-from-state new-state) + tokens-status (ctho/get-tokens-status file')] + + (t/testing "Set group has been toggled (activated)" + (t/is (ctos/set-active? tokens-status (cthi/id :set-a))) + (t/is (ctos/set-active? tokens-status (cthi/id :set-b)))))))))) + +(t/deftest toggle-token-set-group-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/toggle-token-set-group ["group"])]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest delete-token-set-group + (t/async + done + (let [file (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "group/set-a")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b) + :name "group/set-b")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c) + :name "other/set-c")))) + store (ths/setup-store file) + events [(dwtl/delete-token-set-group ["group"])]] + + (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')] + + (t/testing "Group sets deleted, other set remains" + (t/is (nil? (ctob/get-set-by-name tokens-lib' "group/set-a"))) + (t/is (nil? (ctob/get-set-by-name tokens-lib' "group/set-b"))) + (t/is (some? (ctob/get-set-by-name tokens-lib' "other/set-c")))))))))) + +(t/deftest delete-token-set-group-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/delete-token-set-group ["group"])]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest rename-token-set-group + (t/async + done + (let [file (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(ctob/add-set % (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "old-group/set-a"))) + store (ths/setup-store file) + events [(dwtl/rename-token-set-group ["old-group"] "new-group")]] + + (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')] + + (t/testing "Set group has been renamed" + (t/is (some? (ctob/get-set-by-name tokens-lib' "new-group/set-a"))) + (t/is (nil? (ctob/get-set-by-name tokens-lib' "old-group/set-a")))))))))) + +(t/deftest rename-token-set-group-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/rename-token-set-group ["old-group"] "new-group")]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +;; ========================================================================== +;; Drop Operations +;; ========================================================================== + +(t/deftest drop-token-set-group + (t/async + done + (let [file (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "foo/foo")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b) + :name "bar/bar")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c) + :name "baz/baz")))) + store (ths/setup-store file) + events [(dwtl/drop-token-set-group {:from-index 2 + :to-index 0 + :position :top})]] + + (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-set-names tokens-lib')] + + (t/testing "Set groups have been reordered" + (t/is (= ["bar/bar" "foo/foo" "baz/baz"] (vec sets')))))))))) + +(t/deftest drop-token-set-group-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/drop-token-set-group {:from-index 0 + :to-index 1 + :position :top})]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest drop-token-set + (t/async + done + (let [file (ctho/sample-file-with-tokens + :file-id :file-1 + :page-label :page-1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a) + :name "foo")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-b) + :name "bar")) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-c) + :name "baz")))) + store (ths/setup-store file) + events [(dwtl/drop-token-set {:from-index 0 + :to-index 2 + :position :bot})]] + + (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-set-names tokens-lib')] + + (t/testing "Token sets have been reordered" + (t/is (= ["bar" "baz" "foo"] (vec sets')))))))))) + +(t/deftest drop-token-set-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/drop-token-set {:from-index 0 + :to-index 1 + :position :top})]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +;; ========================================================================== +;; Token CRUD +;; ========================================================================== + +(t/deftest create-token-in-set + (t/async + done + (let [file (setup-file-with-token-lib) + store (ths/setup-store file) + token-id (cthi/new-id! :color.primary) + events [(dwtl/create-token (cthi/id :test-token-set) + (ctob/make-token :id token-id + :name "color.primary" + :type :color + :value "#000000"))]] + + (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') + token (ctob/get-token tokens-lib' (cthi/id :test-token-set) + (cthi/id :color.primary))] + + (t/testing "Token has been created in set" + (t/is (some? token))))))))) + +(t/deftest create-token-no-set + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + token-id (cthi/new-id! :color.primary) + events [(dwtl/create-token (ctob/make-token :id token-id + :name "color.primary" + :type :color + :value "#000000"))]] + + (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')] + + (t/testing "Global set has been created with the token" + (t/is (some? tokens-lib')) + (t/is (some? (ctob/get-set-by-name tokens-lib' "Global")))))))))) + +(t/deftest update-token + (t/async + done + (let [file (setup-file-with-token-lib-and-token) + store (ths/setup-store file) + events [(dwtl/update-token (cthi/id :test-token-set) + (cthi/id :color.primary) + {:value "#ffffff" + :name "color.primary.updated"})]] + + (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') + token' (ctob/get-token tokens-lib' (cthi/id :test-token-set) + (cthi/id :color.primary))] + + (t/testing "Token has been updated" + (t/is (= "color.primary.updated" (:name token'))) + (t/is (= "#ffffff" (:value token')))))))))) + +(t/deftest update-token-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/update-token (uuid/next) (uuid/next) {:value "#ffffff"})]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest delete-token + (t/async + done + (let [file (setup-file-with-token-lib-and-token) + store (ths/setup-store file) + events [(dwtl/delete-token (cthi/id :test-token-set) + (cthi/id :color.primary))]] + + (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') + token' (ctob/get-token tokens-lib' (cthi/id :test-token-set) + (cthi/id :color.primary))] + + (t/testing "Token has been deleted" + (t/is (nil? token'))))))))) + +(t/deftest delete-token-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/delete-token (uuid/next) (uuid/next))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +(t/deftest duplicate-token + (t/async + done + (let [file (setup-file-with-token-lib-and-token) + store (ths/setup-store file) + events [(dwtl/set-selected-token-set-id (cthi/id :test-token-set)) + (dwtl/duplicate-token (cthi/id :color.primary))]] + + (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') + tokens (ctob/get-tokens tokens-lib' (cthi/id :test-token-set))] + + (t/testing "Token has been duplicated" + (t/is (= 2 (count tokens)))))))))) + +(t/deftest duplicate-token-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + events [(dwtl/duplicate-token (uuid/next))]] + + (tohs/run-store-async + store done events + (fn [new-state] + (let [file-data (-> (ths/get-file-from-state new-state) :data)] + (t/testing "No crash when file has no tokens-lib" + (t/is (some? file-data))))))))) + +;; ========================================================================== +;; Import Tokens Lib +;; ========================================================================== + +(t/deftest import-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + new-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Imported Set"))) + events [(dwtl/import-tokens-lib new-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')] + + (t/testing "Tokens lib has been imported" + (t/is (some? tokens-lib')) + (t/is (some? (ctob/get-set-by-name tokens-lib' "Imported Set")))))))))) + +(t/deftest import-tokens-lib-no-tokens-lib + (t/async + done + (let [file (setup-file) + store (ths/setup-store file) + new-lib (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :name "Imported Set"))) + events [(dwtl/import-tokens-lib new-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')] + (t/testing "Tokens lib has been imported into file without existing lib" + (t/is (some? tokens-lib')) + (t/is (some? (ctob/get-set-by-name tokens-lib' "Imported Set"))))))))))