@neurodevs/node-lsl 8.0.1 → 8.0.3

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
package/package.json CHANGED
@@ -1,52 +1,52 @@
1
1
  {
2
2
  "name": "@neurodevs/node-lsl",
3
- "version": "8.0.1",
3
+ "version": "8.0.3",
4
4
  "main": "build/index.js",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/neurodevs/node-lsl.git"
8
8
  },
9
9
  "scripts": {
10
- "build.ci": "yarn build.tsc && yarn build.resolve-paths && yarn lint",
11
- "build.dev": "yarn build.tsc --sourceMap ; yarn resolve-paths.lint",
10
+ "build.ci": "yarn run build.tsc && yarn run build.resolve-paths && yarn run lint",
11
+ "build.dev": "yarn run build.tsc --sourceMap ; yarn run resolve-paths.lint",
12
12
  "build.copy-files": "mkdir -p build && rsync -avzq --exclude='*.ts' ./src/ ./build/",
13
13
  "build.resolve-paths": "resolve-path-aliases --target build --patterns '**/*.js,**/*.d.ts'",
14
- "build.tsc": "yarn build.copy-files && tsc",
15
- "clean": "yarn clean.build",
16
- "clean.all": "yarn clean.dependencies && yarn clean.build",
14
+ "build.tsc": "yarn run build.copy-files && tsc",
15
+ "clean": "yarn run clean.build",
16
+ "clean.all": "yarn run clean.dependencies && yarn run clean.build",
17
17
  "clean.build": "rm -rf build/",
18
18
  "clean.dependencies": "rm -rf node_modules/ package-lock.json yarn.lock",
19
19
  "fix.lint": "eslint --fix --cache '**/*.ts'",
20
20
  "lint": "eslint --cache '**/*.ts'",
21
21
  "lint.tsc": "tsc -p . --noEmit",
22
- "post.watch.build": "yarn build.copy-files && yarn build.resolve-paths",
23
- "rebuild": "yarn clean.all && yarn && yarn build.dev",
24
- "update.dependencies": "yarn clean.dependencies && yarn",
25
- "resolve-paths.lint": "yarn build.resolve-paths ; yarn lint",
22
+ "post.watch.build": "yarn run build.copy-files && yarn run build.resolve-paths",
23
+ "rebuild": "yarn run clean.all && yarn install && yarn run build.dev",
24
+ "update.dependencies": "yarn run clean.dependencies && yarn",
25
+ "resolve-paths.lint": "yarn run build.resolve-paths ; yarn run lint",
26
26
  "test": "jest",
27
- "watch.build.dev": "tsc-watch --sourceMap --onCompilationComplete 'yarn post.watch.build'",
27
+ "watch.build.dev": "tsc-watch --sourceMap --onCompilationComplete 'yarn run post.watch.build'",
28
28
  "watch.lint": "concurrently 'yarn lint' \"chokidar 'src/**/*' -c 'yarn lint.tsc'\"",
29
- "watch.rebuild": "yarn clean.all && yarn && yarn watch.build.dev",
29
+ "watch.rebuild": "yarn run clean.all && yarn install && yarn run watch.build.dev",
30
30
  "watch.tsc": "tsc -w",
31
31
  "test.rs": "node build/test.js"
32
32
  },
