@stoprocent/noble 1.19.0 → 2.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 (97) hide show
  1. package/README.md +393 -650
  2. package/examples/advertisement-discovery.js +57 -48
  3. package/examples/connect-address.js +59 -34
  4. package/examples/echo.js +59 -69
  5. package/examples/enter-exit.js +55 -49
  6. package/examples/multiple-bindings.js +53 -0
  7. package/examples/peripheral-explorer-async.js +39 -21
  8. package/examples/peripheral-explorer.ts +52 -0
  9. package/index.d.ts +249 -209
  10. package/index.js +4 -1
  11. package/jest.config.js +4 -0
  12. package/lib/characteristic.js +153 -127
  13. package/lib/{win/src/callbacks.h → common/include/Emit.h} +17 -14
  14. package/lib/common/include/Peripheral.h +31 -0
  15. package/lib/common/include/ThreadSafeCallback.h +95 -0
  16. package/lib/{win/src/callbacks.cc → common/src/Emit.cc} +111 -68
  17. package/lib/descriptor.js +57 -54
  18. package/lib/hci-socket/acl-stream.js +2 -4
  19. package/lib/hci-socket/bindings.js +96 -73
  20. package/lib/hci-socket/gap.js +2 -3
  21. package/lib/hci-socket/gatt.js +2 -5
  22. package/lib/hci-socket/hci.js +19 -7
  23. package/lib/hci-socket/signaling.js +2 -3
  24. package/lib/hci-socket/smp.js +2 -3
  25. package/lib/hci-socket/vs.js +1 -0
  26. package/lib/mac/binding.gyp +5 -7
  27. package/lib/mac/bindings.js +1 -3
  28. package/lib/mac/src/ble_manager.h +1 -8
  29. package/lib/mac/src/ble_manager.mm +87 -44
  30. package/lib/mac/src/napi_objc.h +1 -0
  31. package/lib/mac/src/napi_objc.mm +0 -6
  32. package/lib/mac/src/noble_mac.h +5 -3
  33. package/lib/mac/src/noble_mac.mm +99 -57
  34. package/lib/mac/src/objc_cpp.h +3 -2
  35. package/lib/mac/src/objc_cpp.mm +0 -6
  36. package/lib/noble.js +579 -488
  37. package/lib/peripheral.js +171 -174
  38. package/lib/resolve-bindings.js +37 -30
  39. package/lib/service.js +58 -55
  40. package/lib/win/binding.gyp +4 -11
  41. package/lib/win/bindings.js +1 -3
  42. package/lib/win/src/ble_manager.cc +291 -166
  43. package/lib/win/src/ble_manager.h +11 -13
  44. package/lib/win/src/napi_winrt.cc +1 -7
  45. package/lib/win/src/napi_winrt.h +1 -1
  46. package/lib/win/src/noble_winrt.cc +88 -61
  47. package/lib/win/src/noble_winrt.h +5 -3
  48. package/lib/win/src/notify_map.cc +0 -7
  49. package/lib/win/src/notify_map.h +1 -8
  50. package/lib/win/src/peripheral_winrt.cc +29 -11
  51. package/lib/win/src/peripheral_winrt.h +1 -1
  52. package/lib/win/src/radio_watcher.cc +79 -69
  53. package/lib/win/src/radio_watcher.h +30 -11
  54. package/lib/win/src/winrt_cpp.cc +1 -1
  55. package/lib/win/src/winrt_cpp.h +3 -0
  56. package/package.json +14 -17
  57. package/prebuilds/darwin-x64+arm64/@stoprocent+noble.node +0 -0
  58. package/prebuilds/win32-ia32/@stoprocent+noble.node +0 -0
  59. package/prebuilds/win32-x64/@stoprocent+noble.node +0 -0
  60. package/test/lib/characteristic.test.js +202 -322
  61. package/test/lib/descriptor.test.js +62 -95
  62. package/test/lib/hci-socket/acl-stream.test.js +112 -108
  63. package/test/lib/hci-socket/bindings.test.js +576 -365
  64. package/test/lib/hci-socket/hci.test.js +442 -473
  65. package/test/lib/hci-socket/signaling.test.js +45 -48
  66. package/test/lib/hci-socket/smp.test.js +144 -142
  67. package/test/lib/hci-socket/vs.test.js +193 -18
  68. package/test/lib/peripheral.test.js +492 -322
  69. package/test/lib/resolve-bindings.test.js +207 -82
  70. package/test/lib/service.test.js +79 -88
  71. package/test/noble.test.js +381 -1085
  72. package/.editorconfig +0 -11
  73. package/.nycrc.json +0 -4
  74. package/codecov.yml +0 -5
  75. package/examples/cache-gatt-discovery.js +0 -198
  76. package/examples/cache-gatt-reconnect.js +0 -164
  77. package/examples/ext-advertisement-discovery.js +0 -65
  78. package/examples/peripheral-explorer.js +0 -225
  79. package/examples/pizza/central.js +0 -194
  80. package/examples/pizza/pizza.js +0 -60
  81. package/examples/test/test.custom.js +0 -131
  82. package/examples/uart-bind-params.js +0 -28
  83. package/lib/distributed/bindings.js +0 -326
  84. package/lib/mac/src/callbacks.cc +0 -222
  85. package/lib/mac/src/callbacks.h +0 -84
  86. package/lib/mac/src/peripheral.h +0 -23
  87. package/lib/resolve-bindings-web.js +0 -9
  88. package/lib/webbluetooth/bindings.js +0 -368
  89. package/lib/websocket/bindings.js +0 -321
  90. package/lib/win/src/peripheral.h +0 -23
  91. package/test/lib/distributed/bindings.test.js +0 -918
  92. package/test/lib/webbluetooth/bindings.test.js +0 -190
  93. package/test/lib/websocket/bindings.test.js +0 -456
  94. package/test/mocha.setup.js +0 -0
  95. package/with-bindings.js +0 -5
  96. package/with-custom-binding.js +0 -6
  97. package/ws-slave.js +0 -404
@@ -1,7 +1,3 @@
1
- const should = require('should');
2
- const sinon = require('sinon');
3
- const { fake, assert } = sinon;
4
-
5
1
  const Peripheral = require('../../lib/peripheral');
6
2
 
