- src/lib/motion/tokens.ts und --motion-* in globals.css: Dauern, Kurven und Feder an einer Stelle; Beute-Bühne liest dieselben Werte. - AppMotion: LazyMotion strict, Bewegungs-Code (domAnimation) als ein asynchroner Chunk, 28 kB gzip; check:motion-budget im Docker-Build neben dem 3D-Budget (Spec 17.6). - PresenceList: abgehakte Zeilen blenden aus, die übrigen rücken nur bei „Voll“ nach; „Dezent“ blendet nur, „Aus“ sofort. Die Zeile geht nach der festen Verzögerung, auch wenn der Server schweigt (Spec 17.5); Fokus wandert zum nächsten Haken, auch ohne Bewegungs-Chunk. - Overlays (Sheet, Dialog, Menü, Select) über Base-UI-Start/Ende-Stile mit Token-Zeiten; Verschiebung und Skalierung nur bei motion-full. - Kein transition-all, kein Hover-Heben, keine Breiten-Animation von Daten; Skeleton erst nach 150 ms; Wächter-Regeln für alles davon. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
48 lines
2.4 KiB
JavaScript
48 lines
2.4 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
import { transform } from "./codemod-t5-motion.mjs";
|
|
|
|
describe("codemod t5 motion (spec 17.2, 17.4, 17.7)", () => {
|
|
it("puts movement behind motion-full: instead of motion-safe:", () => {
|
|
expect(transform('cn("size-4 motion-safe:transition-transform", open && "rotate-180")')).toBe(
|
|
'cn("size-4 motion-full:transition-transform", open && "rotate-180")',
|
|
);
|
|
expect(transform('"holo-stage bg-holo-bg motion-safe:animate-pulse"')).toBe('"holo-stage bg-holo-bg motion-full:animate-pulse"');
|
|
expect(transform('" motion-safe:animate-spin"')).toBe('" motion-full:animate-spin"');
|
|
expect(transform('"motion-safe:data-open:zoom-in-95 motion-safe:slide-in-from-top-2"')).toBe(
|
|
'"motion-full:data-open:zoom-in-95 motion-full:slide-in-from-top-2"',
|
|
);
|
|
expect(transform('"motion-safe:-translate-y-1 motion-safe:scale-95 motion-safe:rotate-90"')).toBe(
|
|
'"motion-full:-translate-y-1 motion-full:scale-95 motion-full:rotate-90"',
|
|
);
|
|
});
|
|
|
|
it("drops the variant from colour and opacity transitions (off sets their duration to zero)", () => {
|
|
expect(transform('"rounded-lg px-2 motion-safe:transition-colors hover:bg-muted"')).toBe(
|
|
'"rounded-lg px-2 transition-colors hover:bg-muted"',
|
|
);
|
|
expect(transform('"motion-safe:transition-opacity"')).toBe('"transition-opacity"');
|
|
});
|
|
|
|
it("gates bare pulse and spin behind full motion and drops motion-reduce:animate-none", () => {
|
|
expect(transform('"size-4 animate-spin motion-reduce:animate-none"')).toBe('"size-4 motion-full:animate-spin"');
|
|
expect(transform('"h-14 animate-pulse rounded-xl"')).toBe('"h-14 motion-full:animate-pulse rounded-xl"');
|
|
expect(transform('"size-4 animate-spin"')).toBe('"size-4 motion-full:animate-spin"');
|
|
});
|
|
|
|
it("leaves other animate classes, tokens and identifiers alone", () => {
|
|
const untouched = [
|
|
'"data-open:animate-in data-closed:animate-out"',
|
|
'"motion-full:animate-pulse"',
|
|
"const isAnimating = animate-spinner;",
|
|
'"animate-page-in"',
|
|
"// motion-safe: is forbidden for movement",
|
|
];
|
|
for (const text of untouched) expect(transform(text)).toBe(text);
|
|
});
|
|
|
|
it("is idempotent", () => {
|
|
const once = transform('"a motion-safe:transition-transform animate-spin motion-reduce:animate-none motion-safe:transition-colors"');
|
|
expect(transform(once)).toBe(once);
|
|
});
|
|
});
|