33
33
  "devDependencies": {
34
- "@sprucelabs/jest-json-reporter": "^7.0.197",
35
- "@sprucelabs/resolve-path-aliases": "^1.1.298",
36
- "@sprucelabs/test": "^8.0.49",
37
- "@sprucelabs/test-utils": "^4.0.141",
34
+ "@sprucelabs/jest-json-reporter": "^8.0.24",
35
+ "@sprucelabs/resolve-path-aliases": "^2.0.20",
36
+ "@sprucelabs/test": "^9.0.13",
37
+ "@sprucelabs/test-utils": "^5.0.27",
38
38
  "@types/ffi-napi": "^4.0.10",
39
- "@types/node": "^20.11.30",
39
+ "@types/node": "^20.12.11",
40
40
  "chokidar-cli": "^3.0.0",
41
41
  "concurrently": "^8.2.2",
42
- "eslint": "^8.57.0",
43
- "eslint-config-spruce": "^10.13.6",
42
+ "eslint": "^9.2.0",
43
+ "eslint-config-spruce": "^11.2.17",
44
44
  "jest": "^29.7.0",
45
45
  "jest-circus": "^29.7.0",
46
46
  "prettier": "^3.2.5",
47
47
  "ts-node": "^10.9.2",
48
48
  "tsc-watch": "^6.2.0",
49
- "typescript": "^5.4.3"
49
+ "typescript": "^5.4.5"
50
50
  },
51
51
  "description": "Lab Streaming Layer (LSL) bindings for Node",
52
52
  "skill": {
@@ -74,13 +74,13 @@
74
74
  }
75
75
  },
76
76
  "dependencies": {
77
- "@sprucelabs/error": "^5.1.94",
78
- "@sprucelabs/schema": "^29.4.43",
79
- "@sprucelabs/spruce-core-schemas": "^39.0.277",
80
- "@sprucelabs/spruce-skill-utils": "^30.4.10",
81
- "ffi-rs": "^1.0.62",
77
+ "@sprucelabs/error": "^6.0.23",
78
+ "@sprucelabs/schema": "^30.0.36",
79
+ "@sprucelabs/spruce-core-schemas": "^40.0.30",
80
+ "@sprucelabs/spruce-skill-utils": "^31.0.39",
81
+ "ffi-rs": "^1.0.74",
82
82
  "node-gyp": "^10.1.0",
83
83
  "ref-array-napi": "^1.2.2",
84
84
  "ref-napi": "^3.0.3"
85
85
  }
86
- }
86
+ }
@@ -1,6 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-namespace */
2
- /* eslint-disable no-redeclare */
3
-
4
1
  import { default as SchemaEntity } from '@sprucelabs/schema'
5
2
  import * as SpruceSchema from '@sprucelabs/schema'
6
3
 
@@ -1,282 +1,287 @@
1
1
  import AbstractSpruceTest, {
2
- test,
3
- assert,
4
- errorAssert,
5
- generateId,
2
+ test,
3
+ assert,
4
+ errorAssert,
5
+ generateId,
6
6
  } from '@sprucelabs/test-utils'
7
7
  import LiblslImpl from '../../implementations/Liblsl'
8
8
  import LslOutlet from '../../implementations/LslOutlet'
9
9
  import { LslOutletOptions, LslSample } from '../../nodeLsl.types'
10
10
  import FakeLiblsl from '../../testDoubles/FakeLiblsl'
11
11
  import {
12
- TEST_CHANNEL_FORMATS_MAP,
13
- TEST_SUPPORTED_CHANNEL_FORMATS,
14
- TEST_UNSUPPORTED_CHANNEL_FORMATS,
15
- TestChannelFormat,
12
+ TEST_CHANNEL_FORMATS_MAP,
13
+ TEST_SUPPORTED_CHANNEL_FORMATS,
14
+ TEST_UNSUPPORTED_CHANNEL_FORMATS,
15
+ TestChannelFormat,
16
16
  } from '../support/consts'
17
17
  import generateRandomOutletOptions from '../support/generateRandomOutletOptions'
18
18
 
