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