diff --git a/test/home/widgets/transition_screen_mixin_test.dart b/test/home/widgets/transition_screen_mixin_test.dart new file mode 100644 index 0000000..b756743 --- /dev/null +++ b/test/home/widgets/transition_screen_mixin_test.dart @@ -0,0 +1,186 @@ +import 'dart:async'; + +import 'package:bloc_test/bloc_test.dart'; +import 'package:dash_ai_search/home/home.dart'; +import 'package:dash_ai_search/home/widgets/transition_screen_mixin.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../helpers/helpers.dart'; + +class _MockHomeBloc extends MockBloc + implements HomeBloc {} + +class _MockAnimationController extends Mock implements AnimationController {} + +class SampleWidget extends StatefulWidget { + const SampleWidget({ + required this.mockedEnterTransitionController, + required this.mockedExitTransitionController, + super.key, + }); + + final AnimationController mockedEnterTransitionController; + final AnimationController mockedExitTransitionController; + + @override + State createState() => _SampleWidgetState(); +} + +class _SampleWidgetState extends State + with SingleTickerProviderStateMixin, TransitionScreenMixin { + @override + List get forwardEnterStatuses => [Status.welcome]; + + @override + List get forwardExitStatuses => [Status.welcomeToAskQuestion]; + + @override + List get backEnterStatuses => [Status.askQuestion]; + + @override + List get backExitStatuses => [Status.askQuestionToThinking]; + + @override + void initializeTransitionController() { + enterTransitionController = widget.mockedEnterTransitionController; + exitTransitionController = widget.mockedExitTransitionController; + } + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +void main() { + group('TransitionScreenMixin', () { + late HomeBloc homeBloc; + late AnimationController enterAnimationController; + late AnimationController exitAnimationController; + + setUp(() { + homeBloc = _MockHomeBloc(); + when(() => homeBloc.state).thenReturn(HomeState()); + + enterAnimationController = _MockAnimationController(); + when(() => enterAnimationController.forward()) + .thenAnswer((invocation) => TickerFuture.complete()); + when(() => enterAnimationController.reverse()) + .thenAnswer((invocation) => TickerFuture.complete()); + when(() => enterAnimationController.addStatusListener(any())) + .thenAnswer((invocation) {}); + when(() => enterAnimationController.dispose()) + .thenAnswer((invocation) {}); + + exitAnimationController = _MockAnimationController(); + when(() => exitAnimationController.forward()) + .thenAnswer((invocation) => TickerFuture.complete()); + when(() => exitAnimationController.reverse()) + .thenAnswer((invocation) => TickerFuture.complete()); + when(() => exitAnimationController.addStatusListener(any())) + .thenAnswer((invocation) {}); + when(() => exitAnimationController.dispose()).thenAnswer((invocation) {}); + }); + + testWidgets( + 'calls enterAnimationController forward ' + 'when status is in forwardEnterStatuses', (tester) async { + final streamController = StreamController(); + whenListen( + homeBloc, + streamController.stream, + initialState: HomeState(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: homeBloc, + child: SampleWidget( + mockedEnterTransitionController: enterAnimationController, + mockedExitTransitionController: exitAnimationController, + ), + ), + ); + + streamController.add(const HomeState()); + verify(() => enterAnimationController.forward()).called(1); + }); + + testWidgets( + 'calls exitTransitionController forward ' + 'when status is in forwardExitStatuses', (tester) async { + final streamController = StreamController(); + whenListen( + homeBloc, + streamController.stream, + initialState: HomeState(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: homeBloc, + child: SampleWidget( + mockedEnterTransitionController: enterAnimationController, + mockedExitTransitionController: exitAnimationController, + ), + ), + ); + + streamController + .add(const HomeState(status: Status.welcomeToAskQuestion)); + verify(() => exitAnimationController.forward()).called(1); + }); + + testWidgets( + 'calls exitAnimationController reverse ' + 'when status is in backEnterStatuses', (tester) async { + final streamController = StreamController(); + whenListen( + homeBloc, + streamController.stream, + initialState: HomeState(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: homeBloc, + child: SampleWidget( + mockedEnterTransitionController: enterAnimationController, + mockedExitTransitionController: exitAnimationController, + ), + ), + ); + + streamController.add(const HomeState(status: Status.askQuestion)); + verify(() => exitAnimationController.reverse()).called(1); + }); + + testWidgets( + 'calls enterAnimationController reverse ' + 'when status is in backExitStatuses', (tester) async { + final streamController = StreamController(); + whenListen( + homeBloc, + streamController.stream, + initialState: HomeState(), + ); + + await tester.pumpApp( + BlocProvider.value( + value: homeBloc, + child: SampleWidget( + mockedEnterTransitionController: enterAnimationController, + mockedExitTransitionController: exitAnimationController, + ), + ), + ); + + streamController + .add(const HomeState(status: Status.askQuestionToThinking)); + verify(() => enterAnimationController.reverse()).called(1); + }); + }); +}