🐛 Fix fontFamilies token property mapping in Plugin API (#11566)

* 🐛 Fix fontFamilies token property mapping in Plugin API

The Plugin API exposes the font-family token property as `fontFamilies`,
while Penpot stores the canonical applied-token attribute as
`:font-family`. The bidirectional plugin/internal attribute map did not
contain that alias, so explicit `applyToken(..., ["fontFamilies"])`
validation rejected the property and applied-token readback exposed the
undocumented singular `fontFamily`.

Add `:font-family -> :font-families` to the existing canonical alias
map. The reverse mapping is derived automatically, keeping application
and readback symmetric without introducing a font-specific code path.

Closes #11405

AI-assisted-by: Omen Alpha
Signed-off-by: 최준수 <junsoo1172@gmail.com>

* 🐛 Fix fontFamilies e2e test to target a text shape

The fontFamilies end-to-end regression created a flex layout frame,
whose attribute set (frame-with-layout-attributes) excludes
:font-family. The workspace token application filters such shapes,
so the internal binding and readback assertions would pass vacuously
without exercising the alias.

Target an actual `:text` shape (ctho/add-text) instead, so the test
verifies the full JS "fontFamilies" -> schema -> alias -> canonical
:font-family -> camelCase readback path.

AI-assisted-by: Omen Alpha
Signed-off-by: 최준수 <junsoo1172@gmail.com>

* 🐛 Fix fontFamilies test WASM error and add changelog entry

The text-shape fontFamilies e2e applies a layout-affecting token via
wasm renderer path, hitting missing WASM exports under Node. Merge
thw/setup-wasm-mocks! into the :each fixture and add plugins
CHANGELOG entry for the fontFamilies alias fix.

AI-assisted-by: muse-spark-1.3-contributor
Related to #11566

Signed-off-by: makesomethingshit <junsoo1172@gmail.com>

---------

Signed-off-by: 최준수 <junsoo1172@gmail.com>
Signed-off-by: makesomethingshit <junsoo1172@gmail.com>
This commit is contained in:
makesomethingshit 2026-09-10 20:43:41 +09:00 committed by GitHub
parent dc12f1db91
commit 30849babcc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 2 deletions

View File

@ -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

View File

@ -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"))))

View File

@ -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)