littlejsengine 1.18.2 → 1.18.4

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.
@@ -0,0 +1,576 @@
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,7 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(npm test *)"
5
- ]
6
- }
7
- }