@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.
- package/README.md +197 -0
- package/dist/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/EconomicTraits.test.d.ts.map +1 -0
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts.map +1 -0
- package/dist/__tests__/Sprint33.test.d.ts.map +1 -0
- package/dist/__tests__/Sprint56.test.d.ts.map +1 -0
- package/dist/__tests__/collections.test.d.ts.map +1 -0
- package/dist/__tests__/events.test.d.ts.map +1 -0
- package/dist/__tests__/materials.test.d.ts.map +1 -0
- package/dist/__tests__/math.test.d.ts.map +1 -0
- package/dist/__tests__/physics.test.d.ts.map +1 -0
- package/dist/__tests__/spatial.test.d.ts.map +1 -0
- package/dist/__tests__/string-decoupled.test.d.ts.map +1 -0
- package/dist/__tests__/string.test.d.ts.map +1 -0
- package/dist/__tests__/time.test.d.ts.map +1 -0
- package/dist/collections.d.ts.map +1 -1
- package/dist/events.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -9
- package/dist/materials.d.ts.map +1 -0
- package/dist/math.d.ts.map +1 -1
- package/dist/physics.d.ts.map +1 -0
- package/dist/spatial.d.ts.map +1 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/time.d.ts.map +1 -1
- package/dist/traits/ARTraits.d.ts.map +1 -0
- package/dist/traits/EconomicPrimitives.d.ts.map +1 -0
- package/dist/traits/EconomicTraits.d.ts.map +1 -0
- package/dist/traits/IoTTraits.d.ts.map +1 -0
- package/dist/traits/SimulationLabPrimitives.d.ts.map +1 -0
- package/dist/traits/SimulationLabTraits.d.ts.map +1 -0
- package/dist/traits/VRRTraits.d.ts.map +1 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -6
- package/src/__tests__/EconomicPrimitives.test.ts +690 -0
- package/src/__tests__/EconomicTraits.test.ts +425 -0
- package/src/__tests__/SimulationLabPrimitives.test.ts +415 -0
- package/src/__tests__/Sprint33.test.ts +1301 -0
- package/src/__tests__/Sprint56.test.ts +1070 -0
- package/src/__tests__/collections.test.ts +182 -0
- package/src/__tests__/events.test.ts +135 -0
- package/src/__tests__/materials.test.ts +84 -0
- package/src/__tests__/math.test.ts +246 -0
- package/src/__tests__/physics.test.ts +83 -0
- package/src/__tests__/spatial.test.ts +333 -0
- package/src/__tests__/string-decoupled.test.ts +16 -0
- package/src/__tests__/string.test.ts +164 -0
- package/src/__tests__/time.test.ts +110 -0
- package/src/collections.ts +8 -2
- package/src/events.ts +88 -0
- package/src/index.ts +161 -11
- package/src/materials.ts +109 -0
- package/src/math.ts +30 -14
- package/src/physics.ts +141 -0
- package/src/spatial.ts +320 -0
- package/src/string.basic.test.ts +14 -0
- package/src/string.test.ts +335 -0
- package/src/string.ts +19 -2
- package/src/time.ts +9 -10
- package/src/traits/ARTraits.ts +103 -0
- package/src/traits/EconomicPrimitives.ts +755 -0
- package/src/traits/EconomicTraits.ts +552 -0
- package/src/traits/IoTTraits.ts +102 -0
- package/src/traits/SimulationLabPrimitives.ts +650 -0
- package/src/traits/SimulationLabTraits.ts +191 -0
- package/src/traits/VRRTraits.ts +326 -0
- package/src/types.ts +47 -12
- package/vitest.config.ts +10 -0
- package/dist/collections.d.ts +0 -177
- package/dist/collections.js +0 -720
- package/dist/collections.js.map +0 -1
- package/dist/index.d.ts +0 -89
- package/dist/index.js.map +0 -1
- package/dist/math.d.ts +0 -162
- package/dist/math.js +0 -539
- package/dist/math.js.map +0 -1
- package/dist/string.d.ts +0 -208
- package/dist/string.js +0 -443
- package/dist/string.js.map +0 -1
- package/dist/time.d.ts +0 -215
- package/dist/time.js +0 -530
- package/dist/time.js.map +0 -1
- package/dist/types.d.ts +0 -193
- package/dist/types.js +0 -198
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createBoxCollider,
|
|
4
|
+
createSphereCollider,
|
|
5
|
+
createCapsuleCollider,
|
|
6
|
+
createRigidbody,
|
|
7
|
+
} from '../physics.js';
|
|
8
|
+
import { Vec3 } from '../spatial.js';
|
|
9
|
+
|
|
10
|
+
describe('createBoxCollider', () => {
|
|
11
|
+
it('creates a box collider with defaults', () => {
|
|
12
|
+
const c = createBoxCollider(new Vec3(1, 1, 1));
|
|
13
|
+
expect(c.shape).toBe('box');
|
|
14
|
+
expect(c.isTrigger).toBe(false);
|
|
15
|
+
expect(c.friction).toBe(0.5);
|
|
16
|
+
expect(c.restitution).toBe(0.0);
|
|
17
|
+
expect(c.halfExtents).toBeDefined();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('accepts option overrides', () => {
|
|
21
|
+
const c = createBoxCollider(new Vec3(2, 2, 2), { friction: 0.9, isTrigger: true });
|
|
22
|
+
expect(c.friction).toBe(0.9);
|
|
23
|
+
expect(c.isTrigger).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('createSphereCollider', () => {
|
|
28
|
+
it('creates a sphere collider', () => {
|
|
29
|
+
const c = createSphereCollider(0.5);
|
|
30
|
+
expect(c.shape).toBe('sphere');
|
|
31
|
+
expect(c.radius).toBe(0.5);
|
|
32
|
+
expect(c.friction).toBe(0.5);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('overrides restitution', () => {
|
|
36
|
+
const c = createSphereCollider(1.0, { restitution: 0.8 });
|
|
37
|
+
expect(c.restitution).toBe(0.8);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('createCapsuleCollider', () => {
|
|
42
|
+
it('creates a capsule collider', () => {
|
|
43
|
+
const c = createCapsuleCollider(0.3, 1.8);
|
|
44
|
+
expect(c.shape).toBe('capsule');
|
|
45
|
+
expect(c.radius).toBe(0.3);
|
|
46
|
+
expect(c.height).toBe(1.8);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('createRigidbody', () => {
|
|
51
|
+
it('creates a rigidbody with defaults', () => {
|
|
52
|
+
const rb = createRigidbody(10);
|
|
53
|
+
expect(rb.mass).toBe(10);
|
|
54
|
+
expect(rb.useGravity).toBe(true);
|
|
55
|
+
expect(rb.linearDamping).toBe(0.0);
|
|
56
|
+
expect(rb.angularDamping).toBe(0.05);
|
|
57
|
+
expect(rb.isKinematic).toBe(false);
|
|
58
|
+
expect(rb.freezePosition).toEqual([false, false, false]);
|
|
59
|
+
expect(rb.freezeRotation).toEqual([false, false, false]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('overrides gravity', () => {
|
|
63
|
+
const rb = createRigidbody(5, { useGravity: false });
|
|
64
|
+
expect(rb.useGravity).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('overrides kinematic', () => {
|
|
68
|
+
const rb = createRigidbody(1, { isKinematic: true });
|
|
69
|
+
expect(rb.isKinematic).toBe(true);
|
|
70
|
+
expect(rb.mass).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('overrides damping', () => {
|
|
74
|
+
const rb = createRigidbody(50, { linearDamping: 0.5, angularDamping: 0.8 });
|
|
75
|
+
expect(rb.linearDamping).toBe(0.5);
|
|
76
|
+
expect(rb.angularDamping).toBe(0.8);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('overrides freeze axes', () => {
|
|
80
|
+
const rb = createRigidbody(1, { freezePosition: [true, false, true] });
|
|
81
|
+
expect(rb.freezePosition).toEqual([true, false, true]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
Vec3,
|
|
4
|
+
Quaternion,
|
|
5
|
+
Transform,
|
|
6
|
+
Ray,
|
|
7
|
+
AABB,
|
|
8
|
+
distance,
|
|
9
|
+
lerp,
|
|
10
|
+
clamp,
|
|
11
|
+
degToRad,
|
|
12
|
+
radToDeg,
|
|
13
|
+
} from '../spatial.js';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Vec3
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
describe('Vec3', () => {
|
|
20
|
+
describe('constructors', () => {
|
|
21
|
+
it('should default to zero', () => {
|
|
22
|
+
const v = new Vec3();
|
|
23
|
+
expect(v.x).toBe(0);
|
|
24
|
+
expect(v.y).toBe(0);
|
|
25
|
+
expect(v.z).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create from values', () => {
|
|
29
|
+
const v = new Vec3(1, 2, 3);
|
|
30
|
+
expect(v.x).toBe(1);
|
|
31
|
+
expect(v.y).toBe(2);
|
|
32
|
+
expect(v.z).toBe(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('static zero()', () => {
|
|
36
|
+
expect(Vec3.zero().equals(new Vec3(0, 0, 0))).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('static one()', () => {
|
|
40
|
+
expect(Vec3.one().equals(new Vec3(1, 1, 1))).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('static up()', () => {
|
|
44
|
+
expect(Vec3.up().y).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('static forward()', () => {
|
|
48
|
+
expect(Vec3.forward().z).toBe(-1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('static right()', () => {
|
|
52
|
+
expect(Vec3.right().x).toBe(1);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('arithmetic', () => {
|
|
57
|
+
it('add', () => {
|
|
58
|
+
const r = new Vec3(1, 2, 3).add(new Vec3(4, 5, 6));
|
|
59
|
+
expect(r.equals(new Vec3(5, 7, 9))).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('sub', () => {
|
|
63
|
+
const r = new Vec3(5, 7, 9).sub(new Vec3(4, 5, 6));
|
|
64
|
+
expect(r.equals(new Vec3(1, 2, 3))).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('mul', () => {
|
|
68
|
+
const r = new Vec3(1, 2, 3).mul(2);
|
|
69
|
+
expect(r.equals(new Vec3(2, 4, 6))).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('div', () => {
|
|
73
|
+
const r = new Vec3(4, 6, 8).div(2);
|
|
74
|
+
expect(r.equals(new Vec3(2, 3, 4))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('vector operations', () => {
|
|
79
|
+
it('dot product', () => {
|
|
80
|
+
expect(new Vec3(1, 0, 0).dot(new Vec3(0, 1, 0))).toBe(0);
|
|
81
|
+
expect(new Vec3(1, 0, 0).dot(new Vec3(1, 0, 0))).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('cross product', () => {
|
|
85
|
+
const r = new Vec3(1, 0, 0).cross(new Vec3(0, 1, 0));
|
|
86
|
+
expect(r.equals(new Vec3(0, 0, 1))).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('length', () => {
|
|
90
|
+
expect(new Vec3(3, 4, 0).length()).toBe(5);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('lengthSquared', () => {
|
|
94
|
+
expect(new Vec3(3, 4, 0).lengthSquared()).toBe(25);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('normalize', () => {
|
|
98
|
+
const n = new Vec3(3, 0, 0).normalize();
|
|
99
|
+
expect(n.length()).toBeCloseTo(1);
|
|
100
|
+
expect(n.x).toBeCloseTo(1);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('normalize zero returns zero', () => {
|
|
104
|
+
const n = Vec3.zero().normalize();
|
|
105
|
+
expect(n.length()).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('distanceTo', () => {
|
|
109
|
+
expect(new Vec3(0, 0, 0).distanceTo(new Vec3(3, 4, 0))).toBe(5);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('lerp', () => {
|
|
113
|
+
const r = new Vec3(0, 0, 0).lerp(new Vec3(10, 10, 10), 0.5);
|
|
114
|
+
expect(r.equals(new Vec3(5, 5, 5))).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('lerp at 0 returns start', () => {
|
|
118
|
+
const a = new Vec3(1, 2, 3);
|
|
119
|
+
expect(a.lerp(new Vec3(10, 20, 30), 0).equals(a)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('lerp at 1 returns end', () => {
|
|
123
|
+
const b = new Vec3(10, 20, 30);
|
|
124
|
+
expect(new Vec3(1, 2, 3).lerp(b, 1).equals(b)).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('conversion', () => {
|
|
129
|
+
it('toArray', () => {
|
|
130
|
+
expect(new Vec3(1, 2, 3).toArray()).toEqual([1, 2, 3]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('fromArray', () => {
|
|
134
|
+
expect(Vec3.fromArray([4, 5, 6]).equals(new Vec3(4, 5, 6))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('fromArray handles short arrays', () => {
|
|
138
|
+
expect(Vec3.fromArray([1]).equals(new Vec3(1, 0, 0))).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('toString', () => {
|
|
142
|
+
expect(new Vec3(1, 2, 3).toString()).toBe('Vec3(1, 2, 3)');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('equals', () => {
|
|
147
|
+
it('equal vectors', () => {
|
|
148
|
+
expect(new Vec3(1, 2, 3).equals(new Vec3(1, 2, 3))).toBe(true);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('unequal vectors', () => {
|
|
152
|
+
expect(new Vec3(1, 2, 3).equals(new Vec3(1, 2, 4))).toBe(false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('epsilon comparison', () => {
|
|
156
|
+
expect(new Vec3(1, 2, 3.0000001).equals(new Vec3(1, 2, 3))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// Quaternion
|
|
163
|
+
// =============================================================================
|
|
164
|
+
|
|
165
|
+
describe('Quaternion', () => {
|
|
166
|
+
it('identity', () => {
|
|
167
|
+
const q = Quaternion.identity();
|
|
168
|
+
expect(q.w).toBe(1);
|
|
169
|
+
expect(q.x).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('fromEuler and toEuler roundtrip', () => {
|
|
173
|
+
const q = Quaternion.fromEuler(0.5, 0.3, 0.1);
|
|
174
|
+
const euler = q.toEuler();
|
|
175
|
+
expect(euler.x).toBeCloseTo(0.5, 3);
|
|
176
|
+
expect(euler.y).toBeCloseTo(0.3, 3);
|
|
177
|
+
expect(euler.z).toBeCloseTo(0.1, 3);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('fromAxisAngle', () => {
|
|
181
|
+
const q = Quaternion.fromAxisAngle(Vec3.up(), Math.PI / 2);
|
|
182
|
+
const n = q.normalize();
|
|
183
|
+
expect(n.w).toBeCloseTo(Math.cos(Math.PI / 4));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('multiply identity is no-op', () => {
|
|
187
|
+
const q = Quaternion.fromEuler(0.5, 0.3, 0.1);
|
|
188
|
+
const r = q.multiply(Quaternion.identity());
|
|
189
|
+
expect(r.x).toBeCloseTo(q.x);
|
|
190
|
+
expect(r.y).toBeCloseTo(q.y);
|
|
191
|
+
expect(r.z).toBeCloseTo(q.z);
|
|
192
|
+
expect(r.w).toBeCloseTo(q.w);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('rotateVec3', () => {
|
|
196
|
+
const q = Quaternion.fromAxisAngle(Vec3.up(), Math.PI / 2);
|
|
197
|
+
const v = q.rotateVec3(new Vec3(1, 0, 0));
|
|
198
|
+
expect(v.x).toBeCloseTo(0, 3);
|
|
199
|
+
expect(v.z).toBeCloseTo(-1, 3);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('slerp at 0 returns start', () => {
|
|
203
|
+
const a = Quaternion.identity();
|
|
204
|
+
const b = Quaternion.fromEuler(1, 0, 0);
|
|
205
|
+
const r = a.slerp(b, 0);
|
|
206
|
+
expect(r.w).toBeCloseTo(a.w);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('slerp at 1 returns end', () => {
|
|
210
|
+
const a = Quaternion.identity();
|
|
211
|
+
const b = Quaternion.fromEuler(1, 0, 0);
|
|
212
|
+
const r = a.slerp(b, 1);
|
|
213
|
+
expect(r.x).toBeCloseTo(b.x, 3);
|
|
214
|
+
expect(r.w).toBeCloseTo(b.w, 3);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('normalize preserves direction', () => {
|
|
218
|
+
const q = new Quaternion(0, 0, 0, 2);
|
|
219
|
+
const n = q.normalize();
|
|
220
|
+
expect(n.w).toBeCloseTo(1);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// =============================================================================
|
|
225
|
+
// Transform
|
|
226
|
+
// =============================================================================
|
|
227
|
+
|
|
228
|
+
describe('Transform', () => {
|
|
229
|
+
it('identity does nothing', () => {
|
|
230
|
+
const t = Transform.identity();
|
|
231
|
+
const p = t.transformPoint(new Vec3(1, 2, 3));
|
|
232
|
+
expect(p.equals(new Vec3(1, 2, 3))).toBe(true);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('translation', () => {
|
|
236
|
+
const t = new Transform(new Vec3(10, 0, 0));
|
|
237
|
+
const p = t.transformPoint(Vec3.zero());
|
|
238
|
+
expect(p.equals(new Vec3(10, 0, 0))).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('scale', () => {
|
|
242
|
+
const t = new Transform(Vec3.zero(), Quaternion.identity(), new Vec3(2, 2, 2));
|
|
243
|
+
const p = t.transformPoint(new Vec3(1, 1, 1));
|
|
244
|
+
expect(p.equals(new Vec3(2, 2, 2))).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('transformDirection ignores position', () => {
|
|
248
|
+
const t = new Transform(new Vec3(100, 100, 100));
|
|
249
|
+
const d = t.transformDirection(new Vec3(1, 0, 0));
|
|
250
|
+
expect(d.equals(new Vec3(1, 0, 0))).toBe(true);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Ray & AABB
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
describe('Ray', () => {
|
|
259
|
+
it('pointAt', () => {
|
|
260
|
+
const r = new Ray(Vec3.zero(), new Vec3(1, 0, 0));
|
|
261
|
+
expect(r.pointAt(5).equals(new Vec3(5, 0, 0))).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('AABB', () => {
|
|
266
|
+
const box = new AABB(new Vec3(-1, -1, -1), new Vec3(1, 1, 1));
|
|
267
|
+
|
|
268
|
+
it('contains point inside', () => {
|
|
269
|
+
expect(box.contains(Vec3.zero())).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('does not contain point outside', () => {
|
|
273
|
+
expect(box.contains(new Vec3(2, 0, 0))).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('intersects overlapping AABB', () => {
|
|
277
|
+
const other = new AABB(new Vec3(0, 0, 0), new Vec3(2, 2, 2));
|
|
278
|
+
expect(box.intersects(other)).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('does not intersect non-overlapping AABB', () => {
|
|
282
|
+
const other = new AABB(new Vec3(5, 5, 5), new Vec3(6, 6, 6));
|
|
283
|
+
expect(box.intersects(other)).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('center', () => {
|
|
287
|
+
expect(box.center().equals(Vec3.zero())).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('size', () => {
|
|
291
|
+
expect(box.size().equals(new Vec3(2, 2, 2))).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('ray intersection hit', () => {
|
|
295
|
+
const ray = new Ray(new Vec3(-5, 0, 0), new Vec3(1, 0, 0));
|
|
296
|
+
const t = box.intersectsRay(ray);
|
|
297
|
+
expect(t).not.toBeNull();
|
|
298
|
+
expect(t).toBeCloseTo(4);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('ray intersection miss', () => {
|
|
302
|
+
const ray = new Ray(new Vec3(-5, 5, 0), new Vec3(1, 0, 0));
|
|
303
|
+
expect(box.intersectsRay(ray)).toBeNull();
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// =============================================================================
|
|
308
|
+
// Utility functions
|
|
309
|
+
// =============================================================================
|
|
310
|
+
|
|
311
|
+
describe('utility functions', () => {
|
|
312
|
+
it('distance', () => {
|
|
313
|
+
expect(distance(Vec3.zero(), new Vec3(3, 4, 0))).toBe(5);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('lerp', () => {
|
|
317
|
+
expect(lerp(0, 10, 0.5)).toBe(5);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('clamp', () => {
|
|
321
|
+
expect(clamp(-5, 0, 10)).toBe(0);
|
|
322
|
+
expect(clamp(15, 0, 10)).toBe(10);
|
|
323
|
+
expect(clamp(5, 0, 10)).toBe(5);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('degToRad', () => {
|
|
327
|
+
expect(degToRad(180)).toBeCloseTo(Math.PI);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('radToDeg', () => {
|
|
331
|
+
expect(radToDeg(Math.PI)).toBeCloseTo(180);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// Auto-generated test for string-decoupled
|
|
4
|
+
// Source: packages/std/src/string.ts
|
|
5
|
+
|
|
6
|
+
describe('string-decoupled', () => {
|
|
7
|
+
it('should be defined', () => {
|
|
8
|
+
// TODO: Import and test string-decoupled
|
|
9
|
+
expect(true).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should have basic functionality', () => {
|
|
13
|
+
// TODO: Add meaningful tests for string-decoupled
|
|
14
|
+
expect(true).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
isBlank,
|
|
4
|
+
isNotBlank,
|
|
5
|
+
capitalize,
|
|
6
|
+
titleCase,
|
|
7
|
+
camelCase,
|
|
8
|
+
pascalCase,
|
|
9
|
+
snakeCase,
|
|
10
|
+
kebabCase,
|
|
11
|
+
constantCase,
|
|
12
|
+
} from '../string.js';
|
|
13
|
+
|
|
14
|
+
describe('isBlank', () => {
|
|
15
|
+
it('should return true for empty string', () => {
|
|
16
|
+
expect(isBlank('')).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return true for whitespace only', () => {
|
|
20
|
+
expect(isBlank(' ')).toBe(true);
|
|
21
|
+
expect(isBlank('\t\n')).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should return false for non-empty string', () => {
|
|
25
|
+
expect(isBlank('hello')).toBe(false);
|
|
26
|
+
expect(isBlank(' hello ')).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('isNotBlank', () => {
|
|
31
|
+
it('should return false for empty string', () => {
|
|
32
|
+
expect(isNotBlank('')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return false for whitespace only', () => {
|
|
36
|
+
expect(isNotBlank(' ')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should return true for non-empty string', () => {
|
|
40
|
+
expect(isNotBlank('hello')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('capitalize', () => {
|
|
45
|
+
it('should capitalize first letter', () => {
|
|
46
|
+
expect(capitalize('hello')).toBe('Hello');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle empty string', () => {
|
|
50
|
+
expect(capitalize('')).toBe('');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle already capitalized', () => {
|
|
54
|
+
expect(capitalize('Hello')).toBe('Hello');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle single character', () => {
|
|
58
|
+
expect(capitalize('a')).toBe('A');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('titleCase', () => {
|
|
63
|
+
it('should capitalize each word', () => {
|
|
64
|
+
expect(titleCase('hello world')).toBe('Hello World');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle multiple spaces', () => {
|
|
68
|
+
expect(titleCase('hello world')).toBe('Hello World');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle single word', () => {
|
|
72
|
+
expect(titleCase('hello')).toBe('Hello');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('camelCase', () => {
|
|
77
|
+
it('should convert space-separated to camelCase', () => {
|
|
78
|
+
expect(camelCase('hello world')).toBe('helloWorld');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should convert kebab-case to camelCase', () => {
|
|
82
|
+
expect(camelCase('hello-world')).toBe('helloWorld');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should convert snake_case to camelCase', () => {
|
|
86
|
+
expect(camelCase('hello_world')).toBe('helloWorld');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should lowercase first letter of PascalCase', () => {
|
|
90
|
+
expect(camelCase('HelloWorld')).toBe('helloWorld');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle empty string', () => {
|
|
94
|
+
expect(camelCase('')).toBe('');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('pascalCase', () => {
|
|
99
|
+
it('should convert space-separated to PascalCase', () => {
|
|
100
|
+
expect(pascalCase('hello world')).toBe('HelloWorld');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should convert kebab-case to PascalCase', () => {
|
|
104
|
+
expect(pascalCase('hello-world')).toBe('HelloWorld');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should convert snake_case to PascalCase', () => {
|
|
108
|
+
expect(pascalCase('hello_world')).toBe('HelloWorld');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should handle empty string', () => {
|
|
112
|
+
expect(pascalCase('')).toBe('');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('snakeCase', () => {
|
|
117
|
+
it('should convert space-separated to snake_case', () => {
|
|
118
|
+
expect(snakeCase('hello world')).toBe('hello_world');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should convert camelCase to snake_case', () => {
|
|
122
|
+
expect(snakeCase('helloWorld')).toBe('hello_world');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should convert PascalCase to snake_case', () => {
|
|
126
|
+
expect(snakeCase('HelloWorld')).toBe('hello_world');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should convert kebab-case to snake_case', () => {
|
|
130
|
+
expect(snakeCase('hello-world')).toBe('hello_world');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('kebabCase', () => {
|
|
135
|
+
it('should convert space-separated to kebab-case', () => {
|
|
136
|
+
expect(kebabCase('hello world')).toBe('hello-world');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should convert camelCase to kebab-case', () => {
|
|
140
|
+
expect(kebabCase('helloWorld')).toBe('hello-world');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should convert PascalCase to kebab-case', () => {
|
|
144
|
+
expect(kebabCase('HelloWorld')).toBe('hello-world');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should convert snake_case to kebab-case', () => {
|
|
148
|
+
expect(kebabCase('hello_world')).toBe('hello-world');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('constantCase', () => {
|
|
153
|
+
it('should convert to SCREAMING_SNAKE_CASE', () => {
|
|
154
|
+
expect(constantCase('hello world')).toBe('HELLO_WORLD');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should convert camelCase to SCREAMING_SNAKE_CASE', () => {
|
|
158
|
+
expect(constantCase('helloWorld')).toBe('HELLO_WORLD');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should convert kebab-case to SCREAMING_SNAKE_CASE', () => {
|
|
162
|
+
expect(constantCase('hello-world')).toBe('HELLO_WORLD');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { now, hrTime, sleep, withTimeout } from '../time.js';
|
|
3
|
+
|
|
4
|
+
describe('now', () => {
|
|
5
|
+
it('should return current timestamp in milliseconds', () => {
|
|
6
|
+
const before = Date.now();
|
|
7
|
+
const result = now();
|
|
8
|
+
const after = Date.now();
|
|
9
|
+
|
|
10
|
+
expect(result).toBeGreaterThanOrEqual(before);
|
|
11
|
+
expect(result).toBeLessThanOrEqual(after);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return a number', () => {
|
|
15
|
+
expect(typeof now()).toBe('number');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('hrTime', () => {
|
|
20
|
+
it('should return high-resolution timestamp', () => {
|
|
21
|
+
const result = hrTime();
|
|
22
|
+
expect(typeof result).toBe('number');
|
|
23
|
+
expect(result).toBeGreaterThanOrEqual(0);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should increase over time', async () => {
|
|
27
|
+
const t1 = hrTime();
|
|
28
|
+
await sleep(10);
|
|
29
|
+
const t2 = hrTime();
|
|
30
|
+
expect(t2).toBeGreaterThan(t1);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('sleep', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.useFakeTimers();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.useRealTimers();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return a promise', () => {
|
|
44
|
+
const result = sleep(100);
|
|
45
|
+
expect(result).toBeInstanceOf(Promise);
|
|
46
|
+
vi.runAllTimers();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should resolve after specified time', async () => {
|
|
50
|
+
let resolved = false;
|
|
51
|
+
const promise = sleep(1000).then(() => {
|
|
52
|
+
resolved = true;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(resolved).toBe(false);
|
|
56
|
+
|
|
57
|
+
vi.advanceTimersByTime(500);
|
|
58
|
+
await Promise.resolve();
|
|
59
|
+
expect(resolved).toBe(false);
|
|
60
|
+
|
|
61
|
+
vi.advanceTimersByTime(500);
|
|
62
|
+
await promise;
|
|
63
|
+
expect(resolved).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('withTimeout', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.useFakeTimers();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
vi.useRealTimers();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should resolve if function completes before timeout', async () => {
|
|
77
|
+
const fn = async () => {
|
|
78
|
+
await sleep(50);
|
|
79
|
+
return 'success';
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const promise = withTimeout(fn, 1000);
|
|
83
|
+
vi.advanceTimersByTime(50);
|
|
84
|
+
await vi.runAllTimersAsync();
|
|
85
|
+
|
|
86
|
+
const result = await promise;
|
|
87
|
+
expect(result).toBe('success');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should reject with Timeout error if function takes too long', async () => {
|
|
91
|
+
const fn = async () => {
|
|
92
|
+
await sleep(2000);
|
|
93
|
+
return 'success';
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const promise = withTimeout(fn, 100);
|
|
97
|
+
vi.advanceTimersByTime(100);
|
|
98
|
+
|
|
99
|
+
await expect(promise).rejects.toThrow('Timeout');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('time utilities - real timers', () => {
|
|
104
|
+
it('sleep should actually wait (short duration)', async () => {
|
|
105
|
+
const start = Date.now();
|
|
106
|
+
await sleep(50);
|
|
107
|
+
const elapsed = Date.now() - start;
|
|
108
|
+
expect(elapsed).toBeGreaterThanOrEqual(40); // Allow some tolerance
|
|
109
|
+
});
|
|
110
|
+
});
|