@neurodevs/node-lsl 8.0.1 → 8.0.2

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 (51) hide show
  1. package/.vscode/settings.json +17 -20
  2. package/build/.spruce/errors/errors.types.js +0 -2
  3. package/build/.spruce/errors/errors.types.js.map +1 -1
  4. package/build/__tests__/behavioral/LslOutlet.test.js +9 -14
  5. package/build/__tests__/behavioral/LslOutlet.test.js.map +1 -1
  6. package/build/__tests__/behavioral/TimeMarkerOutlet.test.js.map +1 -1
  7. package/build/__tests__/implementation/Liblsl.test.js +17 -4
  8. package/build/__tests__/implementation/Liblsl.test.js.map +1 -1
  9. package/build/__tests__/support/consts.js.map +1 -1
  10. package/build/__tests__/support/generateRandomOutletOptions.js.map +1 -1
  11. package/build/assertions.js.map +1 -1
  12. package/build/consts.js.map +1 -1
  13. package/build/errors/SpruceError.d.ts +1 -1
  14. package/build/errors/SpruceError.js.map +1 -1
  15. package/build/errors/failedToLoadLiblsl.builder.js.map +1 -1
  16. package/build/implementations/Liblsl.js +22 -6
  17. package/build/implementations/Liblsl.js.map +1 -1
  18. package/build/implementations/LslOutlet.js +6 -14
  19. package/build/implementations/LslOutlet.js.map +1 -1
  20. package/build/implementations/TimeMarkerOutlet.js +4 -1
  21. package/build/implementations/TimeMarkerOutlet.js.map +1 -1
  22. package/build/nodeLsl.types.d.ts +2 -2
  23. package/build/testDoubles/FakeLiblsl.js.map +1 -1
  24. package/build/testDoubles/MockTimeMarkerOutlet.js.map +1 -1
  25. package/build/testDoubles/SpyExtendsTimeMarkerOutlet.js.map +1 -1
  26. package/build/testDoubles/SpyImplementsTimeMarkerOutlet.js.map +1 -1
  27. package/build/validations.js.map +1 -1
  28. package/eslint.config.mjs +3 -0
  29. package/package.json +26 -26
  30. package/src/.spruce/errors/errors.types.ts +0 -3
  31. package/src/__tests__/behavioral/LslOutlet.test.ts +272 -267
  32. package/src/__tests__/behavioral/TimeMarkerOutlet.test.ts +144 -141
  33. package/src/__tests__/implementation/Liblsl.test.ts +461 -438
  34. package/src/__tests__/support/consts.ts +22 -22
  35. package/src/__tests__/support/generateRandomOutletOptions.ts +13 -13
  36. package/src/assertions.ts +39 -39
  37. package/src/consts.ts +16 -16
  38. package/src/errors/SpruceError.ts +16 -16
  39. package/src/errors/failedToLoadLiblsl.builder.ts +8 -8
  40. package/src/implementations/Liblsl.ts +205 -181
  41. package/src/implementations/LslOutlet.ts +114 -114
  42. package/src/implementations/TimeMarkerOutlet.ts +50 -50
  43. package/src/nodeLsl.types.ts +60 -63
  44. package/src/testDoubles/FakeLiblsl.ts +58 -56
  45. package/src/testDoubles/MockTimeMarkerOutlet.ts +56 -56
  46. package/src/testDoubles/SpyExtendsTimeMarkerOutlet.ts +11 -11
  47. package/src/testDoubles/SpyImplementsTimeMarkerOutlet.ts +28 -28
  48. package/src/validations.ts +6 -6
  49. package/tsconfig.json +3 -4
  50. package/.eslintignore +0 -3
  51. package/.eslintrc.js +0 -3
@@ -1,449 +1,472 @@
1
1
  import { randomInt } from 'crypto'
2
2
  import AbstractSpruceTest, {
3
- test,
4
- assert,
5
- errorAssert,
6
- generateId,
3
+ test,
4
+ assert,
5
+ errorAssert,
6
+ generateId,
7
7
  } from '@sprucelabs/test-utils'
8
8
  import { DataType, OpenParams } from 'ffi-rs'
9
9
  import LiblslImpl from '../../implementations/Liblsl'
10
10
  import {
11
- BoundChild,
12
- BoundDescription,
13
- BoundOutlet,
14
- BoundStreamInfo,
15
- FfiRsDefineOptions,
16
- Liblsl,
17
- LiblslBindings,
18
- LslChannel,
11
+ BoundChild,
12
+ BoundDescription,
13
+ BoundOutlet,
14
+ BoundStreamInfo,
15
+ FfiRsDefineOptions,
16
+ Liblsl,
17
+ LiblslBindings,
18
+ LslChannel,
19
19
  } from '../../nodeLsl.types'
