@neurosity/sdk 6.5.10 → 6.5.11

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 (130) hide show
  1. package/dist/browser/neurosity.iife.js +118 -49
  2. package/dist/browser/neurosity.js +112 -112
  3. package/dist/browser/neurosity.js.map +1 -1
  4. package/dist/cjs/__tests__/Neurosity.test.d.ts +1 -0
  5. package/dist/cjs/__tests__/Neurosity.test.js +201 -0
  6. package/dist/cjs/__tests__/WebBluetoothTransport.test.d.ts +1 -0
  7. package/dist/cjs/__tests__/WebBluetoothTransport.test.js +201 -0
  8. package/dist/cjs/__tests__/accelerometer.test.d.ts +1 -0
  9. package/dist/cjs/__tests__/accelerometer.test.js +158 -0
  10. package/dist/cjs/__tests__/auth.test.d.ts +1 -0
  11. package/dist/cjs/__tests__/auth.test.js +239 -0
  12. package/dist/cjs/__tests__/brainwaves.test.d.ts +1 -0
  13. package/dist/cjs/__tests__/brainwaves.test.js +291 -0
  14. package/dist/cjs/__tests__/device.test.d.ts +1 -0
  15. package/dist/cjs/__tests__/device.test.js +281 -0
  16. package/dist/cjs/__tests__/haptics.test.d.ts +1 -0
  17. package/dist/cjs/__tests__/haptics.test.js +162 -0
  18. package/dist/cjs/__tests__/metrics.test.d.ts +1 -0
  19. package/dist/cjs/__tests__/metrics.test.js +178 -0
  20. package/dist/cjs/__tests__/oauth.test.d.ts +1 -0
  21. package/dist/cjs/__tests__/oauth.test.js +138 -0
  22. package/dist/cjs/__tests__/settings.test.d.ts +1 -0
  23. package/dist/cjs/__tests__/settings.test.js +130 -0
  24. package/dist/cjs/__tests__/setup/webBluetooth.setup.d.ts +11 -0
  25. package/dist/cjs/__tests__/setup/webBluetooth.setup.js +35 -0
  26. package/dist/cjs/__tests__/setup.d.ts +0 -0
  27. package/dist/cjs/__tests__/setup.js +7 -0
  28. package/dist/cjs/__tests__/streaming.test.d.ts +1 -0
  29. package/dist/cjs/__tests__/streaming.test.js +259 -0
  30. package/dist/cjs/__tests__/timesync.test.d.ts +1 -0
  31. package/dist/cjs/__tests__/timesync.test.js +54 -0
  32. package/dist/cjs/__tests__/utils.test.d.ts +1 -0
  33. package/dist/cjs/__tests__/utils.test.js +281 -0
  34. package/dist/cjs/api/bluetooth/BluetoothClient.d.ts +6 -6
  35. package/dist/cjs/api/bluetooth/BluetoothTransport.d.ts +1 -1
  36. package/dist/cjs/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
  37. package/dist/cjs/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
  38. package/dist/cjs/api/bluetooth/types/index.d.ts +2 -2
  39. package/dist/cjs/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
  40. package/dist/cjs/api/bluetooth/utils/stitch.d.ts +1 -1
  41. package/dist/cjs/api/bluetooth/utils/textCodec.d.ts +1 -1
  42. package/dist/cjs/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
  43. package/dist/cjs/api/firebase/FirebaseDevice.d.ts +1 -1
  44. package/dist/cjs/api/firebase/FirebaseUser.d.ts +1 -1
  45. package/dist/cjs/api/index.js +1 -1
  46. package/dist/cjs/timesync/Timesync.d.ts +1 -1
  47. package/dist/cjs/types/awareness.d.ts +1 -1
  48. package/dist/cjs/types/brainwaves.d.ts +25 -12
  49. package/dist/cjs/types/credentials.d.ts +4 -4
  50. package/dist/cjs/types/deviceInfo.d.ts +4 -4
  51. package/dist/cjs/types/epoch.d.ts +1 -1
  52. package/dist/cjs/types/experiment.d.ts +1 -1
  53. package/dist/cjs/types/hapticEffects.d.ts +1 -1
  54. package/dist/cjs/types/marker.d.ts +1 -1
  55. package/dist/cjs/types/metrics.d.ts +2 -2
  56. package/dist/cjs/types/oauth.d.ts +4 -4
  57. package/dist/cjs/types/sample.d.ts +2 -2
  58. package/dist/cjs/types/signalQuality.d.ts +1 -1
  59. package/dist/cjs/types/skill.d.ts +2 -2
  60. package/dist/cjs/types/user.d.ts +3 -3
  61. package/dist/cjs/utils/oauth.d.ts +1 -1
  62. package/dist/cjs/utils/transferDevice.d.ts +3 -3
  63. package/dist/cjs/utils/whileOnline.d.ts +1 -1
  64. package/dist/electron/index.js +1 -1
  65. package/dist/electron/index.js.map +1 -1
  66. package/dist/esm/__tests__/Neurosity.test.d.ts +1 -0
  67. package/dist/esm/__tests__/Neurosity.test.js +199 -0
  68. package/dist/esm/__tests__/WebBluetoothTransport.test.d.ts +1 -0
  69. package/dist/esm/__tests__/WebBluetoothTransport.test.js +199 -0
  70. package/dist/esm/__tests__/accelerometer.test.d.ts +1 -0
  71. package/dist/esm/__tests__/accelerometer.test.js +156 -0
  72. package/dist/esm/__tests__/auth.test.d.ts +1 -0
  73. package/dist/esm/__tests__/auth.test.js +237 -0
  74. package/dist/esm/__tests__/brainwaves.test.d.ts +1 -0
  75. package/dist/esm/__tests__/brainwaves.test.js +289 -0
  76. package/dist/esm/__tests__/device.test.d.ts +1 -0
  77. package/dist/esm/__tests__/device.test.js +279 -0
  78. package/dist/esm/__tests__/haptics.test.d.ts +1 -0
  79. package/dist/esm/__tests__/haptics.test.js +160 -0
  80. package/dist/esm/__tests__/metrics.test.d.ts +1 -0
  81. package/dist/esm/__tests__/metrics.test.js +176 -0
  82. package/dist/esm/__tests__/oauth.test.d.ts +1 -0
  83. package/dist/esm/__tests__/oauth.test.js +133 -0
  84. package/dist/esm/__tests__/settings.test.d.ts +1 -0
  85. package/dist/esm/__tests__/settings.test.js +128 -0
  86. package/dist/esm/__tests__/setup/webBluetooth.setup.d.ts +11 -0
  87. package/dist/esm/__tests__/setup/webBluetooth.setup.js +35 -0
  88. package/dist/esm/__tests__/setup.d.ts +0 -0
  89. package/dist/esm/__tests__/setup.js +7 -0
  90. package/dist/esm/__tests__/streaming.test.d.ts +1 -0
  91. package/dist/esm/__tests__/streaming.test.js +257 -0
  92. package/dist/esm/__tests__/timesync.test.d.ts +1 -0
  93. package/dist/esm/__tests__/timesync.test.js +52 -0
  94. package/dist/esm/__tests__/utils.test.d.ts +1 -0
  95. package/dist/esm/__tests__/utils.test.js +279 -0
  96. package/dist/esm/api/bluetooth/BluetoothClient.d.ts +6 -6
  97. package/dist/esm/api/bluetooth/BluetoothTransport.d.ts +1 -1
  98. package/dist/esm/api/bluetooth/react-native/ReactNativeTransport.d.ts +4 -4
  99. package/dist/esm/api/bluetooth/react-native/types/ReactNativeTypes.d.ts +2 -2
  100. package/dist/esm/api/bluetooth/types/index.d.ts +2 -2
  101. package/dist/esm/api/bluetooth/utils/decodeJSONChunks.d.ts +1 -1
  102. package/dist/esm/api/bluetooth/utils/stitch.d.ts +1 -1
  103. package/dist/esm/api/bluetooth/utils/textCodec.d.ts +1 -1
  104. package/dist/esm/api/bluetooth/web/WebBluetoothTransport.d.ts +1 -1
  105. package/dist/esm/api/firebase/FirebaseDevice.d.ts +1 -1
  106. package/dist/esm/api/firebase/FirebaseUser.d.ts +1 -1
  107. package/dist/esm/api/index.js +2 -2
  108. package/dist/esm/neurosity.mjs +118 -49
  109. package/dist/esm/timesync/Timesync.d.ts +1 -1
  110. package/dist/esm/types/awareness.d.ts +1 -1
  111. package/dist/esm/types/brainwaves.d.ts +25 -12
  112. package/dist/esm/types/credentials.d.ts +4 -4
  113. package/dist/esm/types/deviceInfo.d.ts +4 -4
  114. package/dist/esm/types/epoch.d.ts +1 -1
  115. package/dist/esm/types/experiment.d.ts +1 -1
  116. package/dist/esm/types/hapticEffects.d.ts +1 -1
  117. package/dist/esm/types/marker.d.ts +1 -1
  118. package/dist/esm/types/metrics.d.ts +2 -2
  119. package/dist/esm/types/oauth.d.ts +4 -4
  120. package/dist/esm/types/sample.d.ts +2 -2
  121. package/dist/esm/types/signalQuality.d.ts +1 -1
  122. package/dist/esm/types/skill.d.ts +2 -2
  123. package/dist/esm/types/user.d.ts +3 -3
  124. package/dist/esm/utils/oauth.d.ts +1 -1
  125. package/dist/esm/utils/transferDevice.d.ts +3 -3
  126. package/dist/esm/utils/whileOnline.d.ts +1 -1
  127. package/dist/examples/neurosity.iife.js +118 -49
  128. package/dist/examples/neurosity.js +112 -112
  129. package/dist/examples/neurosity.mjs +118 -49
  130. package/package.json +16 -4
