Merge remote-tracking branch 'origin/fix/message-search-pagination' into fix/message-search-pagination

This commit is contained in:
xsahil03x
2021-09-08 15:29:44 +05:30
10 changed files with 257 additions and 98 deletions
@@ -58,7 +58,7 @@ class MessageSearchListView extends StatefulWidget {
required this.filters, required this.filters,
this.messageQuery, this.messageQuery,
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams = const PaginationParams(limit: 30),
this.messageFilters, this.messageFilters,
this.separatorBuilder, this.separatorBuilder,
this.itemBuilder, this.itemBuilder,
@@ -93,7 +93,7 @@ class MessageSearchListView extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? paginationParams; final PaginationParams paginationParams;
/// The message query filters to use. /// The message query filters to use.
/// You can query on any of the custom fields you've defined on the [Channel]. /// You can query on any of the custom fields you've defined on the [Channel].
@@ -51,7 +51,7 @@ class UserListView extends StatefulWidget {
this.filter, this.filter,
this.sort, this.sort,
this.presence, this.presence,
this.pagination, this.pagination = const PaginationParams(limit: 30),
this.onUserTap, this.onUserTap,
this.onUserLongPress, this.onUserLongPress,
this.userWidget, this.userWidget,
@@ -93,7 +93,7 @@ class UserListView extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? pagination; final PaginationParams pagination;
/// Function called when tapping on a channel /// Function called when tapping on a channel
/// By default it calls [Navigator.push] building a [MaterialPageRoute] /// By default it calls [Navigator.push] building a [MaterialPageRoute]
@@ -65,6 +65,8 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
Stream<bool> get queryMessagesLoading => Stream<bool> get queryMessagesLoading =>
_queryMessagesLoadingController.stream; _queryMessagesLoadingController.stream;
bool _paginationEnded = false;
/// Calls [StreamChatClient.search] updating /// Calls [StreamChatClient.search] updating
/// [messagesStream] and [queryMessagesLoading] stream /// [messagesStream] and [queryMessagesLoading] stream
Future<void> search({ Future<void> search({
@@ -72,24 +74,31 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
Filter? messageFilter, Filter? messageFilter,
List<SortOption>? sort, List<SortOption>? sort,
String? query, String? query,
PaginationParams? pagination, PaginationParams pagination = const PaginationParams(limit: 30),
}) async { }) async {
final client = _streamChatCoreState.client; final client = _streamChatCoreState.client;
if (_queryMessagesLoadingController.value == true) return; var clear = false;
if (sort != null) {
clear |= pagination.next == null;
} else {
final offset = pagination.offset;
clear |= offset == null || offset == 0;
}
if (clear && _paginationEnded) {
_paginationEnded = false;
}
if ((!clear && _paginationEnded) ||
_queryMessagesLoadingController.value == true) {
return;
}
if (_messageResponses.hasValue) { if (_messageResponses.hasValue) {
_queryMessagesLoadingController.add(true); _queryMessagesLoadingController.add(true);
} }
try { try {
var clear = pagination == null;
if (sort != null) {
clear |= pagination?.next == null;
} else {
final offset = pagination?.offset;
clear |= offset == null || offset == 0;
}
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []); final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
final response = await client.search( final response = await client.search(
@@ -110,15 +119,19 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
? previous ? previous
: /*reset previousId if we get nothing*/ null; : /*reset previousId if we get nothing*/ null;
final newMessages = response.results;
if (clear) { if (clear) {
_messageResponses.add(response.results); _messageResponses.add(newMessages);
} else { } else {
final temp = oldMessages + response.results; final temp = oldMessages + newMessages;
_messageResponses.add(temp); _messageResponses.add(temp);
} }
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { if (_messageResponses.hasValue && _queryMessagesLoadingController.value) {
_queryMessagesLoadingController.add(false); _queryMessagesLoadingController.add(false);
} }
if (newMessages.isEmpty || newMessages.length < pagination.limit) {
_paginationEnded = true;
}
} catch (e, stk) { } catch (e, stk) {
// reset loading controller // reset loading controller
_queryMessagesLoadingController.add(false); _queryMessagesLoadingController.add(false);
@@ -47,7 +47,7 @@ class MessageSearchListCore extends StatefulWidget {
required this.filters, required this.filters,
this.messageQuery, this.messageQuery,
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams = const PaginationParams(limit: 30),
this.messageFilters, this.messageFilters,
this.messageSearchListController, this.messageSearchListController,
}) : assert( }) : assert(
@@ -84,7 +84,7 @@ class MessageSearchListCore extends StatefulWidget {
/// Pagination parameters /// Pagination parameters
/// limit: the number of messages to return (max is 30) /// limit: the number of messages to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
final PaginationParams? paginationParams; final PaginationParams paginationParams;
/// The message query filters to use. /// The message query filters to use.
/// You can query on any of the custom fields you've defined on the [Channel]. /// You can query on any of the custom fields you've defined on the [Channel].
@@ -163,13 +163,13 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
/// Fetches more messages with updated pagination and updates the widget /// Fetches more messages with updated pagination and updates the widget
Future<void> paginateData() { Future<void> paginateData() {
PaginationParams? pagination; PaginationParams pagination;
if (widget.sortOptions != null) { if (widget.sortOptions != null) {
pagination = widget.paginationParams?.copyWith( pagination = widget.paginationParams.copyWith(
next: _messageSearchBloc?.nextId, next: _messageSearchBloc?.nextId,
); );
} else { } else {
pagination = widget.paginationParams?.copyWith( pagination = widget.paginationParams.copyWith(
offset: _messageSearchBloc?.messageResponses?.length, offset: _messageSearchBloc?.messageResponses?.length,
); );
} }
@@ -190,8 +190,8 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() ||
widget.messageFilters?.toString() != widget.messageFilters?.toString() !=
oldWidget.messageFilters?.toString() || oldWidget.messageFilters?.toString() ||
widget.paginationParams?.toJson().toString() != widget.paginationParams.toJson().toString() !=
oldWidget.paginationParams?.toJson().toString()) { oldWidget.paginationParams.toJson().toString()) {
loadData(); loadData();
} }
@@ -66,7 +66,7 @@ class UserListCore extends StatefulWidget {
this.filter, this.filter,
this.sort, this.sort,
this.presence, this.presence,
this.pagination, this.pagination = const PaginationParams(limit: 30),
this.groupAlphabetically = false, this.groupAlphabetically = false,
this.userListController, this.userListController,
}) : super(key: key); }) : super(key: key);
@@ -106,7 +106,7 @@ class UserListCore extends StatefulWidget {
/// limit: the number of users to return (max is 30) /// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000) /// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel /// message_limit: how many messages should be included to each channel
final PaginationParams? pagination; final PaginationParams pagination;
/// Set it to true to group users by their first character /// Set it to true to group users by their first character
/// ///
@@ -201,7 +201,7 @@ class UserListCoreState extends State<UserListCore>
filter: widget.filter, filter: widget.filter,
sort: widget.sort, sort: widget.sort,
presence: widget.presence, presence: widget.presence,
pagination: widget.pagination!.copyWith( pagination: widget.pagination.copyWith(
offset: _usersBloc!.users?.length ?? 0, offset: _usersBloc!.users?.length ?? 0,
), ),
); );
@@ -212,8 +212,8 @@ class UserListCoreState extends State<UserListCore>
if (widget.filter?.toString() != oldWidget.filter?.toString() || if (widget.filter?.toString() != oldWidget.filter?.toString() ||
jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) ||
widget.presence != oldWidget.presence || widget.presence != oldWidget.presence ||
widget.pagination?.toJson().toString() != widget.pagination.toJson().toString() !=
oldWidget.pagination?.toJson().toString()) { oldWidget.pagination.toJson().toString()) {
loadData(); loadData();
} }
@@ -57,6 +57,8 @@ class UsersBlocState extends State<UsersBloc>
late StreamChatCoreState _streamChatCore; late StreamChatCoreState _streamChatCore;
bool _paginationEnded = false;
/// The Query Users method allows you to search for users and see if they are /// The Query Users method allows you to search for users and see if they are
/// online/offline. /// online/offline.
/// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart) /// [API Reference](https://getstream.io/chat/docs/flutter-dart/query_users/?language=dart)
@@ -64,21 +66,27 @@ class UsersBlocState extends State<UsersBloc>
Filter? filter, Filter? filter,
List<SortOption>? sort, List<SortOption>? sort,
bool? presence, bool? presence,
PaginationParams? pagination, PaginationParams pagination = const PaginationParams(limit: 30),
}) async { }) async {
final client = _streamChatCore.client; final client = _streamChatCore.client;
if (_queryUsersLoadingController.value == true) return; final offset = pagination.offset;
final clear = offset == null || offset == 0;
if (clear && _paginationEnded) {
_paginationEnded = false;
}
if ((!clear && _paginationEnded) ||
_queryUsersLoadingController.value == true) {
return;
}
if (_usersController.hasValue) { if (_usersController.hasValue) {
_queryUsersLoadingController.add(true); _queryUsersLoadingController.add(true);
} }
try { try {
final clear = pagination == null ||
pagination.offset == null ||
pagination.offset == 0;
final oldUsers = List<User>.from(users ?? []); final oldUsers = List<User>.from(users ?? []);
final usersResponse = await client.queryUsers( final usersResponse = await client.queryUsers(
@@ -88,6 +96,7 @@ class UsersBlocState extends State<UsersBloc>
pagination: pagination, pagination: pagination,
); );
final newUsers = usersResponse.users;
if (clear) { if (clear) {
_usersController.add(usersResponse.users); _usersController.add(usersResponse.users);
} else { } else {
@@ -97,6 +106,9 @@ class UsersBlocState extends State<UsersBloc>
if (_usersController.hasValue && _queryUsersLoadingController.value) { if (_usersController.hasValue && _queryUsersLoadingController.value) {
_queryUsersLoadingController.add(false); _queryUsersLoadingController.add(false);
} }
if (newUsers.isEmpty || newUsers.length < pagination.limit) {
_paginationEnded = true;
}
} catch (e, stk) { } catch (e, stk) {
// reset loading controller // reset loading controller
_queryUsersLoadingController.add(false); _queryUsersLoadingController.add(false);
@@ -31,8 +31,7 @@ void main() {
); );
testWidgets( testWidgets(
'messageSearchBlocState.search() should throw if used where ' '''messageSearchBlocState.search() should throw if used where StreamChat is not present in the widget tree''',
'StreamChat is not present in the widget tree',
(tester) async { (tester) async {
const messageSearchBloc = MessageSearchBloc( const messageSearchBloc = MessageSearchBloc(
child: Offstage(), child: Offstage(),
@@ -98,8 +97,7 @@ void main() {
); );
testWidgets( testWidgets(
'messageSearchBlocState.messagesStream should emit error ' '''messageSearchBlocState.messagesStream should emit error if client.search() throws''',
'if client.search() throws',
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
@@ -147,9 +145,7 @@ void main() {
); );
testWidgets( testWidgets(
'calling messageSearchBlocState.search() again with an offset ' '''calling messageSearchBlocState.search() again with an offset should emit new data through messagesStream and also emit loading state through queryMessagesLoading''',
'should emit new data through messagesStream and also emit loading state '
'through queryMessagesLoading',
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
@@ -171,14 +167,15 @@ void main() {
find.byKey(messageSearchBlocKey), find.byKey(messageSearchBlocKey),
); );
final messageResponseList = _generateMessages(); const pagination = PaginationParams(limit: 25);
final messageResponseList = _generateMessages(count: 25);
when(() => mockClient.search( when(() => mockClient.search(
testFilter, testFilter,
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: any(named: 'paginationParams'), paginationParams: pagination,
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse() (_) async => SearchMessagesResponse()
..results = messageResponseList ..results = messageResponseList
@@ -186,7 +183,7 @@ void main() {
..previous = null, ..previous = null,
); );
messageSearchBlocState.search(filter: testFilter); messageSearchBlocState.search(pagination: pagination, filter: testFilter);
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
@@ -198,19 +195,19 @@ void main() {
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: any(named: 'paginationParams'), paginationParams: pagination,
)).called(1); )).called(1);
final offset = messageResponseList.length; final offset = messageResponseList.length;
final paginatedMessageResponseList = _generateMessages(offset: offset); final paginatedMessageResponseList = _generateMessages(offset: offset);
final pagination = PaginationParams(offset: offset); final newPagination = pagination.copyWith(offset: offset);
when(() => mockClient.search( when(() => mockClient.search(
testFilter, testFilter,
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: newPagination,
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse() (_) async => SearchMessagesResponse()
..results = paginatedMessageResponseList ..results = paginatedMessageResponseList
@@ -244,9 +241,7 @@ void main() {
); );
testWidgets( testWidgets(
'calling messageSearchBlocState.search() again with an offset ' '''calling messageSearchBlocState.search() again with an offset should emit error through queryUsersLoading if client.search() throws''',
'should emit error through queryUsersLoading if '
'client.search() throws',
(tester) async { (tester) async {
const messageSearchBlocKey = Key('messageSearchBloc'); const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child'); const childKey = Key('child');
@@ -268,14 +263,15 @@ void main() {
find.byKey(messageSearchBlocKey), find.byKey(messageSearchBlocKey),
); );
final messageResponseList = _generateMessages(); const pagination = PaginationParams(limit: 25);
final messageResponseList = _generateMessages(count: 25);
when(() => mockClient.search( when(() => mockClient.search(
testFilter, testFilter,
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: any(named: 'paginationParams'), paginationParams: pagination,
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse() (_) async => SearchMessagesResponse()
..results = messageResponseList ..results = messageResponseList
@@ -283,7 +279,7 @@ void main() {
..previous = null, ..previous = null,
); );
messageSearchBlocState.search(filter: testFilter); messageSearchBlocState.search(pagination: pagination, filter: testFilter);
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
@@ -295,11 +291,11 @@ void main() {
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: any(named: 'paginationParams'), paginationParams: pagination,
)).called(1); )).called(1);
final offset = messageResponseList.length; final offset = messageResponseList.length;
final pagination = PaginationParams(offset: offset); final newPagination = pagination.copyWith(offset: offset);
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(() => mockClient.search( when(() => mockClient.search(
@@ -307,10 +303,13 @@ void main() {
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: newPagination,
)).thenThrow(error); )).thenThrow(error);
messageSearchBlocState.search(pagination: pagination, filter: testFilter); messageSearchBlocState.search(
pagination: newPagination,
filter: testFilter,
);
await expectLater( await expectLater(
messageSearchBlocState.queryMessagesLoading, messageSearchBlocState.queryMessagesLoading,
@@ -322,8 +321,80 @@ void main() {
query: any(named: 'query'), query: any(named: 'query'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: newPagination,
)).called(1); )).called(1);
}, },
); );
testWidgets(
'''calling messageSearchBlocState.search() again with an offset should do nothing and return if pagination is completed''',
(tester) async {
const messageSearchBlocKey = Key('messageSearchBloc');
const childKey = Key('child');
const messageSearchBloc = MessageSearchBloc(
key: messageSearchBlocKey,
child: Offstage(key: childKey),
);
final mockClient = MockClient();
await tester.pumpWidget(
StreamChatCore(
client: mockClient,
child: messageSearchBloc,
),
);
final messageSearchBlocState = tester.state<MessageSearchBlocState>(
find.byKey(messageSearchBlocKey),
);
const pagination = PaginationParams(limit: 25);
final messageResponseList = _generateMessages(count: 20);
when(() => mockClient.search(
testFilter,
query: any(named: 'query'),
sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'),
paginationParams: pagination,
)).thenAnswer(
(_) async => SearchMessagesResponse()
..results = messageResponseList
..next = null
..previous = null,
);
messageSearchBlocState.search(pagination: pagination, filter: testFilter);
await expectLater(
messageSearchBlocState.messagesStream,
emits(isSameMessageResponseListAs(messageResponseList)),
);
verify(() => mockClient.search(
testFilter,
query: any(named: 'query'),
sort: any(named: 'sort'),
messageFilters: any(named: 'messageFilters'),
paginationParams: pagination,
)).called(1);
final offset = messageResponseList.length;
final newPagination = pagination.copyWith(offset: offset);
messageSearchBlocState.search(
filter: testFilter,
pagination: newPagination,
);
// should emit nothing.
await expectLater(
// skipping the initial data (behaviorSubject).
messageSearchBlocState.messagesStream.skip(1),
emitsInOrder([]),
);
},
);
} }
@@ -322,7 +322,7 @@ void main() {
(tester) async { (tester) async {
const messageSearchListCoreKey = Key('messageSearchListCore'); const messageSearchListCoreKey = Key('messageSearchListCore');
const childWidgetKey = Key('childWidget'); const childWidgetKey = Key('childWidget');
const pagination = PaginationParams(); const pagination = PaginationParams(limit: 25);
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Container( childBuilder: (List<GetMessageResponse> messages) => Container(
@@ -341,7 +341,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages(count: 25);
when(() => mockClient.search( when(() => mockClient.search(
testFilter, testFilter,
query: any(named: 'query'), query: any(named: 'query'),
@@ -321,7 +321,7 @@ void main() {
(tester) async { (tester) async {
const userListCoreKey = Key('userListCore'); const userListCoreKey = Key('userListCore');
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
const pagination = PaginationParams(); const pagination = PaginationParams(limit: 15);
final userListCore = UserListCore( final userListCore = UserListCore(
key: userListCoreKey, key: userListCoreKey,
listBuilder: (_, items) => Container( listBuilder: (_, items) => Container(
@@ -347,7 +347,7 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final users = _generateUsers(); final users = _generateUsers(count: 15);
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
@@ -164,16 +164,17 @@ void main() {
find.byKey(usersBlocKey), find.byKey(usersBlocKey),
); );
final users = _generateUsers(); const pagination = PaginationParams(limit: 25);
final users = _generateUsers(count: 25);
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
presence: any(named: 'presence'), presence: any(named: 'presence'),
pagination: any(named: 'pagination'), pagination: pagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(); usersBlocState.queryUsers(pagination: pagination);
await expectLater( await expectLater(
usersBlocState.usersStream, usersBlocState.usersStream,
@@ -184,23 +185,23 @@ void main() {
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
presence: any(named: 'presence'), presence: any(named: 'presence'),
pagination: any(named: 'pagination'), pagination: pagination,
)).called(1); )).called(1);
final offset = users.length; final offset = users.length;
final paginatedUsers = _generateUsers(offset: offset); final paginatedUsers = _generateUsers(offset: offset);
final pagination = PaginationParams(offset: offset); final newPagination = pagination.copyWith(offset: offset);
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
presence: any(named: 'presence'), presence: any(named: 'presence'),
pagination: pagination, pagination: newPagination,
)) )).thenAnswer(
.thenAnswer( (_) async => QueryUsersResponse()..users = paginatedUsers,
(_) async => QueryUsersResponse()..users = paginatedUsers); );
usersBlocState.queryUsers(pagination: pagination); usersBlocState.queryUsers(pagination: newPagination);
await Future.wait([ await Future.wait([
expectLater( expectLater(
@@ -217,7 +218,7 @@ void main() {
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
presence: any(named: 'presence'), presence: any(named: 'presence'),
pagination: pagination, pagination: newPagination,
)).called(1); )).called(1);
}, },
); );
@@ -247,13 +248,89 @@ void main() {
find.byKey(usersBlocKey), find.byKey(usersBlocKey),
); );
final users = _generateUsers(); const pagination = PaginationParams(limit: 25);
final users = _generateUsers(count: 25);
when(() => mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: any(named: 'filter'), filter: any(named: 'filter'),
sort: any(named: 'sort'), sort: any(named: 'sort'),
presence: any(named: 'presence'), presence: any(named: 'presence'),
pagination: any(named: 'pagination'), pagination: pagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(pagination: pagination);
await expectLater(
usersBlocState.usersStream,
emits(isSameUserListAs(users)),
);
verify(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: pagination,
)).called(1);
final offset = users.length;
final newPagination = pagination.copyWith(offset: offset);
const error = 'Error! Error! Error!';
when(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: newPagination,
)).thenThrow(error);
usersBlocState.queryUsers(pagination: newPagination);
await expectLater(
usersBlocState.queryUsersLoading,
emitsError(error),
);
verify(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: newPagination,
)).called(1);
},
);
testWidgets(
'''calling usersBlocState.queryUsers() again with an offset should do nothing and return if pagination is completed''',
(tester) async {
const usersBlocKey = Key('usersBloc');
const childKey = Key('child');
const usersBloc = UsersBloc(
key: usersBlocKey,
child: Offstage(key: childKey),
);
final mockClient = MockClient();
await tester.pumpWidget(
StreamChatCore(
client: mockClient,
child: usersBloc,
),
);
final usersBlocState = tester.state<UsersBlocState>(
find.byKey(usersBlocKey),
);
const pagination = PaginationParams(limit: 30);
final users = _generateUsers(count: 25);
when(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: pagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(); usersBlocState.queryUsers();
@@ -271,30 +348,16 @@ void main() {
)).called(1); )).called(1);
final offset = users.length; final offset = users.length;
final pagination = PaginationParams(offset: offset); final newPagination = pagination.copyWith(offset: offset);
const error = 'Error! Error! Error!'; usersBlocState.queryUsers(pagination: newPagination);
when(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: pagination,
)).thenThrow(error);
usersBlocState.queryUsers(pagination: pagination);
// should emit nothing.
await expectLater( await expectLater(
usersBlocState.queryUsersLoading, // skipping the initial data (behaviorSubject).
emitsError(error), usersBlocState.usersStream,
emitsInOrder([]),
); );
verify(() => mockClient.queryUsers(
filter: any(named: 'filter'),
sort: any(named: 'sort'),
presence: any(named: 'presence'),
pagination: pagination,
)).called(1);
}, },
); );
} }