[connectivity_for_web] Migration to null-safety. (#3652)

diff --git a/packages/connectivity/connectivity_for_web/CHANGELOG.md b/packages/connectivity/connectivity_for_web/CHANGELOG.md
index f6d83dd..ccd6897 100644
--- a/packages/connectivity/connectivity_for_web/CHANGELOG.md
+++ b/packages/connectivity/connectivity_for_web/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.4.0
+
+* Migrate to null-safety
+* Run tests using flutter driver
+
 ## 0.3.1+4
 
 * Remove unused `test` dependency.
diff --git a/packages/connectivity/connectivity_for_web/example/README.md b/packages/connectivity/connectivity_for_web/example/README.md
new file mode 100644
index 0000000..0ec01e0
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/README.md
@@ -0,0 +1,21 @@
+# Testing
+
+This package utilizes the `integration_test` package to run its tests in a web browser.
+
+See [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) for more info.
+
+## Running the tests
+
+Make sure you have updated to the latest Flutter master.
+
+1. Check what version of Chrome is running on the machine you're running tests on.
+
+2. Download and install driver for that version from here:
+    * <https://chromedriver.chromium.org/downloads>
+
+3. Start the driver using `chromedriver --port=4444`
+
+4. Run tests: `flutter drive -d web-server --browser-name=chrome --driver=test_driver/integration_driver.dart --target=integration_test/TEST_NAME.dart`, or (in Linux):
+
+    * Single: `./run_test.sh integration_test/TEST_NAME.dart`
+    * All: `./run_test.sh`
diff --git a/packages/connectivity/connectivity_for_web/example/integration_test/network_information_test.dart b/packages/connectivity/connectivity_for_web/example/integration_test/network_information_test.dart
new file mode 100644
index 0000000..f8e8059
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/integration_test/network_information_test.dart
@@ -0,0 +1,79 @@
+import 'package:connectivity_for_web/src/network_information_api_connectivity_plugin.dart';
+import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'src/connectivity_mocks.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('checkConnectivity', () {
+    void testCheckConnectivity({
+      String? type,
+      String? effectiveType,
+      num? downlink = 10,
+      int? rtt = 50,
+      required ConnectivityResult expected,
+    }) {
+      final connection = FakeNetworkInformation(
+        type: type,
+        effectiveType: effectiveType,
+        downlink: downlink,
+        rtt: rtt,
+      );
+
+      NetworkInformationApiConnectivityPlugin plugin =
+          NetworkInformationApiConnectivityPlugin.withConnection(connection);
+      expect(plugin.checkConnectivity(), completion(equals(expected)));
+    }
+
+    testWidgets('0 downlink and rtt -> none', (WidgetTester tester) async {
+      testCheckConnectivity(
+          effectiveType: '4g',
+          downlink: 0,
+          rtt: 0,
+          expected: ConnectivityResult.none);
+    });
+    testWidgets('slow-2g -> mobile', (WidgetTester tester) async {
+      testCheckConnectivity(
+          effectiveType: 'slow-2g', expected: ConnectivityResult.mobile);
+    });
+    testWidgets('2g -> mobile', (WidgetTester tester) async {
+      testCheckConnectivity(
+          effectiveType: '2g', expected: ConnectivityResult.mobile);
+    });
+    testWidgets('3g -> mobile', (WidgetTester tester) async {
+      testCheckConnectivity(
+          effectiveType: '3g', expected: ConnectivityResult.mobile);
+    });
+    testWidgets('4g -> wifi', (WidgetTester tester) async {
+      testCheckConnectivity(
+          effectiveType: '4g', expected: ConnectivityResult.wifi);
+    });
+  });
+
+  group('get onConnectivityChanged', () {
+    testWidgets('puts change events in a Stream', (WidgetTester tester) async {
+      final connection = FakeNetworkInformation();
+      NetworkInformationApiConnectivityPlugin plugin =
+          NetworkInformationApiConnectivityPlugin.withConnection(connection);
+
+      // The onConnectivityChanged stream is infinite, so we only .take(2) so the test completes.
+      // We need to do .toList() now, because otherwise the Stream won't be actually listened to,
+      // and we'll miss the calls to mockChangeValue below.
+      final results = plugin.onConnectivityChanged.take(2).toList();
+
+      // Fake a disconnect-reconnect
+      await connection.mockChangeValue(downlink: 0, rtt: 0);
+      await connection.mockChangeValue(
+          downlink: 10, rtt: 50, effectiveType: '4g');
+
+      // Expect to see the disconnect-reconnect in the resulting stream.
+      expect(
+        results,
+        completion([ConnectivityResult.none, ConnectivityResult.wifi]),
+      );
+    });
+  });
+}
diff --git a/packages/connectivity/connectivity_for_web/example/integration_test/src/connectivity_mocks.dart b/packages/connectivity/connectivity_for_web/example/integration_test/src/connectivity_mocks.dart
new file mode 100644
index 0000000..fc79559
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/integration_test/src/connectivity_mocks.dart
@@ -0,0 +1,56 @@
+import 'dart:async';
+import 'dart:html';
+import 'dart:js_util' show getProperty;
+
+import 'package:flutter_test/flutter_test.dart';
+
+/// A Fake implementation of the NetworkInformation API that allows
+/// for external modification of its values.
+///
+/// Note that the DOM API works by internally mutating and broadcasting
+/// 'change' events.
+class FakeNetworkInformation extends Fake implements NetworkInformation {
+  String? _type;
+  String? _effectiveType;
+  num? _downlink;
+  int? _rtt;
+
+  @override
+  String? get type => _type;
+
+  @override
+  String? get effectiveType => _effectiveType;
+
+  @override
+  num? get downlink => _downlink;
+
+  @override
+  int? get rtt => _rtt;
+
+  FakeNetworkInformation({
+    String? type,
+    String? effectiveType,
+    num? downlink,
+    int? rtt,
+  })  : this._type = type,
+        this._effectiveType = effectiveType,
+        this._downlink = downlink,
+        this._rtt = rtt;
+
+  /// Changes the desired values, and triggers the change event listener.
+  Future<void> mockChangeValue({
+    String? type,
+    String? effectiveType,
+    num? downlink,
+    int? rtt,
+  }) async {
+    this._type = type;
+    this._effectiveType = effectiveType;
+    this._downlink = downlink;
+    this._rtt = rtt;
+
+    // This is set by the onConnectivityChanged getter...
+    final Function onchange = getProperty(this, 'onchange') as Function;
+    onchange(Event('change'));
+  }
+}
diff --git a/packages/connectivity/connectivity_for_web/example/lib/main.dart b/packages/connectivity/connectivity_for_web/example/lib/main.dart
new file mode 100644
index 0000000..e1a38dc
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/lib/main.dart
@@ -0,0 +1,25 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+
+void main() {
+  runApp(MyApp());
+}
+
+/// App for testing
+class MyApp extends StatefulWidget {
+  @override
+  _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  @override
+  Widget build(BuildContext context) {
+    return Directionality(
+      textDirection: TextDirection.ltr,
+      child: Text('Testing... Look at the console output for results!'),
+    );
+  }
+}
diff --git a/packages/connectivity/connectivity_for_web/example/pubspec.yaml b/packages/connectivity/connectivity_for_web/example/pubspec.yaml
new file mode 100644
index 0000000..54289bc
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/pubspec.yaml
@@ -0,0 +1,21 @@
+name: connectivity_for_web_integration_tests
+publish_to: none
+
+dependencies:
+  connectivity_for_web:
+    path: ../
+  flutter:
+    sdk: flutter
+
+dev_dependencies:
+  js: ^0.6.3
+  flutter_test:
+    sdk: flutter
+  flutter_driver:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+
+environment:
+  sdk: ">=2.12.0-259.9.beta <3.0.0"
+  flutter: ">=1.27.0-0" # For integration_test from sdk
diff --git a/packages/connectivity/connectivity_for_web/example/run_test.sh b/packages/connectivity/connectivity_for_web/example/run_test.sh
new file mode 100755
index 0000000..8e6f149
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/run_test.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/bash
+if pgrep -lf chromedriver > /dev/null; then
+  echo "chromedriver is running."
+
+  if [ $# -eq 0 ]; then
+    echo "No target specified, running all tests..."
+    find integration_test/ -iname *_test.dart | xargs -n1 -i -t flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_driver.dart --target='{}'
+  else
+    echo "Running test target: $1..."
+    set -x
+    flutter drive -d web-server --web-port=7357 --browser-name=chrome --driver=test_driver/integration_driver.dart --target=$1
+  fi
+
+  else
+    echo "chromedriver is not running."
+    echo "Please, check the README.md for instructions on how to use run_test.sh"
+fi
+
diff --git a/packages/connectivity/connectivity_for_web/example/test_driver/integration_driver.dart b/packages/connectivity/connectivity_for_web/example/test_driver/integration_driver.dart
new file mode 100644
index 0000000..64e2248
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/example/test_driver/integration_driver.dart
@@ -0,0 +1,7 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() async => integrationDriver();
diff --git a/packages/connectivity/connectivity_for_web/test/web/index.html b/packages/connectivity/connectivity_for_web/example/web/index.html
similarity index 100%
rename from packages/connectivity/connectivity_for_web/test/web/index.html
rename to packages/connectivity/connectivity_for_web/example/web/index.html
diff --git a/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart b/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart
index 5caa567..950d268 100644
--- a/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart
+++ b/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart
@@ -9,26 +9,26 @@
   /// Checks the connection status of the device.
   @override
   Future<ConnectivityResult> checkConnectivity() async {
-    return html.window.navigator.onLine
+    return html.window.navigator.onLine ?? false
         ? ConnectivityResult.wifi
         : ConnectivityResult.none;
   }
 
-  StreamController<ConnectivityResult> _connectivityResult;
+  StreamController<ConnectivityResult>? _connectivityResult;
 
   /// Returns a Stream of ConnectivityResults changes.
   @override
   Stream<ConnectivityResult> get onConnectivityChanged {
     if (_connectivityResult == null) {
-      _connectivityResult = StreamController<ConnectivityResult>();
+      _connectivityResult = StreamController<ConnectivityResult>.broadcast();
       // Fallback to dart:html window.onOnline / window.onOffline
       html.window.onOnline.listen((event) {
-        _connectivityResult.add(ConnectivityResult.wifi);
+        _connectivityResult!.add(ConnectivityResult.wifi);
       });
       html.window.onOffline.listen((event) {
-        _connectivityResult.add(ConnectivityResult.none);
+        _connectivityResult!.add(ConnectivityResult.none);
       });
     }
-    return _connectivityResult.stream;
+    return _connectivityResult!.stream;
   }
 }
