node-osc 10.0.0 → 11.1.0

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,496 @@
1
+ import { test } from 'tap';
2
+ import { toBuffer, fromBuffer } from '#osc';
3
+
4
+ test('osc: timetag encoding with non-number value', (t) => {
5
+ // Test the else branch in writeTimeTag that writes zeros for non-number values
6
+ const bundle = {
7
+ oscType: 'bundle',
8
+ timetag: 'immediate', // Non-number value
9
+ elements: [
10
+ {
11
+ oscType: 'message',
12
+ address: '/test',
13
+ args: []
14
+ }
15
+ ]
16
+ };
17
+
18
+ const buffer = toBuffer(bundle);
19
+ const decoded = fromBuffer(buffer);
20
+
21
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
22
+ t.equal(decoded.timetag, 0, 'should decode timetag as 0 for immediate execution');
23
+ t.end();
24
+ });
25
+
26
+ test('osc: timetag with immediate execution values', (t) => {
27
+ // Test readTimeTag with seconds === 0 && fraction === 1
28
+ const bundle = {
29
+ oscType: 'bundle',
30
+ timetag: null, // This will trigger the non-number path
31
+ elements: [
32
+ {
33
+ oscType: 'message',
34
+ address: '/test',
35
+ args: []
36
+ }
37
+ ]
38
+ };
39
+
40
+ const buffer = toBuffer(bundle);
41
+ const decoded = fromBuffer(buffer);
42
+
43
+ t.equal(decoded.timetag, 0, 'should handle immediate execution timetag');
44
+ t.end();
45
+ });
46
+
47
+ test('osc: argument encoding with unknown type', (t) => {
48
+ // Test encodeArgument with unknown argument type to trigger line 122
49
+ const message = {
50
+ oscType: 'message',
51
+ address: '/test',
52
+ args: [
53
+ {
54
+ type: 'unknown',
55
+ value: 'test'
56
+ }
57
+ ]
58
+ };
59
+
60
+ t.throws(() => {
61
+ toBuffer(message);
62
+ }, /Unknown argument type: unknown/, 'should throw error for unknown argument type');
63
+ t.end();
64
+ });
65
+
66
+ test('osc: argument encoding with boolean false', (t) => {
67
+ // Test explicit boolean false encoding to cover lines 132-133
68
+ const message = {
69
+ oscType: 'message',
70
+ address: '/test',
71
+ args: [
72
+ {
73
+ type: 'boolean',
74
+ value: false
75
+ }
76
+ ]
77
+ };
78
+
79
+ const buffer = toBuffer(message);
80
+ const decoded = fromBuffer(buffer);
81
+
82
+ t.equal(decoded.args[0].value, false, 'should encode and decode false boolean');
83
+ t.end();
84
+ });
85
+
86
+ test('osc: argument encoding with unsupported object', (t) => {
87
+ // Test encodeArgument with unsupported object to trigger lines 139-142
88
+ const message = {
89
+ oscType: 'message',
90
+ address: '/test',
91
+ args: [
92
+ { unsupported: 'object' } // Object without type/value properties
93
+ ]
94
+ };
95
+
96
+ t.throws(() => {
97
+ toBuffer(message);
98
+ }, /Don't know how to encode argument/, 'should throw error for unsupported object');
99
+ t.end();
100
+ });
101
+
102
+ test('osc: argument encoding with undefined value', (t) => {
103
+ // Test encodeArgument with undefined to trigger error case
104
+ const message = {
105
+ oscType: 'message',
106
+ address: '/test',
107
+ args: [undefined]
108
+ };
109
+
110
+ t.throws(() => {
111
+ toBuffer(message);
112
+ }, /Don't know how to encode argument/, 'should throw error for undefined argument');
113
+ t.end();
114
+ });
115
+
116
+ test('osc: argument decoding with unknown type tag', (t) => {
117
+ // Test decodeArgument with unknown type tag to trigger line 161
118
+ // We need to manually create a buffer with an invalid type tag
119
+ const addressPart = '/test\0\0\0';
120
+ const typeTagPart = ',X\0\0'; // X is not a valid OSC type tag
121
+ const buffer = Buffer.from(addressPart + typeTagPart);
122
+
123
+ t.throws(() => {
124
+ fromBuffer(buffer);
125
+ }, /I don't understand the argument code X/, 'should throw error for unknown type tag');
126
+ t.end();
127
+ });
128
+
129
+ test('osc: null argument encoding and decoding', (t) => {
130
+ // Test null argument handling (N type tag)
131
+ // Since our current implementation doesn't directly support null in encoding,
132
+ // let's test that we can at least decode it if we manually create the buffer
133
+ const addressPart = '/test\0\0\0';
134
+ const typeTagPart = ',N\0\0'; // N is null type tag
135
+ const buffer = Buffer.from(addressPart + typeTagPart);
136
+
137
+ const decoded = fromBuffer(buffer);
138
+ t.equal(decoded.args[0].value, null, 'should decode null argument');
139
+ t.end();
140
+ });
141
+
142
+ test('osc: double type argument encoding', (t) => {
143
+ // Test double type argument which should fall back to float
144
+ const message = {
145
+ oscType: 'message',
146
+ address: '/test',
147
+ args: [
148
+ {
149
+ type: 'd',
150
+ value: 3.14159
151
+ }
152
+ ]
153
+ };
154
+
155
+ const buffer = toBuffer(message);
156
+ const decoded = fromBuffer(buffer);
157
+
158
+ t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode double as float');
159
+ t.end();
160
+ });
161
+
162
+ test('osc: blob argument with Buffer', (t) => {
163
+ // Test blob encoding with actual Buffer to ensure Buffer.isBuffer path is covered
164
+ const testBuffer = Buffer.from('test data');
165
+ const message = {
166
+ oscType: 'message',
167
+ address: '/test',
168
+ args: [testBuffer] // Direct Buffer without type wrapper
169
+ };
170
+
171
+ const buffer = toBuffer(message);
172
+ const decoded = fromBuffer(buffer);
173
+
174
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
175
+ t.equal(decoded.args[0].value.toString(), 'test data', 'should preserve blob content');
176
+ t.end();
177
+ });
178
+
179
+ test('osc: float number encoding', (t) => {
180
+ // Test encoding of float numbers to cover lines 132-133
181
+ const message = {
182
+ oscType: 'message',
183
+ address: '/test',
184
+ args: [3.14159] // Non-integer number should be encoded as float
185
+ };
186
+
187
+ const buffer = toBuffer(message);
188
+ const decoded = fromBuffer(buffer);
189
+
190
+ t.ok(typeof decoded.args[0].value === 'number', 'should decode as number');
191
+ t.ok(!Number.isInteger(decoded.args[0].value), 'should be float, not integer');
192
+ t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should preserve float value');
193
+ t.end();
194
+ });
195
+
196
+ test('osc: explicit integer type encoding', (t) => {
197
+ // Test explicit integer type to cover line 102
198
+ const message = {
199
+ oscType: 'message',
200
+ address: '/test',
201
+ args: [
202
+ {
203
+ type: 'i',
204
+ value: 42
205
+ }
206
+ ]
207
+ };
208
+
209
+ const buffer = toBuffer(message);
210
+ const decoded = fromBuffer(buffer);
211
+
212
+ t.equal(decoded.args[0].value, 42, 'should encode and decode integer');
213
+ t.end();
214
+ });
215
+
216
+ test('osc: explicit float type encoding', (t) => {
217
+ // Test explicit float type to cover line 105
218
+ const message = {
219
+ oscType: 'message',
220
+ address: '/test',
221
+ args: [
222
+ {
223
+ type: 'f',
224
+ value: 3.14
225
+ }
226
+ ]
227
+ };
228
+
229
+ const buffer = toBuffer(message);
230
+ const decoded = fromBuffer(buffer);
231
+
232
+ t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001, 'should encode and decode float');
233
+ t.end();
234
+ });
235
+
236
+ test('osc: explicit string type encoding', (t) => {
237
+ // Test explicit string type to cover line 108
238
+ const message = {
239
+ oscType: 'message',
240
+ address: '/test',
241
+ args: [
242
+ {
243
+ type: 's',
244
+ value: 'hello'
245
+ }
246
+ ]
247
+ };
248
+
249
+ const buffer = toBuffer(message);
250
+ const decoded = fromBuffer(buffer);
251
+
252
+ t.equal(decoded.args[0].value, 'hello', 'should encode and decode string');
253
+ t.end();
254
+ });
255
+
256
+ test('osc: explicit blob type encoding', (t) => {
257
+ // Test explicit blob type to cover line 111
258
+ const testData = Buffer.from('blob data');
259
+ const message = {
260
+ oscType: 'message',
261
+ address: '/test',
262
+ args: [
263
+ {
264
+ type: 'b',
265
+ value: testData
266
+ }
267
+ ]
268
+ };
269
+
270
+ const buffer = toBuffer(message);
271
+ const decoded = fromBuffer(buffer);
272
+
273
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
274
+ t.equal(decoded.args[0].value.toString(), 'blob data', 'should preserve blob data');
275
+ t.end();
276
+ });
277
+
278
+ test('osc: explicit boolean true type encoding', (t) => {
279
+ // Test explicit boolean true type to cover line 118
280
+ const message = {
281
+ oscType: 'message',
282
+ address: '/test',
283
+ args: [
284
+ {
285
+ type: 'T',
286
+ value: true
287
+ }
288
+ ]
289
+ };
290
+
291
+ const buffer = toBuffer(message);
292
+ const decoded = fromBuffer(buffer);
293
+
294
+ t.equal(decoded.args[0].value, true, 'should encode and decode boolean true');
295
+ t.end();
296
+ });
297
+
298
+ test('osc: MIDI type encoding with Buffer', (t) => {
299
+ // Test MIDI type with 4-byte Buffer
300
+ const midiData = Buffer.from([0x01, 0x90, 0x3C, 0x7F]); // port 1, note on, middle C, velocity 127
301
+ const message = {
302
+ oscType: 'message',
303
+ address: '/midi',
304
+ args: [
305
+ {
306
+ type: 'm',
307
+ value: midiData
308
+ }
309
+ ]
310
+ };
311
+
312
+ const buffer = toBuffer(message);
313
+ const decoded = fromBuffer(buffer);
314
+
315
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
316
+ t.equal(decoded.args[0].value.length, 4, 'should be 4 bytes');
317
+ t.equal(decoded.args[0].value[0], 0x01, 'port id should match');
318
+ t.equal(decoded.args[0].value[1], 0x90, 'status byte should match');
319
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
320
+ t.equal(decoded.args[0].value[3], 0x7F, 'data2 should match');
321
+ t.end();
322
+ });
323
+
324
+ test('osc: MIDI type encoding with object', (t) => {
325
+ // Test MIDI type with object format
326
+ const message = {
327
+ oscType: 'message',
328
+ address: '/midi',
329
+ args: [
330
+ {
331
+ type: 'midi',
332
+ value: {
333
+ port: 2,
334
+ status: 0x80,
335
+ data1: 0x40,
336
+ data2: 0x00
337
+ }
338
+ }
339
+ ]
340
+ };
341
+
342
+ const buffer = toBuffer(message);
343
+ const decoded = fromBuffer(buffer);
344
+
345
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
346
+ t.equal(decoded.args[0].value[0], 2, 'port should match');
347
+ t.equal(decoded.args[0].value[1], 0x80, 'status should match');
348
+ t.equal(decoded.args[0].value[2], 0x40, 'data1 should match');
349
+ t.equal(decoded.args[0].value[3], 0x00, 'data2 should match');
350
+ t.end();
351
+ });
352
+
353
+ test('osc: MIDI type with invalid buffer length', (t) => {
354
+ // Test MIDI type with wrong buffer length
355
+ const message = {
356
+ oscType: 'message',
357
+ address: '/midi',
358
+ args: [
359
+ {
360
+ type: 'm',
361
+ value: Buffer.from([0x90, 0x3C]) // Only 2 bytes
362
+ }
363
+ ]
364
+ };
365
+
366
+ t.throws(() => {
367
+ toBuffer(message);
368
+ }, /MIDI message must be exactly 4 bytes/, 'should throw error for wrong buffer length');
369
+ t.end();
370
+ });
371
+
372
+ test('osc: MIDI type with invalid value type', (t) => {
373
+ // Test MIDI type with invalid value
374
+ const message = {
375
+ oscType: 'message',
376
+ address: '/midi',
377
+ args: [
378
+ {
379
+ type: 'm',
380
+ value: 'invalid'
381
+ }
382
+ ]
383
+ };
384
+
385
+ t.throws(() => {
386
+ toBuffer(message);
387
+ }, /MIDI value must be a 4-byte Buffer or object/, 'should throw error for invalid value type');
388
+ t.end();
389
+ });
390
+
391
+ test('osc: MIDI type with partial object', (t) => {
392
+ // Test MIDI type with object having only some properties (should default others to 0)
393
+ const message = {
394
+ oscType: 'message',
395
+ address: '/midi',
396
+ args: [
397
+ {
398
+ type: 'm',
399
+ value: {
400
+ status: 0x90,
401
+ data1: 0x3C
402
+ // port and data2 should default to 0
403
+ }
404
+ }
405
+ ]
406
+ };
407
+
408
+ const buffer = toBuffer(message);
409
+ const decoded = fromBuffer(buffer);
410
+
411
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
412
+ t.equal(decoded.args[0].value[1], 0x90, 'status should match');
413
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should match');
414
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
415
+ t.end();
416
+ });
417
+
418
+ test('osc: MIDI type decoding with insufficient buffer data', (t) => {
419
+ // Test the error case in readMidi when buffer doesn't have enough bytes
420
+ // This manually crafts a malformed OSC buffer with MIDI type tag but insufficient data
421
+
422
+ // Create a minimal OSC message buffer with MIDI type but truncated data
423
+ // OSC Format: address + typetags + arguments
424
+ // Address: "/m" (padded to 4 bytes)
425
+ const address = Buffer.from('/m\0\0', 'ascii'); // 4 bytes
426
+
427
+ // Type tags: ",m" (padded to 4 bytes)
428
+ const typeTags = Buffer.from(',m\0\0', 'ascii'); // 4 bytes
429
+
430
+ // MIDI data: only 2 bytes instead of required 4
431
+ const insufficientMidiData = Buffer.from([0x90, 0x3C]); // Only 2 bytes, need 4
432
+
433
+ // Combine into malformed OSC buffer
434
+ const malformedBuffer = Buffer.concat([address, typeTags, insufficientMidiData]);
435
+
436
+ t.throws(() => {
437
+ fromBuffer(malformedBuffer);
438
+ }, /Not enough bytes for MIDI message/, 'should throw error when MIDI data is truncated');
439
+ t.end();
440
+ });
441
+
442
+ test('osc: MIDI type with falsy status and data1 values', (t) => {
443
+ // Test MIDI type with object having undefined/falsy status and data1 (should default to 0)
444
+ const message = {
445
+ oscType: 'message',
446
+ address: '/midi',
447
+ args: [
448
+ {
449
+ type: 'm',
450
+ value: {
451
+ port: 5,
452
+ data2: 0x40
453
+ // status and data1 are undefined, should default to 0
454
+ }
455
+ }
456
+ ]
457
+ };
458
+
459
+ const buffer = toBuffer(message);
460
+ const decoded = fromBuffer(buffer);
461
+
462
+ t.equal(decoded.args[0].value[0], 5, 'port should match');
463
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
464
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
465
+ t.equal(decoded.args[0].value[3], 0x40, 'data2 should match');
466
+ t.end();
467
+ });
468
+
469
+ test('osc: MIDI type with explicit zero status and data1 values', (t) => {
470
+ // Test MIDI type with object having explicit 0 values for status and data1
471
+ const message = {
472
+ oscType: 'message',
473
+ address: '/midi',
474
+ args: [
475
+ {
476
+ type: 'm',
477
+ value: {
478
+ port: 3,
479
+ status: 0,
480
+ data1: 0,
481
+ data2: 0x60
482
+ }
483
+ }
484
+ ]
485
+ };
486
+
487
+ const buffer = toBuffer(message);
488
+ const decoded = fromBuffer(buffer);
489
+
490
+ t.equal(decoded.args[0].value[0], 3, 'port should match');
491
+ t.equal(decoded.args[0].value[1], 0, 'status should be 0');
492
+ t.equal(decoded.args[0].value[2], 0, 'data1 should be 0');
493
+ t.equal(decoded.args[0].value[3], 0x60, 'data2 should match');
494
+ t.end();
495
+
496
+ });