|
2 | 2 | // bindings #4 — Node ESM harness for the motion library binding. |
3 | 3 | // |
4 | 4 | // Installs a globalThis.__as_motion mock before importing the |
5 | | -// generated module, drives smokeAnimate / smokeCancel, and asserts |
6 | | -// the arguments + cancel side-effect were observed. |
| 5 | +// generated module, then drives every wrapper (animate / cancel + |
| 6 | +// animateMini / tween / spring / ease) and asserts the arguments + |
| 7 | +// return values land as expected. |
7 | 8 |
|
8 | 9 | import assert from "node:assert/strict"; |
9 | 10 |
|
10 | 11 | let lastAnimateCall = null; |
| 12 | +let lastAnimateMiniCall = null; |
| 13 | +let lastTweenCall = null; |
| 14 | +let lastSpringCall = null; |
| 15 | +let lastEaseName = null; |
11 | 16 | let cancelCount = 0; |
12 | 17 |
|
| 18 | +const makeControls = (label) => ({ |
| 19 | + __label: label, |
| 20 | + then(cb) { if (cb) cb(); return this; }, |
| 21 | + cancel() { cancelCount += 1; }, |
| 22 | +}); |
| 23 | + |
13 | 24 | globalThis.__as_motion = { |
14 | 25 | animate(target, keyframes, options) { |
15 | 26 | lastAnimateCall = { target, keyframes, options }; |
16 | | - return { |
17 | | - then(cb) { if (cb) cb(); return this; }, |
18 | | - cancel() { cancelCount += 1; }, |
19 | | - }; |
| 27 | + return makeControls("animate"); |
| 28 | + }, |
| 29 | + animateMini(target, keyframes, options) { |
| 30 | + lastAnimateMiniCall = { target, keyframes, options }; |
| 31 | + return makeControls("animateMini"); |
| 32 | + }, |
| 33 | + tween(target, from, to, options) { |
| 34 | + lastTweenCall = { target, from, to, options }; |
| 35 | + return makeControls("tween"); |
| 36 | + }, |
| 37 | + spring(target, keyframes, springConfig) { |
| 38 | + lastSpringCall = { target, keyframes, springConfig }; |
| 39 | + return makeControls("spring"); |
| 40 | + }, |
| 41 | + ease(name) { |
| 42 | + lastEaseName = name; |
| 43 | + // Return a sentinel function so consumers can verify the value |
| 44 | + // round-trips through an options record unchanged. |
| 45 | + const fn = (t) => t; |
| 46 | + fn.__easingName = name; |
| 47 | + return fn; |
20 | 48 | }, |
21 | 49 | }; |
22 | 50 |
|
23 | | -const { smokeAnimate, smokeCancel } = await import("./motion_smoke.deno.js"); |
| 51 | +const { |
| 52 | + smokeAnimate, |
| 53 | + smokeCancel, |
| 54 | + smokeAnimateMini, |
| 55 | + smokeTween, |
| 56 | + smokeSpring, |
| 57 | + smokeEase, |
| 58 | +} = await import("./motion_smoke.deno.js"); |
24 | 59 |
|
| 60 | +// ---- animate + cancel (original surface) ---- |
25 | 61 | const controls = smokeAnimate("#player", { x: 100, opacity: 0.5 }, { duration: 1.0 }); |
26 | | -assert.equal(lastAnimateCall.target, "#player", "target reaches host"); |
27 | | -assert.deepEqual(lastAnimateCall.keyframes, { x: 100, opacity: 0.5 }, "keyframes reach host"); |
28 | | -assert.deepEqual(lastAnimateCall.options, { duration: 1.0 }, "options reach host"); |
| 62 | +assert.equal(lastAnimateCall.target, "#player", "animate target reaches host"); |
| 63 | +assert.deepEqual(lastAnimateCall.keyframes, { x: 100, opacity: 0.5 }, "animate keyframes reach host"); |
| 64 | +assert.deepEqual(lastAnimateCall.options, { duration: 1.0 }, "animate options reach host"); |
29 | 65 |
|
30 | 66 | assert.equal(smokeCancel(controls), 0, "cancel returns 0"); |
31 | 67 | assert.equal(cancelCount, 1, "cancel invoked exactly once"); |
32 | 68 |
|
33 | | -// Cancel a null-controls value is a no-op (mock controls without .cancel) |
34 | 69 | assert.equal(smokeCancel({}), 0, "cancel on bare object returns 0"); |
35 | 70 | assert.equal(cancelCount, 1, "cancel count unchanged for bare object"); |
36 | 71 |
|
| 72 | +// ---- animateMini ---- |
| 73 | +const miniControls = smokeAnimateMini("#hud", { y: 50 }, { duration: 0.25 }); |
| 74 | +assert.equal(lastAnimateMiniCall.target, "#hud", "animateMini target reaches host"); |
| 75 | +assert.deepEqual(lastAnimateMiniCall.keyframes, { y: 50 }, "animateMini keyframes reach host"); |
| 76 | +assert.deepEqual(lastAnimateMiniCall.options, { duration: 0.25 }, "animateMini options reach host"); |
| 77 | +assert.equal(miniControls.__label, "animateMini", "animateMini returns its controls handle"); |
| 78 | + |
| 79 | +// ---- tween ---- |
| 80 | +const tweenControls = smokeTween("#enemy", { x: 0 }, { x: 200 }, { duration: 0.6 }); |
| 81 | +assert.equal(lastTweenCall.target, "#enemy", "tween target reaches host"); |
| 82 | +assert.deepEqual(lastTweenCall.from, { x: 0 }, "tween from reaches host"); |
| 83 | +assert.deepEqual(lastTweenCall.to, { x: 200 }, "tween to reaches host"); |
| 84 | +assert.deepEqual(lastTweenCall.options, { duration: 0.6 }, "tween options reach host"); |
| 85 | +assert.equal(tweenControls.__label, "tween", "tween returns its controls handle"); |
| 86 | + |
| 87 | +// ---- spring ---- |
| 88 | +const springControls = smokeSpring("#bubble", { scale: 1.4 }, { stiffness: 240, damping: 18, mass: 1 }); |
| 89 | +assert.equal(lastSpringCall.target, "#bubble", "spring target reaches host"); |
| 90 | +assert.deepEqual(lastSpringCall.keyframes, { scale: 1.4 }, "spring keyframes reach host"); |
| 91 | +assert.deepEqual(lastSpringCall.springConfig, { stiffness: 240, damping: 18, mass: 1 }, "spring config reaches host"); |
| 92 | +assert.equal(springControls.__label, "spring", "spring returns its controls handle"); |
| 93 | + |
| 94 | +// ---- ease ---- |
| 95 | +const easing = smokeEase("backOut"); |
| 96 | +assert.equal(lastEaseName, "backOut", "ease name reaches host"); |
| 97 | +assert.equal(typeof easing, "function", "ease returns an opaque easing-function value"); |
| 98 | +assert.equal(easing.__easingName, "backOut", "easing value carries its name through the boundary"); |
| 99 | + |
37 | 100 | console.log("motion_smoke.harness.mjs OK"); |
0 commit comments