mirror of
https://github.com/penpot/penpot.git
synced 2026-05-31 04:38:08 +00:00
* 🐛 Token remap preserves child component sync after renaming a token group * 📚 Do not update CHANGES.md We are changing the procedures to not update the changelog on each PR. Instead, we use github tracking to check what issues come in a release, and update the changelog automatically in a batch. Signed-off-by: Andrés Moya <andres.moya@kaleidos.net> --------- Signed-off-by: Andrés Moya <andres.moya@kaleidos.net> Co-authored-by: Andrés Moya <andres.moya@kaleidos.net>
228 lines
11 KiB
Clojure
228 lines
11 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns frontend-tests.tokens.logic.token-remapping-test
|
|
(:require
|
|
[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.token :as cto]
|
|
[app.common.types.tokens-lib :as ctob]
|
|
[app.main.data.workspace.tokens.remapping :as dwtr]
|
|
[cljs.test :as t :include-macros true]))
|
|
|
|
(defn setup-file-with-tokens
|
|
"Setup a test file with tokens and shapes that use those tokens"
|
|
[]
|
|
(let [color-token {:id (cthi/new-id! :color-primary)
|
|
:name "color.primary"
|
|
:value "#FF0000"
|
|
:type :color}
|
|
alias-token {:id (cthi/new-id! :color-secondary)
|
|
:name "color.secondary"
|
|
:value "{color.primary}"
|
|
:type :color}]
|
|
(-> (cthf/sample-file :file-1 :page-label :page-1)
|
|
(ctho/add-rect :rect-1)
|
|
(ctho/add-rect :rect-2)
|
|
(assoc-in [:data :tokens-lib]
|
|
(-> (ctob/make-tokens-lib)
|
|
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
|
|
(ctob/set-active-themes #{"/Theme A"})
|
|
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
|
|
:name "Set A"))
|
|
(ctob/add-token (cthi/id :set-a)
|
|
(ctob/make-token color-token))
|
|
(ctob/add-token (cthi/id :set-a)
|
|
(ctob/make-token alias-token))))
|
|
;; Apply the token to rect-1
|
|
(ctht/apply-token-to-shape :rect-1 "color.primary" [:fill] [:fill] "#FF0000"))))
|
|
|
|
(t/deftest test-scan-token-value-references
|
|
(t/testing "should extract token references from alias values"
|
|
(let [token {:id (cthi/id :color-primary)
|
|
:name "color.secondary"
|
|
:value "{color.primary}"
|
|
:type :color}
|
|
references (dwtr/scan-token-value-references token "color.primary")]
|
|
|
|
(t/is (= 1 (count references)))
|
|
(let [ref (first references)]
|
|
(t/is (= :token-alias (:type ref)))
|
|
(t/is (= (cthi/id :color-secondary) (:source-token-id ref)))
|
|
(t/is (= "color.primary" (:referenced-token-name ref)))))))
|
|
|
|
(t/deftest test-scan-workspace-token-references
|
|
(t/testing "should find applied token references"
|
|
(let [file (setup-file-with-tokens)
|
|
file-data (:data file)
|
|
scan-results (dwtr/scan-workspace-token-references file-data "color.primary")]
|
|
|
|
(t/is (= 1 (count (:applied-tokens scan-results))))
|
|
(t/is (= 1 (count (:token-aliases scan-results))))
|
|
(t/is (= 2 (:total-references scan-results)))
|
|
|
|
;; Check applied token reference
|
|
(let [applied-ref (first (:applied-tokens scan-results))]
|
|
(t/is (= :applied-token (:type applied-ref)))
|
|
(t/is (= "color.primary" (:token-name applied-ref)))
|
|
(t/is (= :fill (:attribute applied-ref))))
|
|
|
|
;; Check alias reference
|
|
(let [alias-ref (first (:token-aliases scan-results))]
|
|
(t/is (= :token-alias (:type alias-ref)))
|
|
(t/is (= (cthi/id :color-secondary) (:source-token-id alias-ref)))
|
|
(t/is (= "color.primary" (:referenced-token-name alias-ref)))))))
|
|
|
|
(t/deftest test-update-token-value-references
|
|
(t/testing "should update token references in alias values"
|
|
(let [old-value "{color.primary}"
|
|
new-value (cto/update-token-value-references old-value "color.primary" "brand.primary")]
|
|
(t/is (= "{brand.primary}" new-value))))
|
|
|
|
(t/testing "should update multiple references"
|
|
(let [old-value "calc({spacing.base} + {spacing.base})"
|
|
new-value (cto/update-token-value-references old-value "spacing.base" "spacing.foundation")]
|
|
(t/is (= "calc({spacing.foundation} + {spacing.foundation})" new-value))))
|
|
|
|
(t/testing "should not update partial matches"
|
|
(let [old-value "{color.primary.light}"
|
|
new-value (cto/update-token-value-references old-value "color.primary" "brand.primary")]
|
|
(t/is (= "{color.primary.light}" new-value)))))
|
|
|
|
(t/deftest test-count-token-references
|
|
(t/testing "should count total references to a token"
|
|
(let [file (setup-file-with-tokens)
|
|
file-data (:data file)
|
|
count (dwtr/count-token-references file-data "color.primary")]
|
|
(t/is (= 2 count)))))
|
|
|
|
(t/deftest test-validate-token-remapping
|
|
(t/testing "should validate remapping parameters"
|
|
(t/testing "empty name should be invalid"
|
|
(let [result (dwtr/validate-token-remapping "color.primary" "")]
|
|
(t/is (false? (:valid? result)))
|
|
(t/is (= :invalid-name (:error result)))))
|
|
|
|
(t/testing "same name should be invalid"
|
|
(let [result (dwtr/validate-token-remapping "color.primary" "color.primary")]
|
|
(t/is (false? (:valid? result)))
|
|
(t/is (= :no-change (:error result)))))
|
|
|
|
(t/testing "valid new name should be valid"
|
|
(let [result (dwtr/validate-token-remapping "color.primary" "brand.primary")]
|
|
(t/is (true? (:valid? result)))))))
|
|
|
|
(defn- setup-file-with-component-copy-and-token
|
|
"Create a file containing a component, an instance copy of it, and a single
|
|
`color.primary` token already applied to both the main and the copy shape.
|
|
Mirrors the in-app state right after a user applies a token to a main and
|
|
component sync has propagated the applied-tokens map to the copy."
|
|
[]
|
|
(let [color-primary {:id (cthi/new-id! :color-primary)
|
|
:name "color.primary"
|
|
:value "#FF0000"
|
|
:type :color}
|
|
color-secondary {:id (cthi/new-id! :color-secondary)
|
|
:name "color.secondary"
|
|
:value "#00FF00"
|
|
:type :color}]
|
|
(-> (cthf/sample-file :file-1 :page-label :page-1)
|
|
(ctho/add-simple-component-with-copy :component1
|
|
:main-root
|
|
:main-child
|
|
:copy-root
|
|
:copy-root-params {:children-labels [:copy-child]})
|
|
(assoc-in [:data :tokens-lib]
|
|
(-> (ctob/make-tokens-lib)
|
|
(ctob/add-theme (ctob/make-token-theme :name "Theme A" :sets #{"Set A"}))
|
|
(ctob/set-active-themes #{"/Theme A"})
|
|
(ctob/add-set (ctob/make-token-set :id (cthi/new-id! :set-a)
|
|
:name "Set A"))
|
|
(ctob/add-token (cthi/id :set-a)
|
|
(ctob/make-token color-primary))
|
|
(ctob/add-token (cthi/id :set-a)
|
|
(ctob/make-token color-secondary))))
|
|
(ctht/apply-token-to-shape :main-child "color.primary" [:fill] [:fill] "#FF0000")
|
|
(ctht/apply-token-to-shape :copy-child "color.primary" [:fill] [:fill] "#FF0000"))))
|
|
|
|
(t/deftest test-remap-tokens-finds-both-main-and-copy
|
|
(t/testing "scan should find applied-token references on both main and copy shapes"
|
|
(let [file (setup-file-with-component-copy-and-token)
|
|
scan-results (dwtr/scan-workspace-token-references (:data file) "color.primary")
|
|
shape-ids (set (map :shape-id (:applied-tokens scan-results)))]
|
|
(t/is (= 2 (count (:applied-tokens scan-results))))
|
|
(t/is (contains? shape-ids (cthi/id :main-child)))
|
|
(t/is (contains? shape-ids (cthi/id :copy-child))))))
|
|
|
|
(t/deftest test-remap-tokens-does-not-touch-copy
|
|
(t/testing "renaming a token must not flip the copy's sync group to :touched"
|
|
(let [;; The production flow first renames the token in the tokens-lib,
|
|
;; then dispatches remap-tokens to update applied-token references
|
|
;; on shapes. Mirror that order here.
|
|
file (-> (setup-file-with-component-copy-and-token)
|
|
(update-in [:data :tokens-lib]
|
|
(fn [lib]
|
|
(ctob/update-token lib
|
|
(cthi/id :set-a)
|
|
(cthi/id :color-primary)
|
|
#(assoc % :name "colors.primary")))))
|
|
changes (dwtr/build-remap-changes (:data file)
|
|
"color.primary"
|
|
"colors.primary")
|
|
file' (cthf/apply-changes file changes)
|
|
main-child' (cths/get-shape file' :main-child)
|
|
copy-child' (cths/get-shape file' :copy-child)]
|
|
|
|
(t/is (= "colors.primary" (get-in main-child' [:applied-tokens :fill])))
|
|
(t/is (= "colors.primary" (get-in copy-child' [:applied-tokens :fill])))
|
|
|
|
;; If the rename marks :fill-group as touched on the copy, future
|
|
;; main→copy propagation will skip it — that is the #9495 regression.
|
|
(t/is (not (contains? (set (:touched copy-child')) :fill-group))))))
|
|
|
|
(t/deftest test-remap-preserves-copy-sync-of-later-token-apply
|
|
(t/testing "End-to-end #9495 scenario: after a token group rename + REMAP,
|
|
applying a new token to the main must still propagate to the copy."
|
|
(let [;; 1. Build file with two tokens, a component, and color.primary
|
|
;; already applied to both main-child and copy-child.
|
|
file (setup-file-with-component-copy-and-token)
|
|
|
|
;; 2. Rename the token group: color.primary -> colors.primary,
|
|
;; color.secondary -> colors.secondary (mimics user editing the
|
|
;; group name in the tokens panel).
|
|
file (update-in file [:data :tokens-lib]
|
|
(fn [lib]
|
|
(-> lib
|
|
(ctob/update-token (cthi/id :set-a) (cthi/id :color-primary)
|
|
#(assoc % :name "colors.primary"))
|
|
(ctob/update-token (cthi/id :set-a) (cthi/id :color-secondary)
|
|
#(assoc % :name "colors.secondary")))))
|
|
|
|
;; 3. REMAP runs to update applied-token names on shapes.
|
|
file (cthf/apply-changes
|
|
file
|
|
(dwtr/build-remap-changes (:data file)
|
|
"color.primary"
|
|
"colors.primary"))
|
|
|
|
;; 4. User then applies colors.secondary to the main (Step 7).
|
|
file (ctht/apply-token-to-shape file :main-child "colors.secondary"
|
|
[:fill] [:fill] "#00FF00")
|
|
|
|
;; 5. Component sync propagates the main's change to the copy.
|
|
file (ctho/propagate-component-changes file :component1)
|
|
|
|
copy-child' (cths/get-shape file :copy-child)]
|
|
|
|
;; Step 8 of the issue: the copy must reflect the newly applied token.
|
|
;; Pre-fix, REMAP marks :fill-group as touched on the copy, so the
|
|
;; subsequent sync silently skips :applied-tokens and the copy is left
|
|
;; pointing at colors.primary.
|
|
(t/is (= "colors.secondary" (get-in copy-child' [:applied-tokens :fill]))))))
|