diff --git a/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart b/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart
index 99bac2a..800be2e 100644
--- a/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart
+++ b/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart
@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:html' as html show window, NetworkInformation;
-import 'dart:js';
-import 'dart:js_util';
+import 'dart:js' show allowInterop;
+import 'dart:js_util' show setProperty;
 
 import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
 import 'package:connectivity_for_web/connectivity_for_web.dart';
@@ -18,7 +18,7 @@
 
   /// The constructor of the plugin.
   NetworkInformationApiConnectivityPlugin()
-      : this.withConnection(html.window.navigator.connection);
+      : this.withConnection(html.window.navigator.connection!);
 
   /// Creates the plugin, with an override of the NetworkInformation object.
   @visibleForTesting
@@ -32,8 +32,8 @@
     return networkInformationToConnectivityResult(_networkInformation);
   }
 
-  StreamController<ConnectivityResult> _connectivityResultStreamController;
-  Stream<ConnectivityResult> _connectivityResultStream;
+  StreamController<ConnectivityResult>? _connectivityResultStreamController;
+  late Stream<ConnectivityResult> _connectivityResultStream;
 
   /// Returns a Stream of ConnectivityResults changes.
   @override
@@ -41,8 +41,10 @@
     if (_connectivityResultStreamController == null) {
       _connectivityResultStreamController =
           StreamController<ConnectivityResult>();
+
+      // Directly write the 'onchange' function on the networkInformation object.
       setProperty(_networkInformation, 'onchange', allowInterop((_) {
-        _connectivityResultStreamController
+        _connectivityResultStreamController!
             .add(networkInformationToConnectivityResult(_networkInformation));
       }));
       // TODO: Implement the above with _networkInformation.onChange:
@@ -54,7 +56,7 @@
       // onChange Stream upon hot restart.
       // https://github.com/dart-lang/sdk/issues/42679
       _connectivityResultStream =
-          _connectivityResultStreamController.stream.asBroadcastStream();
+          _connectivityResultStreamController!.stream.asBroadcastStream();
     }
     return _connectivityResultStream;
   }
