@holoscript/std 2.1.0 → 3.1.1

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 (85) hide show
  1. package/README.md +197 -0
  2. package/dist/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
  3. package/dist/__tests__/EconomicTraits.test.d.ts.map +1 -0
  4. package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +1 -0
  5. package/dist/__tests__/Sprint33.test.d.ts.map +1 -0
  6. package/dist/__tests__/Sprint56.test.d.ts.map +1 -0
  7. package/dist/__tests__/collections.test.d.ts.map +1 -0
  8. package/dist/__tests__/events.test.d.ts.map +1 -0
  9. package/dist/__tests__/materials.test.d.ts.map +1 -0
  10. package/dist/__tests__/math.test.d.ts.map +1 -0
  11. package/dist/__tests__/physics.test.d.ts.map +1 -0
  12. package/dist/__tests__/spatial.test.d.ts.map +1 -0
  13. package/dist/__tests__/string-decoupled.test.d.ts.map +1 -0
  14. package/dist/__tests__/string.test.d.ts.map +1 -0
  15. package/dist/__tests__/time.test.d.ts.map +1 -0
  16. package/dist/collections.d.ts.map +1 -1
  17. package/dist/events.d.ts.map +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +42 -9
  20. package/dist/materials.d.ts.map +1 -0
  21. package/dist/math.d.ts.map +1 -1
  22. package/dist/physics.d.ts.map +1 -0
  23. package/dist/spatial.d.ts.map +1 -0
  24. package/dist/string.d.ts.map +1 -1
  25. package/dist/time.d.ts.map +1 -1
  26. package/dist/traits/ARTraits.d.ts.map +1 -0
  27. package/dist/traits/EconomicPrimitives.d.ts.map +1 -0
  28. package/dist/traits/EconomicTraits.d.ts.map +1 -0
  29. package/dist/traits/IoTTraits.d.ts.map +1 -0
  30. package/dist/traits/SimulationLabPrimitives.d.ts.map +1 -0
  31. package/dist/traits/SimulationLabTraits.d.ts.map +1 -0
  32. package/dist/traits/VRRTraits.d.ts.map +1 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +17 -6
  35. package/src/__tests__/EconomicPrimitives.test.ts +690 -0
  36. package/src/__tests__/EconomicTraits.test.ts +425 -0
  37. package/src/__tests__/SimulationLabPrimitives.test.ts +415 -0
  38. package/src/__tests__/Sprint33.test.ts +1301 -0
  39. package/src/__tests__/Sprint56.test.ts +1070 -0
  40. package/src/__tests__/collections.test.ts +182 -0
  41. package/src/__tests__/events.test.ts +135 -0
  42. package/src/__tests__/materials.test.ts +84 -0
  43. package/src/__tests__/math.test.ts +246 -0
  44. package/src/__tests__/physics.test.ts +83 -0
  45. package/src/__tests__/spatial.test.ts +333 -0
  46. package/src/__tests__/string-decoupled.test.ts +16 -0
  47. package/src/__tests__/string.test.ts +164 -0
  48. package/src/__tests__/time.test.ts +110 -0
  49. package/src/collections.ts +8 -2
  50. package/src/events.ts +88 -0
  51. package/src/index.ts +161 -11
  52. package/src/materials.ts +109 -0
  53. package/src/math.ts +30 -14
  54. package/src/physics.ts +141 -0
  55. package/src/spatial.ts +320 -0
  56. package/src/string.basic.test.ts +14 -0
  57. package/src/string.test.ts +335 -0
  58. package/src/string.ts +19 -2
  59. package/src/time.ts +9 -10
  60. package/src/traits/ARTraits.ts +103 -0
  61. package/src/traits/EconomicPrimitives.ts +755 -0
  62. package/src/traits/EconomicTraits.ts +552 -0
  63. package/src/traits/IoTTraits.ts +102 -0
  64. package/src/traits/SimulationLabPrimitives.ts +650 -0
  65. package/src/traits/SimulationLabTraits.ts +191 -0
  66. package/src/traits/VRRTraits.ts +326 -0
  67. package/src/types.ts +47 -12
  68. package/vitest.config.ts +10 -0
  69. package/dist/collections.d.ts +0 -177
  70. package/dist/collections.js +0 -720
  71. package/dist/collections.js.map +0 -1
  72. package/dist/index.d.ts +0 -89
  73. package/dist/index.js.map +0 -1
  74. package/dist/math.d.ts +0 -162
  75. package/dist/math.js +0 -539
  76. package/dist/math.js.map +0 -1
  77. package/dist/string.d.ts +0 -208
  78. package/dist/string.js +0 -443
  79. package/dist/string.js.map +0 -1
  80. package/dist/time.d.ts +0 -215
  81. package/dist/time.js +0 -530
  82. package/dist/time.js.map +0 -1
  83. package/dist/types.d.ts +0 -193
  84. package/dist/types.js +0 -198
  85. package/dist/types.js.map +0 -1
