@neurodevs/node-lsl 12.0.6 → 13.0.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 (122) hide show
  1. package/README.md +2 -5
  2. package/build/__tests__/AbstractPackageTest.d.ts +9 -3
  3. package/build/__tests__/AbstractPackageTest.js +37 -3
  4. package/build/__tests__/AbstractPackageTest.js.map +1 -1
  5. package/build/__tests__/impl/LiblslAdapter.test.d.ts +6 -0
  6. package/build/__tests__/impl/LiblslAdapter.test.js +133 -12
  7. package/build/__tests__/impl/LiblslAdapter.test.js.map +1 -1
  8. package/build/__tests__/impl/LslEventMarkerOutlet.test.js +5 -5
  9. package/build/__tests__/impl/LslEventMarkerOutlet.test.js.map +1 -1
  10. package/build/__tests__/impl/LslStreamInfo.test.js +3 -3
  11. package/build/__tests__/impl/LslStreamInfo.test.js.map +1 -1
  12. package/build/__tests__/impl/LslStreamInlet.test.d.ts +30 -2
  13. package/build/__tests__/impl/LslStreamInlet.test.js +181 -18
  14. package/build/__tests__/impl/LslStreamInlet.test.js.map +1 -1
  15. package/build/__tests__/impl/LslStreamOutlet.test.d.ts +16 -7
  16. package/build/__tests__/impl/LslStreamOutlet.test.js +84 -23
  17. package/build/__tests__/impl/LslStreamOutlet.test.js.map +1 -1
  18. package/build/__tests__/impl/LslWebSocketBridge.test.d.ts +15 -0
  19. package/build/__tests__/impl/LslWebSocketBridge.test.js +112 -2
  20. package/build/__tests__/impl/LslWebSocketBridge.test.js.map +1 -1
  21. package/build/assertions.d.ts +3 -3
  22. package/build/assertions.js +6 -6
  23. package/build/assertions.js.map +1 -1
  24. package/build/handleError.d.ts +8 -0
  25. package/build/handleError.js +25 -0
  26. package/build/handleError.js.map +1 -0
  27. package/build/impl/LiblslAdapter.d.ts +128 -13
  28. package/build/impl/LiblslAdapter.js +149 -71
  29. package/build/impl/LiblslAdapter.js.map +1 -1
  30. package/build/impl/LslEventMarkerOutlet.d.ts +4 -1
  31. package/build/impl/LslEventMarkerOutlet.js +3 -3
  32. package/build/impl/LslEventMarkerOutlet.js.map +1 -1
  33. package/build/impl/LslStreamInfo.d.ts +3 -3
  34. package/build/impl/LslStreamInfo.js +5 -5
  35. package/build/impl/LslStreamInfo.js.map +1 -1
  36. package/build/impl/LslStreamInlet.d.ts +52 -14
  37. package/build/impl/LslStreamInlet.js +157 -20
  38. package/build/impl/LslStreamInlet.js.map +1 -1
  39. package/build/impl/LslStreamOutlet.d.ts +13 -13
  40. package/build/impl/LslStreamOutlet.js +22 -21
  41. package/build/impl/LslStreamOutlet.js.map +1 -1
  42. package/build/impl/LslWebSocketBridge.d.ts +35 -3
  43. package/build/impl/LslWebSocketBridge.js +66 -3
  44. package/build/impl/LslWebSocketBridge.js.map +1 -1
  45. package/build/index.d.ts +0 -1
  46. package/build/index.js +0 -1
  47. package/build/index.js.map +1 -1
  48. package/build/scripts/runLslStreamOutlet.js +2 -3
  49. package/build/scripts/runLslStreamOutlet.js.map +1 -1
  50. package/build/scripts/runOutletToInlet.d.ts +1 -0
  51. package/build/scripts/runOutletToInlet.js +45 -0
  52. package/build/scripts/runOutletToInlet.js.map +1 -0
  53. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.d.ts +1 -2
  54. package/build/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.js.map +1 -1
  55. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.d.ts +2 -2
  56. package/build/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.js.map +1 -1
  57. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.d.ts +1 -2
  58. package/build/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.js.map +1 -1
  59. package/build/testDoubles/Liblsl/FakeLiblsl.d.ts +15 -3
  60. package/build/testDoubles/Liblsl/FakeLiblsl.js +42 -0
  61. package/build/testDoubles/Liblsl/FakeLiblsl.js.map +1 -1
  62. package/build/testDoubles/StreamInfo/FakeStreamInfo.d.ts +1 -1
  63. package/build/testDoubles/StreamInfo/SpyStreamInfo.d.ts +1 -1
  64. package/build/testDoubles/StreamInlet/FakeStreamInlet.d.ts +12 -3
  65. package/build/testDoubles/StreamInlet/FakeStreamInlet.js +25 -5
  66. package/build/testDoubles/StreamInlet/FakeStreamInlet.js.map +1 -1
  67. package/build/testDoubles/StreamInlet/SpyStreamInlet.d.ts +3 -3
  68. package/build/testDoubles/StreamInlet/SpyStreamInlet.js +2 -2
  69. package/build/testDoubles/StreamInlet/SpyStreamInlet.js.map +1 -1
  70. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.d.ts +4 -4
  71. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js +6 -6
  72. package/build/testDoubles/StreamOutlet/FakeStreamOutlet.js.map +1 -1
  73. package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.d.ts +6 -0
  74. package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.js +15 -0
  75. package/build/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.js.map +1 -1
  76. package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.d.ts +4 -0
  77. package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js +7 -0
  78. package/build/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js.map +1 -0
  79. package/build/testDoubles/WebSockets/FakeWebSocket.d.ts +10 -0
  80. package/build/testDoubles/WebSockets/FakeWebSocket.js +14 -0
  81. package/build/testDoubles/WebSockets/FakeWebSocket.js.map +1 -0
  82. package/build/testDoubles/WebSockets/FakeWebSocketServer.d.ts +13 -0
  83. package/build/testDoubles/WebSockets/FakeWebSocketServer.js +22 -0
  84. package/build/testDoubles/WebSockets/FakeWebSocketServer.js.map +1 -0
  85. package/build/testDoubles/generateRandomOutletOptions.js +3 -3
  86. package/build/testDoubles/generateRandomOutletOptions.js.map +1 -1
  87. package/package.json +5 -3
  88. package/src/__tests__/AbstractPackageTest.ts +52 -4
  89. package/src/__tests__/impl/LiblslAdapter.test.ts +181 -16
  90. package/src/__tests__/impl/LslEventMarkerOutlet.test.ts +5 -5
  91. package/src/__tests__/impl/LslStreamInfo.test.ts +3 -3
  92. package/src/__tests__/impl/LslStreamInlet.test.ts +253 -20
  93. package/src/__tests__/impl/LslStreamOutlet.test.ts +111 -24
  94. package/src/__tests__/impl/LslWebSocketBridge.test.ts +163 -5
  95. package/src/assertions.ts +7 -7
  96. package/src/handleError.ts +24 -0
  97. package/src/impl/LiblslAdapter.ts +322 -97
  98. package/src/impl/LslEventMarkerOutlet.ts +8 -4
  99. package/src/impl/LslStreamInfo.ts +9 -9
  100. package/src/impl/LslStreamInlet.ts +233 -25
  101. package/src/impl/LslStreamOutlet.ts +43 -33
  102. package/src/impl/LslWebSocketBridge.ts +115 -5
  103. package/src/index.ts +0 -2
  104. package/src/scripts/runLslStreamOutlet.ts +2 -3
  105. package/src/scripts/runOutletToInlet.ts +53 -0
  106. package/src/testDoubles/EventMarkerOutlet/FakeEventMarkerOutlet.ts +4 -2
  107. package/src/testDoubles/EventMarkerOutlet/MockEventMarkerOutlet.ts +5 -2
  108. package/src/testDoubles/EventMarkerOutlet/ThrowingEventMarkerOutlet.ts +4 -2
  109. package/src/testDoubles/Liblsl/FakeLiblsl.ts +61 -1
  110. package/src/testDoubles/StreamInfo/FakeStreamInfo.ts +1 -1
  111. package/src/testDoubles/StreamInlet/FakeStreamInlet.ts +36 -5
  112. package/src/testDoubles/StreamInlet/SpyStreamInlet.ts +7 -2
  113. package/src/testDoubles/StreamOutlet/FakeStreamOutlet.ts +7 -7
  114. package/src/testDoubles/StreamTransportBridge/FakeStreamTransportBridge.ts +18 -0
  115. package/src/testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.ts +9 -0
  116. package/src/testDoubles/WebSockets/FakeWebSocket.ts +17 -0
  117. package/src/testDoubles/WebSockets/FakeWebSocketServer.ts +32 -0
  118. package/src/testDoubles/generateRandomOutletOptions.ts +3 -3
  119. package/build/types.d.ts +0 -95
  120. package/build/types.js +0 -2
  121. package/build/types.js.map +0 -1
  122. package/src/types.ts +0 -111
