去掉 GSAP。对话入场改为按需加载的 Motion,仍是 160 毫秒,仍尊重减少动态效果。 用 shadcn@4.21.4 补上任务书列出的组件,不覆盖已有的 8 个。设计令牌映射到 shadcn 变量。 手写控件检查和只减不增的白名单。Motion 预设与四个 store 只是骨架,没有接到页面上。 自测:tsc --noEmit 退出码 0;npm run lint 0 error(122 条原有 warning); 定向测试 37 项、fail 0、cancelled 0;next build 后 / 仍是 Static; 首屏 JS gzip 578349 → 578435(+86 字节,+0.01%)。 Co-Authored-By: Grok (agent loop)
79 lines
3.5 KiB
TypeScript
79 lines
3.5 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import { easeOut, easeSpring, motionPresets } from "../src/lib/motion/presets.ts";
|
|
import { MotionProvider } from "../src/lib/motion/provider.tsx";
|
|
|
|
const providerSource = readFileSync(new URL("../src/lib/motion/provider.tsx", import.meta.url), "utf8");
|
|
const pageSource = readFileSync(new URL("../src/app/(app)/page.tsx", import.meta.url), "utf8");
|
|
const layoutSource = readFileSync(new URL("../src/app/layout.tsx", import.meta.url), "utf8");
|
|
|
|
test("every site-motion row is a preset and nothing else is invented", () => {
|
|
assert.deepEqual(Object.keys(motionPresets).sort(), [
|
|
"chartType",
|
|
"dateTurn",
|
|
"dialog",
|
|
"disclosure",
|
|
"drawer",
|
|
"listReorder",
|
|
"localeSwitch",
|
|
"numberChange",
|
|
"pageEnter",
|
|
"pageExit",
|
|
"personSwitch",
|
|
"press",
|
|
"selectionFollow",
|
|
"sendMessage",
|
|
"sessionSwitch",
|
|
"tabSwitch",
|
|
"toast",
|
|
]);
|
|
assert.deepEqual(easeOut, [0.22, 1, 0.36, 1]);
|
|
assert.deepEqual(easeSpring, [0.34, 1.56, 0.64, 1]);
|
|
assert.equal(motionPresets.press.duration, 0.12);
|
|
assert.equal(motionPresets.pageEnter.duration, 0.28);
|
|
assert.equal(motionPresets.pageEnter.distance, 16);
|
|
assert.equal(motionPresets.pageExit.duration, 0.15);
|
|
assert.equal(motionPresets.selectionFollow.duration, 0.26);
|
|
assert.equal(motionPresets.tabSwitch.duration, 0.26);
|
|
assert.equal(motionPresets.chartType.duration, 0.44);
|
|
assert.equal(motionPresets.personSwitch.duration, 0.3);
|
|
assert.equal(motionPresets.personSwitch.durationFollow, 0.8);
|
|
assert.equal(motionPresets.dateTurn.duration, 0.26);
|
|
assert.equal(motionPresets.sendMessage.duration, 0.26);
|
|
assert.equal(motionPresets.sendMessage.direction, "from-composer");
|
|
assert.equal(motionPresets.sessionSwitch.duration, 0.2);
|
|
assert.equal(motionPresets.sessionSwitch.direction, "crossfade");
|
|
assert.equal(motionPresets.listReorder.duration, 0.26);
|
|
assert.equal(motionPresets.listReorder.durationExit, 0.28);
|
|
assert.equal(motionPresets.dialog.duration, 0.22);
|
|
assert.equal(motionPresets.dialog.durationExit, 0.16);
|
|
assert.equal(motionPresets.dialog.scale, true);
|
|
assert.equal(motionPresets.drawer.duration, 0.28);
|
|
assert.equal(motionPresets.drawer.durationExit, 0.2);
|
|
assert.equal(motionPresets.toast.duration, 0.32);
|
|
assert.equal(motionPresets.toast.ease, easeSpring);
|
|
assert.equal(motionPresets.numberChange.duration, 0.36);
|
|
assert.equal(motionPresets.numberChange.reduced.instant, true);
|
|
assert.equal(motionPresets.localeSwitch.duration, 0.32);
|
|
assert.equal(motionPresets.localeSwitch.stagger, 0.04);
|
|
assert.equal(motionPresets.disclosure.duration, 0.24);
|
|
for (const preset of Object.values(motionPresets)) {
|
|
assert.equal(preset.reduced.travel, false);
|
|
assert.ok(preset.reduced.duration <= 0.15);
|
|
if (preset !== motionPresets.toast) assert.equal(preset.ease, easeOut);
|
|
}
|
|
});
|
|
|
|
test("LazyMotion loads domAnimation asynchronously and honors reduced motion", () => {
|
|
assert.equal(typeof MotionProvider, "function");
|
|
assert.match(providerSource, /LazyMotion/);
|
|
assert.match(providerSource, /MotionConfig/);
|
|
assert.match(providerSource, /reducedMotion="user"/);
|
|
assert.match(providerSource, /import\("motion\/react"\)\.then\(\(module\) => module\.domAnimation\)/);
|
|
assert.doesNotMatch(providerSource, /domMax/);
|
|
assert.doesNotMatch(pageSource, /motion\/react|@\/lib\/motion\/provider|@\/stores\//);
|
|
assert.doesNotMatch(layoutSource, /motion\/react|@\/lib\/motion\/provider|@\/stores\//);
|
|
});
|