20
20
  import FakeLiblsl from '../../testDoubles/FakeLiblsl'
21
21
 
22
22
  export default class LiblslTest extends AbstractSpruceTest {
23
- private static lsl: Liblsl
24
- private static libraryPath?: string
25
- private static libraryOptions?: Record<string, any>
26
- private static fakeBindings: LiblslBindings
27
- private static fakeStreamInfo: BoundStreamInfo
28
- private static fakeOutlet: BoundOutlet
29
- private static fakeDesc: BoundDescription
30
- private static fakeChildNamedChannels: BoundChild
31
- private static createStreamInfoParams?: any[]
32
- private static appendChildParams: any[] = []
33
- private static createOutletParams?: any[]
34
- private static destroyOutletParams?: any[]
35
- private static localClockParams?: any[]
36
- private static pushSampleFloatTimestampParams?: any[]
37
- private static pushSampleStringTimestampParams?: any[]
38
- private static appendChildValueParams: any[]
39
- private static shouldThrowWhenCreatingBindings: boolean
40
- private static getDescriptionParams?: [BoundStreamInfo]
41
- private static fakeChildNamedChannel: BoundChild
42
- private static appendChildHitCount: number
43
- private static ffiRsOpenOptions?: OpenParams
44
- private static ffiRsDefineOptions: FfiRsDefineOptions
45
-
46
- protected static async beforeEach() {
47
- await super.beforeEach()
48
-
49
- delete this.libraryPath
50
- delete this.libraryOptions
51
- delete this.createStreamInfoParams
52
- delete this.createOutletParams
53
- delete this.destroyOutletParams
54
- delete this.pushSampleFloatTimestampParams
55
- delete this.getDescriptionParams
56
- this.appendChildParams = []
57
- this.appendChildValueParams = []
58
-
59
- process.env.LIBLSL_PATH = generateId()
60
-
61
- this.fakeStreamInfo = {}
62
- this.fakeDesc = {}
63
- this.fakeOutlet = {}
64
- this.fakeChildNamedChannels = {}
65
- this.fakeChildNamedChannel = {}
66
- this.fakeBindings = this.FakeBindings()
67
-
68
- this.shouldThrowWhenCreatingBindings = false
69
-
70
- this.appendChildHitCount = 0
71
-
72
- delete this.ffiRsOpenOptions
73
- LiblslImpl.ffiRsOpen = (options) => {
74
- this.ffiRsOpenOptions = options
75
- }
76
-
77
- LiblslImpl.ffiRsDefine = (options) => {
78
- this.ffiRsDefineOptions = options
79
- if (this.shouldThrowWhenCreatingBindings) {
80
- throw new Error('Failed to create bindings!')
81
- }
82
- return this.fakeBindings as any
83
- }
84
-
85
- LiblslImpl.resetInstance()
86
- this.lsl = LiblslImpl.getInstance()
87
- }
88
-
89
- @test()
90
- protected static async throwsWithMissingEnv() {
91
- delete process.env.LIBLSL_PATH
92
- LiblslImpl.resetInstance()
93
-
94
- const err = assert.doesThrow(() => LiblslImpl.getInstance())
95
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
96
- parameters: ['env.LIBLSL_PATH'],
97
- })
98
- }
99
-
100
- @test()
101
- protected static async throwsWhenBindingsFailToLoad() {
102
- this.shouldThrowWhenCreatingBindings = true
103
- LiblslImpl.resetInstance()
104
-
105
- const err = assert.doesThrow(() => LiblslImpl.getInstance())
106
- errorAssert.assertError(err, 'FAILED_TO_LOAD_LIBLSL', {
107
- liblslPath: process.env.LIBLSL_PATH,
108
- })
109
- }
110
-
111
- @test()
112
- protected static async callsOpenOnFfiRs() {
113
- assert.isEqualDeep(this.ffiRsOpenOptions, {
114
- library: 'lsl',
115
- path: process.env.LIBLSL_PATH,
116
- })
117
- }
118
-
119
- @test()
120
- protected static throwsWhenCreateStreamInfoIsMissingRequiredParams() {
121
- //@ts-ignore
122
- const err = assert.doesThrow(() => this.lsl.createStreamInfo())
123
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
124
- parameters: [
125
- 'name',
126
- 'type',
127
- 'channelCount',
128
- 'sampleRate',
129
- 'channelFormat',
130
- 'sourceId',
131
- ],
132
- })
133
- }
134
-
135
- @test()
136
- protected static async throwsWhenAppendChannelsIsMissingRequiredParams() {
137
- //@ts-ignore
138
- const err = assert.doesThrow(() => this.lsl.appendChannelsToStreamInfo({}))
139
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
140
- parameters: ['info', 'channels'],
141
- })
142
- }
143
-
144
- @test()
145
- protected static async throwsWhenCreateOutletIsMissingRequiredParams() {
146
- //@ts-ignore
147
- const err = assert.doesThrow(() => this.lsl.createOutlet({}))
148
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
149
- parameters: ['info', 'chunkSize', 'maxBuffered'],
150
- })
151
- }
152
-
153
- @test()
154
- protected static async throwsWhenPushSampleFloatTimestampIsMissingRequiredParams() {
155
- //@ts-ignore
156
- const err = assert.doesThrow(() => this.lsl.pushSampleFloatTimestamp())
157
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
158
- parameters: ['outlet', 'sample', 'timestamp'],
159
- })
160
- }
161
-
162
- @test()
163
- protected static async throwsWhenPushSampleStringTimestampIsMissingRequiredParams() {
164
- //@ts-ignore
165
- const err = assert.doesThrow(() => this.lsl.pushSampleFloatTimestamp())
166
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
167
- parameters: ['outlet', 'sample', 'timestamp'],
168
- })
169
- }
170
-
171
- @test()
172
- protected static async worksAsASingleton() {
173
- const liblsl = LiblslImpl.getInstance()
174
- //@ts-ignore
175
- assert.isInstanceOf(liblsl, LiblslImpl)
176
- }
177
-
178
- @test()
179
- protected static async singletonIsTheSame() {
180
- assert.isEqual(LiblslImpl.getInstance(), LiblslImpl.getInstance())
181
- }
182
-
183
- @test()
184
- protected static canSetInstance() {
185
- const fake = new FakeLiblsl()
186
- LiblslImpl.setInstance(fake)
187
- assert.isEqual(LiblslImpl.getInstance(), fake)
188
- }
189
-
190
- @test()
191
- protected static async createsExpectedBindingsWithFfiRs() {
192
- assert.isEqualDeep(this.ffiRsDefineOptions, {
193
- lsl_create_streaminfo: {
194
- library: 'lsl',
195
- retType: DataType.External,
196
- paramsType: [
197
- DataType.String,
198
- DataType.String,
199
- DataType.I32,
200
- DataType.Double,
201
- DataType.I32,
202
- DataType.String,
203
- ],
204
- },
205
- lsl_create_outlet: {
206
- library: 'lsl',
207
- retType: DataType.External,
208
- paramsType: [DataType.External, DataType.I32, DataType.I32],
209
- },
210
- lsl_destroy_outlet: {
211
- library: 'lsl',
212
- retType: DataType.Void,
213
- paramsType: [DataType.External],
214
- },
215
- lsl_local_clock: {
216
- library: 'lsl',
217
- retType: DataType.Double,
218
- paramsType: [],
219
- },
220
- lsl_push_sample_ft: {
221
- library: 'lsl',
222
- retType: DataType.Void,
223
- paramsType: [DataType.External, DataType.DoubleArray, DataType.Double],
224
- },
225
- lsl_push_sample_strt: {
226
- library: 'lsl',
227
- retType: DataType.Void,
228
- paramsType: [DataType.External, DataType.StringArray, DataType.Double],
229
- },
230
- lsl_get_desc: {
231
- library: 'lsl',
232
- retType: DataType.External,
233
- paramsType: [DataType.External],
234
- },
235
- lsl_append_child: {
236
- library: 'lsl',
237
- retType: DataType.External,
238
- paramsType: [DataType.External, DataType.String],
239
- },
240
- lsl_append_child_value: {
241
- library: 'lsl',
242
- retType: DataType.External,
243
- paramsType: [DataType.External, DataType.String, DataType.String],
244
- },
245
- })
246
- }
247
-
248
- @test()
249
- protected static async canCreateStreamInfoWithRequiredParams() {
250
- const options = this.generateRandomCreateStreamInfoOptions()
251
- const actual = this.lsl.createStreamInfo(options)
252
-
253
- assert.isEqual(actual, this.fakeStreamInfo)
254
- assert.isEqualDeep(this.createStreamInfoParams, Object.values(options))
255
- }
256
-
257
- @test()
258
- protected static async canCreateOutletWithRequiredParams() {
259
- const { options, outlet } = this.createRandomOutlet()
260
- assert.isEqualDeep(this.createOutletParams, Object.values(options))
261
- assert.isEqual(outlet, this.fakeOutlet)
262
- }
263
-
264
- @test()
265
- protected static async canPushFloatSample() {
266
- const expected = [1.0, 2.0, 3.0]
267
- const timestamp = randomInt(100)
268
- const options = {
269
- outlet: this.fakeOutlet,
270
- sample: expected,
271
- timestamp,
272
- }
273
- this.lsl.pushSampleFloatTimestamp(options)
274
-
275
- assert.isEqualDeep(this.pushSampleFloatTimestampParams, [
276
- this.fakeOutlet,
277
- expected,
278
- timestamp,
279
- ])
280
- }
281
-
282
- @test()
283
- protected static async canPushStringSample() {
284
- const expected = [generateId()]
285
- const timestamp = randomInt(100)
286
- const options = {
287
- outlet: this.fakeOutlet,
288
- sample: expected,
289
- timestamp,
290
- }
291
- this.lsl.pushSampleStringTimestamp(options)
292
- assert.isEqual(this.pushSampleStringTimestampParams?.[0], this.fakeOutlet)
293
- assert.isEqualDeep(this.pushSampleStringTimestampParams?.[1], expected)
294
- assert.isEqual(this.pushSampleStringTimestampParams?.[2], timestamp)
295
- }
296
-
297
- @test()
298
- protected static async addingSingleChannelGetsDescription() {
299
- const info = this.createRandomStreamInfo()
300
- const channel: LslChannel = this.generateRandomChannelValues()
301
-
302
- this.lsl.appendChannelsToStreamInfo({
303
- info,
304
- channels: [channel],
305
- })
306
- assert.isEqualDeep(this.getDescriptionParams?.[0], [info])
307
-
308
- assert.isEqual(this.appendChildParams?.[0][0], this.fakeDesc)
309
- assert.isEqual(this.appendChildParams?.[0][1], 'channels')
310
-
311
- assert.isEqual(this.appendChildParams?.[1][0], this.fakeChildNamedChannels)
312
- assert.isEqual(this.appendChildParams?.[1][1], 'channel')
313
-
314
- assert.isLength(this.appendChildValueParams, 3)
315
-
316
- for (let i = 0; i < 3; i++) {
317
- const param = this.appendChildValueParams[i]
318
- assert.isEqual(param[0], this.fakeChildNamedChannel)
319
- }
320
-
321
- assert.isEqual(this.appendChildValueParams[0][1], 'label')
322
- assert.isEqual(this.appendChildValueParams[1][1], 'unit')
323
- assert.isEqual(this.appendChildValueParams[2][1], 'type')
324
-
325
- assert.isEqual(this.appendChildValueParams[0][2], channel.label)
326
- assert.isEqual(this.appendChildValueParams[1][2], channel.unit)
327
- assert.isEqual(this.appendChildValueParams[2][2], channel.type)
328
- }
329
-
330
- @test()
331
- protected static async addingMultpleChannelsAddsChildrenToChannelsChild() {
332
- const info = this.createRandomStreamInfo()
333
- const channel1 = this.generateRandomChannelValues()
334
- const channel2 = this.generateRandomChannelValues()
335
-
336
- this.lsl.appendChannelsToStreamInfo({
337
- info,
338
- channels: [channel1, channel2],
339
- })
340
-
341
- assert.isEqual(this.appendChildParams?.[2][0], this.fakeChildNamedChannels)
342
- assert.isEqual(this.appendChildParams?.[2][1], 'channel')
343
-
344
- assert.isEqual(this.appendChildValueParams[3][2], channel2.label)
345
- assert.isEqual(this.appendChildValueParams[4][2], channel2.unit)
346
- assert.isEqual(this.appendChildValueParams[5][2], channel2.type)
347
- }
348
-
349
- @test()
350
- protected static async canDestroyOutlet() {
351
- const outlet = this.createRandomOutlet()
352
- const options = { outlet }
353
- this.lsl.destroyOutlet(options)
354
-
355
- assert.isEqualDeep(this.destroyOutletParams, Object.values(options))
356
- }
357
-
358
- @test()
359
- protected static async callingLocalClockTwiceReturnsDifferentTimestamps() {
360
- const t1 = this.lsl.localClock()
361
- await this.wait(10)
362
- const t2 = this.lsl.localClock()
363
- assert.isNotEqual(t1, t2)
364
- }
365
-
366
- @test()
367
- protected static async localClockBindingReceivesEmptyArray() {
368
- delete this.localClockParams
369
- this.lsl.localClock()
370
- assert.isEqualDeep(this.localClockParams, [])
371
- }
372
-
373
- private static createRandomStreamInfo() {
374
- return this.lsl.createStreamInfo(
375
- this.generateRandomCreateStreamInfoOptions()
376
- )
377
- }
378
-
379
- private static createRandomOutlet() {
380
- const info = this.createRandomStreamInfo()
381
- const options = {
382
- info,
383
- chunkSize: randomInt(10),
384
- maxBuffered: randomInt(10),
385
- }
386
- const outlet = this.lsl.createOutlet(options)
387
- return { options, outlet }
388
- }
389
-
390
- private static generateRandomChannelValues() {
391
- return {
392
- label: generateId(),
393
- type: generateId(),
394
- unit: generateId(),
395
- }
396
- }
397
-
398
- private static generateRandomCreateStreamInfoOptions() {
399
- return {
400
- name: generateId(),
401
- type: generateId(),
402
- channelCount: randomInt(1, 10),
403
- sampleRate: randomInt(100),
404
- channelFormat: randomInt(7),
405
- sourceId: generateId(),
406
- }
407
- }
408
-
409
- private static FakeBindings() {
410
- return {
411
- lsl_create_streaminfo: (params: any[]) => {
412
- this.createStreamInfoParams = params
413
- return this.fakeStreamInfo
414
- },
415
- lsl_create_outlet: (params: any[]) => {
416
- this.createOutletParams = params
417
- return this.fakeOutlet
418
- },
419
- lsl_destroy_outlet: (params: any[]) => {
420
- this.destroyOutletParams = params
421
- },
422
- lsl_push_sample_ft: (params: any[]) => {
423
- this.pushSampleFloatTimestampParams = params
424
- },
425
- lsl_push_sample_strt: (params: any[]) => {
426
- this.pushSampleStringTimestampParams = params
427
- },
428
- lsl_local_clock: (params: []) => {
429
- this.localClockParams = params
430
- return new Date().getTime()
431
- },
432
- lsl_get_desc: (info: BoundStreamInfo) => {
433
- this.getDescriptionParams = [info]
434
- return this.fakeDesc
435
- },
436
- lsl_append_child: (params: any) => {
437
- this.appendChildParams.push(params)
438
- if (this.appendChildHitCount === 0) {
439
- this.appendChildHitCount++
440
- return this.fakeChildNamedChannels
441
- }
442
- return this.fakeChildNamedChannel
443
- },
444
- lsl_append_child_value: (params: any[]) => {
445
- this.appendChildValueParams.push(params)
446
- },
447
- }
448
- }
23
+ private static lsl: Liblsl
24
+ private static libraryPath?: string
25
+ private static libraryOptions?: Record<string, any>
26
+ private static fakeBindings: LiblslBindings
27
+ private static fakeStreamInfo: BoundStreamInfo
28
+ private static fakeOutlet: BoundOutlet
29
+ private static fakeDesc: BoundDescription
30
+ private static fakeChildNamedChannels: BoundChild
31
+ private static createStreamInfoParams?: any[]
32
+ private static appendChildParams: any[] = []
33
+ private static createOutletParams?: any[]
34
+ private static destroyOutletParams?: any[]
35
+ private static localClockParams?: any[]
36
+ private static pushSampleFloatTimestampParams?: any[]
37
+ private static pushSampleStringTimestampParams?: any[]
38
+ private static appendChildValueParams: any[]
39
+ private static shouldThrowWhenCreatingBindings: boolean
40
+ private static getDescriptionParams?: [BoundStreamInfo]
41
+ private static fakeChildNamedChannel: BoundChild
42
+ private static appendChildHitCount: number
43
+ private static ffiRsOpenOptions?: OpenParams
44
+ private static ffiRsDefineOptions: FfiRsDefineOptions
45
+
46
+ protected static async beforeEach() {
47
+ await super.beforeEach()
48
+
49
+ delete this.libraryPath
50
+ delete this.libraryOptions
51
+ delete this.createStreamInfoParams
52
+ delete this.createOutletParams
53
+ delete this.destroyOutletParams
54
+ delete this.pushSampleFloatTimestampParams
55
+ delete this.getDescriptionParams
56
+ this.appendChildParams = []
57
+ this.appendChildValueParams = []
58
+
59
+ process.env.LIBLSL_PATH = generateId()
60
+
61
+ this.fakeStreamInfo = {}
62
+ this.fakeDesc = {}
63
+ this.fakeOutlet = {}
64
+ this.fakeChildNamedChannels = {}
65
+ this.fakeChildNamedChannel = {}
66
+ this.fakeBindings = this.FakeBindings()
67
+
68
+ this.shouldThrowWhenCreatingBindings = false
69
+
70
+ this.appendChildHitCount = 0
71
+
72
+ delete this.ffiRsOpenOptions
73
+ LiblslImpl.ffiRsOpen = (options) => {
74
+ this.ffiRsOpenOptions = options
75
+ }
76
+
77
+ LiblslImpl.ffiRsDefine = (options) => {
78
+ this.ffiRsDefineOptions = options
79
+ if (this.shouldThrowWhenCreatingBindings) {
80
+ throw new Error('Failed to create bindings!')
81
+ }
82
+ return this.fakeBindings as any
83
+ }
84
+
85
+ LiblslImpl.resetInstance()
86
+ this.lsl = LiblslImpl.getInstance()
87
+ }
88
+
89
+ @test()
90
+ protected static async throwsWithMissingEnv() {
91
+ delete process.env.LIBLSL_PATH
92
+ LiblslImpl.resetInstance()
93
+
94
+ const err = assert.doesThrow(() => LiblslImpl.getInstance())
95
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
96
+ parameters: ['env.LIBLSL_PATH'],
97
+ })
98
+ }
99
+
100
+ @test()
101
+ protected static async throwsWhenBindingsFailToLoad() {
102
+ this.shouldThrowWhenCreatingBindings = true
103
+ LiblslImpl.resetInstance()
104
+
105
+ const err = assert.doesThrow(() => LiblslImpl.getInstance())
106
+ errorAssert.assertError(err, 'FAILED_TO_LOAD_LIBLSL', {
107
+ liblslPath: process.env.LIBLSL_PATH,
108
+ })
109
+ }
110
+
111
+ @test()
112
+ protected static async callsOpenOnFfiRs() {
113
+ assert.isEqualDeep(this.ffiRsOpenOptions, {
114
+ library: 'lsl',
115
+ path: process.env.LIBLSL_PATH,
116
+ })
117
+ }
118
+
119
+ @test()
120
+ protected static throwsWhenCreateStreamInfoIsMissingRequiredParams() {
121
+ //@ts-ignore
122
+ const err = assert.doesThrow(() => this.lsl.createStreamInfo())
123
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
124
+ parameters: [
125
+ 'name',
126
+ 'type',
127
+ 'channelCount',
128
+ 'sampleRate',
129
+ 'channelFormat',
130
+ 'sourceId',
131
+ ],
132
+ })
133
+ }
134
+
135
+ @test()
136
+ protected static async throwsWhenAppendChannelsIsMissingRequiredParams() {
137
+ const err = assert.doesThrow(() =>
138
+ //@ts-ignore
139
+ this.lsl.appendChannelsToStreamInfo({})
140
+ )
141
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
142
+ parameters: ['info', 'channels'],
143
+ })
144
+ }
145
+
146
+ @test()
147
+ protected static async throwsWhenCreateOutletIsMissingRequiredParams() {
148
+ //@ts-ignore
149
+ const err = assert.doesThrow(() => this.lsl.createOutlet({}))
150
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
151
+ parameters: ['info', 'chunkSize', 'maxBuffered'],
152
+ })
153
+ }
154
+
155
+ @test()
156
+ protected static async throwsWhenPushSampleFloatTimestampIsMissingRequiredParams() {
157
+ //@ts-ignore
158
+ const err = assert.doesThrow(() => this.lsl.pushSampleFloatTimestamp())
159
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
160
+ parameters: ['outlet', 'sample', 'timestamp'],
161
+ })
162
+ }
163
+
164
+ @test()
165
+ protected static async throwsWhenPushSampleStringTimestampIsMissingRequiredParams() {
166
+ //@ts-ignore
167
+ const err = assert.doesThrow(() => this.lsl.pushSampleFloatTimestamp())
168
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
169
+ parameters: ['outlet', 'sample', 'timestamp'],
170
+ })
171
+ }
172
+
173
+ @test()
174
+ protected static async worksAsASingleton() {
175
+ const liblsl = LiblslImpl.getInstance()
176
+ //@ts-ignore
177
+ assert.isInstanceOf(liblsl, LiblslImpl)
178
+ }
179
+
180
+ @test()
181
+ protected static async singletonIsTheSame() {
182
+ assert.isEqual(LiblslImpl.getInstance(), LiblslImpl.getInstance())
183
+ }
184
+
185
+ @test()
186
+ protected static canSetInstance() {
187
+ const fake = new FakeLiblsl()
188
+ LiblslImpl.setInstance(fake)
189
+ assert.isEqual(LiblslImpl.getInstance(), fake)
190
+ }
191
+
192
+ @test()
193
+ protected static async createsExpectedBindingsWithFfiRs() {
194
+ assert.isEqualDeep(this.ffiRsDefineOptions, {
195
+ lsl_create_streaminfo: {
196
+ library: 'lsl',
197
+ retType: DataType.External,
198
+ paramsType: [
199
+ DataType.String,
200
+ DataType.String,
201
+ DataType.I32,
202
+ DataType.Double,
203
+ DataType.I32,
204
+ DataType.String,
205
+ ],
206
+ },
207
+ lsl_create_outlet: {
208
+ library: 'lsl',
209
+ retType: DataType.External,
210
+ paramsType: [DataType.External, DataType.I32, DataType.I32],
211
+ },
212
+ lsl_destroy_outlet: {
213
+ library: 'lsl',
214
+ retType: DataType.Void,
215
+ paramsType: [DataType.External],
216
+ },
217
+ lsl_local_clock: {
218
+ library: 'lsl',
219
+ retType: DataType.Double,
220
+ paramsType: [],
221
+ },
222
+ lsl_push_sample_ft: {
223
+ library: 'lsl',
224
+ retType: DataType.Void,
225
+ paramsType: [
226
+ DataType.External,
227
+ DataType.FloatArray,
228
+ DataType.Double,
229
+ ],
230
+ },
231
+ lsl_push_sample_strt: {
232
+ library: 'lsl',
233
+ retType: DataType.Void,
234
+ paramsType: [
235
+ DataType.External,
236
+ DataType.StringArray,
237
+ DataType.Double,
238
+ ],
239
+ },
240
+ lsl_get_desc: {
241
+ library: 'lsl',
242
+ retType: DataType.External,
243
+ paramsType: [DataType.External],
244
+ },
245
+ lsl_append_child: {
246
+ library: 'lsl',
247
+ retType: DataType.External,
248
+ paramsType: [DataType.External, DataType.String],
249
+ },
250
+ lsl_append_child_value: {
251
+ library: 'lsl',
252
+ retType: DataType.External,
253
+ paramsType: [
254
+ DataType.External,
255
+ DataType.String,
256
+ DataType.String,
257
+ ],
258
+ },
259
+ })
260
+ }
261
+
262
+ @test()
263
+ protected static async canCreateStreamInfoWithRequiredParams() {
264
+ const options = this.generateRandomCreateStreamInfoOptions()
265
+ const actual = this.lsl.createStreamInfo(options)
266
+
267
+ assert.isEqual(actual, this.fakeStreamInfo)
268
+ assert.isEqualDeep(this.createStreamInfoParams, Object.values(options))
269
+ }
270
+
271
+ @test()
272
+ protected static async canCreateOutletWithRequiredParams() {
273
+ const { options, outlet } = this.createRandomOutlet()
274
+ assert.isEqualDeep(this.createOutletParams, Object.values(options))
275
+ assert.isEqual(outlet, this.fakeOutlet)
276
+ }
277
+
278
+ @test()
279
+ protected static async canPushFloatSample() {
280
+ const expected = [1.0, 2.0, 3.0]
281
+ const timestamp = randomInt(100)
282
+ const options = {
283
+ outlet: this.fakeOutlet,
284
+ sample: expected,
285
+ timestamp,
286
+ }
287
+ this.lsl.pushSampleFloatTimestamp(options)
288
+
289
+ assert.isEqualDeep(this.pushSampleFloatTimestampParams, [
290
+ this.fakeOutlet,
291
+ expected,
292
+ timestamp,
293
+ ])
294
+ }
295
+
296
+ @test()
297
+ protected static async canPushStringSample() {
298
+ const expected = [generateId()]
299
+ const timestamp = randomInt(100)
300
+ const options = {
301
+ outlet: this.fakeOutlet,
302
+ sample: expected,
303
+ timestamp,
304
+ }
305
+ this.lsl.pushSampleStringTimestamp(options)
306
+ assert.isEqual(
307
+ this.pushSampleStringTimestampParams?.[0],
308
+ this.fakeOutlet
309
+ )
310
+ assert.isEqualDeep(this.pushSampleStringTimestampParams?.[1], expected)
311
+ assert.isEqual(this.pushSampleStringTimestampParams?.[2], timestamp)
312
+ }
313
+
314
+ @test()
315
+ protected static async addingSingleChannelGetsDescription() {
316
+ const info = this.createRandomStreamInfo()
317
+ const channel: LslChannel = this.generateRandomChannelValues()
318
+
319
+ this.lsl.appendChannelsToStreamInfo({
320
+ info,
321
+ channels: [channel],
322
+ })
323
+ assert.isEqualDeep(this.getDescriptionParams?.[0], [info])
324
+
325
+ assert.isEqual(this.appendChildParams?.[0][0], this.fakeDesc)
326
+ assert.isEqual(this.appendChildParams?.[0][1], 'channels')
327
+
328
+ assert.isEqual(
329
+ this.appendChildParams?.[1][0],
330
+ this.fakeChildNamedChannels
331
+ )
332
+ assert.isEqual(this.appendChildParams?.[1][1], 'channel')
333
+
334
+ assert.isLength(this.appendChildValueParams, 3)
335
+
336
+ for (let i = 0; i < 3; i++) {
337
+ const param = this.appendChildValueParams[i]
338
+ assert.isEqual(param[0], this.fakeChildNamedChannel)
339
+ }
340
+
341
+ assert.isEqual(this.appendChildValueParams[0][1], 'label')
342
+ assert.isEqual(this.appendChildValueParams[1][1], 'unit')
343
+ assert.isEqual(this.appendChildValueParams[2][1], 'type')
344
+
345
+ assert.isEqual(this.appendChildValueParams[0][2], channel.label)
346
+ assert.isEqual(this.appendChildValueParams[1][2], channel.unit)
347
+ assert.isEqual(this.appendChildValueParams[2][2], channel.type)
348
+ }
349
+
350
+ @test()
351
+ protected static async addingMultpleChannelsAddsChildrenToChannelsChild() {
352
+ const info = this.createRandomStreamInfo()
353
+ const channel1 = this.generateRandomChannelValues()
354
+ const channel2 = this.generateRandomChannelValues()
355
+
356
+ this.lsl.appendChannelsToStreamInfo({
357
+ info,
358
+ channels: [channel1, channel2],
359
+ })
360
+
361
+ assert.isEqual(
362
+ this.appendChildParams?.[2][0],
363
+ this.fakeChildNamedChannels
364
+ )
365
+ assert.isEqual(this.appendChildParams?.[2][1], 'channel')
366
+
367
+ assert.isEqual(this.appendChildValueParams[3][2], channel2.label)
368
+ assert.isEqual(this.appendChildValueParams[4][2], channel2.unit)
369
+ assert.isEqual(this.appendChildValueParams[5][2], channel2.type)
370
+ }
371
+
372
+ @test()
373
+ protected static async canDestroyOutlet() {
374
+ const outlet = this.createRandomOutlet()
375
+ const options = { outlet }
376
+ this.lsl.destroyOutlet(options)
377
+
378
+ assert.isEqualDeep(this.destroyOutletParams, Object.values(options))
379
+ }
380
+
381
+ @test()
382
+ protected static async callingLocalClockTwiceReturnsDifferentTimestamps() {
383
+ const t1 = this.lsl.localClock()
384
+ await this.wait(10)
385
+ const t2 = this.lsl.localClock()
386
+ assert.isNotEqual(t1, t2)
387
+ }
388
+
389
+ @test()
390
+ protected static async localClockBindingReceivesEmptyArray() {
391
+ delete this.localClockParams
392
+ this.lsl.localClock()
393
+ assert.isEqualDeep(this.localClockParams, [])
394
+ }
395
+
396
+ private static createRandomStreamInfo() {
397
+ return this.lsl.createStreamInfo(
398
+ this.generateRandomCreateStreamInfoOptions()
399
+ )
400
+ }
401
+
402
+ private static createRandomOutlet() {
403
+ const info = this.createRandomStreamInfo()
404
+ const options = {
405
+ info,
406
+ chunkSize: randomInt(10),
407
+ maxBuffered: randomInt(10),
408
+ }
409
+ const outlet = this.lsl.createOutlet(options)
410
+ return { options, outlet }
411
+ }
412
+
413
+ private static generateRandomChannelValues() {
414
+ return {
415
+ label: generateId(),
416
+ type: generateId(),
417
+ unit: generateId(),
418
+ }
419
+ }
420
+
421
+ private static generateRandomCreateStreamInfoOptions() {
422
+ return {
423
+ name: generateId(),
424
+ type: generateId(),
425
+ channelCount: randomInt(1, 10),
426
+ sampleRate: randomInt(100),
427
+ channelFormat: randomInt(7),
428
+ sourceId: generateId(),
429
+ }
430
+ }
431
+
432
+ private static FakeBindings() {
433
+ return {
434
+ lsl_create_streaminfo: (params: any[]) => {
435
+ this.createStreamInfoParams = params
436
+ return this.fakeStreamInfo
437
+ },
438
+ lsl_create_outlet: (params: any[]) => {
439
+ this.createOutletParams = params
440
+ return this.fakeOutlet
441
+ },
442
+ lsl_destroy_outlet: (params: any[]) => {
443
+ this.destroyOutletParams = params
444
+ },
445
+ lsl_push_sample_ft: (params: any[]) => {
446
+ this.pushSampleFloatTimestampParams = params
447
+ },
448
+ lsl_push_sample_strt: (params: any[]) => {
449
+ this.pushSampleStringTimestampParams = params
450
+ },
451
+ lsl_local_clock: (params: []) => {
452
+ this.localClockParams = params
453
+ return new Date().getTime()
454
+ },
455
+ lsl_get_desc: (info: BoundStreamInfo) => {
456
+ this.getDescriptionParams = [info]
457
+ return this.fakeDesc
458
+ },
459
+ lsl_append_child: (params: any) => {
460
+ this.appendChildParams.push(params)
461
+ if (this.appendChildHitCount === 0) {
462
+ this.appendChildHitCount++
463
+ return this.fakeChildNamedChannels
464
+ }
465
+ return this.fakeChildNamedChannel
466
+ },
467
+ lsl_append_child_value: (params: any[]) => {
468
+ this.appendChildValueParams.push(params)
469
+ },
470
+ }
471
+ }
449
472
  }