Skip to content

Commit 5758c78

Browse files
committed
chore: write tests for side menu screen (#61)
# Conflicts: # lib/modules/home/home_presenter.dart # lib/modules/home/home_view.dart
1 parent fae74f7 commit 5758c78

18 files changed

+620
-22
lines changed

lib/modules/home/components/body.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Body extends StatelessWidget {
2222
sliderMain: StreamsSelector0<bool>.value(
2323
stream: state._isUserInteractionEnabled,
2424
builder: (_, isUserInteractionEnabled, child) => IgnorePointer(
25+
key: HomeView.mainIgnorePointer,
2526
ignoring: !isUserInteractionEnabled,
2627
child: child,
2728
),

lib/modules/home/components/top_bar.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class TopBar extends StatelessWidget {
4848
],
4949
),
5050
PlatformButton(
51-
onPressed: () => state.delegate?.userAvatarDidTap.add(null),
51+
key: HomeView.userAvatarButtonKey,
52+
onPressed: () =>
53+
state.delegate?.userAvatarButtonDidTap.add(null),
5254
materialFlat: (_, __) => MaterialFlatButtonData(
5355
color: Colors.transparent,
5456
),

lib/modules/home/home_presenter.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class HomePresenterImpl extends HomePresenter
4444
final alertDialogDidClose = BehaviorSubject<void>();
4545

4646
@override
47-
final userAvatarDidTap = BehaviorSubject<void>();
47+
final userAvatarButtonDidTap = BehaviorSubject<void>();
4848

4949
@override
5050
final sideMenuDidDismiss = BehaviorSubject<void>();
@@ -123,7 +123,7 @@ class HomePresenterImpl extends HomePresenter
123123
interactor.fetchSurveysFromRemote();
124124
}
125125

126-
void _userAvatarDidTap() {
126+
void _userAvatarButtonDidTap() {
127127
view.showSideMenu();
128128
}
129129

lib/modules/home/home_view.dart

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ abstract class HomeViewDelegate implements AlertViewMixinDelegate {
77

88
BehaviorSubject<void> get didSwipeDown;
99

10-
BehaviorSubject<void> get userAvatarDidTap;
10+
BehaviorSubject<void> get userAvatarButtonDidTap;
1111

1212
BehaviorSubject<void> get sideMenuDidShow;
1313

@@ -20,13 +20,15 @@ abstract class HomeView extends View<HomeViewDelegate>
2020
with AlertViewMixin, ProgressHUDViewMixin {
2121
static const currentDateTextKey = Key("current_date_text");
2222
static const userAvatarImageKey = Key("user_avatar_image");
23+
static const userAvatarButtonKey = Key("user_avatar_button");
2324
static const titleTextSlideItemKey = Key("title_text_slide_item");
2425
static const descriptionTextSlideItemKey = Key("description_text_slide_item");
2526
static const backgroundImageSlideItemKey = Key("background_image_slide_item");
2627
static const dotPageControlKey = Key("dot_page_control_key");
2728
static const skeletonKey = Key("skeleton_key");
2829
static const showDetailButtonKey = Key("show_detail_button");
2930
static const sliderMenuContainerKey = Key("slider_menu_container");
31+
static const mainIgnorePointer = Key("home_ignore_pointer");
3032

3133
static const dotPageControlHighlightColor = Colors.white;
3234
static const dotPageControlNormalColor = Color.fromRGBO(255, 255, 255, 0.2);
@@ -69,28 +71,15 @@ class _HomeViewImplState
6971
final _isLoading = BehaviorSubject<bool>.seeded(false);
7072
final _isUserInteractionEnabled = BehaviorSubject<bool>.seeded(true);
7173
final _sliderMenuContainerKey = GlobalKey<SliderMenuContainerState>();
74+
final _widgetsBinding = WidgetsBinding.instance!;
7275

7376
@override
7477
void initState() {
7578
super.initState();
7679
delegate?.stateDidInit.add(null);
7780

78-
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
79-
final animationController =
80-
_sliderMenuContainerKey.currentState?.animationController;
81-
animationController?.addListener(() {
82-
switch (animationController.status) {
83-
case AnimationStatus.completed:
84-
delegate?.sideMenuDidShow.add(null);
85-
break;
86-
case AnimationStatus.dismissed:
87-
delegate?.sideMenuDidDismiss.add(null);
88-
break;
89-
default:
90-
break;
91-
}
92-
});
93-
});
81+
_widgetsBinding
82+
.scheduleFrameCallback(_listenSliderMenuContainerAnimationController);
9483
}
9584

9685
@override
@@ -139,4 +128,27 @@ class _HomeViewImplState
139128
void setCurrentPage(int page) {
140129
_currentPage.add(page);
141130
}
131+
132+
void _listenSliderMenuContainerAnimationController(Duration timestamp) {
133+
final animationController =
134+
_sliderMenuContainerKey.currentState?.animationController;
135+
if (animationController == null) {
136+
_widgetsBinding
137+
.scheduleFrameCallback(_listenSliderMenuContainerAnimationController);
138+
return;
139+
}
140+
141+
animationController.addListener(() {
142+
switch (animationController.status) {
143+
case AnimationStatus.completed:
144+
delegate?.sideMenuDidShow.add(null);
145+
break;
146+
case AnimationStatus.dismissed:
147+
delegate?.sideMenuDidDismiss.add(null);
148+
break;
149+
default:
150+
break;
151+
}
152+
});
153+
}
142154
}

lib/modules/side_menu/components/actions.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Actions extends StatelessWidget {
88
final state = context.findAncestorStateOfType<_SideMenuViewImplState>()!;
99

1010
return PlatformButton(
11+
key: SideMenuView.logoutButtonKey,
1112
onPressed: () => state.delegate?.logoutButtonDidTap.add(null),
1213
cupertino: (_, __) => CupertinoButtonData(
1314
padding: EdgeInsets.zero,

lib/modules/side_menu/components/user.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class User extends StatelessWidget {
3737
child: ClipRRect(
3838
borderRadius: BorderRadius.circular(18),
3939
child: Image(
40+
key: SideMenuView.userAvatarImageKey,
4041
image: NetworkImage(user.avatarUrl!),
4142
),
4243
),

lib/modules/side_menu/side_menu_view.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ abstract class SideMenuViewDelegate {
77
}
88

99
abstract class SideMenuView extends View<SideMenuViewDelegate> {
10+
static const userAvatarImageKey = Key("user_avatar_image");
11+
static const logoutButtonKey = Key("logout_button");
12+
1013
void setUser(UserInfo user);
1114
}
1215

test/modules/home/home_presenter_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,35 @@ void main() {
171171
verify(view.showUser(user)).called(1);
172172
});
173173
});
174+
175+
describe("it's sideMenuDidShow emits", () {
176+
beforeEach(() {
177+
presenter.sideMenuDidShow.add(null);
178+
});
179+
180+
it("trigger view to disable user interaction", () {
181+
verify(view.setUserInteractionEnable(isEnabled: false)).called(1);
182+
});
183+
});
184+
185+
describe("it's sideMenuDidDismiss emits", () {
186+
beforeEach(() {
187+
presenter.sideMenuDidDismiss.add(null);
188+
});
189+
190+
it("trigger view to enable user interaction", () {
191+
verify(view.setUserInteractionEnable(isEnabled: true)).called(1);
192+
});
193+
});
194+
195+
describe("it's userAvatarButtonDidTap emits", () {
196+
beforeEach(() {
197+
presenter.userAvatarButtonDidTap.add(null);
198+
});
199+
200+
it("trigger view to show side menu", () {
201+
verify(view.showSideMenu()).called(1);
202+
});
203+
});
174204
});
175205
}

