From 30f239160a528e60e9410a26a0e3b903bd409f6a Mon Sep 17 00:00:00 2001 From: nmo86 Date: Thu, 2 Oct 2025 13:22:41 +0300 Subject: [PATCH 01/11] add(features)changePassword --- lib/api/mapper/change_password_mapper.dart | 0 lib/api/models/requests/change_password_request_dto.dart | 0 lib/api/models/responses/change_password_response_dto.dart | 0 lib/domain/entites/requests/change_password_request_entity.dart | 0 lib/domain/entites/response/change_password_response_entity.dart | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/api/mapper/change_password_mapper.dart create mode 100644 lib/api/models/requests/change_password_request_dto.dart create mode 100644 lib/api/models/responses/change_password_response_dto.dart create mode 100644 lib/domain/entites/requests/change_password_request_entity.dart create mode 100644 lib/domain/entites/response/change_password_response_entity.dart diff --git a/lib/api/mapper/change_password_mapper.dart b/lib/api/mapper/change_password_mapper.dart new file mode 100644 index 0000000..e69de29 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..e69de29 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..e69de29 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..e69de29 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..e69de29 From 8557daa0dda53f98c4883d4d8bb4bacb38c501d1 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Thu, 2 Oct 2025 13:22:53 +0300 Subject: [PATCH 02/11] add(features)changePassword --- lib/api/client/api_client.dart | 10 +++++++ .../auth_remote_data_source_impl.dart | 14 ++++++++++ lib/api/mapper/change_password_mapper.dart | 24 +++++++++++++++++ .../requests/change_password_request_dto.dart | 26 +++++++++++++++++++ .../change_password_response_dto.dart | 26 +++++++++++++++++++ lib/core/constants/end_points.dart | 1 + .../data_source/auth_remote_data_source.dart | 7 +++++ lib/data/repo/auth_repo_impl.dart | 11 ++++++++ .../change_password_request_entity.dart | 15 +++++++++++ .../change_password_response_entity.dart | 15 +++++++++++ lib/domain/repo/auth_repo.dart | 7 +++++ 11 files changed, 156 insertions(+) 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..d353cd7 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'; @@ -7,6 +8,9 @@ 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 { final ApiClient _apiClient; @@ -20,4 +24,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 index e69de29..3a61c82 100644 --- a/lib/api/mapper/change_password_mapper.dart +++ b/lib/api/mapper/change_password_mapper.dart @@ -0,0 +1,24 @@ +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, + + ); + } +} \ 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 index e69de29..662cf44 100644 --- a/lib/api/models/requests/change_password_request_dto.dart +++ 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 index e69de29..69b083f 100644 --- a/lib/api/models/responses/change_password_response_dto.dart +++ 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/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 index e69de29..d9e041f 100644 --- a/lib/domain/entites/requests/change_password_request_entity.dart +++ 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 index e69de29..ea0c203 100644 --- a/lib/domain/entites/response/change_password_response_entity.dart +++ 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, + ); } From fdb98a9f3ad7f2d811039c544252c1d5660e6b96 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Thu, 2 Oct 2025 13:24:02 +0300 Subject: [PATCH 03/11] add(features)changePassword --- lib/api/data_source/auth_remote_data_source_impl.dart | 1 - 1 file changed, 1 deletion(-) 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 d353cd7..e9759fb 100644 --- a/lib/api/data_source/auth_remote_data_source_impl.dart +++ b/lib/api/data_source/auth_remote_data_source_impl.dart @@ -7,7 +7,6 @@ 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'; From bc4c463c0a87734f6b7be13fecd96617cc6e3f9c Mon Sep 17 00:00:00 2001 From: nmo86 Date: Sat, 4 Oct 2025 02:11:42 +0300 Subject: [PATCH 04/11] add(features)changePassword --- lib/domain/use_cases/change_password_use_case.dart | 0 .../auth/change_password/view_model/change_password_event.dart | 0 .../auth/change_password/view_model/change_password_states.dart | 0 .../change_password/view_model/change_password_view_model.dart | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/domain/use_cases/change_password_use_case.dart create mode 100644 lib/presentation/auth/change_password/view_model/change_password_event.dart create mode 100644 lib/presentation/auth/change_password/view_model/change_password_states.dart create mode 100644 lib/presentation/auth/change_password/view_model/change_password_view_model.dart 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..e69de29 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..e69de29 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..e69de29 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..e69de29 From c7d2d31d4efc37f189ad0d937d91daecc78da6be Mon Sep 17 00:00:00 2001 From: nmo86 Date: Sat, 4 Oct 2025 02:23:13 +0300 Subject: [PATCH 05/11] add(features)changePassword --- lib/core/router/app_router.dart | 7 +- .../use_cases/change_password_use_case.dart | 18 +++++ lib/l10n/intl_en.arb | 8 +- .../view_model/change_password_event.dart | 3 + .../view_model/change_password_states.dart | 46 +++++++++++ .../change_password_view_model.dart | 79 +++++++++++++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 592e649..c276767 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'; @@ -38,12 +39,16 @@ abstract class AppRouter { path: RouteNames.profile, 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.changePassword : RouteNames.changePassword, routes: routes, ); } diff --git a/lib/domain/use_cases/change_password_use_case.dart b/lib/domain/use_cases/change_password_use_case.dart index e69de29..297fccc 100644 --- a/lib/domain/use_cases/change_password_use_case.dart +++ 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_model/change_password_event.dart b/lib/presentation/auth/change_password/view_model/change_password_event.dart index e69de29..c5acd52 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_event.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_event.dart @@ -0,0 +1,3 @@ +sealed class ChangePasswordEvents {} + +class ChangePasswordEvent 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 index e69de29..ef67e82 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_states.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_states.dart @@ -0,0 +1,46 @@ +import '../../../../domain/entites/response/change_password_response_entity.dart'; +import 'package:equatable/equatable.dart'; + +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, + ]; +} 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 index e69de29..696a6ca 100644 --- 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 @@ -0,0 +1,79 @@ +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(); + } + } + + void checkIfAllFieldsFilled() { + final isFilled = + currentPasswordController.text.isNotEmpty && + newPasswordController.text.isNotEmpty && + confirmPasswordController.text.isNotEmpty&& + newPasswordController.text == confirmPasswordController.text; + + emit(state.copyWith(isButtonEnabled: isFilled)); + } + + Future _changePassword() async { + emit(state.copyWith(isLoading: true)); + 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), + ); + } + } + + +@override +Future close() { + currentPasswordController.dispose(); + newPasswordController.dispose(); + return super.close(); +} +} \ No newline at end of file From ff36ee1d8e81bb1c78dd8ba3ab2433bf2a7c61a7 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Sat, 4 Oct 2025 02:23:13 +0300 Subject: [PATCH 06/11] add(features)changePassword --- lib/core/router/app_router.dart | 7 +- .../use_cases/change_password_use_case.dart | 18 +++ lib/l10n/intl_en.arb | 8 +- .../view/change_password_view.dart | 148 ++++++++++++++++++ .../view_model/change_password_event.dart | 3 + .../view_model/change_password_states.dart | 46 ++++++ .../change_password_view_model.dart | 79 ++++++++++ 7 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 lib/presentation/auth/change_password/view/change_password_view.dart diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 592e649..c276767 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'; @@ -38,12 +39,16 @@ abstract class AppRouter { path: RouteNames.profile, 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.changePassword : RouteNames.changePassword, routes: routes, ); } diff --git a/lib/domain/use_cases/change_password_use_case.dart b/lib/domain/use_cases/change_password_use_case.dart index e69de29..297fccc 100644 --- a/lib/domain/use_cases/change_password_use_case.dart +++ 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..04701e5 --- /dev/null +++ b/lib/presentation/auth/change_password/view/change_password_view.dart @@ -0,0 +1,148 @@ +import 'package:elevate_tracking_app/core/constants/app_colors.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, + + listener: (context, state) { + if (isDialogShow == true){ + context.pop(); + isDialogShow = false; + } + if (state.isLoading) { + isDialogShow=true; + CustomDialog.loading(context: context); + } + else if(state.errorMessage!=null ) { + CustomDialog.positiveButton( + context: context, + title: AppLocalizations.of(context).error, + message: state.errorMessage, + ); + } + else if(state.isSuccess){ + 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 + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + 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 + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + 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 + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + 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( + onPressed: state.isButtonEnabled + ? () => _changePasswordViewModel.doIntent(ChangePasswordEvent()) + : null, + style: ElevatedButton.styleFrom( + + ), + child: Text(AppLocalizations.of(context).confirm,style: TextStyle(color: AppColors.white),), + ) + ], + ), + ); + }, + ), + ), + ); + } +} \ No newline at end of file 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 index e69de29..c5acd52 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_event.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_event.dart @@ -0,0 +1,3 @@ +sealed class ChangePasswordEvents {} + +class ChangePasswordEvent 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 index e69de29..ef67e82 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_states.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_states.dart @@ -0,0 +1,46 @@ +import '../../../../domain/entites/response/change_password_response_entity.dart'; +import 'package:equatable/equatable.dart'; + +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, + ]; +} 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 index e69de29..696a6ca 100644 --- 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 @@ -0,0 +1,79 @@ +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(); + } + } + + void checkIfAllFieldsFilled() { + final isFilled = + currentPasswordController.text.isNotEmpty && + newPasswordController.text.isNotEmpty && + confirmPasswordController.text.isNotEmpty&& + newPasswordController.text == confirmPasswordController.text; + + emit(state.copyWith(isButtonEnabled: isFilled)); + } + + Future _changePassword() async { + emit(state.copyWith(isLoading: true)); + 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), + ); + } + } + + +@override +Future close() { + currentPasswordController.dispose(); + newPasswordController.dispose(); + return super.close(); +} +} \ No newline at end of file From 71bbf0014becf23ca0aaf328d9bd77b0240546cf Mon Sep 17 00:00:00 2001 From: youssefmdev22 Date: Sat, 4 Oct 2025 12:40:45 +0300 Subject: [PATCH 07/11] update change password --- .../view/change_password_view.dart | 85 +++++++++++-------- .../view_model/change_password_event.dart | 3 +- .../change_password_view_model.dart | 38 ++++----- 3 files changed, 68 insertions(+), 58 deletions(-) diff --git a/lib/presentation/auth/change_password/view/change_password_view.dart b/lib/presentation/auth/change_password/view/change_password_view.dart index 04701e5..f42d659 100644 --- a/lib/presentation/auth/change_password/view/change_password_view.dart +++ b/lib/presentation/auth/change_password/view/change_password_view.dart @@ -13,7 +13,6 @@ import '../../../../core/utils/validations.dart'; import '../../../../generated/l10n.dart'; import '../view_model/change_password_states.dart'; - class ChangePassword extends StatefulWidget { const ChangePassword({super.key}); @@ -30,6 +29,7 @@ class _ChangePasswordState extends State { super.initState(); _changePasswordViewModel = getIt(); } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -42,39 +42,38 @@ class _ChangePasswordState extends State { icon: const Icon(Icons.arrow_back_ios), onPressed: () {}, ), - Text(AppLocalizations.of(context).resetPassword), + Text(AppLocalizations.of(context).resetPassword), ], ), ), - body: Padding( + body: Padding( padding: EdgeInsets.symmetric(horizontal: 0.05 * width), - child: BlocConsumer( + child: BlocConsumer( bloc: _changePasswordViewModel, listener: (context, state) { - if (isDialogShow == true){ + if (isDialogShow == true) { context.pop(); isDialogShow = false; } if (state.isLoading) { - isDialogShow=true; + isDialogShow = true; CustomDialog.loading(context: context); - } - else if(state.errorMessage!=null ) { + } else if (state.errorMessage != null) { CustomDialog.positiveButton( context: context, title: AppLocalizations.of(context).error, message: state.errorMessage, ); - } - else if(state.isSuccess){ + } else if (state.isSuccess) { CustomDialog.positiveButton( context: context, title: AppLocalizations.of(context).success, message: state.changePasswordResponseEntity?.message, cancelable: false, - positiveOnClick: (){ - context.go(RouteNames.resetPassword);}, + positiveOnClick: () { + context.go(RouteNames.resetPassword); + }, ); } }, @@ -82,20 +81,22 @@ class _ChangePasswordState extends State { builder: (BuildContext context, state) { return Form( key: _changePasswordViewModel.changePasswordFormKey, - child: - Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 16.h, children: [ SizedBox(height: 32.h), TextFormField( //current pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), obscureText: true, - controller: _changePasswordViewModel.currentPasswordController, + controller: + _changePasswordViewModel.currentPasswordController, style: Theme.of(context).textTheme.bodySmall, validator: Validations.validatePassword, - decoration: InputDecoration( + decoration: InputDecoration( labelText: AppLocalizations.of(context).currentPassword, hintText: AppLocalizations.of(context).currentPassword, ), @@ -103,40 +104,52 @@ class _ChangePasswordState extends State { SizedBox(height: 16.h), TextFormField( //new pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), obscureText: true, controller: _changePasswordViewModel.newPasswordController, style: Theme.of(context).textTheme.bodySmall, validator: Validations.validatePassword, - decoration: InputDecoration( + decoration: InputDecoration( labelText: AppLocalizations.of(context).newPassword, hintText: AppLocalizations.of(context).newPassword, ), ), SizedBox(height: 8.h), TextFormField( - //confirm pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + //confirm pass + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), obscureText: true, - controller: _changePasswordViewModel.confirmPasswordController, + 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, + 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( - onPressed: state.isButtonEnabled - ? () => _changePasswordViewModel.doIntent(ChangePasswordEvent()) - : null, - style: ElevatedButton.styleFrom( - - ), - child: Text(AppLocalizations.of(context).confirm,style: TextStyle(color: AppColors.white),), - ) + ElevatedButton( + onPressed: state.isButtonEnabled + ? () => _changePasswordViewModel.doIntent( + ChangePasswordEvent(), + ) + : null, + style: ElevatedButton.styleFrom(), + child: Text( + AppLocalizations.of(context).confirm, + style: TextStyle(color: AppColors.white), + ), + ), ], ), ); @@ -145,4 +158,4 @@ class _ChangePasswordState extends State { ), ); } -} \ No newline at end of file +} 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 index c5acd52..3cc152c 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_event.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_event.dart @@ -1,3 +1,4 @@ sealed class ChangePasswordEvents {} -class ChangePasswordEvent extends ChangePasswordEvents {} \ No newline at end of file +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_view_model.dart b/lib/presentation/auth/change_password/view_model/change_password_view_model.dart index 696a6ca..b596a0b 100644 --- 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 @@ -2,44 +2,41 @@ import 'package:elevate_tracking_app/presentation/auth/change_password/view_mode 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()); + ChangePasswordViewModel(this._changePasswordUseCase) + : super(const ChangePasswordStates()); final GlobalKey changePasswordFormKey = GlobalKey(); - final TextEditingController currentPasswordController = TextEditingController(); + final TextEditingController currentPasswordController = + TextEditingController(); final TextEditingController newPasswordController = TextEditingController(); - final TextEditingController confirmPasswordController = TextEditingController(); - - - + final TextEditingController confirmPasswordController = + TextEditingController(); void doIntent(ChangePasswordEvents events) { switch (events) { case ChangePasswordEvent(): _changePassword(); + case CheckAllFieldsEvent(): + _checkIfAllFieldsFilled(); } } - void checkIfAllFieldsFilled() { + void _checkIfAllFieldsFilled() { final isFilled = currentPasswordController.text.isNotEmpty && newPasswordController.text.isNotEmpty && - confirmPasswordController.text.isNotEmpty&& + confirmPasswordController.text.isNotEmpty && newPasswordController.text == confirmPasswordController.text; emit(state.copyWith(isButtonEnabled: isFilled)); @@ -69,11 +66,10 @@ class ChangePasswordViewModel extends Cubit { } } - -@override -Future close() { - currentPasswordController.dispose(); - newPasswordController.dispose(); - return super.close(); + @override + Future close() { + currentPasswordController.dispose(); + newPasswordController.dispose(); + return super.close(); + } } -} \ No newline at end of file From 3350b17ca67867af4a4eb2dc0d098a7579f37007 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Thu, 16 Oct 2025 21:32:53 +0300 Subject: [PATCH 08/11] Merge remote-tracking branch 'origin/feature/ECOM-46/reset-password' into feature/ECOM-46/reset-password --- test/dummy/change_password_dummy_data.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/dummy/change_password_dummy_data.dart diff --git a/test/dummy/change_password_dummy_data.dart b/test/dummy/change_password_dummy_data.dart new file mode 100644 index 0000000..e69de29 From 43c66a432cff9a5e57f75aeb939ce8000300f9d7 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Thu, 16 Oct 2025 21:34:11 +0300 Subject: [PATCH 09/11] Merge remote-tracking branch 'origin/feature/ECOM-46/reset-password' into feature/ECOM-46/reset-password --- lib/api/mapper/change_password_mapper.dart | 9 ++ lib/core/constants/widgets_keys.dart | 9 ++ lib/core/router/app_router.dart | 3 +- .../view/change_password_view.dart | 85 +++++------ .../auth_remote_data_source_impl_test.dart | 32 ++++ .../mapper/change_password_mapper_test.dart | 27 ++++ test/data/repo/auth_repo_impl_test.dart | 41 ++++- .../change_password_use_case_test.dart | 64 ++++++++ .../domain/use_cases/login_use_case_test.dart | 13 +- test/dummy/change_password_dummy_data.dart | 21 +++ test/dummy/login_dummy_data.dart | 3 + .../view/change_password_view_test.dart | 143 ++++++++++++++++++ .../change_password_view_model_test.dart | 115 ++++++++++++++ 13 files changed, 508 insertions(+), 57 deletions(-) create mode 100644 test/api/mapper/change_password_mapper_test.dart create mode 100644 test/domain/use_cases/change_password_use_case_test.dart create mode 100644 test/presentation/auth/change_password/view/change_password_view_test.dart create mode 100644 test/presentation/auth/change_password/view_model/change_password_view_model_test.dart diff --git a/lib/api/mapper/change_password_mapper.dart b/lib/api/mapper/change_password_mapper.dart index 3a61c82..43e7ad4 100644 --- a/lib/api/mapper/change_password_mapper.dart +++ b/lib/api/mapper/change_password_mapper.dart @@ -21,4 +21,13 @@ extension ChangePasswordRequestMapper on ChangePasswordRequestEntity{ ); } +} + +extension ChangePasswordResponseEntityMapper on ChangePasswordResponseEntity { + ChangePasswordResponseDto toDto() { + return ChangePasswordResponseDto( + message: message, + token: token, + ); + } } \ No newline at end of file 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 c276767..82b9673 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -39,6 +39,7 @@ abstract class AppRouter { path: RouteNames.profile, builder: (context, state) => const ProfileScreen(), ), + GoRoute( path: RouteNames.changePassword, builder: (context, state) => const ChangePassword(), @@ -48,7 +49,7 @@ abstract class AppRouter { static GoRouter router(String? token) { return GoRouter( - initialLocation: token == null ? RouteNames.changePassword : RouteNames.changePassword, + initialLocation: token == null ? RouteNames.changePassword: RouteNames.changePassword, routes: routes, ); } diff --git a/lib/presentation/auth/change_password/view/change_password_view.dart b/lib/presentation/auth/change_password/view/change_password_view.dart index f42d659..04701e5 100644 --- a/lib/presentation/auth/change_password/view/change_password_view.dart +++ b/lib/presentation/auth/change_password/view/change_password_view.dart @@ -13,6 +13,7 @@ import '../../../../core/utils/validations.dart'; import '../../../../generated/l10n.dart'; import '../view_model/change_password_states.dart'; + class ChangePassword extends StatefulWidget { const ChangePassword({super.key}); @@ -29,7 +30,6 @@ class _ChangePasswordState extends State { super.initState(); _changePasswordViewModel = getIt(); } - @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -42,38 +42,39 @@ class _ChangePasswordState extends State { icon: const Icon(Icons.arrow_back_ios), onPressed: () {}, ), - Text(AppLocalizations.of(context).resetPassword), + Text(AppLocalizations.of(context).resetPassword), ], ), ), - body: Padding( + body: Padding( padding: EdgeInsets.symmetric(horizontal: 0.05 * width), - child: BlocConsumer( + child: BlocConsumer( bloc: _changePasswordViewModel, listener: (context, state) { - if (isDialogShow == true) { + if (isDialogShow == true){ context.pop(); isDialogShow = false; } if (state.isLoading) { - isDialogShow = true; + isDialogShow=true; CustomDialog.loading(context: context); - } else if (state.errorMessage != null) { + } + else if(state.errorMessage!=null ) { CustomDialog.positiveButton( context: context, title: AppLocalizations.of(context).error, message: state.errorMessage, ); - } else if (state.isSuccess) { + } + else if(state.isSuccess){ CustomDialog.positiveButton( context: context, title: AppLocalizations.of(context).success, message: state.changePasswordResponseEntity?.message, cancelable: false, - positiveOnClick: () { - context.go(RouteNames.resetPassword); - }, + positiveOnClick: (){ + context.go(RouteNames.resetPassword);}, ); } }, @@ -81,22 +82,20 @@ class _ChangePasswordState extends State { builder: (BuildContext context, state) { return Form( key: _changePasswordViewModel.changePasswordFormKey, - child: Column( + child: + Column( crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 16.h, children: [ SizedBox(height: 32.h), TextFormField( //current pass - onChanged: (_) => _changePasswordViewModel.doIntent( - CheckAllFieldsEvent(), - ), + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), obscureText: true, - controller: - _changePasswordViewModel.currentPasswordController, + controller: _changePasswordViewModel.currentPasswordController, style: Theme.of(context).textTheme.bodySmall, validator: Validations.validatePassword, - decoration: InputDecoration( + decoration: InputDecoration( labelText: AppLocalizations.of(context).currentPassword, hintText: AppLocalizations.of(context).currentPassword, ), @@ -104,52 +103,40 @@ class _ChangePasswordState extends State { SizedBox(height: 16.h), TextFormField( //new pass - onChanged: (_) => _changePasswordViewModel.doIntent( - CheckAllFieldsEvent(), - ), + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), obscureText: true, controller: _changePasswordViewModel.newPasswordController, style: Theme.of(context).textTheme.bodySmall, validator: Validations.validatePassword, - decoration: InputDecoration( + decoration: InputDecoration( labelText: AppLocalizations.of(context).newPassword, hintText: AppLocalizations.of(context).newPassword, ), ), SizedBox(height: 8.h), TextFormField( - //confirm pass - onChanged: (_) => _changePasswordViewModel.doIntent( - CheckAllFieldsEvent(), - ), + //confirm pass + onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), obscureText: true, - controller: - _changePasswordViewModel.confirmPasswordController, + 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, + 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( - onPressed: state.isButtonEnabled - ? () => _changePasswordViewModel.doIntent( - ChangePasswordEvent(), - ) - : null, - style: ElevatedButton.styleFrom(), - child: Text( - AppLocalizations.of(context).confirm, - style: TextStyle(color: AppColors.white), - ), - ), + ElevatedButton( + onPressed: state.isButtonEnabled + ? () => _changePasswordViewModel.doIntent(ChangePasswordEvent()) + : null, + style: ElevatedButton.styleFrom( + + ), + child: Text(AppLocalizations.of(context).confirm,style: TextStyle(color: AppColors.white),), + ) ], ), ); @@ -158,4 +145,4 @@ class _ChangePasswordState extends State { ), ); } -} +} \ No newline at end of file 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..510d7bf 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 @@ -1,12 +1,16 @@ import 'package:elevate_tracking_app/api/client/api_client.dart'; import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.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/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_remote_data_source_impl_test.mocks.dart'; @@ -54,7 +58,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 index e69de29..aa67bc7 100644 --- a/test/dummy/change_password_dummy_data.dart +++ 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..0b45eee 100644 --- a/test/dummy/login_dummy_data.dart +++ b/test/dummy/login_dummy_data.dart @@ -2,7 +2,9 @@ import 'package:elevate_tracking_app/api/mapper/login_mapper.dart'; import 'package:elevate_tracking_app/api/models/requests/login_request.dart'; import 'package:elevate_tracking_app/api/models/responses/login_response.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/requests/login_request_entity.dart'; +import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; class LoginDummyData { LoginRequestEntity get fakeLoginRequestEntity => @@ -15,3 +17,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..2fcef5b --- /dev/null +++ b/test/presentation/auth/change_password/view/change_password_view_test.dart @@ -0,0 +1,143 @@ +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:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_view_model.dart'; +import 'package:flutter/cupertino.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())); + + final viewModel = GetIt.instance(); + final formKey = viewModel.changePasswordFormKey; + + 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(); + + final form = find.byType(Form).evaluate().first.widget as Form; + final isValid = formKey.currentState?.validate() ?? false; + expect(isValid, false); + + }); + 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())); + + final viewModel = GetIt.instance(); + final formKey = viewModel.changePasswordFormKey; + + 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())); + + final viewModel = GetIt.instance(); + final formKey = viewModel.changePasswordFormKey; + + 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); + + // expect(find.text(AppLocalizations().passwordNotMatched), findsOneWidget); + // expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + + + + }); + + + + + }); +} \ No newline at end of file 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..2e71084 --- /dev/null +++ b/test/presentation/auth/change_password/view_model/change_password_view_model_test.dart @@ -0,0 +1,115 @@ +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 = ChangePasswordStates(); + }); + final changePasswordEntity = ChangePasswordDummyData().fakeChangePasswordResponseEntity ; + final changePasswordRequestEntity = ChangePasswordDummyData().fakeChangePasswordRequestEntity ; + final expectedResult= ApiSuccessResult(changePasswordEntity); + + provideDummy >(expectedResult); + + blocTest("when emit success state should call change password with valid data ", + + 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(isLoading: true), + changePasswordStates.copyWith(isLoading: false,changePasswordResponseEntity: changePasswordEntity, + isButtonEnabled: false,isSuccess: true,errorMessage:null + ) + ] + , + verify: (_) { + verify(mockChangePasswordUseCase(any)).called(1); + }, + + + ); + final expectedError = "fake_error" ; + final expectedErrorResult= ApiErrorResult(expectedError); + + provideDummy>(expectedErrorResult); + + blocTest("when emit error state it should call change password fails ", + 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(isLoading: true), + changePasswordStates.copyWith(isLoading: false,changePasswordResponseEntity: null, + isButtonEnabled: false,isSuccess: false,errorMessage:expectedError, + + ) + ], + verify: (_){ + verify(mockChangePasswordUseCase(any)).called(1); + } + + + ); + blocTest( + "should emit isButtonEnabled true when all fields are filled and passwords match", + build: () => changePasswordViewModel, + act: (bloc) { + bloc.currentPasswordController.text = "old123"; + bloc.newPasswordController.text = "new123"; + bloc.confirmPasswordController.text = "new123"; + bloc.checkIfAllFieldsFilled(); + }, + expect: () => [ + changePasswordStates.copyWith(isButtonEnabled: true), + ], + ); + + blocTest( + "should emit isButtonEnabled false when fields are empty or passwords do not match", + build: () => changePasswordViewModel, + act: (bloc) { + bloc.currentPasswordController.text = "old123"; + bloc.newPasswordController.text = "new123"; + bloc.confirmPasswordController.text = "different"; + bloc.checkIfAllFieldsFilled(); + }, + expect: () => [ + changePasswordStates.copyWith(isButtonEnabled: false), + ], + ); + + + }); +} \ No newline at end of file From 4c3dc094f172b00a56171f04d8376eca067ef331 Mon Sep 17 00:00:00 2001 From: nmo86 Date: Fri, 17 Oct 2025 04:17:48 +0300 Subject: [PATCH 10/11] Merge remote-tracking branch 'origin/feature/ECOM-46/reset-password' into feature/ECOM-46/reset-password --- lib/core/router/app_router.dart | 2 +- .../view/change_password_view.dart | 101 ++++++++++------ .../view_model/change_password_states.dart | 77 +++++++++--- .../change_password_view_model.dart | 43 ++++--- .../view/change_password_view_test.dart | 18 +-- .../change_password_view_model_test.dart | 114 +++++++++--------- 6 files changed, 211 insertions(+), 144 deletions(-) diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 82b9673..8b77aed 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -49,7 +49,7 @@ abstract class AppRouter { static GoRouter router(String? token) { return GoRouter( - initialLocation: token == null ? RouteNames.changePassword: RouteNames.changePassword, + initialLocation: token == null ? RouteNames.login: RouteNames.login, routes: routes, ); } diff --git a/lib/presentation/auth/change_password/view/change_password_view.dart b/lib/presentation/auth/change_password/view/change_password_view.dart index 04701e5..dd407fe 100644 --- a/lib/presentation/auth/change_password/view/change_password_view.dart +++ b/lib/presentation/auth/change_password/view/change_password_view.dart @@ -1,4 +1,5 @@ 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'; @@ -13,7 +14,6 @@ import '../../../../core/utils/validations.dart'; import '../../../../generated/l10n.dart'; import '../view_model/change_password_states.dart'; - class ChangePassword extends StatefulWidget { const ChangePassword({super.key}); @@ -30,6 +30,7 @@ class _ChangePasswordState extends State { super.initState(); _changePasswordViewModel = getIt(); } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; @@ -42,39 +43,45 @@ class _ChangePasswordState extends State { icon: const Icon(Icons.arrow_back_ios), onPressed: () {}, ), - Text(AppLocalizations.of(context).resetPassword), + Text(AppLocalizations.of(context).resetPassword), ], ), ), - body: Padding( + body: Padding( padding: EdgeInsets.symmetric(horizontal: 0.05 * width), - child: BlocConsumer( + child: BlocConsumer( bloc: _changePasswordViewModel, - + listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { - if (isDialogShow == true){ + if (state.status == ChangePasswordStatus.loading) { + if (!isDialogShow) { + isDialogShow = true; + CustomDialog.loading(context: context); + } + return; + } + if (isDialogShow) { context.pop(); isDialogShow = false; } - if (state.isLoading) { - isDialogShow=true; - CustomDialog.loading(context: context); - } - else if(state.errorMessage!=null ) { + + if (state.status == ChangePasswordStatus.error) { CustomDialog.positiveButton( context: context, title: AppLocalizations.of(context).error, message: state.errorMessage, ); + return; } - else if(state.isSuccess){ + 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);}, + positiveOnClick: () { + context.go(RouteNames.resetPassword); + }, ); } }, @@ -82,20 +89,23 @@ class _ChangePasswordState extends State { builder: (BuildContext context, state) { return Form( key: _changePasswordViewModel.changePasswordFormKey, - child: - Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 16.h, children: [ SizedBox(height: 32.h), TextFormField( //current pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + key: const Key(WidgetsKeys.kChanePasswordPassword), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), obscureText: true, - controller: _changePasswordViewModel.currentPasswordController, + controller: + _changePasswordViewModel.currentPasswordController, style: Theme.of(context).textTheme.bodySmall, validator: Validations.validatePassword, - decoration: InputDecoration( + decoration: InputDecoration( labelText: AppLocalizations.of(context).currentPassword, hintText: AppLocalizations.of(context).currentPassword, ), @@ -103,40 +113,57 @@ class _ChangePasswordState extends State { SizedBox(height: 16.h), TextFormField( //new pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + 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( + decoration: InputDecoration( labelText: AppLocalizations.of(context).newPassword, hintText: AppLocalizations.of(context).newPassword, ), ), SizedBox(height: 8.h), TextFormField( - //confirm pass - onChanged:(_)=> _changePasswordViewModel.checkIfAllFieldsFilled(), + //confirm pass + key: const Key(WidgetsKeys.kChanePasswordConfirmPassword), + onChanged: (_) => _changePasswordViewModel.doIntent( + CheckAllFieldsEvent(), + ), obscureText: true, - controller: _changePasswordViewModel.confirmPasswordController, + 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, + 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( - onPressed: state.isButtonEnabled - ? () => _changePasswordViewModel.doIntent(ChangePasswordEvent()) - : null, - style: ElevatedButton.styleFrom( - ), - child: Text(AppLocalizations.of(context).confirm,style: TextStyle(color: AppColors.white),), - ) + 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), + ), + ), ], ), ); @@ -145,4 +172,4 @@ class _ChangePasswordState extends State { ), ); } -} \ 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 index ef67e82..20da86c 100644 --- a/lib/presentation/auth/change_password/view_model/change_password_states.dart +++ b/lib/presentation/auth/change_password/view_model/change_password_states.dart @@ -1,46 +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 bool isLoading; + final ChangePasswordStatus status; final ChangePasswordResponseEntity? changePasswordResponseEntity; final String? errorMessage; - final bool isSuccess; final bool isButtonEnabled; const ChangePasswordStates({ - this.isLoading = false, - this.changePasswordResponseEntity, + this.status = ChangePasswordStatus.initial, + this.changePasswordResponseEntity, this.errorMessage, - this.isSuccess = false, this.isButtonEnabled = false, - }); ChangePasswordStates copyWith({ - bool? isLoading, + ChangePasswordStatus? status, 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, + status: status ?? this.status, + changePasswordResponseEntity: + changePasswordResponseEntity ?? this.changePasswordResponseEntity, + errorMessage: errorMessage, isButtonEnabled: isButtonEnabled ?? this.isButtonEnabled, ); } @override List get props => [ - isLoading, + status, changePasswordResponseEntity, errorMessage, - isSuccess, 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 index b596a0b..0ec0eb2 100644 --- 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 @@ -39,11 +39,16 @@ class ChangePasswordViewModel extends Cubit { confirmPasswordController.text.isNotEmpty && newPasswordController.text == confirmPasswordController.text; - emit(state.copyWith(isButtonEnabled: isFilled)); + // emit(state.copyWith(isButtonEnabled: isFilled)); + emit(state.copyWith( + isButtonEnabled: isFilled, + status: state.status, + )); } Future _changePassword() async { - emit(state.copyWith(isLoading: true)); + // emit(state.copyWith(isLoading: true)); + emit(state.copyWith(status: ChangePasswordStatus.loading)); final result = await _changePasswordUseCase( ChangePasswordRequestEntity( password: currentPasswordController.text, @@ -51,18 +56,28 @@ class ChangePasswordViewModel extends Cubit { ), ); 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( + // 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, + )); + } } 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 index 2fcef5b..1869c5c 100644 --- a/test/presentation/auth/change_password/view/change_password_view_test.dart +++ b/test/presentation/auth/change_password/view/change_password_view_test.dart @@ -4,8 +4,6 @@ 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:elevate_tracking_app/presentation/auth/change_password/view_model/change_password_view_model.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; @@ -40,8 +38,6 @@ void main() { testWidgets("verify change password behavior " , (tester) async { await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); - final viewModel = GetIt.instance(); - final formKey = viewModel.changePasswordFormKey; expect(currentPasswordField, findsOneWidget); expect(newPasswordField, findsOneWidget); @@ -58,9 +54,6 @@ void main() { await tester.pumpAndSettle(); await tester.pump(); - final form = find.byType(Form).evaluate().first.widget as Form; - final isValid = formKey.currentState?.validate() ?? false; - expect(isValid, false); }); testWidgets("Verify app bar behaviour", (tester) async { @@ -73,8 +66,6 @@ void main() { testWidgets("verify success change password behavior " , (tester) async { await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); - final viewModel = GetIt.instance(); - final formKey = viewModel.changePasswordFormKey; expect(currentPasswordField, findsOneWidget); expect(newPasswordField, findsOneWidget); @@ -105,8 +96,6 @@ void main() { testWidgets("verify error fields change password behavior " , (tester) async { await tester.pumpWidget(const TestAppWrapper(child: ChangePassword())); - final viewModel = GetIt.instance(); - final formKey = viewModel.changePasswordFormKey; expect(currentPasswordField, findsOneWidget); expect(newPasswordField, findsOneWidget); @@ -125,12 +114,11 @@ void main() { await tester.tap(confirmButton); await tester.pump(const Duration(seconds: 2)); - + final buttonWidget = tester.widget(confirmButton); expect(buttonWidget.onPressed != null, false); - // expect(find.text(AppLocalizations().passwordNotMatched), findsOneWidget); - // expect(find.text(AppLocalizations().enterYourPassword), findsNothing); + @@ -140,4 +128,4 @@ void main() { }); -} \ No newline at end of file +} 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 index 2e71084..eb29a34 100644 --- 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 @@ -10,86 +10,82 @@ 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",(){ - + group("change password view model", () { late MockChangePasswordUseCase mockChangePasswordUseCase; late ChangePasswordStates changePasswordStates; late ChangePasswordViewModel changePasswordViewModel; - setUp((){ + setUp(() { mockChangePasswordUseCase = MockChangePasswordUseCase(); - changePasswordViewModel =ChangePasswordViewModel(mockChangePasswordUseCase ); - changePasswordStates = ChangePasswordStates(); + changePasswordViewModel = ChangePasswordViewModel(mockChangePasswordUseCase); + changePasswordStates = const ChangePasswordStates(); }); - final changePasswordEntity = ChangePasswordDummyData().fakeChangePasswordResponseEntity ; - final changePasswordRequestEntity = ChangePasswordDummyData().fakeChangePasswordRequestEntity ; - final expectedResult= ApiSuccessResult(changePasswordEntity); - provideDummy >(expectedResult); + final changePasswordEntity = ChangePasswordDummyData().fakeChangePasswordResponseEntity; + final changePasswordRequestEntity = ChangePasswordDummyData().fakeChangePasswordRequestEntity; + final expectedResult = ApiSuccessResult(changePasswordEntity); - blocTest("when emit success state should call change password with valid data ", - - build:()=>changePasswordViewModel, + 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(isLoading: true), - changePasswordStates.copyWith(isLoading: false,changePasswordResponseEntity: changePasswordEntity, - isButtonEnabled: false,isSuccess: true,errorMessage:null - ) - ] - , - verify: (_) { - verify(mockChangePasswordUseCase(any)).called(1); + 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); + final expectedError = "fake_error"; + final expectedErrorResult = ApiErrorResult(expectedError); - blocTest("when emit error state it should call change password fails ", - build:() =>changePasswordViewModel, + provideDummy>(expectedErrorResult); - act: (bloc){ - when(mockChangePasswordUseCase(any)).thenAnswer((_) async =>expectedErrorResult); - bloc.newPasswordController.text = changePasswordRequestEntity.newPassword!; - bloc.currentPasswordController.text = changePasswordRequestEntity.password!; - return bloc.doIntent(ChangePasswordEvent()); + 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(isLoading: true), - changePasswordStates.copyWith(isLoading: false,changePasswordResponseEntity: null, - isButtonEnabled: false,isSuccess: false,errorMessage:expectedError, - - ) - ], - verify: (_){ - verify(mockChangePasswordUseCase(any)).called(1); - } - - + 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 filled and passwords match", + "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.checkIfAllFieldsFilled(); + bloc.doIntent(CheckAllFieldsEvent()); }, expect: () => [ changePasswordStates.copyWith(isButtonEnabled: true), @@ -97,19 +93,17 @@ void main() { ); blocTest( - "should emit isButtonEnabled false when fields are empty or passwords do not match", + "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.checkIfAllFieldsFilled(); + bloc.doIntent(CheckAllFieldsEvent()); }, expect: () => [ changePasswordStates.copyWith(isButtonEnabled: false), ], ); - - }); -} \ No newline at end of file +} From a109fa7df502c7d84fbdf202cba83f5a7aee155c Mon Sep 17 00:00:00 2001 From: nmo86 Date: Fri, 17 Oct 2025 16:08:12 +0300 Subject: [PATCH 11/11] Merge remote-tracking branch 'origin/feature/ECOM-46/reset-password' into feature/ECOM-46/reset-password --- .../auth_remote_data_source_impl_test.dart | 2 - test/dummy/login_dummy_data.dart | 2 - .../login/view/screen/login_screen_test.dart | 264 ------------------ .../view_model/login_view_model_test.dart | 225 --------------- .../views/screen/onboarding_screen_test.dart | 79 ------ 5 files changed, 572 deletions(-) delete mode 100644 test/presentation/auth/login/view/screen/login_screen_test.dart delete mode 100644 test/presentation/auth/login/view_model/login_view_model_test.dart delete mode 100644 test/presentation/onboarding/views/screen/onboarding_screen_test.dart 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 510d7bf..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 @@ -1,10 +1,8 @@ import 'package:elevate_tracking_app/api/client/api_client.dart'; import 'package:elevate_tracking_app/api/data_source/auth_remote_data_source_impl.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/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'; diff --git a/test/dummy/login_dummy_data.dart b/test/dummy/login_dummy_data.dart index 0b45eee..2ef5b82 100644 --- a/test/dummy/login_dummy_data.dart +++ b/test/dummy/login_dummy_data.dart @@ -2,9 +2,7 @@ import 'package:elevate_tracking_app/api/mapper/login_mapper.dart'; import 'package:elevate_tracking_app/api/models/requests/login_request.dart'; import 'package:elevate_tracking_app/api/models/responses/login_response.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/requests/login_request_entity.dart'; -import 'package:elevate_tracking_app/domain/entites/response/change_password_response_entity.dart'; class LoginDummyData { LoginRequestEntity get fakeLoginRequestEntity => 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); - }); - }); -}