Commit 5c2f54e

derdilla <82763757+NobodyForNothing@users.noreply.github.com>
2024-05-07 16:56:13
test device scan cubit
Signed-off-by: derdilla <82763757+NobodyForNothing@users.noreply.github.com>
1 parent 55dccad
Changed files (2)
app
app/lib/bluetooth/device_scan_cubit.dart
@@ -2,6 +2,7 @@ import 'dart:async';
 
 import 'package:blood_pressure_app/bluetooth/bluetooth_cubit.dart';
 import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:blood_pressure_app/bluetooth/logging.dart';
 import 'package:blood_pressure_app/model/storage/settings_store.dart';
 import 'package:collection/collection.dart';
 import 'package:flutter/foundation.dart';
@@ -39,13 +40,10 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
   final FlutterBluePlusMockable _flutterBluePlus;
 
   late StreamSubscription<List<ScanResult>> _scanResultsSubscription;
-  late StreamSubscription<bool> _isScanningSubscription;
-  bool _isScanning = false;
 
   @override
   Future<void> close() async {
     await _scanResultsSubscription.cancel();
-    await _isScanningSubscription.cancel();
     await super.close();
   }
 
@@ -65,12 +63,12 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
     } catch (e) {
       _onScanError(e);
     }
-    _isScanningSubscription = _flutterBluePlus.isScanning
-        .listen(_onIsScanningChanged);
   }
 
   void _onScanResult(List<ScanResult> devices) {
-    assert(_isScanning);
+    assert(_flutterBluePlus.isScanningNow);
+    // No need to check whether the devices really support the searched
+    // characteristic as users have to select their device anyways.
     if(state is DeviceSelected) return;
     final preferred = devices.firstWhereOrNull((dev) =>
         settings.knownBleDev.contains(dev.device.advName));
@@ -86,12 +84,7 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
   }
 
   void _onScanError(Object error) {
-    // TODO
-  }
-
-  void _onIsScanningChanged(bool isScanning) {
-    _isScanning = isScanning;
-    // TODO: consider restarting
+    Log.err('Starting device scan failed');
   }
 
   /// Mark a new device as known and switch to selected device state asap.
@@ -103,16 +96,18 @@ class DeviceScanCubit extends Cubit<DeviceScanState> {
       _onScanError(e);
       return;
     }
-    assert(!_isScanning);
+    assert(!_flutterBluePlus.isScanningNow);
     emit(DeviceSelected(device));
-    settings.knownBleDev.add(device.advName); // TODO: does this work?
+    final List<String> list = settings.knownBleDev.toList();
+    list.add(device.advName);
+    settings.knownBleDev = list;
   }
 
   /// Remove all known devices and start scanning again.
   Future<void> clearKnownDevices() async {
     settings.knownBleDev = [];
     emit(DeviceListLoading());
-    if (!_isScanning) await _startScanning();
+    if (!_flutterBluePlus.isScanningNow) await _startScanning();
   }
 
 }
