Skip to content

Commit

Permalink
chore: write tests for side menu screen (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
markgravity committed Jun 24, 2021
1 parent 688dabd commit 9347d0c
Show file tree
Hide file tree
Showing 18 changed files with 639 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/modules/home/components/body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Body extends StatelessWidget {
sliderMain: StreamsSelector0<bool>.value(
stream: state._isUserInteractionEnabled,
builder: (_, isUserInteractionEnabled, child) => IgnorePointer(
key: HomeView.mainIgnorePointer,
ignoring: !isUserInteractionEnabled,
child: child,
),
Expand Down
4 changes: 3 additions & 1 deletion lib/modules/home/components/top_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ class TopBar extends StatelessWidget {
],
),
PlatformButton(
onPressed: () => state.delegate?.userAvatarDidTap.add(null),
key: HomeView.userAvatarButtonKey,
onPressed: () =>
state.delegate?.userAvatarButtonDidTap.add(null),
materialFlat: (_, __) => MaterialFlatButtonData(
color: Colors.transparent,
),
Expand Down
8 changes: 5 additions & 3 deletions lib/modules/home/home_presenter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ class HomePresenterImpl extends HomePresenter
stateDidInit.voidListen(_stateDidInit).addTo(disposeBag);
showDetailButtonDidTap.listen(_showDetailButtonDidTap).addTo(disposeBag);
didSwipeDown.voidListen(_didSwipeDown).addTo(disposeBag);
userAvatarDidTap.voidListen(_userAvatarDidTap).addTo(disposeBag);
userAvatarButtonDidTap
.voidListen(_userAvatarButtonDidTap)
.addTo(disposeBag);
currentPageDidChange.listen(_currentPageDidChange).addTo(disposeBag);
sideMenuDidShow.voidListen(_sideMenuDidShow).addTo(disposeBag);
sideMenuDidDismiss.voidListen(_sideMenuDidDismiss).addTo(disposeBag);
Expand Down Expand Up @@ -44,7 +46,7 @@ class HomePresenterImpl extends HomePresenter
final alertDialogDidClose = BehaviorSubject<void>();

@override
final userAvatarDidTap = BehaviorSubject<void>();
final userAvatarButtonDidTap = BehaviorSubject<void>();

@override
final sideMenuDidDismiss = BehaviorSubject<void>();
Expand Down Expand Up @@ -123,7 +125,7 @@ class HomePresenterImpl extends HomePresenter
interactor.fetchSurveysFromRemote();
}

void _userAvatarDidTap() {
void _userAvatarButtonDidTap() {
view.showSideMenu();
}

Expand Down
4 changes: 3 additions & 1 deletion lib/modules/home/home_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ abstract class HomeViewDelegate implements AlertViewMixinDelegate {

BehaviorSubject<void> get didSwipeDown;

BehaviorSubject<void> get userAvatarDidTap;
BehaviorSubject<void> get userAvatarButtonDidTap;

BehaviorSubject<void> get sideMenuDidShow;

Expand All @@ -20,13 +20,15 @@ abstract class HomeView extends View<HomeViewDelegate>
with AlertViewMixin, ProgressHUDViewMixin {
static const currentDateTextKey = Key("current_date_text");
static const userAvatarImageKey = Key("user_avatar_image");
static const userAvatarButtonKey = Key("user_avatar_button");
static const titleTextSlideItemKey = Key("title_text_slide_item");
static const descriptionTextSlideItemKey = Key("description_text_slide_item");
static const backgroundImageSlideItemKey = Key("background_image_slide_item");
static const dotPageControlKey = Key("dot_page_control_key");
static const skeletonKey = Key("skeleton_key");
static const showDetailButtonKey = Key("show_detail_button");
static const sliderMenuContainerKey = Key("slider_menu_container");
static const mainIgnorePointer = Key("home_ignore_pointer");

static const dotPageControlHighlightColor = Colors.white;
static const dotPageControlNormalColor = Color.fromRGBO(255, 255, 255, 0.2);
Expand Down
1 change: 1 addition & 0 deletions lib/modules/side_menu/components/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Actions extends StatelessWidget {
final state = context.findAncestorStateOfType<_SideMenuViewImplState>()!;

return PlatformButton(
key: SideMenuView.logoutButtonKey,
onPressed: () => state.delegate?.logoutButtonDidTap.add(null),
cupertino: (_, __) => CupertinoButtonData(
padding: EdgeInsets.zero,
Expand Down
1 change: 1 addition & 0 deletions lib/modules/side_menu/components/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class User extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(18),
child: Image(
key: SideMenuView.userAvatarImageKey,
image: NetworkImage(user.avatarUrl!),
),
),
Expand Down
3 changes: 3 additions & 0 deletions lib/modules/side_menu/side_menu_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ abstract class SideMenuViewDelegate with AlertViewMixinDelegate {

abstract class SideMenuView extends View<SideMenuViewDelegate>
with AlertViewMixin {
static const userAvatarImageKey = Key("user_avatar_image");
static const logoutButtonKey = Key("logout_button");

void setUser(UserInfo user);
}

Expand Down
30 changes: 30 additions & 0 deletions test/modules/home/home_presenter_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,35 @@ void main() {
verify(view.showUser(user)).called(1);
});
});

describe("it's sideMenuDidShow emits", () {
beforeEach(() {
presenter.sideMenuDidShow.add(null);
});

it("trigger view to disable user interaction", () {
verify(view.setUserInteractionEnable(isEnabled: false)).called(1);
});
});

describe("it's sideMenuDidDismiss emits", () {
beforeEach(() {
presenter.sideMenuDidDismiss.add(null);
});

it("trigger view to enable user interaction", () {
verify(view.setUserInteractionEnable(isEnabled: true)).called(1);
});
});

describe("it's userAvatarButtonDidTap emits", () {
beforeEach(() {
presenter.userAvatarButtonDidTap.add(null);
});

it("trigger view to show side menu", () {
verify(view.showSideMenu()).called(1);
});
});
});
}
55 changes: 55 additions & 0 deletions test/modules/home/home_view_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ void main() {
.thenAnswer((realInvocation) => generator.make(4));
when(delegate.currentPageDidChange)
.thenAnswer((realInvocation) => generator.make(5));
when(delegate.sideMenuDidShow)
.thenAnswer((realInvocation) => generator.make(6));
when(delegate.sideMenuDidDismiss)
.thenAnswer((realInvocation) => generator.make(7));
when(delegate.userAvatarButtonDidTap)
.thenAnswer((realInvocation) => generator.make(8));

locator.registerSingleton<SideMenuPresenter>(MockSideMenuPresenter());
locator.registerSingleton<SideMenuInteractor>(MockSideMenuInteractor());
Expand Down Expand Up @@ -238,5 +244,54 @@ void main() {
expect(delegate.showDetailButtonDidTap, emits(surveys.first));
});
});

