[file_selector_web] Migrated to null-safety (#3550)


Co-authored-by: David Iglesias Teixeira <ditman@gmail.com>
diff --git a/packages/file_selector/file_selector_web/CHANGELOG.md b/packages/file_selector/file_selector_web/CHANGELOG.md
index 619aa76..4caad4b 100644
--- a/packages/file_selector/file_selector_web/CHANGELOG.md
+++ b/packages/file_selector/file_selector_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.8.0
+
+- Migrated to null-safety
+
 # 0.7.0+1
 
 - Add dummy `ios` dir, so flutter sdk can be lower than 1.20
diff --git a/packages/file_selector/file_selector_web/example/README.md b/packages/file_selector/file_selector_web/example/README.md
new file mode 100644
index 0000000..6187e55
--- /dev/null
+++ b/packages/file_selector/file_selector_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_test.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/file_selector/file_selector_web/integration_test/dom_helper_test.dart b/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart
similarity index 91%
rename from packages/file_selector/file_selector_web/integration_test/dom_helper_test.dart
rename to packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart
index a942c0d..274aed9 100644
--- a/packages/file_selector/file_selector_web/integration_test/dom_helper_test.dart
+++ b/packages/file_selector/file_selector_web/example/integration_test/dom_helper_test.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'dart:html';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:integration_test/integration_test.dart';
@@ -11,19 +9,19 @@
 import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
 
 void main() {
-  group('FileSelectorWeb', () {
+  group('dom_helper', () {
     IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-    DomHelper domHelper;
-    FileUploadInputElement input;
+    late DomHelper domHelper;
+    late FileUploadInputElement input;
 
-    FileList FileListItems(List<File> files) {
+    FileList? createFileList(List<File> files) {
       final dataTransfer = DataTransfer();
-      files.forEach(dataTransfer.items.add);
-      return dataTransfer.files;
+      files.forEach(dataTransfer.items!.add);
+      return dataTransfer.files as FileList?;
     }
 
     void setFilesAndTriggerChange(List<File> files) {
-      input.files = FileListItems(files);
+      input.files = createFileList(files);
       input.dispatchEvent(Event('change'));
     }
 
diff --git a/packages/file_selector/file_selector_web/integration_test/file_selector_web_test.dart b/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart
similarity index 60%
rename from packages/file_selector/file_selector_web/integration_test/file_selector_web_test.dart
rename to packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart
index abd31dd..5442fed 100644
--- a/packages/file_selector/file_selector_web/integration_test/file_selector_web_test.dart
+++ b/packages/file_selector/file_selector_web/example/integration_test/file_selector_web_test.dart
@@ -2,11 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.9
-
+import 'dart:html';
 import 'dart:typed_data';
 import 'package:flutter_test/flutter_test.dart';
-import 'package:mockito/mockito.dart';
 import 'package:integration_test/integration_test.dart';
 import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
 import 'package:file_selector_web/file_selector_web.dart';
@@ -15,18 +13,18 @@
 void main() {
   group('FileSelectorWeb', () {
     IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-    MockDomHelper mockDomHelper;
-    FileSelectorWeb plugin;
-
-    setUp(() {
-      mockDomHelper = MockDomHelper();
-      plugin = FileSelectorWeb(domHelper: mockDomHelper);
-    });
 
     group('openFile', () {
-      final mockFile = createXFile('1001', 'identity.png');
-
       testWidgets('works', (WidgetTester _) async {
+        final mockFile = createXFile('1001', 'identity.png');
+
+        final mockDomHelper = MockDomHelper()
+          ..setFiles([mockFile])
+          ..expectAccept('.jpg,.jpeg,image/png,image/*')
+          ..expectMultiple(false);
+
+        final plugin = FileSelectorWeb(domHelper: mockDomHelper);
+
         final typeGroup = XTypeGroup(
           label: 'images',
           extensions: ['jpg', 'jpeg'],
@@ -34,11 +32,6 @@
           webWildCards: ['image/*'],
         );
 
-        when(mockDomHelper.getFiles(
-          accept: '.jpg,.jpeg,image/png,image/*',
-          multiple: false,
-        )).thenAnswer((_) async => [mockFile]);
-
         final file = await plugin.openFile(acceptedTypeGroups: [typeGroup]);
 
         expect(file.name, mockFile.name);
@@ -49,20 +42,22 @@
     });
 
     group('openFiles', () {
-      final mockFile1 = createXFile('123456', 'file1.txt');
-      final mockFile2 = createXFile('', 'file2.txt');
-
       testWidgets('works', (WidgetTester _) async {
+        final mockFile1 = createXFile('123456', 'file1.txt');
+        final mockFile2 = createXFile('', 'file2.txt');
+
+        final mockDomHelper = MockDomHelper()
+          ..setFiles([mockFile1, mockFile2])
+          ..expectAccept('.txt')
+          ..expectMultiple(true);
+
+        final plugin = FileSelectorWeb(domHelper: mockDomHelper);
+
         final typeGroup = XTypeGroup(
           label: 'files',
           extensions: ['.txt'],
         );
 
-        when(mockDomHelper.getFiles(
-          accept: '.txt',
-          multiple: true,
-        )).thenAnswer((_) async => [mockFile1, mockFile2]);
-
         final files = await plugin.openFiles(acceptedTypeGroups: [typeGroup]);
 
         expect(files.length, 2);
@@ -81,7 +76,36 @@
   });
 }
 
-class MockDomHelper extends Mock implements DomHelper {}
+class MockDomHelper implements DomHelper {
+  List<XFile> _files = <XFile>[];
+  String _expectedAccept = '';
+  bool _expectedMultiple = false;
+
+  @override
+  Future<List<XFile>> getFiles({
+    String accept = '',
+    bool multiple = false,
+    FileUploadInputElement? input,
+  }) {
+    expect(accept, _expectedAccept,
+        reason: 'Expected "accept" value does not match.');
+    expect(multiple, _expectedMultiple,
+        reason: 'Expected "multiple" value does not match.');
+    return Future.value(_files);
+  }
+
+  void setFiles(List<XFile> files) {
+    _files = files;
+  }
+
+  void expectAccept(String accept) {
+    _expectedAccept = accept;
+  }
+
+  void expectMultiple(bool multiple) {
+    _expectedMultiple = multiple;
+  }
+}
 
 XFile createXFile(String content, String name) {
   final data = Uint8List.fromList(content.codeUnits);
diff --git a/packages/file_selector/file_selector_web/example/lib/main.dart b/packages/file_selector/file_selector_web/example/lib/main.dart
new file mode 100644
index 0000000..e1a38dc
--- /dev/null
+++ b/packages/file_selector/file_selector_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/file_selector/file_selector_web/example/pubspec.yaml b/packages/file_selector/file_selector_web/example/pubspec.yaml
new file mode 100644
index 0000000..cae4b13
--- /dev/null
+++ b/packages/file_selector/file_selector_web/example/pubspec.yaml
@@ -0,0 +1,21 @@
+name: file_selector_web_integration_tests
+publish_to: none
+
+dependencies:
+  flutter:
+    sdk: flutter
+
+dev_dependencies:
+  build_runner: ^1.10.0
+  file_selector_web:
+    path: ../
+  flutter_driver:
+    sdk: flutter
+  flutter_test:
+    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/file_selector/file_selector_web/run_integration_test b/packages/file_selector/file_selector_web/example/run_test.sh
similarity index 100%
rename from packages/file_selector/file_selector_web/run_integration_test
rename to packages/file_selector/file_selector_web/example/run_test.sh
diff --git a/packages/file_selector/file_selector_web/test_driver/integration_test.dart b/packages/file_selector/file_selector_web/example/test_driver/integration_test.dart
similarity index 100%
rename from packages/file_selector/file_selector_web/test_driver/integration_test.dart
rename to packages/file_selector/file_selector_web/example/test_driver/integration_test.dart
diff --git a/packages/file_selector/file_selector_web/example/web/index.html b/packages/file_selector/file_selector_web/example/web/index.html
new file mode 100644
index 0000000..dc8d0cf
--- /dev/null
+++ b/packages/file_selector/file_selector_web/example/web/index.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<!-- Copyright 2014 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. -->
+<html>
+    <head>
+        <title>Browser Tests</title>
+    </head>
+    <body>
+        <script src="main.dart.js"></script>
+    </body>
+</html>
diff --git a/packages/file_selector/file_selector_web/lib/file_selector_web.dart b/packages/file_selector/file_selector_web/lib/file_selector_web.dart
index 48f57ee..1c411ca 100644
--- a/packages/file_selector/file_selector_web/lib/file_selector_web.dart
+++ b/packages/file_selector/file_selector_web/lib/file_selector_web.dart
@@ -13,7 +13,7 @@
 ///
 /// This class implements the `package:file_selector` functionality for the web.
 class FileSelectorWeb extends FileSelectorPlatform {
-  final _domHelper;
+  final DomHelper _domHelper;
 
   /// Registers this class as the default instance of [FileSelectorPlatform].
   static void registerWith(Registrar registrar) {
@@ -23,14 +23,14 @@
   /// Default constructor, initializes _domHelper that we can use
   /// to interact with the DOM.
   /// overrides parameter allows for testing to override functions
-  FileSelectorWeb({@visibleForTesting DomHelper domHelper})
+  FileSelectorWeb({@visibleForTesting DomHelper? domHelper})
       : _domHelper = domHelper ?? DomHelper();
 
   @override
   Future<XFile> openFile({
-    List<XTypeGroup> acceptedTypeGroups,
-    String initialDirectory,
-    String confirmButtonText,
+    List<XTypeGroup>? acceptedTypeGroups,
+    String? initialDirectory,
+    String? confirmButtonText,
   }) async {
     final files = await _openFiles(acceptedTypeGroups: acceptedTypeGroups);
     return files.first;
@@ -38,31 +38,31 @@
 
   @override
   Future<List<XFile>> openFiles({
-    List<XTypeGroup> acceptedTypeGroups,
-    String initialDirectory,
-    String confirmButtonText,
+    List<XTypeGroup>? acceptedTypeGroups,
+    String? initialDirectory,
+    String? confirmButtonText,
   }) async {
     return _openFiles(acceptedTypeGroups: acceptedTypeGroups, multiple: true);
   }
 
   @override
-  Future<String> getSavePath({
-    List<XTypeGroup> acceptedTypeGroups,
-    String initialDirectory,
-    String suggestedName,
-    String confirmButtonText,
+  Future<String?> getSavePath({
+    List<XTypeGroup>? acceptedTypeGroups,
+    String? initialDirectory,
+    String? suggestedName,
+    String? confirmButtonText,
   }) async =>
       null;
 
   @override
-  Future<String> getDirectoryPath({
-    String initialDirectory,
-    String confirmButtonText,
+  Future<String?> getDirectoryPath({
+    String? initialDirectory,
+    String? confirmButtonText,
   }) async =>
       null;
 
   Future<List<XFile>> _openFiles({
-    List<XTypeGroup> acceptedTypeGroups,
+    List<XTypeGroup>? acceptedTypeGroups,
     bool multiple = false,
   }) async {
     final accept = acceptedTypesToString(acceptedTypeGroups);
diff --git a/packages/file_selector/file_selector_web/lib/src/dom_helper.dart b/packages/file_selector/file_selector_web/lib/src/dom_helper.dart
index a965ceb..5c578b6 100644
--- a/packages/file_selector/file_selector_web/lib/src/dom_helper.dart
+++ b/packages/file_selector/file_selector_web/lib/src/dom_helper.dart
@@ -14,7 +14,7 @@
 
   /// Default constructor, initializes the container DOM element.
   DomHelper() {
-    final body = querySelector('body');
+    final body = querySelector('body')!;
     body.children.add(_container);
   }
 
@@ -22,42 +22,45 @@
   Future<List<XFile>> getFiles({
     String accept = '',
     bool multiple = false,
-    @visibleForTesting FileUploadInputElement input,
+    @visibleForTesting FileUploadInputElement? input,
   }) {
-    final Completer<List<XFile>> _completer = Completer();
-    input = input ?? FileUploadInputElement();
+    final Completer<List<XFile>> completer = Completer();
+    final FileUploadInputElement inputElement =
+        input ?? FileUploadInputElement();
 
     _container.children.add(
-      input
+      inputElement
         ..accept = accept
         ..multiple = multiple,
     );
 
-    input.onChange.first.then((_) {
-      final List<XFile> files = input.files.map(_convertFileToXFile).toList();
-      input.remove();
-      _completer.complete(files);
+    inputElement.onChange.first.then((_) {
+      final List<XFile> files =
+          inputElement.files!.map(_convertFileToXFile).toList();
+      inputElement.remove();
+      completer.complete(files);
     });
 
-    input.onError.first.then((event) {
-      final ErrorEvent error = event;
+    inputElement.onError.first.then((event) {
+      final ErrorEvent error = event as ErrorEvent;
       final platformException = PlatformException(
         code: error.type,
         message: error.message,
       );
-      input.remove();
-      _completer.completeError(platformException);
+      inputElement.remove();
+      completer.completeError(platformException);
     });
 
-    input.click();
+    inputElement.click();
 
-    return _completer.future;
+    return completer.future;
   }
 
   XFile _convertFileToXFile(File file) => XFile(
         Url.createObjectUrl(file),
         name: file.name,
         length: file.size,
-        lastModified: DateTime.fromMillisecondsSinceEpoch(file.lastModified),
+        lastModified: DateTime.fromMillisecondsSinceEpoch(
+            file.lastModified ?? DateTime.now().millisecondsSinceEpoch),
       );
 }
diff --git a/packages/file_selector/file_selector_web/lib/src/utils.dart b/packages/file_selector/file_selector_web/lib/src/utils.dart
index 4ddd7dd..6be58c2 100644
--- a/packages/file_selector/file_selector_web/lib/src/utils.dart
+++ b/packages/file_selector/file_selector_web/lib/src/utils.dart
@@ -5,19 +5,19 @@
 import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
 
 /// Convert list of XTypeGroups to a comma-separated string
-String acceptedTypesToString(List<XTypeGroup> acceptedTypes) {
+String acceptedTypesToString(List<XTypeGroup>? acceptedTypes) {
   if (acceptedTypes == null) return '';
   final List<String> allTypes = [];
   for (final group in acceptedTypes) {
     _assertTypeGroupIsValid(group);
     if (group.extensions != null) {
-      allTypes.addAll(group.extensions.map(_normalizeExtension));
+      allTypes.addAll(group.extensions!.map(_normalizeExtension));
     }
     if (group.mimeTypes != null) {
-      allTypes.addAll(group.mimeTypes);
+      allTypes.addAll(group.mimeTypes!);
     }
     if (group.webWildCards != null) {
-      allTypes.addAll(group.webWildCards);
+      allTypes.addAll(group.webWildCards!);
     }
   }
   return allTypes.join(',');
@@ -26,9 +26,9 @@
 /// Make sure that at least one of its fields is populated.
 void _assertTypeGroupIsValid(XTypeGroup group) {
   assert(
-      !((group.extensions == null || group.extensions.isEmpty) &&
-          (group.mimeTypes == null || group.mimeTypes.isEmpty) &&
-          (group.webWildCards == null || group.webWildCards.isEmpty)),
+      !((group.extensions == null || group.extensions!.isEmpty) &&
+          (group.mimeTypes == null || group.mimeTypes!.isEmpty) &&
+          (group.webWildCards == null || group.webWildCards!.isEmpty)),
       'At least one of extensions / mimeTypes / webWildCards is required for web.');
 }
 
diff --git a/packages/file_selector/file_selector_web/pubspec.yaml b/packages/file_selector/file_selector_web/pubspec.yaml
index a170d5f..55424a7 100644
--- a/packages/file_selector/file_selector_web/pubspec.yaml
+++ b/packages/file_selector/file_selector_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: file_selector_web
 description: Web platform implementation of file_selector
 homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_web
-version: 0.7.0+1
+version: 0.8.0
 
 flutter:
   plugin:
@@ -11,22 +11,18 @@
         fileName: file_selector_web.dart
 
 dependencies:
-  file_selector_platform_interface: ^1.0.2
-  platform_detect: ^1.4.0
+  file_selector_platform_interface: ^2.0.0
   flutter:
     sdk: flutter
   flutter_web_plugins:
     sdk: flutter
-  meta: ^1.1.7
+  meta: ^1.3.0
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
-  mockito: ^4.1.1
-  pedantic: ^1.8.0
-  integration_test:
-    path: ../../integration_test
+  pedantic: ^1.10.0
 
 environment:
-  sdk: ">=2.2.0 <3.0.0"
-  flutter: ">=1.10.0"
+  sdk: ">=2.12.0-259.9.beta <3.0.0"
+  flutter: ">=1.20.0"
diff --git a/packages/file_selector/file_selector_web/test/more_tests_exist_elsewhere_test.dart b/packages/file_selector/file_selector_web/test/more_tests_exist_elsewhere_test.dart
new file mode 100644
index 0000000..e9faa3a
--- /dev/null
+++ b/packages/file_selector/file_selector_web/test/more_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 also uses integration_test to run additional tests.');
+    print('See `example/README.md` for more info.');
+    print('---');
+  });
+}
diff --git a/packages/file_selector/file_selector_web/test/utils_test.dart b/packages/file_selector/file_selector_web/test/utils_test.dart
index 9fa187e..e3e47c0 100644
--- a/packages/file_selector/file_selector_web/test/utils_test.dart
+++ b/packages/file_selector/file_selector_web/test/utils_test.dart
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// @dart = 2.9
-
 import 'package:flutter_test/flutter_test.dart';
 import 'package:file_selector_web/src/utils.dart';
 import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';