@praxisjs/motion 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @praxisjs/motion
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [fe39901]
8
+ - @praxisjs/core@0.4.1
9
+
10
+ ## 0.2.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [f52354d]
15
+ - @praxisjs/core@0.4.0
16
+
3
17
  ## 0.2.0
4
18
 
5
19
  ### Minor Changes
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=easings.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"easings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/easings.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { easings, resolveEasing } from "../easings";
3
+ const EPSILON = 1e-6;
4
+ function nearly(a, b) {
5
+ return Math.abs(a - b) < EPSILON;
6
+ }
7
+ describe("easings", () => {
8
+ describe("boundary conditions (t=0 → 0, t=1 → 1)", () => {
9
+ for (const [name, fn] of Object.entries(easings)) {
10
+ it(`${name}(0) === 0`, () => {
11
+ expect(nearly(fn(0), 0)).toBe(true);
12
+ });
13
+ it(`${name}(1) === 1`, () => {
14
+ expect(nearly(fn(1), 1)).toBe(true);
15
+ });
16
+ }
17
+ });
18
+ describe("linear", () => {
19
+ it("returns t unchanged", () => {
20
+ expect(easings.linear(0.5)).toBe(0.5);
21
+ expect(easings.linear(0.25)).toBe(0.25);
22
+ });
23
+ });
24
+ describe("easeIn", () => {
25
+ it("is slower at start than end (t² curve)", () => {
26
+ expect(easings.easeIn(0.25)).toBeLessThan(0.25);
27
+ });
28
+ });
29
+ describe("easeOut", () => {
30
+ it("is faster at start than end", () => {
31
+ expect(easings.easeOut(0.75)).toBeGreaterThan(0.75);
32
+ });
33
+ });
34
+ describe("easeInOut", () => {
35
+ it("is symmetric around 0.5", () => {
36
+ const v = easings.easeInOut(0.5);
37
+ expect(nearly(v, 0.5)).toBe(true);
38
+ });
39
+ });
40
+ describe("easeInCubic", () => {
41
+ it("is t³", () => {
42
+ expect(nearly(easings.easeInCubic(0.5), 0.125)).toBe(true);
43
+ });
44
+ });
45
+ describe("bounce", () => {
46
+ it("stays within [0, 1] for t in [0, 1]", () => {
47
+ for (let t = 0; t <= 1; t += 0.1) {
48
+ const v = easings.bounce(t);
49
+ expect(v).toBeGreaterThanOrEqual(0);
50
+ expect(v).toBeLessThanOrEqual(1 + EPSILON);
51
+ }
52
+ });
53
+ });
54
+ describe("elastic", () => {
55
+ it("returns 0 at t=0 and 1 at t=1", () => {
56
+ expect(easings.elastic(0)).toBe(0);
57
+ expect(easings.elastic(1)).toBe(1);
58
+ });
59
+ });
60
+ });
61
+ describe("resolveEasing", () => {
62
+ it("resolves a string key to the easing function", () => {
63
+ const fn = resolveEasing("linear");
64
+ expect(fn).toBe(easings.linear);
65
+ });
66
+ it("passes through a custom function", () => {
67
+ const custom = (t) => t * t * t;
68
+ expect(resolveEasing(custom)).toBe(custom);
69
+ });
70
+ });
71
+ //# sourceMappingURL=easings.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"easings.test.js","sourceRoot":"","sources":["../../src/__tests__/easings.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,OAAO,GAAG,IAAI,CAAC;AAErB,SAAS,MAAM,CAAC,CAAS,EAAE,CAAS;IAClC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;AACnC,CAAC;AAED,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;IACvB,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACtD,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACjD,EAAE,CAAC,GAAG,IAAI,WAAW,EAAE,GAAG,EAAE;gBAC1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,GAAG,IAAI,WAAW,EAAE,GAAG,EAAE;gBAC1B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;YAC7B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;YACjC,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACf,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;QACtB,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC;gBACjC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBAC5B,MAAM,CAAC,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC;gBACpC,MAAM,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,EAAE,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=spring.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spring.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/spring.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { spring } from "../spring";
4
+ describe("spring", () => {
5
+ it("initializes value to the starting number", () => {
6
+ vi.useFakeTimers();
7
+ const s = spring(42);
8
+ expect(s.value()).toBe(42);
9
+ vi.clearAllTimers();
10
+ vi.useRealTimers();
11
+ });
12
+ it("target signal starts at the initial value", () => {
13
+ vi.useFakeTimers();
14
+ const s = spring(10);
15
+ expect(s.target()).toBe(10);
16
+ vi.clearAllTimers();
17
+ vi.useRealTimers();
18
+ });
19
+ it("stop() cancels without throwing", () => {
20
+ vi.useFakeTimers();
21
+ const s = spring(0);
22
+ expect(() => { s.stop(); }).not.toThrow();
23
+ vi.clearAllTimers();
24
+ vi.useRealTimers();
25
+ });
26
+ it("stop() can be called multiple times safely", () => {
27
+ vi.useFakeTimers();
28
+ const s = spring(5);
29
+ s.stop();
30
+ expect(() => { s.stop(); }).not.toThrow();
31
+ vi.clearAllTimers();
32
+ vi.useRealTimers();
33
+ });
34
+ it("updating target signal triggers animation", () => {
35
+ vi.useFakeTimers();
36
+ const s = spring(0);
37
+ s.target.set(100);
38
+ // Run a few frames
39
+ vi.advanceTimersByTime(100);
40
+ // Value should have moved towards 100
41
+ expect(s.value()).toBeGreaterThan(0);
42
+ s.stop();
43
+ vi.clearAllTimers();
44
+ vi.useRealTimers();
45
+ });
46
+ it("accepts custom spring options", () => {
47
+ vi.useFakeTimers();
48
+ const s = spring(0, { stiffness: 0.5, damping: 0.9, mass: 2, precision: 0.01 });
49
+ expect(s.value()).toBe(0);
50
+ vi.clearAllTimers();
51
+ vi.useRealTimers();
52
+ });
53
+ });
54
+ //# sourceMappingURL=spring.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spring.test.js","sourceRoot":"","sources":["../../src/__tests__/spring.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transition.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transition.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/transition.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { createTransition } from "../transition";
4
+ describe("createTransition", () => {
5
+ it("enter() adds and removes CSS classes", async () => {
6
+ vi.useFakeTimers();
7
+ const t = createTransition({ name: "fade", duration: 100 });
8
+ const el = document.createElement("div");
9
+ const p = t.enter(el);
10
+ // enter-from should have been added immediately
11
+ expect(el.classList.contains("fade-enter-from")).toBe(true);
12
+ // requestAnimationFrame fires
13
+ await vi.runAllTimersAsync();
14
+ await p;
15
+ expect(el.classList.contains("fade-enter-from")).toBe(false);
16
+ expect(el.classList.contains("fade-enter-to")).toBe(false);
17
+ vi.useRealTimers();
18
+ });
19
+ it("leave() adds and removes CSS classes", async () => {
20
+ vi.useFakeTimers();
21
+ const t = createTransition({ name: "slide", duration: 100 });
22
+ const el = document.createElement("div");
23
+ const p = t.leave(el);
24
+ expect(el.classList.contains("slide-leave-from")).toBe(true);
25
+ await vi.runAllTimersAsync();
26
+ await p;
27
+ expect(el.classList.contains("slide-leave-from")).toBe(false);
28
+ expect(el.classList.contains("slide-leave-to")).toBe(false);
29
+ vi.useRealTimers();
30
+ });
31
+ it("calls onEnter callback", async () => {
32
+ vi.useFakeTimers();
33
+ const onEnter = vi.fn();
34
+ const t = createTransition({ onEnter, duration: 0 });
35
+ const el = document.createElement("div");
36
+ const p = t.enter(el);
37
+ expect(onEnter).toHaveBeenCalledWith(el);
38
+ await vi.runAllTimersAsync();
39
+ await p;
40
+ vi.useRealTimers();
41
+ });
42
+ it("calls onLeave callback", async () => {
43
+ vi.useFakeTimers();
44
+ const onLeave = vi.fn();
45
+ const t = createTransition({ onLeave, duration: 0 });
46
+ const el = document.createElement("div");
47
+ const p = t.leave(el);
48
+ expect(onLeave).toHaveBeenCalledWith(el);
49
+ await vi.runAllTimersAsync();
50
+ await p;
51
+ vi.useRealTimers();
52
+ });
53
+ it("uses 'transition' as default name", async () => {
54
+ vi.useFakeTimers();
55
+ const t = createTransition();
56
+ const el = document.createElement("div");
57
+ t.enter(el);
58
+ expect(el.classList.contains("transition-enter-from")).toBe(true);
59
+ await vi.runAllTimersAsync();
60
+ vi.useRealTimers();
61
+ });
62
+ });
63
+ //# sourceMappingURL=transition.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transition.test.js","sourceRoot":"","sources":["../../src/__tests__/transition.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tween.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tween.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tween.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,122 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { Animate } from "../decorators";
4
+ import { tween } from "../tween";
5
+ // ── tween ─────────────────────────────────────────────────────────────────────
6
+ describe("tween", () => {
7
+ it("starts with the from value", () => {
8
+ vi.useFakeTimers();
9
+ const t = tween(0, 100);
10
+ expect(t.value()).toBe(0);
11
+ vi.clearAllTimers();
12
+ vi.useRealTimers();
13
+ });
14
+ it("target starts at the to value", () => {
15
+ vi.useFakeTimers();
16
+ const t = tween(0, 100);
17
+ expect(t.target()).toBe(100);
18
+ vi.clearAllTimers();
19
+ vi.useRealTimers();
20
+ });
21
+ it("playing is initially true (animation started immediately)", () => {
22
+ vi.useFakeTimers();
23
+ const t = tween(0, 100);
24
+ expect(t.playing()).toBe(true);
25
+ vi.clearAllTimers();
26
+ vi.useRealTimers();
27
+ });
28
+ it("stop() sets playing to false", () => {
29
+ vi.useFakeTimers();
30
+ const t = tween(0, 100);
31
+ t.stop();
32
+ expect(t.playing()).toBe(false);
33
+ vi.clearAllTimers();
34
+ vi.useRealTimers();
35
+ });
36
+ it("reset() restores to from value and clears progress", () => {
37
+ vi.useFakeTimers();
38
+ const t = tween(5, 100);
39
+ t.reset();
40
+ expect(t.value()).toBe(5);
41
+ expect(t.progress()).toBe(0);
42
+ vi.clearAllTimers();
43
+ vi.useRealTimers();
44
+ });
45
+ it("progress moves towards 1 as animation advances", () => {
46
+ vi.useFakeTimers();
47
+ const t = tween(0, 100, { duration: 300 });
48
+ vi.advanceTimersByTime(300);
49
+ // After full duration, value should be near the target
50
+ expect(t.progress()).toBeGreaterThanOrEqual(0);
51
+ vi.clearAllTimers();
52
+ vi.useRealTimers();
53
+ });
54
+ it("changing target restarts animation", () => {
55
+ vi.useFakeTimers();
56
+ const t = tween(0, 50);
57
+ t.target.set(200);
58
+ expect(t.playing()).toBe(true);
59
+ t.stop();
60
+ vi.clearAllTimers();
61
+ vi.useRealTimers();
62
+ });
63
+ it("accepts custom easing and delay options", () => {
64
+ vi.useFakeTimers();
65
+ const t = tween(0, 100, { easing: "linear", delay: 100, duration: 200 });
66
+ expect(t.value()).toBe(0);
67
+ vi.clearAllTimers();
68
+ vi.useRealTimers();
69
+ });
70
+ });
71
+ // ── Animate decorator ─────────────────────────────────────────────────────────
72
+ describe("Animate decorator", () => {
73
+ function makeCtx(name) {
74
+ const initializers = [];
75
+ return {
76
+ ctx: {
77
+ name,
78
+ kind: "field",
79
+ addInitializer(fn) {
80
+ initializers.push(fn);
81
+ },
82
+ },
83
+ run(instance) {
84
+ initializers.forEach((fn) => { fn.call(instance); });
85
+ },
86
+ };
87
+ }
88
+ it("creates a numeric getter/setter on first assignment", () => {
89
+ vi.useFakeTimers();
90
+ const { ctx, run } = makeCtx("x");
91
+ Animate()(undefined, ctx);
92
+ const instance = {};
93
+ run(instance);
94
+ instance.x = 10;
95
+ expect(typeof instance.x).toBe("number");
96
+ vi.clearAllTimers();
97
+ vi.useRealTimers();
98
+ });
99
+ it("returns 0 before any value is set", () => {
100
+ vi.useFakeTimers();
101
+ const { ctx, run } = makeCtx("opacity");
102
+ Animate()(undefined, ctx);
103
+ const instance = {};
104
+ run(instance);
105
+ expect(instance.opacity).toBe(0);
106
+ vi.clearAllTimers();
107
+ vi.useRealTimers();
108
+ });
109
+ it("updating the property updates the tween target", () => {
110
+ vi.useFakeTimers();
111
+ const { ctx, run } = makeCtx("scale");
112
+ Animate()(undefined, ctx);
113
+ const instance = {};
114
+ run(instance);
115
+ instance.scale = 1;
116
+ instance.scale = 2; // second set updates tween target
117
+ expect(typeof instance.scale).toBe("number");
118
+ vi.clearAllTimers();
119
+ vi.useRealTimers();
120
+ });
121
+ });
122
+ //# sourceMappingURL=tween.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tween.test.js","sourceRoot":"","sources":["../../src/__tests__/tween.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-motion.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-motion.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/use-motion.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,139 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
+ import { useMotion } from "../use-motion";
4
+ function makeRef(el = null) {
5
+ return { current: el };
6
+ }
7
+ describe("useMotion", () => {
8
+ beforeEach(() => {
9
+ vi.useFakeTimers();
10
+ });
11
+ afterEach(() => {
12
+ vi.clearAllTimers();
13
+ vi.useRealTimers();
14
+ });
15
+ it("returns animate, enter, and exit functions", () => {
16
+ const ref = makeRef(document.createElement("div"));
17
+ const m = useMotion(ref);
18
+ expect(typeof m.animate).toBe("function");
19
+ expect(typeof m.enter).toBe("function");
20
+ expect(typeof m.exit).toBe("function");
21
+ });
22
+ it("returns a cancel function when ref.current is null", () => {
23
+ const ref = makeRef(null);
24
+ const m = useMotion(ref);
25
+ const cancel = m.animate({ opacity: [0, 1] });
26
+ expect(typeof cancel).toBe("function");
27
+ // calling cancel on null ref should not throw
28
+ expect(() => { cancel(); }).not.toThrow();
29
+ });
30
+ it("sets opacity style during animation", async () => {
31
+ const el = document.createElement("div");
32
+ const ref = makeRef(el);
33
+ const m = useMotion(ref);
34
+ const rafCallbacks = [];
35
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
36
+ rafCallbacks.push(cb);
37
+ return 1;
38
+ });
39
+ m.animate({ opacity: [0, 1], duration: 100 });
40
+ // Simulate first frame at t=50 (half way through)
41
+ rafCallbacks[0]?.(50);
42
+ expect(el.style.opacity).not.toBe("");
43
+ });
44
+ it("sets transform style for x, y, scale, rotate", () => {
45
+ const el = document.createElement("div");
46
+ const ref = makeRef(el);
47
+ const m = useMotion(ref);
48
+ const rafCallbacks = [];
49
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
50
+ rafCallbacks.push(cb);
51
+ return 1;
52
+ });
53
+ m.animate({ x: [0, 100], y: [0, 50], scale: [1, 2], rotate: [0, 90], duration: 200 });
54
+ rafCallbacks[0]?.(100);
55
+ expect(el.style.transform).toContain("translateX");
56
+ expect(el.style.transform).toContain("translateY");
57
+ expect(el.style.transform).toContain("scale");
58
+ expect(el.style.transform).toContain("rotate");
59
+ });
60
+ it("calls onComplete when animation finishes", () => {
61
+ const el = document.createElement("div");
62
+ const ref = makeRef(el);
63
+ const m = useMotion(ref);
64
+ const onComplete = vi.fn();
65
+ const rafCallbacks = [];
66
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
67
+ rafCallbacks.push(cb);
68
+ return rafCallbacks.length;
69
+ });
70
+ m.animate({ opacity: [0, 1], duration: 100, onComplete });
71
+ // First frame sets startTime = 0
72
+ rafCallbacks[0](0);
73
+ // Second frame at ts=100: elapsed=100, t=1, onComplete fires
74
+ rafCallbacks[1](100);
75
+ expect(onComplete).toHaveBeenCalledTimes(1);
76
+ });
77
+ it("cancel() stops the animation", () => {
78
+ const el = document.createElement("div");
79
+ const ref = makeRef(el);
80
+ const m = useMotion(ref);
81
+ const cancelRaf = vi.fn();
82
+ const rafCallbacks = [];
83
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
84
+ rafCallbacks.push(cb);
85
+ return 42;
86
+ });
87
+ vi.spyOn(window, "cancelAnimationFrame").mockImplementation(cancelRaf);
88
+ const cancel = m.animate({ opacity: [0, 1], duration: 300 });
89
+ cancel();
90
+ // After cancel, further frames should be no-ops
91
+ rafCallbacks[0]?.(100);
92
+ expect(cancelRaf).toHaveBeenCalledWith(42);
93
+ });
94
+ it("exit() reverses opacity and x/y values", () => {
95
+ const el = document.createElement("div");
96
+ const ref = makeRef(el);
97
+ const m = useMotion(ref);
98
+ const rafCallbacks = [];
99
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
100
+ rafCallbacks.push(cb);
101
+ return 1;
102
+ });
103
+ // exit reverses opacity [0,1] → animates [1,0] and x [0,100] → [100,0]
104
+ m.exit({ opacity: [0, 1], x: [0, 100], duration: 100 });
105
+ rafCallbacks[0]?.(0);
106
+ // At t=0 (start), opacity should be 1 (the reversed start)
107
+ expect(el.style.opacity).toBe("1");
108
+ });
109
+ it("enter() is an alias for animate()", () => {
110
+ const el = document.createElement("div");
111
+ const ref = makeRef(el);
112
+ const m = useMotion(ref);
113
+ const onComplete = vi.fn();
114
+ const rafCallbacks = [];
115
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
116
+ rafCallbacks.push(cb);
117
+ return rafCallbacks.length;
118
+ });
119
+ m.enter({ opacity: [0, 1], duration: 50, onComplete });
120
+ rafCallbacks[0](0); // sets startTime=0
121
+ rafCallbacks[1](50); // elapsed=50, t=1
122
+ expect(onComplete).toHaveBeenCalledTimes(1);
123
+ });
124
+ it("respects delay option — no progress before delay elapses", () => {
125
+ const el = document.createElement("div");
126
+ const ref = makeRef(el);
127
+ const m = useMotion(ref);
128
+ const rafCallbacks = [];
129
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
130
+ rafCallbacks.push(cb);
131
+ return 1;
132
+ });
133
+ m.animate({ opacity: [0, 1], delay: 200, duration: 100 });
134
+ // At ts=50, delay not yet passed; opacity should be at start value (0)
135
+ rafCallbacks[0]?.(50);
136
+ expect(el.style.opacity).toBe("0");
137
+ });
138
+ });
139
+ //# sourceMappingURL=use-motion.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-motion.test.js","sourceRoot":"","sources":["../../src/__tests__/use-motion.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisjs/motion",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,7 +14,7 @@
14
14
  "typescript": "^5.9.3"
