@flighthq/easing 0.3.0-next.1315.1d4eb64 → 0.3.0-next.1392.f0bd0cc
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/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -0
- package/dist/contract.js.map +1 -1
- package/dist/enableEasingGuards.d.ts +3 -0
- package/dist/enableEasingGuards.d.ts.map +1 -0
- package/dist/enableEasingGuards.js +25 -0
- package/dist/enableEasingGuards.js.map +1 -0
- package/package.json +3 -2
- package/src/easeSteps.test.ts +18 -19
- package/src/enableEasingGuards.test.ts +70 -0
- package/src/easeStepsGuard.test.ts +0 -34
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableEasingGuards.d.ts","sourceRoot":"","sources":["../src/enableEasingGuards.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAWD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
+
import { LogLevel } from '@flighthq/types/contract';
|
|
3
|
+
import { setEasingStepsGuard } from './easeSteps';
|
|
4
|
+
// Uninstalls the guard installed by enableEasingGuards.
|
|
5
|
+
export function disableEasingGuards() {
|
|
6
|
+
setEasingStepsGuard(null);
|
|
7
|
+
}
|
|
8
|
+
// Installs the caller-facing easing guard (opt-in, dev-only). This package has one silent footgun:
|
|
9
|
+
// `easeSteps(count, 'jumpNone')` with `count < 2` leaves zero jumps, so the returned function divides by
|
|
10
|
+
// zero and hands back NaN for every input. Nothing throws and nothing logs — the NaN just propagates into
|
|
11
|
+
// whatever the easing drives, which is why a comment telling callers "pass count >= 2" was not enough.
|
|
12
|
+
//
|
|
13
|
+
// @flighthq/easing is a CORE package, and core may otherwise depend only on types/core. This module is the
|
|
14
|
+
// sanctioned exception: it is separately importable and shakeable, so a build that never imports it never
|
|
15
|
+
// pulls @flighthq/log, and the layer rule's actual concern — feature weight in core's always-loaded graph —
|
|
16
|
+
// does not arise. `packages:check` enforces that the import appears in guard modules only. Idempotent.
|
|
17
|
+
export function enableEasingGuards() {
|
|
18
|
+
setEasingStepsGuard(warnOnDegenerateSteps);
|
|
19
|
+
}
|
|
20
|
+
function warnOnDegenerateSteps(count, position) {
|
|
21
|
+
logOnce('easing:degenerate-steps', LogLevel.Warn, {
|
|
22
|
+
message: `easeSteps(${count}, '${position}'): '${position}' has count - 1 jumps, so it needs count >= 2. With ${count} there are no jumps, the easing divides by zero, and every input returns NaN. Pass count >= 2, or use a position that jumps at an edge ('jumpEnd', 'jumpStart', 'jumpBoth') — those are defined for count >= 1.`,
|
|
23
|
+
}, 'easing');
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=enableEasingGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableEasingGuards.js","sourceRoot":"","sources":["../src/enableEasingGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,wDAAwD;AACxD,MAAM,UAAU,mBAAmB;IACjC,mBAAmB,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,mGAAmG;AACnG,yGAAyG;AACzG,0GAA0G;AAC1G,uGAAuG;AACvG,EAAE;AACF,2GAA2G;AAC3G,0GAA0G;AAC1G,4GAA4G;AAC5G,uGAAuG;AACvG,MAAM,UAAU,kBAAkB;IAChC,mBAAmB,CAAC,qBAAqB,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa,EAAE,QAAsB;IAClE,OAAO,CACL,yBAAyB,EACzB,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EAAE,aAAa,KAAK,MAAM,QAAQ,QAAQ,QAAQ,uDAAuD,KAAK,iNAAiN;KACvU,EACD,QAAQ,CACT,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/easing",
|
|
3
|
-
"version": "0.3.0-next.
|
|
3
|
+
"version": "0.3.0-next.1392.f0bd0cc",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@flighthq/
|
|
39
|
+
"@flighthq/log": "0.3.0-next.1392.f0bd0cc",
|
|
40
|
+
"@flighthq/types": "0.3.0-next.1392.f0bd0cc"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"typescript": "^5.3.0"
|
package/src/easeSteps.test.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { StepPosition } from '@flighthq/types/contract';
|
|
2
|
+
|
|
1
3
|
import { easeSteps, setEasingStepsGuard } from './easeSteps';
|
|
2
4
|
|
|
3
5
|
describe('easeSteps', () => {
|
|
@@ -65,34 +67,31 @@ describe('easeSteps', () => {
|
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
describe('setEasingStepsGuard', () => {
|
|
68
|
-
it('
|
|
69
|
-
const
|
|
70
|
-
setEasingStepsGuard((count, position) =>
|
|
70
|
+
it('reports the degenerate jumpNone call that silently returns NaN', () => {
|
|
71
|
+
const seen: [number, StepPosition][] = [];
|
|
72
|
+
setEasingStepsGuard((count, position) => seen.push([count, position]));
|
|
71
73
|
try {
|
|
72
|
-
|
|
73
|
-
expect(
|
|
74
|
+
// The failure being reported: one step with no edge jump leaves zero jumps to divide by.
|
|
75
|
+
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
74
76
|
} finally {
|
|
75
77
|
setEasingStepsGuard(null);
|
|
76
78
|
}
|
|
79
|
+
expect(seen).toEqual([[1, 'jumpNone']]);
|
|
77
80
|
});
|
|
78
81
|
|
|
79
|
-
it('
|
|
80
|
-
const
|
|
81
|
-
setEasingStepsGuard((count
|
|
82
|
+
it('stays silent for well-defined calls, and once uninstalled', () => {
|
|
83
|
+
const seen: number[] = [];
|
|
84
|
+
setEasingStepsGuard((count) => seen.push(count));
|
|
82
85
|
try {
|
|
83
|
-
easeSteps(
|
|
84
|
-
easeSteps(1, 'jumpEnd');
|
|
85
|
-
|
|
86
|
+
easeSteps(2, 'jumpNone')(0.5); // count >= 2 is the documented minimum for jumpNone
|
|
87
|
+
easeSteps(1, 'jumpEnd')(0.5); // edge-jumping positions are defined for count >= 1
|
|
88
|
+
easeSteps(1, 'jumpBoth')(0.5);
|
|
86
89
|
} finally {
|
|
87
90
|
setEasingStepsGuard(null);
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
setEasingStepsGuard((count, position) => calls.push([count, position]));
|
|
94
|
-
setEasingStepsGuard(null);
|
|
95
|
-
easeSteps(1, 'jumpNone');
|
|
96
|
-
expect(calls).toEqual([]);
|
|
92
|
+
expect(seen).toEqual([]);
|
|
93
|
+
// Uninstalled: the degenerate call still returns NaN, but nothing is reported.
|
|
94
|
+
easeSteps(1, 'jumpNone')(0.5);
|
|
95
|
+
expect(seen).toEqual([]);
|
|
97
96
|
});
|
|
98
97
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
2
|
+
import type { LogEntry } from '@flighthq/types/contract';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { easeSteps } from './easeSteps';
|
|
6
|
+
import { disableEasingGuards, enableEasingGuards } from './enableEasingGuards';
|
|
7
|
+
|
|
8
|
+
function captureLog(run: () => void): readonly LogEntry[] {
|
|
9
|
+
const sink = createMemoryLogSink(8);
|
|
10
|
+
addLogSink(sink.sink);
|
|
11
|
+
try {
|
|
12
|
+
run();
|
|
13
|
+
return getMemoryLogSinkEntries(sink);
|
|
14
|
+
} finally {
|
|
15
|
+
removeLogSink(sink.sink);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function messageOf(entry: Readonly<LogEntry>): string {
|
|
20
|
+
const data = entry.data;
|
|
21
|
+
return typeof data === 'string' ? data : String(data.message);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('disableEasingGuards', () => {
|
|
25
|
+
it('uninstalls the guard, returning the degenerate call to silence', () => {
|
|
26
|
+
const entries = captureLog(() => {
|
|
27
|
+
enableEasingGuards();
|
|
28
|
+
disableEasingGuards();
|
|
29
|
+
// Still NaN — disabling removes the reporting, not the sharp edge.
|
|
30
|
+
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
expect(entries.length).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('enableEasingGuards', () => {
|
|
37
|
+
it('WARNS on the degenerate jumpNone step count that silently returns NaN', () => {
|
|
38
|
+
const entries = captureLog(() => {
|
|
39
|
+
enableEasingGuards();
|
|
40
|
+
try {
|
|
41
|
+
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
42
|
+
} finally {
|
|
43
|
+
disableEasingGuards();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
expect(entries.length).toBe(1);
|
|
47
|
+
expect(messageOf(entries[0])).toContain('count >= 2');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('stays SILENT for well-defined calls, and without the guard at all', () => {
|
|
51
|
+
const guarded = captureLog(() => {
|
|
52
|
+
enableEasingGuards();
|
|
53
|
+
try {
|
|
54
|
+
easeSteps(2, 'jumpNone')(0.5);
|
|
55
|
+
easeSteps(1, 'jumpEnd')(0.5);
|
|
56
|
+
easeSteps(1, 'jumpStart')(0.5);
|
|
57
|
+
easeSteps(1, 'jumpBoth')(0.5);
|
|
58
|
+
} finally {
|
|
59
|
+
disableEasingGuards();
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
expect(guarded.length).toBe(0);
|
|
63
|
+
|
|
64
|
+
// Production default: still NaN, but nothing is logged — the guard is opt-in.
|
|
65
|
+
const unguarded = captureLog(() => {
|
|
66
|
+
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
expect(unguarded.length).toBe(0);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { StepPosition } from '@flighthq/types/contract';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
|
|
4
|
-
import { easeSteps, setEasingStepsGuard } from './easeSteps';
|
|
5
|
-
|
|
6
|
-
describe('setEasingStepsGuard', () => {
|
|
7
|
-
it('reports the degenerate jumpNone call that silently returns NaN', () => {
|
|
8
|
-
const seen: [number, StepPosition][] = [];
|
|
9
|
-
setEasingStepsGuard((count, position) => seen.push([count, position]));
|
|
10
|
-
try {
|
|
11
|
-
// The failure being reported: one step with no edge jump leaves zero jumps to divide by.
|
|
12
|
-
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
13
|
-
} finally {
|
|
14
|
-
setEasingStepsGuard(null);
|
|
15
|
-
}
|
|
16
|
-
expect(seen).toEqual([[1, 'jumpNone']]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('stays silent for well-defined calls, and when no guard is installed', () => {
|
|
20
|
-
const seen: number[] = [];
|
|
21
|
-
setEasingStepsGuard((count) => seen.push(count));
|
|
22
|
-
try {
|
|
23
|
-
easeSteps(2, 'jumpNone')(0.5); // count >= 2 is the documented minimum for jumpNone
|
|
24
|
-
easeSteps(1, 'jumpEnd')(0.5); // every edge-jumping position is defined for count >= 1
|
|
25
|
-
easeSteps(1, 'jumpStart')(0.5);
|
|
26
|
-
easeSteps(1, 'jumpBoth')(0.5);
|
|
27
|
-
} finally {
|
|
28
|
-
setEasingStepsGuard(null);
|
|
29
|
-
}
|
|
30
|
-
expect(seen).toEqual([]);
|
|
31
|
-
// Production default: still NaN, but nothing is reported.
|
|
32
|
-
expect(Number.isNaN(easeSteps(1, 'jumpNone')(0.5))).toBe(true);
|
|
33
|
-
});
|
|
34
|
-
});
|