19
19
  export default class LslOutletTest extends AbstractSpruceTest {
20
- private static fakeLiblsl: FakeLiblsl
21
- private static randomOutletOptions: LslOutletOptions
22
- private static channelFormatIdx: number
23
-
24
- protected static async beforeEach() {
25
- await super.beforeEach()
26
- delete LslOutlet.Class
27
- this.randomOutletOptions = generateRandomOutletOptions()
28
-
29
- const channelFormat = this.randomOutletOptions.channelFormat
30
- this.channelFormatIdx = TEST_CHANNEL_FORMATS_MAP[channelFormat]
31
- this.fakeLiblsl = new FakeLiblsl()
32
- LiblslImpl.setInstance(this.fakeLiblsl)
33
- }
34
-
35
- @test()
36
- protected static async throwsWhenMissingRequiredParams() {
37
- //@ts-ignore
38
- const err = assert.doesThrow(() => new LslOutlet())
39
- errorAssert.assertError(err, 'MISSING_PARAMETERS', {
40
- parameters: [
41
- 'name',
42
- 'type',
43
- 'channelNames',
44
- 'sampleRate',
45
- 'channelFormat',
46
- 'sourceId',
47
- 'manufacturer',
48
- 'unit',
49
- 'chunkSize',
50
- 'maxBuffered',
51
- ],
52
- })
53
- }
54
-
55
- @test()
56
- protected static async throwsWithInvalidChannelNames() {
57
- this.assertThrowsInvalidChannelNames(0)
58
- }
59
-
60
- @test()
61
- protected static async throwsWithInvalidSampleRate() {
62
- this.Outlet({ sampleRate: 0 })
63
- this.assertThrowsInvalidSampleRate(-1)
64
- this.assertThrowsInvalidSampleRate(-1.5)
65
- }
66
-
67
- @test()
68
- protected static async throwsWithInvalidChannelFormat() {
69
- //@ts-ignore
70
- this.assertDoesThrowInvalidParameters({ channelFormat: generateId() }, [
71
- 'channelFormat',
72
- ])
73
- }
74
-
75
- @test()
76
- protected static async throwsWithInvalidChunkSize() {
77
- this.Outlet({ chunkSize: 0 })
78
- this.assertThrowsInvalidChunkSize(-1)
79
- this.assertThrowsInvalidChunkSize(-1.5)
80
- this.assertThrowsInvalidChunkSize(1.5)
81
- }
82
-
83
- @test()
84
- protected static async throwsWithInvalidMaxBuffered() {
85
- this.Outlet({ maxBuffered: 0 })
86
- this.assertThrowsInvalidMaxBuffered(-1)
87
- this.assertThrowsInvalidMaxBuffered(-1.5)
88
- this.assertThrowsInvalidMaxBuffered(1.5)
89
- }
90
-
91
- @test()
92
- protected static async throwsWithUnsupportedType() {
93
- for (let unsupportedType of TEST_UNSUPPORTED_CHANNEL_FORMATS) {
94
- assert.doesThrow(
95
- () => this.Outlet({ channelFormat: unsupportedType }),
96
- `This method currently does not support the ${unsupportedType} type! Please implement it.`
97
- )
98
- }
99
- }
100
-
101
- @test()
102
- protected static async supportsAllKnownChannelFormats() {
103
- for (const format of TEST_SUPPORTED_CHANNEL_FORMATS) {
104
- this.Outlet({ channelFormat: format as TestChannelFormat })
105
- }
106
- }
107
-
108
- @test('pushing [1, 2] sample sends to LSL', [1.0, 2.0])
109
- @test('pushing [1] sample sends to LSL', [1.0])
110
- protected static async canPushFloatSampleToLsl(sample: LslSample) {
111
- const outlet = this.FloatOutlet()
112
- outlet.pushSample(sample)
113
-
114
- assert.isEqual(
115
- this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.outlet,
116
- this.fakeLiblsl.outlet
117
- )
118
- assert.isEqualDeep(
119
- this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.sample,
120
- sample
121
- )
122
- assert.isEqualDeep(this.fakeLiblsl.lastCreateOutletOptions, {
123
- info: this.fakeLiblsl.streamInfo,
124
- chunkSize: this.randomOutletOptions.chunkSize,
125
- maxBuffered: this.randomOutletOptions.maxBuffered,
126
- })
127
- assert.isNumber(
128
- this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.timestamp
129
- )
130
- }
131
-
132
- @test()
133
- protected static async canPushStringSampleToLsl() {
134
- const sample = [generateId()]
135
- const outlet = this.StringOutlet()
136
-
137
- outlet.pushSample(sample)
138
-
139
- assert.isEqual(
140
- this.fakeLiblsl.lastPushSampleStringTimestampOptions?.outlet,
141
- this.fakeLiblsl.outlet
142
- )
143
- assert.isEqualDeep(
144
- this.fakeLiblsl.lastPushSampleStringTimestampOptions?.sample,
145
- sample
146
- )
147
- assert.isNumber(
148
- this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
149
- )
150
- }
151
-
152
- @test()
153
- protected static async pushingStringTwiceGivesDifferentTimestamps() {
154
- const outlet = this.StringOutlet()
155
- const sample = [generateId()]
156
-
157
- outlet.pushSample(sample)
158
- const t1 = this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
159
-
160
- await this.wait(10)
161
-
162
- outlet.pushSample(sample)
163
- const t2 = this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
164
-
165
- assert.isNotEqual(t1, t2)
166
- assert.isEqual(this.fakeLiblsl.localClockHitCount, 2)
167
- }
168
-
169
- @test()
170
- protected static async constructionCreatesStreamInfo() {
171
- this.Outlet()
172
-
173
- const { ...options } = this.randomOutletOptions as any
174
-
175
- options.channelCount = options.channelNames.length
176
-
177
- delete options.manufacturer
178
- delete options.unit
179
- delete options.chunkSize
180
- delete options.maxBuffered
181
- delete options.channelNames
182
-
183
- assert.isEqualDeep(this.fakeLiblsl.lastCreateStreamInfoOptions, {
184
- ...options,
185
- channelFormat: this.channelFormatIdx,
186
- })
187
- }
188
-
189
- @test()
190
- protected static async canOverrideClassInstantiatedInFactory() {
191
- LslOutlet.Class = CheckingOutlet
192
- const instance = this.Outlet()
193
-
194
- assert.isInstanceOf(instance, CheckingOutlet)
195
- }
196
-
197
- @test('can add one label to channels', [generateId()])
198
- @test('can add two labels to channels', [generateId(), generateId()])
199
- protected static async appendsChannelsBasedOnCount(labels: string[]) {
200
- const type = generateId()
201
- const unit = generateId()
202
-
203
- this.Outlet({ channelNames: labels, type, unit })
204
-
205
- assert.isEqualDeep(this.fakeLiblsl.lastAppendChannelsToStreamInfoOptions, {
206
- info: this.fakeLiblsl.streamInfo,
207
- channels: labels.map((label) => ({
208
- label,
209
- type,
210
- unit,
211
- })),
212
- })
213
- }
214
-
215
- @test()
216
- protected static async canDestroyOutlet() {
217
- const outlet = this.Outlet()
218
- outlet.destroy()
219
-
220
- assert.isEqual(this.fakeLiblsl.destroyOutletHitCount, 1)
221
- }
222
-
223
- @test()
224
- protected static async pushSampleShouldNotCreateMultipleOutlets() {
225
- const outlet = this.FloatOutlet()
226
- outlet.pushSample([1.0])
227
- outlet.pushSample([2.0])
228
- outlet.pushSample([3.0])
229
-
230
- assert.isEqual(this.fakeLiblsl.createStreamInfoHitCount, 1)
231
- }
232
-
233
- private static assertThrowsInvalidMaxBuffered(maxBuffered: number) {
234
- this.assertDoesThrowInvalidParameters({ maxBuffered }, ['maxBuffered'])
235
- }
236
-
237
- private static assertThrowsInvalidChunkSize(chunkSize: number) {
238
- this.assertDoesThrowInvalidParameters({ chunkSize }, ['chunkSize'])
239
- }
240
-
241
- private static assertThrowsInvalidSampleRate(sampleRate: number) {
242
- this.assertDoesThrowInvalidParameters({ sampleRate }, ['sampleRate'])
243
- }
244
-
245
- private static assertThrowsInvalidChannelNames(count: number) {
246
- this.assertDoesThrowInvalidParameters(
247
- { channelNames: new Array(count).fill(generateId()) },
248
- ['channelNames']
249
- )
250
- }
251
-
252
- private static assertDoesThrowInvalidParameters(
253
- options: Partial<LslOutletOptions>,
254
- parameters: string[]
255
- ) {
256
- const err = assert.doesThrow(() => this.Outlet(options))
257
- errorAssert.assertError(err, 'INVALID_PARAMETERS', {
258
- parameters,
259
- })
260
- }
261
-
262
- private static Outlet(options?: Partial<LslOutletOptions>) {
263
- return LslOutlet.Outlet({
264
- ...this.randomOutletOptions,
265
- ...options,
266
- })
267
- }
268
-
269
- private static StringOutlet() {
270
- return this.Outlet({ channelFormat: 'string' })
271
- }
272
-
273
- private static FloatOutlet() {
274
- return this.Outlet({ channelFormat: 'float32' })
275
- }
20
+ private static fakeLiblsl: FakeLiblsl
21
+ private static randomOutletOptions: LslOutletOptions
22
+ private static channelFormatIdx: number
23
+
24
+ protected static async beforeEach() {
25
+ await super.beforeEach()
26
+ delete LslOutlet.Class
27
+ this.randomOutletOptions = generateRandomOutletOptions()
28
+
29
+ const channelFormat = this.randomOutletOptions.channelFormat
30
+ this.channelFormatIdx = TEST_CHANNEL_FORMATS_MAP[channelFormat]
31
+ this.fakeLiblsl = new FakeLiblsl()
32
+ LiblslImpl.setInstance(this.fakeLiblsl)
33
+ }
34
+
35
+ @test()
36
+ protected static async throwsWhenMissingRequiredParams() {
37
+ //@ts-ignore
38
+ const err = assert.doesThrow(() => new LslOutlet())
39
+ errorAssert.assertError(err, 'MISSING_PARAMETERS', {
40
+ parameters: [
41
+ 'name',
42
+ 'type',
43
+ 'channelNames',
44
+ 'sampleRate',
45
+ 'channelFormat',
46
+ 'sourceId',
47
+ 'manufacturer',
48
+ 'unit',
49
+ 'chunkSize',
50
+ 'maxBuffered',
51
+ ],
52
+ })
53
+ }
54
+
55
+ @test()
56
+ protected static async throwsWithInvalidChannelNames() {
57
+ this.assertThrowsInvalidChannelNames(0)
58
+ }
59
+
60
+ @test()
61
+ protected static async throwsWithInvalidSampleRate() {
62
+ this.Outlet({ sampleRate: 0 })
63
+ this.assertThrowsInvalidSampleRate(-1)
64
+ this.assertThrowsInvalidSampleRate(-1.5)
65
+ }
66
+
67
+ @test()
68
+ protected static async throwsWithInvalidChannelFormat() {
69
+ //@ts-ignore
70
+ this.assertDoesThrowInvalidParameters({ channelFormat: generateId() }, [
71
+ 'channelFormat',
72
+ ])
73
+ }
74
+
75
+ @test()
76
+ protected static async throwsWithInvalidChunkSize() {
77
+ this.Outlet({ chunkSize: 0 })
78
+ this.assertThrowsInvalidChunkSize(-1)
79
+ this.assertThrowsInvalidChunkSize(-1.5)
80
+ this.assertThrowsInvalidChunkSize(1.5)
81
+ }
82
+
83
+ @test()
84
+ protected static async throwsWithInvalidMaxBuffered() {
85
+ this.Outlet({ maxBuffered: 0 })
86
+ this.assertThrowsInvalidMaxBuffered(-1)
87
+ this.assertThrowsInvalidMaxBuffered(-1.5)
88
+ this.assertThrowsInvalidMaxBuffered(1.5)
89
+ }
90
+
91
+ @test()
92
+ protected static async throwsWithUnsupportedType() {
93
+ for (let unsupportedType of TEST_UNSUPPORTED_CHANNEL_FORMATS) {
94
+ assert.doesThrow(
95
+ () => this.Outlet({ channelFormat: unsupportedType }),
96
+ `This method currently does not support the ${unsupportedType} type! Please implement it.`
97
+ )
98
+ }
99
+ }
100
+
101
+ @test()
102
+ protected static async supportsAllKnownChannelFormats() {
103
+ for (const format of TEST_SUPPORTED_CHANNEL_FORMATS) {
104
+ this.Outlet({ channelFormat: format as TestChannelFormat })
105
+ }
106
+ }
107
+
108
+ @test('pushing [1, 2] sample sends to LSL', [1.0, 2.0])
109
+ @test('pushing [1] sample sends to LSL', [1.0])
110
+ protected static async canPushFloatSampleToLsl(sample: LslSample) {
111
+ const outlet = this.FloatOutlet()
112
+ outlet.pushSample(sample)
113
+
114
+ assert.isEqual(
115
+ this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.outlet,
116
+ this.fakeLiblsl.outlet
117
+ )
118
+ assert.isEqualDeep(
119
+ this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.sample,
120
+ sample
121
+ )
122
+ assert.isEqualDeep(this.fakeLiblsl.lastCreateOutletOptions, {
123
+ info: this.fakeLiblsl.streamInfo,
124
+ chunkSize: this.randomOutletOptions.chunkSize,
125
+ maxBuffered: this.randomOutletOptions.maxBuffered,
126
+ })
127
+ assert.isNumber(
128
+ this.fakeLiblsl.lastPushSampleFloatTimestampOptions?.timestamp
129
+ )
130
+ }
131
+
132
+ @test()
133
+ protected static async canPushStringSampleToLsl() {
134
+ const sample = [generateId()]
135
+ const outlet = this.StringOutlet()
136
+
137
+ outlet.pushSample(sample)
138
+
139
+ assert.isEqual(
140
+ this.fakeLiblsl.lastPushSampleStringTimestampOptions?.outlet,
141
+ this.fakeLiblsl.outlet
142
+ )
143
+ assert.isEqualDeep(
144
+ this.fakeLiblsl.lastPushSampleStringTimestampOptions?.sample,
145
+ sample
146
+ )
147
+ assert.isNumber(
148
+ this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
149
+ )
150
+ }
151
+
152
+ @test()
153
+ protected static async pushingStringTwiceGivesDifferentTimestamps() {
154
+ const outlet = this.StringOutlet()
155
+ const sample = [generateId()]
156
+
157
+ outlet.pushSample(sample)
158
+ const t1 =
159
+ this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
160
+
161
+ await this.wait(10)
162
+
163
+ outlet.pushSample(sample)
164
+ const t2 =
165
+ this.fakeLiblsl.lastPushSampleStringTimestampOptions?.timestamp
166
+
167
+ assert.isNotEqual(t1, t2)
168
+ assert.isEqual(this.fakeLiblsl.localClockHitCount, 2)
169
+ }
170
+
171
+ @test()
172
+ protected static async constructionCreatesStreamInfo() {
173
+ this.Outlet()
174
+
175
+ const { ...options } = this.randomOutletOptions as any
176
+
177
+ options.channelCount = options.channelNames.length
178
+
179
+ delete options.manufacturer
180
+ delete options.unit
181
+ delete options.chunkSize
182
+ delete options.maxBuffered
183
+ delete options.channelNames
184
+
185
+ assert.isEqualDeep(this.fakeLiblsl.lastCreateStreamInfoOptions, {
186
+ ...options,
187
+ channelFormat: this.channelFormatIdx,
188
+ })
189
+ }
190
+
191
+ @test()
192
+ protected static async canOverrideClassInstantiatedInFactory() {
193
+ LslOutlet.Class = CheckingOutlet
194
+ const instance = this.Outlet()
195
+
196
+ assert.isInstanceOf(instance, CheckingOutlet)
197
+ }
198
+
199
+ @test('can add one label to channels', [generateId()])
200
+ @test('can add two labels to channels', [generateId(), generateId()])
201
+ protected static async appendsChannelsBasedOnCount(labels: string[]) {
202
+ const type = generateId()
203
+ const unit = generateId()
204
+
205
+ this.Outlet({ channelNames: labels, type, unit })
206
+
207
+ assert.isEqualDeep(
208
+ this.fakeLiblsl.lastAppendChannelsToStreamInfoOptions,
209
+ {
210
+ info: this.fakeLiblsl.streamInfo,
211
+ channels: labels.map((label) => ({
212
+ label,
213
+ type,
214
+ unit,
215
+ })),
216
+ }
217
+ )
218
+ }
219
+
220
+ @test()
221
+ protected static async canDestroyOutlet() {
222
+ const outlet = this.Outlet()
223
+ outlet.destroy()
224
+
225
+ assert.isEqual(this.fakeLiblsl.destroyOutletHitCount, 1)
226
+ }
227
+
228
+ @test()
229
+ protected static async pushSampleShouldNotCreateMultipleOutlets() {
230
+ const outlet = this.FloatOutlet()
231
+ outlet.pushSample([1.0])
232
+ outlet.pushSample([2.0])
233
+ outlet.pushSample([3.0])
234
+
235
+ assert.isEqual(this.fakeLiblsl.createStreamInfoHitCount, 1)
236
+ }
237
+
238
+ private static assertThrowsInvalidMaxBuffered(maxBuffered: number) {
239
+ this.assertDoesThrowInvalidParameters({ maxBuffered }, ['maxBuffered'])
240
+ }
241
+
242
+ private static assertThrowsInvalidChunkSize(chunkSize: number) {
243
+ this.assertDoesThrowInvalidParameters({ chunkSize }, ['chunkSize'])
244
+ }
245
+
246
+ private static assertThrowsInvalidSampleRate(sampleRate: number) {
247
+ this.assertDoesThrowInvalidParameters({ sampleRate }, ['sampleRate'])
248
+ }
249
+
250
+ private static assertThrowsInvalidChannelNames(count: number) {
251
+ this.assertDoesThrowInvalidParameters(
252
+ { channelNames: new Array(count).fill(generateId()) },
253
+ ['channelNames']
254
+ )
255
+ }
256
+
257
+ private static assertDoesThrowInvalidParameters(
258
+ options: Partial<LslOutletOptions>,
259
+ parameters: string[]
260
+ ) {
261
+ const err = assert.doesThrow(() => this.Outlet(options))
262
+ errorAssert.assertError(err, 'INVALID_PARAMETERS', {
263
+ parameters,
264
+ })
265
+ }
266
+
267
+ private static Outlet(options?: Partial<LslOutletOptions>) {
268
+ return LslOutlet.Outlet({
269
+ ...this.randomOutletOptions,
270
+ ...options,
271
+ })
272
+ }
273
+
274
+ private static StringOutlet() {
275
+ return this.Outlet({ channelFormat: 'string' })
276
+ }
277
+
278
+ private static FloatOutlet() {
279
+ return this.Outlet({ channelFormat: 'float32' })
280
+ }
276
281
  }
277
282
 
278
283
  class CheckingOutlet extends LslOutlet {
279
- public constructor(options: LslOutletOptions) {
280
- super(options)
281
- }
284
+ public constructor(options: LslOutletOptions) {
285
+ super(options)
286
+ }
282
287
  }