littlejsengine 1.18.4 → 1.18.7

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 (177) hide show
  1. package/COPYRIGHT.txt +38 -0
  2. package/FAQ.md +633 -0
  3. package/README.md +19 -1
  4. package/dist/littlejs.d.ts +349 -54
  5. package/dist/littlejs.esm.js +1469 -448
  6. package/dist/littlejs.esm.min.js +1 -1
  7. package/dist/littlejs.js +1438 -436
  8. package/dist/littlejs.min.js +1 -1
  9. package/dist/littlejs.release.js +1438 -436
  10. package/package.json +8 -1
  11. package/plugins/box2d.js +32 -7
  12. package/{src/engineMedals.js → plugins/medalSystem.js} +248 -196
  13. package/plugins/pathFinder.js +758 -0
  14. package/plugins/pluginExport.js +19 -0
  15. package/plugins/tween.js +1 -1
  16. package/plugins/tweenSystem.js +509 -0
  17. package/plugins/uiSystem.js +150 -11
  18. package/src/engine.js +2 -141
  19. package/src/engineAudio.js +8 -5
  20. package/src/engineBuild.mjs +4 -2
  21. package/src/engineDebug.js +1 -1
  22. package/src/engineDraw.js +21 -3
  23. package/src/engineExport.js +12 -12
  24. package/src/engineInput.js +1 -1
  25. package/src/engineLogo.js +146 -0
  26. package/src/engineMath.js +10 -3
  27. package/src/engineObject.js +11 -5
  28. package/src/engineParticles.js +6 -3
  29. package/src/engineRelease.js +1 -1
  30. package/src/engineSettings.js +0 -47
  31. package/src/engineUtilities.js +7 -2
  32. package/src/engineWebGL.js +34 -7
  33. package/src/jsconfig.json +3 -1
  34. package/.github/workflows/test.yml +0 -17
  35. package/AI.md +0 -173
  36. package/CLAUDE.md +0 -1
  37. package/examples/box2d/game.js +0 -192
  38. package/examples/box2d/gameObjects.js +0 -564
  39. package/examples/box2d/index.html +0 -9
  40. package/examples/box2d/scenes.js +0 -194
  41. package/examples/box2d/tiles.png +0 -0
  42. package/examples/breakout/game.js +0 -175
  43. package/examples/breakout/gameObjects.js +0 -147
  44. package/examples/breakout/index.html +0 -8
  45. package/examples/breakout/tiles.png +0 -0
  46. package/examples/breakoutTutorial/README.md +0 -521
  47. package/examples/breakoutTutorial/game.js +0 -191
  48. package/examples/breakoutTutorial/images/1.png +0 -0
  49. package/examples/breakoutTutorial/images/10.png +0 -0
  50. package/examples/breakoutTutorial/images/11.png +0 -0
  51. package/examples/breakoutTutorial/images/2.png +0 -0
  52. package/examples/breakoutTutorial/images/3.png +0 -0
  53. package/examples/breakoutTutorial/images/4.png +0 -0
  54. package/examples/breakoutTutorial/images/5.png +0 -0
  55. package/examples/breakoutTutorial/images/6.png +0 -0
  56. package/examples/breakoutTutorial/images/7.png +0 -0
  57. package/examples/breakoutTutorial/images/8.png +0 -0
  58. package/examples/breakoutTutorial/images/9.png +0 -0
  59. package/examples/breakoutTutorial/index.html +0 -8
  60. package/examples/electron/build.mjs +0 -127
  61. package/examples/electron/electron.js +0 -35
  62. package/examples/electron/game.js +0 -54
  63. package/examples/electron/index.html +0 -13
  64. package/examples/electron/package.json +0 -5
  65. package/examples/electron/tiles.png +0 -0
  66. package/examples/empty/game.js +0 -51
  67. package/examples/empty/index.html +0 -5
  68. package/examples/empty/tiles.png +0 -0
  69. package/examples/favicon.png +0 -0
  70. package/examples/games.jpg +0 -0
  71. package/examples/htmlMenu/game.js +0 -82
  72. package/examples/htmlMenu/index.html +0 -45
  73. package/examples/htmlMenu/tiles.png +0 -0
  74. package/examples/index.html +0 -62
  75. package/examples/logo.png +0 -0
  76. package/examples/logo2.png +0 -0
  77. package/examples/module/build.mjs +0 -124
  78. package/examples/module/game.js +0 -132
  79. package/examples/module/index.html +0 -10
  80. package/examples/module/tiles.png +0 -0
  81. package/examples/particles/index.html +0 -426
  82. package/examples/particles/tiles.png +0 -0
  83. package/examples/platformer/data/gameLevelData.tmx +0 -143
  84. package/examples/platformer/data/gameLevelData.tsx +0 -4
  85. package/examples/platformer/game.js +0 -147
  86. package/examples/platformer/gameCharacter.js +0 -309
  87. package/examples/platformer/gameEffects.js +0 -278
  88. package/examples/platformer/gameLevel.js +0 -205
  89. package/examples/platformer/gameLevelData.json +0 -171
  90. package/examples/platformer/gameObjects.js +0 -378
  91. package/examples/platformer/gamePlayer.js +0 -35
  92. package/examples/platformer/index.html +0 -9
  93. package/examples/platformer/tiles.png +0 -0
  94. package/examples/platformer/tilesLevel.png +0 -0
  95. package/examples/puzzle/game.js +0 -331
  96. package/examples/puzzle/index.html +0 -8
  97. package/examples/puzzle/tiles.png +0 -0
  98. package/examples/screenshot.jpg +0 -0
  99. package/examples/shorts/animation.js +0 -18
  100. package/examples/shorts/base.html +0 -54
  101. package/examples/shorts/blending.js +0 -14
  102. package/examples/shorts/box2d.js +0 -51
  103. package/examples/shorts/box2dCar.js +0 -59
  104. package/examples/shorts/box2dPool.js +0 -107
  105. package/examples/shorts/box2dTileLayer.js +0 -48
  106. package/examples/shorts/cameraDrag.js +0 -22
  107. package/examples/shorts/clock.js +0 -21
  108. package/examples/shorts/colors.js +0 -25
  109. package/examples/shorts/debugDraw.js +0 -37
  110. package/examples/shorts/empty.js +0 -30
  111. package/examples/shorts/flappyGame.js +0 -55
  112. package/examples/shorts/fontImage.js +0 -17
  113. package/examples/shorts/fps.js +0 -90
  114. package/examples/shorts/helloWorld.js +0 -11
  115. package/examples/shorts/hillGlideGame.js +0 -63
  116. package/examples/shorts/input.js +0 -64
  117. package/examples/shorts/landerGame.js +0 -57
  118. package/examples/shorts/maze.js +0 -48
  119. package/examples/shorts/medals.js +0 -51
  120. package/examples/shorts/music.js +0 -78
  121. package/examples/shorts/musicPlayer.js +0 -137
  122. package/examples/shorts/nineSlice.js +0 -40
  123. package/examples/shorts/parallax.js +0 -72
  124. package/examples/shorts/particles.js +0 -28
  125. package/examples/shorts/piano.js +0 -44
  126. package/examples/shorts/platformer.js +0 -45
  127. package/examples/shorts/pongGame.js +0 -41
  128. package/examples/shorts/postProcess.js +0 -62
  129. package/examples/shorts/sequencer.js +0 -124
  130. package/examples/shorts/shader.js +0 -29
  131. package/examples/shorts/shapes.js +0 -21
  132. package/examples/shorts/slidingPuzzle.js +0 -52
  133. package/examples/shorts/song.mp3 +0 -0
  134. package/examples/shorts/sound.js +0 -36
  135. package/examples/shorts/spaceGame.js +0 -58
  136. package/examples/shorts/spriteAtlas.js +0 -31
  137. package/examples/shorts/starfield.js +0 -15
  138. package/examples/shorts/texture.js +0 -16
  139. package/examples/shorts/textureWrapped.js +0 -12
  140. package/examples/shorts/tileLayer.js +0 -48
  141. package/examples/shorts/tileRaycast.js +0 -39
  142. package/examples/shorts/tiles.png +0 -0
  143. package/examples/shorts/tiltedView.js +0 -63
  144. package/examples/shorts/timers.js +0 -53
  145. package/examples/shorts/topDown.js +0 -38
  146. package/examples/shorts/tween.js +0 -24
  147. package/examples/shorts/uiSystem.js +0 -61
  148. package/examples/shorts/video.webm +0 -0
  149. package/examples/shorts/videoPlayer.js +0 -34
  150. package/examples/shorts.js +0 -743
  151. package/examples/starter/build.bat +0 -7
  152. package/examples/starter/build.mjs +0 -126
  153. package/examples/starter/game.js +0 -137
  154. package/examples/starter/index.html +0 -35
  155. package/examples/starter/tiles.png +0 -0
  156. package/examples/stress/index.html +0 -173
  157. package/examples/style.css +0 -150
  158. package/examples/tweenSystem/game.js +0 -171
  159. package/examples/tweenSystem/index.html +0 -10
  160. package/examples/tweenSystem/tiles.png +0 -0
  161. package/examples/typescript/build.mjs +0 -60
  162. package/examples/typescript/game.js +0 -100
  163. package/examples/typescript/game.ts +0 -132
  164. package/examples/typescript/index.html +0 -10
  165. package/examples/typescript/tiles.png +0 -0
  166. package/examples/typescript/tsconfig.json +0 -17
  167. package/examples/uiSystem/game.js +0 -139
  168. package/examples/uiSystem/index.html +0 -10
  169. package/examples/uiSystem/tiles.png +0 -0
  170. package/jsconfig.json +0 -12
  171. package/plugins/desktop.ini +0 -2
  172. package/reference.md +0 -448
  173. package/test/math.test.mjs +0 -774
  174. package/test/setup.mjs +0 -22
  175. package/test/smoke.test.mjs +0 -274
  176. package/test/tween.test.mjs +0 -576
  177. package/test/util.test.mjs +0 -80
