Files
Jyotisha/frontend/tests/motion-presets.test.ts
T
jesse-uxandGrok (agent loop) d3be9491ee
Independent Staging Quality Gate / validate (push) Successful in 20m4s
Independent Staging Quality Gate / publish (push) Failing after 7m48s
组件库 K0:换上 Motion 底座和 shadcn 零件,页面还没换(#104)。
去掉 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)
2026-10-24 15:37:29 +08:00

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\//);
});