tests: added test driver
This commit is contained in:
parent
7b1872fd12
commit
e6dc938be3
20 changed files with 420 additions and 17 deletions
|
@ -31,7 +31,6 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
|
||||
<!-- TODO TLAD remove this permission once this issue is fixed:
|
||||
https://github.com/flutter/flutter/issues/42349
|
||||
https://github.com/flutter/flutter/issues/42451
|
||||
-->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package deckers.thibault.aves.channel.calls;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.os.Build;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.os.storage.StorageVolume;
|
||||
|
@ -51,6 +52,10 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case "scanFile": {
|
||||
scanFile(call, new MethodResultWrapper(result));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
|
@ -82,4 +87,12 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
return volumes;
|
||||
}
|
||||
|
||||
private void scanFile(MethodCall call, MethodChannel.Result result) {
|
||||
String path = call.argument("path");
|
||||
String mimeType = call.argument("mimeType");
|
||||
MediaScannerConnection.scanFile(context, new String[]{path}, new String[]{mimeType}, (p, uri) -> {
|
||||
result.success(uri != null ? uri.toString() : null);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/location_section.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'source/enums.dart';
|
||||
|
||||
final Settings settings = Settings._private();
|
||||
|
||||
typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
|
||||
|
|
|
@ -12,6 +12,8 @@ import 'package:aves/utils/change_notifier.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin {
|
||||
final CollectionSource source;
|
||||
final Set<CollectionFilter> filters;
|
||||
|
@ -214,12 +216,6 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
|
|||
}
|
||||
}
|
||||
|
||||
enum SortFactor { date, size, name }
|
||||
|
||||
enum GroupFactor { none, album, month, day }
|
||||
|
||||
enum Activity { browse, select }
|
||||
|
||||
mixin CollectionActivityMixin {
|
||||
final ValueNotifier<Activity> _activityNotifier = ValueNotifier(Activity.browse);
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import 'package:aves/model/source/tag.dart';
|
|||
import 'package:event_bus/event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'enums.dart';
|
||||
|
||||
mixin SourceBase {
|
||||
final List<ImageEntry> _rawEntries = [];
|
||||
|
||||
|
|
5
lib/model/source/enums.dart
Normal file
5
lib/model/source/enums.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
enum SortFactor { date, size, name }
|
||||
|
||||
enum GroupFactor { none, album, month, day }
|
||||
|
||||
enum Activity { browse, select }
|
|
@ -52,4 +52,19 @@ class AndroidFileService {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// return media URI
|
||||
static Future<Uri> scanFile(String path, String mimeType) async {
|
||||
debugPrint('scanFile with path=$path, mimeType=$mimeType');
|
||||
try {
|
||||
final uriString = await platform.invokeMethod('scanFile', <String, dynamic>{
|
||||
'path': path,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
return Uri.tryParse(uriString ?? '');
|
||||
} on PlatformException catch (e) {
|
||||
debugPrint('scanFile failed with code=${e.code}, exception=${e.message}, details=${e.details}}');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:aves/main.dart';
|
||||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/utils/durations.dart';
|
||||
import 'package:aves/widgets/album/filter_bar.dart';
|
||||
import 'package:aves/widgets/album/search/search_delegate.dart';
|
||||
|
@ -133,7 +134,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
|
||||
Widget _buildAppBarTitle() {
|
||||
if (collection.isBrowsing) {
|
||||
Widget title = Text(AvesApp.mode == AppMode.pick ? 'Select' : 'Aves');
|
||||
Widget title = Text(AvesApp.mode == AppMode.pick ? 'Select' : 'Aves', key: Key('appbar-title'));
|
||||
if (AvesApp.mode == AppMode.main) {
|
||||
title = SourceStateAwareAppBarTitle(
|
||||
title: title,
|
||||
|
@ -184,15 +185,18 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
)),
|
||||
Builder(
|
||||
builder: (context) => PopupMenuButton<CollectionAction>(
|
||||
key: Key('menu-button'),
|
||||
itemBuilder: (context) {
|
||||
final hasSelection = collection.selection.isNotEmpty;
|
||||
return [
|
||||
PopupMenuItem(
|
||||
key: Key('menu-sort'),
|
||||
value: CollectionAction.sort,
|
||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||
),
|
||||
if (collection.sortFactor == SortFactor.date)
|
||||
PopupMenuItem(
|
||||
key: Key('menu-group'),
|
||||
value: CollectionAction.group,
|
||||
child: MenuRow(text: 'Group...', icon: AIcons.group),
|
||||
),
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/utils/durations.dart';
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/utils/durations.dart';
|
||||
import 'package:aves/widgets/common/fx/sweeper.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -35,6 +35,7 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
|||
child: Text('Cancel'.toUpperCase()),
|
||||
),
|
||||
FlatButton(
|
||||
key: Key('apply-button'),
|
||||
onPressed: () => Navigator.pop(context, _selectedGroup),
|
||||
child: Text('Apply'.toUpperCase()),
|
||||
),
|
||||
|
@ -42,8 +43,9 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioListTile(GroupFactor group, String title) => RadioListTile<GroupFactor>(
|
||||
value: group,
|
||||
Widget _buildRadioListTile(GroupFactor value, String title) => RadioListTile<GroupFactor>(
|
||||
key: Key(value.toString()),
|
||||
value: value,
|
||||
groupValue: _selectedGroup,
|
||||
onChanged: (group) => setState(() => _selectedGroup = group),
|
||||
title: Text(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:aves/model/settings.dart';
|
||||
import 'package:aves/model/source/collection_lens.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
|
@ -34,6 +34,7 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
|||
child: Text('Cancel'.toUpperCase()),
|
||||
),
|
||||
FlatButton(
|
||||
key: Key('apply-button'),
|
||||
onPressed: () => Navigator.pop(context, _selectedSort),
|
||||
child: Text('Apply'.toUpperCase()),
|
||||
),
|
||||
|
@ -41,8 +42,9 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildRadioListTile(SortFactor sort, String title) => RadioListTile<SortFactor>(
|
||||
value: sort,
|
||||
Widget _buildRadioListTile(SortFactor value, String title) => RadioListTile<SortFactor>(
|
||||
key: Key(value.toString()),
|
||||
value: value,
|
||||
groupValue: _selectedSort,
|
||||
onChanged: (sort) => setState(() => _selectedSort = sort),
|
||||
title: Text(
|
||||
|
|
|
@ -93,11 +93,13 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
|
||||
List<Widget> _buildBottomControls(BuildContext context) {
|
||||
final checkbox = LabeledCheckbox(
|
||||
key: Key('agree-checkbox'),
|
||||
value: _hasAcceptedTerms,
|
||||
onChanged: (v) => setState(() => _hasAcceptedTerms = v),
|
||||
text: 'I agree to the terms and conditions',
|
||||
);
|
||||
final button = RaisedButton(
|
||||
key: Key('continue-button'),
|
||||
child: Text('Continue'),
|
||||
onPressed: _hasAcceptedTerms
|
||||
? () {
|
||||
|
|
234
pubspec.lock
234
pubspec.lock
|
@ -1,6 +1,20 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.39.17"
|
||||
ansicolor:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -78,6 +92,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.0"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -106,6 +127,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -113,6 +141,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.2"
|
||||
draggable_scrollbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -178,6 +213,11 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
flutter_driver:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_ijkplayer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -246,6 +286,11 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
fuchsia_remote_debug_protocol:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
geocoder:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -253,6 +298,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
google_maps_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -267,6 +319,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.14.0+3"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -274,6 +333,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.12.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -295,6 +361,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.16.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.4"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -302,6 +375,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.2"
|
||||
json_rpc_2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_rpc_2
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
latlong:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -351,6 +431,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -358,6 +445,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.4"
|
||||
node_interop:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_interop
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
node_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.12"
|
||||
outline_material_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -365,6 +473,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.9.3"
|
||||
package_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -500,6 +615,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
positioned_tap_detector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -605,11 +727,53 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2+7"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.9"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.8"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.10.9"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -666,6 +830,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sync_http
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -680,6 +851,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.15.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -687,6 +865,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.17"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.10"
|
||||
transparent_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -778,6 +963,48 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
vm_service_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.6+2"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.7+15"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
webdriver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
wkt_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -799,6 +1026,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=2.9.0-14.0.dev <3.0.0"
|
||||
flutter: ">=1.18.0-6.0.pre <2.0.0"
|
||||
|
|
|
@ -86,9 +86,12 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
# flutter drive --target=test_driver/app.dart
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
test: any
|
||||
|
||||
flutter:
|
||||
|
||||
uses-material-design: true
|
||||
|
||||
assets:
|
||||
|
|
18
test_driver/app.dart
Normal file
18
test_driver/app.dart
Normal file
|
@ -0,0 +1,18 @@
|
|||
import 'package:aves/main.dart' as app;
|
||||
import 'package:aves/services/android_file_service.dart';
|
||||
import 'package:flutter_driver/driver_extension.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
|
||||
void main() {
|
||||
enableFlutterDriverExtension();
|
||||
|
||||
// scan files copied from test assets
|
||||
// we do it via the app instead of broadcasting via ADB
|
||||
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
|
||||
unawaited(AndroidFileService.scanFile(join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg'));
|
||||
|
||||
app.main();
|
||||
}
|
67
test_driver/app_test.dart
Normal file
67
test_driver/app_test.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'constants.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
void main() {
|
||||
group('Aves app', () {
|
||||
FlutterDriver driver;
|
||||
|
||||
setUpAll(() async {
|
||||
await copyContent(sourcePicturesDir, targetPicturesDir);
|
||||
await grantPermissions('deckers.thibault.aves.debug', [
|
||||
'android.permission.READ_EXTERNAL_STORAGE',
|
||||
'android.permission.WRITE_EXTERNAL_STORAGE',
|
||||
'android.permission.ACCESS_MEDIA_LOCATION',
|
||||
]);
|
||||
driver = await FlutterDriver.connect();
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await removeDirectory(targetPicturesDir);
|
||||
if (driver != null) {
|
||||
unawaited(driver.close());
|
||||
}
|
||||
});
|
||||
|
||||
final appBarTitleFinder = find.byValueKey('appbar-title');
|
||||
|
||||
test('agree to terms and reach home', () async {
|
||||
await driver.scroll(find.text('Terms of Service'), 0, -300, Duration(milliseconds: 500));
|
||||
|
||||
final buttonFinder = find.byValueKey('continue-button');
|
||||
expect(await isEnabled(driver, buttonFinder), equals(false));
|
||||
|
||||
await driver.tap(find.byValueKey('agree-checkbox'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
expect(await isEnabled(driver, buttonFinder), equals(true));
|
||||
|
||||
await driver.tap(buttonFinder);
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
expect(await driver.getText(appBarTitleFinder), 'Aves');
|
||||
});
|
||||
|
||||
test('sort and group', () async {
|
||||
await driver.tap(find.byValueKey('menu-button'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
await driver.tap(find.byValueKey('menu-sort'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
await driver.tap(find.byValueKey(SortFactor.date.toString()));
|
||||
await driver.tap(find.byValueKey('apply-button'));
|
||||
|
||||
await driver.tap(find.byValueKey('menu-button'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
await driver.tap(find.byValueKey('menu-group'));
|
||||
await driver.waitUntilNoTransientCallbacks();
|
||||
|
||||
await driver.tap(find.byValueKey(GroupFactor.album.toString()));
|
||||
await driver.tap(find.byValueKey('apply-button'));
|
||||
});
|
||||
}, timeout: Timeout(Duration(seconds: 10)));
|
||||
}
|
BIN
test_driver/assets/ipse.jpg
Normal file
BIN
test_driver/assets/ipse.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 5 MiB |
3
test_driver/constants.dart
Normal file
3
test_driver/constants.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
const sourcePicturesDir = 'test_driver/assets/';
|
||||
const targetPicturesDir = '/sdcard/Pictures/Aves Test Driver/';
|
||||
|
35
test_driver/utils.dart
Normal file
35
test_driver/utils.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_driver/flutter_driver.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
String get adb {
|
||||
final env = Platform.environment;
|
||||
final sdkDir = env['ANDROID_SDK_ROOT'] ?? env['ANDROID_SDK'];
|
||||
return join(sdkDir, 'platform-tools', Platform.isWindows ? 'adb.exe' : 'adb');
|
||||
}
|
||||
|
||||
Future<void> createDirectory(String dir) async {
|
||||
await Process.run(adb, ['shell', 'mkdir -p', dir.replaceAll(' ', '\\ ')]);
|
||||
}
|
||||
|
||||
Future<void> removeDirectory(String dir) async {
|
||||
await Process.run(adb, ['shell', 'rm -r', dir.replaceAll(' ', '\\ ')]);
|
||||
}
|
||||
|
||||
Future<void> copyContent(String sourceDir, String targetDir) async {
|
||||
// to copy the content of `source` inside `target`
|
||||
// `push source/* target/` works only when the target directory exists, and fails when `target` contains spaces
|
||||
// `push source/ target/` works only when the target directory does not exist
|
||||
await removeDirectory(targetDir);
|
||||
await Process.run(adb, ['push', sourceDir, targetDir]);
|
||||
}
|
||||
|
||||
Future<void> grantPermissions(String packageName, Iterable<String> permissions) async {
|
||||
await Future.forEach(permissions, (permission) => Process.run(adb, ['shell', 'pm', 'grant', packageName, permission]));
|
||||
}
|
||||
|
||||
Future<bool> isEnabled(FlutterDriver driver, SerializableFinder widgetFinder) async {
|
||||
Map widgetDiagnostics = await driver.getWidgetDiagnostics(widgetFinder);
|
||||
return widgetDiagnostics['properties'].firstWhere((property) => property['name'] == 'enabled')['value'];
|
||||
}
|
Loading…
Reference in a new issue