app/test/bluetooth/device_scan_cubit_test.dart
@@ -0,0 +1,110 @@
+import 'dart:async';
+
+import 'package:blood_pressure_app/bluetooth/device_scan_cubit.dart';
+import 'package:blood_pressure_app/bluetooth/flutter_blue_plus_mockable.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+import 'package:flutter_blue_plus/flutter_blue_plus.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:mockito/mockito.dart';
+
+@GenerateNiceMocks([
+  MockSpec<BluetoothDevice>(),
+  MockSpec<BluetoothService>(),
+  MockSpec<AdvertisementData>(),
+  MockSpec<FlutterBluePlusMockable>(),
+  MockSpec<ScanResult>(),
+])
+import 'device_scan_cubit_test.mocks.dart';
+
+void main() {
+  test('finds and connects to devices', () async {
+    final StreamController<List<ScanResult>> mockResults = StreamController.broadcast();
+    final settings = Settings();
+
+    final flutterBluePlus = MockFlutterBluePlusMockable();
+    when(flutterBluePlus.startScan(
+        withServices: [Guid('1810')]
+    )).thenAnswer((_) async {
+      when(flutterBluePlus.isScanningNow).thenReturn(true);
+    });
+    when(flutterBluePlus.stopScan()).thenAnswer((_) async {
+      when(flutterBluePlus.isScanningNow).thenReturn(false);
+    });
+    when(flutterBluePlus.scanResults).thenAnswer((_) => mockResults.stream);
+    final cubit = DeviceScanCubit(
+        service: Guid('1810'),
+        settings: settings,
+        flutterBluePlus: flutterBluePlus
+    );
+    expect(cubit.state, isA<DeviceListLoading>());
+
+    final wrongRes0 = MockScanResult();
+    final wrongDev0 = MockBluetoothDevice();
+    final wrongRes1 = MockScanResult();
+    final wrongDev1 = MockBluetoothDevice();
+    when(wrongDev0.advName).thenReturn('wrongDev0');
+    when(wrongRes0.device).thenReturn(wrongDev0);
+    when(wrongDev1.advName).thenReturn('wrongDev1');
+    when(wrongRes1.device).thenReturn(wrongDev1);
+    mockResults.sink.add([wrongRes0]);
+    await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()));
+
+    mockResults.sink.add([wrongRes0, wrongRes1]);
+    await expectLater(cubit.stream, emits(isA<DeviceListAvailable>()));
+
+    final dev = MockBluetoothDevice();
+    when(dev.advName).thenReturn('testDev');
+    final res = MockScanResult();
+    when(res.device).thenReturn(dev);
+
+    mockResults.sink.add([res]);
+    await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()
+        .having((r) => r.device.device, 'device', dev)));
+
+    expect(settings.knownBleDev, isEmpty);
+    await cubit.acceptDevice(dev);
+    expect(settings.knownBleDev, contains('testDev'));
+    // state should be set as we await above
+    await expectLater(cubit.state, isA<DeviceSelected>()
+        .having((s) => s.device, 'device', dev));
+  });
+  test('recognizes devices', () async {
+    final StreamController<List<ScanResult>> mockResults = StreamController.broadcast();
+    final settings = Settings(
+      knownBleDev: ['testDev']
+    );
+
+    final flutterBluePlus = MockFlutterBluePlusMockable();
+    when(flutterBluePlus.startScan(
+      withServices: [Guid('1810')]
+    )).thenAnswer((_) async {
+      when(flutterBluePlus.isScanningNow).thenReturn(true);
+    });
+    when(flutterBluePlus.stopScan()).thenAnswer((_) async {
+      when(flutterBluePlus.isScanningNow).thenReturn(false);
+    });
+    when(flutterBluePlus.scanResults).thenAnswer((_) => mockResults.stream);
+    final cubit = DeviceScanCubit(
+      service: Guid('1810'),
+      settings: settings,
+      flutterBluePlus: flutterBluePlus
+    );
+    expect(cubit.state, isA<DeviceListLoading>());
+
+    final wrongRes0 = MockScanResult();
+    final wrongDev0 = MockBluetoothDevice();
+    when(wrongDev0.advName).thenReturn('wrongDev0');
+    when(wrongRes0.device).thenReturn(wrongDev0);
+    mockResults.sink.add([wrongRes0]);
+    await expectLater(cubit.stream, emits(isA<SingleDeviceAvailable>()));
+
+    final dev = MockBluetoothDevice();
+    when(dev.advName).thenReturn('testDev');
+    final res = MockScanResult();
+    when(res.device).thenReturn(dev);
+    mockResults.sink.add([wrongRes0, res]);
+    await expectLater(cubit.stream, emits(isA<DeviceSelected>()
+        .having((s) => s.device, 'device', dev)));
+  });
+}