diff --git a/.cirrus.yml b/.cirrus.yml index 4ad5e8e03a25..8f69bd188c06 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -168,7 +168,7 @@ task: env: # Currently missing; see https://github.com/flutter/flutter/issues/81982 # and https://github.com/flutter/flutter/issues/82211 - PLUGINS_TO_EXCLUDE_INTEGRATION_TESTS: "file_selector,image_picker_for_web,shared_preferences_web" + PLUGINS_TO_EXCLUDE_INTEGRATION_TESTS: "file_selector,shared_preferences_web" matrix: CHANNEL: "master" CHANNEL: "stable" diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md index 7b2c4077e28d..b0379ad2c07c 100644 --- a/packages/image_picker/image_picker_for_web/CHANGELOG.md +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -1,3 +1,8 @@ +# 2.1.0 + +* Implemented `getImage`, `getVideo` and `getFile` methods that return `XFile` instances. +* Move tests to `example` directory, so they run as integration_tests with `flutter drive`. + # 2.0.0 * Migrate to null safety. diff --git a/packages/image_picker/image_picker_for_web/example/README.md b/packages/image_picker/image_picker_for_web/example/README.md new file mode 100644 index 000000000000..4348451b14e2 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/example/README.md @@ -0,0 +1,9 @@ +# Testing + +This package uses `package:integration_test` to run its tests in a web browser. + +See [Plugin Tests > Web Tests](https://github.com/flutter/flutter/wiki/Plugin-Tests#web-tests) +in the Flutter wiki for instructions to setup and run the tests in this package. + +Check [flutter.dev > Integration testing](https://flutter.dev/docs/testing/integration-tests) +for more info. diff --git a/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart similarity index 68% rename from packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart rename to packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_test.dart index fbdd1d38bee6..c6d0b3b532ca 100644 --- a/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart +++ b/packages/image_picker/image_picker_for_web/example/integration_test/image_picker_for_web_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. -@TestOn('chrome') // Uses dart:html - import 'dart:convert'; import 'dart:html' as html; import 'dart:typed_data'; @@ -11,12 +9,15 @@ import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:image_picker_for_web/image_picker_for_web.dart'; import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; +import 'package:integration_test/integration_test.dart'; final String expectedStringContents = "Hello, world!"; final Uint8List bytes = utf8.encode(expectedStringContents) as Uint8List; final html.File textFile = html.File([bytes], "hello.txt"); void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + // Under test... late ImagePickerPlugin plugin; @@ -24,7 +25,7 @@ void main() { plugin = ImagePickerPlugin(); }); - test('Can select a file', () async { + testWidgets('Can select a file (Deprecated)', (WidgetTester tester) async { final mockInput = html.FileUploadInputElement(); final overrides = ImagePickerPluginTestOverrides() @@ -45,9 +46,30 @@ void main() { expect((await file).readAsBytes(), completion(isNotEmpty)); }); + testWidgets('Can select a file', (WidgetTester tester) async { + final mockInput = html.FileUploadInputElement(); + + final overrides = ImagePickerPluginTestOverrides() + ..createInputElement = ((_, __) => mockInput) + ..getFileFromInput = ((_) => textFile); + + final plugin = ImagePickerPlugin(overrides: overrides); + + // Init the pick file dialog... + final file = plugin.getFile(); + + // Mock the browser behavior of selecting a file... + mockInput.dispatchEvent(html.Event('change')); + + // Now the file should be available + expect(file, completes); + // And readable + expect((await file).readAsBytes(), completion(isNotEmpty)); + }); + // There's no good way of detecting when the user has "aborted" the selection. - test('computeCaptureAttribute', () { + testWidgets('computeCaptureAttribute', (WidgetTester tester) async { expect( plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.front), isNull, @@ -67,14 +89,14 @@ void main() { }); group('createInputElement', () { - test('accept: any, capture: null', () { + testWidgets('accept: any, capture: null', (WidgetTester tester) async { html.Element input = plugin.createInputElement('any', null); expect(input.attributes, containsPair('accept', 'any')); expect(input.attributes, isNot(contains('capture'))); }); - test('accept: any, capture: something', () { + testWidgets('accept: any, capture: something', (WidgetTester tester) async { html.Element input = plugin.createInputElement('any', 'something'); expect(input.attributes, containsPair('accept', 'any')); diff --git a/packages/image_picker/image_picker_for_web/example/lib/main.dart b/packages/image_picker/image_picker_for_web/example/lib/main.dart new file mode 100644 index 000000000000..e1a38dcdcd46 --- /dev/null +++ b/packages/image_picker/image_picker_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 { + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.ltr, + child: Text('Testing... Look at the console output for results!'), + ); + } +} diff --git a/packages/image_picker/image_picker_for_web/example/pubspec.yaml b/packages/image_picker/image_picker_for_web/example/pubspec.yaml new file mode 100644 index 000000000000..8dadde267e8a --- /dev/null +++ b/packages/image_picker/image_picker_for_web/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: connectivity_for_web_integration_tests +publish_to: none + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=2.2.0" + +dependencies: + image_picker_for_web: + path: ../ + flutter: + sdk: flutter + +dev_dependencies: + js: ^0.6.3 + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + integration_test: + sdk: flutter diff --git a/packages/image_picker/image_picker_for_web/example/run_test.sh b/packages/image_picker/image_picker_for_web/example/run_test.sh new file mode 100755 index 000000000000..aa52974f310e --- /dev/null +++ b/packages/image_picker/image_picker_for_web/example/run_test.sh @@ -0,0 +1,22 @@ +#!/usr/bin/bash +# 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. + +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_test.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_test.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/image_picker/image_picker_for_web/example/test_driver/integration_test.dart b/packages/image_picker/image_picker_for_web/example/test_driver/integration_test.dart new file mode 100644 index 000000000000..4f10f2a522f3 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/example/test_driver/integration_test.dart @@ -0,0 +1,7 @@ +// 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:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/packages/image_picker/image_picker_for_web/example/web/index.html b/packages/image_picker/image_picker_for_web/example/web/index.html new file mode 100644 index 000000000000..7fb138cc90fa --- /dev/null +++ b/packages/image_picker/image_picker_for_web/example/web/index.html @@ -0,0 +1,13 @@ + + + + + + example + + + + + diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart index 2fb66380e1d8..08ce801cafbe 100644 --- a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -96,6 +96,68 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _getSelectedFile(input); } + /// Returns an [XFile] with the image that was picked. + /// + /// The `source` argument controls where the image comes from. This can + /// be either [ImageSource.camera] or [ImageSource.gallery]. + /// + /// Note that the `maxWidth`, `maxHeight` and `imageQuality` arguments are not supported on the web. If any of these arguments is supplied, it'll be silently ignored by the web version of the plugin. + /// + /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. + /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. + /// Defaults to [CameraDevice.rear]. + /// + /// If no images were picked, the return value is null. + @override + Future getImage({ + required ImageSource source, + double? maxWidth, + double? maxHeight, + int? imageQuality, + CameraDevice preferredCameraDevice = CameraDevice.rear, + }) { + String? capture = computeCaptureAttribute(source, preferredCameraDevice); + return getFile(accept: _kAcceptImageMimeType, capture: capture); + } + + /// Returns an [XFile] containing the video that was picked. + /// + /// The [source] argument controls where the video comes from. This can + /// be either [ImageSource.camera] or [ImageSource.gallery]. + /// + /// Note that the `maxDuration` argument is not supported on the web. If the argument is supplied, it'll be silently ignored by the web version of the plugin. + /// + /// Use `preferredCameraDevice` to specify the camera to use when the `source` is [ImageSource.camera]. + /// The `preferredCameraDevice` is ignored when `source` is [ImageSource.gallery]. It is also ignored if the chosen camera is not supported on the device. + /// Defaults to [CameraDevice.rear]. + /// + /// If no images were picked, the return value is null. + @override + Future getVideo({ + required ImageSource source, + CameraDevice preferredCameraDevice = CameraDevice.rear, + Duration? maxDuration, + }) { + String? capture = computeCaptureAttribute(source, preferredCameraDevice); + return getFile(accept: _kAcceptVideoMimeType, capture: capture); + } + + /// Injects a file input with the specified accept+capture attributes, and + /// returns the PickedFile that the user selected locally. + /// + /// `capture` is only supported in mobile browsers. + /// See https://caniuse.com/#feat=html-media-capture + @visibleForTesting + Future getFile({ + String? accept, + String? capture, + }) { + html.FileUploadInputElement input = + createInputElement(accept, capture) as html.FileUploadInputElement; + _injectAndActivate(input); + return _getSelectedXFile(input); + } + // DOM methods /// Converts plugin configuration into a proper value for the `capture` attribute. @@ -150,6 +212,26 @@ class ImagePickerPlugin extends ImagePickerPlatform { return _completer.future; } + Future _getSelectedXFile(html.FileUploadInputElement input) { + final Completer _completer = Completer(); + // Observe the input until we can return something + input.onChange.first.then((event) { + final objectUrl = _handleOnChangeEvent(event); + if (!_completer.isCompleted && objectUrl != null) { + _completer.complete(XFile(objectUrl)); + } + }); + input.onError.first.then((event) { + if (!_completer.isCompleted) { + _completer.completeError(event); + } + }); + // Note that we don't bother detaching from these streams, since the + // "input" gets re-created in the DOM every time the user needs to + // pick a file. + return _completer.future; + } + /// Initializes a DOM container where we can host input elements. html.Element _ensureInitialized(String id) { var target = html.querySelector('#${id}'); diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml index 768f7e27ce77..d9b9c5e5cb86 100644 --- a/packages/image_picker/image_picker_for_web/pubspec.yaml +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_for_web description: Web platform implementation of image_picker repository: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 2.0.0 +version: 2.1.0 environment: sdk: ">=2.12.0 <3.0.0" @@ -20,7 +20,7 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - image_picker_platform_interface: ^2.0.0 + image_picker_platform_interface: ^2.2.0 meta: ^1.3.0 dev_dependencies: diff --git a/packages/image_picker/image_picker_for_web/test/README.md b/packages/image_picker/image_picker_for_web/test/README.md new file mode 100644 index 000000000000..7c5b4ad682ba --- /dev/null +++ b/packages/image_picker/image_picker_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/image_picker/image_picker_for_web/test/tests_exist_elsewhere_test.dart b/packages/image_picker/image_picker_for_web/test/tests_exist_elsewhere_test.dart new file mode 100644 index 000000000000..442c50144727 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/test/tests_exist_elsewhere_test.dart @@ -0,0 +1,14 @@ +// 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_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('---'); + }); +}