Commit 153133a

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 de34245
Changed files (3)
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)));
+  });
+}
app/pubspec.lock
@@ -464,18 +464,18 @@ packages:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+      sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
       url: "https://pub.dev"
     source: hosted
-    version: "10.0.4"
+    version: "10.0.5"
   leak_tracker_flutter_testing:
     dependency: transitive
     description:
       name: leak_tracker_flutter_testing
-      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+      sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.3"
+    version: "3.0.5"
   leak_tracker_testing:
     dependency: transitive
     description:
@@ -520,18 +520,18 @@ packages:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
       url: "https://pub.dev"
     source: hosted
-    version: "0.8.0"
+    version: "0.11.1"
   meta:
     dependency: transitive
     description:
       name: meta
-      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+      sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
       url: "https://pub.dev"
     source: hosted
-    version: "1.12.0"
+    version: "1.14.0"
   mime:
     dependency: transitive
     description:
@@ -941,10 +941,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+      sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.0"
+    version: "0.7.1"
   timing:
     dependency: transitive
     description: