@@ -35,7 +35,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
3535import ColumnBar from "../components/ColumnBar" ;
3636import { Button , ViewSwitcher } from "../ui/primitives" ;
3737import type { ViewSwitcherOption } from "../ui/primitives" ;
38- import { baseSelector , offendersIn , responseRules } from "./responseScan" ;
38+ import { baseSelector , declaredProps , offendersIn , responseRules } from "./responseScan" ;
3939
4040const RULES = responseRules ( ) ;
4141
@@ -56,6 +56,14 @@ const landingOn = (el: Element): readonly string[] =>
5656const stateOn = ( el : Element ) : readonly string [ ] =>
5757 RULES . filter ( ( r ) => r . selector !== baseSelector ( r . selector ) && matchesBase ( el , baseSelector ( r . selector ) ) ) . map ( ( r ) => r . selector ) ;
5858
59+ /** 打到这个元素上、且**声明了过渡**的规则(没有过渡的"回执"是 0ms 硬跳,不是回执) */
60+ const transitionOn = ( el : Element ) : readonly string [ ] =>
61+ RULES . filter (
62+ ( r ) =>
63+ matchesBase ( el , baseSelector ( r . selector ) ) &&
64+ declaredProps ( r . body ) . some ( ( p ) => p === "transition" || p . startsWith ( "transition-" ) ) ,
65+ ) . map ( ( r ) => r . selector ) ;
66+
5967/** 取宿主元素(找不到即抛 —— 判据不许在空节点上变成空真) */
6068function el ( container : HTMLElement , testId : string ) : Element {
6169 const found = container . querySelector ( `[data-testid="${ testId } "]` ) ;
@@ -97,23 +105,34 @@ const OPTIONS: readonly ViewSwitcherOption[] = [
97105afterEach ( cleanup ) ;
98106
99107describe ( "① 可达性:规则要真的打得到今天的元素形态(`matches` 级,node 侧判不到那一半)" , ( ) => {
100- const cases : ReadonlyArray < readonly [ string , ( ) => ReactElement ] > = [
101- [ "裸 button 宿主" , ( ) => < BareButton onClick = { ( ) => undefined } /> ] ,
102- [ "原生 checkbox 宿主" , ( ) => < Checkbox onCheck = { ( ) => undefined } /> ] ,
103- [ "文本输入宿主" , ( ) => < TextInput /> ] ,
104- [ "details/summary 宿主" , ( ) => < Fold /> ] ,
105- [ "draggable 行宿主" , ( ) => < DragRow onDragStart = { ( ) => undefined } /> ] ,
106- [ "role=separator 宿主" , ( ) => < Separator /> ] ,
108+ /** 第三项 = 过渡是否必须由响应层规则**自带**:
109+ * `role="separator"` 的过渡被 `ColumnResizer` 的行内 `transition` shorthand **整条遮蔽**
110+ * (登记在 `responseCoverage.test.ts` 的余量表)⇒ 它只能拿状态回执,平滑半场由调用点自己的 0.15s 承担。 */
111+ const cases : ReadonlyArray < readonly [ string , ( ) => ReactElement , boolean ] > = [
112+ [ "裸 button 宿主" , ( ) => < BareButton onClick = { ( ) => undefined } /> , true ] ,
113+ [ "原生 checkbox 宿主" , ( ) => < Checkbox onCheck = { ( ) => undefined } /> , true ] ,
114+ [ "文本输入宿主" , ( ) => < TextInput /> , true ] ,
115+ [ "details/summary 宿主" , ( ) => < Fold /> , true ] ,
116+ [ "draggable 行宿主" , ( ) => < DragRow onDragStart = { ( ) => undefined } /> , true ] ,
117+ [ "role=separator 宿主" , ( ) => < Separator /> , false ] ,
107118 ] ;
108119
109- for ( const [ name , make ] of cases ) {
110- it ( `${ name } :至少一条响应层规则命中,且带状态回执("收到了"与"做完了"可区分) ` , ( ) => {
120+ for ( const [ name , make , needsTransition ] of cases ) {
121+ it ( `${ name } :至少一条响应层规则命中${ needsTransition ? "且自带过渡" : "(过渡由调用点承担,登记例外)" } ,并有状态回执 ` , ( ) => {
111122 const { container } = render ( make ( ) ) ;
112123 const host = container . firstElementChild ;
113124 if ( host === null ) throw new Error ( `${ name } 没有渲染出元素` ) ;
114125 const target = name . includes ( "summary" ) ? el ( container , "summary" ) : host ;
115126 expect ( landingOn ( target ) . length , `${ name } 没被任何响应层规则打到(选择器写对了但匹配不上)` ) . toBeGreaterThanOrEqual ( 1 ) ;
116127 expect ( stateOn ( target ) . length , `${ name } 只有过渡声明、没有状态回执 ⇒ 「即时反馈」不成立` ) . toBeGreaterThanOrEqual ( 1 ) ;
128+ if ( needsTransition ) {
129+ expect (
130+ transitionOn ( target ) . length ,
131+ `${ name } 的落点里没有一条声明过渡 ⇒ 回执是 0ms 硬跳(只剩状态规则不算回执)` ,
132+ ) . toBeGreaterThanOrEqual ( 1 ) ;
133+ } else {
134+ expect ( transitionOn ( target ) , `${ name } 的过渡登记例外不成立了(要么给它补规则、要么改登记)` ) . toEqual ( [ ] ) ;
135+ }
117136 } ) ;
118137 }
119138
0 commit comments