15
15
  },
16
16
  "dependencies": {
17
- "@praxisjs/core": "0.3.0",
17
+ "@praxisjs/core": "0.4.1",
18
18
  "@praxisjs/shared": "0.2.0"
19
19
  },
20
20
  "scripts": {
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { easings, resolveEasing } from "../easings";
4
+
5
+ const EPSILON = 1e-6;
6
+
7
+ function nearly(a: number, b: number) {
8
+ return Math.abs(a - b) < EPSILON;
9
+ }
10
+
11
+ describe("easings", () => {
12
+ describe("boundary conditions (t=0 → 0, t=1 → 1)", () => {
13
+ for (const [name, fn] of Object.entries(easings)) {
14
+ it(`${name}(0) === 0`, () => {
15
+ expect(nearly(fn(0), 0)).toBe(true);
16
+ });
17
+ it(`${name}(1) === 1`, () => {
18
+ expect(nearly(fn(1), 1)).toBe(true);
19
+ });
20
+ }
21
+ });
22
+
23
+ describe("linear", () => {
24
+ it("returns t unchanged", () => {
25
+ expect(easings.linear(0.5)).toBe(0.5);
26
+ expect(easings.linear(0.25)).toBe(0.25);
27
+ });
28
+ });
29
+
30
+ describe("easeIn", () => {
31
+ it("is slower at start than end (t² curve)", () => {
32
+ expect(easings.easeIn(0.25)).toBeLessThan(0.25);
33
+ });
34
+ });
35
+
36
+ describe("easeOut", () => {
37
+ it("is faster at start than end", () => {
38
+ expect(easings.easeOut(0.75)).toBeGreaterThan(0.75);
39
+ });
40
+ });
41
+
42
+ describe("easeInOut", () => {
43
+ it("is symmetric around 0.5", () => {
44
+ const v = easings.easeInOut(0.5);
45
+ expect(nearly(v, 0.5)).toBe(true);
46
+ });
47
+ });
48
+
49
+ describe("easeInCubic", () => {
50
+ it("is t³", () => {
51
+ expect(nearly(easings.easeInCubic(0.5), 0.125)).toBe(true);
52
+ });
53
+ });
54
+
55
+ describe("bounce", () => {
56
+ it("stays within [0, 1] for t in [0, 1]", () => {
57
+ for (let t = 0; t <= 1; t += 0.1) {
58
+ const v = easings.bounce(t);
59
+ expect(v).toBeGreaterThanOrEqual(0);
60
+ expect(v).toBeLessThanOrEqual(1 + EPSILON);
61
+ }
62
+ });
63
+ });
64
+
65
+ describe("elastic", () => {
66
+ it("returns 0 at t=0 and 1 at t=1", () => {
67
+ expect(easings.elastic(0)).toBe(0);
68
+ expect(easings.elastic(1)).toBe(1);
69
+ });
70
+ });
71
+ });
72
+
73
+ describe("resolveEasing", () => {
74
+ it("resolves a string key to the easing function", () => {
75
+ const fn = resolveEasing("linear");
76
+ expect(fn).toBe(easings.linear);
77
+ });
78
+
79
+ it("passes through a custom function", () => {
80
+ const custom = (t: number) => t * t * t;
81
+ expect(resolveEasing(custom)).toBe(custom);
82
+ });
83
+ });
@@ -0,0 +1,60 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+
4
+ import { spring } from "../spring";
5
+
6
+ describe("spring", () => {
7
+ it("initializes value to the starting number", () => {
8
+ vi.useFakeTimers();
9
+ const s = spring(42);
10
+ expect(s.value()).toBe(42);
11
+ vi.clearAllTimers();
12
+ vi.useRealTimers();
13
+ });
14
+
15
+ it("target signal starts at the initial value", () => {
16
+ vi.useFakeTimers();
17
+ const s = spring(10);
18
+ expect(s.target()).toBe(10);
19
+ vi.clearAllTimers();
20
+ vi.useRealTimers();
21
+ });
22
+
23
+ it("stop() cancels without throwing", () => {
24
+ vi.useFakeTimers();
25
+ const s = spring(0);
26
+ expect(() => { s.stop(); }).not.toThrow();
27
+ vi.clearAllTimers();
28
+ vi.useRealTimers();
29
+ });
30
+
31
+ it("stop() can be called multiple times safely", () => {
32
+ vi.useFakeTimers();
33
+ const s = spring(5);
34
+ s.stop();
35
+ expect(() => { s.stop(); }).not.toThrow();
36
+ vi.clearAllTimers();
37
+ vi.useRealTimers();
38
+ });
39
+
40
+ it("updating target signal triggers animation", () => {
41
+ vi.useFakeTimers();
42
+ const s = spring(0);
43
+ s.target.set(100);
44
+ // Run a few frames
45
+ vi.advanceTimersByTime(100);
46
+ // Value should have moved towards 100
47
+ expect(s.value()).toBeGreaterThan(0);
48
+ s.stop();
49
+ vi.clearAllTimers();
50
+ vi.useRealTimers();
51
+ });
52
+
53
+ it("accepts custom spring options", () => {
54
+ vi.useFakeTimers();
55
+ const s = spring(0, { stiffness: 0.5, damping: 0.9, mass: 2, precision: 0.01 });
56
+ expect(s.value()).toBe(0);
57
+ vi.clearAllTimers();
58
+ vi.useRealTimers();
59
+ });
60
+ });
@@ -0,0 +1,75 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+
4
+ import { createTransition } from "../transition";
5
+
6
+ describe("createTransition", () => {
7
+ it("enter() adds and removes CSS classes", async () => {
8
+ vi.useFakeTimers();
9
+ const t = createTransition({ name: "fade", duration: 100 });
10
+ const el = document.createElement("div");
11
+
12
+ const p = t.enter(el);
13
+
14
+ // enter-from should have been added immediately
15
+ expect(el.classList.contains("fade-enter-from")).toBe(true);
16
+
17
+ // requestAnimationFrame fires
18
+ await vi.runAllTimersAsync();
19
+
20
+ await p;
21
+ expect(el.classList.contains("fade-enter-from")).toBe(false);
22
+ expect(el.classList.contains("fade-enter-to")).toBe(false);
23
+ vi.useRealTimers();
24
+ });
25
+
26
+ it("leave() adds and removes CSS classes", async () => {
27
+ vi.useFakeTimers();
28
+ const t = createTransition({ name: "slide", duration: 100 });
29
+ const el = document.createElement("div");
30
+
31
+ const p = t.leave(el);
32
+ expect(el.classList.contains("slide-leave-from")).toBe(true);
33
+
34
+ await vi.runAllTimersAsync();
35
+ await p;
36
+
37
+ expect(el.classList.contains("slide-leave-from")).toBe(false);
38
+ expect(el.classList.contains("slide-leave-to")).toBe(false);
39
+ vi.useRealTimers();
40
+ });
41
+
42
+ it("calls onEnter callback", async () => {
43
+ vi.useFakeTimers();
44
+ const onEnter = vi.fn();
45
+ const t = createTransition({ onEnter, duration: 0 });
46
+ const el = document.createElement("div");
47
+ const p = t.enter(el);
48
+ expect(onEnter).toHaveBeenCalledWith(el);
49
+ await vi.runAllTimersAsync();
50
+ await p;
51
+ vi.useRealTimers();
52
+ });
53
+
54
+ it("calls onLeave callback", async () => {
55
+ vi.useFakeTimers();
56
+ const onLeave = vi.fn();
57
+ const t = createTransition({ onLeave, duration: 0 });
58
+ const el = document.createElement("div");
59
+ const p = t.leave(el);
60
+ expect(onLeave).toHaveBeenCalledWith(el);
61
+ await vi.runAllTimersAsync();
62
+ await p;
63
+ vi.useRealTimers();
64
+ });
65
+
66
+ it("uses 'transition' as default name", async () => {
67
+ vi.useFakeTimers();
68
+ const t = createTransition();
69
+ const el = document.createElement("div");
70
+ t.enter(el);
71
+ expect(el.classList.contains("transition-enter-from")).toBe(true);
72
+ await vi.runAllTimersAsync();
73
+ vi.useRealTimers();
74
+ });
75
+ });
@@ -0,0 +1,136 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi } from "vitest";
3
+
4
+ import { Animate } from "../decorators";
5
+ import { tween } from "../tween";
6
+
7
+ // ── tween ─────────────────────────────────────────────────────────────────────
8
+
9
+ describe("tween", () => {
10
+ it("starts with the from value", () => {
11
+ vi.useFakeTimers();
12
+ const t = tween(0, 100);
13
+ expect(t.value()).toBe(0);
14
+ vi.clearAllTimers();
15
+ vi.useRealTimers();
16
+ });
17
+
18
+ it("target starts at the to value", () => {
19
+ vi.useFakeTimers();
20
+ const t = tween(0, 100);
21
+ expect(t.target()).toBe(100);
22
+ vi.clearAllTimers();
23
+ vi.useRealTimers();
24
+ });
25
+
26
+ it("playing is initially true (animation started immediately)", () => {
27
+ vi.useFakeTimers();
28
+ const t = tween(0, 100);
29
+ expect(t.playing()).toBe(true);
30
+ vi.clearAllTimers();
31
+ vi.useRealTimers();
32
+ });
33
+
34
+ it("stop() sets playing to false", () => {
35
+ vi.useFakeTimers();
36
+ const t = tween(0, 100);
37
+ t.stop();
38
+ expect(t.playing()).toBe(false);
39
+ vi.clearAllTimers();
40
+ vi.useRealTimers();
41
+ });
42
+
43
+ it("reset() restores to from value and clears progress", () => {
44
+ vi.useFakeTimers();
45
+ const t = tween(5, 100);
46
+ t.reset();
47
+ expect(t.value()).toBe(5);
48
+ expect(t.progress()).toBe(0);
49
+ vi.clearAllTimers();
50
+ vi.useRealTimers();
51
+ });
52
+
53
+ it("progress moves towards 1 as animation advances", () => {
54
+ vi.useFakeTimers();
55
+ const t = tween(0, 100, { duration: 300 });
56
+ vi.advanceTimersByTime(300);
57
+ // After full duration, value should be near the target
58
+ expect(t.progress()).toBeGreaterThanOrEqual(0);
59
+ vi.clearAllTimers();
60
+ vi.useRealTimers();
61
+ });
62
+
63
+ it("changing target restarts animation", () => {
64
+ vi.useFakeTimers();
65
+ const t = tween(0, 50);
66
+ t.target.set(200);
67
+ expect(t.playing()).toBe(true);
68
+ t.stop();
69
+ vi.clearAllTimers();
70
+ vi.useRealTimers();
71
+ });
72
+
73
+ it("accepts custom easing and delay options", () => {
74
+ vi.useFakeTimers();
75
+ const t = tween(0, 100, { easing: "linear", delay: 100, duration: 200 });
76
+ expect(t.value()).toBe(0);
77
+ vi.clearAllTimers();
78
+ vi.useRealTimers();
79
+ });
80
+ });
81
+
82
+ // ── Animate decorator ─────────────────────────────────────────────────────────
83
+
84
+ describe("Animate decorator", () => {
85
+ function makeCtx(name: string) {
86
+ const initializers: Array<(this: unknown) => void> = [];
87
+ return {
88
+ ctx: {
89
+ name,
90
+ kind: "field" as const,
91
+ addInitializer(fn: (this: unknown) => void) {
92
+ initializers.push(fn);
93
+ },
94
+ } as ClassFieldDecoratorContext,
95
+ run(instance: unknown) {
96
+ initializers.forEach((fn) => { fn.call(instance); });
97
+ },
98
+ };
99
+ }
100
+
101
+ it("creates a numeric getter/setter on first assignment", () => {
102
+ vi.useFakeTimers();
103
+ const { ctx, run } = makeCtx("x");
104
+ Animate()(undefined, ctx);
105
+ const instance: Record<string, unknown> = {};
106
+ run(instance);
107
+ instance.x = 10;
108
+ expect(typeof instance.x).toBe("number");
109
+ vi.clearAllTimers();
110
+ vi.useRealTimers();
111
+ });
112
+
113
+ it("returns 0 before any value is set", () => {
114
+ vi.useFakeTimers();
115
+ const { ctx, run } = makeCtx("opacity");
116
+ Animate()(undefined, ctx);
117
+ const instance: Record<string, unknown> = {};
118
+ run(instance);
119
+ expect(instance.opacity).toBe(0);
120
+ vi.clearAllTimers();
121
+ vi.useRealTimers();
122
+ });
123
+
124
+ it("updating the property updates the tween target", () => {
125
+ vi.useFakeTimers();
126
+ const { ctx, run } = makeCtx("scale");
127
+ Animate()(undefined, ctx);
128
+ const instance: Record<string, unknown> = {};
129
+ run(instance);
130
+ instance.scale = 1;
131
+ instance.scale = 2; // second set updates tween target
132
+ expect(typeof instance.scale).toBe("number");
133
+ vi.clearAllTimers();
134
+ vi.useRealTimers();
135
+ });
136
+ });
@@ -0,0 +1,172 @@
1
+ // @vitest-environment jsdom
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
3
+
4
+ import { useMotion } from "../use-motion";
5
+
6
+ function makeRef(el: HTMLElement | null = null) {
7
+ return { current: el };
8
+ }
9
+
10
+ describe("useMotion", () => {
11
+ beforeEach(() => {
12
+ vi.useFakeTimers();
13
+ });
14
+
15
+ afterEach(() => {
16
+ vi.clearAllTimers();
17
+ vi.useRealTimers();
18
+ });
19
+
20
+ it("returns animate, enter, and exit functions", () => {
21
+ const ref = makeRef(document.createElement("div"));
22
+ const m = useMotion(ref);
23
+ expect(typeof m.animate).toBe("function");
24
+ expect(typeof m.enter).toBe("function");
25
+ expect(typeof m.exit).toBe("function");
26
+ });
27
+
28
+ it("returns a cancel function when ref.current is null", () => {
29
+ const ref = makeRef(null);
30
+ const m = useMotion(ref);
31
+ const cancel = m.animate({ opacity: [0, 1] });
32
+ expect(typeof cancel).toBe("function");
33
+ // calling cancel on null ref should not throw
34
+ expect(() => { cancel(); }).not.toThrow();
35
+ });
36
+
37
+ it("sets opacity style during animation", async () => {
38
+ const el = document.createElement("div");
39
+ const ref = makeRef(el);
40
+ const m = useMotion(ref);
41
+
42
+ const rafCallbacks: FrameRequestCallback[] = [];
43
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
44
+ rafCallbacks.push(cb);
45
+ return 1;
46
+ });
47
+
48
+ m.animate({ opacity: [0, 1], duration: 100 });
49
+
50
+ // Simulate first frame at t=50 (half way through)
51
+ rafCallbacks[0]?.(50);
52
+ expect(el.style.opacity).not.toBe("");
53
+ });
54
+
55
+ it("sets transform style for x, y, scale, rotate", () => {
56
+ const el = document.createElement("div");
57
+ const ref = makeRef(el);
58
+ const m = useMotion(ref);
59
+
60
+ const rafCallbacks: FrameRequestCallback[] = [];
61
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
62
+ rafCallbacks.push(cb);
63
+ return 1;
64
+ });
65
+
66
+ m.animate({ x: [0, 100], y: [0, 50], scale: [1, 2], rotate: [0, 90], duration: 200 });
67
+
68
+ rafCallbacks[0]?.(100);
69
+ expect(el.style.transform).toContain("translateX");
70
+ expect(el.style.transform).toContain("translateY");
71
+ expect(el.style.transform).toContain("scale");
72
+ expect(el.style.transform).toContain("rotate");
73
+ });
74
+
75
+ it("calls onComplete when animation finishes", () => {
76
+ const el = document.createElement("div");
77
+ const ref = makeRef(el);
78
+ const m = useMotion(ref);
79
+ const onComplete = vi.fn();
80
+
81
+ const rafCallbacks: FrameRequestCallback[] = [];
82
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
83
+ rafCallbacks.push(cb);
84
+ return rafCallbacks.length;
85
+ });
86
+
87
+ m.animate({ opacity: [0, 1], duration: 100, onComplete });
88
+
89
+ // First frame sets startTime = 0
90
+ rafCallbacks[0](0);
91
+ // Second frame at ts=100: elapsed=100, t=1, onComplete fires
92
+ rafCallbacks[1](100);
93
+ expect(onComplete).toHaveBeenCalledTimes(1);
94
+ });
95
+
96
+ it("cancel() stops the animation", () => {
97
+ const el = document.createElement("div");
98
+ const ref = makeRef(el);
99
+ const m = useMotion(ref);
100
+
101
+ const cancelRaf = vi.fn();
102
+ const rafCallbacks: FrameRequestCallback[] = [];
103
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
104
+ rafCallbacks.push(cb);
105
+ return 42;
106
+ });
107
+ vi.spyOn(window, "cancelAnimationFrame").mockImplementation(cancelRaf);
108
+
109
+ const cancel = m.animate({ opacity: [0, 1], duration: 300 });
110
+ cancel();
111
+
112
+ // After cancel, further frames should be no-ops
113
+ rafCallbacks[0]?.(100);
114
+ expect(cancelRaf).toHaveBeenCalledWith(42);
115
+ });
116
+
117
+ it("exit() reverses opacity and x/y values", () => {
118
+ const el = document.createElement("div");
119
+ const ref = makeRef(el);
120
+ const m = useMotion(ref);
121
+
122
+ const rafCallbacks: FrameRequestCallback[] = [];
123
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
124
+ rafCallbacks.push(cb);
125
+ return 1;
126
+ });
127
+
128
+ // exit reverses opacity [0,1] → animates [1,0] and x [0,100] → [100,0]
129
+ m.exit({ opacity: [0, 1], x: [0, 100], duration: 100 });
130
+
131
+ rafCallbacks[0]?.(0);
132
+ // At t=0 (start), opacity should be 1 (the reversed start)
133
+ expect(el.style.opacity).toBe("1");
134
+ });
135
+
136
+ it("enter() is an alias for animate()", () => {
137
+ const el = document.createElement("div");
138
+ const ref = makeRef(el);
139
+ const m = useMotion(ref);
140
+ const onComplete = vi.fn();
141
+
142
+ const rafCallbacks: FrameRequestCallback[] = [];
143
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
144
+ rafCallbacks.push(cb);
145
+ return rafCallbacks.length;
146
+ });
147
+
148
+ m.enter({ opacity: [0, 1], duration: 50, onComplete });
149
+
150
+ rafCallbacks[0](0); // sets startTime=0
151
+ rafCallbacks[1](50); // elapsed=50, t=1
152
+ expect(onComplete).toHaveBeenCalledTimes(1);
153
+ });
154
+
155
+ it("respects delay option — no progress before delay elapses", () => {
156
+ const el = document.createElement("div");
157
+ const ref = makeRef(el);
158
+ const m = useMotion(ref);
159
+
160
+ const rafCallbacks: FrameRequestCallback[] = [];
161
+ vi.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => {
162
+ rafCallbacks.push(cb);
163
+ return 1;
164
+ });
165
+
166
+ m.animate({ opacity: [0, 1], delay: 200, duration: 100 });
167
+
168
+ // At ts=50, delay not yet passed; opacity should be at start value (0)
169
+ rafCallbacks[0]?.(50);
170
+ expect(el.style.opacity).toBe("0");
171
+ });
172
+ });