react-native-ble-nitro 1.0.0-alpha.1

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 (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +298 -0
  3. package/android/build.gradle +55 -0
  4. package/android/src/main/AndroidManifest.xml +23 -0
  5. package/android/src/main/kotlin/co/zyke/ble/BleNitroBleManager.kt +651 -0
  6. package/android/src/main/kotlin/co/zyke/ble/BleNitroPackage.kt +37 -0
  7. package/ios/BleNitro.podspec +37 -0
  8. package/ios/BleNitroBleManager.swift +509 -0
  9. package/ios/BleNitroModule.swift +31 -0
  10. package/lib/BleManagerCompatFactory.d.ts +53 -0
  11. package/lib/BleManagerCompatFactory.js +191 -0
  12. package/lib/BleManagerFactory.d.ts +12 -0
  13. package/lib/BleManagerFactory.js +22 -0
  14. package/lib/compatibility/constants.d.ts +49 -0
  15. package/lib/compatibility/constants.js +50 -0
  16. package/lib/compatibility/deviceWrapper.d.ts +99 -0
  17. package/lib/compatibility/deviceWrapper.js +259 -0
  18. package/lib/compatibility/enums.d.ts +43 -0
  19. package/lib/compatibility/enums.js +124 -0
  20. package/lib/compatibility/index.d.ts +11 -0
  21. package/lib/compatibility/index.js +12 -0
  22. package/lib/compatibility/serviceData.d.ts +51 -0
  23. package/lib/compatibility/serviceData.js +70 -0
  24. package/lib/errors/BleError.d.ts +59 -0
  25. package/lib/errors/BleError.js +120 -0
  26. package/lib/index.d.ts +7 -0
  27. package/lib/index.js +12 -0
  28. package/lib/specs/BleManager.nitro.d.ts +36 -0
  29. package/lib/specs/BleManager.nitro.js +1 -0
  30. package/lib/specs/Characteristic.nitro.d.ts +26 -0
  31. package/lib/specs/Characteristic.nitro.js +1 -0
  32. package/lib/specs/Descriptor.nitro.d.ts +17 -0
  33. package/lib/specs/Descriptor.nitro.js +1 -0
  34. package/lib/specs/Device.nitro.d.ts +37 -0
  35. package/lib/specs/Device.nitro.js +1 -0
  36. package/lib/specs/Service.nitro.d.ts +19 -0
  37. package/lib/specs/Service.nitro.js +1 -0
  38. package/lib/specs/types.d.ts +228 -0
  39. package/lib/specs/types.js +146 -0
  40. package/lib/utils/base64.d.ts +25 -0
  41. package/lib/utils/base64.js +80 -0
  42. package/lib/utils/index.d.ts +2 -0
  43. package/lib/utils/index.js +2 -0
  44. package/lib/utils/uuid.d.ts +9 -0
  45. package/lib/utils/uuid.js +37 -0
  46. package/nitro.json +15 -0
  47. package/package.json +102 -0
  48. package/plugin/build/index.d.ts +28 -0
  49. package/plugin/build/index.js +29 -0
  50. package/plugin/build/withBleNitro.d.ts +31 -0
  51. package/plugin/build/withBleNitro.js +87 -0
  52. package/react-native.config.js +13 -0
  53. package/src/BleManagerCompatFactory.ts +373 -0
  54. package/src/BleManagerFactory.ts +30 -0
  55. package/src/__tests__/BleManager.test.ts +327 -0
  56. package/src/__tests__/compatibility/deviceWrapper.test.ts +563 -0
  57. package/src/__tests__/compatibility/enums.test.ts +254 -0
  58. package/src/compatibility/constants.ts +71 -0
  59. package/src/compatibility/deviceWrapper.ts +427 -0
  60. package/src/compatibility/enums.ts +160 -0
  61. package/src/compatibility/index.ts +24 -0
  62. package/src/compatibility/serviceData.ts +85 -0
  63. package/src/errors/BleError.ts +193 -0
  64. package/src/index.ts +30 -0
  65. package/src/specs/BleManager.nitro.ts +152 -0
  66. package/src/specs/Characteristic.nitro.ts +61 -0
  67. package/src/specs/Descriptor.nitro.ts +28 -0
  68. package/src/specs/Device.nitro.ts +104 -0
  69. package/src/specs/Service.nitro.ts +64 -0
  70. package/src/specs/types.ts +259 -0
  71. package/src/utils/base64.ts +80 -0
  72. package/src/utils/index.ts +2 -0
  73. package/src/utils/uuid.ts +45 -0
@@ -0,0 +1,563 @@
1
+ /**
2
+ * deviceWrapper.test.ts
3
+ * React Native BLE Nitro - Device Wrapper Tests
4
+ * Copyright © 2025 Zyke (https://zyke.co)
5
+ */
6
+
7
+ import { DeviceWrapper, ServiceWrapper, CharacteristicWrapper, DescriptorWrapper } from '../../compatibility/deviceWrapper';
8
+ import { ConnectionPriority, CharacteristicSubscriptionType } from '../../specs/types';
9
+
10
+ // Mock Nitro Device
11
+ const createMockNitroDevice = () => ({
12
+ id: 'test-device-id',
13
+ deviceName: 'Test Device',
14
+ rssi: -50,
15
+ mtu: 247,
16
+ manufacturerData: 'AQIDBA==', // [1, 2, 3, 4] in base64
17
+ serviceData: [
18
+ { uuid: '180f', data: 'ZA==' }, // Battery service with value 100
19
+ { uuid: '1234', data: 'dGVzdA==' } // Custom service with "test"
20
+ ],
21
+ serviceUUIDs: ['180f', '1234-5678-9abc-def0-123456789abc'],
22
+ localName: 'Local Test Device',
23
+ txPowerLevel: 4,
24
+ solicitedServiceUUIDs: ['abcd'],
25
+ isConnectable: true,
26
+ overflowServiceUUIDs: ['efgh'],
27
+ rawScanRecord: 'AQIDBAU=',
28
+
29
+ // Mock methods
30
+ requestConnectionPriority: jest.fn().mockResolvedValue({}),
31
+ readRSSI: jest.fn().mockResolvedValue({}),
32
+ requestMTU: jest.fn().mockResolvedValue({}),
33
+ connect: jest.fn().mockResolvedValue({}),
34
+ cancelConnection: jest.fn().mockResolvedValue({}),
35
+ isConnected: jest.fn().mockResolvedValue(true),
36
+ onDisconnected: jest.fn().mockReturnValue({ remove: jest.fn() }),
37
+ discoverAllServicesAndCharacteristics: jest.fn().mockResolvedValue({}),
38
+ services: jest.fn().mockResolvedValue([]),
39
+ characteristicsForService: jest.fn().mockResolvedValue([]),
40
+ readCharacteristicForService: jest.fn().mockResolvedValue({}),
41
+ writeCharacteristicWithResponseForService: jest.fn().mockResolvedValue({}),
42
+ writeCharacteristicWithoutResponseForService: jest.fn().mockResolvedValue({}),
43
+ monitorCharacteristicForService: jest.fn().mockReturnValue({ remove: jest.fn() }),
44
+ descriptorsForService: jest.fn().mockResolvedValue([]),
45
+ readDescriptorForService: jest.fn().mockResolvedValue({}),
46
+ writeDescriptorForService: jest.fn().mockResolvedValue({})
47
+ });
48
+
49
+ describe('DeviceWrapper', () => {
50
+ let mockNitroDevice: ReturnType<typeof createMockNitroDevice>;
51
+ let deviceWrapper: DeviceWrapper;
52
+
53
+ beforeEach(() => {
54
+ mockNitroDevice = createMockNitroDevice();
55
+ deviceWrapper = new DeviceWrapper(mockNitroDevice);
56
+ jest.clearAllMocks();
57
+ });
58
+
59
+ describe('Property Mapping', () => {
60
+ it('should map device identification properties correctly', () => {
61
+ expect(deviceWrapper.id).toBe('test-device-id');
62
+ expect(deviceWrapper.name).toBe('Test Device'); // Maps from deviceName
63
+ expect(deviceWrapper.rssi).toBe(-50);
64
+ expect(deviceWrapper.mtu).toBe(247);
65
+ });
66
+
67
+ it('should handle null device name', () => {
68
+ mockNitroDevice.deviceName = null;
69
+ const wrapper = new DeviceWrapper(mockNitroDevice);
70
+ expect(wrapper.name).toBeNull();
71
+ });
72
+
73
+ it('should map advertisement data properties', () => {
74
+ expect(deviceWrapper.manufacturerData).toBe('AQIDBA==');
75
+ expect(deviceWrapper.rawScanRecord).toBe('AQIDBAU=');
76
+ expect(deviceWrapper.localName).toBe('Local Test Device');
77
+ expect(deviceWrapper.txPowerLevel).toBe(4);
78
+ expect(deviceWrapper.isConnectable).toBe(true);
79
+ });
80
+
81
+ it('should map service-related properties', () => {
82
+ expect(deviceWrapper.serviceUUIDs).toEqual(['180f', '1234-5678-9abc-def0-123456789abc']);
83
+ expect(deviceWrapper.solicitedServiceUUIDs).toEqual(['abcd']);
84
+ expect(deviceWrapper.overflowServiceUUIDs).toEqual(['efgh']);
85
+ });
86
+
87
+ it('should convert service data array to map format', () => {
88
+ const serviceData = deviceWrapper.serviceData;
89
+ expect(serviceData).toEqual({
90
+ '180f': 'ZA==',
91
+ '1234': 'dGVzdA=='
92
+ });
93
+ });
94
+
95
+ it('should handle null service data', () => {
96
+ mockNitroDevice.serviceData = null;
97
+ const wrapper = new DeviceWrapper(mockNitroDevice);
98
+ expect(wrapper.serviceData).toBeNull();
99
+ });
100
+ });
101
+
102
+ describe('Connection Management', () => {
103
+ it('should request connection priority', async () => {
104
+ mockNitroDevice.requestConnectionPriority.mockResolvedValue(mockNitroDevice);
105
+
106
+ const result = await deviceWrapper.requestConnectionPriority(
107
+ ConnectionPriority.High,
108
+ 'transaction-123'
109
+ );
110
+
111
+ expect(result).toBeInstanceOf(DeviceWrapper);
112
+ expect(mockNitroDevice.requestConnectionPriority).toHaveBeenCalledWith(
113
+ ConnectionPriority.High,
114
+ 'transaction-123'
115
+ );
116
+ });
117
+
118
+ it('should read RSSI', async () => {
119
+ const updatedDevice = { ...mockNitroDevice, rssi: -45 };
120
+ mockNitroDevice.readRSSI.mockResolvedValue(updatedDevice);
121
+
122
+ const result = await deviceWrapper.readRSSI('transaction-456');
123
+
124
+ expect(result).toBeInstanceOf(DeviceWrapper);
125
+ expect(mockNitroDevice.readRSSI).toHaveBeenCalledWith('transaction-456');
126
+ });
127
+
128
+ it('should request MTU change', async () => {
129
+ const updatedDevice = { ...mockNitroDevice, mtu: 512 };
130
+ mockNitroDevice.requestMTU.mockResolvedValue(updatedDevice);
131
+
132
+ const result = await deviceWrapper.requestMTU(512, 'mtu-transaction');
133
+
134
+ expect(result).toBeInstanceOf(DeviceWrapper);
135
+ expect(mockNitroDevice.requestMTU).toHaveBeenCalledWith(512, 'mtu-transaction');
136
+ });
137
+
138
+ it('should connect with default options', async () => {
139
+ mockNitroDevice.connect.mockResolvedValue(mockNitroDevice);
140
+
141
+ const result = await deviceWrapper.connect();
142
+
143
+ expect(result).toBeInstanceOf(DeviceWrapper);
144
+ expect(mockNitroDevice.connect).toHaveBeenCalledWith({
145
+ autoConnect: false,
146
+ requestMTU: 23,
147
+ timeout: 0
148
+ });
149
+ });
150
+
151
+ it('should connect with custom options', async () => {
152
+ mockNitroDevice.connect.mockResolvedValue(mockNitroDevice);
153
+
154
+ const result = await deviceWrapper.connect({
155
+ autoConnect: true,
156
+ requestMTU: 247,
157
+ timeout: 5000
158
+ });
159
+
160
+ expect(result).toBeInstanceOf(DeviceWrapper);
161
+ expect(mockNitroDevice.connect).toHaveBeenCalledWith({
162
+ autoConnect: true,
163
+ requestMTU: 247,
164
+ timeout: 5000
165
+ });
166
+ });
167
+
168
+ it('should cancel connection', async () => {
169
+ mockNitroDevice.cancelConnection.mockResolvedValue(mockNitroDevice);
170
+
171
+ const result = await deviceWrapper.cancelConnection();
172
+
173
+ expect(result).toBeInstanceOf(DeviceWrapper);
174
+ expect(mockNitroDevice.cancelConnection).toHaveBeenCalled();
175
+ });
176
+
177
+ it('should check connection status', async () => {
178
+ mockNitroDevice.isConnected.mockResolvedValue(true);
179
+
180
+ const result = await deviceWrapper.isConnected();
181
+
182
+ expect(result).toBe(true);
183
+ expect(mockNitroDevice.isConnected).toHaveBeenCalled();
184
+ });
185
+
186
+ it('should handle disconnection listener', () => {
187
+ const listener = jest.fn();
188
+ const mockSubscription = { remove: jest.fn() };
189
+ mockNitroDevice.onDisconnected.mockReturnValue(mockSubscription);
190
+
191
+ const subscription = deviceWrapper.onDisconnected(listener);
192
+
193
+ expect(subscription).toBe(mockSubscription);
194
+ expect(mockNitroDevice.onDisconnected).toHaveBeenCalled();
195
+
196
+ // Verify the listener wrapper is called correctly
197
+ const callArgs = mockNitroDevice.onDisconnected.mock.calls[0][0];
198
+ const mockError = new Error('Disconnected');
199
+ const mockDevice = { ...mockNitroDevice };
200
+
201
+ callArgs(mockError, mockDevice);
202
+ expect(listener).toHaveBeenCalledWith(mockError, expect.any(DeviceWrapper));
203
+ });
204
+ });
205
+
206
+ describe('Service Discovery', () => {
207
+ it('should discover all services and characteristics', async () => {
208
+ mockNitroDevice.discoverAllServicesAndCharacteristics.mockResolvedValue(mockNitroDevice);
209
+
210
+ const result = await deviceWrapper.discoverAllServicesAndCharacteristics('discovery-tx');
211
+
212
+ expect(result).toBeInstanceOf(DeviceWrapper);
213
+ expect(mockNitroDevice.discoverAllServicesAndCharacteristics).toHaveBeenCalledWith('discovery-tx');
214
+ });
215
+
216
+ it('should get services list', async () => {
217
+ const mockServices = [
218
+ { id: 1, uuid: '180f', deviceID: 'test-device-id', isPrimary: true }
219
+ ];
220
+ mockNitroDevice.services.mockResolvedValue(mockServices);
221
+
222
+ const result = await deviceWrapper.services();
223
+
224
+ expect(result).toHaveLength(1);
225
+ expect(result[0]).toBeInstanceOf(ServiceWrapper);
226
+ expect(mockNitroDevice.services).toHaveBeenCalled();
227
+ });
228
+ });
229
+
230
+ describe('Characteristic Operations', () => {
231
+ const serviceUUID = '180f';
232
+ const characteristicUUID = '2a19';
233
+
234
+ it('should get characteristics for service', async () => {
235
+ const mockCharacteristics = [
236
+ {
237
+ id: 1,
238
+ uuid: characteristicUUID,
239
+ serviceID: 1,
240
+ serviceUUID,
241
+ deviceID: 'test-device-id',
242
+ isReadable: true,
243
+ isWritableWithResponse: false,
244
+ isWritableWithoutResponse: false,
245
+ isNotifiable: true,
246
+ isNotifying: false,
247
+ isIndicatable: false,
248
+ value: null
249
+ }
250
+ ];
251
+ mockNitroDevice.characteristicsForService.mockResolvedValue(mockCharacteristics);
252
+
253
+ const result = await deviceWrapper.characteristicsForService(serviceUUID);
254
+
255
+ expect(result).toHaveLength(1);
256
+ expect(result[0]).toBeInstanceOf(CharacteristicWrapper);
257
+ expect(mockNitroDevice.characteristicsForService).toHaveBeenCalledWith(serviceUUID);
258
+ });
259
+
260
+ it('should read characteristic', async () => {
261
+ const mockCharacteristic = {
262
+ id: 1,
263
+ uuid: characteristicUUID,
264
+ value: 'dGVzdA=='
265
+ };
266
+ mockNitroDevice.readCharacteristicForService.mockResolvedValue(mockCharacteristic);
267
+
268
+ const result = await deviceWrapper.readCharacteristicForService(
269
+ serviceUUID,
270
+ characteristicUUID,
271
+ 'read-tx'
272
+ );
273
+
274
+ expect(result).toBeInstanceOf(CharacteristicWrapper);
275
+ expect(mockNitroDevice.readCharacteristicForService).toHaveBeenCalledWith(
276
+ serviceUUID,
277
+ characteristicUUID,
278
+ 'read-tx'
279
+ );
280
+ });
281
+
282
+ it('should write characteristic with response', async () => {
283
+ const value = 'dGVzdCB2YWx1ZQ==';
284
+ const mockCharacteristic = {
285
+ id: 1,
286
+ uuid: characteristicUUID,
287
+ value
288
+ };
289
+ mockNitroDevice.writeCharacteristicWithResponseForService.mockResolvedValue(mockCharacteristic);
290
+
291
+ const result = await deviceWrapper.writeCharacteristicWithResponseForService(
292
+ serviceUUID,
293
+ characteristicUUID,
294
+ value,
295
+ 'write-tx'
296
+ );
297
+
298
+ expect(result).toBeInstanceOf(CharacteristicWrapper);
299
+ expect(mockNitroDevice.writeCharacteristicWithResponseForService).toHaveBeenCalledWith(
300
+ serviceUUID,
301
+ characteristicUUID,
302
+ value,
303
+ 'write-tx'
304
+ );
305
+ });
306
+
307
+ it('should write characteristic without response', async () => {
308
+ const value = 'dGVzdA==';
309
+ const mockCharacteristic = {
310
+ id: 1,
311
+ uuid: characteristicUUID,
312
+ value
313
+ };
314
+ mockNitroDevice.writeCharacteristicWithoutResponseForService.mockResolvedValue(mockCharacteristic);
315
+
316
+ const result = await deviceWrapper.writeCharacteristicWithoutResponseForService(
317
+ serviceUUID,
318
+ characteristicUUID,
319
+ value
320
+ );
321
+
322
+ expect(result).toBeInstanceOf(CharacteristicWrapper);
323
+ expect(mockNitroDevice.writeCharacteristicWithoutResponseForService).toHaveBeenCalledWith(
324
+ serviceUUID,
325
+ characteristicUUID,
326
+ value,
327
+ undefined
328
+ );
329
+ });
330
+
331
+ it('should monitor characteristic for changes', () => {
332
+ const listener = jest.fn();
333
+ const mockSubscription = { remove: jest.fn() };
334
+ mockNitroDevice.monitorCharacteristicForService.mockReturnValue(mockSubscription);
335
+
336
+ const result = deviceWrapper.monitorCharacteristicForService(
337
+ serviceUUID,
338
+ characteristicUUID,
339
+ listener,
340
+ 'monitor-tx',
341
+ 'notification'
342
+ );
343
+
344
+ expect(result).toBe(mockSubscription);
345
+ expect(mockNitroDevice.monitorCharacteristicForService).toHaveBeenCalledWith(
346
+ serviceUUID,
347
+ characteristicUUID,
348
+ expect.any(Function),
349
+ 'monitor-tx',
350
+ CharacteristicSubscriptionType.Notification
351
+ );
352
+
353
+ // Test the listener wrapper
354
+ const callArgs = mockNitroDevice.monitorCharacteristicForService.mock.calls[0][2];
355
+ const mockError = null;
356
+ const mockCharacteristic = { id: 1, uuid: characteristicUUID };
357
+
358
+ callArgs(mockError, mockCharacteristic);
359
+ expect(listener).toHaveBeenCalledWith(mockError, expect.any(CharacteristicWrapper));
360
+ });
361
+
362
+ it('should monitor characteristic without subscription type', () => {
363
+ const listener = jest.fn();
364
+ const mockSubscription = { remove: jest.fn() };
365
+ mockNitroDevice.monitorCharacteristicForService.mockReturnValue(mockSubscription);
366
+
367
+ deviceWrapper.monitorCharacteristicForService(
368
+ serviceUUID,
369
+ characteristicUUID,
370
+ listener
371
+ );
372
+
373
+ expect(mockNitroDevice.monitorCharacteristicForService).toHaveBeenCalledWith(
374
+ serviceUUID,
375
+ characteristicUUID,
376
+ expect.any(Function),
377
+ undefined,
378
+ undefined
379
+ );
380
+ });
381
+ });
382
+
383
+ describe('Descriptor Operations', () => {
384
+ const serviceUUID = '180f';
385
+ const characteristicUUID = '2a19';
386
+ const descriptorUUID = '2902';
387
+
388
+ it('should get descriptors for characteristic', async () => {
389
+ const mockDescriptors = [
390
+ {
391
+ id: 1,
392
+ uuid: descriptorUUID,
393
+ characteristicID: 1,
394
+ characteristicUUID,
395
+ serviceID: 1,
396
+ serviceUUID,
397
+ deviceID: 'test-device-id',
398
+ value: null
399
+ }
400
+ ];
401
+ mockNitroDevice.descriptorsForService.mockResolvedValue(mockDescriptors);
402
+
403
+ const result = await deviceWrapper.descriptorsForService(serviceUUID, characteristicUUID);
404
+
405
+ expect(result).toHaveLength(1);
406
+ expect(result[0]).toBeInstanceOf(DescriptorWrapper);
407
+ expect(mockNitroDevice.descriptorsForService).toHaveBeenCalledWith(
408
+ serviceUUID,
409
+ characteristicUUID
410
+ );
411
+ });
412
+
413
+ it('should read descriptor', async () => {
414
+ const mockDescriptor = {
415
+ id: 1,
416
+ uuid: descriptorUUID,
417
+ value: 'AQA=' // [1, 0] in base64
418
+ };
419
+ mockNitroDevice.readDescriptorForService.mockResolvedValue(mockDescriptor);
420
+
421
+ const result = await deviceWrapper.readDescriptorForService(
422
+ serviceUUID,
423
+ characteristicUUID,
424
+ descriptorUUID,
425
+ 'descriptor-read-tx'
426
+ );
427
+
428
+ expect(result).toBeInstanceOf(DescriptorWrapper);
429
+ expect(mockNitroDevice.readDescriptorForService).toHaveBeenCalledWith(
430
+ serviceUUID,
431
+ characteristicUUID,
432
+ descriptorUUID,
433
+ 'descriptor-read-tx'
434
+ );
435
+ });
436
+
437
+ it('should write descriptor', async () => {
438
+ const value = 'AQA=';
439
+ const mockDescriptor = {
440
+ id: 1,
441
+ uuid: descriptorUUID,
442
+ value
443
+ };
444
+ mockNitroDevice.writeDescriptorForService.mockResolvedValue(mockDescriptor);
445
+
446
+ const result = await deviceWrapper.writeDescriptorForService(
447
+ serviceUUID,
448
+ characteristicUUID,
449
+ descriptorUUID,
450
+ value,
451
+ 'descriptor-write-tx'
452
+ );
453
+
454
+ expect(result).toBeInstanceOf(DescriptorWrapper);
455
+ expect(mockNitroDevice.writeDescriptorForService).toHaveBeenCalledWith(
456
+ serviceUUID,
457
+ characteristicUUID,
458
+ descriptorUUID,
459
+ value,
460
+ 'descriptor-write-tx'
461
+ );
462
+ });
463
+ });
464
+ });
465
+
466
+ describe('ServiceWrapper', () => {
467
+ let mockService: any;
468
+ let mockNitroDevice: any;
469
+ let serviceWrapper: ServiceWrapper;
470
+
471
+ beforeEach(() => {
472
+ mockService = {
473
+ id: 1,
474
+ uuid: '180f',
475
+ deviceID: 'test-device',
476
+ isPrimary: true
477
+ };
478
+
479
+ mockNitroDevice = createMockNitroDevice();
480
+ serviceWrapper = new ServiceWrapper(mockService, mockNitroDevice);
481
+ });
482
+
483
+ it('should expose service properties', () => {
484
+ expect(serviceWrapper.id).toBe(1);
485
+ expect(serviceWrapper.uuid).toBe('180f');
486
+ expect(serviceWrapper.deviceID).toBe('test-device');
487
+ expect(serviceWrapper.isPrimary).toBe(true);
488
+ });
489
+
490
+ it('should delegate characteristics method to device', async () => {
491
+ mockNitroDevice.characteristicsForService.mockResolvedValue([]);
492
+
493
+ await serviceWrapper.characteristics();
494
+
495
+ expect(mockNitroDevice.characteristicsForService).toHaveBeenCalledWith('180f');
496
+ });
497
+
498
+ it('should delegate read characteristic method to device', async () => {
499
+ const characteristicUUID = '2a19';
500
+ mockNitroDevice.readCharacteristicForService.mockResolvedValue({});
501
+
502
+ await serviceWrapper.readCharacteristic(characteristicUUID, 'tx-123');
503
+
504
+ expect(mockNitroDevice.readCharacteristicForService).toHaveBeenCalledWith(
505
+ '180f',
506
+ characteristicUUID,
507
+ 'tx-123'
508
+ );
509
+ });
510
+ });
511
+
512
+ describe('CharacteristicWrapper', () => {
513
+ let mockCharacteristic: any;
514
+ let mockNitroDevice: any;
515
+ let characteristicWrapper: CharacteristicWrapper;
516
+
517
+ beforeEach(() => {
518
+ mockCharacteristic = {
519
+ id: 1,
520
+ uuid: '2a19',
521
+ serviceID: 1,
522
+ serviceUUID: '180f',
523
+ deviceID: 'test-device',
524
+ isReadable: true,
525
+ isWritableWithResponse: true,
526
+ isWritableWithoutResponse: false,
527
+ isNotifiable: true,
528
+ isNotifying: false,
529
+ isIndicatable: false,
530
+ value: 'dGVzdA=='
531
+ };
532
+
533
+ mockNitroDevice = createMockNitroDevice();
534
+ characteristicWrapper = new CharacteristicWrapper(mockCharacteristic, mockNitroDevice);
535
+ });
536
+
537
+ it('should expose characteristic properties', () => {
538
+ expect(characteristicWrapper.id).toBe(1);
539
+ expect(characteristicWrapper.uuid).toBe('2a19');
540
+ expect(characteristicWrapper.serviceID).toBe(1);
541
+ expect(characteristicWrapper.serviceUUID).toBe('180f');
542
+ expect(characteristicWrapper.deviceID).toBe('test-device');
543
+ expect(characteristicWrapper.isReadable).toBe(true);
544
+ expect(characteristicWrapper.isWritableWithResponse).toBe(true);
545
+ expect(characteristicWrapper.isWritableWithoutResponse).toBe(false);
546
+ expect(characteristicWrapper.isNotifiable).toBe(true);
547
+ expect(characteristicWrapper.isNotifying).toBe(false);
548
+ expect(characteristicWrapper.isIndicatable).toBe(false);
549
+ expect(characteristicWrapper.value).toBe('dGVzdA==');
550
+ });
551
+
552
+ it('should delegate read method to device', async () => {
553
+ mockNitroDevice.readCharacteristicForService.mockResolvedValue(mockCharacteristic);
554
+
555
+ await characteristicWrapper.read('read-tx');
556
+
557
+ expect(mockNitroDevice.readCharacteristicForService).toHaveBeenCalledWith(
558
+ '180f',
559
+ '2a19',
560
+ 'read-tx'
561
+ );
562
+ });
563
+ });