diff --git a/invoiceninja.iml b/invoiceninja.iml
index e5c837191..b906a1e50 100644
--- a/invoiceninja.iml
+++ b/invoiceninja.iml
@@ -4,7 +4,7 @@
-
+
diff --git a/lib/ui/auth/login.dart b/lib/ui/auth/login.dart
index 7f86b9457..b0a3218fc 100644
--- a/lib/ui/auth/login.dart
+++ b/lib/ui/auth/login.dart
@@ -20,7 +20,7 @@ class Login extends StatelessWidget {
static final GlobalKey _formKey = GlobalKey();
static final GlobalKey> _emailKey =
- GlobalKey>();
+ GlobalKey>(debugLabel: 'email');
static final GlobalKey> _passwordKey =
GlobalKey>();
static final GlobalKey> _urlKey =
diff --git a/lib/ui/product/product_list_vm.dart b/lib/ui/product/product_list_vm.dart
index f5af02052..a2f7a2150 100644
--- a/lib/ui/product/product_list_vm.dart
+++ b/lib/ui/product/product_list_vm.dart
@@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
+import 'package:invoiceninja/ui/app/snackbar_row.dart';
+import 'package:invoiceninja/utils/localization.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja/data/models/models.dart';
import 'package:invoiceninja/ui/product/product_list.dart';
@@ -51,8 +53,6 @@ class ProductListVM {
Future _handleRefresh(BuildContext context) {
final Completer completer = new Completer();
store.dispatch(LoadProductsAction(completer));
- return completer.future;
- /*
return completer.future.then((_) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
@@ -60,7 +60,6 @@ class ProductListVM {
),
duration: Duration(seconds: 3)));
});
- */
}
return ProductListVM(
diff --git a/pubspec.lock b/pubspec.lock
index a9666a2a4..bb5eae256 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -141,6 +141,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.14"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.0.0"
fixnum:
dependency: transitive
description:
@@ -153,6 +160,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_driver:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
@@ -166,7 +178,7 @@ packages:
source: hosted
version: "0.5.0"
flutter_test:
- dependency: "direct dev"
+ dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
@@ -254,6 +266,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
+ json_rpc_2:
+ dependency: transitive
+ description:
+ name: json_rpc_2
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.7"
kernel:
dependency: transitive
description:
@@ -511,6 +530,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.6"
+ vm_service_client:
+ dependency: transitive
+ description:
+ name: vm_service_client
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.4+1"
watcher:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e1466ff64..5a7a867e4 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -18,8 +18,10 @@ dependencies:
dev_dependencies:
- flutter_test:
+ flutter_driver:
sdk: flutter
+ #flutter_test:
+ # sdk: flutter
build_runner: ^0.8.9
built_value_generator: "^5.4.5"
diff --git a/test/widget_test.dart b/test/widget_test.dart
deleted file mode 100644
index d6d9cca4b..000000000
--- a/test/widget_test.dart
+++ /dev/null
@@ -1,29 +0,0 @@
-// This is a basic Flutter widget test.
-// To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
-// provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
-// find child widgets in the widget tree, read text, and verify that the values of widget properties
-// are correct.
-
-//import 'package:flutter/material.dart';
-import 'package:flutter_test/flutter_test.dart';
-
-void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
- // Build our app and trigger a frame.
- /*
- await tester.pumpWidget(new MyApp());
-
- // Verify that our counter starts at 0.
- expect(find.text('0'), findsOneWidget);
- expect(find.text('1'), findsNothing);
-
- // Tap the '+' icon and trigger a frame.
- await tester.tap(find.byIcon(Icons.add));
- await tester.pump();
-
- // Verify that our counter has incremented.
- expect(find.text('0'), findsNothing);
- expect(find.text('1'), findsOneWidget);
- */
- });
-}
diff --git a/test_driver/widget.dart b/test_driver/widget.dart
new file mode 100644
index 000000000..0648770ae
--- /dev/null
+++ b/test_driver/widget.dart
@@ -0,0 +1,94 @@
+import 'package:flutter_driver/driver_extension.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import 'package:redux/redux.dart';
+import 'package:invoiceninja/routes.dart';
+import 'package:invoiceninja/ui/auth/login_vm.dart';
+import 'package:invoiceninja/ui/dashboard/dashboard.dart';
+import 'package:invoiceninja/ui/product/product_screen.dart';
+import 'package:invoiceninja/redux/app/app_reducer.dart';
+import 'package:invoiceninja/redux/app/app_state.dart';
+import 'package:invoiceninja/redux/auth/auth_middleware.dart';
+import 'package:invoiceninja/redux/auth/auth_actions.dart';
+import 'package:invoiceninja/redux/dashboard/dashboard_actions.dart';
+import 'package:invoiceninja/redux/dashboard/dashboard_middleware.dart';
+import 'package:invoiceninja/redux/product/product_actions.dart';
+import 'package:invoiceninja/redux/product/product_middleware.dart';
+import 'package:invoiceninja/utils/localization.dart';
+//import 'package:redux_logging/redux_logging.dart';
+
+void main() {
+ enableFlutterDriverExtension();
+
+ final store = Store(appReducer,
+ initialState: AppState(),
+ middleware: []
+ ..addAll(createStoreAuthMiddleware())
+ ..addAll(createStoreDashboardMiddleware())
+ ..addAll(createStoreProductsMiddleware())
+ ..addAll([
+ //LoggingMiddleware.printer(),
+ ]));
+
+ runApp(new InvoiceNinjaApp(store: store));
+}
+
+class InvoiceNinjaApp extends StatefulWidget {
+ final Store store;
+
+ InvoiceNinjaApp({Key key, this.store}) : super(key: key);
+
+ @override
+ _InvoiceNinjaAppState createState() => new _InvoiceNinjaAppState();
+}
+
+class _InvoiceNinjaAppState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return new StoreProvider(
+ store: widget.store,
+ child: new MaterialApp(
+ debugShowCheckedModeBanner: false,
+ localizationsDelegates: [
+ const AppLocalizationsDelegate(),
+ GlobalMaterialLocalizations.delegate,
+ ],
+ theme: ThemeData().copyWith(
+ primaryColor: const Color(0xFF117cc1),
+ primaryColorDark: const Color(0xFF005090),
+ primaryColorLight: const Color(0xFF5dabf4),
+ ),
+ /*
+ theme: ThemeData(
+ brightness: Brightness.dark,
+ accentColor: Colors.lightBlueAccent,
+ ),
+ */
+ title: 'Invoice Ninja',
+ routes: {
+ AppRoutes.login: (context) {
+ StoreProvider.of(context).dispatch(LoadUserLogin());
+ return LoginVM();
+ },
+ AppRoutes.dashboard: (context) {
+ StoreProvider.of(context).dispatch(LoadDashboardAction());
+ return Dashboard();
+ },
+ AppRoutes.products: (context) {
+ if (StoreProvider
+ .of(context)
+ .state
+ .productState()
+ .isStale()) {
+ StoreProvider
+ .of(context)
+ .dispatch(LoadProductsAction());
+ }
+ return ProductScreen();
+ },
+ },
+ ),
+ );
+ }
+}
diff --git a/test_driver/widget_test.dart b/test_driver/widget_test.dart
new file mode 100644
index 000000000..353c73381
--- /dev/null
+++ b/test_driver/widget_test.dart
@@ -0,0 +1,71 @@
+import 'dart:async';
+
+// Imports the Flutter Driver API
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:test/test.dart';
+//import 'package:invoiceninja/ui/auth/login.dart';
+
+void main() {
+ group('scrolling performance test', () {
+ FlutterDriver driver;
+
+ setUpAll(() async {
+ // Connects to the app
+ driver = await FlutterDriver.connect();
+ });
+
+ tearDownAll(() async {
+ if (driver != null) {
+ // Closes the connection
+ driver.close();
+ }
+ });
+
+ test('measure', () async {
+ // Record the performance timeline of things that happen inside the closure
+ Timeline timeline = await driver.traceAction(() async {
+
+ //SerializableFinder email = find.byValueKey('email');
+
+ final SerializableFinder email = find.byTooltip('Email');
+ expect(email, isNotNull);
+ await driver.waitFor(email);
+ await driver.enterText('test');
+
+ /*
+ // Find the scrollable user list
+ SerializableFinder userList = find.byValueKey('user-list');
+
+ // Scroll down 5 times
+ for (int i = 0; i < 5; i++) {
+ // Scroll 300 pixels down, for 300 millis
+ await driver.scroll(
+ userList, 0.0, -300.0, new Duration(milliseconds: 300));
+
+ // Emulate a user's finger taking its time to go back to the original
+ // position before the next scroll
+ await new Future.delayed(new Duration(milliseconds: 500));
+ }
+
+ // Scroll up 5 times
+ for (int i = 0; i < 5; i++) {
+ await driver.scroll(
+ userList, 0.0, 300.0, new Duration(milliseconds: 300));
+ await new Future.delayed(new Duration(milliseconds: 500));
+ }
+ */
+ });
+
+ // The `timeline` object contains all the performance data recorded during
+ // the scrolling session. It can be digested into a handful of useful
+ // aggregate numbers, such as "average frame build time".
+ TimelineSummary summary = new TimelineSummary.summarize(timeline);
+
+ // The following line saves the timeline summary to a JSON file.
+ summary.writeSummaryToFile('scrolling_performance', pretty: true);
+
+ // The following line saves the raw timeline data as JSON.
+ summary.writeTimelineToFile('scrolling_performance', pretty: true);
+ });
+ });
+}
\ No newline at end of file