Skip to content

Commit 8f6031b

Browse files
committed
test(motion): 补 DOM 侧回执的过渡见证判据
1 parent 4d60663 commit 8f6031b

1 file changed

Lines changed: 29 additions & 10 deletions

File tree

‎app/src/motion/responseReceipt.dom.test.tsx‎

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
3535
import ColumnBar from "../components/ColumnBar";
3636
import { Button, ViewSwitcher } from "../ui/primitives";
3737
import type { ViewSwitcherOption } from "../ui/primitives";
38-
import { baseSelector, offendersIn, responseRules } from "./responseScan";
38+
import { baseSelector, declaredProps, offendersIn, responseRules } from "./responseScan";
3939

4040
const RULES = responseRules();
4141

@@ -56,6 +56,14 @@ const landingOn = (el: Element): readonly string[] =>
5656
const 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
/** 取宿主元素(找不到即抛 —— 判据不许在空节点上变成空真) */
6068
function el(container: HTMLElement, testId: string): Element {
6169
const found = container.querySelector(`[data-testid="${testId}"]`);
@@ -97,23 +105,34 @@ const OPTIONS: readonly ViewSwitcherOption[] = [
97105
afterEach(cleanup);
98106

99107
describe("① 可达性:规则要真的打得到今天的元素形态(`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

Comments
 (0)