From cd683b8f90b38e312aeffdbde54a8af0aeeff0f8 Mon Sep 17 00:00:00 2001 From: Lzyct Date: Fri, 4 Oct 2024 00:52:17 +0800 Subject: [PATCH] fix(test): resolve test user feature --- .../pages/dashboard/cubit/users_cubit.dart | 10 +- .../dashboard/cubit/users_cubit.freezed.dart | 72 ++++---- .../pages/dashboard/cubit/users_state.dart | 2 +- .../users/pages/dashboard/dashboard_page.dart | 159 +++++++++--------- .../dashboard/cubit/users_cubit_test.dart | 11 +- .../dashboard/cubit/users_state_test.dart | 12 +- .../pages/dashboard/dashboard_page_test.dart | 23 ++- 7 files changed, 153 insertions(+), 136 deletions(-) diff --git a/lib/features/users/pages/dashboard/cubit/users_cubit.dart b/lib/features/users/pages/dashboard/cubit/users_cubit.dart index 3d4b325..302fb3f 100644 --- a/lib/features/users/pages/dashboard/cubit/users_cubit.dart +++ b/lib/features/users/pages/dashboard/cubit/users_cubit.dart @@ -1,11 +1,9 @@ -import 'package:flutter/material.dart'; import 'package:flutter_auth_app/core/core.dart'; import 'package:flutter_auth_app/features/features.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'users_cubit.freezed.dart'; - part 'users_state.dart'; class UsersCubit extends Cubit { @@ -53,8 +51,14 @@ class UsersCubit extends Cubit { currentPage = r.currentPage ?? 1; lastPage = r.lastPage ?? 1; + final updatedUsers = Users( + currentPage: currentPage, + lastPage: lastPage, + users: users, + ); + if (currentPage != 1) emit(const _Initial()); - emit(_Success(users)); + emit(_Success(updatedUsers)); }, ); } diff --git a/lib/features/users/pages/dashboard/cubit/users_cubit.freezed.dart b/lib/features/users/pages/dashboard/cubit/users_cubit.freezed.dart index 3bc00a4..57dee31 100644 --- a/lib/features/users/pages/dashboard/cubit/users_cubit.freezed.dart +++ b/lib/features/users/pages/dashboard/cubit/users_cubit.freezed.dart @@ -20,7 +20,7 @@ mixin _$UsersState { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) => @@ -29,7 +29,7 @@ mixin _$UsersState { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) => @@ -38,7 +38,7 @@ mixin _$UsersState { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), @@ -138,7 +138,7 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) { @@ -150,7 +150,7 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) { @@ -162,7 +162,7 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), @@ -261,7 +261,7 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) { @@ -273,7 +273,7 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) { @@ -285,7 +285,7 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), @@ -347,7 +347,9 @@ abstract class _$$SuccessImplCopyWith<$Res> { _$SuccessImpl value, $Res Function(_$SuccessImpl) then) = __$$SuccessImplCopyWithImpl<$Res>; @useResult - $Res call({List data}); + $Res call({Users data}); + + $UsersCopyWith<$Res> get data; } /// @nodoc @@ -367,25 +369,30 @@ class __$$SuccessImplCopyWithImpl<$Res> }) { return _then(_$SuccessImpl( null == data - ? _value._data + ? _value.data : data // ignore: cast_nullable_to_non_nullable - as List, + as Users, )); } + + /// Create a copy of UsersState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $UsersCopyWith<$Res> get data { + return $UsersCopyWith<$Res>(_value.data, (value) { + return _then(_value.copyWith(data: value)); + }); + } } /// @nodoc class _$SuccessImpl implements _Success { - const _$SuccessImpl(final List data) : _data = data; + const _$SuccessImpl(this.data); - final List _data; @override - List get data { - if (_data is EqualUnmodifiableListView) return _data; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_data); - } + final Users data; @override String toString() { @@ -397,12 +404,11 @@ class _$SuccessImpl implements _Success { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SuccessImpl && - const DeepCollectionEquality().equals(other._data, _data)); + (identical(other.data, data) || other.data == data)); } @override - int get hashCode => - Object.hash(runtimeType, const DeepCollectionEquality().hash(_data)); + int get hashCode => Object.hash(runtimeType, data); /// Create a copy of UsersState /// with the given fields replaced by the non-null parameter values. @@ -417,7 +423,7 @@ class _$SuccessImpl implements _Success { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) { @@ -429,7 +435,7 @@ class _$SuccessImpl implements _Success { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) { @@ -441,7 +447,7 @@ class _$SuccessImpl implements _Success { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), @@ -494,9 +500,9 @@ class _$SuccessImpl implements _Success { } abstract class _Success implements UsersState { - const factory _Success(final List data) = _$SuccessImpl; + const factory _Success(final Users data) = _$SuccessImpl; - List get data; + Users get data; /// Create a copy of UsersState /// with the given fields replaced by the non-null parameter values. @@ -575,7 +581,7 @@ class _$FailureImpl implements _Failure { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) { @@ -587,7 +593,7 @@ class _$FailureImpl implements _Failure { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) { @@ -599,7 +605,7 @@ class _$FailureImpl implements _Failure { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), @@ -706,7 +712,7 @@ class _$EmptyImpl implements _Empty { TResult when({ required TResult Function() loading, required TResult Function() initial, - required TResult Function(List data) success, + required TResult Function(Users data) success, required TResult Function(String message) failure, required TResult Function() empty, }) { @@ -718,7 +724,7 @@ class _$EmptyImpl implements _Empty { TResult? whenOrNull({ TResult? Function()? loading, TResult? Function()? initial, - TResult? Function(List data)? success, + TResult? Function(Users data)? success, TResult? Function(String message)? failure, TResult? Function()? empty, }) { @@ -730,7 +736,7 @@ class _$EmptyImpl implements _Empty { TResult maybeWhen({ TResult Function()? loading, TResult Function()? initial, - TResult Function(List data)? success, + TResult Function(Users data)? success, TResult Function(String message)? failure, TResult Function()? empty, required TResult orElse(), diff --git a/lib/features/users/pages/dashboard/cubit/users_state.dart b/lib/features/users/pages/dashboard/cubit/users_state.dart index 7d77a35..c5b64fa 100644 --- a/lib/features/users/pages/dashboard/cubit/users_state.dart +++ b/lib/features/users/pages/dashboard/cubit/users_state.dart @@ -4,7 +4,7 @@ part of 'users_cubit.dart'; class UsersState with _$UsersState { const factory UsersState.loading() = _Loading; const factory UsersState.initial() = _Initial; - const factory UsersState.success(List data) = _Success; + const factory UsersState.success(Users data) = _Success; const factory UsersState.failure(String message) = _Failure; const factory UsersState.empty() = _Empty; } diff --git a/lib/features/users/pages/dashboard/dashboard_page.dart b/lib/features/users/pages/dashboard/dashboard_page.dart index 9ed8bb5..fba9176 100644 --- a/lib/features/users/pages/dashboard/dashboard_page.dart +++ b/lib/features/users/pages/dashboard/dashboard_page.dart @@ -44,91 +44,14 @@ class _DashboardPageState extends State { success: (data) { return ListView.builder( controller: _scrollController, - itemCount: context.read().currentPage == - context.read().lastPage - ? (data.length) - : ((data.length) + 1), + physics: const AlwaysScrollableScrollPhysics(), + itemCount: data.currentPage == data.lastPage + ? data.users?.length + : ((data.users?.length ?? 0) + 1), padding: EdgeInsets.symmetric(vertical: Dimens.space16), itemBuilder: (_, index) { - return index < data.length - ? Container( - decoration: BoxDecorations(context).card, - margin: EdgeInsets.symmetric( - vertical: Dimens.space12, - horizontal: Dimens.space16, - ), - child: Row( - children: [ - ClipRRect( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(Dimens.space8), - bottomLeft: Radius.circular(Dimens.space8), - ), - child: CachedNetworkImage( - imageUrl: data[index].avatar ?? "", - width: Dimens.profilePicture, - height: Dimens.profilePicture, - fit: BoxFit.cover, - ), - ), - SpacerH(value: Dimens.space16), - Expanded( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - data[index].name ?? "", - style: Theme.of(context) - .textTheme - .titleLargeBold, - ), - Text( - data[index].email ?? "", - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith( - color: Theme.of(context) - .extension()! - .subtitle, - ), - ), - const SpacerV(), - Row( - children: [ - Text( - Strings.of(context)!.lastUpdate, - style: Theme.of(context) - .textTheme - .labelSmall - ?.copyWith( - color: Theme.of(context) - .extension()! - .subtitle, - ), - ), - Text( - (data[index].updatedAt ?? "") - .toStringDateAlt(), - style: Theme.of(context) - .textTheme - .labelSmall - ?.copyWith( - color: Theme.of(context) - .extension()! - .subtitle, - ), - textAlign: TextAlign.end, - ), - ], - ), - ], - ), - ), - ], - ), - ) + return index < (data.users?.length ?? 0) + ? userItem(data.users![index]) : Padding( padding: EdgeInsets.all(Dimens.space16), child: const Center( @@ -146,4 +69,74 @@ class _DashboardPageState extends State { ), ); } + + Container userItem(User user) { + return Container( + decoration: BoxDecorations(context).card, + margin: EdgeInsets.symmetric( + vertical: Dimens.space12, + horizontal: Dimens.space16, + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(Dimens.space8), + bottomLeft: Radius.circular(Dimens.space8), + ), + child: CachedNetworkImage( + imageUrl: user.avatar ?? "", + width: Dimens.profilePicture, + height: Dimens.profilePicture, + fit: BoxFit.cover, + ), + ), + SpacerH(value: Dimens.space16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + user.name ?? "", + style: Theme.of(context).textTheme.titleLargeBold, + ), + Text( + user.email ?? "", + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context) + .extension()! + .subtitle, + ), + ), + const SpacerV(), + Row( + children: [ + Text( + Strings.of(context)!.lastUpdate, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context) + .extension()! + .subtitle, + ), + ), + Flexible( + child: Text( + (user.updatedAt ?? "").toStringDateAlt(), + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: Theme.of(context) + .extension()! + .subtitle, + ), + textAlign: TextAlign.end, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } } diff --git a/test/features/users/pages/dashboard/cubit/users_cubit_test.dart b/test/features/users/pages/dashboard/cubit/users_cubit_test.dart index 886a93d..ac8cbe4 100644 --- a/test/features/users/pages/dashboard/cubit/users_cubit_test.dart +++ b/test/features/users/pages/dashboard/cubit/users_cubit_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_auth_app/features/features.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; + /// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; @@ -61,7 +62,7 @@ void main() { wait: const Duration(milliseconds: 100), expect: () => [ const UsersState.loading(), - UsersState.success(users.users ?? []), + UsersState.success(users), ], ); @@ -75,7 +76,10 @@ void main() { }, act: (UsersCubit usersCubit) => usersCubit.fetchUsers(dummyUsersRequest2), wait: const Duration(milliseconds: 100), - expect: () => [UsersState.success(users.users ?? [])], + expect: () => [ + const UsersState.loading(), + UsersState.success(users), + ], ); blocTest( @@ -106,6 +110,7 @@ void main() { act: (UsersCubit usersCubit) => usersCubit.fetchUsers(dummyUsersRequest2), wait: const Duration(milliseconds: 100), expect: () => [ + const UsersState.loading(), const UsersState.empty(), ], ); @@ -122,7 +127,7 @@ void main() { wait: const Duration(milliseconds: 100), expect: () => [ const UsersState.loading(), - UsersState.success(users.users ?? []), + UsersState.success(users), ], ); } diff --git a/test/features/users/pages/dashboard/cubit/users_state_test.dart b/test/features/users/pages/dashboard/cubit/users_state_test.dart index 95b01a4..c2aa5ec 100644 --- a/test/features/users/pages/dashboard/cubit/users_state_test.dart +++ b/test/features/users/pages/dashboard/cubit/users_state_test.dart @@ -1,11 +1,6 @@ -import 'dart:convert'; - import 'package:flutter_auth_app/features/features.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../../../../../helpers/json_reader.dart'; -import '../../../../../helpers/paths.dart'; - void main() { group('UsersStatusX', () { test('returns correct values for UsersStatus.loading', () { @@ -14,11 +9,8 @@ void main() { }); test('returns correct values for UsersStatus.success', () { - final usersResponse = UsersResponse.fromJson( - json.decode(jsonReader(pathUsersResponse200)) as Map, - ).toEntity(); - final status = UsersState.success(usersResponse.users ?? []); - expect(status, UsersState.success(usersResponse.users ?? [])); + const status = UsersState.success(Users()); + expect(status, const UsersState.success(Users())); }); test('returns correct values for UsersStatus.failure', () { diff --git a/test/features/users/pages/dashboard/dashboard_page_test.dart b/test/features/users/pages/dashboard/dashboard_page_test.dart index 2c7902a..18a787e 100644 --- a/test/features/users/pages/dashboard/dashboard_page_test.dart +++ b/test/features/users/pages/dashboard/dashboard_page_test.dart @@ -12,6 +12,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_test/flutter_test.dart'; // ignore: depend_on_referenced_packages import 'package:mocktail/mocktail.dart'; + /// ignore: depend_on_referenced_packages import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; @@ -100,8 +101,10 @@ void main() { 'renders DashboardPage for UsersStatus.success', (tester) async { when(() => usersCubit.state).thenReturn( - UsersState.success(users.users ?? []), + UsersState.success(users), ); + when(() => usersCubit.fetchUsers(any())).thenAnswer((_) async {}); + await tester.pumpWidget(rootWidget(const DashboardPage())); /// Do loops to waiting refresh indicator showing @@ -118,12 +121,24 @@ void main() { 'trigger refresh when pull to refresh', (tester) async { when(() => usersCubit.state).thenReturn( - UsersState.success(users.users ?? []), + UsersState.success(users), ); when(() => usersCubit.refresh()).thenAnswer((_) async {}); await tester.pumpWidget(rootWidget(const DashboardPage())); - await tester.pumpAndSettle(); + + /// Do loops to waiting refresh indicator showing + /// instead using tester.pumpAndSettle it's will result time out error + for (int i = 0; i < 5; i++) { + await tester.pump(const Duration(milliseconds: 100)); + } + + /// Simulate pull to refresh + + // Do loops to wait for the refresh indicator to show + for (int i = 0; i < 5; i++) { + await tester.pump(const Duration(milliseconds: 100)); + } await tester.fling( find.text('Mudassir'), const Offset(0.0, 500.0), @@ -135,6 +150,8 @@ void main() { for (int i = 0; i < 5; i++) { await tester.pump(const Duration(milliseconds: 100)); } + + // Verify that the refresh method was called verify(() => usersCubit.refresh()).called(1); }, );