describe("it's showSideMenu() is called", () {
beforeEach((tester) async {
await tester.pumpAndSettle();
module.view.showSideMenu();
await tester.pumpAndSettle();
});

it("triggers delegate's sideMenuDidShow emits", (tester) async {
expect(delegate.sideMenuDidShow, emits(null));
});

describe("then wipes from left to right", () {
beforeEach((tester) async {
final location = tester.getCenter(find.byKey(HomeView.bodyKey));
await tester.flingFrom(location, Offset(location.dx, 0), location.dx);
await tester.pumpAndSettle();
});

it("triggers delegate's sideMenuDidDismiss emits", (tester) async {
expect(delegate.sideMenuDidDismiss, emits(null));
});
});
});

describe("it's user avatar button is tapped", () {
beforeEach((tester) async {
await tester.pumpAndSettle();
await tester.tap(find.byKey(HomeView.userAvatarButtonKey));
});

it("triggers delegate's userAvatarButtonDidTap emits", (tester) async {
expect(delegate.userAvatarButtonDidTap, emits(null));
});
});

describe("it's setUserInteractionEnable() is called", () {
beforeEach((tester) async {
module.view.setUserInteractionEnable(isEnabled: false);
await tester.pumpAndSettle();
});

it("triggers main ignore pointer updates with correct ignoring value",
(tester) async {
final widget = tester
.widget<IgnorePointer>(find.byKey(HomeView.mainIgnorePointer));
expect(widget.ignoring, true);
});
});
});
}
40 changes: 28 additions & 12 deletions test/modules/home/home_view_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// in survey/test/modules/home/home_view_test.dart.
// Do not manually edit this file.

import 'package:flutter/src/widgets/framework.dart' as _i7;
import 'package:mockito/mockito.dart' as _i1;
import 'package:rxdart/src/subjects/behavior_subject.dart' as _i2;
import 'package:survey/models/survey_info.dart' as _i4;
import 'package:survey/modules/home/home_module.dart' as _i3;
import 'package:survey/modules/side_menu/side_menu_module.dart' as _i5;
import 'package:survey/models/survey_info.dart' as _i5;
import 'package:survey/models/user_info.dart' as _i3;
import 'package:survey/modules/home/home_module.dart' as _i4;
import 'package:survey/modules/side_menu/side_menu_module.dart' as _i6;

// ignore_for_file: comment_references
// ignore_for_file: unnecessary_parenthesis
Expand All @@ -18,10 +20,12 @@ import 'package:survey/modules/side_menu/side_menu_module.dart' as _i5;
class _FakeBehaviorSubject<T> extends _i1.Fake
implements _i2.BehaviorSubject<T> {}

class _FakeUserInfo extends _i1.Fake implements _i3.UserInfo {}

