chore(test): manually manage flutter dependency

Change-Id: Ifaa232641b3df8cac599a2ba238aaddcdfe32e13
diff --git a/flutter/.analysis_options b/flutter/.analysis_options
new file mode 100644
index 0000000..427c5c3
--- /dev/null
+++ b/flutter/.analysis_options
@@ -0,0 +1,3 @@
+analyzer:
+  exclude:
+    - deps/*
diff --git a/flutter/.gitignore b/flutter/.gitignore
index d815521..794c51a 100644
--- a/flutter/.gitignore
+++ b/flutter/.gitignore
@@ -5,6 +5,7 @@
 .pub/
 build/
 doc/
+deps/
 ios/.generated/
 packages
 pubspec.lock
diff --git a/flutter/FLUTTER_VERSION b/flutter/FLUTTER_VERSION
new file mode 100644
index 0000000..e7a3bf0
--- /dev/null
+++ b/flutter/FLUTTER_VERSION
@@ -0,0 +1 @@
+731c5903c2894df36174edc8facd39613d37e163
diff --git a/flutter/Makefile b/flutter/Makefile
index 3b96309..c7c9ed1 100644
--- a/flutter/Makefile
+++ b/flutter/Makefile
@@ -5,28 +5,34 @@
 .DEFAULT_GOAL := all
 .SUFFIXES:
 
+DIRNAME := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+FLUTTER_BIN := $(DIRNAME)/deps/flutter/bin
+DART_BIN := $(FLUTTER_BIN)/cache/dart-sdk/bin
+PATH := $(FLUTTER_BIN):$(DART_BIN):$(PATH)
+
 .PHONY: all
-all: .packages doc
+all: .packages deps
 	@true # silences watch
 
-.packages: pubspec.yaml
+.packages: pubspec.yaml deps/flutter
 	pub get
 
 .PHONY: upgrade
-upgrade:
+upgrade: deps/flutter
 	pub upgrade
 
 .PHONY: analyze
-analyze:
-	@echo "=== Analyzing Dart code ==="
-	# dartanalyzer lib/main.dart --lints --fatal-hints --fatal-warnings
-	@true
+analyze: deps/flutter
+	@echo "=== Analyzing application code ==="
+	dartanalyzer lib/main.dart --lints --fatal-hints --fatal-warnings
+	@echo "=== Analyzing test code ==="
+	dartanalyzer $(shell find test -name "*.dart") --lints --fatal-hints --fatal-warnings
 
 .PHONY: fmt
-fmt: packages
+fmt: packages deps/flutter
 	dartfmt --overwrite lib
 
-doc:
+doc: deps/flutter $(shell find lib/ -name "*.dart")
 	dartdoc
 	@touch $@
 
@@ -34,7 +40,23 @@
 # NOTE: $FLUTTER_ENGINE is automatically picked up by flutter but is explcitly
 # set here for calrity until the correct path can be set using a jiri profile.
 .PHONY: test
-test: analyze
+test: analyze deps/flutter
 	@echo "=== Running tests ==="
-	# flutter test
-	@true
+	flutter test test/unit/example_test.dart
+	flutter test test/widget/flutter_demo_test.dart
+
+.PHONY: depclean
+depclean:
+	@rm -rf $(shell find . -name "packages")
+	@rm -rf deps
+	@rm -rf .packages
+	@rm -rf pubspec.lock
+
+deps: deps/flutter
+
+deps/flutter:
+	git clone https://github.com/flutter/flutter.git -b alpha $@
+	cd $@ && git checkout $(shell echo -e `cat FLUTTER_VERSION`)
+	flutter doctor
+	pub get
+	@touch $@
diff --git a/flutter/lib/widgets/flutter_demo.dart b/flutter/lib/widgets/flutter_demo.dart
index 0e2e292..9645fa8 100644
--- a/flutter/lib/widgets/flutter_demo.dart
+++ b/flutter/lib/widgets/flutter_demo.dart
@@ -5,8 +5,9 @@
 import 'package:flutter/material.dart';
 
 class FlutterDemo extends StatefulWidget {
-  FlutterDemo({Key key}): super(key: key);
+  FlutterDemo({Key key}) : super(key: key);
 
+  @override
   FlutterDemoState createState() => new FlutterDemoState();
 }
 
@@ -19,12 +20,12 @@
     });
   }
 
+  @override
   Widget build(BuildContext context) {
     return new Scaffold(
         appBar: new AppBar(title: new Text('Flutter Demo')),
-        body: new Center(
-            child: new Text(
-                'Button tapped $counter time${ counter == 1 ? '' : 's' }.')),
+        body: new Center(child: new Text(
+            'Button tapped $counter time${ counter == 1 ? '' : 's' }.')),
         floatingActionButton: new FloatingActionButton(
             onPressed: incrementCounter,
             tooltip: 'Increment',
diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml
index 89098ec..5cfacfb 100644
--- a/flutter/pubspec.yaml
+++ b/flutter/pubspec.yaml
@@ -2,8 +2,8 @@
 description: Create a new Flutter project.
 dependencies:
   flutter:
-    path: ../../../../../flutter/packages/flutter
+    path: deps/flutter/packages/flutter
 dev_dependencies:
   test: ^0.12.6
   flutter_test:
-    path: ../../../../../flutter/packages/flutter_test
+    path: deps/flutter/packages/flutter_test
diff --git a/flutter/test/flutter_demo_test.dart b/flutter/test/flutter_demo_test.dart
deleted file mode 100644
index 7a18709..0000000
--- a/flutter/test/flutter_demo_test.dart
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2016 The Vanadium 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:test/test.dart";
-import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:reader/widgets/flutter_demo.dart';
-
-void main() {
-  test("Example State test", () {
-    testWidgets((WidgetTester tester) {
-      GlobalKey key = new GlobalKey();
-      tester.pumpWidget(new MaterialApp(
-          title: 'Test App',
-          routes: <String, WidgetBuilder>{
-            '/': (BuildContext context) => new FlutterDemo(key: key)
-          }));
-
-      // Test State.
-      StatefulElement element = tester.findElementByKey(key);
-      FlutterDemoState state = element.state;
-
-      expect(tester.findText("Flutter Demo"), isNotNull);
-      expect(tester.findText("Button tapped 0 times."), isNotNull);
-
-      state.incrementCounter();
-      // Advance the flutter framework to the next tick.
-      tester.pump();
-
-      expect(state.counter, equals(1));
-      expect(tester.findText("Button tapped 1 time."), isNotNull);
-
-      // NOTE: There is an animation segue for the floating action button in
-      // the material scaffold. The FAB is not tappable during this initial
-      // segue, the FAB will not be responsive to tapping until the animation
-      // ends at 400ms.
-      //
-      // SEE: https://git.io/vaLPb
-      tester.pump(new Duration(milliseconds: 400));
-
-      // Test Widget input and rendering.
-      StatefulElement fab = tester.findElement((Element element) {
-        return element.widget is FloatingActionButton;
-      });
-
-      expect(fab, isNotNull);
-
-      tester.tap(fab);
-      tester.pump();
-
-      expect(state.counter, equals(2));
-      expect(tester.findText("Button tapped 2 times."), isNotNull);
-    });
-  });
-}
diff --git a/flutter/test/unit/example_test.dart b/flutter/test/unit/example_test.dart
new file mode 100644
index 0000000..6f938ea
--- /dev/null
+++ b/flutter/test/unit/example_test.dart
@@ -0,0 +1,11 @@
+// Copyright 2016 The Vanadium 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:test/test.dart';
+
+void main() {
+  test('this is a unit test', () {
+    expect(42, 42);
+  });
+}
diff --git a/flutter/test/widget/flutter_demo_test.dart b/flutter/test/widget/flutter_demo_test.dart
new file mode 100644
index 0000000..3c22fda
--- /dev/null
+++ b/flutter/test/widget/flutter_demo_test.dart
@@ -0,0 +1,54 @@
+// Copyright 2016 The Vanadium 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:test/test.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:reader/widgets/flutter_demo.dart';
+
+void main() {
+  test("Example State test", () {
+    testWidgets((WidgetTester tester) {
+      tester.pumpWidget(new MaterialApp(
+          title: 'Test App',
+          routes: <String, WidgetBuilder>{
+            '/': (BuildContext context) => new FlutterDemo()
+          }));
+
+      // Ensure default redner is correct.
+      expect(tester, hasWidget(find.text("Flutter Demo")));
+      expect(tester, hasWidget(find.text("Button tapped 0 times.")));
+
+      // Test state methods effect the render.
+      FlutterDemoState state = tester.stateOf(find.byType(FlutterDemo));
+      state.incrementCounter();
+
+      // Advance the flutter framework to the next tick.
+      tester.pump();
+
+      expect(state.counter, equals(1));
+      expect(tester, hasWidget(find.text("Button tapped 1 time.")));
+
+      // NOTE: There is an animation segue for the floating action button in
+      // the material scaffold. The FAB is not tappable during this initial
+      // segue, the FAB will not be responsive to tapping until the animation
+      // ends at 400ms.
+      //
+      // SEE: https://git.io/vwrlS
+      tester.pump(new Duration(milliseconds: 400));
+
+      Finder fab = find.byType(FloatingActionButton);
+      expect(tester, hasWidget(fab));
+
+      tester.tap(fab);
+      tester.pump();
+
+      expect(state.counter, equals(2));
+      expect(tester, hasWidget(find.text("Button tapped 2 times.")));
+
+      // Wait for animations to end.
+      tester.pump(const Duration(seconds: 1));
+    });
+  });
+}