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';
class WebService {
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
Future<List<Todo>> fetchTodos() async {
......
......@@ -19,7 +19,7 @@ class StatsCounter extends StatelessWidget {
return loading
? new Center(
child: new CircularProgressIndicator(
key: ArchSampleKeys.loading,
key: ArchSampleKeys.statsLoading,
))
: new Center(
child: new Column(
......
......@@ -17,7 +17,8 @@ class TodoList extends StatelessWidget {
@required this.onCheckboxChanged,
@required this.onRemove,
@required this.onUndoRemove,
});
})
: super(key: ArchSampleKeys.todoList);
@override
Widget build(BuildContext context) {
......@@ -25,7 +26,7 @@ class TodoList extends StatelessWidget {
return loading
? new Center(
child: new CircularProgressIndicator(
key: ArchSampleKeys.loading,
key: ArchSampleKeys.statsLoading,
))
: new Container(
child: new ListView.builder(
......
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/models/models.dart';
import 'package:flutter_redux/flutter_redux.dart';
......@@ -21,6 +22,7 @@ class AddTodo extends StatelessWidget {
},
builder: (BuildContext context, OnSaveCallback onSave) {
return new AddEditScreen(
key: ArchSampleKeys.addTodoScreen,
onSave: onSave,
isEditing: false,
);
......
import 'package:flutter/widgets.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:redux_sample/actions/actions.dart';
......@@ -26,6 +27,7 @@ class EditTodo extends StatelessWidget {
},
builder: (BuildContext context, OnSaveCallback onSave) {
return new AddEditScreen(
key: ArchSampleKeys.editTodoScreen,
onSave: onSave,
isEditing: true,
todo: todo,
......
......@@ -51,8 +51,12 @@ class TabSelector extends StatelessWidget {
onTap: vm.onTabSelected,
items: AppTab.values.map((tab) {
return new BottomNavigationBarItem(
icon:
new Icon(tab == AppTab.todos ? Icons.list : Icons.show_chart),
icon: new Icon(
tab == AppTab.todos ? Icons.list : Icons.show_chart,
key: tab == AppTab.todos
? ArchSampleKeys.todoTab
: ArchSampleKeys.statsTab,
),
title: new Text(tab == AppTab.stats
? ArchSampleLocalizations.of(context).stats
: ArchSampleLocalizations.of(context).todos),
......
......@@ -16,11 +16,31 @@ class WebService {
return new Future.delayed(
delay,
() => [
new Todo('Buy food for da kitty', note: 'With the chickeny bits!'),
new Todo('Find a Red Sea dive trip', note: 'Echo vs MY Dream'),
new Todo('Book flights to Egypt', complete: true),
new Todo('Decide on accommodation'),
new Todo('Sip Margaritas', note: 'on the beach', complete: true),
new Todo(
'Buy food for da kitty',
note: 'With the chickeny bits!',
id: '1',
),
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',
),
]);
}
......
......@@ -27,6 +27,7 @@ class DetailsScreen extends StatelessWidget {
actions: [
new IconButton(
tooltip: localizations.deleteTodo,
key: ArchSampleKeys.deleteTodoButton,
icon: new Icon(Icons.delete),
onPressed: () {
onDelete();
......@@ -60,11 +61,13 @@ class DetailsScreen extends StatelessWidget {
),
child: new Text(
todo.task,
key: ArchSampleKeys.detailsTodoItemTask,
style: Theme.of(context).textTheme.headline,
),
),
new Text(
todo.note,
key: ArchSampleKeys.detailsTodoItemNote,
style: Theme.of(context).textTheme.subhead,
)
],
......@@ -76,6 +79,7 @@ class DetailsScreen extends StatelessWidget {
),
),
floatingActionButton: new FloatingActionButton(
key: ArchSampleKeys.editTodoFab,
tooltip: localizations.editTodo,
child: new Icon(Icons.edit),
onPressed: () {
......
......@@ -11,7 +11,7 @@ class ExtraActionsButton extends StatelessWidget {
this.allComplete = false,
Key key,
})
: super(key: key);
: super(key: ArchSampleKeys.extraActionsButton);
@override
Widget build(BuildContext context) {
......@@ -19,12 +19,14 @@ class ExtraActionsButton extends StatelessWidget {
onSelected: onSelected,
itemBuilder: (BuildContext context) => <PopupMenuItem<ExtraAction>>[
new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.toggleAll,
value: ExtraAction.toggleAllComplete,
child: new Text(allComplete
? ArchSampleLocalizations.of(context).markAllIncomplete
: ArchSampleLocalizations.of(context).markAllComplete),
),
new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.clearCompleted,
value: ExtraAction.clearCompleted,
child: new Text(
ArchSampleLocalizations.of(context).clearCompleted),
......
......@@ -23,11 +23,13 @@ class FilterButton extends StatelessWidget {
opacity: visible ? 1.0 : 0.0,
duration: new Duration(milliseconds: 150),
child: new PopupMenuButton<VisibilityFilter>(
key: ArchSampleKeys.filterButton,
tooltip: ArchSampleLocalizations.of(context).filterTodos,
onSelected: onSelected,
itemBuilder: (BuildContext context) =>
<PopupMenuItem<VisibilityFilter>>[
new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.allFilter,
value: VisibilityFilter.all,
child: new Text(
ArchSampleLocalizations.of(context).showAll,
......@@ -37,6 +39,7 @@ class FilterButton extends StatelessWidget {
),
),
new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.activeFilter,
value: VisibilityFilter.active,
child: new Text(
ArchSampleLocalizations.of(context).showActive,
......@@ -46,6 +49,7 @@ class FilterButton extends StatelessWidget {
),
),
new PopupMenuItem<VisibilityFilter>(
key: ArchSampleKeys.completedFilter,
value: VisibilityFilter.completed,
child: new Text(
ArchSampleLocalizations.of(context).showCompleted,
......
......@@ -10,7 +10,7 @@ import 'package:redux_sample/models/models.dart';
import 'package:redux_sample/localization.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({Key key}) : super(key: key);
HomeScreen() : super(key: ArchSampleKeys.homeScreen);
@override
Widget build(BuildContext context) {
......
import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
class LoadingIndicator extends StatelessWidget {
LoadingIndicator({Key key}) :super(key: key);
@override
Widget build(BuildContext context) {
return new Center(
child: new CircularProgressIndicator(
key: ArchSampleKeys.loading,
),
child: new CircularProgressIndicator(),
);
}
}
......@@ -17,7 +17,9 @@ class StatsCounter extends StatelessWidget {
@override
Widget build(BuildContext context) {
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 {
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
'$numCompleted',
key: ArchSampleKeys.statsCompletedItems,
style: Theme.of(context).textTheme.subhead,
),
),
......@@ -51,6 +54,7 @@ class StatsCounter extends StatelessWidget {
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
"$numActive",
key: ArchSampleKeys.statsActiveItems,
style: Theme.of(context).textTheme.subhead,
),
)
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:redux_sample/models/models.dart';
class TodoItem extends StatelessWidget {
......@@ -18,20 +19,23 @@ class TodoItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Dismissible(
key: new Key(todo.id),
key: ArchSampleKeys.todoItem(todo.id),
onDismissed: onDismissed,
child: new ListTile(
onTap: onTap,
leading: new Checkbox(
key: ArchSampleKeys.todoItemCheckbox(todo.id),
value: todo.complete,
onChanged: onCheckboxChanged,
),
title: new Text(
todo.task,
key: ArchSampleKeys.todoItemTask(todo.id),
style: Theme.of(context).textTheme.title,
),
subtitle: new Text(
todo.note,
key: ArchSampleKeys.todoItemNote(todo.id),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.subhead,
......
......@@ -14,16 +14,20 @@ class TodoList extends StatelessWidget {
final Function(Todo) onUndoRemove;
TodoList({
Key key,
@required this.todos,
@required this.onCheckboxChanged,
@required this.onRemove,
@required this.onUndoRemove,
});
})
: super(key: key);
@override
Widget build(BuildContext context) {
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 {
.then((removedTodo) {
if (removedTodo != null) {
Scaffold.of(context).showSnackBar(new SnackBar(
key: ArchSampleKeys.snackbar,
duration: new Duration(seconds: 2),
backgroundColor: Theme.of(context).backgroundColor,
content: new Text(
......
......@@ -13,6 +13,8 @@ dependencies:
dev_dependencies:
mockito: 2.2.0
flutter_driver:
sdk: flutter
flutter_test:
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);
return new EditTestScreen(driver);
}
Future<Null> tapDeleteButton() async {
await driver.tap(_deleteButtonFinder);
}
Future<Null> tapBackButton() async {
await driver.tap(_backButtonFinder);
return this;
}
}
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
import '../utils.dart';
import 'test_screen.dart';
class EditTestScreen extends TestScreen {
final _editScreenFinder = find.byValueKey('__editTodoScreen__');
final _backButtonFinder = find.byTooltip('Back');
EditTestScreen(FlutterDriver driver) : super(driver);
@override
Future<bool> isReady({Duration timeout}) =>
widgetExists(driver, _editScreenFinder);
Future<Null> tapBackButton() async {
await driver.tap(_backButtonFinder);
return this;
}
}
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
import '../elements/extra_actions_element.dart';
import '../elements/filters_element.dart';
import '../elements/stats_element.dart';
import '../elements/todo_list_element.dart';
import '../utils.dart';
import 'test_screen.dart';
class HomeTestScreen extends TestScreen {
final _filterButtonFinder = find.byValueKey('__filterButton__');
final _extraActionsButtonFinder = find.byValueKey('__extraActionsButton__');
final _todosTabFinder = find.byValueKey('__todoTab__');
final _statsTabFinder = find.byValueKey('__statsTab__');
final _snackbarFinder = find.byValueKey('__snackbar__');
HomeTestScreen(FlutterDriver driver) : super(driver);
@override
Future<bool> isLoading({Duration timeout}) async =>
new TodoListElement(driver).isLoading;
@override
Future<bool> isReady({Duration timeout}) =>
new TodoListElement(driver).isReady;
TodoListElement get todoList {
return new TodoListElement(driver);
}
StatsElement get stats {
return new StatsElement(driver);
}
TodoListElement tapTodosTab() {
driver.tap(_todosTabFinder);
return new TodoListElement(driver);
}
StatsElement tapStatsTab() {
driver.tap(_statsTabFinder);
return new StatsElement(driver);
}
FiltersElement tapFilterButton() {
driver.tap(_filterButtonFinder);
return new FiltersElement(driver);
}
ExtraActionsElement tapExtraActionsButton() {
driver.tap(_extraActionsButtonFinder);
return new ExtraActionsElement(driver);
}
Future<bool> get snackbarVisible {
return widgetExists(driver, _snackbarFinder);
}
}
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
abstract class TestScreen {
final FlutterDriver driver;
TestScreen(this.driver);
Future<bool> isLoading({Duration timeout}) async {
return !(await isReady(timeout: timeout));
}
Future<bool> isReady({Duration timeout});
}
import 'dart:async';
import 'package:flutter_driver/flutter_driver.dart';
Future<bool> widgetExists(
FlutterDriver driver,
SerializableFinder finder, {
Duration timeout,
}) async {
try {
await driver.waitFor(finder, timeout: timeout);
return true;
} catch (_) {
return false;
}
}
Future<bool> widgetAbsent(
FlutterDriver driver,
SerializableFinder finder, {
Duration timeout,
}) async {
try {
await driver.waitForAbsent(finder, timeout: timeout);
return true;
} catch (_) {
return false;
}
}
\ No newline at end of file
// This line imports the extension
import 'package:flutter_driver/driver_extension.dart';
import 'package:redux_sample/main.dart' as app;
void main() {
// This line enables the extension
enableFlutterDriverExtension(handler: (message) async {
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");