Skip to content

Commit 12767fa

Browse files
committed
chore: write tests for side menu screen
# Conflicts: # coverage/lcov.info # Conflicts: # lib/modules/home/home_view.dart
1 parent 041922e commit 12767fa

18 files changed

+589
-23
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
@@ -51,7 +51,9 @@ class TopBar extends StatelessWidget {
5151
],
5252
),
5353
PlatformButton(
54-
onPressed: () => state.delegate?.userAvatarDidTap.add(null),
54+
key: HomeView.userAvatarButtonKey,
55+
onPressed: () =>
56+
state.delegate?.userAvatarButtonDidTap.add(null),
5557
child: Container(
5658
decoration: BoxDecoration(
5759
borderRadius: BorderRadius.circular(18),

lib/modules/home/home_presenter.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ class HomePresenterImpl extends HomePresenter
99
stateDidInit.voidListen(_stateDidInit).addTo(disposeBag);
1010
showDetailButtonDidTap.listen(_showDetailButtonDidTap).addTo(disposeBag);
1111
didSwipeDown.voidListen(_didSwipeDown).addTo(disposeBag);
12-
userAvatarDidTap.voidListen(_userAvatarDidTap).addTo(disposeBag);
12+
userAvatarButtonDidTap
13+
.voidListen(_userAvatarButtonDidTap)
14+
.addTo(disposeBag);
1315
sideMenuDidShow.voidListen(_sideMenuDidShow).addTo(disposeBag);
1416
sideMenuDidDismiss.voidListen(_sideMenuDidDismiss).addTo(disposeBag);
1517

@@ -32,7 +34,7 @@ class HomePresenterImpl extends HomePresenter
3234
final alertDialogDidClose = BehaviorSubject<void>();
3335

3436
@override
35-
final userAvatarDidTap = BehaviorSubject<void>();
37+
final userAvatarButtonDidTap = BehaviorSubject<void>();
3638

3739
@override
3840
final sideMenuDidDismiss = BehaviorSubject<void>();
@@ -90,7 +92,7 @@ class HomePresenterImpl extends HomePresenter
9092
interactor.fetchSurveys(force: true);
9193
}
9294

93-
void _userAvatarDidTap() {
95+
void _userAvatarButtonDidTap() {
9496
view.showSideMenu();
9597
}
9698

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

@@ -18,13 +18,15 @@ abstract class HomeView extends View<HomeViewDelegate>
1818
with AlertViewMixin, ProgressHUDViewMixin {
1919
static const currentDateTextKey = Key("current_date_text");
2020
static const userAvatarImageKey = Key("user_avatar_image");
21+
static const userAvatarButtonKey = Key("user_avatar_button");
2122
static const titleTextSlideItemKey = Key("title_text_slide_item");
2223
static const descriptionTextSlideItemKey = Key("description_text_slide_item");
2324
static const backgroundImageSlideItemKey = Key("background_image_slide_item");
2425
static const dotPageControlKey = Key("dot_page_control_key");
2526
static const skeletonKey = Key("skeleton_key");
2627
static const showDetailButtonKey = Key("show_detail_button");
2728
static const sliderMenuContainerKey = Key("slider_menu_container");
29+
static const mainIgnorePointer = Key("home_ignore_pointer");
2830

2931
static const dotPageControlHighlightColor = Colors.white;
3032
static const dotPageControlNormalColor = Color.fromRGBO(255, 255, 255, 0.2);
@@ -65,28 +67,15 @@ class _HomeViewImplState
6567
final _isLoading = BehaviorSubject<bool>.seeded(false);
6668
final _isUserInteractionEnabled = BehaviorSubject<bool>.seeded(true);
6769
final _sliderMenuContainerKey = GlobalKey<SliderMenuContainerState>();
70+
final _widgetsBinding = WidgetsBinding.instance!;
6871

6972
@override
7073
void initState() {
7174
super.initState();
7275
delegate?.stateDidInit.add(null);
7376

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

9281
@override
@@ -130,4 +119,27 @@ class _HomeViewImplState
130119
void setUserInteractionEnable({required bool isEnabled}) {
131120
_isUserInteractionEnabled.add(isEnabled);
132121
}
122+
123+
void _listenSliderMenuContainerAnimationController(Duration timestamp) {
124+
final animationController =
125+
_sliderMenuContainerKey.currentState?.animationController;
126+
if (animationController == null) {
127+
_widgetsBinding
128+
.scheduleFrameCallback(_listenSliderMenuContainerAnimationController);
129+
return;
130+
}
131+
132+
animationController.addListener(() {
133+
switch (animationController.status) {
134+
case AnimationStatus.completed:
135+
delegate?.sideMenuDidShow.add(null);
136+
break;
137+
case AnimationStatus.dismissed:
138+
delegate?.sideMenuDidDismiss.add(null);
139+
break;
140+
default:
141+
break;
142+
}
143+
});
144+
}
133145
}

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
@@ -5,6 +5,9 @@ abstract class SideMenuViewDelegate {
55
}
66

77
abstract class SideMenuView extends View<SideMenuViewDelegate> {
8+
static const userAvatarImageKey = Key("user_avatar_image");
9+
static const logoutButtonKey = Key("logout_button");
10+
811
void setUser(UserInfo user);
912
}
1013

test/modules/home/home_presenter_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,35 @@ void main() {
136136
verify(view.showUser(user)).called(1);
137137
});
138138
});
139+
140+
describe("it's sideMenuDidShow emits", () {
141+
beforeEach(() {
142+
presenter.sideMenuDidShow.add(null);
143+
});
144+
145+
it("trigger view to disable user interaction", () {
146+
verify(view.setUserInteractionEnable(isEnabled: false)).called(1);
147+
});
148+
});
149+
150+
describe("it's sideMenuDidDismiss emits", () {
151+
beforeEach(() {
152+
presenter.sideMenuDidDismiss.add(null);
153+
});
154+
155+
it("trigger view to enable user interaction", () {
156+
verify(view.setUserInteractionEnable(isEnabled: true)).called(1);
157+
});
158+
});
159+
160+
describe("it's userAvatarButtonDidTap emits", () {
161+
beforeEach(() {
162+
presenter.userAvatarButtonDidTap.add(null);
163+
});
164+
165+
it("trigger view to show side menu", () {
166+
verify(view.showSideMenu()).called(1);
167+
});
168+
});
139169
});
140170
}

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

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)