diff --git a/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart b/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart
index efefd8d..e7eb896 100644
--- a/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart
+++ b/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart
@@ -3,7 +3,7 @@
 
 /// Converts an incoming NetworkInformation object into the correct ConnectivityResult.
 ConnectivityResult networkInformationToConnectivityResult(
-  html.NetworkInformation info,
+  html.NetworkInformation? info,
 ) {
   if (info == null) {
     return ConnectivityResult.none;
@@ -12,10 +12,10 @@
     return ConnectivityResult.none;
   }
   if (info.effectiveType != null) {
-    return _effectiveTypeToConnectivityResult(info.effectiveType);
+    return _effectiveTypeToConnectivityResult(info.effectiveType!);
   }
   if (info.type != null) {
-    return _typeToConnectivityResult(info.type);
+    return _typeToConnectivityResult(info.type!);
   }
   return ConnectivityResult.none;
 }
diff --git a/packages/connectivity/connectivity_for_web/pubspec.yaml b/packages/connectivity/connectivity_for_web/pubspec.yaml
index 3622b15..5c673e8 100644
--- a/packages/connectivity/connectivity_for_web/pubspec.yaml
+++ b/packages/connectivity/connectivity_for_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: connectivity_for_web
 description: An implementation for the web platform of the Flutter `connectivity` plugin. This uses the NetworkInformation Web API, with a fallback to Navigator.onLine.
