diff --git a/lib/api/client/api_client.dart b/lib/api/client/api_client.dart index 84154eb..d1a6b06 100644 --- a/lib/api/client/api_client.dart +++ b/lib/api/client/api_client.dart @@ -1,10 +1,12 @@ import 'package:dio/dio.dart'; import 'package:elevate_tracking_app/api/models/requests/login_request.dart'; +import 'package:elevate_tracking_app/api/models/responses/change_password_response_dto.dart'; import 'package:elevate_tracking_app/api/models/responses/login_response.dart'; import 'package:injectable/injectable.dart'; import 'package:retrofit/retrofit.dart'; import '../../core/constants/end_points.dart'; +import '../models/requests/change_password_request_dto.dart'; part 'api_client.g.dart'; @@ -16,4 +18,12 @@ abstract class ApiClient { @POST(EndPoints.login) Future login(@Body() LoginRequest loginRequest); + + + @PATCH(EndPoints.changePassword) + Future changePassword( + @Body() ChangePasswordRequestDto body, + ); + + } diff --git a/lib/api/data_source/auth_remote_data_source_impl.dart b/lib/api/data_source/auth_remote_data_source_impl.dart index 3029a0e..e9759fb 100644 --- a/lib/api/data_source/auth_remote_data_source_impl.dart +++ b/lib/api/data_source/auth_remote_data_source_impl.dart @@ -1,4 +1,5 @@ import 'package:elevate_tracking_app/api/client/api_client.dart'; +import 'package:elevate_tracking_app/api/mapper/change_password_mapper.dart'; import 'package:elevate_tracking_app/api/mapper/login_mapper.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/core/api_result/safe_api_call.dart'; @@ -6,6 +7,8 @@ import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.da import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; import 'package:elevate_tracking_app/domain/entites/requests/login_request_entity.dart'; import 'package:injectable/injectable.dart'; +import '../../domain/entites/requests/change_password_request_entity.dart'; +import '../../domain/entites/response/change_password_response_entity.dart'; @Injectable(as: AuthRemoteDataSource) class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { @@ -20,4 +23,14 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { (response) => response.toEntity(), ); } + + @override + Future> changePassword( + ChangePasswordRequestEntity request, + ) async { + return safeApiCall( + () => _apiClient.changePassword(request.fromDomain()), + (response) => response.toEntity(), + ); + } } diff --git a/lib/api/mapper/change_password_mapper.dart b/lib/api/mapper/change_password_mapper.dart new file mode 100644 index 0000000..43e7ad4 --- /dev/null +++ b/lib/api/mapper/change_password_mapper.dart @@ -0,0 +1,33 @@ +import '../../domain/entites/requests/change_password_request_entity.dart'; +import '../../domain/entites/response/change_password_response_entity.dart'; +import '../models/requests/change_password_request_dto.dart'; +import '../models/responses/change_password_response_dto.dart'; + +extension ChangePasswordMapper on ChangePasswordResponseDto{ + ChangePasswordResponseEntity toEntity() { + return ChangePasswordResponseEntity( + message: message, + token: token + ); + } +} + +extension ChangePasswordRequestMapper on ChangePasswordRequestEntity{ + + ChangePasswordRequestDto fromDomain(){ + return ChangePasswordRequestDto( + newPassword: newPassword, + password: password, + + ); + } +} + +extension ChangePasswordResponseEntityMapper on ChangePasswordResponseEntity { + ChangePasswordResponseDto toDto() { + return ChangePasswordResponseDto( + message: message, + token: token, + ); + } +} \ No newline at end of file diff --git a/lib/api/models/requests/change_password_request_dto.dart b/lib/api/models/requests/change_password_request_dto.dart new file mode 100644 index 0000000..662cf44 --- /dev/null +++ b/lib/api/models/requests/change_password_request_dto.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'change_password_request_dto.g.dart'; + +@JsonSerializable() +class ChangePasswordRequestDto { + @JsonKey(name: "password") + final String? password; + @JsonKey(name: "newPassword") + final String? newPassword; + + ChangePasswordRequestDto ({ + this.password, + this.newPassword, + }); + + factory ChangePasswordRequestDto.fromJson(Map json) { + return _$ChangePasswordRequestDtoFromJson(json); + } + + Map toJson() { + return _$ChangePasswordRequestDtoToJson(this); + } +} + + diff --git a/lib/api/models/responses/change_password_response_dto.dart b/lib/api/models/responses/change_password_response_dto.dart new file mode 100644 index 0000000..69b083f --- /dev/null +++ b/lib/api/models/responses/change_password_response_dto.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'change_password_response_dto.g.dart'; + +@JsonSerializable() +class ChangePasswordResponseDto { + @JsonKey(name: "message") + final String? message; + @JsonKey(name: "token") + final String? token; + + ChangePasswordResponseDto ({ + this.message, + this.token, + }); + + factory ChangePasswordResponseDto.fromJson(Map json) { + return _$ChangePasswordResponseDtoFromJson(json); + } + + Map toJson() { + return _$ChangePasswordResponseDtoToJson(this); + } +} + + diff --git a/lib/core/constants/end_points.dart b/lib/core/constants/end_points.dart index e8e8db8..9c1341e 100644 --- a/lib/core/constants/end_points.dart +++ b/lib/core/constants/end_points.dart @@ -1,3 +1,4 @@ abstract class EndPoints { static const login = "api/v1/drivers/signin"; + static const changePassword = "api/v1/drivers/change-password"; } diff --git a/lib/core/constants/widgets_keys.dart b/lib/core/constants/widgets_keys.dart index 4f8effc..97eda1f 100644 --- a/lib/core/constants/widgets_keys.dart +++ b/lib/core/constants/widgets_keys.dart @@ -16,4 +16,13 @@ abstract final class WidgetsKeys { "login_screen_forgot_password_button"; static const String kLoginScreenContinueButton = "login_screen_continue_button"; + static const String kChanePasswordPassword = + "change_password_password"; + static const String kChanePasswordConfirmPassword = + "change_password_confirm_password"; + static const String kChanePasswordNewPassword = + "change_password_new_password"; + static const String kChanePasswordConfirmButton = + "change_password_confirm_button"; + } diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 592e649..8b77aed 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,5 +1,6 @@ import 'package:elevate_tracking_app/core/router/route_names.dart'; import 'package:elevate_tracking_app/presentation/application_approved/views/screen/application_approved_screen.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view/change_password_view.dart'; import 'package:elevate_tracking_app/presentation/home/views/screen/home_screen.dart'; import 'package:elevate_tracking_app/presentation/main_home/views/screen/main_home_screen.dart'; import 'package:elevate_tracking_app/presentation/onboarding/views/screen/onboarding_screen.dart'; @@ -39,11 +40,16 @@ abstract class AppRouter { builder: (context, state) => const ProfileScreen(), ), + GoRoute( + path: RouteNames.changePassword, + builder: (context, state) => const ChangePassword(), + ), + ]; static GoRouter router(String? token) { return GoRouter( - initialLocation: token == null ? RouteNames.onboarding : RouteNames.home, + initialLocation: token == null ? RouteNames.login: RouteNames.login, routes: routes, ); } diff --git a/lib/data/data_source/auth_remote_data_source.dart b/lib/data/data_source/auth_remote_data_source.dart index 8fdc870..3008f0b 100644 --- a/lib/data/data_source/auth_remote_data_source.dart +++ b/lib/data/data_source/auth_remote_data_source.dart @@ -1,7 +1,14 @@ import '../../core/api_result/api_result.dart'; import '../../domain/entites/login_entity.dart'; +import '../../domain/entites/requests/change_password_request_entity.dart'; import '../../domain/entites/requests/login_request_entity.dart'; +import '../../domain/entites/response/change_password_response_entity.dart'; abstract interface class AuthRemoteDataSource { Future> login(LoginRequestEntity loginRequestEntity); + + Future> changePassword( + ChangePasswordRequestEntity request, + ); + } diff --git a/lib/data/repo/auth_repo_impl.dart b/lib/data/repo/auth_repo_impl.dart index c2dc52a..2f49eb1 100644 --- a/lib/data/repo/auth_repo_impl.dart +++ b/lib/data/repo/auth_repo_impl.dart @@ -4,6 +4,8 @@ import 'package:elevate_tracking_app/domain/entites/requests/login_request_entit import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; import 'package:injectable/injectable.dart'; +import '../../domain/entites/requests/change_password_request_entity.dart'; +import '../../domain/entites/response/change_password_response_entity.dart'; import '../data_source/auth_local_data_source.dart'; import '../data_source/auth_remote_data_source.dart'; @@ -34,4 +36,13 @@ class AuthRepoImpl implements AuthRepo { } return result; } + + @override + Future> changePassword( + ChangePasswordRequestEntity request, + ) { + return _authRemoteDataSource.changePassword(request); + } + + } diff --git a/lib/domain/entites/requests/change_password_request_entity.dart b/lib/domain/entites/requests/change_password_request_entity.dart new file mode 100644 index 0000000..d9e041f --- /dev/null +++ b/lib/domain/entites/requests/change_password_request_entity.dart @@ -0,0 +1,15 @@ + + +class ChangePasswordRequestEntity { + final String? password; + final String? newPassword; + + ChangePasswordRequestEntity ({ + this.password, + this.newPassword, + }); + + +} + + diff --git a/lib/domain/entites/response/change_password_response_entity.dart b/lib/domain/entites/response/change_password_response_entity.dart new file mode 100644 index 0000000..ea0c203 --- /dev/null +++ b/lib/domain/entites/response/change_password_response_entity.dart @@ -0,0 +1,15 @@ + + +class ChangePasswordResponseEntity { + final String? message; + final String? token; + + ChangePasswordResponseEntity ({ + this.message, + this.token, + }); + + +} + + diff --git a/lib/domain/repo/auth_repo.dart b/lib/domain/repo/auth_repo.dart index caed925..8e38700 100644 --- a/lib/domain/repo/auth_repo.dart +++ b/lib/domain/repo/auth_repo.dart @@ -2,6 +2,13 @@ import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; import 'package:elevate_tracking_app/domain/entites/requests/login_request_entity.dart'; +import '../entites/requests/change_password_request_entity.dart'; +import '../entites/response/change_password_response_entity.dart'; + abstract interface class AuthRepo { Future> login(LoginRequestEntity loginRequestEntity); + + Future> changePassword( + ChangePasswordRequestEntity request, + ); } diff --git a/lib/domain/use_cases/change_password_use_case.dart b/lib/domain/use_cases/change_password_use_case.dart new file mode 100644 index 0000000..297fccc --- /dev/null +++ b/lib/domain/use_cases/change_password_use_case.dart @@ -0,0 +1,18 @@ +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; +import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; +import 'package:injectable/injectable.dart'; +import '../../core/api_result/api_result.dart'; +import '../entites/requests/change_password_request_entity.dart'; + +@injectable +class ChangePasswordUseCase { + final AuthRepo _authRepo; + + ChangePasswordUseCase(this._authRepo); + + Future> call( + ChangePasswordRequestEntity changePasswordEntity, + ) async { + return await _authRepo.changePassword(changePasswordEntity); + } +} diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 8ec0105..0f4bac2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -44,5 +44,11 @@ "enterYourEmail": "Enter your email", "enterYourPassword": "Enter your password", "continueWord": "Continue", - "error": "Error" + "error": "Error", + "success": "success", + "resetPassword": "Reset Password", + "currentPassword": "Current password", + "newPassword": "New password", + "confirmPassword": "Confirm password", + "confirm": "Confirm" } \ No newline at end of file diff --git a/lib/presentation/auth/change_password/view/change_password_view.dart b/lib/presentation/auth/change_password/view/change_password_view.dart new file mode 100644 index 0000000..dd407fe --- /dev/null +++ b/lib/presentation/auth/change_password/view/change_password_view.dart @@ -0,0 +1,175 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.dart'; +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_event.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../../core/custom_widget/custom_dialog.dart'; +import '../../../../core/di/di.dart'; +import '../../../../core/router/route_names.dart'; +import '../../../../core/utils/validations.dart'; +import '../../../../generated/l10n.dart'; +import '../view_model/change_password_states.dart'; + +class ChangePassword extends StatefulWidget { + const ChangePassword({super.key}); + + @override + State createState() => _ChangePasswordState(); +} + +class _ChangePasswordState extends State { + late final ChangePasswordViewModel _changePasswordViewModel; + bool isDialogShow = false; + + @override + void initState() { + super.initState(); + _changePasswordViewModel = getIt(); + } + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () {}, + ), + Text(AppLocalizations.of(context).resetPassword), + ], + ), + ), + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 0.05 * width), + child: BlocConsumer( + bloc: _changePasswordViewModel, + listenWhen: (previous, current) => previous.status != current.status, + listener: (context, state) { + if (state.status == ChangePasswordStatus.loading) { + if (!isDialogShow) { + isDialogShow = true; + CustomDialog.loading(context: context); + } + return; + } + if (isDialogShow) { + context.pop(); + isDialogShow = false; + } + + if (state.status == ChangePasswordStatus.error) { + CustomDialog.positiveButton( + context: context, + title: AppLocalizations.of(context).error, + message: state.errorMessage, + ); + return; + } + if (state.status == ChangePasswordStatus.success) { + CustomDialog.positiveButton( + context: context, + title: AppLocalizations.of(context).success, + message: state.changePasswordResponseEntity?.message, + cancelable: false, + positiveOnClick: () { + context.go(RouteNames.resetPassword); + }, + ); + } + }, + + builder: (BuildContext context, state) { + return Form( + key: _changePasswordViewModel.changePasswordFormKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 16.h, + children: [ + SizedBox(height: 32.h), + TextFormField( + //current pass + key: const Key(WidgetsKeys.kChanePasswordPassword), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), + obscureText: true, + controller: + _changePasswordViewModel.currentPasswordController, + style: Theme.of(context).textTheme.bodySmall, + validator: Validations.validatePassword, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).currentPassword, + hintText: AppLocalizations.of(context).currentPassword, + ), + ), + SizedBox(height: 16.h), + TextFormField( + //new pass + key: const Key(WidgetsKeys.kChanePasswordNewPassword), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), + obscureText: true, + controller: _changePasswordViewModel.newPasswordController, + style: Theme.of(context).textTheme.bodySmall, + validator: Validations.validatePassword, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).newPassword, + hintText: AppLocalizations.of(context).newPassword, + ), + ), + SizedBox(height: 8.h), + TextFormField( + //confirm pass + key: const Key(WidgetsKeys.kChanePasswordConfirmPassword), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), + obscureText: true, + controller: + _changePasswordViewModel.confirmPasswordController, + style: Theme.of(context).textTheme.bodySmall, + validator: (val) { + return Validations.validateConfirmPassword( + val, + _changePasswordViewModel.newPasswordController.text, + ); + }, + decoration: InputDecoration( + labelText: AppLocalizations.of(context).confirmPassword, + hintText: AppLocalizations.of(context).confirmPassword, + ), + ), + SizedBox(height: 32.h), + + ElevatedButton( + key: const Key(WidgetsKeys.kChanePasswordConfirmButton), + onPressed: state.status == ChangePasswordStatus.loading + ? null + : state.isButtonEnabled + ? () => _changePasswordViewModel.doIntent( + ChangePasswordEvent(), + ) + : null, + child: Text( + AppLocalizations.of(context).confirm, + style: TextStyle(color: AppColors.white), + ), + ), + ], + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/presentation/auth/change_password/view_model/change_password_event.dart b/lib/presentation/auth/change_password/view_model/change_password_event.dart new file mode 100644 index 0000000..3cc152c --- /dev/null +++ b/lib/presentation/auth/change_password/view_model/change_password_event.dart @@ -0,0 +1,4 @@ +sealed class ChangePasswordEvents {} + +class ChangePasswordEvent extends ChangePasswordEvents {} +class CheckAllFieldsEvent extends ChangePasswordEvents {} \ No newline at end of file diff --git a/lib/presentation/auth/change_password/view_model/change_password_states.dart b/lib/presentation/auth/change_password/view_model/change_password_states.dart new file mode 100644 index 0000000..20da86c --- /dev/null +++ b/lib/presentation/auth/change_password/view_model/change_password_states.dart @@ -0,0 +1,89 @@ +import '../../../../domain/entites/response/change_password_response_entity.dart'; +import 'package:equatable/equatable.dart'; + +// enum ChangePasswordStatus { initial, loading, success, error } +// +// class ChangePasswordStates extends Equatable { +// final bool isLoading; +// final ChangePasswordResponseEntity? changePasswordResponseEntity; +// final String? errorMessage; +// final bool isSuccess; +// final bool isButtonEnabled; +// +// const ChangePasswordStates({ +// this.isLoading = false, +// this.changePasswordResponseEntity, +// this.errorMessage, +// this.isSuccess = false, +// this.isButtonEnabled = false, +// +// }); +// +// ChangePasswordStates copyWith({ +// bool? isLoading, +// ChangePasswordResponseEntity? changePasswordResponseEntity, +// String? errorMessage, +// bool? isSuccess, +// bool? isButtonEnabled, +// }) +// +// { +// return ChangePasswordStates( +// isLoading: isLoading ?? this.isLoading, +// changePasswordResponseEntity: changePasswordResponseEntity ?? this.changePasswordResponseEntity, +// errorMessage: errorMessage ?? this.errorMessage, +// isSuccess: isSuccess ?? this.isSuccess, +// isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, +// ); +// } +// +// @override +// List get props => [ +// isLoading, +// changePasswordResponseEntity, +// errorMessage, +// isSuccess, +// isButtonEnabled, +// ]; +// } +import 'package:equatable/equatable.dart'; +import '../../../../domain/entites/response/change_password_response_entity.dart'; + +enum ChangePasswordStatus { initial, loading, success, error } + +class ChangePasswordStates extends Equatable { + final ChangePasswordStatus status; + final ChangePasswordResponseEntity? changePasswordResponseEntity; + final String? errorMessage; + final bool isButtonEnabled; + + const ChangePasswordStates({ + this.status = ChangePasswordStatus.initial, + this.changePasswordResponseEntity, + this.errorMessage, + this.isButtonEnabled = false, + }); + + ChangePasswordStates copyWith({ + ChangePasswordStatus? status, + ChangePasswordResponseEntity? changePasswordResponseEntity, + String? errorMessage, + bool? isButtonEnabled, + }) { + return ChangePasswordStates( + status: status ?? this.status, + changePasswordResponseEntity: + changePasswordResponseEntity ?? this.changePasswordResponseEntity, + errorMessage: errorMessage, + isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, + ); + } + + @override + List get props => [ + status, + changePasswordResponseEntity, + errorMessage, + isButtonEnabled, + ]; +} diff --git a/lib/presentation/auth/change_password/view_model/change_password_view_model.dart b/lib/presentation/auth/change_password/view_model/change_password_view_model.dart new file mode 100644 index 0000000..0ec0eb2 --- /dev/null +++ b/lib/presentation/auth/change_password/view_model/change_password_view_model.dart @@ -0,0 +1,90 @@ +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_event.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../../core/api_result/api_result.dart'; +import '../../../../domain/entites/requests/change_password_request_entity.dart'; +import '../../../../domain/entites/response/change_password_response_entity.dart'; +import '../../../../domain/use_cases/change_password_use_case.dart'; +import 'change_password_states.dart'; + +@injectable +class ChangePasswordViewModel extends Cubit { + final ChangePasswordUseCase _changePasswordUseCase; + + ChangePasswordViewModel(this._changePasswordUseCase) + : super(const ChangePasswordStates()); + + final GlobalKey changePasswordFormKey = GlobalKey(); + final TextEditingController currentPasswordController = + TextEditingController(); + final TextEditingController newPasswordController = TextEditingController(); + final TextEditingController confirmPasswordController = + TextEditingController(); + + void doIntent(ChangePasswordEvents events) { + switch (events) { + case ChangePasswordEvent(): + _changePassword(); + case CheckAllFieldsEvent(): + _checkIfAllFieldsFilled(); + } + } + + void _checkIfAllFieldsFilled() { + final isFilled = + currentPasswordController.text.isNotEmpty && + newPasswordController.text.isNotEmpty && + confirmPasswordController.text.isNotEmpty && + newPasswordController.text == confirmPasswordController.text; + + // emit(state.copyWith(isButtonEnabled: isFilled)); + emit(state.copyWith( + isButtonEnabled: isFilled, + status: state.status, + )); + } + + Future _changePassword() async { + // emit(state.copyWith(isLoading: true)); + emit(state.copyWith(status: ChangePasswordStatus.loading)); + final result = await _changePasswordUseCase( + ChangePasswordRequestEntity( + password: currentPasswordController.text, + newPassword: newPasswordController.text, + ), + ); + switch (result) { + // case ApiSuccessResult(): + // emit( + // state.copyWith( + // isLoading: false, + // changePasswordResponseEntity: result.data, + // isSuccess: true, + // ), + // ); + // case ApiErrorResult(): + // emit( + // state.copyWith(isLoading: false, errorMessage: result.errorMessage), + case ApiSuccessResult(): + emit(state.copyWith( + status: ChangePasswordStatus.success, + changePasswordResponseEntity: result.data, + )); + case ApiErrorResult(): + emit(state.copyWith( + status: ChangePasswordStatus.error, + errorMessage: result.errorMessage, + )); + + } + } + + @override + Future close() { + currentPasswordController.dispose(); + newPasswordController.dispose(); + return super.close(); + } +} diff --git a/test/api/data_source/auth_remote_data_source_impl_test.dart b/test/api/data_source/auth_remote_data_source_impl_test.dart index e01706b..3b72929 100644 --- a/test/api/data_source/auth_remote_data_source_impl_test.dart +++ b/test/api/data_source/auth_remote_data_source_impl_test.dart @@ -3,10 +3,12 @@ import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_imp import 'package:elevate_tracking_app/api/mapper/login_mapper.dart'; import 'package:elevate_tracking_app/core/api_result/api_result.dart'; import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import '../../dummy/change_password_dummy_data.dart'; import '../../dummy/login_dummy_data.dart'; import 'auth_remote_data_source_impl_test.mocks.dart'; @@ -54,7 +56,35 @@ void main() { result as ApiErrorResult; expect(result.errorMessage, contains(expectedError)); }); + }); + + group ("change password test",(){ + final changePasswordRequestEntity = ChangePasswordDummyData().fakeChangePasswordRequestEntity; + test( "when call change password test it should return change password with data " , + () async { + final expectResult= ChangePasswordDummyData().fakeChangePasswordResponse; + when(mockApiClient.changePassword(any)).thenAnswer(( _ ) async => expectResult ); + + final result= await dataSource.changePassword(changePasswordRequestEntity); + + verify(mockApiClient.changePassword(any)).called(1); + expect(result, isA>()); + result as ApiSuccessResult; + expect(result.data.message, expectResult.message); + expect(result.data.token, expectResult.token); + }); + + test("when changePassword failed it should return error result ",() async { + final expectedError = "fake-error"; + when(mockApiClient.changePassword(any)).thenThrow(Exception(expectedError)); + final result = await dataSource.changePassword(changePasswordRequestEntity); + + verify(mockApiClient.changePassword(any)).called(1); + expect(result, isA>()); + result as ApiErrorResult; + expect(result.errorMessage, contains(expectedError)); + }); }); }); diff --git a/test/api/mapper/change_password_mapper_test.dart b/test/api/mapper/change_password_mapper_test.dart new file mode 100644 index 0000000..dbea80f --- /dev/null +++ b/test/api/mapper/change_password_mapper_test.dart @@ -0,0 +1,27 @@ +import 'package:elevate_tracking_app/api/mapper/change_password_mapper.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../dummy/change_password_dummy_data.dart'; + +void main (){ + group("test changePassword mapper",(){ + test("when we call to response it should return changePassword entity ",(){ + + final changePasswordResponse = ChangePasswordDummyData().fakeChangePasswordResponse; + final result = changePasswordResponse.toEntity(); + + expect(result.message, changePasswordResponse.message); + expect(result.token, changePasswordResponse.token); + + }); + test("when we call to request it should return changePassword entity ",(){ + + final changePasswordResponse = ChangePasswordDummyData().fakeChangePasswordRequestEntity; + final result = changePasswordResponse.fromDomain(); + + expect(result.password, changePasswordResponse.password); + expect(result.newPassword, changePasswordResponse.newPassword); + + }); + }); +} \ No newline at end of file diff --git a/test/data/repo/auth_repo_impl_test.dart b/test/data/repo/auth_repo_impl_test.dart index d8d2e41..af25462 100644 --- a/test/data/repo/auth_repo_impl_test.dart +++ b/test/data/repo/auth_repo_impl_test.dart @@ -3,14 +3,16 @@ import 'package:elevate_tracking_app/data/data_source/auth_local_data_source.dar import 'package:elevate_tracking_app/data/data_source/auth_remote_data_source.dart'; import 'package:elevate_tracking_app/data/repo/auth_repo_impl.dart'; import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/requests/change_password_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; - +import '../../dummy/change_password_dummy_data.dart'; import '../../dummy/login_dummy_data.dart'; import 'auth_repo_impl_test.mocks.dart'; -@GenerateMocks([AuthRemoteDataSource, AuthLocalDataSource]) +@GenerateMocks([AuthRemoteDataSource , AuthLocalDataSource]) void main() { group("test AuthRepoImpl", () { late MockAuthRemoteDataSource mockAuthRemoteDataSource; @@ -84,6 +86,41 @@ void main() { ); }); + group("changePassword test",(){ + + final changePasswordRequestEntity= ChangePasswordRequestEntity(newPassword: "123", password: "1234"); + test("when call changePassword it should return changePassword entity with right data " , () async { + final expectedEntity = ChangePasswordDummyData().fakeChangePasswordResponseEntity; + final expectedResult = ApiSuccessResult(expectedEntity ); + + provideDummy> ( expectedResult); + when(mockAuthRemoteDataSource.changePassword(any)).thenAnswer( (_) async => expectedResult); + + final result = await authRepoImpl.changePassword(changePasswordRequestEntity); + + verify(mockAuthRemoteDataSource.changePassword(any)).called(1); + expect(result, isA>()); + result as ApiSuccessResult; + expect(result.data , expectedEntity); + + }); + test("when changePassword failed it should return error data",()async{ + + final expectError="fake-error"; + + provideDummy>( + ApiErrorResult(expectError), + ); + when(mockAuthRemoteDataSource.changePassword(any)) + .thenAnswer((_) async => ApiErrorResult(expectError)); + final result= await authRepoImpl.changePassword(changePasswordRequestEntity); + + verify(mockAuthRemoteDataSource.changePassword(any)).called(1); + expect(result, isA>()); + result as ApiErrorResult; + expect(result.errorMessage, contains(expectError)); + }); + }); }); } diff --git a/test/domain/use_cases/change_password_use_case_test.dart b/test/domain/use_cases/change_password_use_case_test.dart new file mode 100644 index 0000000..b2d1403 --- /dev/null +++ b/test/domain/use_cases/change_password_use_case_test.dart @@ -0,0 +1,64 @@ +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/requests/change_password_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; +import 'package:elevate_tracking_app/domain/repo/auth_repo.dart'; +import 'package:elevate_tracking_app/domain/use_cases/change_password_use_case.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../../dummy/change_password_dummy_data.dart'; +import 'login_use_case_test.mocks.dart'; + +@GenerateMocks([AuthRepo]) +void main() { + + group(" test changePassword use case",(){ + + late MockAuthRepo mockAuthRepo; + late ChangePasswordUseCase changePasswordUseCase; + + setUp( (){ + mockAuthRepo = MockAuthRepo(); + changePasswordUseCase =ChangePasswordUseCase(mockAuthRepo); + }); + + test("when call auth repo it should return change password with right data", () async { + + final changePasswordRequest = ChangePasswordRequestEntity(password: "123",newPassword: "1234"); + + final expectedEntity=ChangePasswordDummyData().fakeChangePasswordResponseEntity; + final expectedResult = ApiSuccessResult(expectedEntity ); + provideDummy >(expectedResult); + when(mockAuthRepo.changePassword(changePasswordRequest)).thenAnswer( (_) async =>expectedResult); + + final result = await changePasswordUseCase(changePasswordRequest); + + verify(mockAuthRepo.changePassword(changePasswordRequest)).called(1); + expect(result, isA>()); + result as ApiSuccessResult; + expect(result.data.token, expectedResult.data.token); + expect(result.data.message, expectedResult.data.message); + + }); + + test("when failed call it should return error data", () async { + + final changePasswordRequest = ChangePasswordRequestEntity(password: "123",newPassword: "1234"); + + final expectedError="fake-error"; + final expectedResult = ApiErrorResult(expectedError); + provideDummy >(expectedResult); + when(mockAuthRepo.changePassword(changePasswordRequest)).thenAnswer( (_) async => expectedResult); + + final result = await changePasswordUseCase(changePasswordRequest); + + verify(mockAuthRepo.changePassword(changePasswordRequest)).called(1); + expect(result, isA>()); + result as ApiErrorResult; + expect(result.errorMessage, expectedResult.errorMessage); + expect(result.error, expectedResult.error); + + }); + + }); +} \ No newline at end of file diff --git a/test/domain/use_cases/login_use_case_test.dart b/test/domain/use_cases/login_use_case_test.dart index 882158b..4907caa 100644 --- a/test/domain/use_cases/login_use_case_test.dart +++ b/test/domain/use_cases/login_use_case_test.dart @@ -11,17 +11,20 @@ import 'login_use_case_test.mocks.dart'; @GenerateMocks([AuthRepo]) void main() { - group("test LoginUseCase", () { - late MockAuthRepo mockAuthRepo; - late LoginUseCase loginUseCase; + group ( " test LoginUseCase " , () { + late MockAuthRepo mockAuthRepo ; + late LoginUseCase loginUseCase ; + setUp(() { + mockAuthRepo = MockAuthRepo(); loginUseCase = LoginUseCase(mockAuthRepo); + }); final loginRequestEntity = LoginDummyData().fakeLoginRequestEntity; - test("when call should return LoginEntity with right data", () async { + test ( " when call should return LoginEntity with right data " , () async { final expectedEntity = LoginDummyData().fakeLoginEntity; final expectedResult = ApiSuccessResult(expectedEntity); @@ -39,7 +42,7 @@ void main() { expect(result.data, expectedEntity); }); - test("when call failed should return error result", () async { + test ( " when call failed should return error result " , () async { final expectedError = "fake-error"; final expectedResult = ApiErrorResult(expectedError); diff --git a/test/dummy/change_password_dummy_data.dart b/test/dummy/change_password_dummy_data.dart new file mode 100644 index 0000000..aa67bc7 --- /dev/null +++ b/test/dummy/change_password_dummy_data.dart @@ -0,0 +1,21 @@ +import 'package:elevate_tracking_app/api/mapper/change_password_mapper.dart'; +import 'package:elevate_tracking_app/api/models/requests/change_password_request_dto.dart'; +import 'package:elevate_tracking_app/api/models/responses/change_password_response_dto.dart'; +import 'package:elevate_tracking_app/domain/entites/requests/change_password_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; + +class ChangePasswordDummyData{ + + + ChangePasswordRequestEntity get fakeChangePasswordRequestEntity => + ChangePasswordRequestEntity(password: "123",newPassword: "1234"); + + ChangePasswordRequestDto get fakeChangePasswordRequest => fakeChangePasswordRequestEntity.fromDomain(); + + + ChangePasswordResponseEntity get fakeChangePasswordResponseEntity => + + ChangePasswordResponseEntity(message: "mo",token: "mon"); + + ChangePasswordResponseDto get fakeChangePasswordResponse => fakeChangePasswordResponseEntity.toDto(); +} \ No newline at end of file diff --git a/test/dummy/login_dummy_data.dart b/test/dummy/login_dummy_data.dart index 48be22a..2ef5b82 100644 --- a/test/dummy/login_dummy_data.dart +++ b/test/dummy/login_dummy_data.dart @@ -15,3 +15,4 @@ class LoginDummyData { LoginEntity get fakeLoginEntity => fakeLoginResponse.toEntity(); } + diff --git a/test/presentation/auth/change_password/view/change_password_view_test.dart b/test/presentation/auth/change_password/view/change_password_view_test.dart new file mode 100644 index 0000000..1869c5c --- /dev/null +++ b/test/presentation/auth/change_password/view/change_password_view_test.dart @@ -0,0 +1,131 @@ +import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; +import 'package:elevate_tracking_app/core/custom_widget/test_app_wrapper.dart'; +import 'package:elevate_tracking_app/core/di/di.config.dart'; +import 'package:elevate_tracking_app/domain/use_cases/change_password_use_case.dart'; +import 'package:elevate_tracking_app/generated/l10n.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view/change_password_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/annotations.dart'; + +@GenerateMocks([ChangePasswordUseCase]) +void main() { + group( "change password view test " , ( ) { + + final getIt = GetIt.instance; + setUp ( () async { + getIt.reset(); + getIt.init(); + + + + + }); + + final currentPasswordField = find.byKey( + const Key(WidgetsKeys.kChanePasswordPassword), + ); + final newPasswordField = find.byKey( + const Key(WidgetsKeys.kChanePasswordNewPassword), + ); + final confirmPasswordField = find.byKey( + const Key(WidgetsKeys.kChanePasswordConfirmPassword), + ); + final confirmButton= find.byKey( + const Key(WidgetsKeys.kChanePasswordConfirmButton), + ); + testWidgets("verify change password behavior " , (tester) async { + await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); + + + expect(currentPasswordField, findsOneWidget); + expect(newPasswordField, findsOneWidget); + expect(confirmPasswordField, findsOneWidget); + expect(confirmButton, findsOneWidget); + + expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + expect(find.text(AppLocalizations().passwordNotMatched), findsNothing); + + await tester.enterText(currentPasswordField, "123"); + await tester.enterText(confirmPasswordField, "466"); + await tester.pumpAndSettle(); + await tester.tap(confirmButton); + await tester.pumpAndSettle(); + await tester.pump(); + + + }); + testWidgets("Verify app bar behaviour", (tester) async { + await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); + + expect(find.byType(AppBar), findsOneWidget); + + + }); + testWidgets("verify success change password behavior " , (tester) async { + await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); + + + expect(currentPasswordField, findsOneWidget); + expect(newPasswordField, findsOneWidget); + expect(confirmPasswordField, findsOneWidget); + expect(confirmButton, findsOneWidget); + + expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + expect(find.text(AppLocalizations().passwordNotMatched), findsNothing); + + await tester.enterText(currentPasswordField, "123456"); + await tester.enterText(newPasswordField, "abcd1234"); + await tester.enterText(confirmPasswordField, "abcd1234"); + await tester.pumpAndSettle(); + + + + final buttonWidget = tester.widget(confirmButton); + expect(buttonWidget.onPressed != null, true); + + await tester.tap(confirmButton); + await tester.pump(const Duration(seconds: 2)); + expect(find.text(AppLocalizations().passwordNotMatched), findsNothing); + expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + + + + }); + testWidgets("verify error fields change password behavior " , (tester) async { + await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); + + + expect(currentPasswordField, findsOneWidget); + expect(newPasswordField, findsOneWidget); + expect(confirmPasswordField, findsOneWidget); + expect(confirmButton, findsOneWidget); + + expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + expect(find.text(AppLocalizations().passwordNotMatched), findsNothing); + + await tester.enterText(currentPasswordField, "123456"); + await tester.enterText(newPasswordField, "abcd1234"); + await tester.enterText(confirmPasswordField, "abcd123"); + await tester.pumpAndSettle(); + + + + await tester.tap(confirmButton); + await tester.pump(const Duration(seconds: 2)); + + final buttonWidget = tester.widget(confirmButton); + expect(buttonWidget.onPressed != null, false); + + + + + + }); + + + + + }); +} diff --git a/test/presentation/auth/change_password/view_model/change_password_view_model_test.dart b/test/presentation/auth/change_password/view_model/change_password_view_model_test.dart new file mode 100644 index 0000000..eb29a34 --- /dev/null +++ b/test/presentation/auth/change_password/view_model/change_password_view_model_test.dart @@ -0,0 +1,109 @@ +import 'package:bloc_test/bloc_test.dart'; +import 'package:elevate_tracking_app/core/api_result/api_result.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; +import 'package:elevate_tracking_app/domain/use_cases/change_password_use_case.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_event.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_states.dart'; +import 'package:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_view_model.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import '../../../../dummy/change_password_dummy_data.dart'; +import 'change_password_view_model_test.mocks.dart'; + + +@GenerateMocks([ChangePasswordUseCase]) +void main() { + group("change password view model", () { + late MockChangePasswordUseCase mockChangePasswordUseCase; + late ChangePasswordStates changePasswordStates; + late ChangePasswordViewModel changePasswordViewModel; + + setUp(() { + mockChangePasswordUseCase = MockChangePasswordUseCase(); + changePasswordViewModel = ChangePasswordViewModel(mockChangePasswordUseCase); + changePasswordStates = const ChangePasswordStates(); + }); + + final changePasswordEntity = ChangePasswordDummyData().fakeChangePasswordResponseEntity; + final changePasswordRequestEntity = ChangePasswordDummyData().fakeChangePasswordRequestEntity; + final expectedResult = ApiSuccessResult(changePasswordEntity); + + provideDummy>(expectedResult); + + blocTest( + "when success should emit loading then success state", + build: () => changePasswordViewModel, + act: (bloc) { + when(mockChangePasswordUseCase(any)).thenAnswer((_) async => expectedResult); + bloc.newPasswordController.text = changePasswordRequestEntity.newPassword!; + bloc.currentPasswordController.text = changePasswordRequestEntity.password!; + return bloc.doIntent(ChangePasswordEvent()); + }, + expect: () => [ + changePasswordStates.copyWith(status: ChangePasswordStatus.loading), + changePasswordStates.copyWith( + status: ChangePasswordStatus.success, + changePasswordResponseEntity: changePasswordEntity, + errorMessage: null, + isButtonEnabled: false, + ), + ], + verify: (_) => verify(mockChangePasswordUseCase(any)).called(1), + ); + + final expectedError = "fake_error"; + final expectedErrorResult = ApiErrorResult(expectedError); + + provideDummy>(expectedErrorResult); + + blocTest( + "when error → should emit loading then error state", + build: () => changePasswordViewModel, + act: (bloc) { + when(mockChangePasswordUseCase(any)).thenAnswer((_) async => expectedErrorResult); + bloc.newPasswordController.text = changePasswordRequestEntity.newPassword!; + bloc.currentPasswordController.text = changePasswordRequestEntity.password!; + return bloc.doIntent(ChangePasswordEvent()); + }, + expect: () => [ + changePasswordStates.copyWith(status: ChangePasswordStatus.loading), + changePasswordStates.copyWith( + status: ChangePasswordStatus.error, + errorMessage: expectedError, + changePasswordResponseEntity: null, + isButtonEnabled: false, + ), + ], + verify: (_) => verify(mockChangePasswordUseCase(any)).called(1), + ); + + blocTest( + "should emit isButtonEnabled true when all fields are valid and match", + build: () => changePasswordViewModel, + act: (bloc) { + bloc.currentPasswordController.text = "old123"; + bloc.newPasswordController.text = "new123"; + bloc.confirmPasswordController.text = "new123"; + bloc.doIntent(CheckAllFieldsEvent()); + }, + expect: () => [ + changePasswordStates.copyWith(isButtonEnabled: true), + ], + ); + + blocTest( + "should emit isButtonEnabled false when passwords do not match", + build: () => changePasswordViewModel, + act: (bloc) { + bloc.currentPasswordController.text = "old123"; + bloc.newPasswordController.text = "new123"; + bloc.confirmPasswordController.text = "different"; + bloc.doIntent(CheckAllFieldsEvent()); + }, + expect: () => [ + changePasswordStates.copyWith(isButtonEnabled: false), + ], + ); + }); +} diff --git a/test/presentation/auth/login/view/screen/login_screen_test.dart b/test/presentation/auth/login/view/screen/login_screen_test.dart deleted file mode 100644 index e4896fd..0000000 --- a/test/presentation/auth/login/view/screen/login_screen_test.dart +++ /dev/null @@ -1,264 +0,0 @@ -import 'package:elevate_tracking_app/core/api_result/api_result.dart'; -import 'package:elevate_tracking_app/core/constants/const_keys.dart'; -import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; -import 'package:elevate_tracking_app/core/custom_widget/test_app_wrapper.dart'; -import 'package:elevate_tracking_app/core/di/di.dart'; -import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; -import 'package:elevate_tracking_app/domain/entites/requests/login_request_entity.dart'; -import 'package:elevate_tracking_app/domain/use_cases/login_use_case.dart'; -import 'package:elevate_tracking_app/generated/l10n.dart'; -import 'package:elevate_tracking_app/presentation/auth/login/view/screen/login_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import '../../../../../dummy/login_dummy_data.dart'; -import 'login_screen_test.mocks.dart'; - -@GenerateMocks([LoginUseCase, FlutterSecureStorage]) -void main() { - group("widget test LoginScreen", () { - late MockLoginUseCase mockLoginUseCase; - late MockFlutterSecureStorage mockFlutterSecureStorage; - - final appBarButton = find.byKey( - const Key(WidgetsKeys.kLoginScreenAppBarButton), - ); - final emailField = find.byKey( - const Key(WidgetsKeys.kLoginScreenEmailField), - ); - final passwordField = find.byKey( - const Key(WidgetsKeys.kLoginScreenPasswordField), - ); - final passwordVisibilityButton = find.byKey( - const Key(WidgetsKeys.kLoginScreenPasswordVisibilityButton), - ); - final rememberMeCheckbox = find.byKey( - const Key(WidgetsKeys.kLoginScreenRememberMeCheckbox), - ); - final forgotPasswordButton = find.byKey( - const Key(WidgetsKeys.kLoginScreenForgotPasswordButton), - ); - final continueButton = find.byKey( - const Key(WidgetsKeys.kLoginScreenContinueButton), - ); - - setUpAll(() async { - configureDependencies(); - await getIt.unregister(); - await getIt.unregister(); - mockLoginUseCase = MockLoginUseCase(); - mockFlutterSecureStorage = MockFlutterSecureStorage(); - getIt.registerSingleton(mockLoginUseCase); - getIt.registerSingleton(mockFlutterSecureStorage); - when( - mockFlutterSecureStorage.read(key: ConstKeys.keyRememberMe), - ).thenAnswer((_) async => ConstKeys.falseKey); - }); - - testWidgets("Verify structure", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(find.byType(AppBar), findsOneWidget); - expect(find.byType(TextFormField), findsNWidgets(2)); - expect(find.byType(IconButton), findsNWidgets(2)); - expect(find.byType(CheckboxListTile), findsNWidgets(1)); - expect(find.byType(ElevatedButton), findsOneWidget); - expect(find.byType(Text), findsNWidgets(8)); - - expect(find.text(AppLocalizations().login), findsOneWidget); - expect(find.text(AppLocalizations().email), findsOneWidget); - expect(find.text(AppLocalizations().enterYourEmail), findsOneWidget); - expect(find.text(AppLocalizations().password), findsOneWidget); - expect(find.text(AppLocalizations().enterYourPassword), findsOneWidget); - expect(find.text(AppLocalizations().rememberMe), findsOneWidget); - expect( - find.text(AppLocalizations().forgotPasswordWithQuestionMark), - findsOneWidget, - ); - expect(find.text(AppLocalizations().continueWord), findsOneWidget); - }); - - testWidgets("Verify app bar behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(find.byType(AppBar), findsOneWidget); - expect(appBarButton, findsOneWidget); - - //await tester.tap(appBarButton); - //await tester.pumpAndSettle(); - - //expect(find.text("Page Not Found"), findsOneWidget); - }); - - testWidgets("Verify login form fields behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(emailField, findsOneWidget); - expect(passwordField, findsOneWidget); - expect(continueButton, findsOneWidget); - - expect(find.text(AppLocalizations().enterValidEmail), findsNothing); - expect(find.text(AppLocalizations().passwordNotMatched), findsNothing); - - await tester.enterText(emailField, "test"); - await tester.enterText(passwordField, "test"); - await tester.pumpAndSettle(); - await tester.tap(continueButton); - await tester.pumpAndSettle(); - - expect(find.text(AppLocalizations().enterValidEmail), findsOneWidget); - expect(find.text(AppLocalizations().passwordNotMatched), findsOneWidget); - }); - - testWidgets("Verify password visibility button behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(passwordField, findsOneWidget); - expect(passwordVisibilityButton, findsOneWidget); - - expect( - tester - .getSemantics(passwordField) - .getSemanticsData() - .flagsCollection - .isObscured, - isTrue, - ); - await tester.tap(passwordVisibilityButton); - await tester.pumpAndSettle(); - expect( - tester - .getSemantics(passwordField) - .getSemanticsData() - .flagsCollection - .isObscured, - isFalse, - ); - }); - - testWidgets("Verify remember me checkbox behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(rememberMeCheckbox, findsOneWidget); - - expect( - tester - .getSemantics(rememberMeCheckbox) - .getSemanticsData() - .flagsCollection - .isChecked, - isFalse, - ); - await tester.tap(rememberMeCheckbox); - await tester.pumpAndSettle(); - expect( - tester - .getSemantics(rememberMeCheckbox) - .getSemanticsData() - .flagsCollection - .isChecked, - isTrue, - ); - }); - - testWidgets("Verify forgetPassword button behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(forgotPasswordButton, findsOneWidget); - - await tester.tap(forgotPasswordButton); - await tester.pumpAndSettle(); - - expect(find.text("Page Not Found"), findsOneWidget); - }); - - testWidgets("Verify continue button behaviour", (tester) async { - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(emailField, findsOneWidget); - expect(passwordField, findsOneWidget); - expect(continueButton, findsOneWidget); - - expect( - tester - .getSemantics(continueButton) - .getSemanticsData() - .flagsCollection - .isEnabled, - isFalse, - ); - - await tester.enterText(emailField, "test"); - await tester.enterText(passwordField, "test"); - await tester.pumpAndSettle(); - - expect( - tester - .getSemantics(continueButton) - .getSemanticsData() - .flagsCollection - .isEnabled, - isTrue, - ); - }); - - testWidgets("Verify success login behaviour", (tester) async { - final expectedRequestEntity = const LoginRequestEntity( - email: "test@gmail.com", - password: "test123654A#", - ); - final expectedResponseEntity = LoginDummyData().fakeLoginEntity; - final expectedResult = ApiSuccessResult( - expectedResponseEntity, - ); - provideDummy>(expectedResult); - when( - mockLoginUseCase(expectedRequestEntity), - ).thenAnswer((_) async => expectedResult); - - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(emailField, findsOneWidget); - expect(passwordField, findsOneWidget); - expect(continueButton, findsOneWidget); - - await tester.enterText(emailField, expectedRequestEntity.email); - await tester.enterText(passwordField, expectedRequestEntity.password); - await tester.pumpAndSettle(); - await tester.tap(continueButton); - await tester.pumpAndSettle(); - expect(find.text("Page Not Found"), findsOneWidget); - }); - - testWidgets("Verify error login behaviour", (tester) async { - final expectedRequestEntity = const LoginRequestEntity( - email: "test@gmail.com", - password: "test123654A#", - ); - final expectedResponse = "errorMessage"; - final expectedResult = ApiErrorResult(expectedResponse); - provideDummy>(expectedResult); - when( - mockLoginUseCase(expectedRequestEntity), - ).thenAnswer((_) async => expectedResult); - - await tester.pumpWidget(const TestAppWrapper(child: LoginScreen())); - - expect(emailField, findsOneWidget); - expect(passwordField, findsOneWidget); - expect(continueButton, findsOneWidget); - - await tester.enterText(emailField, expectedRequestEntity.email); - await tester.enterText(passwordField, expectedRequestEntity.password); - await tester.pumpAndSettle(); - await tester.tap(continueButton); - await tester.pumpAndSettle(); - - expect(find.byType(AlertDialog), findsOneWidget); - expect(find.text(expectedResponse), findsOneWidget); - }); - }); -} diff --git a/test/presentation/auth/login/view_model/login_view_model_test.dart b/test/presentation/auth/login/view_model/login_view_model_test.dart deleted file mode 100644 index b40fa08..0000000 --- a/test/presentation/auth/login/view_model/login_view_model_test.dart +++ /dev/null @@ -1,225 +0,0 @@ -import 'package:bloc_test/bloc_test.dart'; -import 'package:elevate_tracking_app/core/api_result/api_result.dart'; -import 'package:elevate_tracking_app/core/constants/const_keys.dart'; -import 'package:elevate_tracking_app/domain/entites/login_entity.dart'; -import 'package:elevate_tracking_app/domain/use_cases/login_use_case.dart'; -import 'package:elevate_tracking_app/presentation/auth/login/view_model/login_events.dart'; -import 'package:elevate_tracking_app/presentation/auth/login/view_model/login_view_model.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; - -import '../../../../dummy/login_dummy_data.dart'; -import 'login_view_model_test.mocks.dart'; - -@GenerateMocks([LoginUseCase, FlutterSecureStorage]) -void main() { - group("test LoginViewModel", () { - late MockLoginUseCase mockLoginUseCase; - late MockFlutterSecureStorage mockSecureStorage; - late LoginViewModel loginViewModel; - late LoginState state; - setUp(() { - mockLoginUseCase = MockLoginUseCase(); - mockSecureStorage = MockFlutterSecureStorage(); - loginViewModel = LoginViewModel(mockLoginUseCase, mockSecureStorage); - state = const LoginState(); - }); - - group("test RequestLoginEvent", () { - final loginRequestEntity = LoginDummyData().fakeLoginRequestEntity; - final expectedEntity = LoginDummyData().fakeLoginEntity; - final expectedResult = ApiSuccessResult(expectedEntity); - - provideDummy>(expectedResult); - - blocTest( - "call doIntent with RequestLoginEvent then load and succeeded", - build: () => loginViewModel, - act: (bloc) { - when( - mockLoginUseCase(loginRequestEntity), - ).thenAnswer((_) async => expectedResult); - bloc.emailController.text = loginRequestEntity.email; - bloc.passwordController.text = loginRequestEntity.password; - return bloc.doIntent(RequestLoginEvent()); - }, - expect: () => [ - state.copyWith(isLoading: true), - state.copyWith(isLoading: false, isLoggedIn: true), - ], - verify: (_) { - verify(mockLoginUseCase(loginRequestEntity)).called(1); - }, - ); - - final expectedError = "fake-error"; - final expectedErrorResult = ApiErrorResult(expectedError); - - provideDummy>(expectedErrorResult); - - blocTest( - "call doIntent with RequestLoginEvent then load and failed", - build: () => loginViewModel, - act: (bloc) { - when( - mockLoginUseCase(loginRequestEntity), - ).thenAnswer((_) async => expectedErrorResult); - bloc.emailController.text = loginRequestEntity.email; - bloc.passwordController.text = loginRequestEntity.password; - return bloc.doIntent(RequestLoginEvent()); - }, - expect: () => [ - state.copyWith(isLoading: true), - state.copyWith(isLoading: false, errorMessage: expectedError), - ], - verify: (_) { - verify(mockLoginUseCase(loginRequestEntity)).called(1); - }, - ); - }); - - group("test RememberMeEvent", () { - final expectedResult = true; - - blocTest( - "call doIntent with RememberMeEvent then should update isRememberMe and write to storage", - build: () => loginViewModel, - act: (bloc) { - bloc.isRememberMe.value = false; - return bloc.doIntent(RememberMeEvent()); - }, - expect: () => [], - verify: (viewModel) { - verify( - mockSecureStorage.write( - key: ConstKeys.keyRememberMe, - value: expectedResult.toString(), - ), - ).called(1); - expect(viewModel.isRememberMe.value, expectedResult); - }, - ); - }); - - group("test LoadSavedUserDataEvent", () { - final expectedEmail = "fake-email"; - final expectedPassword = "fake-password"; - final expectedResult = true; - - blocTest( - "call doIntent with LoadSavedUserDataEvent should load saved email and password if rememberMe is true", - build: () => loginViewModel, - act: (bloc) { - when( - mockSecureStorage.read(key: ConstKeys.keyRememberMe), - ).thenAnswer((_) async => expectedResult.toString()); - when( - mockSecureStorage.read(key: ConstKeys.kUserLogin), - ).thenAnswer((_) async => expectedEmail); - when( - mockSecureStorage.read(key: ConstKeys.kUserPassword), - ).thenAnswer((_) async => expectedPassword); - return bloc.doIntent(LoadSavedUserDataEvent()); - }, - expect: () => [], - verify: (viewModel) { - verify( - mockSecureStorage.read(key: ConstKeys.keyRememberMe), - ).called(1); - verify(mockSecureStorage.read(key: ConstKeys.kUserLogin)).called(1); - verify( - mockSecureStorage.read(key: ConstKeys.kUserPassword), - ).called(1); - expect(viewModel.isRememberMe.value, expectedResult); - expect(viewModel.emailController.text, expectedEmail); - expect(viewModel.passwordController.text, expectedPassword); - }, - ); - - final expectedFalseResult = false; - - blocTest( - "call doIntent with LoadSavedUserDataEvent should load saved email and password if rememberMe is false", - build: () => loginViewModel, - act: (bloc) { - when( - mockSecureStorage.read(key: ConstKeys.keyRememberMe), - ).thenAnswer((_) async => expectedFalseResult.toString()); - return bloc.doIntent(LoadSavedUserDataEvent()); - }, - expect: () => [], - verify: (viewModel) { - verify( - mockSecureStorage.read(key: ConstKeys.keyRememberMe), - ).called(1); - expect(viewModel.isRememberMe.value, expectedFalseResult); - }, - ); - }); - - group("test LoginButtonStatusEvent", () { - final expectedEmail = "fake-email"; - final expectedPassword = "fake-password"; - - blocTest( - "call doIntent with LoginButtonStatusEvent should return true if email and password is not empty", - build: () => loginViewModel, - act: (bloc) { - bloc.emailController.text = expectedEmail; - bloc.passwordController.text = expectedPassword; - return bloc.doIntent(LoginButtonStatusEvent()); - }, - expect: () => [], - verify: (viewModel) { - expect(viewModel.isButtonEnabled.value, isTrue); - }, - ); - - blocTest( - "call doIntent with LoginButtonStatusEvent should return false if email and password is empty", - build: () => loginViewModel, - act: (bloc) { - return bloc.doIntent(LoginButtonStatusEvent()); - }, - expect: () => [], - verify: (viewModel) { - expect(viewModel.isButtonEnabled.value, isFalse); - }, - ); - }); - - group("test TogglePasswordVisibilityEvent", () { - final expectedResult = false; - - blocTest( - "call doIntent with TogglePasswordVisibilityEvent should toggle isPasswordVisible state if false to true", - build: () => loginViewModel, - act: (bloc) { - bloc.isPasswordVisible.value = expectedResult; - return bloc.doIntent(TogglePasswordVisibilityEvent()); - }, - expect: () => [], - verify: (viewModel) { - expect(viewModel.isPasswordVisible.value, isTrue); - }, - ); - - final expectedTrueResult = true; - - blocTest( - "call doIntent with TogglePasswordVisibilityEvent should toggle isPasswordVisible state if true to false", - build: () => loginViewModel, - act: (bloc) { - bloc.isPasswordVisible.value = expectedTrueResult; - return bloc.doIntent(TogglePasswordVisibilityEvent()); - }, - expect: () => [], - verify: (viewModel) { - expect(viewModel.isPasswordVisible.value, isFalse); - }, - ); - }); - }); -} diff --git a/test/presentation/onboarding/views/screen/onboarding_screen_test.dart b/test/presentation/onboarding/views/screen/onboarding_screen_test.dart deleted file mode 100644 index ae92056..0000000 --- a/test/presentation/onboarding/views/screen/onboarding_screen_test.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:elevate_tracking_app/core/constants/app_images.dart'; -import 'package:elevate_tracking_app/core/constants/widgets_keys.dart'; -import 'package:elevate_tracking_app/core/custom_widget/test_app_wrapper.dart'; -import 'package:elevate_tracking_app/core/di/di.dart'; -import 'package:elevate_tracking_app/presentation/onboarding/views/screen/onboarding_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group("Onboarding screen Widget Test", () { - setUpAll(() { - configureDependencies(); - }); - testWidgets('Verify Structure', (WidgetTester tester) async { - //Arrange - await tester.pumpWidget(const TestAppWrapper(child: OnboardingScreen())); - //Assert - expect(find.byType(ElevatedButton), findsNWidgets(2)); - expect(find.byType(Text), findsNWidgets(3)); - expect(find.text("Login"), findsOneWidget); - expect(find.text("Apply now"), findsOneWidget); - expect(find.text("Welcome to \nFlowery rider app"), findsOneWidget); - expect(find.widgetWithText(ElevatedButton, "Login"), findsOneWidget); - expect(find.widgetWithText(ElevatedButton, "Apply now"), findsOneWidget); - expect( - find.byWidgetPredicate( - (widget) => - widget is Center && - widget.child is Container && - (widget.child as Container).decoration is BoxDecoration && - ((widget.child as Container).decoration as BoxDecoration).image != - null && - ((widget.child as Container).decoration as BoxDecoration) - .image! - .image - is AssetImage && - (((widget.child as Container).decoration as BoxDecoration) - .image! - .image - as AssetImage) - .assetName == - AppImages.imageOnboarding, - ), - findsOneWidget, - ); - }); - testWidgets("Verify login elevatedButton behaviour", ( - WidgetTester tester, - ) async { - //Arrange - await tester.pumpWidget(const TestAppWrapper(child: OnboardingScreen())); - final loginButtonFinder = find.byKey( - const Key(WidgetsKeys.kOnboardingScreenLoginButton), - ); - expect(loginButtonFinder, findsOneWidget); - //Act - await tester.tap(loginButtonFinder); - await tester.pumpAndSettle(); - //Assert - expect(find.text("Login"), findsOneWidget); - }); - - testWidgets("Verify Apply now elevatedButton behaviour", ( - WidgetTester tester, - ) async { - //Arrange - await tester.pumpWidget(const TestAppWrapper(child: OnboardingScreen())); - final applyButtonFinder = find.byKey( - const Key(WidgetsKeys.kOnboardingScreenApplyNowButton), - ); - expect(applyButtonFinder, findsOneWidget); - //Act - await tester.tap(applyButtonFinder); - await tester.pumpAndSettle(); - //Assert - expect(find.text("Apply"), findsOneWidget); - }); - }); -}