@@ -0,0 +1,182 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { List } from '../collections.js';
3
+
4
+ describe('List', () => {
5
+ describe('construction', () => {
6
+ it('should create empty list', () => {
7
+ const list = new List<number>();
8
+ expect(list.length).toBe(0);
9
+ expect(list.isEmpty).toBe(true);
10
+ });
11
+
12
+ it('should create from array', () => {
13
+ const list = new List([1, 2, 3]);
14
+ expect(list.length).toBe(3);
15
+ });
16
+
17
+ it('should create with List.of()', () => {
18
+ const list = List.of(1, 2, 3);
19
+ expect(list.length).toBe(3);
20
+ });
21
+
22
+ it('should create with List.from()', () => {
23
+ const list = List.from([1, 2, 3]);
24
+ expect(list.length).toBe(3);
25
+ });
26
+ });
27
+
28
+ describe('List.range()', () => {
29
+ it('should create range with positive step', () => {
30
+ const list = List.range(0, 5, 1);
31
+ expect(list.toArray()).toEqual([0, 1, 2, 3, 4]);
32
+ });
33
+
34
+ it('should create range with default step', () => {
35
+ const list = List.range(0, 3);
36
+ expect(list.toArray()).toEqual([0, 1, 2]);
37
+ });
38
+
39
+ it('should create range with custom step', () => {
40
+ const list = List.range(0, 10, 2);
41
+ expect(list.toArray()).toEqual([0, 2, 4, 6, 8]);
42
+ });
43
+
44
+ it('should create range with negative step', () => {
45
+ const list = List.range(5, 0, -1);
46
+ expect(list.toArray()).toEqual([5, 4, 3, 2, 1]);
47
+ });
48
+ });
49
+
50
+ describe('List.repeat()', () => {
51
+ it('should create list with repeated values', () => {
52
+ const list = List.repeat('a', 3);
53
+ expect(list.toArray()).toEqual(['a', 'a', 'a']);
54
+ });
55
+
56
+ it('should create empty list with count 0', () => {
57
+ const list = List.repeat('a', 0);
58
+ expect(list.isEmpty).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe('accessors', () => {
63
+ const list = List.of(10, 20, 30, 40, 50);
64
+
65
+ it('should get element by positive index', () => {
66
+ expect(list.get(0)).toBe(10);
67
+ expect(list.get(2)).toBe(30);
68
+ });
69
+
70
+ it('should get element by negative index', () => {
71
+ expect(list.get(-1)).toBe(50);
72
+ expect(list.get(-2)).toBe(40);
73
+ });
74
+
75
+ it('should return undefined for out of bounds', () => {
76
+ expect(list.get(10)).toBeUndefined();
77
+ });
78
+
79
+ it('should get first element', () => {
80
+ expect(list.first()).toBe(10);
81
+ });
82
+
83
+ it('should get last element', () => {
84
+ expect(list.last()).toBe(50);
85
+ });
86
+
87
+ it('should return undefined for first/last on empty list', () => {
88
+ const empty = new List<number>();
89
+ expect(empty.first()).toBeUndefined();
90
+ expect(empty.last()).toBeUndefined();
91
+ });
92
+ });
93
+
94
+ describe('map', () => {
95
+ it('should map over elements', () => {
96
+ const list = List.of(1, 2, 3);
97
+ const doubled = list.map((x) => x * 2);
98
+ expect(doubled.toArray()).toEqual([2, 4, 6]);
99
+ });
100
+
101
+ it('should receive index in map callback', () => {
102
+ const list = List.of('a', 'b', 'c');
103
+ const indexed = list.map((_, i) => i);
104
+ expect(indexed.toArray()).toEqual([0, 1, 2]);
105
+ });
106
+
107
+ it('should return new List instance', () => {
108
+ const list = List.of(1, 2, 3);
109
+ const mapped = list.map((x) => x);
110
+ expect(mapped).not.toBe(list);
111
+ });
112
+ });
113
+
114
+ describe('filter', () => {
115
+ it('should filter elements', () => {
116
+ const list = List.of(1, 2, 3, 4, 5);
117
+ const evens = list.filter((x) => x % 2 === 0);
118
+ expect(evens.toArray()).toEqual([2, 4]);
119
+ });
120
+
121
+ it('should return empty list when nothing matches', () => {
122
+ const list = List.of(1, 3, 5);
123
+ const evens = list.filter((x) => x % 2 === 0);
124
+ expect(evens.isEmpty).toBe(true);
125
+ });
126
+ });
127
+
128
+ describe('flatMap', () => {
129
+ it('should flatMap with arrays', () => {
130
+ const list = List.of(1, 2, 3);
131
+ const result = list.flatMap((x) => [x, x * 10]);
132
+ expect(result.toArray()).toEqual([1, 10, 2, 20, 3, 30]);
133
+ });
134
+
135
+ it('should flatMap with Lists', () => {
136
+ const list = List.of(1, 2);
137
+ const result = list.flatMap((x) => List.of(x, x + 1));
138
+ expect(result.toArray()).toEqual([1, 2, 2, 3]);
139
+ });
140
+ });
141
+
142
+ describe('reduce', () => {
143
+ it('should reduce with initial value', () => {
144
+ const list = List.of(1, 2, 3, 4);
145
+ const sum = list.reduce((acc, x) => acc + x, 0);
146
+ expect(sum).toBe(10);
147
+ });
148
+ });
149
+
150
+ describe('immutability', () => {
151
+ it('operations should return new List instances', () => {
152
+ const original = List.of(1, 2, 3);
153
+ const mapped = original.map((x) => x * 2);
154
+ const filtered = original.filter((x) => x > 1);
155
+
156
+ expect(original.toArray()).toEqual([1, 2, 3]);
157
+ expect(mapped.toArray()).toEqual([2, 4, 6]);
158
+ expect(filtered.toArray()).toEqual([2, 3]);
159
+ });
160
+ });
161
+
162
+ describe('toArray', () => {
163
+ it('should return array copy', () => {
164
+ const list = List.of(1, 2, 3);
165
+ const arr = list.toArray();
166
+ expect(arr).toEqual([1, 2, 3]);
167
+ arr.push(4);
168
+ expect(list.length).toBe(3); // Original unchanged
169
+ });
170
+ });
171
+
172
+ describe('iteration', () => {
173
+ it('should be iterable', () => {
174
+ const list = List.of(1, 2, 3);
175
+ const result: number[] = [];
176
+ for (const item of list) {
177
+ result.push(item);
178
+ }
179
+ expect(result).toEqual([1, 2, 3]);
180
+ });
181
+ });
182
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { EventBus } from '../events.js';
3
+
4
+ describe('EventBus', () => {
5
+ describe('on / emit', () => {
6
+ it('calls handler on emit', () => {
7
+ const bus = new EventBus();
8
+ const fn = vi.fn();
9
+ bus.on('test', fn);
10
+ bus.emit('test', 42);
11
+ expect(fn).toHaveBeenCalledWith(42);
12
+ });
13
+
14
+ it('calls handler multiple times', () => {
15
+ const bus = new EventBus();
16
+ const fn = vi.fn();
17
+ bus.on('x', fn);
18
+ bus.emit('x');
19
+ bus.emit('x');
20
+ bus.emit('x');
21
+ expect(fn).toHaveBeenCalledTimes(3);
22
+ });
23
+
24
+ it('handles multiple listeners on same event', () => {
25
+ const bus = new EventBus();
26
+ const fn1 = vi.fn();
27
+ const fn2 = vi.fn();
28
+ bus.on('e', fn1);
29
+ bus.on('e', fn2);
30
+ bus.emit('e', 'data');
31
+ expect(fn1).toHaveBeenCalledWith('data');
32
+ expect(fn2).toHaveBeenCalledWith('data');
33
+ });
34
+
35
+ it('does not call unrelated listeners', () => {
36
+ const bus = new EventBus();
37
+ const fn = vi.fn();
38
+ bus.on('a', fn);
39
+ bus.emit('b');
40
+ expect(fn).not.toHaveBeenCalled();
41
+ });
42
+ });
43
+
44
+ describe('once', () => {
45
+ it('calls handler only once', () => {
46
+ const bus = new EventBus();
47
+ const fn = vi.fn();
48
+ bus.once('x', fn);
49
+ bus.emit('x', 1);
50
+ bus.emit('x', 2);
51
+ expect(fn).toHaveBeenCalledTimes(1);
52
+ expect(fn).toHaveBeenCalledWith(1);
53
+ });
54
+ });
55
+
56
+ describe('off', () => {
57
+ it('removes a listener', () => {
58
+ const bus = new EventBus();
59
+ const fn = vi.fn();
60
+ bus.on('x', fn);
61
+ bus.off('x', fn);
62
+ bus.emit('x');
63
+ expect(fn).not.toHaveBeenCalled();
64
+ });
65
+
66
+ it('returns unsubscribe function', () => {
67
+ const bus = new EventBus();
68
+ const fn = vi.fn();
69
+ const unsub = bus.on('x', fn);
70
+ unsub();
71
+ bus.emit('x');
72
+ expect(fn).not.toHaveBeenCalled();
73
+ });
74
+ });
75
+
76
+ describe('onAny', () => {
77
+ it('receives all events', () => {
78
+ const bus = new EventBus();
79
+ const fn = vi.fn();
80
+ bus.onAny(fn);
81
+ bus.emit('a', 1);
82
+ bus.emit('b', 2);
83
+ expect(fn).toHaveBeenCalledTimes(2);
84
+ expect(fn).toHaveBeenCalledWith({ event: 'a', data: 1 });
85
+ expect(fn).toHaveBeenCalledWith({ event: 'b', data: 2 });
86
+ });
87
+
88
+ it('returns unsubscribe function', () => {
89
+ const bus = new EventBus();
90
+ const fn = vi.fn();
91
+ const unsub = bus.onAny(fn);
92
+ unsub();
93
+ bus.emit('x');
94
+ expect(fn).not.toHaveBeenCalled();
95
+ });
96
+ });
97
+
98
+ describe('clear', () => {
99
+ it('removes all listeners', () => {
100
+ const bus = new EventBus();
101
+ const fn1 = vi.fn();
102
+ const fn2 = vi.fn();
103
+ bus.on('a', fn1);
104
+ bus.onAny(fn2);
105
+ bus.clear();
106
+ bus.emit('a');
107
+ expect(fn1).not.toHaveBeenCalled();
108
+ expect(fn2).not.toHaveBeenCalled();
109
+ });
110
+ });
111
+
112
+ describe('listenerCount', () => {
113
+ it('counts specific event listeners', () => {
114
+ const bus = new EventBus();
115
+ bus.on('a', () => {});
116
+ bus.on('a', () => {});
117
+ bus.on('b', () => {});
118
+ expect(bus.listenerCount('a')).toBe(2);
119
+ expect(bus.listenerCount('b')).toBe(1);
120
+ expect(bus.listenerCount('c')).toBe(0);
121
+ });
122
+
123
+ it('counts all listeners', () => {
124
+ const bus = new EventBus();
125
+ bus.on('a', () => {});
126
+ bus.on('b', () => {});
127
+ bus.onAny(() => {});
128
+ expect(bus.listenerCount()).toBe(3);
129
+ });
130
+
131
+ it('empty bus has 0 listeners', () => {
132
+ expect(new EventBus().listenerCount()).toBe(0);
133
+ });
134
+ });
135
+ });
@@ -0,0 +1,84 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { MATERIAL_PRESETS, createPBRMaterial } from '../materials.js';
3
+ import type { PBRMaterial } from '../materials.js';
4
+
5
+ describe('MATERIAL_PRESETS', () => {
6
+ it('should have at least 10 presets', () => {
7
+ expect(Object.keys(MATERIAL_PRESETS).length).toBeGreaterThanOrEqual(10);
8
+ });
9
+
10
+ it('metal preset should be highly metallic', () => {
11
+ expect(MATERIAL_PRESETS.metal.metallic).toBe(1.0);
12
+ expect(MATERIAL_PRESETS.metal.roughness).toBeLessThan(0.5);
13
+ });
14
+
15
+ it('wood preset should not be metallic', () => {
16
+ expect(MATERIAL_PRESETS.wood.metallic).toBe(0.0);
17
+ expect(MATERIAL_PRESETS.wood.roughness).toBeGreaterThan(0.7);
18
+ });
19
+
20
+ it('glass preset should have low roughness and ior', () => {
21
+ expect(MATERIAL_PRESETS.glass.roughness).toBeLessThan(0.2);
22
+ expect(MATERIAL_PRESETS.glass.ior).toBeCloseTo(1.5);
23
+ expect(MATERIAL_PRESETS.glass.opacity).toBeLessThan(1.0);
24
+ });
25
+
26
+ it('water preset should have correct ior', () => {
27
+ expect(MATERIAL_PRESETS.water.ior).toBeCloseTo(1.33);
28
+ });
29
+
30
+ it('chrome preset should be fully metallic with low roughness', () => {
31
+ expect(MATERIAL_PRESETS.chrome.metallic).toBe(1.0);
32
+ expect(MATERIAL_PRESETS.chrome.roughness).toBeLessThan(0.1);
33
+ });
34
+
35
+ it('skin preset should have subsurface scattering', () => {
36
+ expect(MATERIAL_PRESETS.skin.subsurface).toBeGreaterThan(0);
37
+ });
38
+
39
+ it('all presets should have roughness defined', () => {
40
+ for (const [name, preset] of Object.entries(MATERIAL_PRESETS)) {
41
+ expect(preset.roughness, `${name} missing roughness`).toBeDefined();
42
+ }
43
+ });
44
+
45
+ it('all presets should have metallic defined', () => {
46
+ for (const [name, preset] of Object.entries(MATERIAL_PRESETS)) {
47
+ expect(preset.metallic, `${name} missing metallic`).toBeDefined();
48
+ }
49
+ });
50
+ });
51
+
52
+ describe('createPBRMaterial', () => {
53
+ it('creates material with defaults', () => {
54
+ const mat = createPBRMaterial('Test');
55
+ expect(mat.name).toBe('Test');
56
+ expect(mat.baseColor).toBe('#ffffff');
57
+ expect(mat.roughness).toBe(0.5);
58
+ expect(mat.metallic).toBe(0.0);
59
+ });
60
+
61
+ it('applies preset', () => {
62
+ const mat = createPBRMaterial('Steel', 'metal');
63
+ expect(mat.metallic).toBe(1.0);
64
+ expect(mat.roughness).toBe(0.2);
65
+ });
66
+
67
+ it('overrides preset values', () => {
68
+ const mat = createPBRMaterial('CustomMetal', 'metal', { roughness: 0.8 });
69
+ expect(mat.metallic).toBe(1.0);
70
+ expect(mat.roughness).toBe(0.8);
71
+ });
72
+
73
+ it('creates material without preset', () => {
74
+ const mat = createPBRMaterial('Plain', undefined, { baseColor: '#ff0000' });
75
+ expect(mat.baseColor).toBe('#ff0000');
76
+ expect(mat.roughness).toBe(0.5);
77
+ });
78
+
79
+ it('preserves name through preset', () => {
80
+ const mat = createPBRMaterial('Gold', 'gold');
81
+ expect(mat.name).toBe('Gold');
82
+ expect(mat.baseColor).toBe('#ffd700');
83
+ });
84
+ });
@@ -0,0 +1,246 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ PI,
4
+ TAU,
5
+ HALF_PI,
6
+ DEG_TO_RAD,
7
+ RAD_TO_DEG,
8
+ EPSILON,
9
+ clamp,
10
+ lerp,
11
+ inverseLerp,
12
+ remap,
13
+ smoothstep,
14
+ smootherstep,
15
+ degToRad,
16
+ radToDeg,
17
+ mod,
18
+ fract,
19
+ sign,
20
+ step,
21
+ } from '../math.js';
22
+
23
+ describe('Math Constants', () => {
24
+ it('PI should be Math.PI', () => {
25
+ expect(PI).toBe(Math.PI);
26
+ });
27
+
28
+ it('TAU should be 2 * PI', () => {
29
+ expect(TAU).toBe(Math.PI * 2);
30
+ });
31
+
32
+ it('HALF_PI should be PI / 2', () => {
33
+ expect(HALF_PI).toBe(Math.PI / 2);
34
+ });
35
+
36
+ it('DEG_TO_RAD should convert degrees to radians', () => {
37
+ expect(DEG_TO_RAD).toBe(Math.PI / 180);
38
+ });
39
+
40
+ it('RAD_TO_DEG should convert radians to degrees', () => {
41
+ expect(RAD_TO_DEG).toBe(180 / Math.PI);
42
+ });
43
+
44
+ it('EPSILON should be a small value for float comparison', () => {
45
+ expect(EPSILON).toBe(1e-6);
46
+ });
47
+ });
48
+
49
+ describe('clamp', () => {
50
+ it('should return min when value is below min', () => {
51
+ expect(clamp(-5, 0, 10)).toBe(0);
52
+ });
53
+
54
+ it('should return max when value is above max', () => {
55
+ expect(clamp(15, 0, 10)).toBe(10);
56
+ });
57
+
58
+ it('should return value when within range', () => {
59
+ expect(clamp(5, 0, 10)).toBe(5);
60
+ });
61
+
62
+ it('should handle edge cases at boundaries', () => {
63
+ expect(clamp(0, 0, 10)).toBe(0);
64
+ expect(clamp(10, 0, 10)).toBe(10);
65
+ });
66
+
67
+ it('should work with negative ranges', () => {
68
+ expect(clamp(-5, -10, -1)).toBe(-5);
69
+ expect(clamp(-15, -10, -1)).toBe(-10);
70
+ });
71
+ });
72
+
73
+ describe('lerp', () => {
74
+ it('should return a when t is 0', () => {
75
+ expect(lerp(0, 10, 0)).toBe(0);
76
+ });
77
+
78
+ it('should return b when t is 1', () => {
79
+ expect(lerp(0, 10, 1)).toBe(10);
80
+ });
81
+
82
+ it('should return midpoint when t is 0.5', () => {
83
+ expect(lerp(0, 10, 0.5)).toBe(5);
84
+ });
85
+
86
+ it('should extrapolate when t > 1', () => {
87
+ expect(lerp(0, 10, 2)).toBe(20);
88
+ });
89
+
90
+ it('should work with negative values', () => {
91
+ expect(lerp(-10, 10, 0.5)).toBe(0);
92
+ });
93
+ });
94
+
95
+ describe('inverseLerp', () => {
96
+ it('should return 0 when value equals a', () => {
97
+ expect(inverseLerp(0, 10, 0)).toBe(0);
98
+ });
99
+
100
+ it('should return 1 when value equals b', () => {
101
+ expect(inverseLerp(0, 10, 10)).toBe(1);
102
+ });
103
+
104
+ it('should return 0.5 when value is midpoint', () => {
105
+ expect(inverseLerp(0, 10, 5)).toBe(0.5);
106
+ });
107
+
108
+ it('should work with negative ranges', () => {
109
+ expect(inverseLerp(-10, 10, 0)).toBe(0.5);
110
+ });
111
+ });
112
+
113
+ describe('remap', () => {
114
+ it('should remap 0-1 to 0-100', () => {
115
+ expect(remap(0.5, 0, 1, 0, 100)).toBe(50);
116
+ });
117
+
118
+ it('should remap between different ranges', () => {
119
+ expect(remap(5, 0, 10, 100, 200)).toBe(150);
120
+ });
121
+
122
+ it('should handle inverted output ranges', () => {
123
+ expect(remap(0, 0, 10, 100, 0)).toBe(100);
124
+ expect(remap(10, 0, 10, 100, 0)).toBe(0);
125
+ });
126
+ });
127
+
128
+ describe('smoothstep', () => {
129
+ it('should return 0 when x <= edge0', () => {
130
+ expect(smoothstep(0, 1, -0.5)).toBe(0);
131
+ expect(smoothstep(0, 1, 0)).toBe(0);
132
+ });
133
+
134
+ it('should return 1 when x >= edge1', () => {
135
+ expect(smoothstep(0, 1, 1)).toBe(1);
136
+ expect(smoothstep(0, 1, 1.5)).toBe(1);
137
+ });
138
+
139
+ it('should return smooth interpolation in between', () => {
140
+ const mid = smoothstep(0, 1, 0.5);
141
+ expect(mid).toBe(0.5);
142
+ });
143
+ });
144
+
145
+ describe('smootherstep', () => {
146
+ it('should return 0 when x <= edge0', () => {
147
+ expect(smootherstep(0, 1, 0)).toBe(0);
148
+ });
149
+
150
+ it('should return 1 when x >= edge1', () => {
151
+ expect(smootherstep(0, 1, 1)).toBe(1);
152
+ });
153
+
154
+ it('should return smooth interpolation at midpoint', () => {
155
+ const mid = smootherstep(0, 1, 0.5);
156
+ expect(mid).toBe(0.5);
157
+ });
158
+ });
159
+
160
+ describe('degToRad', () => {
161
+ it('should convert 0 degrees to 0 radians', () => {
162
+ expect(degToRad(0)).toBe(0);
163
+ });
164
+
165
+ it('should convert 180 degrees to PI radians', () => {
166
+ expect(degToRad(180)).toBeCloseTo(Math.PI);
167
+ });
168
+
169
+ it('should convert 360 degrees to 2*PI radians', () => {
170
+ expect(degToRad(360)).toBeCloseTo(2 * Math.PI);
171
+ });
172
+
173
+ it('should convert 90 degrees to PI/2 radians', () => {
174
+ expect(degToRad(90)).toBeCloseTo(Math.PI / 2);
175
+ });
176
+ });
177
+
178
+ describe('radToDeg', () => {
179
+ it('should convert 0 radians to 0 degrees', () => {
180
+ expect(radToDeg(0)).toBe(0);
181
+ });
182
+
183
+ it('should convert PI radians to 180 degrees', () => {
184
+ expect(radToDeg(Math.PI)).toBeCloseTo(180);
185
+ });
186
+
187
+ it('should convert 2*PI radians to 360 degrees', () => {
188
+ expect(radToDeg(2 * Math.PI)).toBeCloseTo(360);
189
+ });
190
+ });
191
+
192
+ describe('mod', () => {
193
+ it('should handle positive modulo', () => {
194
+ expect(mod(7, 3)).toBe(1);
195
+ });
196
+
197
+ it('should handle negative values correctly (always positive result)', () => {
198
+ expect(mod(-1, 3)).toBe(2);
199
+ expect(mod(-4, 3)).toBe(2);
200
+ });
201
+
202
+ it('should return 0 for exact multiples', () => {
203
+ expect(mod(6, 3)).toBe(0);
204
+ });
205
+ });
206
+
207
+ describe('fract', () => {
208
+ it('should return fractional part of positive numbers', () => {
209
+ expect(fract(3.7)).toBeCloseTo(0.7);
210
+ });
211
+
212
+ it('should return 0 for integers', () => {
213
+ expect(fract(5)).toBe(0);
214
+ });
215
+
216
+ it('should handle negative numbers', () => {
217
+ expect(fract(-3.3)).toBeCloseTo(0.7);
218
+ });
219
+ });
220
+
221
+ describe('sign', () => {
222
+ it('should return 1 for positive numbers', () => {
223
+ expect(sign(5)).toBe(1);
224
+ expect(sign(0.001)).toBe(1);
225
+ });
226
+
227
+ it('should return -1 for negative numbers', () => {
228
+ expect(sign(-5)).toBe(-1);
229
+ expect(sign(-0.001)).toBe(-1);
230
+ });
231
+
232
+ it('should return 0 for zero', () => {
233
+ expect(sign(0)).toBe(0);
234
+ });
235
+ });
236
+
237
+ describe('step', () => {
238
+ it('should return 0 when x < edge', () => {
239
+ expect(step(0.5, 0.3)).toBe(0);
240
+ });
241
+
242
+ it('should return 1 when x >= edge', () => {
243
+ expect(step(0.5, 0.5)).toBe(1);
244
+ expect(step(0.5, 0.7)).toBe(1);
245
+ });
246
+ });