Skip to content

Commit 8e4710e

Browse files
committed
Add test for simple_bloc_flutter
1 parent 8b45433 commit 8e4710e

File tree

6 files changed

+343
-11
lines changed

6 files changed

+343
-11
lines changed

simple_bloc_flutter/lib/app.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class SimpleBlocApp extends StatelessWidget {
2626
child: TodosBlocProvider(
2727
bloc: TodosListBloc(todosInteractor),
2828
child: MaterialApp(
29-
onGenerateTitle: (context) => BlocLocalizations.of(context).appTitle,
29+
onGenerateTitle: (context) =>
30+
SimpleBlocLocalizations.of(context).appTitle,
3031
theme: ArchSampleTheme.theme,
3132
localizationsDelegates: [
3233
ArchSampleLocalizationsDelegate(),
33-
InheritedWidgetLocalizationsDelegate(),
34+
SimpleBlocLocalizationsDelegate(),
3435
],
3536
routes: {
3637
ArchSampleRoutes.home: (context) {

simple_bloc_flutter/lib/localization.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ import 'dart:async';
22

33
import 'package:flutter/material.dart';
44

5-
class BlocLocalizations {
6-
static BlocLocalizations of(BuildContext context) {
7-
return Localizations.of<BlocLocalizations>(context, BlocLocalizations)!;
5+
class SimpleBlocLocalizations {
6+
static SimpleBlocLocalizations of(BuildContext context) {
7+
return Localizations.of<SimpleBlocLocalizations>(
8+
context,
9+
SimpleBlocLocalizations,
10+
)!;
811
}
912

1013
String get appTitle => 'Simple Bloc Example';
1114
}
1215

13-
class InheritedWidgetLocalizationsDelegate
14-
extends LocalizationsDelegate<BlocLocalizations> {
16+
class SimpleBlocLocalizationsDelegate
17+
extends LocalizationsDelegate<SimpleBlocLocalizations> {
1518
@override
16-
Future<BlocLocalizations> load(Locale locale) =>
17-
Future(() => BlocLocalizations());
19+
Future<SimpleBlocLocalizations> load(Locale locale) =>
20+
Future(() => SimpleBlocLocalizations());
1821

1922
@override
20-
bool shouldReload(InheritedWidgetLocalizationsDelegate old) => false;
23+
bool shouldReload(SimpleBlocLocalizationsDelegate old) => false;
2124

2225
@override
2326
bool isSupported(Locale locale) =>

simple_bloc_flutter/lib/screens/home_screen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class HomeScreenState extends State<HomeScreen> {
5656
builder: (context, activeTabSnapshot) {
5757
return Scaffold(
5858
appBar: AppBar(
59-
title: Text(BlocLocalizations.of(context).appTitle),
59+
title: Text(SimpleBlocLocalizations.of(context).appTitle),
6060
actions: _buildActions(
6161
todosBloc,
6262
activeTabSnapshot,

simple_bloc_flutter/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dev_dependencies:
3939
sdk: flutter
4040
test:
4141
mockito:
42+
build_runner:
4243
integration_tests:
4344
path: ../integration_tests
4445

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:mockito/annotations.dart';
4+
import 'package:mockito/mockito.dart';
5+
import 'package:simple_bloc_flutter_sample/anonymous_user_repository.dart';
6+
import 'package:simple_bloc_flutter_sample/dependency_injection.dart';
7+
import 'package:simple_bloc_flutter_sample/localization.dart';
8+
import 'package:simple_bloc_flutter_sample/screens/home_screen.dart';
9+
import 'package:simple_bloc_flutter_sample/widgets/todos_bloc_provider.dart';
10+
import 'package:simple_blocs/simple_blocs.dart';
11+
import 'package:todos_app_core/todos_app_core.dart';
12+
import 'package:todos_repository_core/todos_repository_core.dart';
13+
14+
import 'home_screen_test.mocks.dart';
15+
16+
@GenerateNiceMocks([MockSpec<TodosInteractor>(), MockSpec<UserRepository>()])
17+
void main() {
18+
group('HomeScreen', () {
19+
final todoListFinder = find.byKey(ArchSampleKeys.todoList);
20+
final todoItem1Finder = find.byKey(ArchSampleKeys.todoItem('1'));
21+
final todoItem2Finder = find.byKey(ArchSampleKeys.todoItem('2'));
22+
final todoItem3Finder = find.byKey(ArchSampleKeys.todoItem('3'));
23+
24+
testWidgets('should render loading indicator at first', (tester) async {
25+
await tester.pumpWidget(
26+
_TestWidget(
27+
todosInteractor: MockTodosInteractor(),
28+
userRepository: AnonymousUserRepository(),
29+
),
30+
);
31+
await tester.pump(Duration.zero);
32+
33+
expect(find.byKey(ArchSampleKeys.todosLoading), findsOneWidget);
34+
});
35+
36+
testWidgets('should display a list after loading todos', (tester) async {
37+
final handle = tester.ensureSemantics();
38+
final interactor = MockTodosInteractor();
39+
40+
when(
41+
interactor.todos,
42+
).thenAnswer((_) => Stream.fromIterable([_TestWidget._defaultTodos]));
43+
44+
await tester.pumpWidget(
45+
_TestWidget(
46+
todosInteractor: interactor,
47+
userRepository: AnonymousUserRepository(),
48+
),
49+
);
50+
await tester.pumpAndSettle();
51+
52+
final checkbox1 = find.descendant(
53+
of: find.byKey(ArchSampleKeys.todoItemCheckbox('1')),
54+
matching: find.byType(Focus),
55+
);
56+
final checkbox2 = find.descendant(
57+
of: find.byKey(ArchSampleKeys.todoItemCheckbox('2')),
58+
matching: find.byType(Focus),
59+
);
60+
final checkbox3 = find.descendant(
61+
of: find.byKey(ArchSampleKeys.todoItemCheckbox('3')),
62+
matching: find.byType(Focus),
63+
);
64+
65+
expect(todoListFinder, findsOneWidget);
66+
expect(todoItem1Finder, findsOneWidget);
67+
expect(find.text('T1'), findsOneWidget);
68+
expect(find.text('N1'), findsOneWidget);
69+
expect(tester.getSemantics(checkbox1), isChecked(false));
70+
expect(todoItem2Finder, findsOneWidget);
71+
expect(find.text('T2'), findsOneWidget);
72+
expect(tester.getSemantics(checkbox2), isChecked(false));
73+
expect(todoItem3Finder, findsOneWidget);
74+
expect(find.text('T3'), findsOneWidget);
75+
expect(tester.getSemantics(checkbox3), isChecked(true));
76+
77+
handle.dispose();
78+
});
79+
80+
testWidgets('should remove todos using a dismissible', (tester) async {
81+
final interactor = MockTodosInteractor();
82+
83+
when(
84+
interactor.todos,
85+
).thenAnswer((_) => Stream.fromIterable([_TestWidget._defaultTodos]));
86+
87+
await tester.pumpWidget(
88+
_TestWidget(
89+
todosInteractor: interactor,
90+
userRepository: AnonymousUserRepository(),
91+
),
92+
);
93+
await tester.pumpAndSettle();
94+
await tester.drag(todoItem1Finder, Offset(-1000, 0));
95+
await tester.pumpAndSettle(Duration(seconds: 5));
96+
97+
expect(todoItem1Finder, findsNothing);
98+
expect(todoItem2Finder, findsOneWidget);
99+
expect(todoItem3Finder, findsOneWidget);
100+
});
101+
102+
testWidgets('should display stats when switching tabs', (tester) async {
103+
final interactor = MockTodosInteractor();
104+
105+
when(
106+
interactor.todos,
107+
).thenAnswer((_) => Stream.fromIterable([_TestWidget._defaultTodos]));
108+
109+
await tester.pumpWidget(
110+
_TestWidget(
111+
todosInteractor: interactor,
112+
userRepository: AnonymousUserRepository(),
113+
),
114+
);
115+
await tester.pumpAndSettle();
116+
await tester.tap(find.byKey(ArchSampleKeys.statsTab));
117+
await tester.pump();
118+
119+
expect(find.byKey(ArchSampleKeys.statsNumActive), findsOneWidget);
120+
expect(find.byKey(ArchSampleKeys.statsNumActive), findsOneWidget);
121+
});
122+
});
123+
}
124+
125+
class _TestWidget extends StatelessWidget {
126+
const _TestWidget({
127+
required this.todosInteractor,
128+
required this.userRepository,
129+
});
130+
131+
final TodosInteractor todosInteractor;
132+
final UserRepository userRepository;
133+
134+
@override
135+
Widget build(BuildContext context) {
136+
return Injector(
137+
todosInteractor: todosInteractor,
138+
userRepository: userRepository,
139+
child: TodosBlocProvider(
140+
bloc: TodosListBloc(todosInteractor),
141+
child: MaterialApp(
142+
localizationsDelegates: [
143+
SimpleBlocLocalizationsDelegate(),
144+
ArchSampleLocalizationsDelegate(),
145+
],
146+
home: const HomeScreen(),
147+
),
148+
),
149+
);
150+
}
151+
152+
static List<Todo> get _defaultTodos {
153+
return [
154+
Todo('T1', id: '1', note: 'N1'),
155+
Todo('T2', id: '2'),
156+
Todo('T3', id: '3', complete: true),
157+
];
158+
}
159+
}
160+
161+
Matcher isChecked(bool isChecked) {
162+
return matchesSemantics(
163+
isChecked: isChecked,
164+
hasTapAction: true,
165+
hasFocusAction: true,
166+
hasCheckedState: true,
167+
isFocusable: true,
168+
hasEnabledState: true,
169+
isEnabled: true,
170+
);
171+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Mocks generated by Mockito 5.4.6 from annotations
2+
// in simple_bloc_flutter_sample/test/home_screen_test.dart.
3+
// Do not manually edit this file.
4+
5+
// ignore_for_file: no_leading_underscores_for_library_prefixes
6+
import 'dart:async' as _i4;
7+
8+
import 'package:mockito/mockito.dart' as _i1;
9+
import 'package:simple_blocs/simple_blocs.dart' as _i3;
10+
import 'package:todos_repository_core/todos_repository_core.dart' as _i2;
11+
12+
// ignore_for_file: type=lint
13+
// ignore_for_file: avoid_redundant_argument_values
14+
// ignore_for_file: avoid_setters_without_getters
15+
// ignore_for_file: comment_references
16+
// ignore_for_file: deprecated_member_use
17+
// ignore_for_file: deprecated_member_use_from_same_package
18+
// ignore_for_file: implementation_imports
19+
// ignore_for_file: invalid_use_of_visible_for_testing_member
20+
// ignore_for_file: must_be_immutable
21+
// ignore_for_file: prefer_const_constructors
22+
// ignore_for_file: unnecessary_parenthesis
23+
// ignore_for_file: camel_case_types
24+
// ignore_for_file: subtype_of_sealed_class
25+
26+
class _FakeReactiveTodosRepository_0 extends _i1.SmartFake
27+
implements _i2.ReactiveTodosRepository {
28+
_FakeReactiveTodosRepository_0(Object parent, Invocation parentInvocation)
29+
: super(parent, parentInvocation);
30+
}
31+
32+
class _FakeUserEntity_1 extends _i1.SmartFake implements _i2.UserEntity {
33+
_FakeUserEntity_1(Object parent, Invocation parentInvocation)
34+
: super(parent, parentInvocation);
35+
}
36+
37+
/// A class which mocks [TodosInteractor].
38+
///
39+
/// See the documentation for Mockito's code generation for more information.
40+
class MockTodosInteractor extends _i1.Mock implements _i3.TodosInteractor {
41+
@override
42+
_i2.ReactiveTodosRepository get repository =>
43+
(super.noSuchMethod(
44+
Invocation.getter(#repository),
45+
returnValue: _FakeReactiveTodosRepository_0(
46+
this,
47+
Invocation.getter(#repository),
48+
),
49+
returnValueForMissingStub: _FakeReactiveTodosRepository_0(
50+
this,
51+
Invocation.getter(#repository),
52+
),
53+
)
54+
as _i2.ReactiveTodosRepository);
55+
56+
@override
57+
_i4.Stream<List<_i3.Todo>> get todos =>
58+
(super.noSuchMethod(
59+
Invocation.getter(#todos),
60+
returnValue: _i4.Stream<List<_i3.Todo>>.empty(),
61+
returnValueForMissingStub: _i4.Stream<List<_i3.Todo>>.empty(),
62+
)
63+
as _i4.Stream<List<_i3.Todo>>);
64+
65+
@override
66+
_i4.Stream<bool> get allComplete =>
67+
(super.noSuchMethod(
68+
Invocation.getter(#allComplete),
69+
returnValue: _i4.Stream<bool>.empty(),
70+
returnValueForMissingStub: _i4.Stream<bool>.empty(),
71+
)
72+
as _i4.Stream<bool>);
73+
74+
@override
75+
_i4.Stream<bool> get hasCompletedTodos =>
76+
(super.noSuchMethod(
77+
Invocation.getter(#hasCompletedTodos),
78+
returnValue: _i4.Stream<bool>.empty(),
79+
returnValueForMissingStub: _i4.Stream<bool>.empty(),
80+
)
81+
as _i4.Stream<bool>);
82+
83+
@override
84+
_i4.Stream<_i3.Todo> todo(String? id) =>
85+
(super.noSuchMethod(
86+
Invocation.method(#todo, [id]),
87+
returnValue: _i4.Stream<_i3.Todo>.empty(),
88+
returnValueForMissingStub: _i4.Stream<_i3.Todo>.empty(),
89+
)
90+
as _i4.Stream<_i3.Todo>);
91+
92+
@override
93+
_i4.Future<void> updateTodo(_i3.Todo? todo) =>
94+
(super.noSuchMethod(
95+
Invocation.method(#updateTodo, [todo]),
96+
returnValue: _i4.Future<void>.value(),
97+
returnValueForMissingStub: _i4.Future<void>.value(),
98+
)
99+
as _i4.Future<void>);
100+
101+
@override
102+
_i4.Future<void> addNewTodo(_i3.Todo? todo) =>
103+
(super.noSuchMethod(
104+
Invocation.method(#addNewTodo, [todo]),
105+
returnValue: _i4.Future<void>.value(),
106+
returnValueForMissingStub: _i4.Future<void>.value(),
107+
)
108+
as _i4.Future<void>);
109+
110+
@override
111+
_i4.Future<void> deleteTodo(String? id) =>
112+
(super.noSuchMethod(
113+
Invocation.method(#deleteTodo, [id]),
114+
returnValue: _i4.Future<void>.value(),
115+
returnValueForMissingStub: _i4.Future<void>.value(),
116+
)
117+
as _i4.Future<void>);
118+
119+
@override
120+
_i4.Future<void> clearCompleted([dynamic _0]) =>
121+
(super.noSuchMethod(
122+
Invocation.method(#clearCompleted, [_0]),
123+
returnValue: _i4.Future<void>.value(),
124+
returnValueForMissingStub: _i4.Future<void>.value(),
125+
)
126+
as _i4.Future<void>);
127+
128+
@override
129+
_i4.Future<List<dynamic>> toggleAll([dynamic _0]) =>
130+
(super.noSuchMethod(
131+
Invocation.method(#toggleAll, [_0]),
132+
returnValue: _i4.Future<List<dynamic>>.value(<dynamic>[]),
133+
returnValueForMissingStub: _i4.Future<List<dynamic>>.value(
134+
<dynamic>[],
135+
),
136+
)
137+
as _i4.Future<List<dynamic>>);
138+
}
139+
140+
/// A class which mocks [UserRepository].
141+
///
142+
/// See the documentation for Mockito's code generation for more information.
143+
class MockUserRepository extends _i1.Mock implements _i2.UserRepository {
144+
@override
145+
_i4.Future<_i2.UserEntity> login() =>
146+
(super.noSuchMethod(
147+
Invocation.method(#login, []),
148+
returnValue: _i4.Future<_i2.UserEntity>.value(
149+
_FakeUserEntity_1(this, Invocation.method(#login, [])),
150+
),
151+
returnValueForMissingStub: _i4.Future<_i2.UserEntity>.value(
152+
_FakeUserEntity_1(this, Invocation.method(#login, [])),
153+
),
154+
)
155+
as _i4.Future<_i2.UserEntity>);
156+
}

0 commit comments

Comments
 (0)