fix: scope Component attrs to retained roots

This commit is contained in:
Kinneyzhang 2026-08-29 01:11:42 +08:00
parent 68e0dd0676
commit c54e32529d
4 changed files with 47 additions and 12 deletions

View File

@ -479,8 +479,9 @@ attrs 遵守下面的不变量:
- `class` 合并并去重; - `class` 合并并去重;
- Ebox style/layout attrs 先按 canonical property domain 去重,调用方值覆盖组件视觉 - Ebox style/layout attrs 先按 canonical property domain 去重,调用方值覆盖组件视觉
默认值; 默认值;
- `role`、`ref`、`disabled`、`:on-*` 等 Runtime metadata 若与根 Host 已有值冲突, - `role`、`ref`、`disabled`、`:on-*` 等 Runtime metadata 与普通前端 attrs 一样由
精确报错;需要由 Component 管理的 metadata 应声明为业务 prop 调用方覆盖根默认值;必须由 Component 掌控的 metadata 应显式声明为业务 prop
从 attrs 域中消费;
- attrs 在调用方 Component 环境中求值,不能泄漏到被调用 Component 的 setup、 - attrs 在调用方 Component 环境中求值,不能泄漏到被调用 Component 的 setup、
`etaf-state` 或业务 props `etaf-state` 或业务 props
- 实际根 Host 仍通过 `ebox-style-property-accepted-p` 验证适用域,例如 Text 根不能 - 实际根 Host 仍通过 `ebox-style-property-accepted-p` 验证适用域,例如 Text 根不能

View File

@ -144,10 +144,14 @@ SITE-TOKEN replaces PATH as the generated call-site identity when non-nil."
(or (plist-get props :ref) (or (plist-get props :ref)
(let ((site (or site-token (copy-sequence path)))) (let ((site (or site-token (copy-sequence path))))
(list 'etaf-host (list 'etaf-host
(if etaf--rendering-range-p (cond
(etaf--rendering-range-p
(list :range etaf--current-semantic-parent-id (list :range etaf--current-semantic-parent-id
:site site) :site site))
site))))) (etaf--render-runtime
(list :parent etaf--current-semantic-parent-id :site site))
(t
(list :path (copy-sequence path) :site site)))))))
(defun etaf--merge-property (props key value) (defun etaf--merge-property (props key value)
"Return PROPS with KEY set to VALUE, preserving the original order." "Return PROPS with KEY set to VALUE, preserving the original order."

View File

@ -287,9 +287,7 @@ Component semantics. COMPONENT-NAME labels diagnostics."
(append (etaf--plist-remove-domain result key) (append (etaf--plist-remove-domain result key)
(list key value)))) (list key value))))
((plist-member result key) ((plist-member result key)
(etaf--component-error (setq result (etaf--plist-set result key value)))
"Component %S root metadata %S conflicts with fallthrough"
component-name key))
(t (t
(setq result (append result (list key value))))))) (setq result (append result (list key value)))))))
result)) result))

View File

@ -651,16 +651,31 @@
`((" *etaf-g6b-attrs-fragment*" `((" *etaf-g6b-attrs-fragment*"
,(etaf-view (etaf-test-g6b-attr-fragment :color "red"))) ,(etaf-view (etaf-test-g6b-attr-fragment :color "red")))
(" *etaf-g6b-attrs-text*" (" *etaf-g6b-attrs-text*"
,(etaf-view (etaf-test-g6b-attr-text :padding 1))) ,(etaf-view (etaf-test-g6b-attr-text :padding 1)))))
(" *etaf-g6b-attrs-role*"
,(etaf-view (etaf-test-g6b-attr-role :role 'navigation)))))
(let ((buffer (car entry))) (let ((buffer (car entry)))
(unwind-protect (unwind-protect
(should-error (etaf-mount buffer (cadr entry)) (should-error (etaf-mount buffer (cadr entry))
:type 'etaf-component-call-error) :type 'etaf-component-call-error)
(when-let* ((runtime (etaf-runtime-for-buffer buffer))) (when-let* ((runtime (etaf-runtime-for-buffer buffer)))
(etaf-unmount runtime)) (etaf-unmount runtime))
(when-let* ((live (get-buffer buffer))) (kill-buffer live)))))) (when-let* ((live (get-buffer buffer))) (kill-buffer live)))))
(let ((buffer " *etaf-g6b-attrs-role*"))
(unwind-protect
(progn
(etaf-mount
buffer
(etaf-view
(etaf-test-g6b-attr-role
:role 'navigation :ref 'g6b-attrs-role)))
(should
(eq 'navigation
(plist-get
(etaf-runtime-host-props-for
(etaf-runtime-for-buffer buffer) 'g6b-attrs-role)
:role))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer)))
(etaf-unmount runtime))
(when-let* ((live (get-buffer buffer))) (kill-buffer live)))))
(ert-deftest etaf-component-host-attrs-rollback-root-shape-failure () (ert-deftest etaf-component-host-attrs-rollback-root-shape-failure ()
"A later multi-root result cannot publish or retire the previous root." "A later multi-root result cannot publish or retire the previous root."
@ -687,5 +702,22 @@
(etaf-unmount runtime)) (etaf-unmount runtime))
(when-let* ((live (get-buffer buffer))) (kill-buffer live))))) (when-let* ((live (get-buffer buffer))) (kill-buffer live)))))
(ert-deftest etaf-component-compiled-host-sites-are-instance-scoped ()
"Two instances of one compiled Component never share generated Host refs."
(let ((buffer " *etaf-g6b-instance-sites*"))
(unwind-protect
(progn
(etaf-mount
buffer
(etaf-view
(column
(etaf-test-g6b-attr-leaf :key 'one :label "One")
(etaf-test-g6b-attr-leaf :key 'two :label "Two"))))
(should (string-match-p
"One[[:space:]]+Two" (etaf-test-g6b--text buffer))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer)))
(etaf-unmount runtime))
(when-let* ((live (get-buffer buffer))) (kill-buffer live)))))
(provide 'etaf-component-frontends-tests) (provide 'etaf-component-frontends-tests)
;;; etaf-component-frontends-tests.el ends here ;;; etaf-component-frontends-tests.el ends here