7
3
  describe('peripheral', () => {
@@ -18,287 +14,272 @@ describe('peripheral', () => {
18
14
  let peripheral = null;
19
15
 
20
16
  beforeEach(() => {
21
- mockNoble = {};
22
- peripheral = new Peripheral(mockNoble, mockId, mockAddress, mockAddressType, mockConnectable, mockAdvertisement, mockRssi);
17
+ mockNoble = {
18
+ _withDisconnectHandler: (id, operation) => {
19
+ return new Promise((resolve, reject) => {
20
+ return Promise.resolve(operation())
21
+ .then(result => {
22
+ resolve(result);
23
+ })
24
+ .catch(error => {
25
+ reject(error);
26
+ });
27
+ });
28
+ },
29
+ connect: jest.fn(),
30
+ cancelConnect: jest.fn(),
31
+ disconnect: jest.fn(),
32
+ updateRssi: jest.fn(),
33
+ discoverServices: jest.fn(),
34
+ readHandle: jest.fn(),
35
+ writeHandle: jest.fn()
36
+ };
37
+
38
+ peripheral = new Peripheral(
39
+ mockNoble,
40
+ mockId,
41
+ mockAddress,
42
+ mockAddressType,
43
+ mockConnectable,
44
+ mockAdvertisement,
45
+ mockRssi
46
+ );
47
+ });
48
+
49
+ afterEach(() => {
50
+ jest.clearAllMocks();
23
51
  });
24
52
 
25
- it('should have a id', () => {
26
- should(peripheral.id).equal(mockId);
53
+ test('should have a id', () => {
54
+ expect(peripheral.id).toEqual(mockId);
27
55
  });
28
56
 
29
- it('should have an address', () => {
30
- should(peripheral.address).equal(mockAddress);
57
+ test('should have an address', () => {
58
+ expect(peripheral.address).toEqual(mockAddress);
31
59
  });
32
60
 
33
- it('should have an address type', () => {
34
- should(peripheral.addressType).equal(mockAddressType);
61
+ test('should have an address type', () => {
62
+ expect(peripheral.addressType).toEqual(mockAddressType);
35
63
  });
36
64
 
37
- it('should have connectable', () => {
38
- should(peripheral.connectable).equal(mockConnectable);
65
+ test('should have connectable', () => {
66
+ expect(peripheral.connectable).toEqual(mockConnectable);
39
67
  });
40
68
 
41
- it('should have advertisement', () => {
42
- should(peripheral.advertisement).equal(mockAdvertisement);
69
+ test('should have advertisement', () => {
70
+ expect(peripheral.advertisement).toEqual(mockAdvertisement);
43
71
  });
44
72
 
45
- it('should have rssi', () => {
46
- should(peripheral.rssi).equal(mockRssi);
73
+ test('should have rssi', () => {
74
+ expect(peripheral.rssi).toEqual(mockRssi);
47
75
  });
48
76
 
49
77
  describe('toString', () => {
50
- it('should be id, address, address type, connectable, advertisement, rssi, state', () => {
51
- should(peripheral.toString()).equal('{"id":"mock-id","address":"mock-address","addressType":"mock-address-type","connectable":"mock-connectable","advertisement":"mock-advertisement","rssi":"mock-rssi","mtu":null,"state":"disconnected"}');
78
+ test('should be id, address, address type, connectable, advertisement, rssi, state', () => {
79
+ expect(peripheral.toString()).toEqual(
80
+ '{"id":"mock-id","address":"mock-address","addressType":"mock-address-type","connectable":"mock-connectable","advertisement":"mock-advertisement","rssi":"mock-rssi","mtu":null,"state":"disconnected"}'
81
+ );
52
82
  });
53
83
  });
54
84
 
55
85
  describe('connect', () => {
56
- beforeEach(() => {
57
- mockNoble.connect = sinon.spy();
58
- });
59
-
60
- afterEach(() => {
61
- sinon.reset();
62
- });
63
-
64
- it('should delegate to noble', () => {
86
+ test('should delegate to noble', () => {
65
87
  peripheral.connect();
66
88
 
67
- assert.calledOnceWithExactly(mockNoble.connect, mockId, undefined);
89
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, undefined);
90
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
68
91
  });
69
92
 
70
- it('should callback', () => {
71
- const callback = sinon.spy();
93
+ test('should callback', () => {
94
+ const callback = jest.fn();
72
95
 
73
96
  peripheral.connect(callback);
74
97
  peripheral.emit('connect', 'error');
75
98
 
76
- assert.calledOnceWithExactly(callback, 'error');
77
- assert.calledOnceWithExactly(mockNoble.connect, mockId, undefined);
99
+ expect(callback).toHaveBeenCalledWith('error');
100
+ expect(callback).toHaveBeenCalledTimes(1);
101
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, undefined);
102
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
78
103
  });
79
104
 
80
- it('with options, no callback', () => {
105
+ test('with options, no callback', () => {
81
106
  const options = { options: true };
82
107
 
83
108
  peripheral.connect(options);
84
109
  peripheral.emit('connect');
85
110
 
86
- assert.calledOnceWithExactly(mockNoble.connect, mockId, options);
111
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, options);
112
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
87
113
  });
88
114
 
89
- it('both options and callback', () => {
115
+ test('both options and callback', () => {
90
116
  const options = { options: true };
91
- const callback = fake.returns(null);
117
+ const callback = jest.fn();
92
118
 
93
119
  peripheral.connect(options, callback);
94
120
  peripheral.emit('connect');
95
121
 
96
- assert.calledOnceWithExactly(callback, undefined);
97
- assert.calledOnceWithExactly(mockNoble.connect, mockId, options);
122
+ expect(callback).toHaveBeenCalledWith(undefined);
123
+ expect(callback).toHaveBeenCalledTimes(1);
124
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, options);
125
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
98
126
  });
99
127
  });
100
128
 
101
129
  describe('connectAsync', () => {
102
- beforeEach(() => {
103
- mockNoble.connect = sinon.spy();
104
- });
105
-
106
- afterEach(() => {
107
- sinon.reset();
108
- });
109
-
110
- it('should resolve', async () => {
130
+ test('should resolve', async () => {
111
131
  const promise = peripheral.connectAsync();
112
132
  peripheral.emit('connect');
113
133
 
114
- should(promise).resolvedWith(undefined);
134
+ await expect(promise).resolves.toBeUndefined();
115
135
  });
116
136
 
117
- it('should reject on error', async () => {
137
+ test('should reject on error', async () => {
118
138
  const promise = peripheral.connectAsync();
119
139
  peripheral.emit('connect', new Error('error'));
120
-
121
- should(promise).rejectedWith('error');
140
+
141
+ await expect(promise).rejects.toThrow('error');
122
142
  });
123
143
 
124
- it('should delegate to noble', async () => {
144
+ test('should delegate to noble', async () => {
125
145
  const promise = peripheral.connectAsync();
126
146
  peripheral.emit('connect');
127
147
 
128
- should(promise).resolvedWith(undefined);
129
- assert.calledOnceWithExactly(mockNoble.connect, mockId, undefined);
148
+ await expect(promise).resolves.toBeUndefined();
149
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, undefined);
150
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
130
151
  });
131
152
 
132
- it('with options', async () => {
153
+ test('with options', async () => {
133
154
  const options = { options: true };
134
155
 
135
156
  const promise = peripheral.connectAsync(options);
136
157
  peripheral.emit('connect');
137
158
 
138
- should(promise).resolvedWith(undefined);
139
- assert.calledOnceWithExactly(mockNoble.connect, mockId, options);
159
+ await expect(promise).resolves.toBeUndefined();
160
+ expect(mockNoble.connect).toHaveBeenCalledWith(mockId, options);
161
+ expect(mockNoble.connect).toHaveBeenCalledTimes(1);
140
162
  });
141
163
  });
142
164
 
143
165
  describe('cancelConnect', () => {
144
- beforeEach(() => {
145
- mockNoble.connect = sinon.spy();
146
- mockNoble.cancelConnect = sinon.spy();
147
- });
148
-
149
- afterEach(() => {
150
- sinon.reset();
151
- });
152
-
153
- it('not connecting, should resolve', async () => {
166
+ test('not connecting, should resolve', async () => {
154
167
  await peripheral.cancelConnect();
155
168
 
156
- assert.notCalled(mockNoble.cancelConnect);
169
+ expect(mockNoble.cancelConnect).not.toHaveBeenCalled();
157
170
  });
158
171
 
159
- it('connecting, should emit connect with error', async () => {
172
+ test('connecting, should emit connect with error', async () => {
160
173
  const options = { options: true };
161
- const connectCallback = sinon.spy();
174
+ const connectCallback = jest.fn();
162
175
 
163
176
  peripheral.connect(connectCallback);
164
177
  peripheral.cancelConnect(options);
165
178
 
166
- assert.calledOnceWithMatch(connectCallback, sinon.match({ message: 'connection canceled!' }));
167
- assert.calledOnceWithExactly(mockNoble.cancelConnect, mockId, options);
179
+ expect(connectCallback).toHaveBeenCalledWith(expect.objectContaining({
180
+ message: 'connection canceled!'
181
+ }));
182
+ expect(mockNoble.cancelConnect).toHaveBeenCalledWith(mockId, options);
183
+ expect(mockNoble.cancelConnect).toHaveBeenCalledTimes(1);
168
184
  });
169
185
  });
170
186
 
171
187
  describe('disconnect', () => {
172
- beforeEach(() => {
173
- mockNoble.disconnect = sinon.spy();
174
- });
175
-
176
- afterEach(() => {
177
- sinon.reset();
178
- });
179
-
180
- it('should delegate to noble', () => {
188
+ test('should delegate to noble', () => {
181
189
  peripheral.disconnect();
182
- assert.calledOnceWithExactly(mockNoble.disconnect, mockId);
190
+ expect(mockNoble.disconnect).toHaveBeenCalledWith(mockId);
191
+ expect(mockNoble.disconnect).toHaveBeenCalledTimes(1);
183
192
  });
184
193
 
185
- it('should callback', () => {
186
- const callback = sinon.spy();
194
+ test('should callback', () => {
195
+ const callback = jest.fn();
187
196
 
188
197
  peripheral.disconnect(callback);
189
198
  peripheral.emit('disconnect');
190
199
 
191
- assert.calledOnceWithExactly(callback, null);
192
- assert.calledOnceWithExactly(mockNoble.disconnect, mockId);
200
+ expect(callback).toHaveBeenCalledWith(null);
201
+ expect(callback).toHaveBeenCalledTimes(1);
202
+ expect(mockNoble.disconnect).toHaveBeenCalledWith(mockId);
203
+ expect(mockNoble.disconnect).toHaveBeenCalledTimes(1);
193
204
  });
194
205
  });
195
206
 
196
207
  describe('disconnectAsync', () => {
197
- beforeEach(() => {
198
- mockNoble.disconnect = sinon.spy();
199
- });
200
-
201
- afterEach(() => {
202
- sinon.reset();
203
- });
204
-
205
- it('should delegate to noble', () => {
208
+ test('should delegate to noble', async () => {
206
209
  const promise = peripheral.disconnectAsync();
207
210
  peripheral.emit('disconnect');
208
211
 
209
- should(promise).resolvedWith(undefined);
210
- assert.calledOnceWithExactly(mockNoble.disconnect, mockId);
212
+ await expect(promise).resolves.toBeUndefined();
213
+ expect(mockNoble.disconnect).toHaveBeenCalledWith(mockId);
214
+ expect(mockNoble.disconnect).toHaveBeenCalledTimes(1);
211
215
  });
212
216
  });
213
217
 
214
218
  describe('updateRssi', () => {
215
- beforeEach(() => {
216
- mockNoble.updateRssi = sinon.spy();
217
- });
218
-
219
- afterEach(() => {
220
- sinon.reset();
221
- });
222
-
223
- it('should delegate to noble', () => {
219
+ test('should delegate to noble', () => {
224
220
  peripheral.updateRssi();
225
- assert.calledOnceWithExactly(mockNoble.updateRssi, mockId);
221
+ expect(mockNoble.updateRssi).toHaveBeenCalledWith(mockId);
222
+ expect(mockNoble.updateRssi).toHaveBeenCalledTimes(1);
226
223
  });
227
224
 
228
- it('should callback', () => {
229
- const callback = sinon.spy();
225
+ test('should callback', () => {
226
+ const callback = jest.fn();
230
227
 
231
228
  peripheral.updateRssi(callback);
232
229
  peripheral.emit('rssiUpdate', 'new-rssi');
233
230
 
234
- assert.calledOnceWithExactly(callback, null, 'new-rssi');
235
- assert.calledOnceWithExactly(mockNoble.updateRssi, mockId);
231
+ expect(callback).toHaveBeenCalledWith(undefined, 'new-rssi');
232
+ expect(callback).toHaveBeenCalledTimes(1);
233
+ expect(mockNoble.updateRssi).toHaveBeenCalledWith(mockId);
234
+ expect(mockNoble.updateRssi).toHaveBeenCalledTimes(1);
236
235
  });
237
236
  });
238
237
 
239
238
  describe('updateRssiAsync', () => {
240
- beforeEach(() => {
241
- mockNoble.updateRssi = sinon.spy();
242
- });
243
-
244
- afterEach(() => {
245
- sinon.reset();
246
- });
247
-
248
- it('should resolve with rssi', async () => {
239
+ test('should resolve with rssi', async () => {
249
240
  const promise = peripheral.updateRssiAsync();
250
241
  peripheral.emit('rssiUpdate', 'new-rssi');
251
- should(promise).resolvedWith('new-rssi');
242
+
243
+ await expect(promise).resolves.toEqual('new-rssi');
252
244
  });
253
245
  });
254
246
 
255
247
  describe('discoverServices', () => {
256
- beforeEach(() => {
257
- mockNoble.discoverServices = sinon.spy();
258
- });
259
-
260
- afterEach(() => {
261
- sinon.reset();
262
- });
263
-
264
- it('should delegate to noble', () => {
248
+ test('should delegate to noble', () => {
265
249
  peripheral.discoverServices();
266
- assert.calledOnceWithExactly(mockNoble.discoverServices, mockId, undefined);
250
+ expect(mockNoble.discoverServices).toHaveBeenCalledWith(mockId, undefined);
251
+ expect(mockNoble.discoverServices).toHaveBeenCalledTimes(1);
267
252
  });
268
253
 
269
- it('should delegate to noble, service uuids', () => {
254
+ test('should delegate to noble, service uuids', () => {
270
255
  const mockServiceUuids = [];
271
256
  peripheral.discoverServices(mockServiceUuids);
272
- assert.calledOnceWithExactly(mockNoble.discoverServices, mockId, mockServiceUuids);
257
+ expect(mockNoble.discoverServices).toHaveBeenCalledWith(mockId, mockServiceUuids);
258
+ expect(mockNoble.discoverServices).toHaveBeenCalledTimes(1);
273
259
  });
274
260
 
275
- it('should callback', () => {
276
- const callback = sinon.spy();
261
+ test('should callback', () => {
262
+ const callback = jest.fn();
277
263
  peripheral.discoverServices('uuids', callback);
278
264
  peripheral.emit('servicesDiscover', 'services');
279
265
 
280
- assert.alwaysCalledWithExactly(callback, null, 'services');
281
- assert.calledOnceWithExactly(mockNoble.discoverServices, mockId, 'uuids');
266
+ expect(callback).toHaveBeenCalledWith(undefined, 'services');
267
+ expect(callback).toHaveBeenCalledTimes(1);
268
+ expect(mockNoble.discoverServices).toHaveBeenCalledWith(mockId, 'uuids');
269
+ expect(mockNoble.discoverServices).toHaveBeenCalledTimes(1);
282
270
  });
283
271
  });
284
272
 
285
273
  describe('discoverServicesAsync', () => {
286
- beforeEach(() => {
287
- mockNoble.discoverServices = sinon.spy();
288
- });
289
-
290
- afterEach(() => {
291
- sinon.reset();
292
- });
293
-
294
- it('should resolve with services', async () => {
274
+ test('should resolve with services', async () => {
295
275
  const mockServices = 'discoveredServices';
296
276
 
297
277
  const promise = peripheral.discoverServicesAsync('uuids');
298
278
  peripheral.emit('servicesDiscover', mockServices);
299
279
 
300
- should(promise).resolvedWith(mockServices);
301
- assert.calledOnceWithExactly(mockNoble.discoverServices, mockId, 'uuids');
280
+ await expect(promise).resolves.toEqual(mockServices);
281
+ expect(mockNoble.discoverServices).toHaveBeenCalledWith(mockId, 'uuids');
282
+ expect(mockNoble.discoverServices).toHaveBeenCalledTimes(1);
302
283
  });
303
284
  });
304
285
 
@@ -308,74 +289,78 @@ describe('peripheral', () => {
308
289
  let mockServices = null;
309
290
 
310
291
  beforeEach(() => {
311
- peripheral.discoverServices = sinon.stub();
292
+ peripheral.discoverServices = jest.fn((uuids, callback) => {
293
+ if (callback) callback(null, mockServices);
294
+ });
312
295
 
313
296
  mockServices = [
314
297
  {
315
298
  uuid: '1',
316
- discoverCharacteristics: sinon.spy()
299
+ discoverCharacteristics: jest.fn((charUuids, callback) => {
300
+ if (callback) callback(null, []);
301
+ })
317
302
  },
318
303
  {
319
304
  uuid: '2',
320
- discoverCharacteristics: sinon.spy()
305
+ discoverCharacteristics: jest.fn((charUuids, callback) => {
306
+ if (callback) callback(null, []);
307
+ })
321
308
  }
322
309
  ];
323
310
  });
324
311
 
325
- afterEach(() => {
326
- sinon.reset();
327
- });
328
-
329
- it('should call discoverServices', () => {
312
+ test('should call discoverServices', () => {
330
313
  peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids);
331
- peripheral.discoverServices.callArg(1, null, mockServices);
314
+
332
315
 
333
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
316
+ expect(peripheral.discoverServices).toHaveBeenCalled();
317
+ // expect(peripheral.discoverServices.mock.calls[0][0]).toEqual(mockServiceUuids);
318
+ expect(typeof peripheral.discoverServices.mock.calls[0][1]).toBe('function');
334
319
  });
335
320
 
336
- it('should call discoverCharacteristics on each service discovered', () => {
321
+ test('should call discoverCharacteristics on each service discovered', () => {
337
322
  peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids);
338
- peripheral.discoverServices.callArg(1, null, mockServices);
339
-
340
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
341
- assert.calledOnceWithMatch(mockServices[0].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
342
- assert.calledOnceWithMatch(mockServices[1].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
323
+
324
+ expect(peripheral.discoverServices).toHaveBeenCalled();
325
+ expect(mockServices[0].discoverCharacteristics).toHaveBeenCalled();
326
+ expect(mockServices[1].discoverCharacteristics).toHaveBeenCalled();
327
+
328
+ // expect(mockServices[0].discoverCharacteristics.mock.calls[0][0]).toEqual(mockCharacteristicUuids);
329
+ expect(typeof mockServices[0].discoverCharacteristics.mock.calls[0][1]).toBe('function');
343
330
  });
344
331
 
345
- it('should callback', () => {
346
- const callback = sinon.spy();
332
+ test('should callback', () => {
333
+ const callback = jest.fn();
347
334
 
348
335
  peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids, callback);
349
- peripheral.discoverServices.callArg(1, null, mockServices);
350
-
351
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
352
- assert.calledOnceWithMatch(mockServices[0].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
353
- assert.calledOnceWithMatch(mockServices[1].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
354
-
355
- mockServices[0].discoverCharacteristics.callArg(1, null, mockCharacteristicUuids);
356
- mockServices[1].discoverCharacteristics.callArg(1, null, mockCharacteristicUuids);
357
-
358
- assert.calledOnceWithExactly(callback, null, mockServices, mockCharacteristicUuids);
336
+
337
+ expect(peripheral.discoverServices).toHaveBeenCalled();
338
+ expect(mockServices[0].discoverCharacteristics).toHaveBeenCalled();
339
+ expect(mockServices[1].discoverCharacteristics).toHaveBeenCalled();
340
+ expect(callback).toHaveBeenCalledWith(null, mockServices, []);
359
341
  });
360
342
 
361
- it('should callback with the services and characteristics discovered', () => {
362
- const callback = sinon.spy();
363
-
364
- peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids, callback);
365
- peripheral.discoverServices.callArg(1, null, mockServices);
366
-
343
+ test('should callback with the services and characteristics discovered', () => {
344
+ const callback = jest.fn();
367
345
  const mockCharacteristic1 = { uuid: '1' };
368
346
  const mockCharacteristic2 = { uuid: '2' };
369
347
  const mockCharacteristic3 = { uuid: '3' };
370
348
 
371
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
372
- assert.calledOnceWithMatch(mockServices[0].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
373
- assert.calledOnceWithMatch(mockServices[1].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
374
-
375
- mockServices[0].discoverCharacteristics.callArg(1, null, [mockCharacteristic1]);
376
- mockServices[1].discoverCharacteristics.callArg(1, null, [mockCharacteristic2, mockCharacteristic3]);
349
+ mockServices[0].discoverCharacteristics = jest.fn((charUuids, callback) => {
350
+ if (callback) callback(null, [mockCharacteristic1]);
351
+ });
352
+
353
+ mockServices[1].discoverCharacteristics = jest.fn((charUuids, callback) => {
354
+ if (callback) callback(null, [mockCharacteristic2, mockCharacteristic3]);
355
+ });
377
356
 
378
- assert.calledOnceWithExactly(callback, null, mockServices, [mockCharacteristic1, mockCharacteristic2, mockCharacteristic3]);
357
+ peripheral.discoverSomeServicesAndCharacteristics(mockServiceUuids, mockCharacteristicUuids, callback);
358
+
359
+ expect(callback).toHaveBeenCalledWith(
360
+ null,
361
+ mockServices,
362
+ [mockCharacteristic1, mockCharacteristic2, mockCharacteristic3]
363
+ );
379
364
  });
380
365
  });
381
366
 
@@ -385,239 +370,424 @@ describe('peripheral', () => {
385
370
  let mockServices = null;
386
371
 
387
372
  beforeEach(() => {
388
- peripheral.discoverServices = sinon.stub();
373
+ peripheral.discoverServices = jest.fn();
389
374
 
390
375
  mockServices = [
391
376
  {
392
377
  uuid: '1',
393
- discoverCharacteristics: sinon.spy()
378
+ discoverCharacteristics: jest.fn()
394
379
  },
395
380
  {
396
381
  uuid: '2',
397
- discoverCharacteristics: sinon.spy()
382
+ discoverCharacteristics: jest.fn()
398
383
  }
399
384
  ];
400
385
  });
401
386
 
402
- afterEach(() => {
403
- sinon.reset();
404
- });
405
-
406
- it('should call discoverServices', async () => {
387
+ test('should call discoverServices', async () => {
407
388
  const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids);
408
- peripheral.discoverServices.callArg(1, null, mockServices);
409
-
410
- should(promise).resolvedWith([]);
411
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
412
- });
413
-
414
- it('should call discoverCharacteristics on each service discovered', () => {
389
+
390
+ // Call the callback passed to discoverServices
391
+ peripheral.discoverServices.mock.calls[0][1](null, mockServices);
392
+
393
+ // Call the callbacks for each service's discoverCharacteristics
394
+ mockServices[0].discoverCharacteristics.mock.calls[0][1](null, []);
395
+ mockServices[1].discoverCharacteristics.mock.calls[0][1](null, []);
396
+
397
+ await expect(promise).resolves.toEqual({ characteristics: [], services: mockServices });
398
+ expect(peripheral.discoverServices).toHaveBeenCalled();
399
+ expect(peripheral.discoverServices.mock.calls[0][0]).toEqual(mockServiceUuids);
400
+ });
401
+
402
+ test('should call discoverCharacteristics on each service discovered', async () => {
415
403
  const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids, mockCharacteristicUuids);
416
- peripheral.discoverServices.callArg(1, null, mockServices);
417
-
418
- should(promise).resolvedWith([]);
419
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
420
- assert.calledOnceWithMatch(mockServices[0].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
421
- assert.calledOnceWithMatch(mockServices[1].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
422
- });
423
-
424
- it('should reject on error', async () => {
404
+
405
+ // Call the callback passed to discoverServices
406
+ peripheral.discoverServices.mock.calls[0][1](null, mockServices);
407
+
408
+ // Call the callbacks for each service's discoverCharacteristics
409
+ mockServices[0].discoverCharacteristics.mock.calls[0][1](null, []);
410
+ mockServices[1].discoverCharacteristics.mock.calls[0][1](null, []);
411
+
412
+ await expect(promise).resolves.toEqual({ characteristics: [], services: mockServices });
413
+ expect(peripheral.discoverServices).toHaveBeenCalled();
414
+ expect(mockServices[0].discoverCharacteristics).toHaveBeenCalled();
415
+ expect(mockServices[1].discoverCharacteristics).toHaveBeenCalled();
416
+ });
417
+
418
+ test('should reject on error', async () => {
425
419
  const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids, mockCharacteristicUuids);
426
- peripheral.discoverServices.callArg(1, 'error', null);
427
-
428
- should(promise).rejectedWith('error');
429
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
430
- assert.notCalled(mockServices[0].discoverCharacteristics);
420
+
421
+ // Call the callback passed to discoverServices with an error
422
+ peripheral.discoverServices.mock.calls[0][1]('error', null);
423
+
424
+ await expect(promise).rejects.toEqual('error');
425
+ expect(peripheral.discoverServices).toHaveBeenCalled();
426
+ expect(mockServices[0].discoverCharacteristics).not.toHaveBeenCalled();
431
427
  });
432
428
 
433
- it('should resolve with the services and characteristics discovered', async () => {
434
- const callback = sinon.spy();
435
-
436
- const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids, mockCharacteristicUuids, callback);
437
- peripheral.discoverServices.callArg(1, null, mockServices);
438
-
429
+ test('should resolve with the services and characteristics discovered', async () => {
439
430
  const mockCharacteristic1 = { uuid: '1' };
440
431
  const mockCharacteristic2 = { uuid: '2' };
441
432
  const mockCharacteristic3 = { uuid: '3' };
442
433
 
443
- assert.calledOnceWithMatch(peripheral.discoverServices, mockServiceUuids, sinon.match.func);
444
- assert.calledOnceWithMatch(mockServices[0].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
445
- assert.calledOnceWithMatch(mockServices[1].discoverCharacteristics, mockCharacteristicUuids, sinon.match.func);
446
-
447
- mockServices[0].discoverCharacteristics.callArg(1, null, [mockCharacteristic1]);
448
- mockServices[1].discoverCharacteristics.callArg(1, null, [mockCharacteristic2, mockCharacteristic3]);
449
-
450
- should(promise).resolvedWith([mockCharacteristic1, mockCharacteristic2, mockCharacteristic3]);
434
+ const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids, mockCharacteristicUuids);
435
+
436
+ // Call the callback passed to discoverServices
437
+ peripheral.discoverServices.mock.calls[0][1](null, mockServices);
438
+
439
+ // Call the callbacks for each service's discoverCharacteristics
440
+ mockServices[0].discoverCharacteristics.mock.calls[0][1](null, [mockCharacteristic1]);
441
+ mockServices[1].discoverCharacteristics.mock.calls[0][1](null, [mockCharacteristic2, mockCharacteristic3]);
442
+
443
+ await expect(promise).resolves.toEqual({ characteristics: [mockCharacteristic1, mockCharacteristic2, mockCharacteristic3], services: mockServices });
444
+ });
445
+
446
+ test('should reject when disconnect happens during execution', async () => {
447
+ const mockServiceUuids = [];
448
+ const mockCharacteristicUuids = [];
449
+
450
+ // Override the implementation of _withDisconnectHandler to simulate disconnect during operation
451
+ mockNoble._withDisconnectHandler = jest.fn((id, operation) => {
452
+ return new Promise((resolve, reject) => {
453
+ // Start the operation
454
+ const operationPromise = operation();
455
+
456
+ // Simulate a disconnect by rejecting with a disconnect error
457
+ setTimeout(() => {
458
+ reject(new Error('Peripheral disconnected'));
459
+ }, 10);
460
+
461
+ return operationPromise;
462
+ });
463
+ });
464
+
465
+ // Start the async operation
466
+ const promise = peripheral.discoverSomeServicesAndCharacteristicsAsync(mockServiceUuids, mockCharacteristicUuids);
467
+
468
+ // Verify the promise rejects
469
+ await expect(promise).rejects.toEqual(expect.objectContaining({
470
+ message: 'Peripheral disconnected'
471
+ }));
472
+
473
+ // Restore original implementation
474
+ mockNoble._withDisconnectHandler = jest.fn((id, operation) => {
475
+ return new Promise((resolve, reject) => {
476
+ return Promise.resolve(operation())
477
+ .then(result => {
478
+ resolve(result);
479
+ })
480
+ .catch(error => {
481
+ reject(error);
482
+ });
483
+ });
484
+ });
451
485
  });
452
486
  });
453
487
 
454
488
  describe('discoverAllServicesAndCharacteristics', () => {
455
489
  beforeEach(() => {
456
- peripheral.discoverSomeServicesAndCharacteristics = sinon.stub();
457
- });
458
-
459
- afterEach(() => {
460
- sinon.reset();
490
+ peripheral.discoverSomeServicesAndCharacteristics = jest.fn();
461
491
  });
462
492
 
463
- it('should call discoverSomeServicesAndCharacteristics', () => {
464
- const callback = sinon.spy();
493
+ test('should call discoverSomeServicesAndCharacteristics', () => {
494
+ const callback = jest.fn();
465
495
  peripheral.discoverAllServicesAndCharacteristics(callback);
466
- assert.calledOnceWithExactly(peripheral.discoverSomeServicesAndCharacteristics, [], [], callback);
496
+ expect(peripheral.discoverSomeServicesAndCharacteristics).toHaveBeenCalledWith([], [], callback);
467
497
  });
468
498
  });
469
499
 
470
500
  describe('discoverAllServicesAndCharacteristicsAsync', () => {
471
501
  beforeEach(() => {
472
- peripheral.discoverSomeServicesAndCharacteristics = sinon.stub();
473
- });
474
-
475
- afterEach(() => {
476
- sinon.reset();
502
+ peripheral.discoverAllServicesAndCharacteristics = jest.fn();
477
503
  });
478
-
479
- it('should call discoverSomeServicesAndCharacteristics', async () => {
504
+
505
+ test('should call discoverAllServicesAndCharacteristics with a callback', async () => {
506
+ const promise = peripheral.discoverAllServicesAndCharacteristicsAsync();
507
+
508
+ // Find the callback that was passed to discoverAllServicesAndCharacteristics
509
+ const callback = peripheral.discoverAllServicesAndCharacteristics.mock.calls[0][0];
510
+
511
+ // Simulate successful callback
512
+ const mockServices = ['service1', 'service2'];
513
+ const mockCharacteristics = ['char1', 'char2'];
514
+ callback(null, mockServices, mockCharacteristics);
515
+
516
+ const result = await promise;
517
+ expect(result).toEqual({ services: mockServices, characteristics: mockCharacteristics });
518
+ expect(peripheral.discoverAllServicesAndCharacteristics).toHaveBeenCalledTimes(1);
519
+ });
520
+
521
+ test('should reject when discoverAllServicesAndCharacteristics returns an error', async () => {
480
522
  const promise = peripheral.discoverAllServicesAndCharacteristicsAsync();
481
- peripheral.discoverSomeServicesAndCharacteristics.callArg(2, null);
482
- should(promise).resolvedWith([]);
523
+
524
+ // Find the callback that was passed to discoverAllServicesAndCharacteristics
525
+ const callback = peripheral.discoverAllServicesAndCharacteristics.mock.calls[0][0];
526
+
527
+ // Simulate error callback
528
+ const mockError = new Error('Discovery failed');
529
+ callback(mockError);
530
+
531
+ await expect(promise).rejects.toEqual(mockError);
532
+ expect(peripheral.discoverAllServicesAndCharacteristics).toHaveBeenCalledTimes(1);
483
533
  });
484
534
  });
485
535
 
486
536
  describe('readHandle', () => {
487
- beforeEach(() => {
488
- mockNoble.readHandle = sinon.spy();
489
- });
490
-
491
- afterEach(() => {
492
- sinon.reset();
493
- });
494
-
495
- it('should delegate to noble', () => {
537
+ test('should delegate to noble', () => {
496
538
  peripheral.readHandle(mockHandle);
497
- assert.calledOnceWithExactly(mockNoble.readHandle, mockId, mockHandle);
539
+ expect(mockNoble.readHandle).toHaveBeenCalledWith(mockId, mockHandle);
540
+ expect(mockNoble.readHandle).toHaveBeenCalledTimes(1);
498
541
  });
499
542
 
500
- it('should callback', () => {
501
- const callback = sinon.spy();
543
+ test('should callback', () => {
544
+ const callback = jest.fn();
502
545
 
503
546
  peripheral.readHandle(mockHandle, callback);
504
547
  peripheral.emit(`handleRead${mockHandle}`);
505
548
 
506
- assert.calledOnceWithExactly(callback, null, undefined);
507
- assert.calledOnceWithExactly(mockNoble.readHandle, mockId, mockHandle);
549
+ expect(callback).toHaveBeenCalledWith(undefined, undefined);
550
+ expect(callback).toHaveBeenCalledTimes(1);
551
+ expect(mockNoble.readHandle).toHaveBeenCalledWith(mockId, mockHandle);
552
+ expect(mockNoble.readHandle).toHaveBeenCalledTimes(1);
508
553
  });
509
554
 
510
- it('should callback with data', () => {
511
- const callback = sinon.spy();
555
+ test('should callback with data', () => {
556
+ const callback = jest.fn();
512
557
 
513
558
  peripheral.readHandle(mockHandle, callback);
514
559
  peripheral.emit(`handleRead${mockHandle}`, mockData);
515
560
 
516
- assert.calledOnceWithExactly(callback, null, mockData);
517
- assert.calledOnceWithExactly(mockNoble.readHandle, mockId, mockHandle);
561
+ expect(callback).toHaveBeenCalledWith(undefined, mockData);
562
+ expect(callback).toHaveBeenCalledTimes(1);
563
+ expect(mockNoble.readHandle).toHaveBeenCalledWith(mockId, mockHandle);
564
+ expect(mockNoble.readHandle).toHaveBeenCalledTimes(1);
518
565
  });
519
566
  });
520
567
 
521
568
  describe('readHandleAsync', () => {
522
- beforeEach(() => {
523
- mockNoble.readHandle = sinon.spy();
524
- });
525
-
526
- afterEach(() => {
527
- sinon.reset();
528
- });
529
-
530
- it('should delegate to noble', async () => {
569
+ test('should delegate to noble', async () => {
531
570
  const promise = peripheral.readHandleAsync(mockHandle);
532
571
  peripheral.emit(`handleRead${mockHandle}`);
533
572
 
534
- should(promise).resolvedWith(undefined);
535
- assert.calledOnceWithExactly(mockNoble.readHandle, mockId, mockHandle);
573
+ await expect(promise).resolves.toBeUndefined();
574
+ expect(mockNoble.readHandle).toHaveBeenCalledWith(mockId, mockHandle);
575
+ expect(mockNoble.readHandle).toHaveBeenCalledTimes(1);
536
576
  });
537
577
 
538
- it('should resolve with data', async () => {
578
+ test('should resolve with data', async () => {
539
579
  const promise = peripheral.readHandleAsync(mockHandle);
540
580
  peripheral.emit(`handleRead${mockHandle}`, mockData);
541
581
 
542
- should(promise).resolvedWith(mockData);
543
- assert.calledOnceWithExactly(mockNoble.readHandle, mockId, mockHandle);
582
+ await expect(promise).resolves.toEqual(mockData);
583
+ expect(mockNoble.readHandle).toHaveBeenCalledWith(mockId, mockHandle);
584
+ expect(mockNoble.readHandle).toHaveBeenCalledTimes(1);
544
585
  });
545
586
  });
546
587
 
547
588
  describe('writeHandle', () => {
548
- beforeEach(() => {
549
- mockNoble.writeHandle = sinon.spy();
550
- });
551
-
552
- afterEach(() => {
553
- sinon.reset();
554
- });
555
-
556
- it('should only accept data as a buffer', () => {
589
+ test('should only accept data as a buffer', () => {
557
590
  const mockData = {};
558
- should(() => peripheral.writeHandle(mockHandle, mockData)).throw('data must be a Buffer');
559
- assert.notCalled(mockNoble.writeHandle);
591
+ expect(() => peripheral.writeHandle(mockHandle, mockData)).toThrow('data must be a Buffer');
592
+ expect(mockNoble.writeHandle).not.toHaveBeenCalled();
560
593
  });
561
594
 
562
- it('should delegate to noble, withoutResponse false', () => {
595
+ test('should delegate to noble, withoutResponse false', () => {
563
596
  const mockData = Buffer.alloc(0);
564
597
  peripheral.writeHandle(mockHandle, mockData, false);
565
598
 
566
- assert.alwaysCalledWithExactly(mockNoble.writeHandle, mockId, mockHandle, mockData, false);
599
+ expect(mockNoble.writeHandle).toHaveBeenCalledWith(mockId, mockHandle, mockData, false);
600
+ expect(mockNoble.writeHandle).toHaveBeenCalledTimes(1);
567
601
  });
568
602
 
569
- it('should delegate to noble, withoutResponse true', () => {
603
+ test('should delegate to noble, withoutResponse true', () => {
570
604
  const mockData = Buffer.alloc(0);
571
605
  peripheral.writeHandle(mockHandle, mockData, true);
572
606
 
573
- assert.alwaysCalledWithExactly(mockNoble.writeHandle, mockId, mockHandle, mockData, true);
607
+ expect(mockNoble.writeHandle).toHaveBeenCalledWith(mockId, mockHandle, mockData, true);
608
+ expect(mockNoble.writeHandle).toHaveBeenCalledTimes(1);
574
609
  });
575
610
 
576
- it('should callback', () => {
611
+ test('should callback', () => {
577
612
  const mockData = Buffer.alloc(0);
578
- const callback = sinon.spy();
613
+ const callback = jest.fn();
579
614
 
580
615
  peripheral.writeHandle(mockHandle, mockData, false, callback);
581
616
  peripheral.emit(`handleWrite${mockHandle}`);
582
617
 
583
- assert.calledOnceWithExactly(callback, null);
584
- assert.alwaysCalledWithExactly(mockNoble.writeHandle, mockId, mockHandle, mockData, false);
618
+ expect(callback).toHaveBeenCalledWith(undefined);
619
+ expect(callback).toHaveBeenCalledTimes(1);
620
+ expect(mockNoble.writeHandle).toHaveBeenCalledWith(mockId, mockHandle, mockData, false);
621
+ expect(mockNoble.writeHandle).toHaveBeenCalledTimes(1);
585
622
  });
586
623
  });
587
624
 
588
625
  describe('writeHandleAsync', () => {
589
- beforeEach(() => {
590
- mockNoble.writeHandle = sinon.spy();
591
- });
592
-
593
- afterEach(() => {
594
- sinon.reset();
595
- });
596
-
597
- it('should only accept data as a buffer', async () => {
626
+ test('should only accept data as a buffer', async () => {
598
627
  const mockData = {};
599
- const promise = peripheral.writeHandleAsync(mockHandle, mockData);
600
-
601
- should(promise).rejectedWith('data must be a Buffer');
602
- assert.notCalled(mockNoble.writeHandle);
628
+
629
+ await expect(peripheral.writeHandleAsync(mockHandle, mockData)).rejects.toThrow('data must be a Buffer');
630
+ expect(mockNoble.writeHandle).not.toHaveBeenCalled();
603
631
  });
604
632
 
605
- it('should delegate to noble, withoutResponse false', async () => {
633
+ test('should delegate to noble, withoutResponse false', async () => {
606
634
  const mockData = Buffer.alloc(0);
607
635
  const promise = peripheral.writeHandleAsync(mockHandle, mockData, false);
608
636
  peripheral.emit(`handleWrite${mockHandle}`);
609
637
 
610
- should(promise).resolvedWith(null);
611
- assert.alwaysCalledWithExactly(mockNoble.writeHandle, mockId, mockHandle, mockData, false);
638
+ await expect(promise).resolves.toBeUndefined();
639
+ expect(mockNoble.writeHandle).toHaveBeenCalledWith(mockId, mockHandle, mockData, false);
640
+ expect(mockNoble.writeHandle).toHaveBeenCalledTimes(1);
612
641
  });
613
642
 
614
- it('should delegate to noble, withoutResponse true', async () => {
643
+ test('should delegate to noble, withoutResponse true', async () => {
615
644
  const mockData = Buffer.alloc(0);
616
645
  const promise = peripheral.writeHandleAsync(mockHandle, mockData, true);
617
646
  peripheral.emit(`handleWrite${mockHandle}`);
618
647
 
619
- should(promise).resolvedWith(null);
620
- assert.alwaysCalledWithExactly(mockNoble.writeHandle, mockId, mockHandle, mockData, true);
648
+ await expect(promise).resolves.toBeUndefined();
649
+ expect(mockNoble.writeHandle).toHaveBeenCalledWith(mockId, mockHandle, mockData, true);
650
+ expect(mockNoble.writeHandle).toHaveBeenCalledTimes(1);
651
+ });
652
+ });
653
+
654
+ describe('async commands with disconnect handling', () => {
655
+ // Setup a reusable helper to manage the original and mocked _withDisconnectHandler
656
+ let originalWithDisconnectHandler;
657
+
658
+ const setupDisconnectMock = (simulateDisconnect = true, disconnectDelay = 10) => {
659
+ // Save the original implementation
660
+ originalWithDisconnectHandler = mockNoble._withDisconnectHandler;
661
+
662
+ // Create the mock implementation
663
+ mockNoble._withDisconnectHandler = jest.fn((id, operation) => {
664
+ return new Promise((resolve, reject) => {
665
+ // Start the operation
666
+ const operationPromise = operation();
667
+
668
+ if (simulateDisconnect) {
669
+ // Simulate a disconnect by rejecting with a disconnect error
670
+ setTimeout(() => {
671
+ reject(new Error('Peripheral disconnected'));
672
+ }, disconnectDelay);
673
+ }
674
+
675
+ return operationPromise;
676
+ });
677
+ });
678
+ };
679
+
680
+ const restoreDisconnectMock = () => {
681
+ // Restore the original implementation
682
+ mockNoble._withDisconnectHandler = originalWithDisconnectHandler;
683
+ };
684
+
685
+ afterEach(() => {
686
+ // Always restore after each test
687
+ restoreDisconnectMock();
688
+ });
689
+
690
+ test('discoverServicesAsync should handle disconnect during operation', async () => {
691
+ setupDisconnectMock();
692
+
693
+ const promise = peripheral.discoverServicesAsync();
694
+
695
+ await expect(promise).rejects.toEqual(expect.objectContaining({
696
+ message: 'Peripheral disconnected'
697
+ }));
698
+ });
699
+
700
+ test('updateRssiAsync should handle disconnect during operation', async () => {
701
+ setupDisconnectMock();
702
+
703
+ const promise = peripheral.updateRssiAsync();
704
+
705
+ await expect(promise).rejects.toEqual(expect.objectContaining({
706
+ message: 'Peripheral disconnected'
707
+ }));
708
+ });
709
+
710
+ test('readHandleAsync should handle disconnect during operation', async () => {
711
+ setupDisconnectMock();
712
+
713
+ const promise = peripheral.readHandleAsync(mockHandle);
714
+
715
+ await expect(promise).rejects.toEqual(expect.objectContaining({
716
+ message: 'Peripheral disconnected'
717
+ }));
718
+ });
719
+
720
+ test('writeHandleAsync should handle disconnect during operation', async () => {
721
+ setupDisconnectMock();
722
+
723
+ const mockData = Buffer.alloc(0);
724
+ const promise = peripheral.writeHandleAsync(mockHandle, mockData, false);
725
+
726
+ await expect(promise).rejects.toEqual(expect.objectContaining({
727
+ message: 'Peripheral disconnected'
728
+ }));
729
+ });
730
+
731
+ test('chain of async operations should all fail on disconnect', async () => {
732
+ // We'll run without disconnect initially
733
+ setupDisconnectMock(false);
734
+
735
+ // Setup mock responses for service discovery
736
+ const mockCharacteristic = { uuid: '1' };
737
+ const mockService = {
738
+ uuid: '1',
739
+ discoverCharacteristics: jest.fn((charUuids, callback) => {
740
+ callback(null, [mockCharacteristic]);
741
+ })
742
+ };
743
+
744
+ peripheral.discoverServices = jest.fn((uuids, callback) => {
745
+ callback(null, [mockService]);
746
+ });
747
+
748
+ // Start a chain of operations
749
+ const runOperations = async () => {
750
+
751
+ setTimeout(() => peripheral.emit('connect'), 20);
752
+
753
+ // First operation succeeds
754
+ await peripheral.connectAsync();
755
+
756
+ // Enable disconnection before second operation
757
+ setupDisconnectMock(true, 5);
758
+
759
+ // This should fail with disconnect
760
+ await peripheral.discoverAllServicesAndCharacteristics();
761
+
762
+ // These should never be reached
763
+ await peripheral.readHandleAsync(mockHandle);
764
+ await peripheral.writeHandleAsync(mockHandle, Buffer.alloc(0));
765
+
766
+ return 'completed';
767
+ };
768
+
769
+ await expect(runOperations()).rejects.toEqual(expect.objectContaining({
770
+ message: 'Peripheral disconnected'
771
+ }));
772
+ });
773
+
774
+ test('multiple concurrent async operations should all fail on disconnect', async () => {
775
+ setupDisconnectMock(true, 5);
776
+
777
+ // Start multiple operations at the same time
778
+ const promises = [
779
+ peripheral.updateRssiAsync(),
780
+ peripheral.discoverServicesAsync(),
781
+ peripheral.readHandleAsync(mockHandle),
782
+ peripheral.writeHandleAsync(mockHandle, Buffer.alloc(0))
783
+ ];
784
+
785
+ // All operations should fail with the same disconnect error
786
+ await Promise.all(promises.map(promise =>
787
+ expect(promise).rejects.toEqual(expect.objectContaining({
788
+ message: 'Peripheral disconnected'
789
+ }))
790
+ ));
621
791
  });
622
792
  });
623
- });
793
+ });