node-osc 11.1.1 → 11.2.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.
Files changed (69) hide show
  1. package/.gitattributes +11 -0
  2. package/.github/workflows/bump-version.yml +2 -0
  3. package/.github/workflows/nodejs.yml +3 -0
  4. package/LICENSE +201 -165
  5. package/README.md +135 -42
  6. package/dist/lib/Bundle.js +66 -0
  7. package/dist/lib/Client.js +137 -22
  8. package/dist/lib/Message.js +87 -1
  9. package/dist/lib/Server.js +117 -6
  10. package/dist/lib/index.js +3 -0
  11. package/dist/lib/internal/decode.js +4 -4
  12. package/dist/lib/{internal/osc.js → osc.js} +70 -5
  13. package/dist/test/lib/osc.js +395 -0
  14. package/dist/test/test-client.js +152 -0
  15. package/dist/test/test-e2e.js +9 -3
  16. package/dist/test/test-encode-decode.js +849 -0
  17. package/dist/test/test-error-handling.js +116 -0
  18. package/dist/test/test-osc-internal.js +399 -41
  19. package/dist/test/test-promises.js +250 -0
  20. package/dist/test/test-types.js +42 -0
  21. package/dist/test/util.js +15 -8
  22. package/docs/API.md +477 -0
  23. package/docs/GUIDE.md +605 -0
  24. package/examples/README.md +119 -0
  25. package/examples/async-await.mjs +57 -0
  26. package/examples/bundle-example.mjs +92 -0
  27. package/examples/client.js +22 -5
  28. package/examples/error-handling.mjs +152 -0
  29. package/examples/esm.mjs +21 -0
  30. package/examples/server.js +16 -0
  31. package/jsdoc.json +16 -0
  32. package/lib/Bundle.mjs +66 -0
  33. package/lib/Client.mjs +137 -22
  34. package/lib/Message.mjs +87 -1
  35. package/lib/Server.mjs +117 -6
  36. package/lib/index.mjs +1 -0
  37. package/lib/internal/decode.mjs +4 -4
  38. package/lib/{internal/osc.mjs → osc.mjs} +71 -4
  39. package/package.json +12 -10
  40. package/rollup.config.mjs +48 -41
  41. package/scripts/generate-docs.mjs +229 -0
  42. package/test/fixtures/types/test-cjs-types.ts +19 -0
  43. package/test/fixtures/types/test-esm-types.ts +35 -0
  44. package/test/fixtures/types/tsconfig-cjs.test.json +17 -0
  45. package/test/fixtures/types/tsconfig-esm.test.json +17 -0
  46. package/test/test-bundle.mjs +0 -1
  47. package/test/test-client.mjs +152 -0
  48. package/test/test-e2e.mjs +9 -3
  49. package/test/test-encode-decode.mjs +847 -0
  50. package/test/test-error-handling.mjs +115 -0
  51. package/test/test-osc-internal.mjs +400 -42
  52. package/test/test-promises.mjs +249 -0
  53. package/test/test-types.mjs +39 -0
  54. package/test/util.mjs +15 -8
  55. package/tsconfig.json +45 -0
  56. package/types/Bundle.d.mts +70 -0
  57. package/types/Bundle.d.mts.map +1 -0
  58. package/types/Client.d.mts +101 -0
  59. package/types/Client.d.mts.map +1 -0
  60. package/types/Message.d.mts +84 -0
  61. package/types/Message.d.mts.map +1 -0
  62. package/types/Server.d.mts +98 -0
  63. package/types/Server.d.mts.map +1 -0
  64. package/types/index.d.mts +6 -0
  65. package/types/index.d.mts.map +1 -0
  66. package/types/internal/decode.d.mts +4 -0
  67. package/types/internal/decode.d.mts.map +1 -0
  68. package/types/osc.d.mts +66 -0
  69. package/types/osc.d.mts.map +1 -0