-version: 0.3.1+4
 repository: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_for_web
+version: 0.4.0
 
 flutter:
   plugin:
@@ -11,21 +11,16 @@
         fileName: connectivity_for_web.dart
 
 dependencies:
-  connectivity_platform_interface: ^1.0.3
+  connectivity_platform_interface: ^2.0.0
   flutter_web_plugins:
     sdk: flutter
   flutter:
     sdk: flutter
 
 dev_dependencies:
-  flutter_driver:
-    sdk: flutter
   flutter_test:
     sdk: flutter
-  integration_test:
-    path: ../../integration_test
-  mockito: ^4.1.1
 
 environment:
-  sdk: ">=2.6.0 <3.0.0"
-  flutter: ">=1.12.13+hotfix.4"
+  sdk: ">=2.12.0-259.9.beta <3.0.0"
+  flutter: ">=1.20.0"
diff --git a/packages/connectivity/connectivity_for_web/test/.gitignore b/packages/connectivity/connectivity_for_web/test/.gitignore
deleted file mode 100644
index d7dee82..0000000
--- a/packages/connectivity/connectivity_for_web/test/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-.DS_Store
-.dart_tool/
-
-.packages
-.pub/
-
-build/
-lib/generated_plugin_registrant.dart
diff --git a/packages/connectivity/connectivity_for_web/test/README.md b/packages/connectivity/connectivity_for_web/test/README.md
new file mode 100644
index 0000000..7c5b4ad
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/test/README.md
@@ -0,0 +1,5 @@
+## test
+
+This package uses integration tests for testing.
+
+See `example/README.md` for more info.
diff --git a/packages/connectivity/connectivity_for_web/test/lib/main.dart b/packages/connectivity/connectivity_for_web/test/lib/main.dart
deleted file mode 100644
index e347353..0000000
--- a/packages/connectivity/connectivity_for_web/test/lib/main.dart
+++ /dev/null
@@ -1,77 +0,0 @@
-import 'package:integration_test/integration_test.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:connectivity_platform_interface/connectivity_platform_interface.dart';
-import 'package:connectivity_for_web/src/network_information_api_connectivity_plugin.dart';
-
-import 'package:mockito/mockito.dart';
-
-import 'src/connectivity_mocks.dart';
-
-void main() {
-  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
-  group('checkConnectivity', () {
-    void testCheckConnectivity({
-      String type,
-      String effectiveType,
-      num downlink = 10,
-      num rtt = 50,
-      ConnectivityResult expected,
-    }) {
-      final connection = MockNetworkInformation();
-      when(connection.type).thenReturn(type);
-      when(connection.effectiveType).thenReturn(effectiveType);
-      when(connection.downlink).thenReturn(downlink);
-      when(connection.rtt).thenReturn(downlink);
-
-      NetworkInformationApiConnectivityPlugin plugin =
-          NetworkInformationApiConnectivityPlugin.withConnection(connection);
-      expect(plugin.checkConnectivity(), completion(equals(expected)));
-    }
-
-    test('0 downlink and rtt -> none', () {
-      testCheckConnectivity(
-          effectiveType: '4g',
-          downlink: 0,
-          rtt: 0,
-          expected: ConnectivityResult.none);
-    });
-    test('slow-2g -> mobile', () {
-      testCheckConnectivity(
-          effectiveType: 'slow-2g', expected: ConnectivityResult.mobile);
-    });
-    test('2g -> mobile', () {
-      testCheckConnectivity(
-          effectiveType: '2g', expected: ConnectivityResult.mobile);
-    });
-    test('3g -> mobile', () {
-      testCheckConnectivity(
-          effectiveType: '3g', expected: ConnectivityResult.mobile);
-    });
-    test('4g -> wifi', () {
-      testCheckConnectivity(
-          effectiveType: '4g', expected: ConnectivityResult.wifi);
-    });
-  });
-
-  group('get onConnectivityChanged', () {
-    test('puts change events in a Stream', () async {
-      final connection = MockNetworkInformation();
-      NetworkInformationApiConnectivityPlugin plugin =
-          NetworkInformationApiConnectivityPlugin.withConnection(connection);
-
-      Stream<ConnectivityResult> results = plugin.onConnectivityChanged;
-
-      // Fake a disconnect-reconnect
-      await connection.mockChangeValue(downlink: 0, rtt: 0);
-      await connection.mockChangeValue(
-          downlink: 10, rtt: 50, effectiveType: '4g');
-
-      // The stream of results is infinite, so we need to .take(2) for this test to complete.
-      expect(
-          results.take(2).toList(),
-          completion(
-              equals([ConnectivityResult.none, ConnectivityResult.wifi])));
-    });
-  });
-}
diff --git a/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart b/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart
deleted file mode 100644
index 7b82b51..0000000
--- a/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart
+++ /dev/null
@@ -1,25 +0,0 @@
-import 'dart:html';
-
-import 'package:mockito/mockito.dart';
-
-/// A Mock implementation of the NetworkInformation API that allows
-/// for external modification of its values.
-class MockNetworkInformation extends Mock implements NetworkInformation {
-  /// The callback that will fire after the network information values change.
-  Function onchange;
-
-  /// Changes the desired values, and triggers the change event listener.
-  void mockChangeValue({
-    String type,
-    String effectiveType,
-    num downlink,
-    num rtt,
-  }) async {
-    when(this.type).thenAnswer((_) => type);
-    when(this.effectiveType).thenAnswer((_) => effectiveType);
-    when(this.downlink).thenAnswer((_) => downlink);
-    when(this.rtt).thenAnswer((_) => rtt);
-
-    onchange(Event('change'));
-  }
-}
diff --git a/packages/connectivity/connectivity_for_web/test/pubspec.yaml b/packages/connectivity/connectivity_for_web/test/pubspec.yaml
deleted file mode 100644
index 512b7df..0000000
--- a/packages/connectivity/connectivity_for_web/test/pubspec.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: connectivity_web_example
-description: Example web app for the connectivity plugin
-homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_web
-
-dependencies:
-  connectivity_for_web:
-    path: ../
-  js: ^0.6.1+1
-  flutter_web_plugins:
-    sdk: flutter
-  flutter:
-    sdk: flutter
-
-dev_dependencies:
-  flutter_test:
-    sdk: flutter
-  flutter_driver:
-    sdk: flutter
-  integration_test:
-    path: ../../../integration_test
-  mockito: ^4.1.1
-
-environment:
-  sdk: ">=2.6.0 <3.0.0"
-  flutter: ">=1.12.13+hotfix.4"
diff --git a/packages/connectivity/connectivity_for_web/test/tests_exist_elsewhere_test.dart b/packages/connectivity/connectivity_for_web/test/tests_exist_elsewhere_test.dart
new file mode 100644
index 0000000..334f521
--- /dev/null
+++ b/packages/connectivity/connectivity_for_web/test/tests_exist_elsewhere_test.dart
@@ -0,0 +1,10 @@
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('Tell the user where to find the real tests', () {
+    print('---');
+    print('This package uses integration_test for its tests.');
+    print('See `example/README.md` for more info.');
+    print('---');
+  });
+}