From a67091acc33f1b05c5cb538c54d98b5a70b3b9f2 Mon Sep 17 00:00:00 2001 From: MarkG Date: Wed, 12 May 2021 13:11:44 +0700 Subject: [PATCH] chore: write tests for home screen (#26) --- test/modules/home/home_interactor_test.dart | 98 +++++++ .../home/home_interactor_test.mocks.dart | 113 +++++++++ test/modules/home/home_module_test.dart | 27 ++ test/modules/home/home_presenter_test.dart | 140 ++++++++++ .../home/home_presenter_test.mocks.dart | 119 +++++++++ test/modules/home/home_router_test.dart | 42 +++ test/modules/home/home_view_test.dart | 240 ++++++++++++++++++ test/modules/home/home_view_test.mocks.dart | 45 ++++ 8 files changed, 824 insertions(+) create mode 100644 test/modules/home/home_interactor_test.dart create mode 100644 test/modules/home/home_interactor_test.mocks.dart create mode 100644 test/modules/home/home_module_test.dart create mode 100644 test/modules/home/home_presenter_test.dart create mode 100644 test/modules/home/home_presenter_test.mocks.dart create mode 100644 test/modules/home/home_router_test.dart create mode 100644 test/modules/home/home_view_test.dart create mode 100644 test/modules/home/home_view_test.mocks.dart diff --git a/test/modules/home/home_interactor_test.dart b/test/modules/home/home_interactor_test.dart new file mode 100644 index 0000000..516adaa --- /dev/null +++ b/test/modules/home/home_interactor_test.dart @@ -0,0 +1,98 @@ +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/models/survey_info.dart'; +import 'package:survey/models/user_info.dart'; +import 'package:survey/modules/home/home_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 'home_interactor_test.mocks.dart'; + +@GenerateMocks([HomeInteractorDelegate, SurveyRepository, AuthRepository]) +void main() { + describe("a Home interactor", () { + late HomeInteractor interactor; + late MockHomeInteractorDelegate delegate; + late MockSurveyRepository surveyRepository; + late MockAuthRepository authRepository; + late BehaviorSubjectGenerator generator; + + beforeEach(() { + generator = BehaviorSubjectGenerator(); + + delegate = MockHomeInteractorDelegate(); + when(delegate.isSurveysCachedDidGet) + .thenAnswer((realInvocation) => generator.make(0)); + when(delegate.authenticatedUserDidGet) + .thenAnswer((realInvocation) => generator.make(1)); + when(delegate.surveysDidFetch) + .thenAnswer((realInvocation) => generator.make(2)); + when(delegate.surveysDidFailToFetch) + .thenAnswer((realInvocation) => generator.make(3)); + + surveyRepository = MockSurveyRepository(); + locator.registerSingleton(surveyRepository); + + authRepository = MockAuthRepository(); + locator.registerSingleton(authRepository); + + interactor = HomeInteractorImpl(); + interactor.delegate = delegate; + }); + + describe("it's getIsSurveysCached is called", () { + beforeEach(() { + when(surveyRepository.isSurveysCached) + .thenAnswer((realInvocation) => Future.value(true)); + interactor.getIsSurveysCached(); + }); + + it("triggers delegate's isSurveysCachedDidGet emits", () { + expect(delegate.isSurveysCachedDidGet, emits(true)); + }); + }); + + describe("it's getAuthenticatedUser is called", () { + final user = UserInfo(); + beforeEach(() { + when(authRepository.user).thenReturn(user); + interactor.getAuthenticatedUser(); + }); + + it("triggers delegate's authenticatedUserDidGet emits", () { + expect(delegate.authenticatedUserDidGet, emits(user)); + }); + }); + + describe("it's fetchSurveys is called", () { + context("when surveyRepository's fetchSurveys return success", () { + final surveys = List.empty(); + beforeEach(() { + when(surveyRepository.fetchSurveys(force: anyNamed("force"))) + .thenAnswer((realInvocation) => Future.value(surveys)); + interactor.fetchSurveys(); + }); + + it("triggers delegate's surveysDidFetch emits", () { + expect(delegate.surveysDidFetch, emits(surveys)); + }); + }); + + context("when surveyRepository's fetchSurveys return failure", () { + final exception = Exception(); + beforeEach(() { + when(surveyRepository.fetchSurveys(force: anyNamed("force"))) + .thenAnswer((realInvocation) => Future.error(exception)); + interactor.fetchSurveys(); + }); + + it("triggers delegate's surveysDidFailToFetch emits", () { + expect(delegate.surveysDidFailToFetch, emits(exception)); + }); + }); + }); + }); +} diff --git a/test/modules/home/home_interactor_test.mocks.dart b/test/modules/home/home_interactor_test.mocks.dart new file mode 100644 index 0000000..73113f5 --- /dev/null +++ b/test/modules/home/home_interactor_test.mocks.dart @@ -0,0 +1,113 @@ +// Mocks generated by Mockito 5.0.7 from annotations +// in survey/test/modules/home/home_interactor_test.dart. +// Do not manually edit this file. + +import 'dart:async' 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/models/user_info.dart' as _i5; +import 'package:survey/modules/home/home_module.dart' as _i3; +import 'package:survey/repositories/auth_repository.dart' as _i8; +import 'package:survey/repositories/survey_repository.dart' as _i6; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +// ignore_for_file: prefer_const_constructors + +// ignore_for_file: avoid_redundant_argument_values + +class _FakeBehaviorSubject extends _i1.Fake + implements _i2.BehaviorSubject {} + +/// A class which mocks [HomeInteractorDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHomeInteractorDelegate extends _i1.Mock + implements _i3.HomeInteractorDelegate { + MockHomeInteractorDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.BehaviorSubject get isSurveysCachedDidGet => (super.noSuchMethod( + Invocation.getter(#isSurveysCachedDidGet), + returnValue: _FakeBehaviorSubject()) as _i2.BehaviorSubject); + @override + _i2.BehaviorSubject> get surveysDidFetch => + (super.noSuchMethod(Invocation.getter(#surveysDidFetch), + returnValue: _FakeBehaviorSubject>()) + as _i2.BehaviorSubject>); + @override + _i2.BehaviorSubject get surveysDidFailToFetch => + (super.noSuchMethod(Invocation.getter(#surveysDidFailToFetch), + returnValue: _FakeBehaviorSubject()) + as _i2.BehaviorSubject); + @override + _i2.BehaviorSubject<_i5.UserInfo> get authenticatedUserDidGet => + (super.noSuchMethod(Invocation.getter(#authenticatedUserDidGet), + returnValue: _FakeBehaviorSubject<_i5.UserInfo>()) + as _i2.BehaviorSubject<_i5.UserInfo>); +} + +/// A class which mocks [SurveyRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSurveyRepository extends _i1.Mock implements _i6.SurveyRepository { + MockSurveyRepository() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.Future get isSurveysCached => + (super.noSuchMethod(Invocation.getter(#isSurveysCached), + returnValue: Future.value(false)) as _i7.Future); + @override + _i7.Future> fetchSurveys({bool? force}) => + (super.noSuchMethod(Invocation.method(#fetchSurveys, [], {#force: force}), + returnValue: + Future>.value(<_i4.SurveyInfo>[])) + as _i7.Future>); +} + +/// A class which mocks [AuthRepository]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAuthRepository extends _i1.Mock implements _i8.AuthRepository { + MockAuthRepository() { + _i1.throwOnMissingStub(this); + } + + @override + bool get isAuthenticated => (super + .noSuchMethod(Invocation.getter(#isAuthenticated), returnValue: false) + as bool); + @override + _i7.Future login({String? email, String? password}) => + (super.noSuchMethod( + Invocation.method(#login, [], {#email: email, #password: password}), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future logout() => + (super.noSuchMethod(Invocation.method(#logout, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future attempt() => + (super.noSuchMethod(Invocation.method(#attempt, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future fetchUser() => + (super.noSuchMethod(Invocation.method(#fetchUser, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future attemptAndFetchUser() => + (super.noSuchMethod(Invocation.method(#attemptAndFetchUser, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); +} diff --git a/test/modules/home/home_module_test.dart b/test/modules/home/home_module_test.dart new file mode 100644 index 0000000..e449e6d --- /dev/null +++ b/test/modules/home/home_module_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quick_test/quick_test.dart'; +import 'package:survey/modules/home/home_module.dart'; + +import '../../mocks/build_context.dart'; + +void main() { + describe("a Home module", () { + late HomeModule module; + beforeEach(() { + module = HomeModule(); + }); + + describe("it's build is called", () { + late Widget widget; + + beforeEach(() { + widget = module.build(MockBuildContext()); + }); + + it("returns LandingViewImpl", () { + expect(widget, isA()); + }); + }); + }); +} diff --git a/test/modules/home/home_presenter_test.dart b/test/modules/home/home_presenter_test.dart new file mode 100644 index 0000000..6b11ee3 --- /dev/null +++ b/test/modules/home/home_presenter_test.dart @@ -0,0 +1,140 @@ +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:quick_test/quick_test.dart'; +import 'package:survey/models/survey_info.dart'; +import 'package:survey/models/user_info.dart'; +import 'package:survey/modules/home/home_module.dart'; + +import '../../mocks/build_context.dart'; +import 'home_presenter_test.mocks.dart'; + +@GenerateMocks([HomeView, HomeRouter, HomeInteractor]) +void main() { + describe("a Home presenter", () { + late HomePresenterImpl presenter; + late MockHomeView view; + late MockHomeRouter router; + late MockHomeInteractor interactor; + late MockBuildContext buildContext; + + beforeEach(() { + buildContext = MockBuildContext(); + + view = MockHomeView(); + when(view.context).thenReturn(buildContext); + + router = MockHomeRouter(); + + interactor = MockHomeInteractor(); + + presenter = HomePresenterImpl(); + presenter.configure(view: view, interactor: interactor, router: router); + }); + + describe("it's stateDidInit emits", () { + beforeEach(() { + presenter.stateDidInit.add(null); + }); + + it("triggers interactor to get isSurveysCached and authenticatedUser", + () { + verify(interactor.getIsSurveysCached()).called(1); + verify(interactor.getAuthenticatedUser()).called(1); + }); + }); + + describe("it's showDetailButtonDidTap emits", () { + final survey = SurveyInfo(); + + beforeEach(() { + presenter.showDetailButtonDidTap.add(survey); + }); + + it("triggers router to push to Survey Detail screen", () { + verify(router.pushToSurveyDetail(context: buildContext, survey: survey)) + .called(1); + }); + }); + + describe("it's surveysDidFetch emits", () { + final surveys = List.empty(); + + beforeEach(() { + presenter.surveysDidFetch.add(surveys); + }); + + it("trigger view to stop skeleton animation, dismiss progress hud and show surveys", + () { + verify(view.stopSkeletonAnimation()).called(1); + verify(view.showSurveys(surveys)).called(1); + verify(view.dismissProgressHUD()).called(1); + }); + }); + + describe("it's surveysDidFailToFetch emits", () { + final exception = Exception(); + + beforeEach(() { + presenter.surveysDidFailToFetch.add(exception); + }); + + it("trigger view to stop skeleton animation, dismiss progress hud and alert error", + () { + verify(view.stopSkeletonAnimation()).called(1); + verify(view.alert(exception)).called(1); + verify(view.dismissProgressHUD()).called(1); + }); + }); + + describe("it's isSurveysCachedDidGet emits", () { + context("when isSurveysCached is true", () { + beforeEach(() { + presenter.isSurveysCachedDidGet.add(true); + }); + + it("trigger interactor to fetch surveys", () { + verify(interactor.fetchSurveys()).called(1); + }); + }); + + context("when isSurveysCached is false", () { + beforeEach(() { + presenter.isSurveysCachedDidGet.add(false); + }); + + it("trigger interactor to fetch surveys", () { + verify(interactor.fetchSurveys()).called(1); + }); + + it("trigger view to begin skeleton animation", () { + verify(view.beginSkeletonAnimation()).called(1); + }); + }); + }); + + describe("it's didSwipeDown emits", () { + beforeEach(() { + presenter.didSwipeDown.add(null); + }); + + it("trigger view to show progress hud", () { + verify(view.showProgressHUD()).called(1); + }); + + it("trigger interactor to force fetch surveys", () { + verify(interactor.fetchSurveys(force: true)).called(1); + }); + }); + + describe("it's authenticatedUserDidGet emits", () { + final user = UserInfo(); + beforeEach(() { + presenter.authenticatedUserDidGet.add(user); + }); + + it("trigger view to show user", () { + verify(view.showUser(user)).called(1); + }); + }); + }); +} diff --git a/test/modules/home/home_presenter_test.mocks.dart b/test/modules/home/home_presenter_test.mocks.dart new file mode 100644 index 0000000..da6d37f --- /dev/null +++ b/test/modules/home/home_presenter_test.mocks.dart @@ -0,0 +1,119 @@ +// Mocks generated by Mockito 5.0.7 from annotations +// in survey/test/modules/home/home_presenter_test.dart. +// Do not manually edit this file. + +import 'package:flutter/src/widgets/framework.dart' as _i3; +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 _i6; +import 'package:survey/models/user_info.dart' as _i5; +import 'package:survey/modules/home/home_module.dart' as _i4; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +// ignore_for_file: prefer_const_constructors + +// ignore_for_file: avoid_redundant_argument_values + +class _FakeBehaviorSubject extends _i1.Fake + implements _i2.BehaviorSubject {} + +class _FakeBuildContext extends _i1.Fake implements _i3.BuildContext {} + +/// A class which mocks [HomeView]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHomeView extends _i1.Mock implements _i4.HomeView { + MockHomeView() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.BehaviorSubject get isProgressHUDShown => (super.noSuchMethod( + Invocation.getter(#isProgressHUDShown), + returnValue: _FakeBehaviorSubject()) as _i2.BehaviorSubject); + @override + set delegate(_i4.HomeViewDelegate? _delegate) => + super.noSuchMethod(Invocation.setter(#delegate, _delegate), + returnValueForMissingStub: null); + @override + _i3.BuildContext get context => + (super.noSuchMethod(Invocation.getter(#context), + returnValue: _FakeBuildContext()) as _i3.BuildContext); + @override + void setDateTime(DateTime? dateTime) => + super.noSuchMethod(Invocation.method(#setDateTime, [dateTime]), + returnValueForMissingStub: null); + @override + void showUser(_i5.UserInfo? user) => + super.noSuchMethod(Invocation.method(#showUser, [user]), + returnValueForMissingStub: null); + @override + void showSurveys(List<_i6.SurveyInfo>? surveys) => + super.noSuchMethod(Invocation.method(#showSurveys, [surveys]), + returnValueForMissingStub: null); + @override + void beginSkeletonAnimation() => + super.noSuchMethod(Invocation.method(#beginSkeletonAnimation, []), + returnValueForMissingStub: null); + @override + void stopSkeletonAnimation() => + super.noSuchMethod(Invocation.method(#stopSkeletonAnimation, []), + returnValueForMissingStub: null); + @override + void alert(Object? error) => + super.noSuchMethod(Invocation.method(#alert, [error]), + returnValueForMissingStub: null); + @override + void showProgressHUD() => + super.noSuchMethod(Invocation.method(#showProgressHUD, []), + returnValueForMissingStub: null); + @override + void dismissProgressHUD() => + super.noSuchMethod(Invocation.method(#dismissProgressHUD, []), + returnValueForMissingStub: null); +} + +/// A class which mocks [HomeRouter]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHomeRouter extends _i1.Mock implements _i4.HomeRouter { + MockHomeRouter() { + _i1.throwOnMissingStub(this); + } + + @override + void pushToSurveyDetail( + {_i3.BuildContext? context, _i6.SurveyInfo? survey}) => + super.noSuchMethod( + Invocation.method( + #pushToSurveyDetail, [], {#context: context, #survey: survey}), + returnValueForMissingStub: null); +} + +/// A class which mocks [HomeInteractor]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHomeInteractor extends _i1.Mock implements _i4.HomeInteractor { + MockHomeInteractor() { + _i1.throwOnMissingStub(this); + } + + @override + set delegate(_i4.HomeInteractorDelegate? _delegate) => + super.noSuchMethod(Invocation.setter(#delegate, _delegate), + returnValueForMissingStub: null); + @override + void getIsSurveysCached() => + super.noSuchMethod(Invocation.method(#getIsSurveysCached, []), + returnValueForMissingStub: null); + @override + void fetchSurveys({bool? force = false}) => + super.noSuchMethod(Invocation.method(#fetchSurveys, [], {#force: force}), + returnValueForMissingStub: null); + @override + void getAuthenticatedUser() => + super.noSuchMethod(Invocation.method(#getAuthenticatedUser, []), + returnValueForMissingStub: null); +} diff --git a/test/modules/home/home_router_test.dart b/test/modules/home/home_router_test.dart new file mode 100644 index 0000000..153d5e7 --- /dev/null +++ b/test/modules/home/home_router_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; +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/models/survey_info.dart'; +import 'package:survey/modules/home/home_module.dart'; +import 'package:survey/modules/survey_detail/survey_detail_module.dart'; + +import '../../mocks/build_context.dart'; +import '../../mocks/navigator_state.dart'; + +void main() { + describe("a Home router", () { + late HomeRouter router; + late MockBuildContext buildContext; + late MockNavigatorState navigatorState; + + beforeEach(() { + buildContext = MockBuildContext(); + navigatorState = MockNavigatorState(); + + router = HomeRouterImpl(); + }); + + describe("it's pushToSurveyDetail() is called", () { + beforeEach(() { + when(buildContext.findAncestorStateOfType()) + .thenReturn(navigatorState); + when(navigatorState.pushNamed(any)).thenAnswer((_) => Future.value()); + router.pushToSurveyDetail(context: buildContext, survey: SurveyInfo()); + }); + + it("triggers navigator to push to Survey Detail screen", () { + final routePath = verify(navigatorState.pushNamed(captureAny)) + .captured + .single as String; + expect(routePath, SurveyDetailModule.routePath); + }); + }); + }); +} diff --git a/test/modules/home/home_view_test.dart b/test/modules/home/home_view_test.dart new file mode 100644 index 0000000..58e8c5a --- /dev/null +++ b/test/modules/home/home_view_test.dart @@ -0,0 +1,240 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:quick_test/quick_widget_test.dart'; +import 'package:skeletor/skeletor.dart'; +import 'package:survey/models/survey_info.dart'; +import 'package:survey/models/user_info.dart'; +import 'package:survey/modules/home/home_module.dart'; +import 'package:survey/core/viper/module.dart'; +import 'package:mockito/mockito.dart'; +import '../../fakers/fake_module.dart'; +import '../../helpers/behavior_subject_generator.dart'; +import '../../helpers/extensions/widget_tester.dart'; +import 'home_view_test.mocks.dart'; + +@GenerateMocks([HomeViewDelegate]) +void main() { + describe("a Home view", () { + late FakeModule module; + late MockHomeViewDelegate delegate; + late BehaviorSubjectGenerator generator; + + final surveys = List.empty(growable: true); + const numberOfSurvey = 5; + + for (int i = 1; i <= numberOfSurvey; i++) { + final survey = SurveyInfo(); + survey.title = "title $i"; + survey.description = "description $i"; + survey.coverImageUrl = "https://example.com/$i"; + surveys.add(survey); + } + + beforeEach((tester) async { + HttpOverrides.global = null; + + generator = BehaviorSubjectGenerator(); + delegate = MockHomeViewDelegate(); + when(delegate.stateDidInit) + .thenAnswer((realInvocation) => generator.make(1)); + when(delegate.alertDialogDidClose) + .thenAnswer((realInvocation) => generator.make(2)); + when(delegate.showDetailButtonDidTap) + .thenAnswer((realInvocation) => generator.make(3)); + when(delegate.didSwipeDown) + .thenAnswer((realInvocation) => generator.make(4)); + + module = FakeModule( + builder: () => const HomeViewImpl(), + delegate: delegate, + ); + ViewState.overriddenModule = module; + await tester.pumpModule(module); + }); + + context("when wipes from top to bottom on screen", () { + beforeEach((tester) async { + await tester.pumpAndSettle(); + + final center = tester.getCenter(find.byKey(HomeView.bodyKey)); + final location = Offset(center.dx, 0); + await tester.flingFrom(location, Offset(0, center.dy), center.dy); + }); + + it("triggers delegate's didSwipeDown emits", (tester) async { + expect(delegate.didSwipeDown, emits(null)); + }); + }); + + describe("it's setDateTime() is called", () { + final dateTime = DateTime(2020, 5, 2); + beforeEach((tester) async { + module.view.setDateTime(dateTime); + await tester.pumpAndSettle(); + }); + + it("shows correct current date text", (tester) async { + const text = "SATURDAY, MAY 2"; + expect(find.text(text), findsOneWidget); + }); + }); + + describe("it's showUser() is called", () { + final user = UserInfo(); + user.avatarUrl = "https://example.com"; + + beforeEach((tester) async { + module.view.showUser(user); + await tester.pumpAndSettle(); + }); + + it("makes user's avatar image displays input user's avatar", + (tester) async { + final widget = + tester.widget(find.byKey(HomeView.userAvatarImageKey)); + final networkImage = widget.image as NetworkImage; + + expect(networkImage.url, user.avatarUrl); + }); + }); + + describe("it's showSurveys() is called", () { + beforeEach((tester) async { + module.view.showSurveys(surveys); + await tester.pumpAndSettle(); + }); + + it("triggers carousel slider displays the first survey information", + (tester) async { + final survey = surveys.first; + expect(find.text(survey.title!), findsOneWidget); + expect(find.text(survey.description!), findsOneWidget); + + final widget = tester + .widget(find.byKey(HomeView.backgroundImageSlideItemKey)); + final networkImage = widget.image as NetworkImage; + expect(networkImage.url, survey.coverImageUrl); + }); + + it("triggers page control displays correct number of dots and highlight the first dot", + (tester) async { + expect(find.byKey(HomeView.dotPageControlKey), + findsNWidgets(surveys.length)); + + final dots = tester + .widgetList(find.byKey(HomeView.dotPageControlKey)) + .toList(); + for (int i = 0; i < dots.length; i++) { + final dot = dots[i]; + final boxDecoration = dot.decoration! as BoxDecoration; + final color = i == 0 + ? HomeView.dotPageControlHighlightColor + : HomeView.dotPageControlNormalColor; + expect(boxDecoration.color, color); + } + }); + + context("when swipes right to left on screen", () { + 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 carousel slider the second survey information", + (tester) async { + final survey = surveys[1]; + expect(find.text(survey.title!), findsOneWidget); + expect(find.text(survey.description!), findsOneWidget); + + final widget = tester + .widget(find.byKey(HomeView.backgroundImageSlideItemKey)); + final networkImage = widget.image as NetworkImage; + expect(networkImage.url, survey.coverImageUrl); + }); + + it("triggers page control highlights the second dot", (tester) async { + final dot = tester + .widgetList(find.byKey(HomeView.dotPageControlKey)) + .toList()[1]; + final boxDecoration = dot.decoration! as BoxDecoration; + expect(boxDecoration.color, HomeView.dotPageControlHighlightColor); + }); + }); + + context("when swipes left to right on screen", () { + 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 carousel slider displays the last survey information", + (tester) async { + final survey = surveys.last; + expect(find.text(survey.title!), findsOneWidget); + expect(find.text(survey.description!), findsOneWidget); + + final widget = tester + .widget(find.byKey(HomeView.backgroundImageSlideItemKey)); + final networkImage = widget.image as NetworkImage; + expect(networkImage.url, survey.coverImageUrl); + }); + + it("triggers page control highlights the last dot", (tester) async { + final dot = tester + .widgetList(find.byKey(HomeView.dotPageControlKey)) + .toList() + .last; + final boxDecoration = dot.decoration! as BoxDecoration; + expect(boxDecoration.color, HomeView.dotPageControlHighlightColor); + }); + }); + }); + + describe("it's beginSkeletonAnimation() is called", () { + beforeEach((tester) async { + module.view.beginSkeletonAnimation(); + await tester.pump(); + await tester.pump(); + }); + + it("triggers skeleton begin animation", (tester) async { + final widget = + tester.widget(find.byKey(HomeView.skeletonKey)); + expect(widget.isShown, isTrue); + }); + }); + + describe("it's stopSkeletonAnimation() is called", () { + beforeEach((tester) async { + module.view.stopSkeletonAnimation(); + await tester.pump(); + await tester.pump(); + }); + + it("triggers skeleton stop animation", (tester) async { + final widget = + tester.widget(find.byKey(HomeView.skeletonKey)); + expect(widget.isShown, isFalse); + }); + }); + + describe("it's show detail button in one of slide items is tapped", () { + beforeEach((tester) async { + module.view.showSurveys(surveys); + await tester.pumpAndSettle(); + await tester.tap(find.byKey(HomeView.showDetailButtonKey)); + }); + + it("triggers delegate's showDetailButtonDidTap emits", (tester) async { + expect(delegate.showDetailButtonDidTap, emits(surveys.first)); + }); + }); + }); +} diff --git a/test/modules/home/home_view_test.mocks.dart b/test/modules/home/home_view_test.mocks.dart new file mode 100644 index 0000000..498ddb0 --- /dev/null +++ b/test/modules/home/home_view_test.mocks.dart @@ -0,0 +1,45 @@ +// Mocks generated by Mockito 5.0.7 from annotations +// in survey/test/modules/home/home_view_test.dart. +// Do not manually edit this file. + +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; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +// ignore_for_file: prefer_const_constructors + +// ignore_for_file: avoid_redundant_argument_values + +class _FakeBehaviorSubject extends _i1.Fake + implements _i2.BehaviorSubject {} + +/// A class which mocks [HomeViewDelegate]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockHomeViewDelegate extends _i1.Mock implements _i3.HomeViewDelegate { + MockHomeViewDelegate() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.BehaviorSubject get stateDidInit => (super.noSuchMethod( + Invocation.getter(#stateDidInit), + returnValue: _FakeBehaviorSubject()) as _i2.BehaviorSubject); + @override + _i2.BehaviorSubject<_i4.SurveyInfo> get showDetailButtonDidTap => + (super.noSuchMethod(Invocation.getter(#showDetailButtonDidTap), + returnValue: _FakeBehaviorSubject<_i4.SurveyInfo>()) + as _i2.BehaviorSubject<_i4.SurveyInfo>); + @override + _i2.BehaviorSubject get didSwipeDown => (super.noSuchMethod( + Invocation.getter(#didSwipeDown), + returnValue: _FakeBehaviorSubject()) as _i2.BehaviorSubject); + @override + _i2.BehaviorSubject get alertDialogDidClose => (super.noSuchMethod( + Invocation.getter(#alertDialogDidClose), + returnValue: _FakeBehaviorSubject()) as _i2.BehaviorSubject); +}