Commit 50573fda authored by Brian Egan's avatar Brian Egan

Add e2e Test Suite for Redux example as a start

parent ce10ea12
...@@ -9,7 +9,7 @@ import 'package:built_redux_sample/models/models.dart'; ...@@ -9,7 +9,7 @@ import 'package:built_redux_sample/models/models.dart';
class WebService { class WebService {
final Duration delay; final Duration delay;
const WebService([this.delay = const Duration(milliseconds: 1200)]); const WebService([this.delay = const Duration(milliseconds: 3000)]);
/// Mock that "fetches" some Todos from a "web service" after a short delay /// Mock that "fetches" some Todos from a "web service" after a short delay
Future<List<Todo>> fetchTodos() async { Future<List<Todo>> fetchTodos() async {
......
...@@ -19,7 +19,7 @@ class StatsCounter extends StatelessWidget { ...@@ -19,7 +19,7 @@ class StatsCounter extends StatelessWidget {
return loading return loading
? new Center( ? new Center(
child: new CircularProgressIndicator( child: new CircularProgressIndicator(
key: ArchSampleKeys.loading, key: ArchSampleKeys.statsLoading,
)) ))
: new Center( : new Center(
child: new Column( child: new Column(
......
...@@ -17,7 +17,8 @@ class TodoList extends StatelessWidget { ...@@ -17,7 +17,8 @@ class TodoList extends StatelessWidget {
@required this.onCheckboxChanged, @required this.onCheckboxChanged,
@required this.onRemove, @required this.onRemove,
@required this.onUndoRemove, @required this.onUndoRemove,
}); })
: super(key: ArchSampleKeys.todoList);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -25,7 +26,7 @@ class TodoList extends StatelessWidget { ...@@ -25,7 +26,7 @@ class TodoList extends StatelessWidget {
return loading return loading
? new Center( ? new Center(
child: new CircularProgressIndicator( child: new CircularProgressIndicator(
key: ArchSampleKeys.loading, key: ArchSampleKeys.statsLoading,
)) ))
: new Container( : new Container(
child: new ListView.builder( child: new ListView.builder(
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:redux_sample/actions/actions.dart'; import 'package:redux_sample/actions/actions.dart';
import 'package:redux_sample/models/models.dart'; import 'package:redux_sample/models/models.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
...@@ -21,6 +22,7 @@ class AddTodo extends StatelessWidget { ...@@ -21,6 +22,7 @@ class AddTodo extends StatelessWidget {
}, },
builder: (BuildContext context, OnSaveCallback onSave) { builder: (BuildContext context, OnSaveCallback onSave) {
return new AddEditScreen( return new AddEditScreen(
key: ArchSampleKeys.addTodoScreen,
onSave: onSave, onSave: onSave,
isEditing: false, isEditing: false,
); );
......
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'package:redux_sample/actions/actions.dart'; import 'package:redux_sample/actions/actions.dart';
...@@ -26,6 +27,7 @@ class EditTodo extends StatelessWidget { ...@@ -26,6 +27,7 @@ class EditTodo extends StatelessWidget {
}, },
builder: (BuildContext context, OnSaveCallback onSave) { builder: (BuildContext context, OnSaveCallback onSave) {
return new AddEditScreen( return new AddEditScreen(
key: ArchSampleKeys.editTodoScreen,
onSave: onSave, onSave: onSave,
isEditing: true, isEditing: true,
todo: todo, todo: todo,
......
...@@ -51,8 +51,12 @@ class TabSelector extends StatelessWidget { ...@@ -51,8 +51,12 @@ class TabSelector extends StatelessWidget {
onTap: vm.onTabSelected, onTap: vm.onTabSelected,
items: AppTab.values.map((tab) { items: AppTab.values.map((tab) {
return new BottomNavigationBarItem( return new BottomNavigationBarItem(
icon: icon: new Icon(
new Icon(tab == AppTab.todos ? Icons.list : Icons.show_chart), tab == AppTab.todos ? Icons.list : Icons.show_chart,
key: tab == AppTab.todos
? ArchSampleKeys.todoTab
: ArchSampleKeys.statsTab,
),
title: new Text(tab == AppTab.stats title: new Text(tab == AppTab.stats
? ArchSampleLocalizations.of(context).stats ? ArchSampleLocalizations.of(context).stats
: ArchSampleLocalizations.of(context).todos), : ArchSampleLocalizations.of(context).todos),
......
...@@ -16,12 +16,32 @@ class WebService { ...@@ -16,12 +16,32 @@ class WebService {
return new Future.delayed( return new Future.delayed(
delay, delay,
() => [ () => [
new Todo('Buy food for da kitty', note: 'With the chickeny bits!'), new Todo(
new Todo('Find a Red Sea dive trip', note: 'Echo vs MY Dream'), 'Buy food for da kitty',
new Todo('Book flights to Egypt', complete: true), note: 'With the chickeny bits!',
new Todo('Decide on accommodation'), id: '1',
new Todo('Sip Margaritas', note: 'on the beach', complete: true), ),
]); new Todo(
'Find a Red Sea dive trip',
note: 'Echo vs MY Dream',
id: '2',
),
new Todo(
'Book flights to Egypt',
complete: true,
id: '3',
),
new Todo(
'Decide on accommodation',
id: '4',
),
new Todo(
'Sip Margaritas',
note: 'on the beach',
complete: true,
id: '5',
),
]);
} }
/// Mock that returns true or false for success or failure. In this case, /// Mock that returns true or false for success or failure. In this case,
......
...@@ -27,6 +27,7 @@ class DetailsScreen extends StatelessWidget { ...@@ -27,6 +27,7 @@ class DetailsScreen extends StatelessWidget {
actions: [ actions: [
new IconButton( new IconButton(
tooltip: localizations.deleteTodo, tooltip: localizations.deleteTodo,
key: ArchSampleKeys.deleteTodoButton,
icon: new Icon(Icons.delete), icon: new Icon(Icons.delete),
onPressed: () { onPressed: () {
onDelete(); onDelete();
...@@ -60,11 +61,13 @@ class DetailsScreen extends StatelessWidget { ...@@ -60,11 +61,13 @@ class DetailsScreen extends StatelessWidget {
), ),
child: new Text( child: new Text(
todo.task, todo.task,
key: ArchSampleKeys.detailsTodoItemTask,
style: Theme.of(context).textTheme.headline, style: Theme.of(context).textTheme.headline,
), ),
), ),
new Text( new Text(
todo.note, todo.note,
key: ArchSampleKeys.detailsTodoItemNote,
style: Theme.of(context).textTheme.subhead, style: Theme.of(context).textTheme.subhead,
) )
], ],
...@@ -76,6 +79,7 @@ class DetailsScreen extends StatelessWidget { ...@@ -76,6 +79,7 @@ class DetailsScreen extends StatelessWidget {
), ),
), ),
floatingActionButton: new FloatingActionButton( floatingActionButton: new FloatingActionButton(
key: ArchSampleKeys.editTodoFab,
tooltip: localizations.editTodo, tooltip: localizations.editTodo,
child: new Icon(Icons.edit), child: new Icon(Icons.edit),
onPressed: () { onPressed: () {
......
...@@ -11,7 +11,7 @@ class ExtraActionsButton extends StatelessWidget { ...@@ -11,7 +11,7 @@ class ExtraActionsButton extends StatelessWidget {
this.allComplete = false, this.allComplete = false,
Key key, Key key,
}) })
: super(key: key); : super(key: ArchSampleKeys.extraActionsButton);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -19,12 +19,14 @@ class ExtraActionsButton extends StatelessWidget { ...@@ -19,12 +19,14 @@ class ExtraActionsButton extends StatelessWidget {
onSelected: onSelected, onSelected: onSelected,
itemBuilder: (BuildContext context) => <PopupMenuItem<ExtraAction>>[ itemBuilder: (BuildContext context) => <PopupMenuItem<ExtraAction>>[
new PopupMenuItem<ExtraAction>( new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.toggleAll,
value: ExtraAction.toggleAllComplete, value: ExtraAction.toggleAllComplete,
child: new Text(allComplete child: new Text(allComplete
? ArchSampleLocalizations.of(context).markAllIncomplete ? ArchSampleLocalizations.of(context).markAllIncomplete
: ArchSampleLocalizations.of(context).markAllComplete), : ArchSampleLocalizations.of(context).markAllComplete),
), ),
new PopupMenuItem<ExtraAction>( new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.clearCompleted,
value: ExtraAction.clearCompleted, value: ExtraAction.clearCompleted,
child: new Text( child: new Text(
ArchSampleLocalizations.of(context).clearCompleted), ArchSampleLocalizations.of(context).clearCompleted),
......
...@@ -23,11 +23,13 @@ class FilterButton extends StatelessWidget { ...@@ -23,11 +23,13 @@ class FilterButton extends StatelessWidget {
opacity: visible ? 1.0 : 0.0, opacity: visible ? 1.0 : 0.0,
duration: new Duration(milliseconds: 150), duration: new Duration(milliseconds: 150),
child: new PopupMenuButton<VisibilityFilter>( child: new PopupMenuButton<VisibilityFilter>(
key: ArchSampleKeys.filterButton,
tooltip: ArchSampleLocalizations.of(context).filterTodos, tooltip: ArchSampleLocalizations.of(context).filterTodos,
onSelected: onSelected, onSelected: onSelected,
itemBuilder: (BuildContext context) => itemBuilder: (BuildContext context) =>
<PopupMenuItem<VisibilityFilter>>[ <PopupMenuItem<VisibilityFilter>>[
new PopupMenuItem<VisibilityFilter>( new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.allFilter,
value: VisibilityFilter.all, value: VisibilityFilter.all,
child: new Text( child: new Text(
ArchSampleLocalizations.of(context).showAll, ArchSampleLocalizations.of(context).showAll,
...@@ -37,6 +39,7 @@ class FilterButton extends StatelessWidget { ...@@ -37,6 +39,7 @@ class FilterButton extends StatelessWidget {
), ),
), ),
new PopupMenuItem<VisibilityFilter>( new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.activeFilter,
value: VisibilityFilter.active, value: VisibilityFilter.active,
child: new Text( child: new Text(
ArchSampleLocalizations.of(context).showActive, ArchSampleLocalizations.of(context).showActive,
...@@ -46,6 +49,7 @@ class FilterButton extends StatelessWidget { ...@@ -46,6 +49,7 @@ class FilterButton extends StatelessWidget {
), ),
), ),
new PopupMenuItem<VisibilityFilter>( new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.completedFilter,
value: VisibilityFilter.completed, value: VisibilityFilter.completed,
child: new Text( child: new Text(
ArchSampleLocalizations.of(context).showCompleted, ArchSampleLocalizations.of(context).showCompleted,
......
...@@ -10,7 +10,7 @@ import 'package:redux_sample/models/models.dart'; ...@@ -10,7 +10,7 @@ import 'package:redux_sample/models/models.dart';
import 'package:redux_sample/localization.dart'; import 'package:redux_sample/localization.dart';
class HomeScreen extends StatelessWidget { class HomeScreen extends StatelessWidget {
HomeScreen({Key key}) : super(key: key); HomeScreen() : super(key: ArchSampleKeys.homeScreen);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
class LoadingIndicator extends StatelessWidget { class LoadingIndicator extends StatelessWidget {
LoadingIndicator({Key key}) :super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Center( return new Center(
child: new CircularProgressIndicator( child: new CircularProgressIndicator(),
key: ArchSampleKeys.loading,
),
); );
} }
} }
...@@ -17,7 +17,9 @@ class StatsCounter extends StatelessWidget { ...@@ -17,7 +17,9 @@ class StatsCounter extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new AppLoading(builder: (context, loading) { return new AppLoading(builder: (context, loading) {
return loading ? new LoadingIndicator() : _buildStats(context); return loading
? new LoadingIndicator(key: new Key('__statsLoading__'))
: _buildStats(context);
}); });
} }
...@@ -37,6 +39,7 @@ class StatsCounter extends StatelessWidget { ...@@ -37,6 +39,7 @@ class StatsCounter extends StatelessWidget {
padding: new EdgeInsets.only(bottom: 24.0), padding: new EdgeInsets.only(bottom: 24.0),
child: new Text( child: new Text(
'$numCompleted', '$numCompleted',
key: ArchSampleKeys.statsCompletedItems,
style: Theme.of(context).textTheme.subhead, style: Theme.of(context).textTheme.subhead,
), ),
), ),
...@@ -51,6 +54,7 @@ class StatsCounter extends StatelessWidget { ...@@ -51,6 +54,7 @@ class StatsCounter extends StatelessWidget {
padding: new EdgeInsets.only(bottom: 24.0), padding: new EdgeInsets.only(bottom: 24.0),
child: new Text( child: new Text(
"$numActive", "$numActive",
key: ArchSampleKeys.statsActiveItems,
style: Theme.of(context).textTheme.subhead, style: Theme.of(context).textTheme.subhead,
), ),
) )
......
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:redux_sample/models/models.dart'; import 'package:redux_sample/models/models.dart';
class TodoItem extends StatelessWidget { class TodoItem extends StatelessWidget {
...@@ -18,20 +19,23 @@ class TodoItem extends StatelessWidget { ...@@ -18,20 +19,23 @@ class TodoItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Dismissible( return new Dismissible(
key: new Key(todo.id), key: ArchSampleKeys.todoItem(todo.id),
onDismissed: onDismissed, onDismissed: onDismissed,
child: new ListTile( child: new ListTile(
onTap: onTap, onTap: onTap,
leading: new Checkbox( leading: new Checkbox(
key: ArchSampleKeys.todoItemCheckbox(todo.id),
value: todo.complete, value: todo.complete,
onChanged: onCheckboxChanged, onChanged: onCheckboxChanged,
), ),
title: new Text( title: new Text(
todo.task, todo.task,
key: ArchSampleKeys.todoItemTask(todo.id),
style: Theme.of(context).textTheme.title, style: Theme.of(context).textTheme.title,
), ),
subtitle: new Text( subtitle: new Text(
todo.note, todo.note,
key: ArchSampleKeys.todoItemNote(todo.id),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.subhead, style: Theme.of(context).textTheme.subhead,
......
...@@ -14,16 +14,20 @@ class TodoList extends StatelessWidget { ...@@ -14,16 +14,20 @@ class TodoList extends StatelessWidget {
final Function(Todo) onUndoRemove; final Function(Todo) onUndoRemove;
TodoList({ TodoList({
Key key,
@required this.todos, @required this.todos,
@required this.onCheckboxChanged, @required this.onCheckboxChanged,
@required this.onRemove, @required this.onRemove,
@required this.onUndoRemove, @required this.onUndoRemove,
}); })
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new AppLoading(builder: (context, loading) { return new AppLoading(builder: (context, loading) {
return loading ? new LoadingIndicator() : _buildListView(); return loading
? new LoadingIndicator(key: ArchSampleKeys.todosLoading)
: _buildListView();
}); });
} }
...@@ -74,6 +78,7 @@ class TodoList extends StatelessWidget { ...@@ -74,6 +78,7 @@ class TodoList extends StatelessWidget {
.then((removedTodo) { .then((removedTodo) {
if (removedTodo != null) { if (removedTodo != null) {
Scaffold.of(context).showSnackBar(new SnackBar( Scaffold.of(context).showSnackBar(new SnackBar(
key: ArchSampleKeys.snackbar,
duration: new Duration(seconds: 2), duration: new Duration(seconds: 2),
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
content: new Text( content: new Text(
......
...@@ -13,6 +13,8 @@ dependencies: ...@@ -13,6 +13,8 @@ dependencies:
dev_dependencies: dev_dependencies:
mockito: 2.2.0 mockito: 2.2.0
flutter_driver:
sdk: flutter
flutter_test: flutter_test:
sdk: flutter sdk: flutter
......
import 'dart:async';
import 'package:flutter_driver/src/driver.dart';
import 'test_element.dart';
class ExtraActionsElement extends TestElement {
final _toggleAll = find.byValueKey('__markAllDone__');
final _clearCompleted = find.byValueKey('__clearCompleted__');
ExtraActionsElement(FlutterDriver driver) : super(driver);
Future<ExtraActionsElement> tapToggleAll() async {
await driver.tap(_toggleAll);
return this;
}
Future<ExtraActionsElement> tapClearCompleted() async {
await driver.tap(_clearCompleted);
return this;
}
}
import 'dart:async';
import 'package:flutter_driver/src/driver.dart';
import 'test_element.dart';
class FiltersElement extends TestElement {
final _allFilter = find.byValueKey('__allFilter__');
final _activeFilter = find.byValueKey('__activeFilter__');
final _completedFilter = find.byValueKey('__completedFilter__');
FiltersElement(FlutterDriver driver) : super(driver);
Future<Null> tapShowAll() async =>
await driver.tap(_allFilter);
Future<Null> tapShowActive() async =>
await driver.tap(_activeFilter);
Future<Null> tapShowCompleted() async {
return await driver.tap(_completedFilter);
}
}
import 'dart:async';
import 'package:flutter_driver/src/driver.dart';
import 'test_element.dart';
class StatsElement extends TestElement {
final _activeItemsFinder = find.byValueKey('__statsActiveItems__');
final _completedItemsFinder = find.byValueKey('__statsCompletedItems__');
StatsElement(FlutterDriver driver) : super(driver);
Future<int> get numActive async =>
int.parse((await driver.getText(_activeItemsFinder)));
Future<int> get numCompleted async =>
int.parse((await driver.getText(_completedItemsFinder)));
}
import 'package:flutter_driver/flutter_driver.dart';
abstract class TestElement {
final FlutterDriver driver;
TestElement(this.driver);
}
import 'dart:async';
import 'package:flutter_driver/src/driver.dart';
import '../screens/details_test_screen.dart';
import '../utils.dart';
import 'package:flutter_driver/src/find.dart';
import 'test_element.dart';
class TodoItemElement extends TestElement {
final String id;
TodoItemElement(this.id, FlutterDriver driver) : super(driver);
SerializableFinder get _taskFinder =>
find.byValueKey('TodoItem__${id}__Task');
SerializableFinder get _checkboxFinder =>
find.byValueKey('TodoItem__${id}__Checkbox');
SerializableFinder get _todoItemFinder => find.byValueKey('TodoItem__${id}');
Future<bool> get isVisible => widgetExists(driver, _todoItemFinder);
Future<bool> get isAbsent => widgetAbsent(driver, _todoItemFinder);
Future<String> get task async => await driver.getText(_taskFinder);
Future<String> get note async =>
await driver.getText(find.byValueKey('TodoItem__${id}__Note'));
Future<TodoItemElement> tapCheckbox() async {
await driver.tap(_checkboxFinder);
await driver.waitUntilNoTransientCallbacks();
return this;
}
DetailsTestScreen tap() {
driver.tap(_taskFinder);
return new DetailsTestScreen(driver);
}
}
import 'dart:async';
import 'package:flutter_driver/src/driver.dart';
import '../utils.dart';
import 'test_element.dart';
import 'todo_item_element.dart';
class TodoListElement extends TestElement {
final _todoListFinder = find.byValueKey('__todoList__');
final _loadingFinder = find.byValueKey('__todosLoading__');
TodoListElement(FlutterDriver driver) : super(driver);
Future<bool> get isLoading {
return driver.runUnsynchronized(() {
return widgetExists(driver, _loadingFinder);
});
}
Future<bool> get isReady => widgetExists(driver, _todoListFinder);
TodoItemElement todoItem(String id) => new TodoItemElement(id, driver);
TodoItemElement todoItemAbsent(String id) => new TodoItemElement(id, driver);
}
export 'screens/home_test_screen.dart';
export 'screens/add_test_screen.dart';
\ No newline at end of file
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
import '../utils.dart';
import 'test_screen.dart';
class AddTestScreen extends TestScreen {
final _addScreenFinder = find.byValueKey('__addTodoScreen__');
final _backButtonFinder = find.byTooltip('Back');
AddTestScreen(FlutterDriver driver) : super(driver);
@override
Future<bool> isReady({Duration timeout}) =>
widgetExists(driver, _addScreenFinder);
Future<Null> tapBackButton() async {
await driver.tap(_backButtonFinder);
return this;
}
}
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
import '../utils.dart';
import 'edit_test_screen.dart';
import 'test_screen.dart';
class DetailsTestScreen extends TestScreen {
final _detailsScreenFinder = find.byValueKey('__todoDetailsScreen__');
final _deleteButtonFinder = find.byValueKey('__deleteTodoFab__');
final _checkboxFinder = find.byValueKey('DetailsTodo__Checkbox');
final _taskFinder = find.byValueKey('DetailsTodo__Task');
final _noteFinder = find.byValueKey('DetailsTodo__Note');
final _editTodoFabFinder = find.byValueKey('__editTodoFab__');
final _backButtonFinder = find.byTooltip('Back');
DetailsTestScreen(FlutterDriver driver) : super(driver);
@override
Future<bool> isReady({Duration timeout}) =>
widgetExists(driver, _detailsScreenFinder);
Future<String> get task async => await driver.getText(_taskFinder);
Future<String> get note async => await driver.getText(_noteFinder);
Future<DetailsTestScreen> tapCheckbox() async {
await driver.tap(_checkboxFinder);
return this;
}
EditTestScreen tapEditTodoButton() {
driver.tap(_editTodoFabFinder);