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 2, 2021
1 parent e3c3de5 commit 4ab4ccb
Show file tree
Hide file tree
Showing 18 changed files with 587 additions and 23 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 @@ -51,7 +51,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);
sideMenuDidShow.voidListen(_sideMenuDidShow).addTo(disposeBag);
sideMenuDidDismiss.voidListen(_sideMenuDidDismiss).addTo(disposeBag);

Expand All @@ -32,7 +34,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 @@ -90,7 +92,7 @@ class HomePresenterImpl extends HomePresenter
interactor.fetchSurveys(force: true);
}

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

Expand Down
46 changes: 29 additions & 17 deletions 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 @@ -18,13 +18,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 Expand Up @@ -65,28 +67,15 @@ class _HomeViewImplState
final _isLoading = BehaviorSubject<bool>.seeded(false);
final _isUserInteractionEnabled = BehaviorSubject<bool>.seeded(true);
final _sliderMenuContainerKey = GlobalKey<SliderMenuContainerState>();
final _widgetsBinding = WidgetsBinding.instance!;

@override
void initState() {
super.initState();
delegate?.stateDidInit.add(null);

WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
final animationController =
_sliderMenuContainerKey.currentState?.animationController;
animationController?.addListener(() {
switch (animationController.status) {
case AnimationStatus.completed:
delegate?.sideMenuDidShow.add(null);
break;
case AnimationStatus.dismissed:
delegate?.sideMenuDidDismiss.add(null);
break;
default:
break;
}
});
});
_widgetsBinding
.scheduleFrameCallback(_listenSliderMenuContainerAnimationController);
}

@override
Expand Down Expand Up @@ -130,4 +119,27 @@ class _HomeViewImplState
void setUserInteractionEnable({required bool isEnabled}) {
_isUserInteractionEnabled.add(isEnabled);
}

void _listenSliderMenuContainerAnimationController(Duration timestamp) {
final animationController =
_sliderMenuContainerKey.currentState?.animationController;
if (animationController == null) {
_widgetsBinding
.scheduleFrameCallback(_listenSliderMenuContainerAnimationController);
return;
}

animationController.addListener(() {
switch (animationController.status) {
case AnimationStatus.completed:
delegate?.sideMenuDidShow.add(null);
break;
case AnimationStatus.dismissed:
delegate?.sideMenuDidDismiss.add(null);
break;
default:
break;
}
});
}
}
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 @@ -5,6 +5,9 @@ abstract class SideMenuViewDelegate {
}

abstract class SideMenuView extends View<SideMenuViewDelegate> {
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 @@ -136,5 +136,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 @@ -65,6 +65,12 @@ void main() {
.thenAnswer((realInvocation) => generator.make(3));
when(delegate.didSwipeDown)
.thenAnswer((realInvocation) => generator.make(4));
when(delegate.sideMenuDidShow)
.thenAnswer((realInvocation) => generator.make(5));
when(delegate.sideMenuDidDismiss)
.thenAnswer((realInvocation) => generator.make(6));
when(delegate.userAvatarButtonDidTap)
.thenAnswer((realInvocation) => generator.make(7));

module = FakeModule(
builder: () => const HomeViewImpl(),
Expand Down Expand Up @@ -256,5 +262,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);
});
});
});
}
4 changes: 2 additions & 2 deletions test/modules/home/home_view_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate {
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 Down
60 changes: 60 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,60 @@
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));

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", () {
beforeEach(() {
when(authRepository.logout())
.thenAnswer((realInvocation) => Future.error(Exception()));
interactor.logout();
});

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

0 comments on commit 4ab4ccb

Please sign in to comment.