@flighthq/easing 0.1.0

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.
Files changed (100) hide show
  1. package/dist/createEasingSamples.d.ts +3 -0
  2. package/dist/createEasingSamples.d.ts.map +1 -0
  3. package/dist/createEasingSamples.js +36 -0
  4. package/dist/createEasingSamples.js.map +1 -0
  5. package/dist/easeBack.d.ts +5 -0
  6. package/dist/easeBack.d.ts.map +1 -0
  7. package/dist/easeBack.js +6 -0
  8. package/dist/easeBack.js.map +1 -0
  9. package/dist/easeBounce.d.ts +5 -0
  10. package/dist/easeBounce.d.ts.map +1 -0
  11. package/dist/easeBounce.js +13 -0
  12. package/dist/easeBounce.js.map +1 -0
  13. package/dist/easeCircular.d.ts +5 -0
  14. package/dist/easeCircular.d.ts.map +1 -0
  15. package/dist/easeCircular.js +4 -0
  16. package/dist/easeCircular.js.map +1 -0
  17. package/dist/easeCombinators.d.ts +8 -0
  18. package/dist/easeCombinators.d.ts.map +1 -0
  19. package/dist/easeCombinators.js +53 -0
  20. package/dist/easeCombinators.js.map +1 -0
  21. package/dist/easeCubic.d.ts +5 -0
  22. package/dist/easeCubic.d.ts.map +1 -0
  23. package/dist/easeCubic.js +4 -0
  24. package/dist/easeCubic.js.map +1 -0
  25. package/dist/easeCubicBezier.d.ts +3 -0
  26. package/dist/easeCubicBezier.d.ts.map +1 -0
  27. package/dist/easeCubicBezier.js +68 -0
  28. package/dist/easeCubicBezier.js.map +1 -0
  29. package/dist/easeElastic.d.ts +5 -0
  30. package/dist/easeElastic.d.ts.map +1 -0
  31. package/dist/easeElastic.js +22 -0
  32. package/dist/easeElastic.js.map +1 -0
  33. package/dist/easeExponential.d.ts +5 -0
  34. package/dist/easeExponential.d.ts.map +1 -0
  35. package/dist/easeExponential.js +8 -0
  36. package/dist/easeExponential.js.map +1 -0
  37. package/dist/easeLinear.d.ts +3 -0
  38. package/dist/easeLinear.d.ts.map +1 -0
  39. package/dist/easeLinear.js +2 -0
  40. package/dist/easeLinear.js.map +1 -0
  41. package/dist/easePiecewise.d.ts +3 -0
  42. package/dist/easePiecewise.d.ts.map +1 -0
  43. package/dist/easePiecewise.js +45 -0
  44. package/dist/easePiecewise.js.map +1 -0
  45. package/dist/easePower.d.ts +5 -0
  46. package/dist/easePower.d.ts.map +1 -0
  47. package/dist/easePower.js +24 -0
  48. package/dist/easePower.js.map +1 -0
  49. package/dist/easeQuadratic.d.ts +5 -0
  50. package/dist/easeQuadratic.d.ts.map +1 -0
  51. package/dist/easeQuadratic.js +4 -0
  52. package/dist/easeQuadratic.js.map +1 -0
  53. package/dist/easeQuartic.d.ts +5 -0
  54. package/dist/easeQuartic.d.ts.map +1 -0
  55. package/dist/easeQuartic.js +4 -0
  56. package/dist/easeQuartic.js.map +1 -0
  57. package/dist/easeQuintic.d.ts +5 -0
  58. package/dist/easeQuintic.d.ts.map +1 -0
  59. package/dist/easeQuintic.js +4 -0
  60. package/dist/easeQuintic.js.map +1 -0
  61. package/dist/easeSine.d.ts +5 -0
  62. package/dist/easeSine.d.ts.map +1 -0
  63. package/dist/easeSine.js +4 -0
  64. package/dist/easeSine.js.map +1 -0
  65. package/dist/easeSmoothstep.d.ts +5 -0
  66. package/dist/easeSmoothstep.d.ts.map +1 -0
  67. package/dist/easeSmoothstep.js +17 -0
  68. package/dist/easeSmoothstep.js.map +1 -0
  69. package/dist/easeSteps.d.ts +3 -0
  70. package/dist/easeSteps.d.ts.map +1 -0
  71. package/dist/easeSteps.js +27 -0
  72. package/dist/easeSteps.js.map +1 -0
  73. package/dist/getEasingDerivative.d.ts +3 -0
  74. package/dist/getEasingDerivative.d.ts.map +1 -0
  75. package/dist/getEasingDerivative.js +28 -0
  76. package/dist/getEasingDerivative.js.map +1 -0
  77. package/dist/index.d.ts +20 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +20 -0
  80. package/dist/index.js.map +1 -0
  81. package/package.json +37 -0
  82. package/src/createEasingSamples.test.ts +51 -0
  83. package/src/easeBack.test.ts +30 -0
  84. package/src/easeBounce.test.ts +30 -0
  85. package/src/easeCircular.test.ts +44 -0
  86. package/src/easeCombinators.test.ts +119 -0
  87. package/src/easeCubic.test.ts +34 -0
  88. package/src/easeCubicBezier.test.ts +47 -0
  89. package/src/easeElastic.test.ts +30 -0
  90. package/src/easeExponential.test.ts +34 -0
  91. package/src/easeLinear.test.ts +17 -0
  92. package/src/easePiecewise.test.ts +58 -0
  93. package/src/easePower.test.ts +82 -0
  94. package/src/easeQuadratic.test.ts +34 -0
  95. package/src/easeQuartic.test.ts +34 -0
  96. package/src/easeQuintic.test.ts +34 -0
  97. package/src/easeSine.test.ts +34 -0
  98. package/src/easeSmoothstep.test.ts +76 -0
  99. package/src/easeSteps.test.ts +65 -0
  100. package/src/getEasingDerivative.test.ts +37 -0