@@ -0,0 +1,279 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Neurosity } from "../Neurosity";
11
+ import { firstValueFrom, of, BehaviorSubject, take } from "rxjs";
12
+ import { STATUS, SLEEP_MODE_REASON } from "../types/status";
13
+ // Mock CloudClient
14
+ jest.mock("../api", () => {
15
+ const originalModule = jest.requireActual("../api");
16
+ class MockCloudClient {
17
+ constructor(options) {
18
+ this.user = null;
19
+ this.userClaims = { scopes: ["brainwaves"] };
20
+ this.subscriptionManager = {
21
+ add: jest.fn(),
22
+ remove: jest.fn(),
23
+ removeAll: jest.fn()
24
+ };
25
+ this._deviceInfo = {
26
+ deviceId: "test-device-id",
27
+ deviceNickname: "Test Device",
28
+ channelNames: ["CH1", "CH2"],
29
+ channels: 2,
30
+ samplingRate: 250,
31
+ manufacturer: "Neurosity",
32
+ model: "Crown",
33
+ modelName: "Crown",
34
+ modelVersion: "v1",
35
+ apiVersion: "1.0.0",
36
+ osVersion: "1.0.0",
37
+ emulator: false
38
+ };
39
+ this._selectedDevice = new BehaviorSubject(this._deviceInfo);
40
+ this._deviceStatus = new BehaviorSubject({
41
+ state: STATUS.ONLINE,
42
+ charging: false,
43
+ battery: 100,
44
+ sleepMode: false,
45
+ sleepModeReason: null,
46
+ lastHeartbeat: Date.now(),
47
+ ssid: "test-network"
48
+ });
49
+ this._signalQuality = new BehaviorSubject({
50
+ CH1: {
51
+ standardDeviation: 0.1,
52
+ status: "good"
53
+ },
54
+ CH2: {
55
+ standardDeviation: 0.1,
56
+ status: "good"
57
+ }
58
+ });
59
+ this.getInfo = jest.fn().mockResolvedValue(this._deviceInfo);
60
+ this.selectDevice = jest
61
+ .fn()
62
+ .mockImplementation((selector) => __awaiter(this, void 0, void 0, function* () {
63
+ try {
64
+ const selectedDevice = selector([this._deviceInfo]);
65
+ this._selectedDevice.next(selectedDevice);
66
+ return selectedDevice;
67
+ }
68
+ catch (error) {
69
+ this._selectedDevice.next(null);
70
+ throw error;
71
+ }
72
+ }));
73
+ this.didSelectDevice = jest.fn().mockResolvedValue(true);
74
+ this.onDeviceChange = jest
75
+ .fn()
76
+ .mockReturnValue(this._selectedDevice.asObservable());
77
+ this.status = jest.fn().mockReturnValue(this._deviceStatus.asObservable());
78
+ this.signalQuality = jest
79
+ .fn()
80
+ .mockReturnValue(this._signalQuality.asObservable());
81
+ this.osVersion = jest.fn().mockReturnValue(of("1.0.0"));
82
+ this.metrics = {
83
+ subscribe: jest.fn().mockImplementation((subscription) => {
84
+ return Object.assign({ id: "test-id" }, subscription);
85
+ }),
86
+ on: jest.fn().mockImplementation((subscription, callback) => {
87
+ if (subscription.metric === "signalQuality") {
88
+ const sub = this._signalQuality.subscribe(callback);
89
+ return () => sub.unsubscribe();
90
+ }
91
+ return () => { };
92
+ })
93
+ };
94
+ this.addDevice = jest.fn().mockImplementation((deviceId) => __awaiter(this, void 0, void 0, function* () {
95
+ if (deviceId === "invalid-id") {
96
+ throw new Error("Invalid device ID");
97
+ }
98
+ return Promise.resolve();
99
+ }));
100
+ this.removeDevice = jest.fn().mockImplementation((deviceId) => __awaiter(this, void 0, void 0, function* () {
101
+ if (deviceId === "invalid-id") {
102
+ throw new Error("Invalid device ID");
103
+ }
104
+ return Promise.resolve();
105
+ }));
106
+ this.transferDevice = jest
107
+ .fn()
108
+ .mockImplementation((options) => __awaiter(this, void 0, void 0, function* () {
109
+ if (options.deviceId === "invalid-id" ||
110
+ options.recipientsUserId === "invalid-user") {
111
+ throw new Error("Invalid transfer parameters");
112
+ }
113
+ return Promise.resolve();
114
+ }));
115
+ this.onUserDevicesChange = jest.fn().mockReturnValue(of([this._deviceInfo]));
116
+ this.options = options;
117
+ }
118
+ // Helper methods for tests
119
+ _updateDeviceStatus(status) {
120
+ this._deviceStatus.next(Object.assign(Object.assign({}, this._deviceStatus.value), status));
121
+ }
122
+ _updateSignalQuality(quality) {
123
+ this._signalQuality.next(quality);
124
+ }
125
+ }
126
+ return Object.assign(Object.assign({}, originalModule), { CloudClient: jest
127
+ .fn()
128
+ .mockImplementation((options) => new MockCloudClient(options)) });
129
+ });
130
+ describe("Device Management", () => {
131
+ let neurosity;
132
+ let cloudClient;
133
+ const testDeviceId = "test-device-id";
134
+ beforeEach(() => {
135
+ neurosity = new Neurosity({
136
+ deviceId: testDeviceId,
137
+ emulator: true
138
+ });
139
+ cloudClient = neurosity.cloudClient;
140
+ });
141
+ describe("Device Information", () => {
142
+ it("should get device info", () => __awaiter(void 0, void 0, void 0, function* () {
143
+ const info = yield neurosity.getInfo();
144
+ expect(info).toBeDefined();
145
+ expect(info.deviceId).toBe(testDeviceId);
146
+ expect(info.manufacturer).toBe("Neurosity");
147
+ expect(info.model).toBe("Crown");
148
+ }));
149
+ it("should get device status", () => __awaiter(void 0, void 0, void 0, function* () {
150
+ const status = yield firstValueFrom(neurosity.status());
151
+ expect(status).toBeDefined();
152
+ expect(status.state).toBe(STATUS.ONLINE);
153
+ expect(status.charging).toBe(false);
154
+ expect(status.battery).toBe(100);
155
+ expect(status.sleepMode).toBe(false);
156
+ expect(status.ssid).toBe("test-network");
157
+ }));
158
+ it("should get signal quality", () => __awaiter(void 0, void 0, void 0, function* () {
159
+ const quality = yield firstValueFrom(neurosity.signalQuality());
160
+ expect(quality).toBeDefined();
161
+ expect(quality.CH1.status).toBe("good");
162
+ expect(quality.CH1.standardDeviation).toBe(0.1);
163
+ }));
164
+ it("should get OS version", () => __awaiter(void 0, void 0, void 0, function* () {
165
+ const version = yield firstValueFrom(neurosity.osVersion());
166
+ expect(version).toBeDefined();
167
+ expect(version).toBe("1.0.0");
168
+ }));
169
+ it("should handle device status changes", (done) => {
170
+ const newStatus = {
171
+ state: STATUS.ONLINE,
172
+ charging: true,
173
+ battery: 80,
174
+ sleepMode: true,
175
+ sleepModeReason: SLEEP_MODE_REASON.CHARGING
176
+ };
177
+ neurosity.status().subscribe({
178
+ next: (status) => {
179
+ if (status.charging === true) {
180
+ expect(status.battery).toBe(80);
181
+ expect(status.sleepMode).toBe(true);
182
+ expect(status.sleepModeReason).toBe(SLEEP_MODE_REASON.CHARGING);
183
+ done();
184
+ }
185
+ },
186
+ error: done
187
+ });
188
+ cloudClient._updateDeviceStatus(newStatus);
189
+ });
190
+ it("should handle signal quality changes", (done) => {
191
+ const newQuality = {
192
+ CH1: {
193
+ standardDeviation: 0.5,
194
+ status: "bad"
195
+ },
196
+ CH2: {
197
+ standardDeviation: 0.5,
198
+ status: "bad"
199
+ }
200
+ };
201
+ cloudClient._updateSignalQuality(newQuality);
202
+ neurosity
203
+ .signalQuality()
204
+ .pipe(take(1))
205
+ .subscribe({
206
+ next: (quality) => {
207
+ expect(quality.CH1.standardDeviation).toBe(0.5);
208
+ expect(quality.CH2.status).toBe("bad");
209
+ done();
210
+ },
211
+ error: done
212
+ });
213
+ }, 10000);
214
+ });
215
+ describe("Device Selection", () => {
216
+ it("should select device", () => __awaiter(void 0, void 0, void 0, function* () {
217
+ yield expect(neurosity.selectDevice((devices) => {
218
+ const device = devices.find((d) => d.deviceId === testDeviceId);
219
+ if (!device)
220
+ throw new Error("Device not found");
221
+ return device;
222
+ })).resolves.not.toThrow();
223
+ const deviceInfo = yield neurosity.getInfo();
224
+ expect(deviceInfo.deviceId).toBe(testDeviceId);
225
+ }));
226
+ it("should handle device selection failure", () => __awaiter(void 0, void 0, void 0, function* () {
227
+ yield expect(neurosity.selectDevice(() => {
228
+ throw new Error("Device not found");
229
+ })).rejects.toThrow("Device not found");
230
+ }));
231
+ it("should monitor device changes", (done) => {
232
+ neurosity
233
+ .onDeviceChange()
234
+ .pipe(take(1))
235
+ .subscribe({
236
+ next: (deviceInfo) => {
237
+ expect(deviceInfo).toBeDefined();
238
+ expect(deviceInfo.deviceId).toBe(testDeviceId);
239
+ done();
240
+ },
241
+ error: done
242
+ });
243
+ });
244
+ });
245
+ describe("Device Management", () => {
246
+ it("should add device", () => __awaiter(void 0, void 0, void 0, function* () {
247
+ yield expect(neurosity.addDevice(testDeviceId)).resolves.not.toThrow();
248
+ }));
249
+ it("should handle invalid device ID when adding", () => __awaiter(void 0, void 0, void 0, function* () {
250
+ yield expect(neurosity.addDevice("invalid-id")).rejects.toThrow("Invalid device ID");
251
+ }));
252
+ it("should remove device", () => __awaiter(void 0, void 0, void 0, function* () {
253
+ yield expect(neurosity.removeDevice(testDeviceId)).resolves.not.toThrow();
254
+ }));
255
+ it("should handle invalid device ID when removing", () => __awaiter(void 0, void 0, void 0, function* () {
256
+ yield expect(neurosity.removeDevice("invalid-id")).rejects.toThrow("Invalid device ID");
257
+ }));
258
+ it("should transfer device", () => __awaiter(void 0, void 0, void 0, function* () {
259
+ const transferOptions = {
260
+ deviceId: testDeviceId,
261
+ recipientsUserId: "target-user-id"
262
+ };
263
+ yield expect(neurosity.transferDevice(transferOptions)).resolves.not.toThrow();
264
+ }));
265
+ it("should handle invalid transfer parameters", () => __awaiter(void 0, void 0, void 0, function* () {
266
+ const invalidOptions = {
267
+ deviceId: "invalid-id",
268
+ recipientsUserId: "invalid-user"
269
+ };
270
+ yield expect(neurosity.transferDevice(invalidOptions)).rejects.toThrow("Invalid transfer parameters");
271
+ }));
272
+ it("should monitor user devices changes", () => __awaiter(void 0, void 0, void 0, function* () {
273
+ const devices = yield firstValueFrom(neurosity.onUserDevicesChange());
274
+ expect(devices).toBeDefined();
275
+ expect(Array.isArray(devices)).toBe(true);
276
+ expect(devices[0].deviceId).toBe(testDeviceId);
277
+ }));
278
+ });
279
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,160 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Neurosity } from "../Neurosity";
11
+ import { of, ReplaySubject } from "rxjs";
12
+ import { STATUS } from "../types/status";
13
+ import { strongClick100, strongBuzz100, alert750ms, doubleClick100, sharpClick100 } from "../utils/hapticEffects";
14
+ import { MODEL_VERSION_2, HAPTIC_P7, HAPTIC_P8 } from "../utils/platform";
15
+ // Mock CloudClient
16
+ jest.mock("../api", () => {
17
+ const originalModule = jest.requireActual("../api");
18
+ class MockCloudClient {
19
+ constructor(options) {
20
+ this.user = null;
21
+ this.userClaims = { scopes: ["brainwaves"] };
22
+ this.subscriptionManager = {
23
+ add: jest.fn(),
24
+ remove: jest.fn(),
25
+ removeAll: jest.fn()
26
+ };
27
+ this._selectedDevice = new ReplaySubject(1);
28
+ this._deviceInfo = {
29
+ deviceId: "test-device-id",
30
+ deviceNickname: "Test Device",
31
+ channelNames: ["CH1", "CH2"],
32
+ channels: 2,
33
+ samplingRate: 250,
34
+ manufacturer: "Neurosity",
35
+ model: "Crown",
36
+ modelName: "Crown",
37
+ modelVersion: MODEL_VERSION_2,
38
+ apiVersion: "1.0.0",
39
+ osVersion: "1.0.0",
40
+ emulator: false
41
+ };
42
+ this.getInfo = jest.fn().mockResolvedValue(this._deviceInfo);
43
+ this.selectDevice = jest
44
+ .fn()
45
+ .mockImplementation((selector) => __awaiter(this, void 0, void 0, function* () {
46
+ const selectedDevice = selector([this._deviceInfo]);
47
+ this._selectedDevice.next(selectedDevice);
48
+ return selectedDevice;
49
+ }));
50
+ this.getSelectedDevice = jest.fn().mockResolvedValue(this._deviceInfo);
51
+ this.didSelectDevice = jest.fn().mockResolvedValue(true);
52
+ this.onDeviceChange = jest.fn().mockReturnValue(of({
53
+ state: STATUS.ONLINE,
54
+ charging: false,
55
+ battery: 100,
56
+ sleepMode: false,
57
+ updatingProgress: 0,
58
+ bluetoothEnabled: false,
59
+ sleepModeReason: null,
60
+ lastHeartbeat: Date.now(),
61
+ ssid: "test-network"
62
+ }));
63
+ this.osVersion = jest.fn().mockReturnValue(of("1.0.0"));
64
+ this.status = jest.fn().mockReturnValue(of({
65
+ state: STATUS.ONLINE,
66
+ charging: false,
67
+ battery: 100,
68
+ sleepMode: false,
69
+ updatingProgress: 0,
70
+ bluetoothEnabled: false,
71
+ sleepModeReason: null,
72
+ lastHeartbeat: Date.now(),
73
+ ssid: "test-network"
74
+ }));
75
+ this.dispatchAction = jest.fn().mockImplementation((payload) => __awaiter(this, void 0, void 0, function* () {
76
+ const { effects } = payload.message;
77
+ for (const location of Object.keys(effects)) {
78
+ if (![HAPTIC_P7, HAPTIC_P8].includes(location)) {
79
+ throw new Error(`Invalid haptic location: ${location}`);
80
+ }
81
+ const effectList = effects[location];
82
+ if (!Array.isArray(effectList)) {
83
+ throw new Error("Effects must be an array");
84
+ }
85
+ if (effectList.length > 7) {
86
+ throw new Error("Maximum items in array is 7");
87
+ }
88
+ for (const effect of effectList) {
89
+ const validEffects = [
90
+ strongClick100,
91
+ strongBuzz100,
92
+ alert750ms,
93
+ doubleClick100,
94
+ sharpClick100
95
+ ];
96
+ if (!validEffects.includes(effect)) {
97
+ throw new Error("Invalid haptic effect");
98
+ }
99
+ }
100
+ }
101
+ return Promise.resolve();
102
+ }));
103
+ this.options = options;
104
+ this._selectedDevice.next(this._deviceInfo);
105
+ }
106
+ }
107
+ return Object.assign(Object.assign({}, originalModule), { CloudClient: jest
108
+ .fn()
109
+ .mockImplementation((options) => new MockCloudClient(options)) });
110
+ });
111
+ describe("Haptics", () => {
112
+ let neurosity;
113
+ const testDeviceId = "test-device-id";
114
+ beforeEach(() => {
115
+ neurosity = new Neurosity({
116
+ deviceId: testDeviceId,
117
+ emulator: true
118
+ });
119
+ });
120
+ describe("Haptic Effects", () => {
121
+ it("should trigger strong click haptic effect on P7", () => __awaiter(void 0, void 0, void 0, function* () {
122
+ yield expect(neurosity.haptics({
123
+ [HAPTIC_P7]: [strongClick100]
124
+ })).resolves.not.toThrow();
125
+ }));
126
+ it("should trigger strong buzz haptic effect on P8", () => __awaiter(void 0, void 0, void 0, function* () {
127
+ yield expect(neurosity.haptics({
128
+ [HAPTIC_P8]: [strongBuzz100]
129
+ })).resolves.not.toThrow();
130
+ }));
131
+ it("should trigger effects on both motors", () => __awaiter(void 0, void 0, void 0, function* () {
132
+ yield expect(neurosity.haptics({
133
+ [HAPTIC_P7]: [alert750ms],
134
+ [HAPTIC_P8]: [doubleClick100]
135
+ })).resolves.not.toThrow();
136
+ }));
137
+ it("should trigger multiple effects on one motor", () => __awaiter(void 0, void 0, void 0, function* () {
138
+ yield expect(neurosity.haptics({
139
+ [HAPTIC_P7]: [sharpClick100, strongClick100, doubleClick100]
140
+ })).resolves.not.toThrow();
141
+ }));
142
+ });
143
+ describe("Error Handling", () => {
144
+ it("should handle invalid haptic location", () => __awaiter(void 0, void 0, void 0, function* () {
145
+ yield expect(neurosity.haptics({
146
+ invalid: [strongClick100]
147
+ })).rejects.toThrow(/location not supported/);
148
+ }));
149
+ it("should handle invalid haptic effect", () => __awaiter(void 0, void 0, void 0, function* () {
150
+ yield expect(neurosity.haptics({
151
+ [HAPTIC_P7]: ["invalid"]
152
+ })).rejects.toThrow("Invalid haptic effect");
153
+ }));
154
+ it("should handle too many effects", () => __awaiter(void 0, void 0, void 0, function* () {
155
+ yield expect(neurosity.haptics({
156
+ [HAPTIC_P7]: Array(8).fill(strongClick100)
157
+ })).rejects.toThrow(/Maximum items in array is 7/);
158
+ }));
159
+ });
160
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,176 @@
1
+ import { Neurosity } from "../Neurosity";
2
+ import { BehaviorSubject, of, throwError } from "rxjs";
3
+ import { take } from "rxjs/operators";
4
+ import { STATUS } from "../types/status";
5
+ // Mock CloudClient
6
+ jest.mock("../api/index", () => {
7
+ const mockSubscriptions = new Map();
8
+ const mockListeners = new Map();
9
+ let subscriptionId = 0;
10
+ const mockCloudClient = {
11
+ login: jest.fn(),
12
+ logout: jest.fn(),
13
+ onAuthStateChanged: jest.fn(),
14
+ onDeviceChange: jest.fn(),
15
+ status: jest.fn(),
16
+ metrics: {
17
+ on: jest.fn((subscription, callback) => {
18
+ const listener = (value) => callback(value);
19
+ const key = `${subscription.id}-${subscription.metric}-${subscription.labels.join(",")}`;
20
+ if (!mockListeners.has(key)) {
21
+ mockListeners.set(key, []);
22
+ }
23
+ mockListeners.get(key).push(listener);
24
+ return listener;
25
+ }),
26
+ subscribe: jest.fn((subscription) => {
27
+ const id = `subscription-${subscriptionId++}`;
28
+ const sub = Object.assign({ id }, subscription);
29
+ mockSubscriptions.set(id, sub);
30
+ return sub;
31
+ }),
32
+ unsubscribe: jest.fn((subscription, listener) => {
33
+ const key = `${subscription.id}-${subscription.metric}-${subscription.labels.join(",")}`;
34
+ const listeners = mockListeners.get(key) || [];
35
+ const index = listeners.indexOf(listener);
36
+ if (index > -1) {
37
+ listeners.splice(index, 1);
38
+ }
39
+ mockSubscriptions.delete(subscription.id);
40
+ })
41
+ },
42
+ osVersion: jest.fn(),
43
+ userClaims: {
44
+ scopes: ["focus", "kinesis"]
45
+ }
46
+ };
47
+ return {
48
+ CloudClient: jest.fn().mockImplementation(() => mockCloudClient)
49
+ };
50
+ });
51
+ const testDeviceId = "mock-device-id";
52
+ describe("Metrics", () => {
53
+ let neurosity;
54
+ let mockFocusData;
55
+ let mockKinesisData;
56
+ let cloudClient;
57
+ beforeEach(() => {
58
+ neurosity = new Neurosity({
59
+ deviceId: testDeviceId,
60
+ emulator: true
61
+ });
62
+ cloudClient = neurosity.cloudClient;
63
+ // Mock device info
64
+ const mockDeviceInfo = {
65
+ deviceId: testDeviceId,
66
+ channelNames: ["CP3", "C3", "F5", "PO3", "PO4", "F6", "C4", "CP4"],
67
+ samplingRate: 256,
68
+ modelName: "crown",
69
+ modelVersion: "v3"
70
+ };
71
+ // Mock device status
72
+ const mockDeviceStatus = {
73
+ state: STATUS.ONLINE,
74
+ charging: false,
75
+ battery: 100,
76
+ sleepMode: false,
77
+ sleepModeReason: null,
78
+ lastHeartbeat: Date.now(),
79
+ ssid: "test-network"
80
+ };
81
+ // Setup mock data
82
+ mockFocusData = new BehaviorSubject({
83
+ probability: 0.85,
84
+ label: "focus",
85
+ metric: "awareness",
86
+ timestamp: Date.now()
87
+ });
88
+ mockKinesisData = new BehaviorSubject({
89
+ probability: 0.75,
90
+ label: "leftArm",
91
+ metric: "kinesis",
92
+ timestamp: Date.now()
93
+ });
94
+ // Mock cloud client methods
95
+ cloudClient.onDeviceChange.mockReturnValue(of(mockDeviceInfo));
96
+ cloudClient.status.mockReturnValue(of(mockDeviceStatus));
97
+ cloudClient.osVersion.mockReturnValue(of("16.0.0"));
98
+ // Setup metrics subscription behavior
99
+ cloudClient.metrics.subscribe.mockImplementation((subscription) => {
100
+ return Object.assign({ id: "test-id" }, subscription);
101
+ });
102
+ cloudClient.metrics.on.mockImplementation((subscription, callback) => {
103
+ if (subscription.metric === "awareness") {
104
+ const sub = mockFocusData.subscribe((value) => callback(value));
105
+ return () => sub.unsubscribe();
106
+ }
107
+ else if (subscription.metric === "kinesis") {
108
+ const sub = mockKinesisData.subscribe((value) => callback(value));
109
+ return () => sub.unsubscribe();
110
+ }
111
+ return () => { };
112
+ });
113
+ });
114
+ describe("Focus", () => {
115
+ it("should get focus metrics", (done) => {
116
+ neurosity
117
+ .focus()
118
+ .pipe(take(1))
119
+ .subscribe({
120
+ next: (focus) => {
121
+ expect(focus).toBeDefined();
122
+ expect(focus.probability).toBe(0.85);
123
+ expect(focus.label).toBe("focus");
124
+ expect(focus.metric).toBe("awareness");
125
+ expect(focus.timestamp).toBeDefined();
126
+ done();
127
+ },
128
+ error: done
129
+ });
130
+ });
131
+ });
132
+ describe("Kinesis", () => {
133
+ it("should get kinesis metrics", (done) => {
134
+ neurosity
135
+ .kinesis("leftArm")
136
+ .pipe(take(1))
137
+ .subscribe({
138
+ next: (kinesis) => {
139
+ expect(kinesis).toBeDefined();
140
+ expect(kinesis.probability).toBe(0.75);
141
+ expect(kinesis.label).toBe("leftArm");
142
+ expect(kinesis.metric).toBe("kinesis");
143
+ expect(kinesis.timestamp).toBeDefined();
144
+ done();
145
+ },
146
+ error: done
147
+ });
148
+ });
149
+ });
150
+ describe("Error Handling", () => {
151
+ it("should handle device offline state", (done) => {
152
+ // Mock device going offline
153
+ cloudClient.onDeviceChange.mockReturnValue(of({
154
+ deviceId: testDeviceId,
155
+ channelNames: ["CP3", "C3", "F5", "PO3", "PO4", "F6", "C4", "CP4"],
156
+ samplingRate: 256,
157
+ modelName: "crown",
158
+ modelVersion: "v3"
159
+ }));
160
+ cloudClient.status.mockReturnValue(throwError(() => new Error("Device is offline")));
161
+ neurosity
162
+ .focus()
163
+ .pipe(take(1))
164
+ .subscribe({
165
+ next: () => {
166
+ done(new Error("Should not emit when device is offline"));
167
+ },
168
+ error: (err) => {
169
+ expect(err).toBeDefined();
170
+ expect(err.message).toContain("offline");
171
+ done();
172
+ }
173
+ });
174
+ });
175
+ });
176
+ });
@@ -0,0 +1 @@
1
+ export {};