/// A class which mocks [HomeViewDelegate].
///
/// See the documentation for Mockito's code generation for more information.
class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate {
class MockHomeViewDelegate extends _i1.Mock implements _i4.HomeViewDelegate {
MockHomeViewDelegate() {
_i1.throwOnMissingStub(this);
}
Expand All @@ -31,17 +35,17 @@ class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate {
Invocation.getter(#stateDidInit),
returnValue: _FakeBehaviorSubject<void>()) as _i2.BehaviorSubject<void>);
@override
_i2.BehaviorSubject<_i4.SurveyInfo> get showDetailButtonDidTap =>
_i2.BehaviorSubject<_i5.SurveyInfo> get showDetailButtonDidTap =>
(super.noSuchMethod(Invocation.getter(#showDetailButtonDidTap),
returnValue: _FakeBehaviorSubject<_i4.SurveyInfo>())
as _i2.BehaviorSubject<_i4.SurveyInfo>);
returnValue: _FakeBehaviorSubject<_i5.SurveyInfo>())
as _i2.BehaviorSubject<_i5.SurveyInfo>);
@override
_i2.BehaviorSubject<void> get didSwipeDown => (super.noSuchMethod(
Invocation.getter(#didSwipeDown),
returnValue: _FakeBehaviorSubject<void>()) as _i2.BehaviorSubject<void>);
@override
_i2.BehaviorSubject<void> get userAvatarDidTap => (super.noSuchMethod(
Invocation.getter(#userAvatarDidTap),
_i2.BehaviorSubject<void> get userAvatarButtonDidTap => (super.noSuchMethod(
Invocation.getter(#userAvatarButtonDidTap),
returnValue: _FakeBehaviorSubject<void>()) as _i2.BehaviorSubject<void>);
@override
_i2.BehaviorSubject<void> get sideMenuDidShow => (super.noSuchMethod(
Expand All @@ -65,22 +69,34 @@ class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate {
///
/// See the documentation for Mockito's code generation for more information.
class MockSideMenuInteractor extends _i1.Mock
implements _i5.SideMenuInteractor {
implements _i6.SideMenuInteractor {
MockSideMenuInteractor() {
_i1.throwOnMissingStub(this);
}

@override
set delegate(_i5.SideMenuInteractorDelegate? _delegate) =>
_i3.UserInfo get authenticatedUser =>
(super.noSuchMethod(Invocation.getter(#authenticatedUser),
returnValue: _FakeUserInfo()) as _i3.UserInfo);
@override
set delegate(_i6.SideMenuInteractorDelegate? _delegate) =>
super.noSuchMethod(Invocation.setter(#delegate, _delegate),
returnValueForMissingStub: null);
@override
void logout() => super.noSuchMethod(Invocation.method(#logout, []),
returnValueForMissingStub: null);
}

/// A class which mocks [SideMenuRouter].
///
/// See the documentation for Mockito's code generation for more information.
class MockSideMenuRouter extends _i1.Mock implements _i5.SideMenuRouter {
class MockSideMenuRouter extends _i1.Mock implements _i6.SideMenuRouter {
MockSideMenuRouter() {
_i1.throwOnMissingStub(this);
}

@override
void replaceToLoginScreen(_i7.BuildContext? context) =>
super.noSuchMethod(Invocation.method(#replaceToLoginScreen, [context]),
returnValueForMissingStub: null);
}
63 changes: 63 additions & 0 deletions test/modules/side_menu/side_menu_interactor_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:quick_test/quick_test.dart';
import 'package:survey/modules/side_menu/side_menu_module.dart';
import 'package:survey/repositories/auth_repository.dart';
import 'package:survey/repositories/survey_repository.dart';
import 'package:survey/services/locator/locator_service.dart';
import '../../helpers/behavior_subject_generator.dart';
import 'side_menu_interactor_test.mocks.dart';

@GenerateMocks([SideMenuInteractorDelegate, SurveyRepository, AuthRepository])
void main() {
describe("a SideMenu interactor", () {
late SideMenuInteractor interactor;
late MockSideMenuInteractorDelegate delegate;
late MockAuthRepository authRepository;
late BehaviorSubjectGenerator generator;

beforeEach(() {
generator = BehaviorSubjectGenerator();

delegate = MockSideMenuInteractorDelegate();
when(delegate.logoutDidSuccess)
.thenAnswer((realInvocation) => generator.make(0));
when(delegate.logoutDidFail)
.thenAnswer((realInvocation) => generator.make(1));

authRepository = MockAuthRepository();
locator.registerSingleton<AuthRepository>(authRepository);

interactor = SideMenuInteractorImpl();
interactor.delegate = delegate;
});

describe("it's logout() is called", () {
context("when auth repository's logout() return success", () {
beforeEach(() {
when(authRepository.logout())
.thenAnswer((realInvocation) => Future.value(null));
interactor.logout();
});

it("triggers delegate's logoutDidSuccess emits", () {
expect(delegate.logoutDidSuccess, emits(null));
});
});

context("when auth repository's logout() return failure", () {
final exception = Exception();
beforeEach(() {
when(authRepository.logout())
.thenAnswer((realInvocation) => Future.error(exception));
interactor.logout();
});

it("triggers delegate's logoutDidSuccess emits", () {
expect(delegate.logoutDidFail, emits(exception));
});
});
});
});
}
Loading

0 comments on commit 9347d0c

Please sign in to comment.