diff --git a/frontend/src/app/plugins/tokens.cljs b/frontend/src/app/plugins/tokens.cljs index f47debeaff..b27019c7ba 100644 --- a/frontend/src/app/plugins/tokens.cljs +++ b/frontend/src/app/plugins/tokens.cljs @@ -41,7 +41,9 @@ :m1 :margin-top :m2 :margin-right :m3 :margin-bottom - :m4 :margin-left}) + :m4 :margin-left + + :font-family :font-families}) (def ^:private map:token-attr-plugin->token-attr (merge diff --git a/frontend/test/frontend_tests/plugins/tokens_test.cljs b/frontend/test/frontend_tests/plugins/tokens_test.cljs index 620e0cce7c..3f4f56f6d9 100644 --- a/frontend/test/frontend_tests/plugins/tokens_test.cljs +++ b/frontend/test/frontend_tests/plugins/tokens_test.cljs @@ -22,9 +22,14 @@ [cljs.test :as t :include-macros true] [frontend-tests.helpers.mock :as mock] [frontend-tests.helpers.state :as ths] + [frontend-tests.helpers.wasm :as thw] [potok.v2.core :as ptk])) -(t/use-fixtures :each {:before cthi/reset-idmap!}) +(t/use-fixtures :each + {:before (fn [] + (cthi/reset-idmap!) + (thw/setup-wasm-mocks!)) + :after thw/teardown-wasm-mocks!}) (def ^:private get-resolved-value @#'ptok/get-resolved-value) @@ -81,6 +86,18 @@ (t/is (= :m3 (ptok/token-attr-plugin->token-attr :margin-bottom))) (t/is (= :m4 (ptok/token-attr-plugin->token-attr :margin-left)))) +(t/deftest token-attr-plugin->token-attr-resolves-font-family-alias + ;; Plugin-facing `fontFamilies` (kebab-cased to `:font-families` by the + ;; schema layer) maps to the canonical internal `:font-family`. + (t/is (= :font-family (ptok/token-attr-plugin->token-attr :font-families))) + (t/is (= :font-family (ptok/token-attr-plugin->token-attr "font-families")))) + +(t/deftest token-attr->token-attr-plugin-resolves-font-family-alias + ;; Symmetric direction: the canonical internal attribute maps to the + ;; plural plugin-facing name so applied-token readback serializes as + ;; camelCase `fontFamilies`, not the undocumented singular `fontFamily`. + (t/is (= :font-families (ptok/token-attr->token-attr-plugin :font-family)))) + (t/deftest token-attr-plugin->token-attr-coerces-string-input ;; This is the actual regression — JS plugin calls supply strings. (t/is (= :fill (ptok/token-attr-plugin->token-attr "fill"))) @@ -150,6 +167,57 @@ (done))) 0)))) +(t/deftest shape-apply-token-accepts-font-families + (t/async + done + (let [set-id (cthi/new-id! :token-set) + token-id (cthi/new-id! :font-family-token) + file (-> (cthf/sample-file :file1 :page-label :page1) + (ctho/add-text :text1 "Hello World!") + (ctht/add-tokens-lib) + (ctht/update-tokens-lib + #(-> % + (ctob/add-set + (ctob/make-token-set :id set-id + :name "fonts")) + (ctob/add-theme + (ctob/make-token-theme :name "theme" + :sets #{"fonts"})) + (ctob/set-active-themes #{"/theme"}) + (ctob/add-token + set-id + (ctob/make-token :id token-id + :name "font.primary" + :type :font-family + :value ["Inter"]))))) + store (ths/setup-store file) + _ (set! st/state store) + _ (set! st/stream (ptk/input-stream store)) + ^js context (api/create-context "00000000-0000-0000-0000-000000000000") + ^js page (.-currentPage context) + ^js shape (.getShapeById page (str (cthi/id :text1))) + ^js library (.-library context) + ^js local (.-local library) + ^js catalog (.-tokens local) + ^js token-set (.getSetById catalog (str set-id)) + ^js token (.getTokenById token-set (str token-id))] + (.applyToken shape token #js ["fontFamilies"]) + (js/setTimeout + (fn [] + (let [shape-id (cthi/id :text1) + page-id (cthf/current-page-id file)] + ;; Plugin readback exposes the documented plural key. + (t/is (= "font.primary" (.. shape -tokens -fontFamilies))) + ;; The undocumented singular spelling must not leak. + (t/is (undefined? (.. shape -tokens -fontFamily))) + ;; Internal state keeps the canonical `:font-family` key. + (t/is (= "font.primary" + (get-in @store + [:files (:id file) :data :pages-index page-id + :objects shape-id :applied-tokens :font-family]))) + (done))) + 0)))) + (t/deftest token-attr?-rejects-unknown-input (t/is (false? (boolean (ptok/token-attr? :not-a-real-attr)))) (t/is (false? (boolean (ptok/token-attr? "not-a-real-attr")))) diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index 63163e2aca..96bd5dc421 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -11,6 +11,7 @@ - **plugins-runtime**: `Library.createComponent()` now rejects invalid input (an empty shape list, or a shape inside a component copy) with a validation error instead of returning a component proxy pointing at nothing. - **plugins-runtime**: Setting an individual padding/margin side (`leftPadding`, `topMargin`, …) now re-derives the padding/margin type, switching to `multiple` when the four sides stop being symmetric (so the value is actually painted) and back to `simple` once top/bottom and left/right are mirrored again. - **plugins-runtime**: Removed the premature deep-hardening of the host plugin context, which froze shared host functions (including `Function.prototype`) before SES override taming, causing `TypeError: Cannot assign to read only property 'toString'` on later host-side function extension. Related to #11001. +- **plugins-runtime**: Fixed the `fontFamilies` token property mapping so `Shape.applyToken(token, ["fontFamilies"])` resolves to the canonical `:font-family` attribute and applied-token readback exposes the documented `fontFamilies` key instead of the undocumented singular `fontFamily`. Closes #11405. ## 1.5.0 (2026-07-08)