@@ -0,0 +1,849 @@
1
+ 'use strict';
2
+
3
+ var tap = require('tap');
4
+ var nodeOsc = require('node-osc');
5
+
6
+ tap.test('encode and decode: simple message', (t) => {
7
+ const message = new nodeOsc.Message('/test', 42, 'hello', 3.14);
8
+
9
+ const buffer = nodeOsc.encode(message);
10
+ t.ok(Buffer.isBuffer(buffer), 'encode should return a Buffer');
11
+
12
+ const decoded = nodeOsc.decode(buffer);
13
+ t.equal(decoded.oscType, 'message', 'should decode as message');
14
+ t.equal(decoded.address, '/test', 'should preserve address');
15
+ t.equal(decoded.args.length, 3, 'should have 3 arguments');
16
+ t.equal(decoded.args[0].value, 42, 'should preserve integer argument');
17
+ t.equal(decoded.args[1].value, 'hello', 'should preserve string argument');
18
+ t.ok(Math.abs(decoded.args[2].value - 3.14) < 0.001, 'should preserve float argument');
19
+
20
+ t.end();
21
+ });
22
+
23
+ tap.test('encode and decode: bundle', (t) => {
24
+ const bundle = new nodeOsc.Bundle(
25
+ ['/test1', 100],
26
+ ['/test2', 'world']
27
+ );
28
+
29
+ const buffer = nodeOsc.encode(bundle);
30
+ t.ok(Buffer.isBuffer(buffer), 'encode should return a Buffer');
31
+
32
+ const decoded = nodeOsc.decode(buffer);
33
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
34
+ t.equal(decoded.timetag, 0, 'should have timetag of 0');
35
+ t.equal(decoded.elements.length, 2, 'should have 2 elements');
36
+ t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
37
+ t.equal(decoded.elements[0].address, '/test1', 'first element should have correct address');
38
+ t.equal(decoded.elements[0].args[0].value, 100, 'first element should have correct argument');
39
+ t.equal(decoded.elements[1].address, '/test2', 'second element should have correct address');
40
+ t.equal(decoded.elements[1].args[0].value, 'world', 'second element should have correct argument');
41
+
42
+ t.end();
43
+ });
44
+
45
+ tap.test('encode and decode: nested bundle', (t) => {
46
+ const innerBundle = new nodeOsc.Bundle(['/inner', 42]);
47
+ const outerBundle = new nodeOsc.Bundle(10, ['/outer', 'test']);
48
+ outerBundle.append(innerBundle);
49
+
50
+ const buffer = nodeOsc.encode(outerBundle);
51
+ const decoded = nodeOsc.decode(buffer);
52
+
53
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
54
+ t.ok(decoded.timetag > 0, 'should have non-zero timetag');
55
+ t.equal(decoded.elements.length, 2, 'should have 2 elements');
56
+ t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
57
+ t.equal(decoded.elements[1].oscType, 'bundle', 'second element should be bundle');
58
+ t.equal(decoded.elements[1].elements[0].address, '/inner', 'nested bundle should preserve address');
59
+
60
+ t.end();
61
+ });
62
+
63
+ tap.test('encode and decode: round-trip with boolean values', (t) => {
64
+ const message = new nodeOsc.Message('/booleans');
65
+ message.append(true);
66
+ message.append(false);
67
+
68
+ const buffer = nodeOsc.encode(message);
69
+ const decoded = nodeOsc.decode(buffer);
70
+
71
+ t.equal(decoded.args[0].value, true, 'should preserve true value');
72
+ t.equal(decoded.args[1].value, false, 'should preserve false value');
73
+
74
+ t.end();
75
+ });
76
+
77
+ tap.test('encode and decode: round-trip with blob', (t) => {
78
+ const blobData = Buffer.from([0x01, 0x02, 0x03, 0x04]);
79
+ const message = new nodeOsc.Message('/blob', { type: 'blob', value: blobData });
80
+
81
+ const buffer = nodeOsc.encode(message);
82
+ const decoded = nodeOsc.decode(buffer);
83
+
84
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
85
+ t.same(decoded.args[0].value, blobData, 'should preserve blob data');
86
+
87
+ t.end();
88
+ });
89
+
90
+ tap.test('encode and decode: message with mixed types', (t) => {
91
+ const message = new nodeOsc.Message('/mixed');
92
+ message.append(42); // integer
93
+ message.append(3.14); // float
94
+ message.append('hello'); // string
95
+ message.append(true); // boolean
96
+ message.append({ type: 'blob', value: Buffer.from([0x01, 0x02]) }); // blob
97
+
98
+ const buffer = nodeOsc.encode(message);
99
+ const decoded = nodeOsc.decode(buffer);
100
+
101
+ t.equal(decoded.args.length, 5, 'should have 5 arguments');
102
+ t.equal(decoded.args[0].value, 42, 'should preserve integer');
103
+ t.ok(Math.abs(decoded.args[1].value - 3.14) < 0.001, 'should preserve float');
104
+ t.equal(decoded.args[2].value, 'hello', 'should preserve string');
105
+ t.equal(decoded.args[3].value, true, 'should preserve boolean');
106
+ t.ok(Buffer.isBuffer(decoded.args[4].value), 'should preserve blob as Buffer');
107
+
108
+ t.end();
109
+ });
110
+
111
+ tap.test('decode: raw buffer from external source', (t) => {
112
+ // Simulate receiving a raw OSC message buffer from an external source
113
+ // This is a hand-crafted OSC message for "/test" with integer 123
114
+ const rawBuffer = Buffer.from([
115
+ 0x2f, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, // "/test\0\0\0"
116
+ 0x2c, 0x69, 0x00, 0x00, // ",i\0\0"
117
+ 0x00, 0x00, 0x00, 0x7b // 123
118
+ ]);
119
+
120
+ const decoded = nodeOsc.decode(rawBuffer);
121
+
122
+ t.equal(decoded.oscType, 'message', 'should decode as message');
123
+ t.equal(decoded.address, '/test', 'should decode correct address');
124
+ t.equal(decoded.args.length, 1, 'should have 1 argument');
125
+ t.equal(decoded.args[0].value, 123, 'should decode correct value');
126
+
127
+ t.end();
128
+ });
129
+
130
+ tap.test('encode: message for external consumption', (t) => {
131
+ const message = new nodeOsc.Message('/oscillator/frequency', 440);
132
+ const buffer = nodeOsc.encode(message);
133
+
134
+ // Verify buffer is suitable for sending over network
135
+ t.ok(Buffer.isBuffer(buffer), 'should be a Buffer');
136
+ t.ok(buffer.length > 0, 'should have non-zero length');
137
+
138
+ // Verify it can be decoded back
139
+ const decoded = nodeOsc.decode(buffer);
140
+ t.equal(decoded.address, '/oscillator/frequency', 'should preserve address');
141
+ t.equal(decoded.args[0].value, 440, 'should preserve value');
142
+
143
+ t.end();
144
+ });
145
+
146
+ tap.test('encode and decode: MIDI messages with Buffer', (t) => {
147
+ const midiBuffer = Buffer.from([0x00, 0x90, 0x3C, 0x7F]); // port 0, note on, note 60, velocity 127
148
+ const message = new nodeOsc.Message('/midi', { type: 'midi', value: midiBuffer });
149
+
150
+ const buffer = nodeOsc.encode(message);
151
+ const decoded = nodeOsc.decode(buffer);
152
+
153
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
154
+ t.same(decoded.args[0].value, midiBuffer, 'should preserve MIDI data');
155
+
156
+ t.end();
157
+ });
158
+
159
+ tap.test('encode and decode: MIDI messages with object', (t) => {
160
+ const midiObj = { port: 0, status: 0x90, data1: 0x3C, data2: 0x7F };
161
+ const message = new nodeOsc.Message('/midi', { type: 'midi', value: midiObj });
162
+
163
+ const buffer = nodeOsc.encode(message);
164
+ const decoded = nodeOsc.decode(buffer);
165
+
166
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
167
+ t.equal(decoded.args[0].value[0], 0, 'port should be 0');
168
+ t.equal(decoded.args[0].value[1], 0x90, 'status should be 0x90');
169
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should be 0x3C');
170
+ t.equal(decoded.args[0].value[3], 0x7F, 'data2 should be 0x7F');
171
+
172
+ t.end();
173
+ });
174
+
175
+ tap.test('encode and decode: null type tag', (t) => {
176
+ // Create a message with null value by manually constructing the buffer
177
+ // OSC format: address + type tags + no data for 'N' type
178
+ const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
179
+ const typeTagsBuf = Buffer.from(',N\0\0', 'utf8');
180
+ const buffer = Buffer.concat([addressBuf, typeTagsBuf]);
181
+
182
+ const decoded = nodeOsc.decode(buffer);
183
+
184
+ t.equal(decoded.oscType, 'message', 'should decode as message');
185
+ t.equal(decoded.address, '/test', 'should have correct address');
186
+ t.equal(decoded.args[0].value, null, 'should decode null value');
187
+
188
+ t.end();
189
+ });
190
+
191
+ tap.test('encode: error on unknown argument type', (t) => {
192
+ const message = new nodeOsc.Message('/test', { type: 'unknown', value: 42 });
193
+
194
+ t.throws(() => {
195
+ nodeOsc.encode(message);
196
+ }, /Unknown argument type: unknown/, 'should throw on unknown type');
197
+
198
+ t.end();
199
+ });
200
+
201
+ tap.test('encode: error on unencodable argument', (t) => {
202
+ const message = new nodeOsc.Message('/test');
203
+ message.args.push(() => {}); // Functions can't be encoded
204
+
205
+ t.throws(() => {
206
+ nodeOsc.encode(message);
207
+ }, /Don't know how to encode argument/, 'should throw on unencodable argument');
208
+
209
+ t.end();
210
+ });
211
+
212
+ tap.test('decode: error on unknown type tag', (t) => {
213
+ // Create a message with an unknown type tag 'X'
214
+ const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
215
+ const typeTagsBuf = Buffer.from(',X\0\0', 'utf8');
216
+ const buffer = Buffer.concat([addressBuf, typeTagsBuf]);
217
+
218
+ t.throws(() => {
219
+ nodeOsc.decode(buffer);
220
+ }, /I don't understand the argument code X/, 'should throw on unknown type tag');
221
+
222
+ t.end();
223
+ });
224
+
225
+ tap.test('encode: MIDI error on wrong buffer length', (t) => {
226
+ const wrongBuffer = Buffer.from([0x90, 0x3C]); // Only 2 bytes, should be 4
227
+ const message = new nodeOsc.Message('/midi', { type: 'midi', value: wrongBuffer });
228
+
229
+ t.throws(() => {
230
+ nodeOsc.encode(message);
231
+ }, /MIDI message must be exactly 4 bytes/, 'should throw on wrong buffer length');
232
+
233
+ t.end();
234
+ });
235
+
236
+ tap.test('encode: MIDI error on invalid value type', (t) => {
237
+ const message = new nodeOsc.Message('/midi', { type: 'midi', value: 'not a buffer' });
238
+
239
+ t.throws(() => {
240
+ nodeOsc.encode(message);
241
+ }, /MIDI value must be a 4-byte Buffer/, 'should throw on invalid MIDI value');
242
+
243
+ t.end();
244
+ });
245
+
246
+ tap.test('decode: MIDI error on insufficient buffer', (t) => {
247
+ // Create a message with MIDI type tag but not enough data
248
+ const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
249
+ const typeTagsBuf = Buffer.from(',m\0\0', 'utf8');
250
+ const dataBuf = Buffer.from([0x90, 0x3C]); // Only 2 bytes, should be 4
251
+ const buffer = Buffer.concat([addressBuf, typeTagsBuf, dataBuf]);
252
+
253
+ t.throws(() => {
254
+ nodeOsc.decode(buffer);
255
+ }, /Not enough bytes for MIDI message/, 'should throw on insufficient MIDI data');
256
+
257
+ t.end();
258
+ });
259
+
260
+ tap.test('encode and decode: Buffer argument (inferred blob type)', (t) => {
261
+ const bufferArg = Buffer.from([0x01, 0x02, 0x03, 0x04]);
262
+ const message = new nodeOsc.Message('/buffer', bufferArg);
263
+
264
+ const buffer = nodeOsc.encode(message);
265
+ const decoded = nodeOsc.decode(buffer);
266
+
267
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
268
+ t.same(decoded.args[0].value, bufferArg, 'should preserve Buffer data');
269
+
270
+ t.end();
271
+ });
272
+
273
+ tap.test('encode and decode: double type (treated as float)', (t) => {
274
+ const message = new nodeOsc.Message('/double', { type: 'double', value: 3.141592653589793 });
275
+
276
+ const buffer = nodeOsc.encode(message);
277
+ const decoded = nodeOsc.decode(buffer);
278
+
279
+ // Doubles are encoded as floats in OSC 1.0, so precision is reduced
280
+ t.ok(Math.abs(decoded.args[0].value - 3.141592653589793) < 0.001, 'should preserve approximate value');
281
+
282
+ t.end();
283
+ });
284
+
285
+ tap.test('decode: error on malformed type tags (no leading comma)', (t) => {
286
+ // Create a message with malformed type tags (missing comma)
287
+ const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
288
+ const typeTagsBuf = Buffer.from('iXX\0', 'utf8'); // Should start with comma
289
+ const buffer = Buffer.concat([addressBuf, typeTagsBuf]);
290
+
291
+ t.throws(() => {
292
+ nodeOsc.decode(buffer);
293
+ }, /Malformed Packet/, 'should throw on malformed type tags');
294
+
295
+ t.end();
296
+ });
297
+
298
+ tap.test('encode and decode: nested bundle with message and bundle elements', (t) => {
299
+ // Test the else branch in encodeBundleToBuffer for message elements
300
+ const innerBundle = new nodeOsc.Bundle(['/inner/message', 123]);
301
+ const outerBundle = new nodeOsc.Bundle(['/outer/message', 'test'], innerBundle);
302
+
303
+ const buffer = nodeOsc.encode(outerBundle);
304
+ const decoded = nodeOsc.decode(buffer);
305
+
306
+ t.equal(decoded.oscType, 'bundle', 'should be a bundle');
307
+ t.equal(decoded.elements.length, 2, 'should have 2 elements');
308
+ t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
309
+ t.equal(decoded.elements[1].oscType, 'bundle', 'second element should be bundle');
310
+
311
+ t.end();
312
+ });
313
+
314
+ tap.test('encode and decode: MIDI with all zero values', (t) => {
315
+ // Test MIDI encoding with object where all values are 0 or falsy (covers || branches)
316
+ const message = new nodeOsc.Message('/midi', {
317
+ type: 'midi',
318
+ value: {
319
+ port: 0,
320
+ status: 0,
321
+ data1: 0,
322
+ data2: 0
323
+ }
324
+ });
325
+
326
+ const buffer = nodeOsc.encode(message);
327
+ const decoded = nodeOsc.decode(buffer);
328
+
329
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
330
+ t.equal(decoded.args[0].value[0], 0, 'port should be 0');
331
+ t.equal(decoded.args[0].value[1], 0, 'status should be 0');
332
+ t.equal(decoded.args[0].value[2], 0, 'data1 should be 0');
333
+ t.equal(decoded.args[0].value[3], 0, 'data2 should be 0');
334
+
335
+ t.end();
336
+ });
337
+
338
+ tap.test('encode and decode: MIDI with undefined values defaulting', (t) => {
339
+ // Test MIDI encoding where values are undefined (triggers || default to 0)
340
+ const message = new nodeOsc.Message('/midi', {
341
+ type: 'midi',
342
+ value: {
343
+ // All undefined, should default to 0
344
+ }
345
+ });
346
+
347
+ const buffer = nodeOsc.encode(message);
348
+ const decoded = nodeOsc.decode(buffer);
349
+
350
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
351
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
352
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
353
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
354
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
355
+
356
+ t.end();
357
+ });
358
+
359
+ tap.test('encode and decode: MIDI with only port set', (t) => {
360
+ // Test MIDI where only port is set, others should default
361
+ const message = new nodeOsc.Message('/midi', {
362
+ type: 'midi',
363
+ value: {
364
+ port: 3
365
+ // status, data1, data2 undefined
366
+ }
367
+ });
368
+
369
+ const buffer = nodeOsc.encode(message);
370
+ const decoded = nodeOsc.decode(buffer);
371
+
372
+ t.equal(decoded.args[0].value[0], 3, 'port should be 3');
373
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
374
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
375
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
376
+
377
+ t.end();
378
+ });
379
+
380
+ tap.test('encode and decode: MIDI with only status set', (t) => {
381
+ // Test MIDI where only status is set
382
+ const message = new nodeOsc.Message('/midi', {
383
+ type: 'midi',
384
+ value: {
385
+ status: 0x90
386
+ // port, data1, data2 undefined
387
+ }
388
+ });
389
+
390
+ const buffer = nodeOsc.encode(message);
391
+ const decoded = nodeOsc.decode(buffer);
392
+
393
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
394
+ t.equal(decoded.args[0].value[1], 0x90, 'status should be 0x90');
395
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
396
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
397
+
398
+ t.end();
399
+ });
400
+
401
+ tap.test('encode and decode: MIDI with only data1 set', (t) => {
402
+ // Test MIDI where only data1 is set
403
+ const message = new nodeOsc.Message('/midi', {
404
+ type: 'midi',
405
+ value: {
406
+ data1: 0x3C
407
+ // port, status, data2 undefined
408
+ }
409
+ });
410
+
411
+ const buffer = nodeOsc.encode(message);
412
+ const decoded = nodeOsc.decode(buffer);
413
+
414
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
415
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
416
+ t.equal(decoded.args[0].value[2], 0x3C, 'data1 should be 0x3C');
417
+ t.equal(decoded.args[0].value[3], 0, 'data2 should default to 0');
418
+
419
+ t.end();
420
+ });
421
+
422
+ tap.test('encode and decode: MIDI with only data2 set', (t) => {
423
+ // Test MIDI where only data2 is set
424
+ const message = new nodeOsc.Message('/midi', {
425
+ type: 'midi',
426
+ value: {
427
+ data2: 0x7F
428
+ // port, status, data1 undefined
429
+ }
430
+ });
431
+
432
+ const buffer = nodeOsc.encode(message);
433
+ const decoded = nodeOsc.decode(buffer);
434
+
435
+ t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
436
+ t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
437
+ t.equal(decoded.args[0].value[2], 0, 'data1 should default to 0');
438
+ t.equal(decoded.args[0].value[3], 0x7F, 'data2 should be 0x7F');
439
+
440
+ t.end();
441
+ });
442
+
443
+ tap.test('encode and decode: explicit integer type name', (t) => {
444
+ // Test with 'integer' type name (alternate for 'i')
445
+ const message = new nodeOsc.Message('/test', { type: 'integer', value: 999 });
446
+
447
+ const buffer = nodeOsc.encode(message);
448
+ const decoded = nodeOsc.decode(buffer);
449
+
450
+ t.equal(decoded.args[0].value, 999, 'should encode and decode integer');
451
+ t.end();
452
+ });
453
+
454
+ tap.test('encode and decode: explicit float type name', (t) => {
455
+ // Test with 'float' type name (alternate for 'f')
456
+ const message = new nodeOsc.Message('/test', { type: 'float', value: 2.718 });
457
+
458
+ const buffer = nodeOsc.encode(message);
459
+ const decoded = nodeOsc.decode(buffer);
460
+
461
+ t.ok(Math.abs(decoded.args[0].value - 2.718) < 0.001, 'should encode and decode float');
462
+ t.end();
463
+ });
464
+
465
+ tap.test('encode and decode: explicit string type name', (t) => {
466
+ // Test with 'string' type name (alternate for 's')
467
+ const message = new nodeOsc.Message('/test', { type: 'string', value: 'alternate' });
468
+
469
+ const buffer = nodeOsc.encode(message);
470
+ const decoded = nodeOsc.decode(buffer);
471
+
472
+ t.equal(decoded.args[0].value, 'alternate', 'should encode and decode string');
473
+ t.end();
474
+ });
475
+
476
+ tap.test('encode and decode: explicit blob type name', (t) => {
477
+ // Test with 'blob' type name (alternate for 'b')
478
+ const blobData = Buffer.from([0xDE, 0xAD, 0xBE, 0xEF]);
479
+ const message = new nodeOsc.Message('/test', { type: 'blob', value: blobData });
480
+
481
+ const buffer = nodeOsc.encode(message);
482
+ const decoded = nodeOsc.decode(buffer);
483
+
484
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
485
+ t.same(decoded.args[0].value, blobData, 'should preserve blob data');
486
+ t.end();
487
+ });
488
+
489
+ tap.test('encode and decode: explicit boolean type name', (t) => {
490
+ // Test with 'boolean' type name (alternate for 'T'/'F')
491
+ const message1 = new nodeOsc.Message('/test', { type: 'boolean', value: true });
492
+ const message2 = new nodeOsc.Message('/test', { type: 'boolean', value: false });
493
+
494
+ const buffer1 = nodeOsc.encode(message1);
495
+ const buffer2 = nodeOsc.encode(message2);
496
+ const decoded1 = nodeOsc.decode(buffer1);
497
+ const decoded2 = nodeOsc.decode(buffer2);
498
+
499
+ t.equal(decoded1.args[0].value, true, 'should encode and decode boolean true');
500
+ t.equal(decoded2.args[0].value, false, 'should encode and decode boolean false');
501
+ t.end();
502
+ });
503
+
504
+ tap.test('encode and decode: explicit T type tag', (t) => {
505
+ // Test with 'T' type tag directly (not 'boolean')
506
+ const message = new nodeOsc.Message('/test', { type: 'T', value: true });
507
+
508
+ const buffer = nodeOsc.encode(message);
509
+ const decoded = nodeOsc.decode(buffer);
510
+
511
+ t.equal(decoded.args[0].value, true, 'should encode and decode true with T tag');
512
+ t.end();
513
+ });
514
+
515
+ tap.test('encode and decode: explicit double type name', (t) => {
516
+ // Test with 'double' type name
517
+ const message = new nodeOsc.Message('/test', { type: 'double', value: 3.141592653589793 });
518
+
519
+ const buffer = nodeOsc.encode(message);
520
+ const decoded = nodeOsc.decode(buffer);
521
+
522
+ t.ok(Math.abs(decoded.args[0].value - 3.141592653589793) < 0.001, 'should encode double as float');
523
+ t.end();
524
+ });
525
+
526
+ tap.test('encode and decode: raw message with float type', (t) => {
527
+ // Send raw message object directly to hit the 'float' case label
528
+ const rawMessage = {
529
+ oscType: 'message',
530
+ address: '/float',
531
+ args: [{ type: 'float', value: 1.414 }]
532
+ };
533
+
534
+ const buffer = nodeOsc.encode(rawMessage);
535
+ const decoded = nodeOsc.decode(buffer);
536
+
537
+ t.ok(Math.abs(decoded.args[0].value - 1.414) < 0.001, 'should handle float type');
538
+ t.end();
539
+ });
540
+
541
+ tap.test('encode and decode: raw message with blob type', (t) => {
542
+ // Send raw message object directly to hit the 'blob' case label
543
+ const blobData = Buffer.from([1, 2, 3, 4]);
544
+ const rawMessage = {
545
+ oscType: 'message',
546
+ address: '/blob',
547
+ args: [{ type: 'blob', value: blobData }]
548
+ };
549
+
550
+ const buffer = nodeOsc.encode(rawMessage);
551
+ const decoded = nodeOsc.decode(buffer);
552
+
553
+ t.same(decoded.args[0].value, blobData, 'should handle blob type');
554
+ t.end();
555
+ });
556
+
557
+ tap.test('encode and decode: raw message with double type', (t) => {
558
+ // Send raw message object directly to hit the 'double' case label
559
+ const rawMessage = {
560
+ oscType: 'message',
561
+ address: '/double',
562
+ args: [{ type: 'double', value: 2.71828 }]
563
+ };
564
+
565
+ const buffer = nodeOsc.encode(rawMessage);
566
+ const decoded = nodeOsc.decode(buffer);
567
+
568
+ t.ok(Math.abs(decoded.args[0].value - 2.71828) < 0.001, 'should handle double type');
569
+ t.end();
570
+ });
571
+
572
+ tap.test('encode and decode: raw message with T type', (t) => {
573
+ // Send raw message object directly to hit the 'T' case label
574
+ const rawMessage = {
575
+ oscType: 'message',
576
+ address: '/bool',
577
+ args: [{ type: 'T', value: true }]
578
+ };
579
+
580
+ const buffer = nodeOsc.encode(rawMessage);
581
+ const decoded = nodeOsc.decode(buffer);
582
+
583
+ t.equal(decoded.args[0].value, true, 'should handle T type');
584
+ t.end();
585
+ });
586
+
587
+ tap.test('encode and decode: raw message with midi type', (t) => {
588
+ // Send raw message object directly to hit the 'midi' case label
589
+ const midiData = Buffer.from([0x01, 0x90, 0x3C, 0x7F]);
590
+ const rawMessage = {
591
+ oscType: 'message',
592
+ address: '/midi',
593
+ args: [{ type: 'midi', value: midiData }]
594
+ };
595
+
596
+ const buffer = nodeOsc.encode(rawMessage);
597
+ const decoded = nodeOsc.decode(buffer);
598
+
599
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should handle midi type');
600
+ t.equal(decoded.args[0].value.length, 4, 'should have 4 bytes');
601
+ t.end();
602
+ });
603
+
604
+ tap.test('encode and decode: blob with length multiple of 4', (t) => {
605
+ // Test blob where length % 4 === 0 (padding === 4, should use 0 padding)
606
+ const blobData = Buffer.from([0x00, 0x01, 0x02, 0x03]); // length 4, multiple of 4
607
+ const message = new nodeOsc.Message('/blob4', { type: 'b', value: blobData });
608
+
609
+ const buffer = nodeOsc.encode(message);
610
+ const decoded = nodeOsc.decode(buffer);
611
+
612
+ t.same(decoded.args[0].value, blobData, 'should handle blob with length multiple of 4');
613
+ t.end();
614
+ });
615
+
616
+ tap.test('encode and decode: blob with length not multiple of 4', (t) => {
617
+ // Test blob where length % 4 !== 0 (padding < 4)
618
+ const blobData = Buffer.from([0xAA, 0xBB, 0xCC]); // length 3, not multiple of 4
619
+ const message = new nodeOsc.Message('/blob3', { type: 'b', value: blobData });
620
+
621
+ const buffer = nodeOsc.encode(message);
622
+ const decoded = nodeOsc.decode(buffer);
623
+
624
+ t.same(decoded.args[0].value, blobData, 'should handle blob with length not multiple of 4');
625
+ t.end();
626
+ });
627
+
628
+ tap.test('encode and decode: blob with length 1', (t) => {
629
+ // Test blob with length 1 (padding will be 3)
630
+ const blobData = Buffer.from([0xFF]); // length 1
631
+ const message = new nodeOsc.Message('/blob1', { type: 'b', value: blobData });
632
+
633
+ const buffer = nodeOsc.encode(message);
634
+ const decoded = nodeOsc.decode(buffer);
635
+
636
+ t.same(decoded.args[0].value, blobData, 'should handle blob with length 1');
637
+ t.end();
638
+ });
639
+
640
+ tap.test('encode and decode: blob with length 2', (t) => {
641
+ // Test blob with length 2 (padding will be 2)
642
+ const blobData = Buffer.from([0xDE, 0xAD]); // length 2
643
+ const message = new nodeOsc.Message('/blob2', { type: 'b', value: blobData });
644
+
645
+ const buffer = nodeOsc.encode(message);
646
+ const decoded = nodeOsc.decode(buffer);
647
+
648
+ t.same(decoded.args[0].value, blobData, 'should handle blob with length 2');
649
+ t.end();
650
+ });
651
+
652
+ tap.test('encode and decode: blob with length 8', (t) => {
653
+ // Test blob with length 8 (multiple of 4, padding === 4)
654
+ const blobData = Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]); // length 8
655
+ const message = new nodeOsc.Message('/blob8', { type: 'b', value: blobData });
656
+
657
+ const buffer = nodeOsc.encode(message);
658
+ const decoded = nodeOsc.decode(buffer);
659
+
660
+ t.same(decoded.args[0].value, blobData, 'should handle blob with length 8');
661
+ t.end();
662
+ });
663
+
664
+ tap.test('encode and decode: MUST hit float case label directly', (t) => {
665
+ // This test MUST hit the 'float' case label (line 139) in dist/lib/osc.js
666
+ // We import from 'node-osc' which uses dist/lib/osc.js in CJS
667
+ // We use type: 'float' explicitly (not 'f')
668
+ const rawMessage = {
669
+ oscType: 'message',
670
+ address: '/float-label-test',
671
+ args: [{ type: 'float', value: 123.456 }]
672
+ };
673
+
674
+ const buffer = nodeOsc.encode(rawMessage);
675
+ const decoded = nodeOsc.decode(buffer);
676
+
677
+ t.ok(Math.abs(decoded.args[0].value - 123.456) < 0.001, 'should encode/decode float');
678
+ t.end();
679
+ });
680
+
681
+ tap.test('encode and decode: MUST hit double case label directly', (t) => {
682
+ // This test MUST hit the 'double' case label (line 148) in dist/lib/osc.js
683
+ const rawMessage = {
684
+ oscType: 'message',
685
+ address: '/double-label-test',
686
+ args: [{ type: 'double', value: 987.654 }]
687
+ };
688
+
689
+ const buffer = nodeOsc.encode(rawMessage);
690
+ const decoded = nodeOsc.decode(buffer);
691
+
692
+ t.ok(Math.abs(decoded.args[0].value - 987.654) < 0.001, 'should encode/decode double');
693
+ t.end();
694
+ });
695
+
696
+ tap.test('encode and decode: MUST hit midi case label directly', (t) => {
697
+ // This test MUST hit the 'midi' case label (line 155) in dist/lib/osc.js
698
+ const midiBuffer = Buffer.from([0x02, 0xA0, 0x50, 0x60]);
699
+ const rawMessage = {
700
+ oscType: 'message',
701
+ address: '/midi-label-test',
702
+ args: [{ type: 'midi', value: midiBuffer }]
703
+ };
704
+
705
+ const buffer = nodeOsc.encode(rawMessage);
706
+ const decoded = nodeOsc.decode(buffer);
707
+
708
+ t.ok(Buffer.isBuffer(decoded.args[0].value), 'should encode/decode midi');
709
+ t.equal(decoded.args[0].value.length, 4, 'should have 4 bytes');
710
+ t.end();
711
+ });
712
+
713
+ // Tests for explicit type name coverage (both short and long forms)
714
+ tap.test('encode and decode: type "f" (short form for float)', (t) => {
715
+ const msg = new nodeOsc.Message('/test', { type: 'f', value: 1.23 });
716
+ const buffer = nodeOsc.encode(msg);
717
+ const decoded = nodeOsc.decode(buffer);
718
+
719
+ t.ok(Math.abs(decoded.args[0].value - 1.23) < 0.001);
720
+ t.end();
721
+ });
722
+
723
+ tap.test('encode and decode: type "float" (long form)', (t) => {
724
+ const msg = new nodeOsc.Message('/test', { type: 'float', value: 3.14 });
725
+ const buffer = nodeOsc.encode(msg);
726
+ const decoded = nodeOsc.decode(buffer);
727
+
728
+ t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001);
729
+ t.end();
730
+ });
731
+
732
+ tap.test('encode and decode: type "d" (short form for double)', (t) => {
733
+ const msg = new nodeOsc.Message('/test', { type: 'd', value: 4.56 });
734
+ const buffer = nodeOsc.encode(msg);
735
+ const decoded = nodeOsc.decode(buffer);
736
+
737
+ t.ok(Math.abs(decoded.args[0].value - 4.56) < 0.001);
738
+ t.end();
739
+ });
740
+
741
+ tap.test('encode and decode: type "m" (short form for MIDI)', (t) => {
742
+ const msg = new nodeOsc.Message('/test', { type: 'm', value: Buffer.from([5, 6, 7, 8]) });
743
+ const buffer = nodeOsc.encode(msg);
744
+ const decoded = nodeOsc.decode(buffer);
745
+
746
+ t.ok(Buffer.isBuffer(decoded.args[0].value));
747
+ t.end();
748
+ });
749
+
750
+ tap.test('encode and decode: type "midi" (long form)', (t) => {
751
+ const msg = new nodeOsc.Message('/test', { type: 'midi', value: Buffer.from([1, 2, 3, 4]) });
752
+ const buffer = nodeOsc.encode(msg);
753
+ const decoded = nodeOsc.decode(buffer);
754
+
755
+ t.ok(Buffer.isBuffer(decoded.args[0].value));
756
+ t.end();
757
+ });
758
+
759
+ tap.test('encode and decode: type "i" (short form for integer)', (t) => {
760
+ const msg = new nodeOsc.Message('/test', { type: 'i', value: 42 });
761
+ const buffer = nodeOsc.encode(msg);
762
+ const decoded = nodeOsc.decode(buffer);
763
+
764
+ t.equal(decoded.args[0].value, 42);
765
+ t.end();
766
+ });
767
+
768
+ tap.test('encode and decode: type "integer" (long form)', (t) => {
769
+ const msg = new nodeOsc.Message('/test', { type: 'integer', value: 999 });
770
+ const buffer = nodeOsc.encode(msg);
771
+ const decoded = nodeOsc.decode(buffer);
772
+
773
+ t.equal(decoded.args[0].value, 999);
774
+ t.end();
775
+ });
776
+
777
+ tap.test('encode and decode: type "s" (short form for string)', (t) => {
778
+ const msg = new nodeOsc.Message('/test', { type: 's', value: 'hello' });
779
+ const buffer = nodeOsc.encode(msg);
780
+ const decoded = nodeOsc.decode(buffer);
781
+
782
+ t.equal(decoded.args[0].value, 'hello');
783
+ t.end();
784
+ });
785
+
786
+ tap.test('encode and decode: type "string" (long form)', (t) => {
787
+ const msg = new nodeOsc.Message('/test', { type: 'string', value: 'world' });
788
+ const buffer = nodeOsc.encode(msg);
789
+ const decoded = nodeOsc.decode(buffer);
790
+
791
+ t.equal(decoded.args[0].value, 'world');
792
+ t.end();
793
+ });
794
+
795
+ tap.test('encode and decode: type "b" (short form for blob)', (t) => {
796
+ const msg = new nodeOsc.Message('/test', { type: 'b', value: Buffer.from([0xAA, 0xBB]) });
797
+ const buffer = nodeOsc.encode(msg);
798
+ const decoded = nodeOsc.decode(buffer);
799
+
800
+ t.ok(Buffer.isBuffer(decoded.args[0].value));
801
+ t.same(decoded.args[0].value, Buffer.from([0xAA, 0xBB]));
802
+ t.end();
803
+ });
804
+
805
+ tap.test('encode and decode: type "blob" (long form)', (t) => {
806
+ const msg = new nodeOsc.Message('/test', { type: 'blob', value: Buffer.from([0xCC, 0xDD]) });
807
+ const buffer = nodeOsc.encode(msg);
808
+ const decoded = nodeOsc.decode(buffer);
809
+
810
+ t.ok(Buffer.isBuffer(decoded.args[0].value));
811
+ t.same(decoded.args[0].value, Buffer.from([0xCC, 0xDD]));
812
+ t.end();
813
+ });
814
+
815
+ tap.test('encode and decode: type "T" (explicit true)', (t) => {
816
+ const msg = new nodeOsc.Message('/test', { type: 'T', value: true });
817
+ const buffer = nodeOsc.encode(msg);
818
+ const decoded = nodeOsc.decode(buffer);
819
+
820
+ t.equal(decoded.args[0].value, true);
821
+ t.end();
822
+ });
823
+
824
+ tap.test('encode and decode: type "F" (explicit false)', (t) => {
825
+ const msg = new nodeOsc.Message('/test', { type: 'F', value: false });
826
+ const buffer = nodeOsc.encode(msg);
827
+ const decoded = nodeOsc.decode(buffer);
828
+
829
+ t.equal(decoded.args[0].value, false);
830
+ t.end();
831
+ });
832
+
833
+ tap.test('encode and decode: type "boolean" with true value', (t) => {
834
+ const msg = new nodeOsc.Message('/test', { type: 'boolean', value: true });
835
+ const buffer = nodeOsc.encode(msg);
836
+ const decoded = nodeOsc.decode(buffer);
837
+
838
+ t.equal(decoded.args[0].value, true);
839
+ t.end();
840
+ });
841
+
842
+ tap.test('encode and decode: type "boolean" with false value', (t) => {
843
+ const msg = new nodeOsc.Message('/test', { type: 'boolean', value: false });
844
+ const buffer = nodeOsc.encode(msg);
845
+ const decoded = nodeOsc.decode(buffer);
846
+
847
+ t.equal(decoded.args[0].value, false);
848
+ t.end();
849
+ });