test/modules/home/home_view_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ void main() {
6565
.thenAnswer((realInvocation) => generator.make(3));
6666
when(delegate.didSwipeDown)
6767
.thenAnswer((realInvocation) => generator.make(4));
68+
when(delegate.sideMenuDidShow)
69+
.thenAnswer((realInvocation) => generator.make(5));
70+
when(delegate.sideMenuDidDismiss)
71+
.thenAnswer((realInvocation) => generator.make(6));
72+
when(delegate.userAvatarButtonDidTap)
73+
.thenAnswer((realInvocation) => generator.make(7));
6874

6975
module = FakeModule(
7076
builder: () => const HomeViewImpl(),
@@ -255,5 +261,54 @@ void main() {
255261
expect(delegate.showDetailButtonDidTap, emits(surveys.first));
256262
});
257263
});
264+
265+
describe("it's showSideMenu() is called", () {
266+
beforeEach((tester) async {
267+
await tester.pumpAndSettle();
268+
module.view.showSideMenu();
269+
await tester.pumpAndSettle();
270+
});
271+
272+
it("triggers delegate's sideMenuDidShow emits", (tester) async {
273+
expect(delegate.sideMenuDidShow, emits(null));
274+
});
275+
276+
describe("then wipes from left to right", () {
277+
beforeEach((tester) async {
278+
final location = tester.getCenter(find.byKey(HomeView.bodyKey));
279+
await tester.flingFrom(location, Offset(location.dx, 0), location.dx);
280+
await tester.pumpAndSettle();
281+
});
282+
283+
it("triggers delegate's sideMenuDidDismiss emits", (tester) async {
284+
expect(delegate.sideMenuDidDismiss, emits(null));
285+
});
286+
});
287+
});
288+
289+
describe("it's user avatar button is tapped", () {
290+
beforeEach((tester) async {
291+
await tester.pumpAndSettle();
292+
await tester.tap(find.byKey(HomeView.userAvatarButtonKey));
293+
});
294+
295+
it("triggers delegate's userAvatarButtonDidTap emits", (tester) async {
296+
expect(delegate.userAvatarButtonDidTap, emits(null));
297+
});
298+
});
299+
300+
describe("it's setUserInteractionEnable() is called", () {
301+
beforeEach((tester) async {
302+
module.view.setUserInteractionEnable(isEnabled: false);
303+
await tester.pumpAndSettle();
304+
});
305+
306+
it("triggers main ignore pointer updates with correct ignoring value",
307+
(tester) async {
308+
final widget = tester
309+
.widget<IgnorePointer>(find.byKey(HomeView.mainIgnorePointer));
310+
expect(widget.ignoring, true);
311+
});
312+
});
258313
});
259314
}

