From 7fee82409a496f9fb2dac19e331f2f3ea62ebcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 21 Aug 2026 18:29:43 +0200 Subject: [PATCH] :wrench: Add integration tests for tokens in external libs --- .../src/app/common/files/changes_builder.cljc | 7 +- common/src/app/common/files/tokens.cljc | 11 +- common/src/app/common/logic/tokens.cljc | 20 +- .../src/app/common/test_helpers/tokens.cljc | 4 + .../test/common_tests/logic/token_test.cljc | 219 +++++++++++++++--- .../workspace/get-file-libraries-tokens.json | 23 ++ .../data/workspace/get-file-with-lib.json | 60 +++++ frontend/playwright/ui/pages/WorkspacePage.js | 7 +- .../ui/specs/tokens/libraries.spec.js | 159 +++++++++++++ .../app/main/data/workspace/libraries.cljs | 18 +- .../data/workspace/tokens/library_edit.cljs | 2 + 11 files changed, 477 insertions(+), 53 deletions(-) create mode 100644 frontend/playwright/data/workspace/get-file-libraries-tokens.json create mode 100644 frontend/playwright/data/workspace/get-file-with-lib.json create mode 100644 frontend/playwright/ui/specs/tokens/libraries.spec.js diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 57b6082eff..b95402d0ae 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -1052,9 +1052,10 @@ (defn set-tokens-status ([changes tokens-status] (assert-library! changes) - (assert (ctos/tokens-status? tokens-status)) - (let [theme-ids (ctos/get-active-theme-ids tokens-status) - set-ids (ctos/get-active-set-ids tokens-status) + (assert (or (ctos/tokens-status? tokens-status) + (nil? tokens-status))) + (let [theme-ids (if tokens-status (ctos/get-active-theme-ids tokens-status) #{}) + set-ids (if tokens-status (ctos/get-active-set-ids tokens-status) #{}) library-data (::library-data (meta changes)) prev-tokens-status (cfo/get-tokens-status library-data) prev-theme-ids (if prev-tokens-status (ctos/get-active-theme-ids prev-tokens-status) #{}) diff --git a/common/src/app/common/files/tokens.cljc b/common/src/app/common/files/tokens.cljc index ba004a716b..4f5e41fd62 100644 --- a/common/src/app/common/files/tokens.cljc +++ b/common/src/app/common/files/tokens.cljc @@ -462,6 +462,13 @@ :always (update :tokens-status #(or % (ctos/make-tokens-status))))) +(defn ensure-tokens-status + "Ensure file-data has a :tokens-status, creating it empty if necessary." + [file-data] + (cond-> file-data + (nil? (:tokens-status file-data)) + (update :tokens-status #(or % (ctos/make-tokens-status))))) + (defn get-tokens-source "Return the current value of :tokens-source attribute." [file-data] @@ -476,7 +483,9 @@ (defn set-tokens-source [file-data tokens-source] (assert (or (nil? tokens-source) (uuid? tokens-source)) "expected nil or valid uuid") - (assoc file-data :tokens-source tokens-source)) + (if (nil? tokens-source) + (dissoc file-data :tokens-source) + (assoc file-data :tokens-source tokens-source))) (defn effective-tokens-source? "Returns true if the given id is the current tokens source of the file-data." diff --git a/common/src/app/common/logic/tokens.cljc b/common/src/app/common/logic/tokens.cljc index 6b7d7a7172..b09145ec1c 100644 --- a/common/src/app/common/logic/tokens.cljc +++ b/common/src/app/common/logic/tokens.cljc @@ -9,7 +9,24 @@ [app.common.files.changes :as ch] [app.common.files.changes-builder :as pcb] [app.common.files.tokens :as cfo] - [app.common.types.tokens-lib :as ctob])) + [app.common.types.file :as ctf] + [app.common.types.tokens-lib :as ctob] + [app.common.types.tokens-status :as ctos])) + +;; Tokens source + +(defn generate-set-tokens-source + "Create changes for setting the tokens source of a file to `library`, + copying the library's tokens status. If the library is nil, the tokens source + is removed and the tokens status is cleared." + [changes library] + (let [library-id (:id library) + library-tokens-status (if library + (-> library ctf/file-data cfo/get-tokens-status) + (ctos/make-tokens-status))] + (-> changes + (pcb/set-tokens-source library-id) + (pcb/set-tokens-status library-tokens-status)))) ;; Tokens lib @@ -190,3 +207,4 @@ (if tokens-lib (update-tokens-status changes tokens-status cfo/sync-tokens-status-with-lib tokens-lib) changes)) + diff --git a/common/src/app/common/test_helpers/tokens.cljc b/common/src/app/common/test_helpers/tokens.cljc index 13d88234d1..23a0550a4a 100644 --- a/common/src/app/common/test_helpers/tokens.cljc +++ b/common/src/app/common/test_helpers/tokens.cljc @@ -18,6 +18,10 @@ [app.common.types.token :as cto] [app.common.types.tokens-lib :as ctob])) +(defn get-tokens-source + [file] + (-> file (ctf/file-data) (cfo/get-tokens-source))) + (defn get-tokens-lib [file] (-> file (ctf/file-data) (cfo/get-tokens-lib))) diff --git a/common/test/common_tests/logic/token_test.cljc b/common/test/common_tests/logic/token_test.cljc index feca1b1c2c..4ff3d97a10 100644 --- a/common/test/common_tests/logic/token_test.cljc +++ b/common/test/common_tests/logic/token_test.cljc @@ -11,6 +11,7 @@ [app.common.test-helpers.files :as thf] [app.common.test-helpers.ids-map :as thi] [app.common.test-helpers.tokens :as tht] + [app.common.types.file :as ctf] [app.common.types.tokens-lib :as ctob] [app.common.types.tokens-status :as ctos] [app.common.uuid :as uuid] @@ -18,6 +19,166 @@ (t/use-fixtures :each thi/test-fixture) +;; Tokens Source + +(t/deftest generate-set-tokens-source-test + (t/testing "setting a library as tokens source copies its tokens status" + (let [set-id (uuid/next) + theme-id (uuid/next) + token-id (uuid/next) + library-file (tht/sample-file-with-tokens + :file-id :library + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name "core")) + (ctob/add-token set-id (ctob/make-token {:id token-id + :name "color.red" + :value "#ff0000" + :type :color})) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "light" + :group "main" + :sets #{"core"}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id})) + file (thf/sample-file :file1) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (ctf/file-data file)) + (clt/generate-set-tokens-source library-file)) + redo (thf/apply-changes file changes) + undo (thf/apply-undo-changes redo changes) + library-tokens-status (tht/get-tokens-status library-file) + redo-tokens-source (tht/get-tokens-source redo) + redo-tokens-status (tht/get-tokens-status redo) + undo-tokens-source (tht/get-tokens-source undo) + undo-tokens-status (tht/get-tokens-status undo)] + + ;; Redo: source and status copied from library + (t/is (= (:id library-file) redo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids library-tokens-status) + (ctos/get-active-theme-ids redo-tokens-status))) + (t/is (= (ctos/get-active-set-ids library-tokens-status) + (ctos/get-active-set-ids redo-tokens-status))) + + ;; Undo: source and status restored to the original empty state + (t/is (nil? undo-tokens-source)) + (t/is (= #{} (ctos/get-active-theme-ids undo-tokens-status))) + (t/is (= #{} (ctos/get-active-set-ids undo-tokens-status))))) + + (t/testing "any previous tokens status is replaced by the library's tokens status" + (let [set-id (uuid/next) + theme-id (uuid/next) + token-id (uuid/next) + other-set-id (uuid/next) + library-file (tht/sample-file-with-tokens + :file-id :library + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name "core")) + (ctob/add-token set-id (ctob/make-token {:id token-id + :name "color.red" + :value "#ff0000" + :type :color})) + (ctob/add-theme (ctob/make-token-theme :id theme-id + :name "light" + :group "main" + :sets #{"core"}))) + :status-fn #(ctos/set-tokens-status % #{theme-id} #{set-id})) + file (tht/sample-file-with-tokens + :file-id :file1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id other-set-id + :name "other"))) + :status-fn #(ctos/set-tokens-status % #{} #{other-set-id})) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (ctf/file-data file)) + (clt/generate-set-tokens-source library-file)) + redo (thf/apply-changes file changes) + undo (thf/apply-undo-changes redo changes) + file-tokens-status (tht/get-tokens-status file) + library-tokens-status (tht/get-tokens-status library-file) + redo-tokens-source (tht/get-tokens-source redo) + redo-tokens-status (tht/get-tokens-status redo) + undo-tokens-source (tht/get-tokens-source undo) + undo-tokens-status (tht/get-tokens-status undo)] + + ;; Redo: source and status copied from library, replacing the previous status + (t/is (= (:id library-file) redo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids library-tokens-status) + (ctos/get-active-theme-ids redo-tokens-status))) + (t/is (= (ctos/get-active-set-ids library-tokens-status) + (ctos/get-active-set-ids redo-tokens-status))) + + ;; Undo: source and status restored to the original state + (t/is (nil? undo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids file-tokens-status) + (ctos/get-active-theme-ids undo-tokens-status))) + (t/is (= (ctos/get-active-set-ids file-tokens-status) + (ctos/get-active-set-ids undo-tokens-status))))) + + (t/testing "if the library is nil, the tokens source is cleared and the tokens status is reset to empty" + (let [set-id (uuid/next) + file (tht/sample-file-with-tokens + :file-id :file1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name "one set"))) + :status-fn #(ctos/set-tokens-status % #{} #{set-id})) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (ctf/file-data file)) + (clt/generate-set-tokens-source nil)) + redo (thf/apply-changes file changes) + undo (thf/apply-undo-changes redo changes) + file-tokens-status (tht/get-tokens-status file) + redo-tokens-source (tht/get-tokens-source redo) + redo-tokens-status (tht/get-tokens-status redo) + undo-tokens-source (tht/get-tokens-source undo) + undo-tokens-status (tht/get-tokens-status undo)] + + ;; Redo: source and status copied from library, replacing the previous status + (t/is (= nil redo-tokens-source)) + (t/is (= #{} (ctos/get-active-theme-ids redo-tokens-status))) + (t/is (= #{} (ctos/get-active-set-ids redo-tokens-status))) + + ;; Undo: source and status restored to the original state + (t/is (nil? undo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids file-tokens-status) + (ctos/get-active-theme-ids undo-tokens-status))) + (t/is (= (ctos/get-active-set-ids file-tokens-status) + (ctos/get-active-set-ids undo-tokens-status))))) + + (t/testing "if the library is the same file, the source is set to self and the status is the same one" + (let [set-id (uuid/next) + file (tht/sample-file-with-tokens + :file-id :file1 + :lib-fn #(-> % + (ctob/add-set (ctob/make-token-set :id set-id + :name "one set"))) + :status-fn #(ctos/set-tokens-status % #{} #{set-id})) + changes (-> (pcb/empty-changes) + (pcb/with-library-data (ctf/file-data file)) + (clt/generate-set-tokens-source file)) + redo (thf/apply-changes file changes) + undo (thf/apply-undo-changes redo changes) + file-tokens-status (tht/get-tokens-status file) + redo-tokens-source (tht/get-tokens-source redo) + redo-tokens-status (tht/get-tokens-status redo) + undo-tokens-source (tht/get-tokens-source undo) + undo-tokens-status (tht/get-tokens-status undo)] + + ;; Redo: source and status copied from library, replacing the previous status + (t/is (= (:id file) redo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids file-tokens-status) + (ctos/get-active-theme-ids redo-tokens-status))) + (t/is (= (ctos/get-active-set-ids file-tokens-status) + (ctos/get-active-set-ids redo-tokens-status))) + + ;; Undo: source and status restored to the original state + (t/is (nil? undo-tokens-source)) + (t/is (= (ctos/get-active-theme-ids file-tokens-status) + (ctos/get-active-theme-ids undo-tokens-status))) + (t/is (= (ctos/get-active-set-ids file-tokens-status) + (ctos/get-active-set-ids undo-tokens-status)))))) + ;; Tokens lib (t/deftest generate-move-token-set-test @@ -284,7 +445,7 @@ (ctob/add-set (ctob/make-token-set :id set-c-id :name "other/set-c")))) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-delete-token-set-group tokens-lib ["group"])) redo (thf/apply-changes file changes) @@ -308,7 +469,7 @@ (ctob/add-set (ctob/make-token-set :id set-b-id :name "group/set-b")))) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-delete-token-set-group tokens-lib ["group"])) redo (thf/apply-changes file changes) @@ -327,7 +488,7 @@ :lib-fn #(ctob/add-set % (ctob/make-token-set :name "other/set-a"))) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-delete-token-set-group tokens-lib ["nonexistent"]))] (t/is (= [] (:redo-changes changes)))))) @@ -340,7 +501,7 @@ :name "foo" :group "main")))) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-theme theme-id nil)) redo (thf/apply-changes file changes) @@ -359,7 +520,7 @@ :group "main") file (tht/sample-file-with-tokens) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-theme theme-id theme)) redo (thf/apply-changes file changes) redo-lib (tht/get-tokens-lib redo) @@ -380,7 +541,7 @@ :lib-fn #(ctob/add-theme % prev-theme)) new-theme-name "foo1" changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-theme theme-id (ctob/rename prev-theme new-theme-name))) redo (thf/apply-changes file changes) redo-lib (tht/get-tokens-lib redo) @@ -406,7 +567,7 @@ (ctob/add-set token-set))) theme' (assoc theme :sets #{set-name}) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-theme theme-id theme')) changed-file (-> file (thf/apply-changes changes) @@ -430,7 +591,7 @@ :value "red" :type :color})))) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token set-id token-id nil)) redo (thf/apply-changes file changes) @@ -451,7 +612,7 @@ :lib-fn #(-> % (ctob/add-set (ctob/make-token-set :id set-id :name set-name)))) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token set-id (:id token) token)) redo (thf/apply-changes file changes) @@ -477,7 +638,7 @@ :name set-name)) (ctob/add-token set-id prev-token))) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token set-id (:id prev-token) token)) redo (thf/apply-changes file changes) @@ -495,7 +656,7 @@ file (tht/sample-file-with-tokens :lib-fn #(ctob/add-set % (ctob/make-token-set :id set-id :name set-name))) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-set set-id nil)) redo (thf/apply-changes file changes) @@ -512,7 +673,7 @@ token-set (ctob/make-token-set :id set-id :name set-name) file (tht/sample-file-with-tokens) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-set set-id token-set)) redo (thf/apply-changes file changes) @@ -532,7 +693,7 @@ new-set-name "foo1" changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (pcb/set-token-set set-id (ctob/rename token-set new-set-name))) redo (thf/apply-changes file changes) @@ -563,7 +724,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-activate-theme tokens-status tokens-lib theme-id)) file' (thf/apply-changes file changes) tokens-status' (tht/get-tokens-status file') @@ -587,7 +748,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-activate-theme tokens-status tokens-lib theme-id))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -604,7 +765,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-deactivate-theme tokens-status tokens-lib theme-id)) file' (thf/apply-changes file changes) tokens-status' (tht/get-tokens-status file') @@ -626,7 +787,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-deactivate-theme tokens-status tokens-lib theme-id))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -642,7 +803,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-set-theme-status tokens-status tokens-lib theme-id true)) file' (thf/apply-changes file changes) tokens-status' (tht/get-tokens-status file') @@ -663,7 +824,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-set-theme-status tokens-status tokens-lib theme-id false))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -680,7 +841,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-theme tokens-status tokens-lib theme-id)) file' (thf/apply-changes file changes) tokens-status' (tht/get-tokens-status file') @@ -701,7 +862,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-theme tokens-status tokens-lib (uuid/next)))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -720,7 +881,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-set-enabled-token-set tokens-status tokens-lib set-id true)) redo (thf/apply-changes file changes) @@ -742,7 +903,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-set-enabled-token-set tokens-status tokens-lib set-id true))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -761,7 +922,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-token-set tokens-status tokens-lib set-id)) redo (thf/apply-changes file changes) @@ -788,7 +949,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-token-set tokens-status tokens-lib (uuid/next)))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -809,7 +970,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-token-set-group tokens-status tokens-lib ["foo" "bar"])) redo (thf/apply-changes file changes) @@ -843,7 +1004,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-toggle-token-set-group tokens-status tokens-lib ["foo" "nonexistent"]))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) @@ -858,7 +1019,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib)) file' (thf/apply-changes file changes) tokens-status' (tht/get-tokens-status file') @@ -884,7 +1045,7 @@ tokens-status (tht/get-tokens-status file) tokens-lib (tht/get-tokens-lib file) changes (-> (pcb/empty-changes) - (pcb/with-library-data (:data file)) + (pcb/with-library-data (ctf/file-data file)) (clt/generate-sync-tokens-status-with-lib tokens-status tokens-lib))] (t/is (= [] (:redo-changes changes))) (t/is (= [] (:undo-changes changes)))))) diff --git a/frontend/playwright/data/workspace/get-file-libraries-tokens.json b/frontend/playwright/data/workspace/get-file-libraries-tokens.json new file mode 100644 index 0000000000..5163f45542 --- /dev/null +++ b/frontend/playwright/data/workspace/get-file-libraries-tokens.json @@ -0,0 +1,23 @@ +[ + { + "~:features": { + "~#set": [ + "layout/grid", + "styles/v2", + "fdata/pointer-map", + "fdata/objects-map", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:is-indirect": false, + "~:name": "published component", + "~:revn": 3, + "~:modified-at": "~m1730103592325", + "~:vern": 0, + "~:id": "~uc7ce0794-0992-8105-8004-38f280443849", + "~:project-id": "~u3622460c-3408-81e2-8005-2fc9059741e0", + "~:synced-at": "~m1730103575856", + "~:created-at": "~m1730101410834" + } +] diff --git a/frontend/playwright/data/workspace/get-file-with-lib.json b/frontend/playwright/data/workspace/get-file-with-lib.json new file mode 100644 index 0000000000..b3133a6722 --- /dev/null +++ b/frontend/playwright/data/workspace/get-file-with-lib.json @@ -0,0 +1,60 @@ +{ + "~:features": { + "~#set": [ + "fdata/path-data", + "plugins/runtime", + "design-tokens/v1", + "variants/v1", + "layout/grid", + "styles/v2", + "fdata/objects-map", + "tokens/numeric-input", + "render-wasm/v1", + "components/v2", + "fdata/shape-data-type" + ] + }, + "~:team-id": "~u5519794a-7293-802b-8008-16c227930902", + "~:permissions": { + "~:type": "~:membership", + "~:is-owner": true, + "~:is-admin": true, + "~:can-edit": true, + "~:can-read": true, + "~:is-logged": true + }, + "~:has-media-trimmed": false, + "~:comment-thread-seqn": 0, + "~:name": "File with attached lib", + "~:revn": 3, + "~:modified-at": "~m1787664703168", + "~:vern": 0, + "~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d2", + "~:is-shared": false, + "~:version": 67, + "~:project-id": "~ua604eafd-4265-803c-8008-898b8b680ab4", + "~:created-at": "~m1787664674511", + "~:backend": "db", + "~:data": { + "~:pages": [ + "~ua604eafd-4265-803c-8008-898ba4b3c8d3" + ], + "~:pages-index": { + "~ua604eafd-4265-803c-8008-898ba4b3c8d3": { + "~:objects": { + "~#penpot/objects-map/v2": { + "~u00000000-0000-0000-0000-000000000000": "[\"~#shape\",[\"^ \",\"~:y\",0,\"~:hide-fill-on-export\",false,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:name\",\"Root Frame\",\"~:width\",0.01,\"~:type\",\"~:frame\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",0.0,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.0]],[\"^:\",[\"^ \",\"~:x\",0.01,\"~:y\",0.01]],[\"^:\",[\"^ \",\"~:x\",0.0,\"~:y\",0.01]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^3\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",0,\"~:proportion\",1.0,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",0,\"~:y\",0,\"^6\",0.01,\"~:height\",0.01,\"~:x1\",0,\"~:y1\",0,\"~:x2\",0.01,\"~:y2\",0.01]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#FFFFFF\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^H\",0.01,\"~:flip-y\",null,\"~:shapes\",[\"~ud9574b23-9bcc-80d2-8008-898baf40c2e8\"]]]", + "~ud9574b23-9bcc-80d2-8008-898baf40c2e8": "[\"~#shape\",[\"^ \",\"~:y\",100,\"~:transform\",[\"~#matrix\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:rotation\",0,\"~:grow-type\",\"~:fixed\",\"~:hide-in-viewer\",false,\"~:name\",\"Rectangle\",\"~:width\",199.9999966621399,\"~:type\",\"~:rect\",\"~:points\",[[\"~#point\",[\"^ \",\"~:x\",100,\"~:y\",100]],[\"^<\",[\"^ \",\"~:x\",299.9999966621399,\"~:y\",100]],[\"^<\",[\"^ \",\"~:x\",299.9999966621399,\"~:y\",199.99999511241913]],[\"^<\",[\"^ \",\"~:x\",100,\"~:y\",199.99999511241913]]],\"~:r2\",0,\"~:proportion-lock\",false,\"~:transform-inverse\",[\"^2\",[\"^ \",\"~:a\",1.0,\"~:b\",0.0,\"~:c\",0.0,\"~:d\",1.0,\"~:e\",0.0,\"~:f\",0.0]],\"~:r3\",0,\"~:r1\",0,\"~:id\",\"~ud9574b23-9bcc-80d2-8008-898baf40c2e8\",\"~:parent-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:frame-id\",\"~u00000000-0000-0000-0000-000000000000\",\"~:strokes\",[],\"~:x\",100,\"~:proportion\",1,\"~:r4\",0,\"~:selrect\",[\"~#rect\",[\"^ \",\"~:x\",100,\"~:y\",100,\"^8\",199.9999966621399,\"~:height\",99.99999511241913,\"~:x1\",100,\"~:y1\",100,\"~:x2\",299.9999966621399,\"~:y2\",199.99999511241913]],\"~:fills\",[[\"^ \",\"~:fill-color\",\"#B1B2B5\",\"~:fill-opacity\",1]],\"~:flip-x\",null,\"^J\",99.99999511241913,\"~:flip-y\",null]]" + } + }, + "~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d3", + "~:name": "Page 1" + } + }, + "~:id": "~ua604eafd-4265-803c-8008-898ba4b3c8d2", + "~:options": { + "~:components-v2": true, + "~:base-font-size": "16px" + } + } +} \ No newline at end of file diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index f6b6a5a11a..c10e3d38f0 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -554,11 +554,14 @@ export class WorkspacePage extends BaseWebSocketPage { await expect(this.toolbarOptions).toHaveCSS("opacity", "0"); } + async clickLayers(clickOptions = {}) { + await this.sidebar.getByText("Layers").click(clickOptions); + } async clickAssets(clickOptions = {}) { await this.sidebar.getByText("Assets").click(clickOptions); } - async clickLayers(clickOptions = {}) { - await this.sidebar.getByText("Layers").click(clickOptions); + async clickTokens(clickOptions = {}) { + await this.sidebar.getByText("Tokens").click(clickOptions); } async openLibrariesModal(clickOptions = {}) { diff --git a/frontend/playwright/ui/specs/tokens/libraries.spec.js b/frontend/playwright/ui/specs/tokens/libraries.spec.js new file mode 100644 index 0000000000..b48c136d75 --- /dev/null +++ b/frontend/playwright/ui/specs/tokens/libraries.spec.js @@ -0,0 +1,159 @@ +import { test, expect } from "@playwright/test"; +import { WasmWorkspacePage } from "../../pages/WasmWorkspacePage"; +import { + setupEmptyTokensFileRender, + createToken, + unfoldTokenType, +} from "./helpers"; + +test.beforeEach(async ({ page }) => { + await WasmWorkspacePage.init(page); +}); + +test("File with tokens is its own tokens source", async ({ page }) => { + await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]); + const workspacePage = new WasmWorkspacePage(page); + + await workspacePage.setupEmptyFile(page); + + await workspacePage.mockRPC( + "get-team-shared-files?team-id=*", + "workspace/get-team-shared-libraries-non-empty.json", + ); + + await workspacePage.goToWorkspace(); + + // A file without tokens is not tokens source + await workspacePage.clickAssets(); + await workspacePage.openLibrariesModal(); + await workspacePage.librariesModal + .getByRole("tab", { name: "This file" }) + .click(); + + await expect( + workspacePage.librariesModal.getByText("Tokens source"), + ).not.toBeVisible(); + + await workspacePage.closeLibrariesModal(); + + // Create a token in the file + await workspacePage.clickTokens(); + await createToken( + page, + "Color", + "color.primary", + "Value", + "textbox", + "#ff0000", + ); + + // Now it is a tokens source + await workspacePage.clickAssets(); + await workspacePage.openLibrariesModal(); + await workspacePage.librariesModal + .getByRole("tab", { name: "This file" }) + .click(); + + await expect( + workspacePage.librariesModal.getByText("Tokens source"), + ).toBeVisible(); +}); + +test("User sets a library as tokens source and then they can use the tokens in it", async ({ + page, +}) => { + await WasmWorkspacePage.mockConfigFlags(page, ["enable-token-lib-sync"]); + + // Set up a file linked to a library with tokens + + const { workspacePage, tokenThemesSetsSidebar, tokenContextMenuForSet } = + await setupEmptyTokensFileRender(page); + + await workspacePage.mockGetFile("workspace/get-file-with-lib.json"); + + await workspacePage.mockRPC( + "get-file-libraries?file-id=*", + "workspace/get-file-libraries-tokens.json", + ); + + await workspacePage.mockRPC( + /get\-file\?id=c7ce0794-0992-8105-8004-38f280443849/, + "workspace/get-file-tokens.json", + ); + + await workspacePage.mockRPC( + "get-team-shared-files?team-id=*", + "workspace/get-team-shared-libraries-non-empty.json", + ); + + await workspacePage.goToWorkspace(); + + // Set the external library as the tokens source + + await workspacePage.clickAssets(); + await workspacePage.openLibrariesModal(); + await workspacePage.librariesModal + .getByRole("tab", { name: "This file" }) + .click(); + await workspacePage.librariesModal + .getByRole("button", { name: "Set as tokens source" }) + .click(); + await workspacePage.closeLibrariesModal(); + + // Check that we can apply a token from the library to a shape + + await workspacePage.sidebar.getByRole("tab", { name: "Tokens" }).click(); + await page.waitForTimeout(500); + + // Select the first shape in the file + await page.keyboard.press("v"); + await workspacePage.clickAt(200, 150); + + // Apply a color token from the library as the shape fill + await workspacePage.sidebar.getByRole("tab", { name: "Tokens" }).click(); + await unfoldTokenType(workspacePage.tokensSidebar, "Color"); + + const colorTokenName = "colors.red.600"; + await workspacePage.tokensSidebar + .getByRole("button", { name: colorTokenName }) + .click({ button: "right" }); + await workspacePage.tokenContextMenuForToken.getByText("Fill").click(); + + // Check that the token has been correctly applied in the right sidebar + const fillSection = workspacePage.rightSidebar.getByRole("region", { + name: "Fill section", + }); + await expect(fillSection).toBeVisible(); + + const fillTokenPill = fillSection.getByLabel(colorTokenName, { + exact: true, + }); + await expect(fillTokenPill).toBeVisible(); + + // Go back to the libraries modal and set the current file as tokens source + await workspacePage.clickAssets(); + await workspacePage.openLibrariesModal(); + await workspacePage.librariesModal + .getByRole("tab", { name: "This file" }) + .click(); + await workspacePage.librariesModal + .getByRole("button", { name: "Set as tokens source" }) + .click(); + await workspacePage.closeLibrariesModal(); + + // Check that the applied token now looks broken + await expect(fillSection).toBeVisible(); + const brokenTokenPill = fillSection.getByLabel(colorTokenName, { + exact: true, + }); + await expect(brokenTokenPill).toBeVisible(); + await brokenTokenPill.hover(); + + const brokenTokenTooltip = page.getByRole("tooltip", { + name: colorTokenName, + }); + await expect(brokenTokenTooltip).toBeVisible(); + await expect(brokenTokenTooltip).toHaveText( + `{${colorTokenName}} token does not exist or has been deleted.`, + ); +}); diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 6c0d55e33b..f0b1fdaa84 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -12,7 +12,6 @@ [app.common.files.changes-builder :as pcb] [app.common.files.helpers :as cfh] [app.common.files.shapes-helpers :as cfsh] - [app.common.files.tokens :as cfo] [app.common.geom.point :as gpt] [app.common.logging :as log] [app.common.logic.libraries :as cll] @@ -1615,20 +1614,6 @@ (map #(assoc % :library-of file-id)) (d/index-by :id)))))) -(defn- initialize-tokens-status - [library-id] - (ptk/reify ::initialize-tokens-status - ptk/WatchEvent - (watch [it state _] - (let [library (dm/get-in state [:files library-id]) - library-data (ctf/file-data library)] - (when (some? (cfo/get-tokens-lib library-data)) - (when-let [tokens-status (cfo/get-tokens-status library-data)] - (let [changes (-> (pcb/empty-changes it) - (pcb/with-library-data library-data) - (pcb/set-tokens-status tokens-status))] - (rx/of - (dch/commit-changes changes))))))))) (defn- load-library-file [file-id library-id] @@ -1641,8 +1626,7 @@ (rx/merge-map fpmap/resolve-file) (rx/mapcat (fn [file] (rx/of - (libraries-fetched file-id [file]) - (initialize-tokens-status library-id))))) + (libraries-fetched file-id [file]))))) (->> (rp/cmd! :get-file-object-thumbnails {:file-id library-id :tag "component"}) (rx/map (fn [thumbnails] (fn [state] 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 2348eb9fce..da0bdf11e2 100644 --- a/frontend/src/app/main/data/workspace/tokens/library_edit.cljs +++ b/frontend/src/app/main/data/workspace/tokens/library_edit.cljs @@ -430,8 +430,10 @@ ptk/WatchEvent (watch [it state _] (let [data (dsh/lookup-file-data state) + library (dsh/lookup-file state library-id) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) + (clo/generate-set-tokens-source library) (pcb/set-tokens-source library-id))] (rx/of (dch/commit-changes changes))))))