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" />
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
|
||||||
<!-- TODO TLAD remove this permission once this issue is fixed:
|
<!-- TODO TLAD remove this permission once this issue is fixed:
|
||||||
https://github.com/flutter/flutter/issues/42349
|
|
||||||
https://github.com/flutter/flutter/issues/42451
|
https://github.com/flutter/flutter/issues/42451
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.calls;
|
package deckers.thibault.aves.channel.calls;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.media.MediaScannerConnection;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.storage.StorageManager;
|
import android.os.storage.StorageManager;
|
||||||
import android.os.storage.StorageVolume;
|
import android.os.storage.StorageVolume;
|
||||||
|
@ -51,6 +52,10 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "scanFile": {
|
||||||
|
scanFile(call, new MethodResultWrapper(result));
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
result.notImplemented();
|
result.notImplemented();
|
||||||
break;
|
break;
|
||||||
|
@ -82,4 +87,12 @@ public class StorageHandler implements MethodChannel.MethodCallHandler {
|
||||||
}
|
}
|
||||||
return volumes;
|
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:aves/widgets/fullscreen/info/location_section.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'source/enums.dart';
|
||||||
|
|
||||||
final Settings settings = Settings._private();
|
final Settings settings = Settings._private();
|
||||||
|
|
||||||
typedef SettingsCallback = void Function(String key, dynamic oldValue, dynamic newValue);
|
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:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin {
|
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin {
|
||||||
final CollectionSource source;
|
final CollectionSource source;
|
||||||
final Set<CollectionFilter> filters;
|
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 {
|
mixin CollectionActivityMixin {
|
||||||
final ValueNotifier<Activity> _activityNotifier = ValueNotifier(Activity.browse);
|
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:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'enums.dart';
|
||||||
|
|
||||||
mixin SourceBase {
|
mixin SourceBase {
|
||||||
final List<ImageEntry> _rawEntries = [];
|
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 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/main.dart';
|
||||||
import 'package:aves/model/settings.dart';
|
import 'package:aves/model/settings.dart';
|
||||||
import 'package:aves/model/source/collection_lens.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/utils/durations.dart';
|
||||||
import 'package:aves/widgets/album/filter_bar.dart';
|
import 'package:aves/widgets/album/filter_bar.dart';
|
||||||
import 'package:aves/widgets/album/search/search_delegate.dart';
|
import 'package:aves/widgets/album/search/search_delegate.dart';
|
||||||
|
@ -133,7 +134,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
|
|
||||||
Widget _buildAppBarTitle() {
|
Widget _buildAppBarTitle() {
|
||||||
if (collection.isBrowsing) {
|
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) {
|
if (AvesApp.mode == AppMode.main) {
|
||||||
title = SourceStateAwareAppBarTitle(
|
title = SourceStateAwareAppBarTitle(
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -184,15 +185,18 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
||||||
)),
|
)),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) => PopupMenuButton<CollectionAction>(
|
builder: (context) => PopupMenuButton<CollectionAction>(
|
||||||
|
key: Key('menu-button'),
|
||||||
itemBuilder: (context) {
|
itemBuilder: (context) {
|
||||||
final hasSelection = collection.selection.isNotEmpty;
|
final hasSelection = collection.selection.isNotEmpty;
|
||||||
return [
|
return [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
key: Key('menu-sort'),
|
||||||
value: CollectionAction.sort,
|
value: CollectionAction.sort,
|
||||||
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
child: MenuRow(text: 'Sort...', icon: AIcons.sort),
|
||||||
),
|
),
|
||||||
if (collection.sortFactor == SortFactor.date)
|
if (collection.sortFactor == SortFactor.date)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
|
key: Key('menu-group'),
|
||||||
value: CollectionAction.group,
|
value: CollectionAction.group,
|
||||||
child: MenuRow(text: 'Group...', icon: AIcons.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_lens.dart';
|
||||||
import 'package:aves/model/source/collection_source.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/android_file_utils.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/utils/durations.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/image_entry.dart';
|
||||||
import 'package:aves/model/source/collection_lens.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/utils/durations.dart';
|
||||||
import 'package:aves/widgets/common/fx/sweeper.dart';
|
import 'package:aves/widgets/common/fx/sweeper.dart';
|
||||||
import 'package:aves/widgets/common/icons.dart';
|
import 'package:aves/widgets/common/icons.dart';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/settings.dart';
|
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/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
||||||
child: Text('Cancel'.toUpperCase()),
|
child: Text('Cancel'.toUpperCase()),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
key: Key('apply-button'),
|
||||||
onPressed: () => Navigator.pop(context, _selectedGroup),
|
onPressed: () => Navigator.pop(context, _selectedGroup),
|
||||||
child: Text('Apply'.toUpperCase()),
|
child: Text('Apply'.toUpperCase()),
|
||||||
),
|
),
|
||||||
|
@ -42,8 +43,9 @@ class _GroupCollectionDialogState extends State<GroupCollectionDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioListTile(GroupFactor group, String title) => RadioListTile<GroupFactor>(
|
Widget _buildRadioListTile(GroupFactor value, String title) => RadioListTile<GroupFactor>(
|
||||||
value: group,
|
key: Key(value.toString()),
|
||||||
|
value: value,
|
||||||
groupValue: _selectedGroup,
|
groupValue: _selectedGroup,
|
||||||
onChanged: (group) => setState(() => _selectedGroup = group),
|
onChanged: (group) => setState(() => _selectedGroup = group),
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/model/settings.dart';
|
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/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
||||||
child: Text('Cancel'.toUpperCase()),
|
child: Text('Cancel'.toUpperCase()),
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
|
key: Key('apply-button'),
|
||||||
onPressed: () => Navigator.pop(context, _selectedSort),
|
onPressed: () => Navigator.pop(context, _selectedSort),
|
||||||
child: Text('Apply'.toUpperCase()),
|
child: Text('Apply'.toUpperCase()),
|
||||||
),
|
),
|
||||||
|
@ -41,8 +42,9 @@ class _SortCollectionDialogState extends State<SortCollectionDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioListTile(SortFactor sort, String title) => RadioListTile<SortFactor>(
|
Widget _buildRadioListTile(SortFactor value, String title) => RadioListTile<SortFactor>(
|
||||||
value: sort,
|
key: Key(value.toString()),
|
||||||
|
value: value,
|
||||||
groupValue: _selectedSort,
|
groupValue: _selectedSort,
|
||||||
onChanged: (sort) => setState(() => _selectedSort = sort),
|
onChanged: (sort) => setState(() => _selectedSort = sort),
|
||||||
title: Text(
|
title: Text(
|
||||||
|
|
|
@ -93,11 +93,13 @@ class _WelcomePageState extends State<WelcomePage> {
|
||||||
|
|
||||||
List<Widget> _buildBottomControls(BuildContext context) {
|
List<Widget> _buildBottomControls(BuildContext context) {
|
||||||
final checkbox = LabeledCheckbox(
|
final checkbox = LabeledCheckbox(
|
||||||
|
key: Key('agree-checkbox'),
|
||||||
value: _hasAcceptedTerms,
|
value: _hasAcceptedTerms,
|
||||||
onChanged: (v) => setState(() => _hasAcceptedTerms = v),
|
onChanged: (v) => setState(() => _hasAcceptedTerms = v),
|
||||||
text: 'I agree to the terms and conditions',
|
text: 'I agree to the terms and conditions',
|
||||||
);
|
);
|
||||||
final button = RaisedButton(
|
final button = RaisedButton(
|
||||||
|
key: Key('continue-button'),
|
||||||
child: Text('Continue'),
|
child: Text('Continue'),
|
||||||
onPressed: _hasAcceptedTerms
|
onPressed: _hasAcceptedTerms
|
||||||
? () {
|
? () {
|
||||||
|
|
234
pubspec.lock
234
pubspec.lock
|
@ -1,6 +1,20 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
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:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -78,6 +92,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.0"
|
version: "0.9.0"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -106,6 +127,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.0"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -113,6 +141,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.5"
|
version: "2.1.5"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.16.2"
|
||||||
draggable_scrollbar:
|
draggable_scrollbar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -178,6 +213,11 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
|
flutter_driver:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_ijkplayer:
|
flutter_ijkplayer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -246,6 +286,11 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fuchsia_remote_debug_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
geocoder:
|
geocoder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -253,6 +298,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1"
|
version: "0.2.1"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
google_maps_flutter:
|
google_maps_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -267,6 +319,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.0+3"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -274,6 +333,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.2"
|
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:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -295,6 +361,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.16.1"
|
version: "0.16.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -302,6 +375,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.2"
|
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:
|
latlong:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -351,6 +431,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.7"
|
||||||
nested:
|
nested:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -358,6 +445,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.4"
|
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:
|
outline_material_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -365,6 +473,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.1"
|
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:
|
package_info:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -500,6 +615,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
positioned_tap_detector:
|
positioned_tap_detector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -605,11 +727,53 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.2+7"
|
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:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
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:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -666,6 +830,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
sync_http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sync_http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -680,6 +851,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.2"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -687,6 +865,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.17"
|
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:
|
transparent_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -778,6 +963,48 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.8"
|
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:
|
wkt_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -799,6 +1026,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.2.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.9.0-14.0.dev <3.0.0"
|
dart: ">=2.9.0-14.0.dev <3.0.0"
|
||||||
flutter: ">=1.18.0-6.0.pre <2.0.0"
|
flutter: ">=1.18.0-6.0.pre <2.0.0"
|
||||||
|
|
|
@ -86,9 +86,12 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
# flutter drive --target=test_driver/app.dart
|
||||||
|
flutter_driver:
|
||||||
|
sdk: flutter
|
||||||
|
test: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
assets:
|
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