test/modules/home/home_view_test.mocks.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate {
3939
Invocation.getter(#didSwipeDown),
4040
returnValue: _FakeBehaviorSubject<void>()) as _i2.BehaviorSubject<void>);
4141
@override
42-
_i2.BehaviorSubject<void> get userAvatarDidTap => (super.noSuchMethod(
43-
Invocation.getter(#userAvatarDidTap),
42+
_i2.BehaviorSubject<void> get userAvatarButtonDidTap => (super.noSuchMethod(
43+
Invocation.getter(#userAvatarButtonDidTap),
4444
returnValue: _FakeBehaviorSubject<void>()) as _i2.BehaviorSubject<void>);
4545
@override
4646
_i2.BehaviorSubject<void> get sideMenuDidShow => (super.noSuchMethod(
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:mockito/annotations.dart';
3+
import 'package:mockito/mockito.dart';
4+
import 'package:quick_test/quick_test.dart';
5+
import 'package:survey/modules/side_menu/side_menu_module.dart';
6+
import 'package:survey/repositories/auth_repository.dart';
7+
import 'package:survey/repositories/survey_repository.dart';
8+
import 'package:survey/services/locator/locator_service.dart';
9+
import '../../helpers/behavior_subject_generator.dart';
10+
import 'side_menu_interactor_test.mocks.dart';
11+
12+
@GenerateMocks([SideMenuInteractorDelegate, SurveyRepository, AuthRepository])
13+
void main() {
14+
describe("a SideMenu interactor", () {
15+
late SideMenuInteractor interactor;
16+
late MockSideMenuInteractorDelegate delegate;
17+
late MockAuthRepository authRepository;
18+
late BehaviorSubjectGenerator generator;
19+
20+
beforeEach(() {
21+
generator = BehaviorSubjectGenerator();
22+
23+
delegate = MockSideMenuInteractorDelegate();
24+
when(delegate.logoutDidSuccess)
25+
.thenAnswer((realInvocation) => generator.make(0));
26+
27+
authRepository = MockAuthRepository();
28+
locator.registerSingleton<AuthRepository>(authRepository);
29+
30+
interactor = SideMenuInteractorImpl();
31+
interactor.delegate = delegate;
32+
});
33+
34+
describe("it's logout() is called", () {
35+
context("when auth repository's logout() return success", () {
36+
beforeEach(() {
37+
when(authRepository.logout())
38+
.thenAnswer((realInvocation) => Future.value(null));
39+
interactor.logout();
40+
});
41+
42+
it("triggers delegate's logoutDidSuccess emits", () {
43+
expect(delegate.logoutDidSuccess, emits(null));
44+
});
45+
});
46+
47+
context("when auth repository's logout() return failure", () {
48+
beforeEach(() {
49+
when(authRepository.logout())
50+
.thenAnswer((realInvocation) => Future.error(Exception()));
51+
interactor.logout();
52+
});
53+
54+
it("triggers delegate's logoutDidSuccess emits", () {
55+
expect(delegate.logoutDidSuccess, emits(null));
56+
});
57+
});
58+
});
59+
});
60+
}

0 commit comments

Comments
 (0)