@@ -0,0 +1,58 @@
1
+ import { easeInCubic } from './easeCubic';
2
+ import { easeLinear } from './easeLinear';
3
+ import { easePiecewise } from './easePiecewise';
4
+
5
+ describe('easePiecewise', () => {
6
+ it('throws on an empty segments array', () => {
7
+ expect(() => easePiecewise([])).toThrow();
8
+ });
9
+ it('throws when total weight is zero', () => {
10
+ expect(() => easePiecewise([{ ease: easeLinear, weight: 0 }])).toThrow();
11
+ });
12
+ it('single segment is equivalent to the wrapped function', () => {
13
+ const fn = easePiecewise([{ ease: easeInCubic }]);
14
+ expect(fn(0)).toBeCloseTo(easeInCubic(0));
15
+ expect(fn(0.5)).toBeCloseTo(easeInCubic(0.5));
16
+ expect(fn(1)).toBeCloseTo(easeInCubic(1));
17
+ });
18
+ it('two equal-weight segments split the range at t=0.5', () => {
19
+ // First half uses easeLinear (identity), second half uses constant 0.
20
+ const fn = easePiecewise([{ ease: easeLinear }, { ease: () => 0 }]);
21
+ // At t=0.25 → in first segment, local t=0.5, easeLinear(0.5) = 0.5
22
+ expect(fn(0.25)).toBeCloseTo(0.5);
23
+ // At t=0.75 → in second segment, localT = 0.5, constant 0
24
+ expect(fn(0.75)).toBeCloseTo(0);
25
+ });
26
+ it('respects weights: a segment with weight=2 gets twice the range', () => {
27
+ // Segment 0: weight=1, range [0, 1/3]
28
+ // Segment 1: weight=2, range [1/3, 1]
29
+ const fn = easePiecewise([
30
+ { ease: easeLinear, weight: 1 },
31
+ { ease: () => 1, weight: 2 },
32
+ ]);
33
+ // t=1/6 is midpoint of segment 0 → easeLinear(0.5) = 0.5
34
+ expect(fn(1 / 6)).toBeCloseTo(0.5);
35
+ // t=2/3 is midpoint of segment 1 → constant 1
36
+ expect(fn(2 / 3)).toBeCloseTo(1);
37
+ });
38
+ it('returns 0 at t=0 and uses the last segment at t=1', () => {
39
+ const fn = easePiecewise([{ ease: easeLinear }, { ease: easeInCubic }]);
40
+ expect(fn(0)).toBeCloseTo(0);
41
+ expect(fn(1)).toBeCloseTo(1);
42
+ });
43
+ it('is boundary-safe for t=0 and t=1', () => {
44
+ const fn = easePiecewise([{ ease: easeInCubic }, { ease: easeLinear }]);
45
+ expect(fn(0)).toBeCloseTo(0);
46
+ expect(fn(1)).toBeCloseTo(1);
47
+ });
48
+ it('default weight (undefined) is treated as 1', () => {
49
+ const explicit = easePiecewise([
50
+ { ease: easeLinear, weight: 1 },
51
+ { ease: easeInCubic, weight: 1 },
52
+ ]);
53
+ const implicit = easePiecewise([{ ease: easeLinear }, { ease: easeInCubic }]);
54
+ for (let i = 0; i <= 10; i++) {
55
+ expect(implicit(i / 10)).toBeCloseTo(explicit(i / 10), 10);
56
+ }
57
+ });
58
+ });
@@ -0,0 +1,82 @@
1
+ import { easeInOutCubic } from './easeCubic';
2
+ import { easeInOutPower, easeInPower, easeOutPower } from './easePower';
3
+ import { easeInQuadratic, easeOutQuadratic } from './easeQuadratic';
4
+ import { easeInQuintic } from './easeQuintic';
5
+
6
+ describe('easeInOutPower', () => {
7
+ it('returns 0 at t=0 and 1 at t=1', () => {
8
+ const fn = easeInOutPower(3);
9
+ expect(fn(0)).toBeCloseTo(0);
10
+ expect(fn(1)).toBeCloseTo(1);
11
+ });
12
+ it('passes through 0.5 at t=0.5', () => {
13
+ const fn = easeInOutPower(3);
14
+ expect(fn(0.5)).toBeCloseTo(0.5);
15
+ });
16
+ it('matches easeInOutCubic at exponent=3', () => {
17
+ const fn = easeInOutPower(3);
18
+ expect(fn(0.3)).toBeCloseTo(easeInOutCubic(0.3), 10);
19
+ expect(fn(0.7)).toBeCloseTo(easeInOutCubic(0.7), 10);
20
+ });
21
+ it('is symmetric: f(t) + f(1-t) = 1', () => {
22
+ const fn = easeInOutPower(4);
23
+ expect(fn(0.3) + fn(0.7)).toBeCloseTo(1);
24
+ expect(fn(0.1) + fn(0.9)).toBeCloseTo(1);
25
+ });
26
+ });
27
+
28
+ describe('easeInPower', () => {
29
+ it('returns 0 at t=0 and 1 at t=1 for any exponent', () => {
30
+ for (const exp of [0.5, 1, 2, 3, 5]) {
31
+ const fn = easeInPower(exp);
32
+ expect(fn(0)).toBeCloseTo(0);
33
+ expect(fn(1)).toBeCloseTo(1);
34
+ }
35
+ });
36
+ it('matches easeInQuadratic at exponent=2', () => {
37
+ const fn = easeInPower(2);
38
+ expect(fn(0.5)).toBeCloseTo(easeInQuadratic(0.5), 10);
39
+ expect(fn(0.3)).toBeCloseTo(easeInQuadratic(0.3), 10);
40
+ });
41
+ it('matches easeInQuintic at exponent=5', () => {
42
+ const fn = easeInPower(5);
43
+ expect(fn(0.5)).toBeCloseTo(easeInQuintic(0.5), 10);
44
+ expect(fn(0.7)).toBeCloseTo(easeInQuintic(0.7), 10);
45
+ });
46
+ it('sub-linear at exponent < 1', () => {
47
+ const fn = easeInPower(0.5);
48
+ // sqrt(0.25) = 0.5, so output should be 0.5 at t=0.25
49
+ expect(fn(0.25)).toBeCloseTo(0.5, 10);
50
+ });
51
+ it('is monotonically increasing', () => {
52
+ const fn = easeInPower(2.5);
53
+ let prev = fn(0);
54
+ for (let i = 1; i <= 20; i++) {
55
+ const v = fn(i / 20);
56
+ expect(v).toBeGreaterThan(prev);
57
+ prev = v;
58
+ }
59
+ });
60
+ });
61
+
62
+ describe('easeOutPower', () => {
63
+ it('returns 0 at t=0 and 1 at t=1', () => {
64
+ const fn = easeOutPower(2);
65
+ expect(fn(0)).toBeCloseTo(0);
66
+ expect(fn(1)).toBeCloseTo(1);
67
+ });
68
+ it('matches easeOutQuadratic at exponent=2', () => {
69
+ const fn = easeOutPower(2);
70
+ expect(fn(0.5)).toBeCloseTo(easeOutQuadratic(0.5), 10);
71
+ expect(fn(0.8)).toBeCloseTo(easeOutQuadratic(0.8), 10);
72
+ });
73
+ it('is monotonically increasing', () => {
74
+ const fn = easeOutPower(3);
75
+ let prev = fn(0);
76
+ for (let i = 1; i <= 20; i++) {
77
+ const v = fn(i / 20);
78
+ expect(v).toBeGreaterThan(prev);
79
+ prev = v;
80
+ }
81
+ });
82
+ });
@@ -0,0 +1,34 @@
1
+ import { easeInOutQuadratic, easeInQuadratic, easeOutQuadratic } from './easeQuadratic';
2
+
3
+ describe('easeInOutQuadratic', () => {
4
+ it('returns 0 at t=0 and 1 at t=1', () => {
5
+ expect(easeInOutQuadratic(0)).toBe(0);
6
+ expect(easeInOutQuadratic(1)).toBe(1);
7
+ });
8
+
9
+ it('is symmetric at midpoint', () => {
10
+ expect(easeInOutQuadratic(0.5)).toBeCloseTo(0.5);
11
+ });
12
+ });
13
+
14
+ describe('easeInQuadratic', () => {
15
+ it('returns 0 at t=0 and 1 at t=1', () => {
16
+ expect(easeInQuadratic(0)).toBe(0);
17
+ expect(easeInQuadratic(1)).toBe(1);
18
+ });
19
+
20
+ it('is slow at start (value below 0.5 at midpoint)', () => {
21
+ expect(easeInQuadratic(0.5)).toBeLessThan(0.5);
22
+ });
23
+ });
24
+
25
+ describe('easeOutQuadratic', () => {
26
+ it('returns 0 at t=0 and 1 at t=1', () => {
27
+ expect(easeOutQuadratic(0)).toBe(0);
28
+ expect(easeOutQuadratic(1)).toBe(1);
29
+ });
30
+
31
+ it('is fast at start (value above 0.5 at midpoint)', () => {
32
+ expect(easeOutQuadratic(0.5)).toBeGreaterThan(0.5);
33
+ });
34
+ });
@@ -0,0 +1,34 @@
1
+ import { easeInOutQuartic, easeInQuartic, easeOutQuartic } from './easeQuartic';
2
+
3
+ describe('easeInOutQuartic', () => {
4
+ it('returns 0 at t=0 and 1 at t=1', () => {
5
+ expect(easeInOutQuartic(0)).toBe(0);
6
+ expect(easeInOutQuartic(1)).toBe(1);
7
+ });
8
+
9
+ it('is symmetric at midpoint', () => {
10
+ expect(easeInOutQuartic(0.5)).toBeCloseTo(0.5);
11
+ });
12
+ });
13
+
14
+ describe('easeInQuartic', () => {
15
+ it('returns 0 at t=0 and 1 at t=1', () => {
16
+ expect(easeInQuartic(0)).toBe(0);
17
+ expect(easeInQuartic(1)).toBe(1);
18
+ });
19
+
20
+ it('is slow at start (value below 0.5 at midpoint)', () => {
21
+ expect(easeInQuartic(0.5)).toBeLessThan(0.5);
22
+ });
23
+ });
24
+
25
+ describe('easeOutQuartic', () => {
26
+ it('returns 0 at t=0 and 1 at t=1', () => {
27
+ expect(easeOutQuartic(0)).toBe(0);
28
+ expect(easeOutQuartic(1)).toBe(1);
29
+ });
30
+
31
+ it('is fast at start (value above 0.5 at midpoint)', () => {
32
+ expect(easeOutQuartic(0.5)).toBeGreaterThan(0.5);
33
+ });
34
+ });
@@ -0,0 +1,34 @@
1
+ import { easeInOutQuintic, easeInQuintic, easeOutQuintic } from './easeQuintic';
2
+
3
+ describe('easeInOutQuintic', () => {
4
+ it('returns 0 at t=0 and 1 at t=1', () => {
5
+ expect(easeInOutQuintic(0)).toBe(0);
6
+ expect(easeInOutQuintic(1)).toBe(1);
7
+ });
8
+
9
+ it('is symmetric at midpoint', () => {
10
+ expect(easeInOutQuintic(0.5)).toBeCloseTo(0.5);
11
+ });
12
+ });
13
+
14
+ describe('easeInQuintic', () => {
15
+ it('returns 0 at t=0 and 1 at t=1', () => {
16
+ expect(easeInQuintic(0)).toBe(0);
17
+ expect(easeInQuintic(1)).toBe(1);
18
+ });
19
+
20
+ it('is slow at start (value below 0.5 at midpoint)', () => {
21
+ expect(easeInQuintic(0.5)).toBeLessThan(0.5);
22
+ });
23
+ });
24
+
25
+ describe('easeOutQuintic', () => {
26
+ it('returns 0 at t=0 and 1 at t=1', () => {
27
+ expect(easeOutQuintic(0)).toBe(0);
28
+ expect(easeOutQuintic(1)).toBe(1);
29
+ });
30
+
31
+ it('is fast at start (value above 0.5 at midpoint)', () => {
32
+ expect(easeOutQuintic(0.5)).toBeGreaterThan(0.5);
33
+ });
34
+ });
@@ -0,0 +1,34 @@
1
+ import { easeInOutSine, easeInSine, easeOutSine } from './easeSine';
2
+
3
+ describe('easeInOutSine', () => {
4
+ it('returns 0 at t=0 and 1 at t=1', () => {
5
+ expect(easeInOutSine(0)).toBeCloseTo(0);
6
+ expect(easeInOutSine(1)).toBeCloseTo(1);
7
+ });
8
+
9
+ it('is symmetric at midpoint', () => {
10
+ expect(easeInOutSine(0.5)).toBeCloseTo(0.5);
11
+ });
12
+ });
13
+
14
+ describe('easeInSine', () => {
15
+ it('returns 0 at t=0 and 1 at t=1', () => {
16
+ expect(easeInSine(0)).toBeCloseTo(0);
17
+ expect(easeInSine(1)).toBeCloseTo(1);
18
+ });
19
+
20
+ it('is slow at start (value below 0.5 at midpoint)', () => {
21
+ expect(easeInSine(0.5)).toBeLessThan(0.5);
22
+ });
23
+ });
24
+
25
+ describe('easeOutSine', () => {
26
+ it('returns 0 at t=0 and 1 at t=1', () => {
27
+ expect(easeOutSine(0)).toBeCloseTo(0);
28
+ expect(easeOutSine(1)).toBeCloseTo(1);
29
+ });
30
+
31
+ it('is fast at start (value above 0.5 at midpoint)', () => {
32
+ expect(easeOutSine(0.5)).toBeGreaterThan(0.5);
33
+ });
34
+ });
@@ -0,0 +1,76 @@
1
+ import { easeSmootherstep, easeSmoothstep, easeSmoothstepRange } from './easeSmoothstep';
2
+
3
+ describe('easeSmootherstep', () => {
4
+ it('returns 0 at t=0 and 1 at t=1', () => {
5
+ expect(easeSmootherstep(0)).toBe(0);
6
+ expect(easeSmootherstep(1)).toBe(1);
7
+ });
8
+
9
+ it('is symmetric about the midpoint', () => {
10
+ expect(easeSmootherstep(0.5)).toBeCloseTo(0.5);
11
+ });
12
+
13
+ it('is monotonically increasing', () => {
14
+ let prev = easeSmootherstep(0);
15
+ for (let i = 1; i <= 20; i++) {
16
+ const value = easeSmootherstep(i / 20);
17
+ expect(value).toBeGreaterThanOrEqual(prev);
18
+ prev = value;
19
+ }
20
+ });
21
+
22
+ it('approaches endpoints more gently than smoothstep', () => {
23
+ // Higher-order flattening means a smaller value near the start.
24
+ expect(easeSmootherstep(0.25)).toBeLessThan(easeSmoothstep(0.25));
25
+ });
26
+ });
27
+
28
+ describe('easeSmoothstep', () => {
29
+ it('returns 0 at t=0 and 1 at t=1', () => {
30
+ expect(easeSmoothstep(0)).toBe(0);
31
+ expect(easeSmoothstep(1)).toBe(1);
32
+ });
33
+
34
+ it('is symmetric about the midpoint', () => {
35
+ expect(easeSmoothstep(0.5)).toBeCloseTo(0.5);
36
+ });
37
+
38
+ it('matches the known cubic value at t=0.25', () => {
39
+ expect(easeSmoothstep(0.25)).toBeCloseTo(0.15625);
40
+ });
41
+
42
+ it('is monotonically increasing', () => {
43
+ let prev = easeSmoothstep(0);
44
+ for (let i = 1; i <= 20; i++) {
45
+ const value = easeSmoothstep(i / 20);
46
+ expect(value).toBeGreaterThanOrEqual(prev);
47
+ prev = value;
48
+ }
49
+ });
50
+ });
51
+
52
+ describe('easeSmoothstepRange', () => {
53
+ it('returns 0 at edge0 and 1 at edge1', () => {
54
+ const fn = easeSmoothstepRange(10, 20);
55
+ expect(fn(10)).toBe(0);
56
+ expect(fn(20)).toBe(1);
57
+ });
58
+
59
+ it('clamps below edge0 to 0 and above edge1 to 1', () => {
60
+ const fn = easeSmoothstepRange(10, 20);
61
+ expect(fn(5)).toBe(0);
62
+ expect(fn(25)).toBe(1);
63
+ });
64
+
65
+ it('applies smoothstep at the midpoint', () => {
66
+ const fn = easeSmoothstepRange(0, 100);
67
+ expect(fn(50)).toBeCloseTo(0.5);
68
+ });
69
+
70
+ it('matches easeSmoothstep when range is [0, 1]', () => {
71
+ const fn = easeSmoothstepRange(0, 1);
72
+ for (const t of [0, 0.25, 0.5, 0.75, 1]) {
73
+ expect(fn(t)).toBeCloseTo(easeSmoothstep(t));
74
+ }
75
+ });
76
+ });
@@ -0,0 +1,65 @@
1
+ import { easeSteps } from './easeSteps';
2
+
3
+ describe('easeSteps', () => {
4
+ it('jumpEnd holds at 0 first and reaches 1 only at t=1', () => {
5
+ const ease = easeSteps(4, 'jumpEnd');
6
+ expect(ease(0)).toBe(0);
7
+ expect(ease(0.1)).toBe(0);
8
+ expect(ease(0.25)).toBeCloseTo(0.25);
9
+ expect(ease(0.6)).toBeCloseTo(0.5);
10
+ expect(ease(0.99)).toBeCloseTo(0.75);
11
+ expect(ease(1)).toBe(1);
12
+ });
13
+
14
+ it('jumpEnd is the default position', () => {
15
+ const explicit = easeSteps(4, 'jumpEnd');
16
+ const implicit = easeSteps(4);
17
+ for (let i = 0; i <= 10; i++) {
18
+ expect(implicit(i / 10)).toBe(explicit(i / 10));
19
+ }
20
+ });
21
+
22
+ it('jumpStart jumps immediately and reaches 1 before t=1', () => {
23
+ const ease = easeSteps(4, 'jumpStart');
24
+ expect(ease(0)).toBeCloseTo(0.25);
25
+ expect(ease(0.1)).toBeCloseTo(0.25);
26
+ expect(ease(0.5)).toBeCloseTo(0.75);
27
+ expect(ease(0.99)).toBe(1);
28
+ expect(ease(1)).toBe(1);
29
+ });
30
+
31
+ it('jumpNone spans the closed range 0..1 with count-1 jumps', () => {
32
+ const ease = easeSteps(4, 'jumpNone');
33
+ expect(ease(0)).toBe(0);
34
+ expect(ease(0.25)).toBeCloseTo(1 / 3);
35
+ expect(ease(0.5)).toBeCloseTo(2 / 3);
36
+ expect(ease(0.99)).toBe(1);
37
+ expect(ease(1)).toBe(1);
38
+ });
39
+
40
+ it('jumpNone with count=1 yields NaN, matching the CSS-forbidden steps(1, jump-none)', () => {
41
+ const ease = easeSteps(1, 'jumpNone');
42
+ expect(Number.isNaN(ease(0))).toBe(true);
43
+ expect(Number.isNaN(ease(0.5))).toBe(true);
44
+ expect(Number.isNaN(ease(1))).toBe(true);
45
+ });
46
+
47
+ it('jumpBoth never outputs 0 at the start, with interior 1/(n+1) steps', () => {
48
+ const ease = easeSteps(4, 'jumpBoth');
49
+ expect(ease(0)).toBeCloseTo(0.2);
50
+ expect(ease(0.1)).toBeCloseTo(0.2);
51
+ expect(ease(0.25)).toBeCloseTo(0.4);
52
+ expect(ease(0.99)).toBeCloseTo(0.8);
53
+ expect(ease(1)).toBe(1);
54
+ });
55
+
56
+ it('is monotonically non-decreasing for jumpEnd', () => {
57
+ const ease = easeSteps(5, 'jumpEnd');
58
+ let prev = ease(0);
59
+ for (let i = 1; i <= 50; i++) {
60
+ const value = ease(i / 50);
61
+ expect(value).toBeGreaterThanOrEqual(prev);
62
+ prev = value;
63
+ }
64
+ });
65
+ });
@@ -0,0 +1,37 @@
1
+ import { easeInCubic, easeOutCubic } from './easeCubic';
2
+ import { easeLinear } from './easeLinear';
3
+ import { getEasingDerivative } from './getEasingDerivative';
4
+
5
+ describe('getEasingDerivative', () => {
6
+ it('returns ~1 everywhere for easeLinear (slope is constant 1)', () => {
7
+ for (const t of [0, 0.25, 0.5, 0.75, 1]) {
8
+ expect(getEasingDerivative(easeLinear, t)).toBeCloseTo(1, 4);
9
+ }
10
+ });
11
+ it('returns 0 at t=0 for easeInCubic (zero slope at start)', () => {
12
+ // easeInCubic = t^3, derivative = 3t^2 → 0 at t=0
13
+ expect(getEasingDerivative(easeInCubic, 0)).toBeCloseTo(0, 3);
14
+ });
15
+ it('returns 3 at t=1 for easeInCubic (derivative = 3t² = 3 at t=1)', () => {
16
+ expect(getEasingDerivative(easeInCubic, 1)).toBeCloseTo(3, 3);
17
+ });
18
+ it('returns 3 at t=0 for easeOutCubic (derivative = 3(1-t)² = 3 at t=0)', () => {
19
+ expect(getEasingDerivative(easeOutCubic, 0)).toBeCloseTo(3, 3);
20
+ });
21
+ it('returns 0 at t=1 for easeOutCubic (zero slope at end)', () => {
22
+ expect(getEasingDerivative(easeOutCubic, 1)).toBeCloseTo(0, 3);
23
+ });
24
+ it('uses centered difference at interior points', () => {
25
+ // At t=0.5, easeInCubic derivative = 3*(0.5)^2 = 0.75
26
+ expect(getEasingDerivative(easeInCubic, 0.5)).toBeCloseTo(0.75, 3);
27
+ });
28
+ it('accepts a custom epsilon', () => {
29
+ expect(getEasingDerivative(easeLinear, 0.5, 1e-4)).toBeCloseTo(1, 3);
30
+ });
31
+ it('handles the left boundary without throwing', () => {
32
+ expect(() => getEasingDerivative(easeInCubic, 0)).not.toThrow();
33
+ });
34
+ it('handles the right boundary without throwing', () => {
35
+ expect(() => getEasingDerivative(easeInCubic, 1)).not.toThrow();
36
+ });
37
+ });