@@ -1,576 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { Tween, tweenProperty, tweenStopAll, tweenUpdate, Ease, vec2, rgb, Vector2, Color } from '../dist/littlejs.esm.js';
4
-
5
- test('Tween, tweenProperty, tweenStopAll, tweenUpdate, Ease are exported from the bundle', () =>
6
- {
7
- assert.equal(typeof Tween, 'function');
8
- assert.equal(typeof tweenProperty, 'function');
9
- assert.equal(typeof tweenStopAll, 'function');
10
- assert.equal(typeof tweenUpdate, 'function');
11
- assert.equal(typeof Ease, 'object'); // namespace object, not a class
12
- assert.equal(typeof Ease.LINEAR, 'function');
13
- });
14
-
15
- const near = (a, b, eps=1e-9) => Math.abs(a - b) <= eps;
16
-
17
- test('Ease.LINEAR is the identity on [0,1]', () =>
18
- {
19
- assert(near(Ease.LINEAR(0), 0));
20
- assert(near(Ease.LINEAR(0.5), 0.5));
21
- assert(near(Ease.LINEAR(1), 1));
22
- });
23
-
24
- test('Ease.POWER(2) returns x squared', () =>
25
- {
26
- const f = Ease.POWER(2);
27
- assert(near(f(0), 0));
28
- assert(near(f(0.5), 0.25));
29
- assert(near(f(1), 1));
30
- });
31
-
32
- test('Ease.SINE / CIRC / BACK / SPRING anchor at both 0 and 1; EXPO and ELASTIC only at 1', () =>
33
- {
34
- for (const f of [Ease.SINE, Ease.CIRC, Ease.BACK])
35
- {
36
- assert(near(f(0), 0), `${f.name}(0) should be 0`);
37
- assert(near(f(1), 1), `${f.name}(1) should be 1`);
38
- }
39
- // EXPO doesn't hit 0 exactly at x=0 by formula (2^-10 ≈ 0.001), but does hit 1 at x=1
40
- assert(near(Ease.EXPO(1), 1));
41
- // ELASTIC also doesn't start at 0 (formula gives -2^-10 * sin(37π/6) ≈ -0.0005),
42
- // but does finish at exactly 1 at x=1.
43
- assert(near(Ease.ELASTIC(1), 1));
44
- // SPRING formula evaluates to 1 at x=1
45
- assert(near(Ease.SPRING(1), 1));
46
- });
47
-
48
- test('Ease.BOUNCE finishes at 1', () =>
49
- {
50
- assert(near(Ease.BOUNCE(1), 1, 1e-6));
51
- });
52
-
53
- test('Ease.IN is the identity transformer: returns the curve unchanged', () =>
54
- {
55
- // Direction-modifier shape: takes a curve, returns a curve. Used for
56
- // programmatic direction picking, e.g. (bouncy ? Ease.OUT : Ease.IN)(curve).
57
- assert.equal(Ease.IN(Ease.SINE), Ease.SINE);
58
- assert.equal(Ease.IN(Ease.BACK), Ease.BACK);
59
- // The implementation is a true identity function, so passing a number
60
- // also returns it unchanged — meaning Ease.IN doubles as a linear curve.
61
- assert(near(Ease.IN(0), 0));
62
- assert(near(Ease.IN(0.5), 0.5));
63
- assert(near(Ease.IN(1), 1));
64
- });
65
-
66
- test('Ease.OUT(LINEAR) is identity', () =>
67
- {
68
- const f = Ease.OUT(Ease.LINEAR);
69
- assert(near(f(0), 0));
70
- assert(near(f(0.5), 0.5));
71
- assert(near(f(1), 1));
72
- });
73
-
74
- test('Ease.OUT(POWER(2)) starts fast, ends slow', () =>
75
- {
76
- const f = Ease.OUT(Ease.POWER(2));
77
- assert(near(f(0), 0));
78
- assert(near(f(1), 1));
79
- // f(x) = 1 - (1-x)^2; f(0.5) = 1 - 0.25 = 0.75
80
- assert(near(f(0.5), 0.75));
81
- });
82
-
83
- test('Ease.PIECEWISE splits range across the supplied curves', () =>
84
- {
85
- // PIECEWISE(LINEAR, LINEAR) is equivalent to LINEAR
86
- const f = Ease.PIECEWISE(Ease.LINEAR, Ease.LINEAR);
87
- assert(near(f(0), 0));
88
- assert(near(f(0.25), 0.25));
89
- assert(near(f(0.5), 0.5));
90
- assert(near(f(0.75), 0.75));
91
- assert(near(f(0.9999), 0.9999, 1e-3));
92
- });
93
-
94
- test('Ease.IN_OUT(LINEAR) at 0.5 is 0.5 (regression: original referenced undefined Piecewise)', () =>
95
- {
96
- const f = Ease.IN_OUT(Ease.LINEAR);
97
- assert(near(f(0), 0));
98
- assert(near(f(0.5), 0.5));
99
- assert(near(f(1), 1, 1e-3));
100
- });
101
-
102
- test('Ease.BOUNCE is the ease-in form (slow ramp, bouncing impacts near the end)', () =>
103
- {
104
- // Mirror of easeOutBounce: where easeOutBounce peaks at 1 at x = 4/11
105
- // (early), easeInBounce reaches 0 at x = 1 - 4/11 = 7/11 (late dip near
106
- // the start). This direction matches the other ease-in base curves.
107
- assert(near(Ease.BOUNCE(7/11), 0, 1e-9));
108
- // At x=0.5 the ease-in form is 1 - 0.765625 = 0.234375.
109
- assert(near(Ease.BOUNCE(0.5), 0.234375, 1e-9));
110
- // Endpoints
111
- assert(near(Ease.BOUNCE(0), 0, 1e-9));
112
- assert(near(Ease.BOUNCE(1), 1, 1e-9));
113
- });
114
-
115
- test('Ease.OUT(Ease.BOUNCE) is the canonical "ground impact" easeOutBounce', () =>
116
- {
117
- const f = Ease.OUT(Ease.BOUNCE);
118
- // Now THIS is the form where the value peaks at 1 at x = 4/11 (early
119
- // bounce) — the shape users expect when they want a bouncing landing.
120
- assert(near(f(4/11), 1, 1e-9));
121
- assert(near(f(0.5), 0.765625, 1e-9));
122
- });
123
-
124
- test('Ease.BEZIER(0,0,1,1) is approximately linear', () =>
125
- {
126
- const f = Ease.BEZIER(0, 0, 1, 1);
127
- assert(near(f(0), 0, 1e-3));
128
- assert(near(f(0.25), 0.25, 1e-3));
129
- assert(near(f(0.5), 0.5, 1e-3));
130
- assert(near(f(0.75), 0.75, 1e-3));
131
- assert(near(f(1), 1, 1e-3));
132
- });
133
-
134
- test('Ease.BEZIER endpoints are (0,0) and (1,1) for symmetric ease-in-out', () =>
135
- {
136
- // ease-in-out style control points
137
- const f = Ease.BEZIER(0.42, 0, 0.58, 1);
138
- assert(near(f(0), 0, 1e-3));
139
- assert(near(f(1), 1, 1e-3));
140
- // crosses 0.5 at x=0.5 due to symmetry
141
- assert(near(f(0.5), 0.5, 1e-2));
142
- });
143
-
144
- test('Tween constructor calls callback once with the start value', () =>
145
- {
146
- const calls = [];
147
- const t = new Tween((v) => calls.push(v), 5, 10, 1);
148
- assert.deepEqual(calls, [5]);
149
- t.stop(); // cleanup
150
- });
151
-
152
- test('Tween constructor stores duration, ease, useRealTime, paused options', () =>
153
- {
154
- const t = new Tween(() => {}, 0, 1, 2, { ease: Ease.SINE, useRealTime: true, paused: true });
155
- assert.equal(t.duration, 2);
156
- assert.equal(t.life, 2);
157
- assert.equal(t.ease, Ease.SINE);
158
- assert.equal(t.useRealTime, true);
159
- assert.equal(t.paused, true);
160
- t.stop();
161
- });
162
-
163
- test('Tween defaults: start=0 end=1 duration=1 ease=LINEAR useRealTime=false paused=false', () =>
164
- {
165
- const t = new Tween(() => {});
166
- assert.equal(t.start, 0);
167
- assert.equal(t.end, 1);
168
- assert.equal(t.duration, 1);
169
- assert.equal(t.ease, Ease.LINEAR);
170
- assert.equal(t.useRealTime, false);
171
- assert.equal(t.paused, false);
172
- t.stop();
173
- });
174
-
175
- test('Tween.setEase returns this and updates ease', () =>
176
- {
177
- const t = new Tween(() => {});
178
- const ret = t.setEase(Ease.SINE);
179
- assert.equal(ret, t);
180
- assert.equal(t.ease, Ease.SINE);
181
- t.stop();
182
- });
183
-
184
- test('Tween advances toward end as tweenUpdate is called', () =>
185
- {
186
- const calls = [];
187
- const t = new Tween((v) => calls.push(v), 0, 10, 1); // 1 second, 0 → 10
188
- // constructor pushed start
189
- assert.deepEqual(calls, [0]);
190
- tweenUpdate(0.5); // halfway
191
- assert.equal(calls.length, 2);
192
- assert(near(calls[1], 5));
193
- t.stop();
194
- });
195
-
196
- test('Tween completes after duration seconds and fires the end value', () =>
197
- {
198
- const calls = [];
199
- new Tween((v) => calls.push(v), 0, 10, 1);
200
- tweenUpdate(1.0); // exactly one duration
201
- // expect [start=0, end=10] — final callback fires with the end value
202
- assert.deepEqual(calls, [0, 10]);
203
- });
204
-
205
- test('Tween.then(fn) fires once at completion', () =>
206
- {
207
- let thenCalls = 0;
208
- const t = new Tween(() => {}, 0, 1, 1).then(() => thenCalls++);
209
- tweenUpdate(1.0);
210
- assert.equal(thenCalls, 1);
211
- // a second update must not re-fire it (tween is removed from active list)
212
- tweenUpdate(1.0);
213
- assert.equal(thenCalls, 1);
214
- });
215
-
216
- test('Tween.then returns this for chaining', () =>
217
- {
218
- const t = new Tween(() => {});
219
- assert.equal(t.then(() => {}), t);
220
- t.stop();
221
- });
222
-
223
- test('Tween that overshoots its remaining life still fires end value once', () =>
224
- {
225
- const calls = [];
226
- new Tween((v) => calls.push(v), 0, 10, 1);
227
- tweenUpdate(5.0); // way past duration
228
- assert.deepEqual(calls, [0, 10]);
229
- });
230
-
231
- test('useRealTime: true tween advances on realDelta even when gameDelta is 0', () =>
232
- {
233
- const calls = [];
234
- new Tween((v) => calls.push(v), 0, 10, 1, { useRealTime: true });
235
- tweenUpdate(0, 1.0); // game frozen, real time advances 1s
236
- assert.deepEqual(calls, [0, 10]);
237
- });
238
-
239
- test('Default tween (game time) does not advance when gameDelta is 0', () =>
240
- {
241
- const calls = [];
242
- const t = new Tween((v) => calls.push(v), 0, 10, 1);
243
- tweenUpdate(0, 1.0); // game frozen, only real advances; default tween ignores realDelta
244
- assert.deepEqual(calls, [0]);
245
- t.stop();
246
- });
247
-
248
- test('Multiple tweens complete in a single tweenUpdate without skipping', () =>
249
- {
250
- const aCalls = [];
251
- const bCalls = [];
252
- let aThen = 0;
253
- let bThen = 0;
254
- new Tween((v) => aCalls.push(v), 0, 10, 1).then(() => aThen++);
255
- new Tween((v) => bCalls.push(v), 0, 20, 1).then(() => bThen++);
256
- tweenUpdate(1.0); // both should finish
257
- assert.deepEqual(aCalls, [0, 10]);
258
- assert.deepEqual(bCalls, [0, 20]);
259
- assert.equal(aThen, 1);
260
- assert.equal(bThen, 1);
261
- });
262
-
263
- test('tweenUpdate skips a tween whose paused field is true', () =>
264
- {
265
- const calls = [];
266
- const t = new Tween((v) => calls.push(v), 0, 10, 1, { paused: true });
267
- tweenUpdate(2.0);
268
- assert.deepEqual(calls, [0]); // only the constructor start snap
269
- t.stop();
270
- });
271
-
272
- test('Tween.pause prevents advancement', () =>
273
- {
274
- const calls = [];
275
- const t = new Tween((v) => calls.push(v), 0, 10, 1);
276
- t.pause();
277
- tweenUpdate(0.5);
278
- assert.deepEqual(calls, [0]); // no advance
279
- t.stop();
280
- });
281
-
282
- test('Tween.resume re-enables advancement', () =>
283
- {
284
- const calls = [];
285
- const t = new Tween((v) => calls.push(v), 0, 10, 1, { paused: true });
286
- tweenUpdate(0.5); // paused, no advance
287
- assert.deepEqual(calls, [0]);
288
- t.resume();
289
- tweenUpdate(0.5);
290
- assert.equal(calls.length, 2);
291
- assert(near(calls[1], 5));
292
- t.stop();
293
- });
294
-
295
- test('Tween.stop prevents the then-callback from firing', () =>
296
- {
297
- let thenCalls = 0;
298
- const t = new Tween(() => {}, 0, 1, 1).then(() => thenCalls++);
299
- t.stop();
300
- tweenUpdate(2.0);
301
- assert.equal(thenCalls, 0);
302
- });
303
-
304
- test('Tween.restart resets life, clears pause, re-snaps to start', () =>
305
- {
306
- const calls = [];
307
- const t = new Tween((v) => calls.push(v), 0, 10, 1);
308
- tweenUpdate(0.6); // partway: calls = [0, 6]
309
- t.pause();
310
- t.restart();
311
- assert.equal(t.life, 1);
312
- assert.equal(t.paused, false);
313
- // restart fires callback with start again
314
- assert.equal(calls[calls.length - 1], 0);
315
- t.stop();
316
- });
317
-
318
- test('Tween.restart re-adds a stopped tween to the active list', () =>
319
- {
320
- const calls = [];
321
- const t = new Tween((v) => calls.push(v), 0, 10, 1);
322
- t.stop();
323
- assert.equal(t.isActive(), false);
324
- t.restart();
325
- assert.equal(t.isActive(), true);
326
- t.stop();
327
- });
328
-
329
- test('Tween.isActive reflects active list + pause', () =>
330
- {
331
- const t = new Tween(() => {}, 0, 1, 1);
332
- assert.equal(t.isActive(), true);
333
- t.pause();
334
- assert.equal(t.isActive(), false);
335
- t.resume();
336
- assert.equal(t.isActive(), true);
337
- t.stop();
338
- assert.equal(t.isActive(), false);
339
- });
340
-
341
- test('Tween.loop(3) runs 3 total iterations', () =>
342
- {
343
- const calls = [];
344
- new Tween((v) => calls.push(v), 0, 10, 1).loop(3);
345
- // iteration 1: constructor pushes 0; tweenUpdate(1.0) pushes 10
346
- tweenUpdate(1.0);
347
- // then-callback fires, creates iteration 2; that constructor pushes 0
348
- // iteration 2: tweenUpdate below pushes 10, etc
349
- tweenUpdate(1.0);
350
- tweenUpdate(1.0);
351
- // 3 iterations × 2 callback fires (start, end) each = 6 calls
352
- assert.deepEqual(calls, [0, 10, 0, 10, 0, 10]);
353
- });
354
-
355
- test('Tween.loop(1) is the same as no loop (chain ends after first iteration)', () =>
356
- {
357
- const calls = [];
358
- new Tween((v) => calls.push(v), 0, 10, 1).loop(1);
359
- tweenUpdate(1.0);
360
- tweenUpdate(1.0); // no second iteration scheduled
361
- assert.deepEqual(calls, [0, 10]);
362
- });
363
-
364
- test('Tween.loop returns this for chaining', () =>
365
- {
366
- const t = new Tween(() => {});
367
- assert.equal(t.loop(2), t);
368
- t.stop();
369
- });
370
-
371
- test('Calling Tween.then after Tween.loop overrides the loop (last call wins)', () =>
372
- {
373
- let thenCalls = 0;
374
- const calls = [];
375
- new Tween((v) => { calls.push(v); }, 0, 10, 1)
376
- .loop(5)
377
- .then(() => thenCalls++);
378
- tweenUpdate(1.0); // iteration 1 ends; then() fires its callback; loop chain is replaced
379
- assert.equal(thenCalls, 1);
380
- tweenUpdate(1.0); // no further iterations
381
- assert.deepEqual(calls, [0, 10]);
382
- });
383
-
384
- test('Tween.pingPong(3) swaps start/end between iterations', () =>
385
- {
386
- const calls = [];
387
- new Tween((v) => calls.push(v), 0, 10, 1).pingPong(3);
388
- tweenUpdate(1.0); // iter 1 (0→10) completes; iter 2 (10→0) starts (snaps to 10)
389
- tweenUpdate(1.0); // iter 2 completes (→0); iter 3 (0→10) starts (snaps to 0)
390
- tweenUpdate(1.0); // iter 3 completes (→10); chain ends
391
- assert.deepEqual(calls, [0, 10, 10, 0, 0, 10]);
392
- });
393
-
394
- test('Tween.pingPong returns this for chaining', () =>
395
- {
396
- const t = new Tween(() => {});
397
- assert.equal(t.pingPong(2), t);
398
- t.stop();
399
- });
400
-
401
- test('Tween.pingPong(1) ends after one iteration', () =>
402
- {
403
- const calls = [];
404
- new Tween((v) => calls.push(v), 0, 10, 1).pingPong(1);
405
- tweenUpdate(1.0);
406
- tweenUpdate(1.0);
407
- assert.deepEqual(calls, [0, 10]);
408
- });
409
-
410
- test('tweenProperty drives a flat property by name', () =>
411
- {
412
- const obj = { x: 0 };
413
- const t = tweenProperty(obj, 'x', 0, 10, 1);
414
- assert.equal(obj.x, 0); // constructor snaps to start
415
- tweenUpdate(0.5);
416
- assert(near(obj.x, 5));
417
- tweenUpdate(0.5);
418
- assert(near(obj.x, 10));
419
- });
420
-
421
- test('tweenProperty walks dot paths into nested objects', () =>
422
- {
423
- const obj = { pos: { x: 0, y: 0 } };
424
- tweenProperty(obj, 'pos.x', 0, 10, 1);
425
- assert.equal(obj.pos.x, 0);
426
- assert.equal(obj.pos.y, 0); // unaffected
427
- tweenUpdate(1.0);
428
- assert(near(obj.pos.x, 10));
429
- assert.equal(obj.pos.y, 0);
430
- });
431
-
432
- test('tweenProperty returns the underlying Tween for chaining', () =>
433
- {
434
- const obj = { x: 0 };
435
- const t = tweenProperty(obj, 'x', 0, 10, 1);
436
- assert(t instanceof Tween);
437
- // chain methods work
438
- assert.equal(t.setEase(Ease.SINE), t);
439
- t.stop();
440
- });
441
-
442
- test('tweenProperty supports options like useRealTime', () =>
443
- {
444
- const obj = { x: 0 };
445
- const t = tweenProperty(obj, 'x', 0, 10, 1, { useRealTime: true });
446
- assert.equal(t.useRealTime, true);
447
- tweenUpdate(0, 1.0); // game frozen, real advances
448
- assert(near(obj.x, 10));
449
- });
450
-
451
- test('integration: chain setEase + then + tweenProperty + useRealTime', () =>
452
- {
453
- const obj = { value: 0 };
454
- let chainFinished = false;
455
-
456
- tweenProperty(obj, 'value', 0, 100, 2, { useRealTime: true })
457
- .setEase(Ease.OUT(Ease.POWER(2)))
458
- .then(() => { chainFinished = true; });
459
-
460
- assert.equal(obj.value, 0);
461
- tweenUpdate(0, 1.0); // half duration on real time
462
- assert(obj.value > 0 && obj.value < 100, 'value should be partway');
463
- tweenUpdate(0, 1.0); // finish
464
- assert(near(obj.value, 100));
465
- assert.equal(chainFinished, true);
466
- });
467
-
468
- test('Tween.getPercent: 0 at start, 0.5 at midpoint, 1 at completion', () =>
469
- {
470
- const t = new Tween(() => {}, 0, 10, 1);
471
- assert.equal(t.getPercent(), 0);
472
- tweenUpdate(0.5);
473
- assert(near(t.getPercent(), 0.5));
474
- tweenUpdate(0.5); // completes the tween
475
- assert.equal(t.getPercent(), 1);
476
- });
477
-
478
- test('Tween.getPercent clamps overshoot to 1', () =>
479
- {
480
- const t = new Tween(() => {}, 0, 10, 1);
481
- tweenUpdate(5.0); // overshoot
482
- assert.equal(t.getPercent(), 1);
483
- });
484
-
485
- test('tweenStopAll stops every active tween and prevents then-callbacks from firing', () =>
486
- {
487
- let cbCalls = 0;
488
- let thenCalls = 0;
489
- new Tween(() => cbCalls++, 0, 1, 1).then(() => thenCalls += 100);
490
- new Tween(() => cbCalls++, 0, 1, 1).then(() => thenCalls += 100);
491
- new Tween(() => cbCalls++, 0, 1, 1);
492
- // 3 callbacks fired from constructors snapping to start
493
- assert.equal(cbCalls, 3);
494
- tweenStopAll();
495
- tweenUpdate(2.0); // would normally complete every tween, but they're stopped
496
- assert.equal(cbCalls, 3); // no advancement — still just the snaps
497
- assert.equal(thenCalls, 0); // no then-callbacks fired
498
- });
499
-
500
- test('Tween accepts Vector2 start/end and interpolates each component', () =>
501
- {
502
- const calls = [];
503
- new Tween((v) => calls.push(v), vec2(0, 0), vec2(10, 20), 1);
504
- // Constructor snap fires the start value
505
- assert.equal(calls.length, 1);
506
- assert(calls[0] instanceof Vector2);
507
- assert(near(calls[0].x, 0));
508
- assert(near(calls[0].y, 0));
509
-
510
- // Halfway: interpolated component-wise
511
- tweenUpdate(0.5);
512
- assert.equal(calls.length, 2);
513
- assert(near(calls[1].x, 5));
514
- assert(near(calls[1].y, 10));
515
-
516
- // Completion: end value fires once
517
- tweenUpdate(0.5);
518
- assert.equal(calls.length, 3);
519
- assert(near(calls[2].x, 10));
520
- assert(near(calls[2].y, 20));
521
- });
522
-
523
- test('Tween accepts Color start/end and interpolates each channel', () =>
524
- {
525
- const calls = [];
526
- new Tween((c) => calls.push(c), rgb(0, 0, 0, 1), rgb(1, 1, 1, 0), 1);
527
- // Halfway should be (0.5, 0.5, 0.5, 0.5)
528
- tweenUpdate(0.5);
529
- const mid = calls[calls.length - 1];
530
- assert(mid instanceof Color);
531
- assert(near(mid.r, 0.5));
532
- assert(near(mid.g, 0.5));
533
- assert(near(mid.b, 0.5));
534
- assert(near(mid.a, 0.5));
535
- });
536
-
537
- test('tweenProperty walks dot-paths and assigns Vector2 values', () =>
538
- {
539
- const obj = { pos: vec2(0, 0) };
540
- tweenProperty(obj, 'pos', vec2(0, 0), vec2(10, 20), 1);
541
- // Constructor snap: obj.pos is replaced with a fresh Vector2 equal to start
542
- assert(obj.pos instanceof Vector2);
543
- assert(near(obj.pos.x, 0));
544
- assert(near(obj.pos.y, 0));
545
-
546
- // Completion
547
- tweenUpdate(1.0);
548
- assert(near(obj.pos.x, 10));
549
- assert(near(obj.pos.y, 20));
550
- });
551
-
552
- test('Tween throws on type mismatch (Vector2 start, Color end)', () =>
553
- {
554
- assert.throws(() => new Tween(() => {}, vec2(0, 0), rgb(1, 0, 0, 1), 1));
555
- });
556
-
557
- test('Tween.getValue returns the current interpolated value', () =>
558
- {
559
- const t = new Tween(() => {}, 0, 100, 1);
560
- assert.equal(t.getValue(), 0); // at construction
561
- tweenUpdate(0.25);
562
- assert(near(t.getValue(), 25));
563
- tweenUpdate(0.25);
564
- assert(near(t.getValue(), 50));
565
- t.stop();
566
- });
567
-
568
- test('Tween.getValue returns a Vector2 for Vector2 tweens', () =>
569
- {
570
- const t = new Tween(() => {}, vec2(0, 0), vec2(10, 20), 1);
571
- const v = t.getValue();
572
- assert(v instanceof Vector2);
573
- assert(near(v.x, 0));
574
- assert(near(v.y, 0));
575
- t.stop();
576
- });
@@ -1,80 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { isNumber, isString, isArray, isVector2, isColor, vec2, rgb, Vector2, Color, formatTime } from '../dist/littlejs.esm.js';
4
-
5
- test('isNumber', () =>
6
- {
7
- assert.equal(isNumber(0), true);
8
- assert.equal(isNumber(-1.5), true);
9
- assert.equal(isNumber(Infinity), true);
10
- assert.equal(isNumber(NaN), false);
11
- assert.equal(isNumber('1'), false);
12
- assert.equal(isNumber(null), false);
13
- assert.equal(isNumber(undefined), false);
14
- assert.equal(isNumber({}), false);
15
- });
16
-
17
- test('isString', () =>
18
- {
19
- // Implementation is `s != null && typeof s?.toString() === 'string'`, so it
20
- // accepts anything with a toString — including arrays, objects, numbers.
21
- // Only null/undefined are rejected. These cases pin that (surprising) contract
22
- // so a future "tightening" to `typeof s === 'string'` would fail here loudly.
23
- assert.equal(isString(''), true);
24
- assert.equal(isString('abc'), true);
25
- assert.equal(isString(42), true);
26
- assert.equal(isString([]), true);
27
- assert.equal(isString({}), true);
28
- assert.equal(isString(null), false);
29
- assert.equal(isString(undefined), false);
30
- });
31
-
32
- test('isArray', () =>
33
- {
34
- assert.equal(isArray([]), true);
35
- assert.equal(isArray([1, 2, 3]), true);
36
- assert.equal(isArray({}), false);
37
- assert.equal(isArray('abc'), false);
38
- assert.equal(isArray(null), false);
39
- });
40
-
41
- test('isVector2', () =>
42
- {
43
- // Contract: instanceof Vector2 AND isValid() (no NaN components).
44
- assert.equal(isVector2(vec2()), true);
45
- assert.equal(isVector2(new Vector2(1, 2)), true);
46
- assert.equal(isVector2({ x: 1, y: 2 }), false);
47
- assert.equal(isVector2(null), false);
48
- assert.equal(isVector2(undefined), false);
49
- assert.equal(isVector2([1, 2]), false);
50
- // post-hoc NaN mutation: still a Vector2 instance but no longer valid
51
- const bad = vec2(1, 2); bad.x = NaN;
52
- assert.equal(isVector2(bad), false);
53
- });
54
-
55
- test('isColor', () =>
56
- {
57
- // Contract: instanceof Color AND isValid() (no NaN components).
58
- assert.equal(isColor(rgb()), true);
59
- assert.equal(isColor(new Color(1, 0, 0)), true);
60
- assert.equal(isColor({ r: 1, g: 1, b: 1, a: 1 }), false);
61
- assert.equal(isColor(null), false);
62
- assert.equal(isColor(undefined), false);
63
- const bad = rgb(1, 0, 0); bad.g = NaN;
64
- assert.equal(isColor(bad), false);
65
- });
66
-
67
- test('formatTime', () =>
68
- {
69
- assert.equal(formatTime(0), '0:00');
70
- assert.equal(formatTime(5), '0:05');
71
- assert.equal(formatTime(59), '0:59');
72
- assert.equal(formatTime(60), '1:00');
73
- assert.equal(formatTime(125), '2:05');
74
- assert.equal(formatTime(3599), '59:59');
75
- // fractional seconds are truncated (floor)
76
- assert.equal(formatTime(1.9), '0:01');
77
- // negative times get a leading minus
78
- assert.equal(formatTime(-30), '-0:30');
79
- assert.equal(formatTime(-125), '-2:05');
80
- });