@@ -1,7 +1,5 @@
1
1
  import { test, assert } from '@neurodevs/node-tdd'
2
- import LslStreamInlet, {
3
- StreamInletOptions,
4
- } from '../../impl/LslStreamInlet.js'
2
+ import { StreamInletOptions } from '../../impl/LslStreamInlet.js'
5
3
  import FakeStreamInfo from '../../testDoubles/StreamInfo/FakeStreamInfo.js'
6
4
  import { SpyStreamInlet } from '../../testDoubles/StreamInlet/SpyStreamInlet.js'
7
5
  import AbstractPackageTest from '../AbstractPackageTest.js'
@@ -9,12 +7,19 @@ import AbstractPackageTest from '../AbstractPackageTest.js'
9
7
  export default class LslStreamInletTest extends AbstractPackageTest {
10
8
  private static instance: SpyStreamInlet
11
9
 
10
+ private static callsToOnData: {
11
+ samples: Float32Array
12
+ timestamps: Float64Array
13
+ }[]
14
+
12
15
  protected static async beforeEach() {
13
16
  await super.beforeEach()
14
17
 
15
18
  this.setSpyStreamInlet()
16
19
  this.setFakeStreamInfo()
17
20
 
21
+ this.callsToOnData = []
22
+
18
23
  this.instance = this.LslStreamInlet()
19
24
  }
20
25
 
@@ -30,7 +35,7 @@ export default class LslStreamInletTest extends AbstractPackageTest {
30
35
  {
31
36
  channelNames: this.channelNames,
32
37
  channelFormat: 'float32',
33
- sampleRate: 0,
38
+ sampleRateHz: 0,
34
39
  name: this.name_,
35
40
  type: this.type,
36
41
  sourceId: this.sourceId,
@@ -61,35 +66,263 @@ export default class LslStreamInletTest extends AbstractPackageTest {
61
66
  {
62
67
  info: fakeInfo.boundStreamInfo,
63
68
  chunkSize: this.chunkSize,
64
- maxBuffered: this.maxBuffered,
69
+ maxBufferedMs: this.maxBufferedMs / 1000,
65
70
  },
66
71
  'Should have called createInlet!'
67
72
  )
68
73
  }
69
74
 
70
75
  @test()
71
- protected static async flushSamplesCallsLslBinding() {
72
- this.instance.flushSamples()
76
+ protected static async flushQueueCallsLslBinding() {
77
+ this.instance.flushQueue()
73
78
 
74
79
  assert.isEqualDeep(
75
80
  this.fakeLiblsl.lastFlushInletOptions,
76
- { inlet: this.instance.getBoundInlet() },
81
+ { inlet: this.boundInlet },
77
82
  'Should have called flushInlet!'
78
83
  )
79
84
  }
80
85
 
81
- private static LslStreamInlet(options?: Partial<StreamInletOptions>) {
82
- const defaultOptions = {
83
- channelNames: this.channelNames,
84
- channelFormat: 'float32',
85
- sampleRate: 0,
86
- name: this.name_,
87
- type: this.type,
88
- sourceId: this.sourceId,
86
+ @test()
87
+ protected static async destroyCallsLslBinding() {
88
+ this.instance.destroy()
89
+
90
+ assert.isEqualDeep(
91
+ this.fakeLiblsl.lastDestroyInletOptions,
92
+ { inlet: this.boundInlet },
93
+ 'Should have called destroyInlet!'
94
+ )
95
+ }
96
+
97
+ @test()
98
+ protected static async exposesIsRunningFieldThatIsFalseAtFirst() {
99
+ assert.isFalse(this.isRunning, 'isRunning should be false at first!')
100
+ }
101
+
102
+ @test()
103
+ protected static async startPullingSetsIsRunningToTrue() {
104
+ this.startPulling()
105
+ await this.wait(10)
106
+
107
+ assert.isTrue(this.isRunning, 'isRunning should be true!')
108
+
109
+ this.stopPulling()
110
+ }
111
+
112
+ @test()
113
+ protected static async stopPullingSetsIsRunningToFalse() {
114
+ await this.startThenStop()
115
+
116
+ assert.isFalse(this.isRunning, 'isRunning should be false!')
117
+ }
118
+
119
+ @test()
120
+ protected static async callsOnDataForChunkSizeNotOne() {
121
+ await this.startThenStop()
122
+
123
+ assert.isEqual(
124
+ this.callsToOnData.length,
125
+ 2,
126
+ 'Did not call onData as expected!'
127
+ )
128
+ }
129
+
130
+ @test()
131
+ protected static async callsOnDataForChunkSizeOne() {
132
+ await this.runChunkSizeOne()
133
+
134
+ assert.isEqual(
135
+ this.callsToOnData.length,
136
+ 2,
137
+ 'Did not call onData as expected!'
138
+ )
139
+ }
140
+
141
+ @test()
142
+ protected static async callsPullSampleIfChunkSizeIsOne() {
143
+ const inlet = await this.runChunkSizeOne()
144
+
145
+ assert.isEqualDeep(
146
+ this.fakeLiblsl.lastPullSampleOptions,
147
+ {
148
+ inlet: this.boundInlet,
149
+ dataBufferPtr: inlet['dataBufferPtr'],
150
+ dataBufferElements: this.channelCount,
151
+ timeout: 0,
152
+ errcodePtr: inlet['errorCodeBufferPtr'],
153
+ },
154
+ 'Should have called pullSample!'
155
+ )
156
+ }
157
+
158
+ @test()
159
+ protected static async callsPullChunkIfChunkSizeIsNotOne() {
160
+ await this.startThenStop()
161
+
162
+ assert.isEqualDeep(
163
+ this.fakeLiblsl.lastPullChunkOptions,
164
+ {
165
+ inlet: this.boundInlet,
166
+ dataBufferPtr: this.instance['dataBufferPtr'],
167
+ timestampBufferPtr: this.instance['timestampBufferPtr'],
168
+ dataBufferElements: this.chunkSize * this.channelCount,
169
+ timestampBufferElements: this.chunkSize,
170
+ timeout: 0,
171
+ errcodePtr: this.instance['errorCodeBufferPtr'],
172
+ },
173
+ 'Should have called pullChunk!'
174
+ )
175
+ }
176
+
177
+ @test()
178
+ protected static async passesTimeoutMsOptionToPullSample() {
179
+ const timeoutMs = 1000 * Math.random()
180
+
181
+ await this.runInletWithOptions({
182
+ chunkSize: 1,
183
+ timeoutMs,
184
+ })
185
+
186
+ assert.isEqual(
187
+ this.fakeLiblsl.lastPullSampleOptions?.timeout,
188
+ timeoutMs / 1000,
189
+ 'Did not pass timeoutMs to pullSample!'
190
+ )
191
+ }
192
+
193
+ @test()
194
+ protected static async passesTimeoutMsOptionToPullChunk() {
195
+ const timeoutMs = 1000 * Math.random()
196
+
197
+ await this.runInletWithOptions({
89
198
  chunkSize: this.chunkSize,
90
- maxBuffered: this.maxBuffered,
91
- ...options,
92
- } as StreamInletOptions
93
- return LslStreamInlet.Create(defaultOptions) as SpyStreamInlet
199
+ timeoutMs,
200
+ })
201
+
202
+ assert.isEqual(
203
+ this.fakeLiblsl.lastPullChunkOptions?.timeout,
204
+ timeoutMs / 1000,
205
+ 'Did not pass timeoutMs to pullChunk!'
206
+ )
207
+ }
208
+
209
+ @test()
210
+ protected static async defaultsMaxBufferedMsToSixMinutesInSeconds() {
211
+ await this.runInletWithOptions({
212
+ maxBufferedMs: undefined,
213
+ })
214
+
215
+ const sixMinutesInSeconds = 360
216
+
217
+ assert.isEqualDeep(
218
+ this.fakeLiblsl.lastCreateInletOptions?.maxBufferedMs,
219
+ sixMinutesInSeconds
220
+ )
221
+ }
222
+
223
+ @test()
224
+ protected static async throwsWithUnknownErrorCode() {
225
+ await this.assertThrowsWithErrorCode(
226
+ -999,
227
+ `An unknown liblsl error has occurred!`
228
+ )
229
+ }
230
+
231
+ @test()
232
+ protected static async throwsWithErrorCodeNegativeOne() {
233
+ await this.assertThrowsWithErrorCode(
234
+ -1,
235
+ `The liblsl operation failed due to a timeout!`
236
+ )
237
+ }
238
+
239
+ @test()
240
+ protected static async throwsWithErrorCodeNegativeTwo() {
241
+ await this.assertThrowsWithErrorCode(
242
+ -2,
243
+ `The liblsl stream has been lost!`
244
+ )
245
+ }
246
+
247
+ @test()
248
+ protected static async throwsWithErrorCodeNegativeThree() {
249
+ await this.assertThrowsWithErrorCode(
250
+ -3,
251
+ 'A liblsl argument was incorrectly specified!'
252
+ )
253
+ }
254
+
255
+ @test()
256
+ protected static async throwsWithErrorCodeNegativeFour() {
257
+ await this.assertThrowsWithErrorCode(
258
+ -4,
259
+ 'An internal liblsl error has occurred!'
260
+ )
261
+ }
262
+
263
+ private static async assertThrowsWithErrorCode(
264
+ errorCode: number,
265
+ message: string
266
+ ) {
267
+ await this.startThenStop()
268
+
269
+ this.instance['errorCodeBuffer'].writeInt32LE(errorCode)
270
+
271
+ assert.doesThrow(() => {
272
+ this.instance['handleErrorCodeIfPresent']()
273
+ }, message)
274
+ }
275
+
276
+ private static async runChunkSizeOne() {
277
+ return await this.runInletWithOptions({
278
+ chunkSize: 1,
279
+ })
280
+ }
281
+
282
+ private static async runInletWithOptions(
283
+ options?: Partial<StreamInletOptions>
284
+ ) {
285
+ const inlet = this.LslStreamInlet(options)
286
+ await this.startThenStop(inlet)
287
+ return inlet
288
+ }
289
+
290
+ private static async startThenStop(inlet?: SpyStreamInlet) {
291
+ const instance = inlet || this.instance
292
+ instance.startPulling()
293
+ await this.wait(10)
294
+ instance.stopPulling()
295
+ }
296
+
297
+ private static startPulling() {
298
+ this.instance.startPulling()
299
+ }
300
+
301
+ private static stopPulling() {
302
+ this.instance.stopPulling()
303
+ }
304
+
305
+ private static get isRunning() {
306
+ return this.instance.isRunning
307
+ }
308
+
309
+ private static get boundInlet() {
310
+ return this.instance.getBoundInlet()
311
+ }
312
+
313
+ private static onData = (
314
+ samples: Float32Array,
315
+ timestamps: Float64Array
316
+ ) => {
317
+ this.callsToOnData.push({ samples, timestamps })
318
+ }
319
+
320
+ protected static LslStreamInlet(options?: Partial<StreamInletOptions>) {
321
+ return AbstractPackageTest.LslStreamInlet(
322
+ {
323
+ ...options,
324
+ },
325
+ this.onData
326
+ ) as SpyStreamInlet
94
327
  }
95
328
  }
@@ -1,5 +1,6 @@
1
1
  import { test, assert } from '@neurodevs/node-tdd'
2
2
 
3
+ import { ChannelFormat, LslSample } from 'impl/LiblslAdapter.js'
3
4
  import { CHANNEL_FORMATS } from '../../consts.js'
4
5
  import { StreamInfo } from '../../impl/LslStreamInfo.js'
5
6
  import LslStreamOutlet, {
@@ -11,8 +12,8 @@ import {
11
12
  TestChannelFormat,
12
13
  } from '../../testDoubles/consts.js'
13
14
  import generateRandomOutletOptions from '../../testDoubles/generateRandomOutletOptions.js'
15
+ import FakeLiblsl from '../../testDoubles/Liblsl/FakeLiblsl.js'
14
16
  import FakeStreamInfo from '../../testDoubles/StreamInfo/FakeStreamInfo.js'
15
- import { ChannelFormat, LslSample } from '../../types.js'
16
17
  import AbstractPackageTest from '../AbstractPackageTest.js'
17
18
 
18
19
  export default class LslStreamOutletTest extends AbstractPackageTest {
@@ -35,14 +36,14 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
35
36
  }
36
37
 
37
38
  @test()
38
- protected static async allowsZeroSampleRate() {
39
- await this.LslStreamOutlet({ sampleRate: 0 })
39
+ protected static async allowsZeroSampleRateHz() {
40
+ await this.LslStreamOutlet({ sampleRateHz: 0 })
40
41
  }
41
42
 
42
43
  @test()
43
- protected static async throwsWithInvalidSampleRate() {
44
- await this.assertThrowsInvalidSampleRate(-1)
45
- await this.assertThrowsInvalidSampleRate(-1.5)
44
+ protected static async throwsWithInvalidSampleRateHz() {
45
+ await this.assertThrowsInvalidSampleRateHz(-1)
46
+ await this.assertThrowsInvalidSampleRateHz(-1.5)
46
47
  }
47
48
 
48
49
  @test()
@@ -65,15 +66,15 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
65
66
  }
66
67
 
67
68
  @test()
68
- protected static async allowsZeroMaxBuffered() {
69
- await this.LslStreamOutlet({ maxBuffered: 0 })
69
+ protected static async allowsZeroMaxBufferedMs() {
70
+ await this.LslStreamOutlet({ maxBufferedMs: 0 })
70
71
  }
71
72
 
72
73
  @test()
73
- protected static async throwsWithInvalidMaxBuffered() {
74
- await this.assertThrowsInvalidMaxBuffered(-1)
75
- await this.assertThrowsInvalidMaxBuffered(-1.5)
76
- await this.assertThrowsInvalidMaxBuffered(1.5)
74
+ protected static async throwsWithInvalidMaxBufferedMs() {
75
+ await this.assertThrowsInvalidMaxBufferedMs(-1)
76
+ await this.assertThrowsInvalidMaxBufferedMs(-1.5)
77
+ await this.assertThrowsInvalidMaxBufferedMs(1.5)
77
78
  }
78
79
 
79
80
  @test()
@@ -115,7 +116,7 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
115
116
  assert.isEqualDeep(this.fakeLiblsl.lastCreateOutletOptions, {
116
117
  info: this.fakeLiblsl.streamInfo,
117
118
  chunkSize: this.randomOutletOptions.chunkSize,
118
- maxBuffered: this.randomOutletOptions.maxBuffered,
119
+ maxBufferedMs: this.randomOutletOptions.maxBufferedMs,
119
120
  })
120
121
  assert.isNumber(
121
122
  this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.timestamp
@@ -168,11 +169,11 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
168
169
  assert.isEqualDeep(FakeStreamInfo.callsToConstructor[0], {
169
170
  channelNames: this.randomOutletOptions.channelNames,
170
171
  channelFormat: this.randomOutletOptions.channelFormat,
171
- sampleRate: this.randomOutletOptions.sampleRate,
172
+ sampleRateHz: this.randomOutletOptions.sampleRateHz,
172
173
  name: this.randomOutletOptions.name,
173
174
  type: this.randomOutletOptions.type,
174
175
  sourceId: this.randomOutletOptions.sourceId,
175
- units: this.randomOutletOptions.unit,
176
+ units: this.randomOutletOptions.units,
176
177
  })
177
178
  }
178
179
 
@@ -208,7 +209,91 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
208
209
  await this.LslStreamOutlet({ waitAfterConstructionMs: 10 })
209
210
  const endMs = Date.now()
210
211
 
211
- assert.isAbove(endMs - startMs, 10)
212
+ assert.isAbove(endMs - startMs, 8)
213
+ }
214
+
215
+ @test()
216
+ protected static async defaultsMaxBufferedMsToSixMinutesInSeconds() {
217
+ const outlet = await this.LslStreamOutlet({ maxBufferedMs: undefined })
218
+
219
+ assert.isEqualDeep(
220
+ outlet['maxBufferedMs'],
221
+ 360 * 1000,
222
+ 'Did not set maxBufferedMs as 6 minutes!'
223
+ )
224
+ }
225
+
226
+ @test()
227
+ protected static async defaultsManufacturerToNA() {
228
+ const outlet = await this.LslStreamOutlet({ manufacturer: undefined })
229
+
230
+ assert.isEqualDeep(
231
+ outlet['manufacturer'],
232
+ 'N/A',
233
+ 'Did not set manufacturer as N/A!'
234
+ )
235
+ }
236
+
237
+ @test()
238
+ protected static async defaultsUnitsToNA() {
239
+ const outlet = await this.LslStreamOutlet({ units: undefined })
240
+ assert.isEqualDeep(outlet['units'], 'N/A', 'Did not set units as N/A!')
241
+ }
242
+
243
+ @test()
244
+ protected static async throwsWithUnknownErrorCode() {
245
+ await this.assertThrowsWithErrorCode(
246
+ -999,
247
+ `An unknown liblsl error has occurred!`
248
+ )
249
+ }
250
+
251
+ @test()
252
+ protected static async throwsWithErrorCodeNegativeOne() {
253
+ await this.assertThrowsWithErrorCode(
254
+ -1,
255
+ `The liblsl operation failed due to a timeout!`
256
+ )
257
+ }
258
+
259
+ @test()
260
+ protected static async throwsWithErrorCodeNegativeTwo() {
261
+ await this.assertThrowsWithErrorCode(
262
+ -2,
263
+ `The liblsl stream has been lost!`
264
+ )
265
+ }
266
+
267
+ @test()
268
+ protected static async throwsWithErrorCodeNegativeThree() {
269
+ await this.assertThrowsWithErrorCode(
270
+ -3,
271
+ 'A liblsl argument was incorrectly specified!'
272
+ )
273
+ }
274
+
275
+ @test()
276
+ protected static async throwsWithErrorCodeNegativeFour() {
277
+ await this.assertThrowsWithErrorCode(
278
+ -4,
279
+ 'An internal liblsl error has occurred!'
280
+ )
281
+ }
282
+
283
+ private static async assertThrowsWithErrorCode(
284
+ errorCode: number,
285
+ message: string
286
+ ) {
287
+ FakeLiblsl.fakeErrorCode = errorCode
288
+
289
+ const outlet =
290
+ Math.random() > 0.5
291
+ ? await this.FloatOutlet()
292
+ : await this.StringOutlet()
293
+
294
+ assert.doesThrow(() => {
295
+ outlet.pushSample([1.0])
296
+ }, message)
212
297
  }
213
298
 
214
299
  private static async assertThrowsWithEmptyChannelNames() {
@@ -219,11 +304,11 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
219
304
  )
220
305
  }
221
306
 
222
- private static async assertThrowsInvalidSampleRate(sampleRate: number) {
307
+ private static async assertThrowsInvalidSampleRateHz(sampleRateHz: number) {
223
308
  await this.createAndAssertThrows(
224
- 'sampleRate',
225
- sampleRate,
226
- `Invalid sample rate! Must be a positive number or zero, not: ${sampleRate}`
309
+ 'sampleRateHz',
310
+ sampleRateHz,
311
+ `Invalid sample rate! Must be a positive number or zero, not: ${sampleRateHz}`
227
312
  )
228
313
  }
229
314
 
@@ -237,11 +322,13 @@ export default class LslStreamOutletTest extends AbstractPackageTest {
237
322
  )
238
323
  }
239
324
 
240
- private static async assertThrowsInvalidMaxBuffered(maxBuffered: number) {
325
+ private static async assertThrowsInvalidMaxBufferedMs(
326
+ maxBufferedMs: number
327
+ ) {
241
328
  await this.createAndAssertThrows(
242
- 'maxBuffered',
243
- maxBuffered,
244
- `Invalid max buffered! Must be a positive integer or zero, not: ${maxBuffered}`
329
+ 'maxBufferedMs',
330
+ maxBufferedMs,
331
+ `Invalid max buffered! Must be a positive integer or zero, not: ${maxBufferedMs}`
245
332
  )
246
333
  }
247
334
 
@@ -1,17 +1,27 @@
1
+ import { randomInt } from 'crypto'
2
+
1
3
  import { test, assert } from '@neurodevs/node-tdd'
2
4
 
3
5
  import LslWebSocketBridge, {
4
- StreamTransportBridge,
6
+ StreamTransportBridgeOptions,
5
7
  } from '../../impl/LslWebSocketBridge.js'
8
+ import FakeStreamInlet from '../../testDoubles/StreamInlet/FakeStreamInlet.js'
9
+ import SpyLslWebSocketBridge from '../../testDoubles/StreamTransportBridge/SpyLslWebSocketBridge.js'
10
+ import FakeWebSocket from '../../testDoubles/WebSockets/FakeWebSocket.js'
11
+ import FakeWebSocketServer from '../../testDoubles/WebSockets/FakeWebSocketServer.js'
6
12
  import AbstractPackageTest from '../AbstractPackageTest.js'
7
13
 
8
14
  export default class LslWebSocketBridgeTest extends AbstractPackageTest {
9
- private static instance: StreamTransportBridge
15
+ private static instance: SpyLslWebSocketBridge
10
16
 
11
17
  protected static async beforeEach() {
12
18
  await super.beforeEach()
13
19
 
14
- this.instance = this.LslWebSocketBridge()
20
+ this.setFakeStreamInlet()
21
+ this.setFakeWebSocketServer()
22
+ this.setSpyLslWebSocketBridge()
23
+
24
+ this.instance = this.LslWebSocketBridge() as SpyLslWebSocketBridge
15
25
  }
16
26
 
17
27
  @test()
@@ -19,7 +29,155 @@ export default class LslWebSocketBridgeTest extends AbstractPackageTest {
19
29
  assert.isTruthy(this.instance, 'Failed to create instance!')
20
30
  }
21
31
 
22
- private static LslWebSocketBridge() {
23
- return LslWebSocketBridge.Create()
32
+ @test()
33
+ protected static async createsLslStreamInlet() {
34
+ const { channelNames, channelFormat, sampleRateHz, chunkSize } =
35
+ FakeStreamInlet.callsToConstructor[0]?.options ?? {}
36
+
37
+ assert.isEqualDeep(
38
+ {
39
+ channelNames,
40
+ channelFormat,
41
+ sampleRateHz,
42
+ chunkSize,
43
+ },
44
+ this.baseOptions
45
+ )
46
+ }
47
+
48
+ @test()
49
+ protected static async activateCallsStartPullingOnInlet() {
50
+ this.activate()
51
+
52
+ assert.isEqual(
53
+ FakeStreamInlet.numCallsToStartPulling,
54
+ 1,
55
+ 'Did not call startPulling on inlet!'
56
+ )
57
+ }
58
+
59
+ @test()
60
+ protected static async deactivateCallsStopPullingOnInlet() {
61
+ this.activate()
62
+ this.deactivate()
63
+
64
+ assert.isEqual(
65
+ FakeStreamInlet.numCallsToStopPulling,
66
+ 1,
67
+ 'Did not call stopPulling on inlet!'
68
+ )
69
+ }
70
+
71
+ @test()
72
+ protected static async destroyCallsDestroyOnInlet() {
73
+ this.destroy()
74
+
75
+ assert.isEqual(
76
+ FakeStreamInlet.numCallsToDestroy,
77
+ 1,
78
+ 'Did not call destroy on inlet!'
79
+ )
80
+ }
81
+
82
+ @test()
83
+ protected static async createsWebSocketServer() {
84
+ assert.isEqualDeep(
85
+ FakeWebSocketServer.callsToConstructor[0],
86
+ { port: this.wssPort },
87
+ 'Did not create WebSocketServer!'
88
+ )
89
+ }
90
+
91
+ @test()
92
+ protected static async activateSendsDataToWebSocketClients() {
93
+ this.activate()
94
+
95
+ const { samples, timestamps } = this.simulateOnDataCallback()
96
+
97
+ for (const client of FakeWebSocketServer.clients) {
98
+ const callToClient = FakeWebSocket.callsToSend.filter(
99
+ (c) => c.id === client.id
100
+ )[0]
101
+
102
+ assert.isEqualDeep(
103
+ callToClient?.data,
104
+ JSON.stringify({
105
+ samples,
106
+ timestamps,
107
+ }),
108
+ 'Did not send data to client!'
109
+ )
110
+ }
111
+ }
112
+
113
+ @test()
114
+ protected static async doesNotSendToClientsThatAreNotReady() {
115
+ for (const client of FakeWebSocketServer.clients) {
116
+ client.readyState = WebSocket.CONNECTING
117
+ }
118
+
119
+ this.activate()
120
+ this.simulateOnDataCallback()
121
+
122
+ assert.isEqual(
123
+ FakeWebSocket.callsToSend.length,
124
+ 0,
125
+ 'Sent data to clients that are not ready!'
126
+ )
127
+ }
128
+
129
+ @test()
130
+ protected static async destroyClosesWebSocketServer() {
131
+ this.destroy()
132
+
133
+ assert.isEqual(
134
+ FakeWebSocketServer.numCallsToClose,
135
+ 1,
136
+ 'Did not call close on WebSocketServer!'
137
+ )
138
+ }
139
+
140
+ @test()
141
+ protected static async throwsIfActivateIsCalledAfterDestroy() {
142
+ this.destroy()
143
+
144
+ assert.doesThrow(() => {
145
+ this.activate()
146
+ }, `\n\n Cannot re-activate bridge after destroying it! \n\n Please create and activate a new instance. \n`)
147
+ }
148
+
149
+ private static activate() {
150
+ this.instance.activate()
151
+ }
152
+
153
+ private static deactivate() {
154
+ this.instance.deactivate()
155
+ }
156
+
157
+ private static destroy() {
158
+ this.instance.destroy()
159
+ }
160
+
161
+ private static simulateOnDataCallback() {
162
+ const samples = new Float32Array([1, 2, 3, 4, 5, 6])
163
+ const timestamps = new Float64Array([7, 8])
164
+
165
+ FakeStreamInlet.callsToConstructor[0]?.onData!(samples, timestamps)
166
+ return { samples, timestamps }
167
+ }
168
+
169
+ private static readonly wssPort = 8080
170
+
171
+ private static readonly baseOptions = {
172
+ channelNames: this.channelNames,
173
+ channelFormat: 'float32' as const,
174
+ sampleRateHz: 100 * Math.random(),
175
+ chunkSize: randomInt(1, 100),
176
+ }
177
+
178
+ private static LslWebSocketBridge(
179
+ options?: Partial<StreamTransportBridgeOptions>
180
+ ) {
181
+ return LslWebSocketBridge.Create({ ...this.baseOptions, ...options })
24
182
  }
25
183
  }