Commit b613648a authored by Brian Egan's avatar Brian Egan

Get built_redux sample running with the integration tests

parent 28890e40
......@@ -2,6 +2,7 @@ import 'package:built_redux_sample/models/models.dart';
import 'package:built_redux_sample/actions/actions.dart';
import 'package:built_redux_sample/presentation/add_edit_screen.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:flutter_built_redux/flutter_built_redux.dart';
class EditTodo
......@@ -13,6 +14,7 @@ class EditTodo
@override
Widget build(BuildContext context, _, AppActions actions) {
return new AddEditScreen(
key: ArchSampleKeys.editTodoScreen,
isEditing: true,
onSave: (task, note) {
actions.updateTodoAction(new UpdateTodoActionPayload(
......
......@@ -5,7 +5,6 @@ import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
import 'package:flutter_built_redux/flutter_built_redux.dart';
typedef OnTabsSelected = void Function(int);
class TabSelector
......@@ -25,7 +24,12 @@ class TabSelector
},
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.stats
? ArchSampleKeys.statsTab
: ArchSampleKeys.todoTab,
),
title: new Text(tab == AppTab.stats
? ArchSampleLocalizations.of(context).stats
: ArchSampleLocalizations.of(context).todos),
......
import 'package:flutter/src/widgets/framework.dart';
typedef ViewModelBuilder<ViewModel> = Function(
BuildContext context, ViewModel vm
);
BuildContext context, ViewModel vm);
......@@ -11,13 +11,16 @@ import 'package:path_provider/path_provider.dart';
/// In most apps, we use the provided repository. In this case, it makes sense
/// to demonstrate the built_value serializers, which are used in the
/// FileStorage part of this app.
///
/// Please see the `todos_repository` library for more information about the
/// Repository pattern.
class TodosRepository {
final FileStorage fileStorage;
final WebClient webClient;
const TodosRepository({
this.fileStorage = const FileStorage(
'__built_redux__',
'__built_redux_sample_app__',
getApplicationDocumentsDirectory,
),
this.webClient = const WebClient(),
......
......@@ -9,7 +9,7 @@ import 'package:built_redux_sample/models/models.dart';
class WebClient {
final Duration delay;
const WebClient([this.delay = const Duration(milliseconds: 3000)]);
const WebClient([this.delay = const Duration(milliseconds: 1200)]);
/// Mock that "fetches" some Todos from a "web service" after a short delay
Future<List<Todo>> fetchTodos() async {
......@@ -19,24 +19,32 @@ class WebClient {
new Todo.builder(
(b) => b
..task = 'Buy food for da kitty'
..note = 'With the chickeny bits!',
..note = 'With the chickeny bits!'
..id = '1',
),
new Todo.builder(
(b) => b
..task = 'Find a Red Sea dive trip'
..note = 'Echo vs MY Dream',
..note = 'Echo vs MY Dream'
..id = '2',
),
new Todo.builder(
(b) => b
..task = 'Book flights to Egypt'
..complete = true,
..complete = true
..id = '3',
),
new Todo.builder(
(b) => b
..task = 'Decide on accommodation'
..id = '4',
),
new Todo('Decide on accommodation'),
new Todo.builder(
(b) => b
..task = 'Sip Margaritas'
..note = 'on the beach'
..complete = true,
..complete = true
..id = '5',
),
]);
}
......
......@@ -16,7 +16,7 @@ void main() {
runApp(new BuiltReduxApp());
}
class BuiltReduxApp extends StatelessWidget {
class BuiltReduxApp extends StatefulWidget {
final store = new Store<AppState, AppStateBuilder, AppActions>(
reducerBuilder.build(),
new AppState.loading(),
......@@ -26,8 +26,26 @@ class BuiltReduxApp extends StatelessWidget {
],
);
@override
State<StatefulWidget> createState() {
return new BuiltReduxAppState();
}
}
class BuiltReduxAppState extends State<BuiltReduxApp> {
Store<AppState, AppStateBuilder, AppActions> store;
BuiltReduxApp() {}
@override
void initState() {
store = widget.store;
store.actions.fetchTodosAction();
super.initState();
}
@override
Widget build(BuildContext context) {
return new ReduxProvider(
......@@ -41,9 +59,7 @@ class BuiltReduxApp extends StatelessWidget {
],
routes: {
ArchSampleRoutes.home: (context) {
store.actions.fetchTodosAction();
return new HomeScreen();
return new HomeScreen(key: ArchSampleKeys.homeScreen);
},
ArchSampleRoutes.addTodo: (context) {
return new AddTodo();
......
......@@ -11,6 +11,8 @@ Middleware<AppState, AppStateBuilder, AppActions> createStoreTodosMiddleware([
..add(AppActionsNames.addTodoAction, createSaveTodos<Todo>(service))
..add(AppActionsNames.clearCompletedAction,
createSaveTodos<Null>(service))
..add(AppActionsNames.loadTodosSuccess,
createSaveTodos<List<Todo>>(service))
..add(
AppActionsNames.deleteTodoAction, createSaveTodos<String>(service))
..add(AppActionsNames.toggleAllAction, createSaveTodos<Null>(service))
......@@ -24,10 +26,9 @@ MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null> createFetchTodos(
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
if (api.state.isLoading) {
service
.loadTodos()
.then(api.actions.loadTodosSuccess)
.catchError(api.actions.loadTodosFailure);
service.loadTodos().then((todos) {
return api.actions.loadTodosSuccess(todos);
}).catchError(api.actions.loadTodosFailure);
}
next(action);
......
......@@ -15,7 +15,7 @@ class DetailsScreen extends StatelessWidget {
@required this.onDelete,
@required this.toggleCompleted,
})
: super(key: key);
: super(key: ArchSampleKeys.todoDetailsScreen);
@override
Widget build(BuildContext context) {
......@@ -28,6 +28,7 @@ class DetailsScreen extends StatelessWidget {
new IconButton(
tooltip: localizations.deleteTodo,
icon: new Icon(Icons.delete),
key: ArchSampleKeys.deleteTodoButton,
onPressed: () {
onDelete();
Navigator.pop(context, todo);
......@@ -45,6 +46,7 @@ class DetailsScreen extends StatelessWidget {
new Padding(
padding: new EdgeInsets.only(right: 8.0),
child: new Checkbox(
key: ArchSampleKeys.detailsTodoItemCheckbox,
value: todo.complete,
onChanged: toggleCompleted,
),
......@@ -60,11 +62,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 +80,7 @@ class DetailsScreen extends StatelessWidget {
),
),
floatingActionButton: new FloatingActionButton(
key: ArchSampleKeys.editTodoFab,
tooltip: localizations.editTodo,
child: new Icon(Icons.edit),
onPressed: () {
......
......@@ -16,18 +16,21 @@ class ExtraActionsButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new PopupMenuButton<ExtraAction>(
key: ArchSampleKeys.extraActionsButton,
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),
child:
new Text(ArchSampleLocalizations.of(context).clearCompleted),
),
],
);
......
......@@ -23,6 +23,7 @@ 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) =>
......@@ -31,6 +32,7 @@ class FilterButton extends StatelessWidget {
value: VisibilityFilter.all,
child: new Text(
ArchSampleLocalizations.of(context).showAll,
key: ArchSampleKeys.allFilter,
style: activeFilter == VisibilityFilter.all
? activeStyle
: defaultStyle,
......@@ -40,6 +42,7 @@ class FilterButton extends StatelessWidget {
value: VisibilityFilter.active,
child: new Text(
ArchSampleLocalizations.of(context).showActive,
key: ArchSampleKeys.activeFilter,
style: activeFilter == VisibilityFilter.active
? activeStyle
: defaultStyle,
......@@ -49,6 +52,7 @@ class FilterButton extends StatelessWidget {
value: VisibilityFilter.completed,
child: new Text(
ArchSampleLocalizations.of(context).showCompleted,
key: ArchSampleKeys.completedFilter,
style: activeFilter == VisibilityFilter.completed
? activeStyle
: defaultStyle,
......
......@@ -18,6 +18,7 @@ class StatsCounter extends StatelessWidget {
return new AppLoading(builder: (context, loading) {
return loading
? new Center(
key: ArchSampleKeys.statsLoading,
child: new CircularProgressIndicator(
key: ArchSampleKeys.statsLoading,
))
......@@ -36,6 +37,7 @@ class StatsCounter extends StatelessWidget {
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
'$numCompleted',
key: ArchSampleKeys.statsNumCompleted,
style: Theme.of(context).textTheme.subhead,
),
),
......@@ -50,6 +52,7 @@ class StatsCounter extends StatelessWidget {
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
"$numActive",
key: ArchSampleKeys.statsNumActive,
style: Theme.of(context).textTheme.subhead,
),
)
......
import 'package:built_redux_sample/models/models.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_architecture_samples/flutter_architecture_samples.dart';
class TodoItem extends StatelessWidget {
final DismissDirectionCallback onDismissed;
......@@ -13,7 +14,8 @@ class TodoItem extends StatelessWidget {
@required this.onTap,
@required this.onCheckboxChanged,
@required this.todo,
});
})
: super(key: ArchSampleKeys.todoItem(todo.id));
@override
Widget build(BuildContext context) {
......@@ -23,15 +25,18 @@ class TodoItem extends StatelessWidget {
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,
......
......@@ -25,6 +25,7 @@ class TodoList extends StatelessWidget {
return new AppLoading(builder: (context, loading) {
return loading
? new Center(
key: ArchSampleKeys.todosLoading,
child: new CircularProgressIndicator(
key: ArchSampleKeys.statsLoading,
))
......@@ -55,6 +56,7 @@ class TodoList extends StatelessWidget {
onRemove(todo);
Scaffold.of(context).showSnackBar(new SnackBar(
key: ArchSampleKeys.snackbar,
duration: new Duration(seconds: 2),
backgroundColor: Theme.of(context).backgroundColor,
content: new Text(
......@@ -81,6 +83,7 @@ class TodoList extends StatelessWidget {
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(
......
......@@ -20,6 +20,10 @@ dev_dependencies:
source_gen: ^0.7.0
test: any
mockito: 2.2.0
flutter_driver:
sdk: flutter
integration_tests:
path: ../integration_tests
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
......
// This line imports the extension
import 'package:flutter_driver/driver_extension.dart';
import 'package:built_redux_sample/main.dart' as app;
void main() {
enableFlutterDriverExtension();
app.main();
}
import 'package:integration_tests/integration_tests.dart' as integrationTests;
main() {
integrationTests.main();
}
......@@ -4,7 +4,6 @@ import 'package:inherited_widget_sample/localization.dart';
import 'package:inherited_widget_sample/screens/add_edit_screen.dart';
import 'package:inherited_widget_sample/screens/home_screen.dart';
class InheritedWidgetApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
......
......@@ -16,8 +16,7 @@ class AppState {
bool get allComplete => todos.every((todo) => todo.complete);
List<Todo> get filteredTodos =>
todos.where((todo) {
List<Todo> get filteredTodos => todos.where((todo) {
if (activeFilter == VisibilityFilter.all) {
return true;
} else if (activeFilter == VisibilityFilter.active) {
......
......@@ -31,8 +31,8 @@ class ExtraActionsButton extends StatelessWidget {
new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.clearCompleted,
value: ExtraAction.clearCompleted,
child: new Text(
ArchSampleLocalizations.of(context).clearCompleted),
child:
new Text(ArchSampleLocalizations.of(context).clearCompleted),
),
],
);
......
......@@ -10,4 +10,4 @@ typedef TodoUpdater(
String id,
String note,
String task,
});
});
......@@ -52,8 +52,10 @@ main() {
await detailsScreen.tapDeleteButton();
expect(await homeScreen.todoList.todoItem('2').isAbsent, isTrue);
expect(await homeScreen.snackbarVisible, isTrue);
expect(await homeScreen.todoList.todoItem('2').isAbsent, isTrue,
reason: 'TodoItem2 should be absent');
expect(await homeScreen.snackbarVisible, isTrue,
reason: 'snackbar should be visible');
});
test('should filter to completed todos', () async {
......
......@@ -9,11 +9,9 @@ class FiltersElement extends TestElement {
FiltersElement(FlutterDriver driver) : super(driver);
Future<Null> tapShowAll() async =>
await driver.tap(_allFilter);
Future<Null> tapShowAll() async => await driver.tap(_allFilter);
Future<Null> tapShowActive() async =>
await driver.tap(_activeFilter);
Future<Null> tapShowActive() async => await driver.tap(_activeFilter);
Future<Null> tapShowCompleted() async {
return await driver.tap(_completedFilter);
......
......@@ -6,7 +6,7 @@ Future<bool> widgetExists(
FlutterDriver driver,
SerializableFinder finder, {
Duration timeout,
}) async {
}) async {
try {
await driver.waitFor(finder, timeout: timeout);
......@@ -20,7 +20,7 @@ Future<bool> widgetAbsent(
FlutterDriver driver,
SerializableFinder finder, {
Duration timeout,
}) async {
}) async {
try {
await driver.waitForAbsent(finder, timeout: timeout);
......
......@@ -8,7 +8,7 @@ import 'package:path_provider/path_provider.dart';
List<Middleware<AppState>> createStoreTodosMiddleware([
TodosRepository repository = const TodosRepository(
fileStorage: const FileStorage(
'redux_sample',
'redux_sample_app_',
getApplicationDocumentsDirectory,
),
),
......
......@@ -3,4 +3,3 @@ export 'extra_action.dart';
export 'visibility_filter.dart';
export 'app_state.dart';
export 'todo.dart';
......@@ -28,8 +28,8 @@ class ExtraActionsButton extends StatelessWidget {
new PopupMenuItem<ExtraAction>(
key: ArchSampleKeys.clearCompleted,
value: ExtraAction.clearCompleted,
child: new Text(
ArchSampleLocalizations.of(context).clearCompleted),
child:
new Text(ArchSampleLocalizations.of(context).clearCompleted),
),
],
);
......
import 'package:flutter/material.dart';
class LoadingIndicator extends StatelessWidget {
LoadingIndicator({Key key}) :super(key: key);
LoadingIndicator({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
......
......@@ -4,6 +4,4 @@ typedef TodoAdder(Todo todo);
typedef TodoRemover(String id);
typedef TodoUpdater(
String id,
Todo todo);
typedef TodoUpdater(String id, Todo todo);
......@@ -14,8 +14,7 @@ final todosReducer = combineTypedReducers<List<Todo>>([
]);
List<Todo> _addTodo(List<Todo> todos, AddTodoAction action) {
return new List.from(todos)
..add(action.todo);
return new List.from(todos)..add(action.todo);
}
List<Todo> _deleteTodo(List<Todo> todos, DeleteTodoAction action) {
......
......@@ -22,7 +22,8 @@ class FileStorage {
final string = await file.readAsString();
final json = new JsonDecoder().convert(string);
final todos = (json['todos'] as List<Map<String, dynamic>>)
.map((todo) => TodoEntity.fromJson(todo)).toList();
.map((todo) => TodoEntity.fromJson(todo))
.toList();
return todos;
}
......
......@@ -49,8 +49,7 @@ class AddEditScreen extends StatelessWidget {
autofocus: isEditing ? false : true,
style: Theme.of(context).textTheme.headline,
decoration: new InputDecoration(
hintText:
ArchSampleLocalizations.of(context).newTodoHint),
hintText: ArchSampleLocalizations.of(context).newTodoHint),
validator: (val) => val.trim().isEmpty
? ArchSampleLocalizations.of(context).emptyTodoError
: null,
......
......@@ -28,8 +28,8 @@ class ExtraActionsButton extends StatelessWidget {
),
new PopupMenuItem<ExtraAction>(
value: ExtraAction.clearCompleted,
child: new Text(
ArchSampleLocalizations.of(context).clearCompleted),
child:
new Text(ArchSampleLocalizations.of(context).clearCompleted),
),
],
);
......
......@@ -18,45 +18,29 @@ class StatsCounter extends StatelessWidget {
new Padding(
padding: new EdgeInsets.only(bottom: 8.0),
child: new Text(
ArchSampleLocalizations
.of(context)
.completedTodos,
style: Theme
.of(context)
.textTheme
.title,
ArchSampleLocalizations.of(context).completedTodos,
style: Theme.of(context).textTheme.title,
),
),
new Padding(
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
'$numCompleted',
style: Theme
.of(context)
.textTheme
.subhead,
style: Theme.of(context).textTheme.subhead,
),
),
new Padding(
padding: new EdgeInsets.only(bottom: 8.0),
child: new Text(
ArchSampleLocalizations
.of(context)
.activeTodos,
style: Theme
.of(context)
.textTheme
.title,
ArchSampleLocalizations.of(context).activeTodos,
style: Theme.of(context).textTheme.title,
),
),
new Padding(
padding: new EdgeInsets.only(bottom: 24.0),
child: new Text(
"$numActive",
style: Theme
.of(context)
.textTheme
.subhead,
style: Theme.of(context).textTheme.subhead,
),
)
],
......
......@@ -10,4 +10,4 @@ typedef TodoUpdater(
String id,
String note,
String task,
});
});
......@@ -43,8 +43,8 @@ bool _messagesExistFor(String locale) {
}
MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
onFailure: (_) => null);
var actualLocale =
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
if (actualLocale == null) return null;
return _findExact(actualLocale);
}
......@@ -17,29 +17,37 @@ class MessageLookup extends MessageLookupByLibrary {
final messages = _notInlinedMessages(_notInlinedMessages);
static _notInlinedMessages(_) => {
"activeTodos" : MessageLookupByLibrary.simpleMessage("Active Todos"),
"addTodo" : MessageLookupByLibrary.simpleMessage("Add Todo"),
"cancel" : MessageLookupByLibrary.simpleMessage("Cancel"),
"clearCompleted" : MessageLookupByLibrary.simpleMessage("Clear completed"),
"completedTodos" : MessageLookupByLibrary.simpleMessage("Completed Todos"),
"delete" : MessageLookupByLibrary.simpleMessage("Delete"),
"deleteTodo" : MessageLookupByLibrary.simpleMessage("Delete Todo"),
"deleteTodoConfirmation" : MessageLookupByLibrary.simpleMessage("Delete this todo?"),
"editTodo" : MessageLookupByLibrary.simpleMessage("Edit Todo"),
"emptyTodoError" : MessageLookupByLibrary.simpleMessage("Please enter some text"),
"filterTodos" : MessageLookupByLibrary.simpleMessage("Filter Todos"),
"markAllComplete" : MessageLookupByLibrary.simpleMessage("Mark all complete"),
"markAllIncomplete" : MessageLookupByLibrary.simpleMessage("Mark all incomplete"),
"newTodoHint" : MessageLookupByLibrary.simpleMessage("What needs to be done?"),
"notesHint" : MessageLookupByLibrary.simpleMessage("Additional Notes..."),
"saveChanges" : MessageLookupByLibrary.simpleMessage("Save changes"),
"showActive" : MessageLookupByLibrary.simpleMessage("Show Active"),
"showAll" : MessageLookupByLibrary.simpleMessage("Show All"),
"showCompleted" : MessageLookupByLibrary.simpleMessage("Show Completed"),
"stats" : MessageLookupByLibrary.simpleMessage("Stats"),
"todoDeleted" : m0,
"todoDetails" : MessageLookupByLibrary.simpleMessage("Todo Details"),
"todos" : MessageLookupByLibrary.simpleMessage("Todos"),
"undo" : MessageLookupByLibrary.simpleMessage("Undo")
"activeTodos": MessageLookupByLibrary.simpleMessage("Active Todos"),
"addTodo": MessageLookupByLibrary.simpleMessage("Add Todo"),