Merge pull request #388 from GetStream/core-nnbd

feat: Core nnbd
This commit is contained in:
Salvatore Giordano
2021-04-21 10:06:04 +02:00
committed by GitHub
36 changed files with 1254 additions and 1530 deletions
+68 -74
View File
@@ -27,7 +27,7 @@ class Channel {
} }
/// Create a channel client instance from a [ChannelState] object /// Create a channel client instance from a [ChannelState] object
Channel.fromState(this._client, ChannelState channelState) { Channel.fromState(this._client, ChannelState channelState) : _extraData = {} {
_cid = channelState.channel!.cid; _cid = channelState.channel!.cid;
_id = channelState.channel!.id; _id = channelState.channel!.id;
type = channelState.channel!.type; type = channelState.channel!.type;
@@ -45,9 +45,9 @@ class Channel {
String? _id; String? _id;
String? _cid; String? _cid;
Map<String, dynamic>? _extraData; Map<String, dynamic> _extraData;
set extraData(Map<String, dynamic>? extraData) { set extraData(Map<String, dynamic> extraData) {
if (_initializedCompleter.isCompleted) { if (_initializedCompleter.isCompleted) {
throw Exception( throw Exception(
'Once the channel is initialized you should use channel.update ' 'Once the channel is initialized you should use channel.update '
@@ -81,7 +81,7 @@ class Channel {
/// Channel configuration as a stream /// Channel configuration as a stream
Stream<ChannelConfig?>? get configStream { Stream<ChannelConfig?>? get configStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.config); return state?.channelStateStream.map((cs) => cs.channel?.config);
} }
/// Channel user creator /// Channel user creator
@@ -93,7 +93,7 @@ class Channel {
/// Channel user creator as a stream /// Channel user creator as a stream
Stream<User?>? get createdByStream { Stream<User?>? get createdByStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.createdBy); return state?.channelStateStream.map((cs) => cs.channel?.createdBy);
} }
/// Channel frozen status /// Channel frozen status
@@ -105,7 +105,7 @@ class Channel {
/// Channel frozen status as a stream /// Channel frozen status as a stream
Stream<bool?>? get frozenStream { Stream<bool?>? get frozenStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.frozen); return state?.channelStateStream.map((cs) => cs.channel?.frozen);
} }
/// Channel creation date /// Channel creation date
@@ -117,7 +117,7 @@ class Channel {
/// Channel creation date as a stream /// Channel creation date as a stream
Stream<DateTime?>? get createdAtStream { Stream<DateTime?>? get createdAtStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.createdAt); return state?.channelStateStream.map((cs) => cs.channel?.createdAt);
} }
/// Channel last message date /// Channel last message date
@@ -131,7 +131,7 @@ class Channel {
Stream<DateTime?>? get lastMessageAtStream { Stream<DateTime?>? get lastMessageAtStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.lastMessageAt); return state?.channelStateStream.map((cs) => cs.channel?.lastMessageAt);
} }
/// Channel updated date /// Channel updated date
@@ -145,7 +145,7 @@ class Channel {
Stream<DateTime?>? get updatedAtStream { Stream<DateTime?>? get updatedAtStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.updatedAt); return state?.channelStateStream.map((cs) => cs.channel?.updatedAt);
} }
/// Channel deletion date /// Channel deletion date
@@ -159,7 +159,7 @@ class Channel {
Stream<DateTime?>? get deletedAtStream { Stream<DateTime?>? get deletedAtStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.deletedAt); return state?.channelStateStream.map((cs) => cs.channel?.deletedAt);
} }
/// Channel member count /// Channel member count
@@ -173,7 +173,7 @@ class Channel {
Stream<int?>? get memberCountStream { Stream<int?>? get memberCountStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.memberCount); return state?.channelStateStream.map((cs) => cs.channel?.memberCount);
} }
/// Channel id /// Channel id
@@ -189,13 +189,13 @@ class Channel {
} }
/// Channel extra data /// Channel extra data
Map<String, dynamic>? get extraData => Map<String, dynamic> get extraData =>
state?._channelState.channel?.extraData ?? _extraData; state?._channelState.channel?.extraData ?? _extraData;
/// Channel extra data as a stream /// Channel extra data as a stream
Stream<Map<String, dynamic>?>? get extraDataStream { Stream<Map<String, dynamic>?>? get extraDataStream {
_checkInitialized(); _checkInitialized();
return state?.channelStateStream.map((cs) => cs!.channel?.extraData); return state?.channelStateStream.map((cs) => cs.channel?.extraData);
} }
/// The main Stream chat client /// The main Stream chat client
@@ -842,7 +842,7 @@ class Channel {
messages: state?.messages?..remove(oldMessage), messages: state?.messages?..remove(oldMessage),
)); ));
} else { } else {
oldMessage = state!.threads!.values oldMessage = state!.threads.values
.expand((messages) => messages) .expand((messages) => messages)
.firstWhereOrNull((m) => m.id == messageId); .firstWhereOrNull((m) => m.id == messageId);
if (oldMessage?.parentId != null) { if (oldMessage?.parentId != null) {
@@ -853,8 +853,8 @@ class Channel {
state!.addMessage(parentMessage.copyWith( state!.addMessage(parentMessage.copyWith(
replyCount: parentMessage.replyCount! - 1)); replyCount: parentMessage.replyCount! - 1));
} }
state!.updateThreadInfo(oldMessage!.parentId, state!.updateThreadInfo(oldMessage!.parentId!,
state!.threads![oldMessage.parentId!]!..remove(oldMessage)); state!.threads[oldMessage.parentId!]!..remove(oldMessage));
} }
} }
@@ -1028,7 +1028,7 @@ class Channel {
}) })
..addAll(options); ..addAll(options);
if (_extraData != null) { if (_extraData.isNotEmpty) {
payload['data'] = _extraData; payload['data'] = _extraData;
} }
@@ -1333,7 +1333,7 @@ class ChannelClientState {
final _subscriptions = <StreamSubscription>[]; final _subscriptions = <StreamSubscription>[];
void _computeInitialUnread() { void _computeInitialUnread() {
final userRead = channelState?.read.firstWhereOrNull( final userRead = channelState.read.firstWhereOrNull(
(r) => r.user.id == _channel._client.state.user?.id, (r) => r.user.id == _channel._client.state.user?.id,
); );
if (userRead != null) { if (userRead != null) {
@@ -1372,9 +1372,9 @@ class ChannelClientState {
void _listenMemberAdded() { void _listenMemberAdded() {
_subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) { _subscriptions.add(_channel.on(EventType.memberAdded).listen((Event e) {
final member = e.member; final member = e.member;
updateChannelState(channelState!.copyWith( updateChannelState(channelState.copyWith(
members: [ members: [
...channelState!.members, ...channelState.members,
member!, member!,
], ],
)); ));
@@ -1384,9 +1384,9 @@ class ChannelClientState {
void _listenMemberRemoved() { void _listenMemberRemoved() {
_subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) { _subscriptions.add(_channel.on(EventType.memberRemoved).listen((Event e) {
final user = e.user; final user = e.user;
updateChannelState(channelState!.copyWith( updateChannelState(channelState.copyWith(
members: List.from( members: List.from(
channelState!.members..removeWhere((m) => m.userId == user!.id)), channelState.members..removeWhere((m) => m.userId == user!.id)),
)); ));
})); }));
} }
@@ -1394,7 +1394,7 @@ class ChannelClientState {
void _listenChannelUpdated() { void _listenChannelUpdated() {
_subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) { _subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) {
final channel = e.channel!; final channel = e.channel!;
updateChannelState(channelState!.copyWith( updateChannelState(channelState.copyWith(
channel: channel, channel: channel,
members: channel.members, members: channel.members,
)); ));
@@ -1416,14 +1416,14 @@ class ChannelClientState {
/// This flag should be managed by UI sdks. /// This flag should be managed by UI sdks.
/// When false, any new message (received by WebSocket event /// When false, any new message (received by WebSocket event
/// - [EventType.messageNew]) will not be pushed on to message list. /// - [EventType.messageNew]) will not be pushed on to message list.
bool get isUpToDate => _isUpToDateController.value!; bool get isUpToDate => _isUpToDateController.value ?? true;
set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate); set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate);
/// [isUpToDate] flag count as a stream /// [isUpToDate] flag count as a stream
Stream<bool?> get isUpToDateStream => _isUpToDateController.stream; Stream<bool> get isUpToDateStream => _isUpToDateController.stream;
final BehaviorSubject<bool?> _isUpToDateController = final BehaviorSubject<bool> _isUpToDateController =
BehaviorSubject.seeded(true); BehaviorSubject.seeded(true);
/// The retry queue associated to this channel /// The retry queue associated to this channel
@@ -1432,7 +1432,7 @@ class ChannelClientState {
/// Retry failed message /// Retry failed message
Future<void> retryFailedMessages() async { Future<void> retryFailedMessages() async {
final failedMessages = final failedMessages =
<Message>[...messages, ...threads!.values.expand((v) => v)] <Message>[...messages, ...threads.values.expand((v) => v)]
.where( .where(
(message) => (message) =>
message.status != MessageSendingStatus.sent && message.status != MessageSendingStatus.sent &&
@@ -1549,7 +1549,7 @@ class ChannelClientState {
} }
if (message.parentId != null) { if (message.parentId != null) {
updateThreadInfo(message.parentId, [message]); updateThreadInfo(message.parentId!, [message]);
} }
} }
@@ -1592,14 +1592,14 @@ class ChannelClientState {
/// Channel message list as a stream /// Channel message list as a stream
Stream<List<Message>?> get messagesStream => Stream<List<Message>?> get messagesStream =>
channelStateStream.map((cs) => cs!.messages); channelStateStream.map((cs) => cs.messages);
/// Channel pinned message list /// Channel pinned message list
List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList(); List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList();
/// Channel pinned message list as a stream /// Channel pinned message list as a stream
Stream<List<Message>?> get pinnedMessagesStream => Stream<List<Message>?> get pinnedMessagesStream =>
channelStateStream.map((cs) => cs!.pinnedMessages.toList()); channelStateStream.map((cs) => cs.pinnedMessages.toList());
/// Get channel last message /// Get channel last message
Message? get lastMessage => _channelState.messages.isNotEmpty == true Message? get lastMessage => _channelState.messages.isNotEmpty == true
@@ -1618,7 +1618,7 @@ class ChannelClientState {
/// Channel members list as a stream /// Channel members list as a stream
Stream<List<Member>> get membersStream => CombineLatestStream.combine2< Stream<List<Member>> get membersStream => CombineLatestStream.combine2<
List<Member?>?, Map<String?, User?>, List<Member>>( List<Member?>?, Map<String?, User?>, List<Member>>(
channelStateStream.map((cs) => cs!.members), channelStateStream.map((cs) => cs.members),
_channel.client.state.usersStream, _channel.client.state.usersStream,
(members, users) => (members, users) =>
members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(), members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(),
@@ -1629,7 +1629,7 @@ class ChannelClientState {
/// Channel watcher count as a stream /// Channel watcher count as a stream
Stream<int?> get watcherCountStream => Stream<int?> get watcherCountStream =>
channelStateStream.map((cs) => cs!.watcherCount); channelStateStream.map((cs) => cs.watcherCount);
/// Channel watchers list /// Channel watchers list
List<User> get watchers => _channelState.watchers List<User> get watchers => _channelState.watchers
@@ -1639,7 +1639,7 @@ class ChannelClientState {
/// Channel watchers list as a stream /// Channel watchers list as a stream
Stream<List<User>> get watchersStream => CombineLatestStream.combine2< Stream<List<User>> get watchersStream => CombineLatestStream.combine2<
List<User>?, Map<String?, User?>, List<User>>( List<User>?, Map<String?, User?>, List<User>>(
channelStateStream.map((cs) => cs!.watchers), channelStateStream.map((cs) => cs.watchers),
_channel.client.state.usersStream, _channel.client.state.usersStream,
(watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(), (watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(),
); );
@@ -1648,8 +1648,7 @@ class ChannelClientState {
List<Read>? get read => _channelState.read; List<Read>? get read => _channelState.read;
/// Channel read list as a stream /// Channel read list as a stream
Stream<List<Read>?> get readStream => Stream<List<Read>?> get readStream => channelStateStream.map((cs) => cs.read);
channelStateStream.map((cs) => cs!.read);
final BehaviorSubject<int> _unreadCountController = BehaviorSubject.seeded(0); final BehaviorSubject<int> _unreadCountController = BehaviorSubject.seeded(0);
@@ -1672,21 +1671,20 @@ class ChannelClientState {
} }
/// Update threads with updated information about messages /// Update threads with updated information about messages
void updateThreadInfo(String? parentId, List<Message>? messages) { void updateThreadInfo(String parentId, List<Message> messages) {
final newThreads = Map<String?, List<Message>?>.from(threads!); final newThreads = Map<String, List<Message>>.from(threads);
if (newThreads.containsKey(parentId)) { if (newThreads.containsKey(parentId)) {
newThreads[parentId] = [ newThreads[parentId] = [
...newThreads[parentId] ...newThreads[parentId]
?.where((newMessage) => ?.where(
!messages!.any((m) => m.id == newMessage.id)) (newMessage) => !messages.any((m) => m.id == newMessage.id))
.toList() ?? .toList() ??
[], [],
...messages!, ...messages,
]; ];
newThreads[parentId]! newThreads[parentId]!.sort(_sortByCreatedAt);
.sort(_sortByCreatedAt as int Function(Message, Message)?);
} else { } else {
newThreads[parentId] = messages; newThreads[parentId] = messages;
} }
@@ -1713,7 +1711,7 @@ class ChannelClientState {
.any((newMessage) => newMessage.id == m.id) != .any((newMessage) => newMessage.id == m.id) !=
true) true)
.toList(), .toList(),
]..sort(_sortByCreatedAt as int Function(Message, Message)?); ]..sort(_sortByCreatedAt);
final newWatchers = <User>[ final newWatchers = <User>[
...updatedState.watchers, ...updatedState.watchers,
@@ -1752,27 +1750,17 @@ class ChannelClientState {
); );
} }
int? _sortByCreatedAt(a, b) { int _sortByCreatedAt(Message a, Message b) =>
if (a.createdAt == null) { a.createdAt.compareTo(b.createdAt);
return 1;
}
if (b.createdAt == null) {
return -1;
}
return a.createdAt.compareTo(b.createdAt);
}
/// The channel state related to this client /// The channel state related to this client
ChannelState get _channelState => _channelStateController.value!; ChannelState get _channelState => _channelStateController.value!;
/// The channel state related to this client as a stream /// The channel state related to this client as a stream
Stream<ChannelState?> get channelStateStream => Stream<ChannelState> get channelStateStream => _channelStateController.stream;
_channelStateController.stream;
/// The channel state related to this client /// The channel state related to this client
ChannelState? get channelState => _channelStateController.value; ChannelState get channelState => _channelStateController.value!;
late BehaviorSubject<ChannelState> _channelStateController; late BehaviorSubject<ChannelState> _channelStateController;
final Debounce _debouncedUpdatePersistenceChannelState; final Debounce _debouncedUpdatePersistenceChannelState;
@@ -1783,33 +1771,33 @@ class ChannelClientState {
} }
/// The channel threads related to this channel /// The channel threads related to this channel
Map<String, List<Message>>? get threads => _threadsController.value Map<String, List<Message>> get threads =>
?.map((key, value) => MapEntry(key ?? '', value ?? [])); _threadsController.value!.map((key, value) => MapEntry(key, value));
/// The channel threads related to this channel as a stream /// The channel threads related to this channel as a stream
Stream<Map<String?, List<Message>?>> get threadsStream => Stream<Map<String, List<Message>>> get threadsStream =>
_threadsController.stream; _threadsController.stream;
final BehaviorSubject<Map<String?, List<Message>?>> _threadsController = final BehaviorSubject<Map<String, List<Message>>> _threadsController =
BehaviorSubject.seeded({}); BehaviorSubject.seeded({});
set _threads(Map<String?, List<Message>?> v) { set _threads(Map<String, List<Message>> v) {
_channel._client.chatPersistenceClient?.updateMessages( _channel._client.chatPersistenceClient?.updateMessages(
_channel.cid!, _channel.cid!,
v.values.expand((v) => v!).toList(), v.values.expand((v) => v).toList(),
); );
_threadsController.add(v); _threadsController.add(v);
} }
/// Channel related typing users last value /// Channel related typing users last value
List<User>? get typingEvents => _typingEventsController.value as List<User>?; List<User> get typingEvents => _typingEventsController.value!;
/// Channel related typing users stream /// Channel related typing users stream
Stream<List<User?>> get typingEventsStream => _typingEventsController.stream; Stream<List<User>> get typingEventsStream => _typingEventsController.stream;
final BehaviorSubject<List<User?>> _typingEventsController = final BehaviorSubject<List<User>> _typingEventsController =
BehaviorSubject.seeded([]); BehaviorSubject.seeded([]);
final Channel _channel; final Channel _channel;
final Map<User?, DateTime> _typings = {}; final Map<User, DateTime> _typings = {};
void _listenTypingEvents() { void _listenTypingEvents() {
if (_channelState.channel?.config.typingEvents == false) { if (_channelState.channel?.config.typingEvents == false) {
@@ -1820,9 +1808,12 @@ class ChannelClientState {
..add( ..add(
_channel.on(EventType.typingStart).listen( _channel.on(EventType.typingStart).listen(
(event) { (event) {
if (event.user!.id != _channel.client.state.user!.id) { if (event.user != null) {
_typings[event.user] = DateTime.now(); final user = event.user!;
_typingEventsController.add(_typings.keys.toList()); if (user.id != _channel.client.state.user?.id) {
_typings[user] = DateTime.now();
_typingEventsController.add(_typings.keys.toList());
}
} }
}, },
), ),
@@ -1830,9 +1821,12 @@ class ChannelClientState {
..add( ..add(
_channel.on(EventType.typingStop).listen( _channel.on(EventType.typingStop).listen(
(event) { (event) {
if (event.user!.id != _channel.client.state.user!.id) { if (event.user != null) {
_typings.remove(event.user); final user = event.user!;
_typingEventsController.add(_typings.keys.toList()); if (user.id != _channel.client.state.user?.id) {
_typings.remove(event.user);
_typingEventsController.add(_typings.keys.toList());
}
} }
}, },
), ),
@@ -1888,7 +1882,7 @@ class ChannelClientState {
void _startCleaningPinnedMessages() { void _startCleaningPinnedMessages() {
_pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) { _pinnedMessagesTimer = Timer.periodic(const Duration(seconds: 30), (_) {
final now = DateTime.now(); final now = DateTime.now();
var expiredMessages = channelState!.pinnedMessages var expiredMessages = channelState.pinnedMessages
.where((m) => m.pinExpires?.isBefore(now) == true) .where((m) => m.pinExpires?.isBefore(now) == true)
.toList(); .toList();
if (expiredMessages.isNotEmpty) { if (expiredMessages.isNotEmpty) {
+6 -5
View File
@@ -780,10 +780,11 @@ class StreamChatClient {
PaginationParams paginationParams = const PaginationParams(), PaginationParams paginationParams = const PaginationParams(),
}) async { }) async {
final offlineChannels = (await _chatPersistenceClient?.getChannelStates( final offlineChannels = (await _chatPersistenceClient?.getChannelStates(
filter: filter, filter: filter,
sort: sort, sort: sort,
paginationParams: paginationParams, paginationParams: paginationParams,
))!; )) ??
[];
final updatedData = _mapChannelStateToChannel(offlineChannels); final updatedData = _mapChannelStateToChannel(offlineChannels);
state.channels = updatedData.key; state.channels = updatedData.key;
return updatedData.value; return updatedData.value;
@@ -1193,7 +1194,7 @@ class StreamChatClient {
Channel channel( Channel channel(
String type, { String type, {
String? id, String? id,
Map<String, dynamic>? extraData, Map<String, dynamic> extraData = const {},
}) { }) {
if (id != null && state.channels?.containsKey('$type:$id') == true) { if (id != null && state.channels?.containsKey('$type:$id') == true) {
if (state.channels!['$type:$id'] != null) { if (state.channels!['$type:$id'] != null) {
@@ -27,7 +27,7 @@ class ChannelModel {
createdAt = createdAt ?? DateTime.now(), createdAt = createdAt ?? DateTime.now(),
updatedAt = updatedAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(),
assert( assert(
cid != null || (id != null && type != null), (cid != null && cid.contains(':')) || (id != null && type != null),
'provide either a cid or an id and type', 'provide either a cid or an id and type',
), ),
id = id ?? cid!.split(':')[1], id = id ?? cid!.split(':')[1],
@@ -17,8 +17,8 @@ class OwnUser extends User {
this.totalUnreadCount = 0, this.totalUnreadCount = 0,
this.unreadChannels, this.unreadChannels,
this.channelMutes = const [], this.channelMutes = const [],
String id = '', required String id,
String role = '', String? role,
DateTime? createdAt, DateTime? createdAt,
DateTime? updatedAt, DateTime? updatedAt,
DateTime? lastActive, DateTime? lastActive,
@@ -23,7 +23,7 @@ OwnUser _$OwnUserFromJson(Map<String, dynamic> json) {
.toList() ?? .toList() ??
[], [],
id: json['id'] as String, id: json['id'] as String,
role: json['role'] as String, role: json['role'] as String?,
createdAt: json['created_at'] == null createdAt: json['created_at'] == null
? null ? null
: DateTime.parse(json['created_at'] as String), : DateTime.parse(json['created_at'] as String),
@@ -81,7 +81,7 @@ class User {
int get hashCode => id.hashCode; int get hashCode => id.hashCode;
/// Shortcut for user name /// Shortcut for user name
String? get name => String get name =>
(extraData.containsKey('name') == true && extraData['name'] != '') (extraData.containsKey('name') == true && extraData['name'] != '')
? extraData['name'] ? extraData['name']
: id; : id;
@@ -3,6 +3,7 @@ library stream_chat;
export 'package:async/async.dart'; export 'package:async/async.dart';
export 'package:dio/src/dio_error.dart'; export 'package:dio/src/dio_error.dart';
export 'package:dio/src/multipart_file.dart'; export 'package:dio/src/multipart_file.dart';
export 'package:dio/src/options.dart';
export 'package:dio/src/options.dart' show ProgressCallback; export 'package:dio/src/options.dart' show ProgressCallback;
export 'package:logging/logging.dart' show Logger, Level; export 'package:logging/logging.dart' show Logger, Level;
@@ -516,7 +516,7 @@ void main() {
); );
await channelClient.watch(); await channelClient.watch();
final event = Event(type: EventType.any); final event = const Event(type: EventType.any);
when( when(
() => mockDio.post<String>( () => mockDio.post<String>(
@@ -564,7 +564,7 @@ void main() {
); );
await channelClient.watch(); await channelClient.watch();
final event = Event(type: EventType.typingStart); final event = const Event(type: EventType.typingStart);
when( when(
() => mockDio.post<String>( () => mockDio.post<String>(
@@ -610,7 +610,7 @@ void main() {
); );
await channelClient.watch(); await channelClient.watch();
final event = Event(type: EventType.typingStop); final event = const Event(type: EventType.typingStop);
when( when(
() => mockDio.post<String>( () => mockDio.post<String>(
@@ -901,8 +901,10 @@ void main() {
'presence': true, 'presence': true,
}; };
when(() => mockDio.post<String>('/channels/messaging/query', when(() => mockDio.post<String>(
data: options)).thenAnswer( '/channels/messaging/query',
data: options,
)).thenAnswer(
(_) async => Response( (_) async => Response(
data: r''' data: r'''
{ {
@@ -1198,8 +1200,10 @@ void main() {
final response = await channelClient.query(options: options); final response = await channelClient.query(options: options);
verify(() => mockDio.post<String>('/channels/messaging/query', verify(() => mockDio.post<String>(
data: options)).called(1); '/channels/messaging/query',
data: options,
)).called(1);
expect(channelClient.id, response.channel?.id); expect(channelClient.id, response.channel?.id);
expect(channelClient.cid, response.channel?.cid); expect(channelClient.cid, response.channel?.cid);
}); });
@@ -38,8 +38,8 @@ class StreamExample extends StatelessWidget {
/// If you'd prefer using pre-made UI widgets for your app, please see our other /// If you'd prefer using pre-made UI widgets for your app, please see our other
/// package, `stream_chat_flutter`. /// package, `stream_chat_flutter`.
const StreamExample({ const StreamExample({
Key key, Key? key,
@required this.client, required this.client,
}) : super(key: key); }) : super(key: key);
/// Instance of Stream Client. /// Instance of Stream Client.
@@ -55,7 +55,7 @@ class StreamExample extends StatelessWidget {
home: HomeScreen(), home: HomeScreen(),
builder: (context, child) => StreamChatCore( builder: (context, child) => StreamChatCore(
client: client, client: client,
child: child, child: child!,
), ),
); );
} }
@@ -82,7 +82,7 @@ class HomeScreen extends StatelessWidget {
'type': 'messaging', 'type': 'messaging',
'members': { 'members': {
r'$in': [ r'$in': [
StreamChatCore.of(context).user.id, StreamChatCore.of(context).user!.id,
] ]
} }
}, },
@@ -100,10 +100,13 @@ class HomeScreen extends StatelessWidget {
), ),
); );
}, },
errorBuilder: (BuildContext context, dynamic error) { errorBuilder: (
BuildContext context,
dynamic error,
) {
return Center( return Center(
child: Text( child: Text(
'Oh no, something went wrong. Please check your config.'), 'Oh no, something went wrong. Please check your config. ${error}'),
); );
}, },
listBuilder: ( listBuilder: (
@@ -112,20 +115,20 @@ class HomeScreen extends StatelessWidget {
) => ) =>
LazyLoadScrollView( LazyLoadScrollView(
onEndOfPage: () async { onEndOfPage: () async {
channelListController.paginateData(); channelListController.paginateData!();
}, },
child: ListView.builder( child: ListView.builder(
itemCount: channels.length, itemCount: channels.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final _item = channels[index]; final _item = channels[index];
return ListTile( return ListTile(
title: Text(_item.name), title: Text(_item.name!),
subtitle: StreamBuilder<Message>( subtitle: StreamBuilder<Message?>(
stream: _item.state.lastMessageStream, stream: _item.state!.lastMessageStream,
initialData: _item.state.lastMessage, initialData: _item.state!.lastMessage,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return Text(snapshot.data.text); return Text(snapshot.data!.text!);
} }
return SizedBox(); return SizedBox();
@@ -169,8 +172,8 @@ class MessageScreen extends StatefulWidget {
} }
class _MessageScreenState extends State<MessageScreen> { class _MessageScreenState extends State<MessageScreen> {
TextEditingController _controller; late final TextEditingController _controller;
ScrollController _scrollController; late final ScrollController _scrollController;
final messageListController = MessageListController(); final messageListController = MessageListController();
@override @override
@@ -203,11 +206,11 @@ class _MessageScreenState extends State<MessageScreen> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: StreamBuilder<List<User>>( title: StreamBuilder<List<User>>(
initialData: channel.state.typingEvents, initialData: channel.state?.typingEvents,
stream: channel.state.typingEventsStream, stream: channel.state?.typingEventsStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmpty) { if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return Text('${snapshot.data.first.name} is typing...'); return Text('${snapshot.data!.first.name} is typing...');
} }
return SizedBox(); return SizedBox();
}, },
@@ -219,9 +222,10 @@ class _MessageScreenState extends State<MessageScreen> {
Expanded( Expanded(
child: LazyLoadScrollView( child: LazyLoadScrollView(
onEndOfPage: () async { onEndOfPage: () async {
messageListController.paginateData(); messageListController.paginateData!();
}, },
child: MessageListCore( child: MessageListCore(
messageListController: messageListController,
emptyBuilder: (BuildContext context) { emptyBuilder: (BuildContext context) {
return Center( return Center(
child: Text('Nothing here yet'), child: Text('Nothing here yet'),
@@ -247,12 +251,12 @@ class _MessageScreenState extends State<MessageScreen> {
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final item = messages[index]; final item = messages[index];
final client = StreamChatCore.of(context).client; final client = StreamChatCore.of(context).client;
if (item.user.id == client.uid) { if (item.user!.id == client.uid) {
return Align( return Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text(item.text), child: Text(item.text!),
), ),
); );
} else { } else {
@@ -260,7 +264,7 @@ class _MessageScreenState extends State<MessageScreen> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text(item.text), child: Text(item.text!),
), ),
); );
} }
@@ -268,7 +272,7 @@ class _MessageScreenState extends State<MessageScreen> {
); );
}, },
errorWidgetBuilder: (BuildContext context, error) { errorWidgetBuilder: (BuildContext context, error) {
print(error?.toString()); print(error.toString());
return Center( return Center(
child: SizedBox( child: SizedBox(
height: 100.0, height: 100.0,
@@ -282,7 +286,7 @@ class _MessageScreenState extends State<MessageScreen> {
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -303,12 +307,14 @@ class _MessageScreenState extends State<MessageScreen> {
await channel.sendMessage( await channel.sendMessage(
Message(text: _controller.value.text), Message(text: _controller.value.text),
); );
_controller.clear(); if (mounted) {
_updateList(); _controller.clear();
_updateList();
}
} }
}, },
child: const Padding( child: const Padding(
padding: EdgeInsets.all(8.0), padding: EdgeInsets.all(8),
child: Center( child: Center(
child: Icon( child: Icon(
Icons.send, Icons.send,
@@ -332,12 +338,12 @@ class _MessageScreenState extends State<MessageScreen> {
/// below, we add two simple extensions to the [StreamChatClient] and [Channel]. /// below, we add two simple extensions to the [StreamChatClient] and [Channel].
extension on StreamChatClient { extension on StreamChatClient {
/// Fetches the current user id. /// Fetches the current user id.
String get uid => state.user.id; String get uid => state.user!.id;
} }
extension on Channel { extension on Channel {
/// Fetches the name of the channel by accessing [extraData] or [cid]. /// Fetches the name of the channel by accessing [extraData] or [cid].
String get name { String? get name {
final _channelName = extraData['name']; final _channelName = extraData['name'];
if (_channelName != null) { if (_channelName != null) {
return _channelName; return _channelName;
@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
flutter: flutter:
@@ -57,11 +57,11 @@ import 'package:stream_chat_flutter_core/src/typedef.dart';
class ChannelListCore extends StatefulWidget { class ChannelListCore extends StatefulWidget {
/// Instantiate a new ChannelListView /// Instantiate a new ChannelListView
const ChannelListCore({ const ChannelListCore({
Key key, Key? key,
@required this.errorBuilder, required this.errorBuilder,
@required this.emptyBuilder, required this.emptyBuilder,
@required this.loadingBuilder, required this.loadingBuilder,
@required this.listBuilder, required this.listBuilder,
this.filter, this.filter,
this.options, this.options,
this.sort, this.sort,
@@ -69,29 +69,13 @@ class ChannelListCore extends StatefulWidget {
limit: 25, limit: 25,
), ),
this.channelListController, this.channelListController,
}) : assert( }) : super(key: key);
errorBuilder != null,
'Parameter errorBuilder should not be null',
),
assert(
emptyBuilder != null,
'Parameter emptyBuilder should not be null',
),
assert(
loadingBuilder != null,
'Parameter loadingBuilder should not be null',
),
assert(
listBuilder != null,
'Parameter listBuilder should not be null',
),
super(key: key);
/// A [ChannelListController] allows reloading and pagination. /// A [ChannelListController] allows reloading and pagination.
/// Use [ChannelListController.loadData] and /// Use [ChannelListController.loadData] and
/// [ChannelListController.paginateData] respectively for reloading and /// [ChannelListController.paginateData] respectively for reloading and
/// pagination. /// pagination.
final ChannelListController channelListController; final ChannelListController? channelListController;
/// The builder that will be used in case of error /// The builder that will be used in case of error
final ErrorBuilder errorBuilder; final ErrorBuilder errorBuilder;
@@ -108,20 +92,20 @@ class ChannelListCore extends StatefulWidget {
/// The query filters to use. /// The 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].
/// You can also filter other built-in channel fields. /// You can also filter other built-in channel fields.
final Map<String, dynamic> filter; final Map<String, dynamic>? filter;
/// Query channels options. /// Query channels options.
/// ///
/// state: if true returns the Channel state /// state: if true returns the Channel state
/// watch: if true listen to changes to this Channel in real time. /// watch: if true listen to changes to this Channel in real time.
final Map<String, dynamic> options; final Map<String, dynamic>? options;
/// The sorting used for the channels matching the filters. /// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be /// Sorting is based on field and direction, multiple sorting options can be
/// provided. /// provided.
/// You can sort based on last_updated, last_message_at, updated_at, created /// You can sort based on last_updated, last_message_at, updated_at, created
/// _at or member_count. Direction can be ascending or descending. /// _at or member_count. Direction can be ascending or descending.
final List<SortOption<ChannelModel>> sort; final List<SortOption<ChannelModel>>? sort;
/// Pagination parameters /// Pagination parameters
/// limit: the number of channels to return (max is 30) /// limit: the number of channels to return (max is 30)
@@ -149,12 +133,12 @@ class ChannelListCoreState extends State<ChannelListCore> {
stream: channelsBlocState.channelsStream, stream: channelsBlocState.channelsStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return widget.errorBuilder(context, snapshot.error); return widget.errorBuilder(context, snapshot.error!);
} }
if (!snapshot.hasData) { if (!snapshot.hasData) {
return widget.loadingBuilder(context); return widget.loadingBuilder(context);
} }
final channels = snapshot.data; final channels = snapshot.data!;
if (channels.isEmpty) { if (channels.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
@@ -186,7 +170,7 @@ class ChannelListCoreState extends State<ChannelListCore> {
); );
} }
StreamSubscription<Event> _subscription; late StreamSubscription<Event> _subscription;
@override @override
void initState() { void initState() {
@@ -203,8 +187,8 @@ class ChannelListCoreState extends State<ChannelListCore> {
.listen((event) => loadData()); .listen((event) => loadData());
if (widget.channelListController != null) { if (widget.channelListController != null) {
widget.channelListController.loadData = loadData; widget.channelListController!.loadData = loadData;
widget.channelListController.paginateData = paginateData; widget.channelListController!.paginateData = paginateData;
} }
} }
@@ -215,8 +199,8 @@ class ChannelListCoreState extends State<ChannelListCore> {
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.options?.toString() != oldWidget.options?.toString() || widget.options?.toString() != oldWidget.options?.toString() ||
widget.pagination?.toJson()?.toString() != widget.pagination.toJson().toString() !=
oldWidget.pagination?.toJson()?.toString()) { oldWidget.pagination.toJson().toString()) {
loadData(); loadData();
} }
} }
@@ -233,10 +217,10 @@ class ChannelListCoreState extends State<ChannelListCore> {
class ChannelListController { class ChannelListController {
/// This function calls Stream's servers to load a list of channels. /// This function calls Stream's servers to load a list of channels.
/// If there is existing data, calling this function causes a reload. /// If there is existing data, calling this function causes a reload.
AsyncCallback loadData; AsyncCallback? loadData;
/// This function is used to load another page of data. Note, [loadData] /// This function is used to load another page of data. Note, [loadData]
/// should be used to populate the initial page of data. Calling /// should be used to populate the initial page of data. Calling
/// [paginateData] performs a query to load subsequent pages. /// [paginateData] performs a query to load subsequent pages.
AsyncCallback paginateData; AsyncCallback? paginateData;
} }
@@ -20,13 +20,12 @@ class ChannelsBloc extends StatefulWidget {
/// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and /// Creates a new [ChannelsBloc]. The parameter [child] must be supplied and
/// not null. /// not null.
const ChannelsBloc({ const ChannelsBloc({
Key key, Key? key,
@required this.child, required this.child,
this.lockChannelsOrder = false, this.lockChannelsOrder = false,
this.channelsComparator, this.channelsComparator,
this.shouldAddChannel, this.shouldAddChannel,
}) : assert(child != null, 'Parameter child should not be null.'), }) : super(key: key);
super(key: key);
/// The widget child /// The widget child
final Widget child; final Widget child;
@@ -36,18 +35,18 @@ class ChannelsBloc extends StatefulWidget {
final bool lockChannelsOrder; final bool lockChannelsOrder;
/// Comparator used to sort the channels when a message.new event is received /// Comparator used to sort the channels when a message.new event is received
final Comparator<Channel> channelsComparator; final Comparator<Channel>? channelsComparator;
/// Function used to evaluate if a channel should be added to the list when a /// Function used to evaluate if a channel should be added to the list when a
/// message.new event is received /// message.new event is received
final bool Function(Event) shouldAddChannel; final bool Function(Event)? shouldAddChannel;
@override @override
ChannelsBlocState createState() => ChannelsBlocState(); ChannelsBlocState createState() => ChannelsBlocState();
/// Use this method to get the current [ChannelsBlocState] instance /// Use this method to get the current [ChannelsBlocState] instance
static ChannelsBlocState of(BuildContext context) { static ChannelsBlocState of(BuildContext context) {
ChannelsBlocState streamChatState; ChannelsBlocState? streamChatState;
streamChatState = context.findAncestorStateOfType<ChannelsBlocState>(); streamChatState = context.findAncestorStateOfType<ChannelsBlocState>();
@@ -69,14 +68,15 @@ class ChannelsBlocState extends State<ChannelsBloc>
} }
/// The current channel list /// The current channel list
List<Channel> get channels => _channelsController.value; List<Channel>? get channels => _channelsController.value;
/// The current channel list as a stream /// The current channel list as a stream
Stream<List<Channel>> get channelsStream => _channelsController.stream; Stream<List<Channel>> get channelsStream => _channelsController.stream;
final _queryChannelsLoadingController = BehaviorSubject.seeded(false); final _queryChannelsLoadingController = BehaviorSubject.seeded(false);
final _channelsController = BehaviorSubject<List<Channel>>(); final BehaviorSubject<List<Channel>> _channelsController =
BehaviorSubject<List<Channel>>();
/// The stream notifying the state of queryChannel call /// The stream notifying the state of queryChannel call
Stream<bool> get queryChannelsLoading => Stream<bool> get queryChannelsLoading =>
@@ -88,10 +88,10 @@ class ChannelsBlocState extends State<ChannelsBloc>
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream /// Calls [client.queryChannels] updating [queryChannelsLoading] stream
Future<void> queryChannels({ Future<void> queryChannels({
Map<String, dynamic> filter, Map<String, dynamic>? filter,
List<SortOption<ChannelModel>> sortOptions, List<SortOption<ChannelModel>>? sortOptions,
PaginationParams paginationParams, PaginationParams paginationParams = const PaginationParams(limit: 30),
Map<String, dynamic> options, Map<String, dynamic>? options,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = StreamChatCore.of(context).client;
@@ -104,9 +104,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
} }
try { try {
final clear = paginationParams == null || final clear = paginationParams.offset == 0;
paginationParams.offset == null ||
paginationParams.offset == 0;
final oldChannels = List<Channel>.from(channels ?? []); final oldChannels = List<Channel>.from(channels ?? []);
var newChannels = <Channel>[]; var newChannels = <Channel>[];
await for (final channels in client.queryChannels( await for (final channels in client.queryChannels(
@@ -123,7 +121,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
_channelsController.add(temp); _channelsController.add(temp);
} }
if (_channelsController.hasValue && if (_channelsController.hasValue &&
_queryChannelsLoadingController.value) { _queryChannelsLoadingController.value!) {
_queryChannelsLoadingController.sink.add(false); _queryChannelsLoadingController.sink.add(false);
} }
} }
@@ -162,9 +160,9 @@ class ChannelsBlocState extends State<ChannelsBloc>
newChannels.insert(0, _hiddenChannels[hiddenIndex]); newChannels.insert(0, _hiddenChannels[hiddenIndex]);
_hiddenChannels.removeAt(hiddenIndex); _hiddenChannels.removeAt(hiddenIndex);
} else { } else {
if (client.state?.channels != null && if (client.state.channels != null &&
client.state?.channels[e.cid] != null) { client.state.channels?[e.cid] != null) {
newChannels.insert(0, client.state.channels[e.cid]); newChannels.insert(0, client.state.channels![e.cid]!);
} }
} }
} }
@@ -195,7 +193,7 @@ class ChannelsBlocState extends State<ChannelsBloc>
// ignore: cascade_invocations // ignore: cascade_invocations
final channel = e.channel; final channel = e.channel;
_channelsController.add(List.from( _channelsController.add(List.from(
(channels ?? [])..removeWhere((c) => c.cid == channel.cid))); (channels ?? [])..removeWhere((c) => c.cid == channel?.cid)));
})); }));
} }
@@ -9,34 +9,33 @@ class LazyLoadScrollView extends StatefulWidget {
/// Creates a new instance of [LazyLoadScrollView]. The parameter [child] /// Creates a new instance of [LazyLoadScrollView]. The parameter [child]
/// must be supplied and not null. /// must be supplied and not null.
const LazyLoadScrollView({ const LazyLoadScrollView({
Key key, Key? key,
@required this.child, required this.child,
this.onStartOfPage, this.onStartOfPage,
this.onEndOfPage, this.onEndOfPage,
this.onPageScrollStart, this.onPageScrollStart,
this.onPageScrollEnd, this.onPageScrollEnd,
this.onInBetweenOfPage, this.onInBetweenOfPage,
this.scrollOffset = 100, this.scrollOffset = 100,
}) : assert(child != null, 'Parameter child should not be null'), }) : super(key: key);
super(key: key);
/// The [Widget] that this widget watches for changes on /// The [Widget] that this widget watches for changes on
final Widget child; final Widget child;
/// Called when the [child] reaches the start of the list /// Called when the [child] reaches the start of the list
final AsyncCallback onStartOfPage; final AsyncCallback? onStartOfPage;
/// Called when the [child] reaches the end of the list /// Called when the [child] reaches the end of the list
final AsyncCallback onEndOfPage; final AsyncCallback? onEndOfPage;
/// Called when the list scrolling starts /// Called when the list scrolling starts
final VoidCallback onPageScrollStart; final VoidCallback? onPageScrollStart;
/// Called when the list scrolling ends /// Called when the list scrolling ends
final VoidCallback onPageScrollEnd; final VoidCallback? onPageScrollEnd;
/// Called every time the [child] is in-between the list /// Called every time the [child] is in-between the list
final VoidCallback onInBetweenOfPage; final VoidCallback? onInBetweenOfPage;
/// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels /// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels
final double scrollOffset; final double scrollOffset;
@@ -46,7 +45,7 @@ class LazyLoadScrollView extends StatefulWidget {
} }
class _LazyLoadScrollViewState extends State<LazyLoadScrollView> { class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
_LoadingStatus _loadMoreStatus = _LoadingStatus.stable; var _loadMoreStatus = _LoadingStatus.stable;
double _scrollPosition = 0; double _scrollPosition = 0;
@override @override
@@ -59,13 +58,13 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
bool _onNotification(ScrollNotification notification) { bool _onNotification(ScrollNotification notification) {
if (notification is ScrollStartNotification) { if (notification is ScrollStartNotification) {
if (widget.onPageScrollStart != null) { if (widget.onPageScrollStart != null) {
widget.onPageScrollStart(); widget.onPageScrollStart!();
return true; return true;
} }
} }
if (notification is ScrollEndNotification) { if (notification is ScrollEndNotification) {
if (widget.onPageScrollEnd != null) { if (widget.onPageScrollEnd != null) {
widget.onPageScrollEnd(); widget.onPageScrollEnd!();
return true; return true;
} }
} }
@@ -73,12 +72,12 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
final pixels = notification.metrics.pixels; final pixels = notification.metrics.pixels;
final maxScrollExtent = notification.metrics.maxScrollExtent; final maxScrollExtent = notification.metrics.maxScrollExtent;
final minScrollExtent = notification.metrics.minScrollExtent; final minScrollExtent = notification.metrics.minScrollExtent;
final scrollOffset = widget.scrollOffset ?? 0; final scrollOffset = widget.scrollOffset;
if (pixels > (minScrollExtent + scrollOffset) && if (pixels > (minScrollExtent + scrollOffset) &&
pixels < (maxScrollExtent - scrollOffset)) { pixels < (maxScrollExtent - scrollOffset)) {
if (widget.onInBetweenOfPage != null) { if (widget.onInBetweenOfPage != null) {
widget.onInBetweenOfPage(); widget.onInBetweenOfPage!();
return true; return true;
} }
} }
@@ -114,10 +113,10 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
} }
void _onEndOfPage() { void _onEndOfPage() {
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { if (_loadMoreStatus == _LoadingStatus.stable) {
if (widget.onEndOfPage != null) { if (widget.onEndOfPage != null) {
_loadMoreStatus = _LoadingStatus.loading; _loadMoreStatus = _LoadingStatus.loading;
widget.onEndOfPage().whenComplete(() { widget.onEndOfPage!().whenComplete(() {
_loadMoreStatus = _LoadingStatus.stable; _loadMoreStatus = _LoadingStatus.stable;
}); });
} }
@@ -125,10 +124,10 @@ class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
} }
void _onStartOfPage() { void _onStartOfPage() {
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.stable) { if (_loadMoreStatus == _LoadingStatus.stable) {
if (widget.onStartOfPage != null) { if (widget.onStartOfPage != null) {
_loadMoreStatus = _LoadingStatus.loading; _loadMoreStatus = _LoadingStatus.loading;
widget.onStartOfPage().whenComplete(() { widget.onStartOfPage!().whenComplete(() {
_loadMoreStatus = _LoadingStatus.stable; _loadMoreStatus = _LoadingStatus.stable;
}); });
} }
@@ -61,30 +61,20 @@ import 'package:stream_chat_flutter_core/src/typedef.dart';
class MessageListCore extends StatefulWidget { class MessageListCore extends StatefulWidget {
/// Instantiate a new [MessageListView]. /// Instantiate a new [MessageListView].
const MessageListCore({ const MessageListCore({
Key key, Key? key,
@required this.loadingBuilder, required this.loadingBuilder,
@required this.emptyBuilder, required this.emptyBuilder,
@required this.messageListBuilder, required this.messageListBuilder,
@required this.errorWidgetBuilder, required this.errorWidgetBuilder,
this.showScrollToBottom = true, this.showScrollToBottom = true,
this.parentMessage, this.parentMessage,
this.messageListController, this.messageListController,
this.messageFilter, this.messageFilter,
}) : assert(loadingBuilder != null, 'loadingBuilder should not be null'), }) : super(key: key);
assert(emptyBuilder != null, 'emptyBuilder should not be null'),
assert(
messageListBuilder != null,
'messageListBuilder should not be null',
),
assert(
errorWidgetBuilder != null,
'errorWidgetBuilder should not be null',
),
super(key: key);
/// A [MessageListController] allows pagination. /// A [MessageListController] allows pagination.
/// Use [ChannelListController.paginateData] pagination. /// Use [ChannelListController.paginateData] pagination.
final MessageListController messageListController; final MessageListController? messageListController;
/// Function called when messages are fetched /// Function called when messages are fetched
final Widget Function(BuildContext, List<Message>) messageListBuilder; final Widget Function(BuildContext, List<Message>) messageListBuilder;
@@ -108,10 +98,10 @@ class MessageListCore extends StatefulWidget {
/// If the current message belongs to a `thread`, this property represents the /// If the current message belongs to a `thread`, this property represents the
/// first message or the parent of the conversation. /// first message or the parent of the conversation.
final Message parentMessage; final Message? parentMessage;
/// Predicate used to filter messages /// Predicate used to filter messages
final bool Function(Message) messageFilter; final bool Function(Message)? messageFilter;
@override @override
MessageListCoreState createState() => MessageListCoreState(); MessageListCoreState createState() => MessageListCoreState();
@@ -119,41 +109,44 @@ class MessageListCore extends StatefulWidget {
/// The current state of the [MessageListCore]. /// The current state of the [MessageListCore].
class MessageListCoreState extends State<MessageListCore> { class MessageListCoreState extends State<MessageListCore> {
StreamChannelState _streamChannel; late StreamChannelState _streamChannel;
bool get _upToDate => _streamChannel.channel.state.isUpToDate; bool get _upToDate => _streamChannel.channel.state?.isUpToDate ?? true;
bool get _isThreadConversation => widget.parentMessage != null; bool get _isThreadConversation => widget.parentMessage != null;
OwnUser get _currentUser => _streamChannel.channel.client.state.user; OwnUser? get _currentUser => _streamChannel.channel.client.state.user;
var _messages = <Message>[]; var _messages = <Message>[];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final messagesStream = _isThreadConversation final messagesStream = _isThreadConversation
? _streamChannel.channel.state.threadsStream ? _streamChannel.channel.state?.threadsStream
.where((threads) => threads.containsKey(widget.parentMessage.id)) .where((threads) => threads.containsKey(widget.parentMessage!.id))
.map((threads) => threads[widget.parentMessage.id]) .map((threads) => threads[widget.parentMessage!.id])
: _streamChannel.channel.state?.messagesStream; : _streamChannel.channel.state?.messagesStream;
bool defaultFilter(Message m) { bool defaultFilter(Message m) {
final isMyMessage = m.user.id == _currentUser.id; final isMyMessage = m.user?.id == _currentUser?.id;
final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true; final isDeletedOrShadowed = m.isDeleted == true || m.shadowed == true;
if (isDeletedOrShadowed && !isMyMessage) return false; if (isDeletedOrShadowed && !isMyMessage) return false;
return true; return true;
} }
return StreamBuilder<List<Message>>( return StreamBuilder<List<Message>?>(
stream: messagesStream?.map((messages) => stream: messagesStream?.map((messages) =>
messages?.where(widget.messageFilter ?? defaultFilter)?.toList()), messages?.where(widget.messageFilter ?? defaultFilter).toList(
growable: false,
)),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return widget.errorWidgetBuilder(context, snapshot.error); return widget.errorWidgetBuilder(context, snapshot.error!);
} else if (!snapshot.hasData) { } else if (!snapshot.hasData) {
return widget.loadingBuilder(context); return widget.loadingBuilder(context);
} else { } else {
final messageList = snapshot.data?.reversed?.toList() ?? []; final messageList =
snapshot.data?.reversed.toList(growable: false) ?? [];
if (messageList.isEmpty && !_isThreadConversation) { if (messageList.isEmpty && !_isThreadConversation) {
if (_upToDate) { if (_upToDate) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
@@ -169,13 +162,14 @@ class MessageListCoreState extends State<MessageListCore> {
/// Fetches more messages with updated pagination and updates the widget. /// Fetches more messages with updated pagination and updates the widget.
/// ///
/// Optionally pass the fetch direction, defaults to [QueryDirection.bottom] /// Optionally pass the fetch direction, defaults to [QueryDirection.top]
Future<void> paginateData( Future<void> paginateData({
{QueryDirection direction = QueryDirection.bottom}) { QueryDirection direction = QueryDirection.top,
}) {
if (!_isThreadConversation) { if (!_isThreadConversation) {
return _streamChannel.queryMessages(direction: direction); return _streamChannel.queryMessages(direction: direction);
} else { } else {
return _streamChannel.getReplies(widget.parentMessage.id); return _streamChannel.getReplies(widget.parentMessage!.id);
} }
} }
@@ -184,11 +178,11 @@ class MessageListCoreState extends State<MessageListCore> {
_streamChannel = StreamChannel.of(context); _streamChannel = StreamChannel.of(context);
if (_isThreadConversation) { if (_isThreadConversation) {
_streamChannel.getReplies(widget.parentMessage.id); _streamChannel.getReplies(widget.parentMessage!.id);
} }
if (widget.messageListController != null) { if (widget.messageListController != null) {
widget.messageListController.paginateData = paginateData; widget.messageListController!.paginateData = paginateData;
} }
super.initState(); super.initState();
@@ -206,5 +200,5 @@ class MessageListCoreState extends State<MessageListCore> {
/// Controller used for paginating data in [ChannelListView] /// Controller used for paginating data in [ChannelListView]
class MessageListController { class MessageListController {
/// Call this function to load further data /// Call this function to load further data
Future<void> Function({QueryDirection direction}) paginateData; Future<void> Function({QueryDirection direction})? paginateData;
} }
@@ -13,10 +13,9 @@ import 'package:stream_chat_flutter_core/src/stream_chat_core.dart';
class MessageSearchBloc extends StatefulWidget { class MessageSearchBloc extends StatefulWidget {
/// Instantiate a new MessageSearchBloc /// Instantiate a new MessageSearchBloc
const MessageSearchBloc({ const MessageSearchBloc({
Key key, Key? key,
@required this.child, required this.child,
}) : assert(child != null, 'Parameter child should not be null.'), }) : super(key: key);
super(key: key);
/// The widget child /// The widget child
final Widget child; final Widget child;
@@ -26,7 +25,7 @@ class MessageSearchBloc extends StatefulWidget {
/// Use this method to get the current [MessageSearchBlocState] instance /// Use this method to get the current [MessageSearchBlocState] instance
static MessageSearchBlocState of(BuildContext context) { static MessageSearchBlocState of(BuildContext context) {
MessageSearchBlocState state; MessageSearchBlocState? state;
state = context.findAncestorStateOfType<MessageSearchBlocState>(); state = context.findAncestorStateOfType<MessageSearchBlocState>();
@@ -42,7 +41,7 @@ class MessageSearchBloc extends StatefulWidget {
class MessageSearchBlocState extends State<MessageSearchBloc> class MessageSearchBlocState extends State<MessageSearchBloc>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
/// The current messages list /// The current messages list
List<GetMessageResponse> get messageResponses => _messageResponses.value; List<GetMessageResponse>? get messageResponses => _messageResponses.value;
/// The current messages list as a stream /// The current messages list as a stream
Stream<List<GetMessageResponse>> get messagesStream => Stream<List<GetMessageResponse>> get messagesStream =>
@@ -59,11 +58,11 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
/// Calls [StreamChatClient.search] updating /// Calls [StreamChatClient.search] updating
/// [messagesStream] and [queryMessagesLoading] stream /// [messagesStream] and [queryMessagesLoading] stream
Future<void> search({ Future<void> search({
Map<String, dynamic> filter, required Map<String, dynamic> filter,
Map<String, dynamic> messageFilter, Map<String, dynamic>? messageFilter,
List<SortOption> sort, List<SortOption>? sort,
String query, String? query,
PaginationParams pagination, PaginationParams? pagination,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = StreamChatCore.of(context).client;
@@ -73,9 +72,7 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
_queryMessagesLoadingController.add(true); _queryMessagesLoadingController.add(true);
} }
try { try {
final clear = pagination == null || final clear = pagination == null || pagination.offset == 0;
pagination.offset == null ||
pagination.offset == 0;
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []); final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
@@ -93,7 +90,8 @@ class MessageSearchBlocState extends State<MessageSearchBloc>
final temp = oldMessages + messages.results; final temp = oldMessages + messages.results;
_messageResponses.add(temp); _messageResponses.add(temp);
} }
if (_messageResponses.hasValue && _queryMessagesLoadingController.value) { if (_messageResponses.hasValue &&
_queryMessagesLoadingController.value!) {
_queryMessagesLoadingController.add(false); _queryMessagesLoadingController.add(false);
} }
} catch (e, stk) { } catch (e, stk) {
@@ -42,31 +42,27 @@ class MessageSearchListCore extends StatefulWidget {
/// * [loadingBuilder] /// * [loadingBuilder]
/// * [childBuilder] /// * [childBuilder]
const MessageSearchListCore({ const MessageSearchListCore({
Key key, Key? key,
@required this.emptyBuilder, required this.emptyBuilder,
@required this.errorBuilder, required this.errorBuilder,
@required this.loadingBuilder, required this.loadingBuilder,
@required this.childBuilder, required this.childBuilder,
required this.filters,
this.messageQuery, this.messageQuery,
this.filters,
this.sortOptions, this.sortOptions,
this.paginationParams, this.paginationParams,
this.messageFilters, this.messageFilters,
this.messageSearchListController, this.messageSearchListController,
}) : assert(emptyBuilder != null, 'emptyBuilder should not be null'), }) : super(key: key);
assert(errorBuilder != null, 'errorBuilder should not be null'),
assert(loadingBuilder != null, 'loadingBuilder should not be null'),
assert(childBuilder != null, 'childBuilder should not be null'),
super(key: key);
/// A [MessageSearchListController] allows reloading and pagination. /// A [MessageSearchListController] allows reloading and pagination.
/// Use [MessageSearchListController.loadData] and /// Use [MessageSearchListController.loadData] and
/// [MessageSearchListController.paginateData] respectively for reloading and /// [MessageSearchListController.paginateData] respectively for reloading and
/// pagination. /// pagination.
final MessageSearchListController messageSearchListController; final MessageSearchListController? messageSearchListController;
/// Message String to search on /// Message String to search on
final String messageQuery; final String? messageQuery;
/// The query filters to use. /// The 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].
@@ -78,18 +74,18 @@ class MessageSearchListCore extends StatefulWidget {
/// provided. /// provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_ /// You can sort based on last_updated, last_message_at, updated_at, created_
/// at or member_count. Direction can be ascending or descending. /// at or member_count. Direction can be ascending or descending.
final List<SortOption> sortOptions; final List<SortOption>? sortOptions;
/// Pagination parameters /// Pagination parameters
/// 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].
/// You can also filter other built-in channel fields. /// You can also filter other built-in channel fields.
final Map<String, dynamic> messageFilters; final Map<String, dynamic>? messageFilters;
/// The builder that is used when the search messages are fetched /// The builder that is used when the search messages are fetched
final Widget Function(List<GetMessageResponse>) childBuilder; final Widget Function(List<GetMessageResponse>) childBuilder;
@@ -114,8 +110,8 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
super.didChangeDependencies(); super.didChangeDependencies();
loadData(); loadData();
if (widget.messageSearchListController != null) { if (widget.messageSearchListController != null) {
widget.messageSearchListController.loadData = loadData; widget.messageSearchListController!.loadData = loadData;
widget.messageSearchListController.paginateData = paginateData; widget.messageSearchListController!.paginateData = paginateData;
} }
} }
@@ -130,16 +126,16 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
stream: messageSearchBloc.messagesStream, stream: messageSearchBloc.messagesStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return widget.errorBuilder(context, snapshot.error); return widget.errorBuilder(context, snapshot.error!);
} }
if (!snapshot.hasData) { if (!snapshot.hasData) {
return widget.loadingBuilder(context); return widget.loadingBuilder(context);
} }
final items = snapshot.data; final items = snapshot.data!;
if (items.isEmpty) { if (items.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
return widget.childBuilder(snapshot.data); return widget.childBuilder(items);
}, },
); );
@@ -161,7 +157,7 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
return messageSearchBloc.search( return messageSearchBloc.search(
filter: widget.filters, filter: widget.filters,
sort: widget.sortOptions, sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith( pagination: widget.paginationParams!.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0, offset: messageSearchBloc.messageResponses?.length ?? 0,
), ),
query: widget.messageQuery, query: widget.messageQuery,
@@ -172,13 +168,13 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
@override @override
void didUpdateWidget(MessageSearchListCore oldWidget) { void didUpdateWidget(MessageSearchListCore oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.filters?.toString() != oldWidget.filters?.toString() || if (widget.filters.toString() != oldWidget.filters.toString() ||
jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) ||
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();
} }
} }
@@ -187,8 +183,8 @@ class MessageSearchListCoreState extends State<MessageSearchListCore> {
/// Controller used for paginating data in [ChannelListView] /// Controller used for paginating data in [ChannelListView]
class MessageSearchListController { class MessageSearchListController {
/// Call this function to reload data /// Call this function to reload data
AsyncCallback loadData; AsyncCallback? loadData;
/// Call this function to load further data /// Call this function to load further data
AsyncCallback paginateData; AsyncCallback? paginateData;
} }
@@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
@@ -21,16 +22,14 @@ class StreamChannel extends StatefulWidget {
/// Creates a new instance of [StreamChannel]. Both [child] and [client] must /// Creates a new instance of [StreamChannel]. Both [child] and [client] must
/// be supplied and not null. /// be supplied and not null.
const StreamChannel({ const StreamChannel({
Key key, Key? key,
@required this.child, required this.child,
@required this.channel, required this.channel,
this.showLoading = true, this.showLoading = true,
this.initialMessageId, this.initialMessageId,
}) : assert(child != null, 'Child should not be null'), }) : super(key: key);
assert(channel != null, 'Channel should not be null'),
super(key: key);
// ignore: public_member_api_docs /// The child of the widget
final Widget child; final Widget child;
/// [channel] specifies the channel with which child should be wrapped /// [channel] specifies the channel with which child should be wrapped
@@ -40,11 +39,11 @@ class StreamChannel extends StatefulWidget {
final bool showLoading; final bool showLoading;
/// If passed the channel will load from this particular message. /// If passed the channel will load from this particular message.
final String initialMessageId; final String? initialMessageId;
/// Use this method to get the current [StreamChannelState] instance /// Use this method to get the current [StreamChannelState] instance
static StreamChannelState of(BuildContext context) { static StreamChannelState of(BuildContext context) {
StreamChannelState streamChannelState; StreamChannelState? streamChannelState;
streamChannelState = context.findAncestorStateOfType<StreamChannelState>(); streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
@@ -67,11 +66,11 @@ class StreamChannelState extends State<StreamChannel> {
Channel get channel => widget.channel; Channel get channel => widget.channel;
/// InitialMessageId /// InitialMessageId
String get initialMessageId => widget.initialMessageId; String? get initialMessageId => widget.initialMessageId;
/// Current channel state stream /// Current channel state stream
Stream<ChannelState> get channelStateStream => Stream<ChannelState>? get channelStateStream =>
widget.channel.state.channelStateStream; widget.channel.state?.channelStateStream;
final _queryTopMessagesController = BehaviorSubject.seeded(false); final _queryTopMessagesController = BehaviorSubject.seeded(false);
final _queryBottomMessagesController = BehaviorSubject.seeded(false); final _queryBottomMessagesController = BehaviorSubject.seeded(false);
@@ -89,16 +88,18 @@ class StreamChannelState extends State<StreamChannel> {
int limit = 20, int limit = 20,
bool preferOffline = false, bool preferOffline = false,
}) async { }) async {
if (_topPaginationEnded || _queryTopMessagesController?.value == true) { if (_topPaginationEnded ||
_queryTopMessagesController.value == true ||
channel.state == null) {
return; return;
} }
_queryTopMessagesController.add(true); _queryTopMessagesController.add(true);
if (channel.state.messages.isEmpty) { if (channel.state!.messages.isEmpty) {
return _queryTopMessagesController.add(false); return _queryTopMessagesController.add(false);
} }
final oldestMessage = channel.state.messages.first; final oldestMessage = channel.state!.messages.first;
try { try {
final state = await queryBeforeMessage( final state = await queryBeforeMessage(
@@ -120,15 +121,16 @@ class StreamChannelState extends State<StreamChannel> {
bool preferOffline = false, bool preferOffline = false,
}) async { }) async {
if (_bottomPaginationEnded || if (_bottomPaginationEnded ||
_queryBottomMessagesController?.value == true || _queryBottomMessagesController.value == true ||
channel?.state?.isUpToDate == true) return; channel.state == null ||
channel.state!.isUpToDate == true) return;
_queryBottomMessagesController.add(true); _queryBottomMessagesController.add(true);
if (channel.state.messages.isEmpty) { if (channel.state!.messages.isEmpty) {
return _queryBottomMessagesController.add(false); return _queryBottomMessagesController.add(false);
} }
final recentMessage = channel.state.messages.last; final recentMessage = channel.state!.messages.last;
try { try {
final state = await queryAfterMessage( final state = await queryAfterMessage(
@@ -146,7 +148,7 @@ class StreamChannelState extends State<StreamChannel> {
} }
/// Calls [channel.query] updating [queryMessage] stream /// Calls [channel.query] updating [queryMessage] stream
Future<void> queryMessages({QueryDirection direction = QueryDirection.top}) { Future<void> queryMessages({QueryDirection? direction = QueryDirection.top}) {
if (direction == QueryDirection.top) return _queryTopMessages(); if (direction == QueryDirection.top) return _queryTopMessages();
return _queryBottomMessages(); return _queryBottomMessages();
} }
@@ -157,12 +159,14 @@ class StreamChannelState extends State<StreamChannel> {
int limit = 50, int limit = 50,
bool preferOffline = false, bool preferOffline = false,
}) async { }) async {
if (_topPaginationEnded || _queryTopMessagesController.value) return; if (_topPaginationEnded ||
_queryTopMessagesController.value! ||
channel.state == null) return;
_queryTopMessagesController.add(true); _queryTopMessagesController.add(true);
Message message; late Message message;
if (channel.state.threads.containsKey(parentId)) { if (channel.state!.threads.containsKey(parentId)) {
final thread = channel.state.threads[parentId]; final thread = channel.state!.threads[parentId]!;
if (thread.isNotEmpty) { if (thread.isNotEmpty) {
message = thread.first; message = thread.first;
} }
@@ -172,7 +176,7 @@ class StreamChannelState extends State<StreamChannel> {
final response = await channel.getReplies( final response = await channel.getReplies(
parentId, parentId,
PaginationParams( PaginationParams(
lessThan: message?.id, lessThan: message.id,
limit: limit, limit: limit,
), ),
preferOffline: preferOffline, preferOffline: preferOffline,
@@ -188,21 +192,26 @@ class StreamChannelState extends State<StreamChannel> {
/// Query the channel members and watchers /// Query the channel members and watchers
Future<void> queryMembersAndWatchers() async { Future<void> queryMembersAndWatchers() async {
await widget.channel.query( final _members = channel.state?.members;
membersPagination: PaginationParams( if (_members != null) {
offset: channel.state.members?.length, await widget.channel.query(
limit: 100, membersPagination: PaginationParams(
), offset: _members.length,
watchersPagination: PaginationParams( limit: 100,
offset: channel.state.watchers?.length, ),
limit: 100, watchersPagination: PaginationParams(
), offset: _members.length,
); limit: 100,
),
);
} else {
return;
}
} }
/// Loads channel at specific message /// Loads channel at specific message
Future<void> loadChannelAtMessage( Future<void> loadChannelAtMessage(
String messageId, { String? messageId, {
int before = 20, int before = 20,
int after = 20, int after = 20,
bool preferOffline = false, bool preferOffline = false,
@@ -214,15 +223,15 @@ class StreamChannelState extends State<StreamChannel> {
preferOffline: preferOffline, preferOffline: preferOffline,
); );
Future<void> _queryAtMessage({ Future<List<ChannelState>> _queryAtMessage({
String messageId, String? messageId,
int before = 20, int before = 20,
int after = 20, int after = 20,
bool preferOffline = false, bool preferOffline = false,
}) async { }) async {
if (channel.state == null) return; if (channel.state == null) return [];
channel.state.isUpToDate = false; channel.state!.isUpToDate = false;
channel.state.truncate(); channel.state!.truncate();
if (messageId == null) { if (messageId == null) {
await channel.query( await channel.query(
@@ -231,8 +240,8 @@ class StreamChannelState extends State<StreamChannel> {
), ),
preferOffline: preferOffline, preferOffline: preferOffline,
); );
channel.state.isUpToDate = true; channel.state!.isUpToDate = true;
return; return [];
} }
return Future.wait([ return Future.wait([
@@ -277,16 +286,15 @@ class StreamChannelState extends State<StreamChannel> {
preferOffline: preferOffline, preferOffline: preferOffline,
); );
if (state.messages.isEmpty || state.messages.length < limit) { if (state.messages.isEmpty || state.messages.length < limit) {
channel.state.isUpToDate = true; channel.state?.isUpToDate = true;
} }
return state; return state;
} }
/// ///
Future<Message> getMessage(String messageId) async { Future<Message> getMessage(String messageId) async {
var message = channel.state.messages.firstWhere( var message = channel.state?.messages.firstWhereOrNull(
(it) => it.id == messageId, (it) => it.id == messageId,
orElse: () => null,
); );
if (message == null) { if (message == null) {
final response = await channel.getMessagesById([messageId]); final response = await channel.getMessagesById([messageId]);
@@ -298,7 +306,7 @@ class StreamChannelState extends State<StreamChannel> {
/// Reloads the channel with latest message /// Reloads the channel with latest message
Future<void> reloadChannel() => _queryAtMessage(before: 30); Future<void> reloadChannel() => _queryAtMessage(before: 30);
List<Future<bool>> _futures; late List<Future<bool>> _futures;
Future<bool> get _loadChannelAtMessage async { Future<bool> get _loadChannelAtMessage async {
try { try {
@@ -358,9 +366,9 @@ class StreamChannelState extends State<StreamChannel> {
} }
return Center(child: Text(message)); return Center(child: Text(message));
} }
final initialized = snapshot.data[0]; final initialized = snapshot.data![0];
// ignore: avoid_bool_literals_in_conditional_expressions // ignore: avoid_bool_literals_in_conditional_expressions
final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; final dataLoaded = initialMessageId == null ? true : snapshot.data![1];
if (widget.showLoading && (!initialized || !dataLoaded)) { if (widget.showLoading && (!initialized || !dataLoaded)) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
@@ -38,14 +38,12 @@ class StreamChatCore extends StatefulWidget {
/// [StreamChatCore] is a stateful widget which reacts to system events and /// [StreamChatCore] is a stateful widget which reacts to system events and
/// updates Stream's connection status accordingly. /// updates Stream's connection status accordingly.
const StreamChatCore({ const StreamChatCore({
Key key, Key? key,
@required this.client, required this.client,
@required this.child, required this.child,
this.onBackgroundEventReceived, this.onBackgroundEventReceived,
this.backgroundKeepAlive = const Duration(minutes: 1), this.backgroundKeepAlive = const Duration(minutes: 1),
}) : assert(client != null, 'Stream Chat Client should not be null'), }) : super(key: key);
assert(child != null, 'Child should not be null'),
super(key: key);
/// Instance of Stream Chat Client containing information about the current /// Instance of Stream Chat Client containing information about the current
/// application. /// application.
@@ -61,14 +59,14 @@ class StreamChatCore extends StatefulWidget {
/// Handler called whenever the [client] receives a new [Event] while the app /// Handler called whenever the [client] receives a new [Event] while the app
/// is in background. Can be used to display various notifications depending /// is in background. Can be used to display various notifications depending
/// upon the [Event.type] /// upon the [Event.type]
final EventHandler onBackgroundEventReceived; final EventHandler? onBackgroundEventReceived;
@override @override
StreamChatCoreState createState() => StreamChatCoreState(); StreamChatCoreState createState() => StreamChatCoreState();
/// Use this method to get the current [StreamChatCoreState] instance /// Use this method to get the current [StreamChatCoreState] instance
static StreamChatCoreState of(BuildContext context) { static StreamChatCoreState of(BuildContext context) {
StreamChatCoreState streamChatState; StreamChatCoreState? streamChatState;
streamChatState = context.findAncestorStateOfType<StreamChatCoreState>(); streamChatState = context.findAncestorStateOfType<StreamChatCoreState>();
@@ -87,24 +85,24 @@ class StreamChatCoreState extends State<StreamChatCore>
/// Initialized client used throughout the application. /// Initialized client used throughout the application.
StreamChatClient get client => widget.client; StreamChatClient get client => widget.client;
Timer _disconnectTimer; Timer? _disconnectTimer;
@override @override
Widget build(BuildContext context) => widget.child; Widget build(BuildContext context) => widget.child;
/// The current user /// The current user
User get user => client.state?.user; User? get user => client.state.user;
/// The current user as a stream /// The current user as a stream
Stream<User> get userStream => client.state?.userStream; Stream<User?> get userStream => client.state.userStream;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance?.addObserver(this);
} }
StreamSubscription _eventSubscription; StreamSubscription? _eventSubscription;
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
@@ -119,15 +117,15 @@ class StreamChatCoreState extends State<StreamChatCore>
); );
void onTimerComplete() { void onTimerComplete() {
_eventSubscription.cancel(); _eventSubscription?.cancel();
client.disconnect(); client.disconnect();
} }
_disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete);
} else if (state == AppLifecycleState.resumed) { } else if (state == AppLifecycleState.resumed) {
if (_disconnectTimer?.isActive == true) { if (_disconnectTimer?.isActive == true) {
_eventSubscription.cancel(); _eventSubscription?.cancel();
_disconnectTimer.cancel(); _disconnectTimer?.cancel();
} else { } else {
if (client.wsConnectionStatus == ConnectionStatus.disconnected) { if (client.wsConnectionStatus == ConnectionStatus.disconnected) {
client.connect(); client.connect();
@@ -139,7 +137,7 @@ class StreamChatCoreState extends State<StreamChatCore>
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance?.removeObserver(this);
_eventSubscription?.cancel(); _eventSubscription?.cancel();
_disconnectTimer?.cancel(); _disconnectTimer?.cancel();
super.dispose(); super.dispose();
@@ -57,27 +57,23 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart';
class UserListCore extends StatefulWidget { class UserListCore extends StatefulWidget {
/// Instantiate a new [UserListCore] /// Instantiate a new [UserListCore]
const UserListCore({ const UserListCore({
@required this.errorBuilder, required this.errorBuilder,
@required this.emptyBuilder, required this.emptyBuilder,
@required this.loadingBuilder, required this.loadingBuilder,
@required this.listBuilder, required this.listBuilder,
Key key, Key? key,
this.filter, this.filter,
this.options, this.options,
this.sort, this.sort,
this.pagination, this.pagination,
this.groupAlphabetically = false, this.groupAlphabetically = false,
this.userListController, this.userListController,
}) : assert(errorBuilder != null, ''), }) : super(key: key);
assert(emptyBuilder != null, ''),
assert(loadingBuilder != null, ''),
assert(listBuilder != null, ''),
super(key: key);
/// A [UserListController] allows reloading and pagination. /// A [UserListController] allows reloading and pagination.
/// Use [UserListController.loadData] and [UserListController.paginateData] /// Use [UserListController.loadData] and [UserListController.paginateData]
/// respectively for reloading and pagination. /// respectively for reloading and pagination.
final UserListController userListController; final UserListController? userListController;
/// The builder that will be used in case of error /// The builder that will be used in case of error
final Widget Function(Object error) errorBuilder; final Widget Function(Object error) errorBuilder;
@@ -94,25 +90,25 @@ class UserListCore extends StatefulWidget {
/// The query filters to use. /// The 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].
/// You can also filter other built-in channel fields. /// You can also filter other built-in channel fields.
final Map<String, dynamic> filter; final Map<String, dynamic>? filter;
/// Query channels options. /// Query channels options.
/// ///
/// state: if true returns the Channel state /// state: if true returns the Channel state
/// watch: if true listen to changes to this Channel in real time. /// watch: if true listen to changes to this Channel in real time.
final Map<String, dynamic> options; final Map<String, dynamic>? options;
/// The sorting used for the channels matching the filters. /// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be /// Sorting is based on field and direction, multiple sorting options can be
/// provided. You can sort based on last_updated, last_message_at, updated_at, /// provided. You can sort based on last_updated, last_message_at, updated_at,
/// created_at or member_count. Direction can be ascending or descending. /// created_at or member_count. Direction can be ascending or descending.
final List<SortOption> sort; final List<SortOption>? sort;
/// Pagination parameters /// Pagination parameters
/// 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
/// ///
@@ -131,8 +127,8 @@ class UserListCoreState extends State<UserListCore>
super.didChangeDependencies(); super.didChangeDependencies();
loadData(); loadData();
if (widget.userListController != null) { if (widget.userListController != null) {
widget.userListController.loadData = loadData; widget.userListController!.loadData = loadData;
widget.userListController.paginateData = paginateData; widget.userListController!.paginateData = paginateData;
} }
} }
@@ -158,14 +154,14 @@ class UserListCoreState extends State<UserListCore>
} }
final groupedUsers = <String, List<User>>{}; final groupedUsers = <String, List<User>>{};
for (final e in temp) { for (final e in temp) {
final alphabet = e.name[0]?.toUpperCase(); final alphabet = e.name[0].toUpperCase();
groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e];
} }
final items = <ListItem>[]; final items = <ListItem>[];
for (final key in groupedUsers.keys) { for (final key in groupedUsers.keys) {
items items
..add(ListHeaderItem(key)) ..add(ListHeaderItem(key))
..addAll(groupedUsers[key].map((e) => ListUserItem(e))); ..addAll(groupedUsers[key]!.map((e) => ListUserItem(e)));
} }
return items; return items;
} }
@@ -180,12 +176,12 @@ class UserListCoreState extends State<UserListCore>
stream: _buildUserStream(usersBlocState), stream: _buildUserStream(usersBlocState),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasError) { if (snapshot.hasError) {
return widget.errorBuilder(snapshot.error); return widget.errorBuilder(snapshot.error!);
} }
if (!snapshot.hasData) { if (!snapshot.hasData) {
return widget.loadingBuilder(context); return widget.loadingBuilder(context);
} }
final items = snapshot.data; final items = snapshot.data!;
if (items.isEmpty) { if (items.isEmpty) {
return widget.emptyBuilder(context); return widget.emptyBuilder(context);
} }
@@ -210,7 +206,7 @@ class UserListCoreState extends State<UserListCore>
return _usersBloc.queryUsers( return _usersBloc.queryUsers(
filter: widget.filter, filter: widget.filter,
sort: widget.sort, sort: widget.sort,
pagination: widget.pagination.copyWith( pagination: widget.pagination!.copyWith(
offset: _usersBloc.users?.length ?? 0, offset: _usersBloc.users?.length ?? 0,
), ),
options: widget.options, options: widget.options,
@@ -223,8 +219,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.options?.toString() != oldWidget.options?.toString() || widget.options?.toString() != oldWidget.options?.toString() ||
widget.pagination?.toJson()?.toString() != widget.pagination?.toJson().toString() !=
oldWidget.pagination?.toJson()?.toString()) { oldWidget.pagination?.toJson().toString()) {
loadData(); loadData();
} }
} }
@@ -235,7 +231,7 @@ class UserListCoreState extends State<UserListCore>
/// with `USER`. /// with `USER`.
abstract class ListItem { abstract class ListItem {
/// Unique key per list item /// Unique key per list item
String get key { String? get key {
if (this is ListHeaderItem) { if (this is ListHeaderItem) {
final header = (this as ListHeaderItem).heading; final header = (this as ListHeaderItem).heading;
return 'HEADER-${header.toLowerCase()}'; return 'HEADER-${header.toLowerCase()}';
@@ -250,8 +246,8 @@ abstract class ListItem {
/// Helper function to build widget based on ListItem type /// Helper function to build widget based on ListItem type
// ignore: missing_return // ignore: missing_return
Widget when({ Widget when({
@required Widget Function(String heading) headerItem, required Widget Function(String heading) headerItem,
@required Widget Function(User user) userItem, required Widget Function(User user) userItem,
}) { }) {
if (this is ListHeaderItem) { if (this is ListHeaderItem) {
return headerItem((this as ListHeaderItem).heading); return headerItem((this as ListHeaderItem).heading);
@@ -259,6 +255,7 @@ abstract class ListItem {
if (this is ListUserItem) { if (this is ListUserItem) {
return userItem((this as ListUserItem).user); return userItem((this as ListUserItem).user);
} }
return Container();
} }
} }
@@ -283,8 +280,8 @@ class ListUserItem extends ListItem {
/// Controller used for paginating data in [ChannelListView] /// Controller used for paginating data in [ChannelListView]
class UserListController { class UserListController {
/// Call this function to reload data /// Call this function to reload data
AsyncCallback loadData; AsyncCallback? loadData;
/// Call this function to load further data /// Call this function to load further data
AsyncCallback paginateData; AsyncCallback? paginateData;
} }
@@ -14,13 +14,9 @@ class UsersBloc extends StatefulWidget {
/// Instantiate a new [UsersBloc]. The parameter [child] must be supplied and /// Instantiate a new [UsersBloc]. The parameter [child] must be supplied and
/// not null. /// not null.
const UsersBloc({ const UsersBloc({
@required this.child, required this.child,
Key key, Key? key,
}) : assert( }) : super(key: key);
child != null,
'When constructing a UsersBloc, the parameter '
'child should not be null.'),
super(key: key);
/// The widget child /// The widget child
final Widget child; final Widget child;
@@ -30,7 +26,7 @@ class UsersBloc extends StatefulWidget {
/// Use this method to get the current [UsersBlocState] instance /// Use this method to get the current [UsersBlocState] instance
static UsersBlocState of(BuildContext context) { static UsersBlocState of(BuildContext context) {
UsersBlocState state; UsersBlocState? state;
state = context.findAncestorStateOfType<UsersBlocState>(); state = context.findAncestorStateOfType<UsersBlocState>();
@@ -46,7 +42,7 @@ class UsersBloc extends StatefulWidget {
class UsersBlocState extends State<UsersBloc> class UsersBlocState extends State<UsersBloc>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
/// The current users list /// The current users list
List<User> get users => _usersController.value; List<User>? get users => _usersController.value;
/// The current users list as a stream /// The current users list as a stream
Stream<List<User>> get usersStream => _usersController.stream; Stream<List<User>> get usersStream => _usersController.stream;
@@ -62,10 +58,10 @@ class UsersBlocState extends State<UsersBloc>
/// 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)
Future<void> queryUsers({ Future<void> queryUsers({
Map<String, dynamic> filter, Map<String, dynamic>? filter,
List<SortOption> sort, List<SortOption>? sort,
Map<String, dynamic> options, Map<String, dynamic>? options,
PaginationParams pagination, PaginationParams? pagination,
}) async { }) async {
final client = StreamChatCore.of(context).client; final client = StreamChatCore.of(context).client;
@@ -76,9 +72,7 @@ class UsersBlocState extends State<UsersBloc>
} }
try { try {
final clear = pagination == null || final clear = pagination == null || pagination.offset == 0;
pagination.offset == null ||
pagination.offset == 0;
final oldUsers = List<User>.from(users ?? []); final oldUsers = List<User>.from(users ?? []);
@@ -95,7 +89,7 @@ class UsersBlocState extends State<UsersBloc>
final temp = oldUsers + usersResponse.users; final temp = oldUsers + usersResponse.users;
_usersController.add(temp); _usersController.add(temp);
} }
if (_usersController.hasValue && _queryUsersLoadingController.value) { if (_usersController.hasValue && _queryUsersLoadingController.value!) {
_queryUsersLoadingController.add(false); _queryUsersLoadingController.add(false);
} }
} catch (e, stk) { } catch (e, stk) {
@@ -8,23 +8,24 @@ issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
collection: ^1.15.0-nullsafety.4
flutter: flutter:
sdk: flutter sdk: flutter
meta: ^1.2.4 meta: ^1.3.0
rxdart: ^0.26.0 rxdart: ^0.26.0
stream_chat: ^1.5.0 stream_chat: ^1.5.2
dependency_overrides: dependency_overrides:
stream_chat: stream_chat:
path: ../stream_chat path: ../stream_chat
dev_dependencies: dev_dependencies:
fake_async: ^1.1.0 fake_async: ^1.2.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
mockito: ^4.1.3 mocktail: ^0.1.1
@@ -1,8 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:mockito/mockito.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/src/channel_list_core.dart'; import 'package:stream_chat_flutter_core/src/channel_list_core.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -30,58 +30,6 @@ void main() {
); );
} }
test(
'should throw assertion error in case listBuilder is null',
() {
final channelListCore = () => ChannelListCore(
listBuilder: null,
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(channelListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case loadingBuilder is null',
() {
final channelListCore = () => ChannelListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: null,
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(channelListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case emptyBuilder is null',
() {
final channelListCore = () => ChannelListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: null,
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(channelListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case errorBuilder is null',
() {
final channelListCore = () => ChannelListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: null,
);
expect(channelListCore, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should throw if ChannelListCore is used where ChannelsBloc is not present ' 'should throw if ChannelListCore is used where ChannelsBloc is not present '
'in the widget tree', 'in the widget tree',
@@ -116,7 +64,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -151,7 +100,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -185,15 +135,16 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).thenThrow(error); )).thenThrow(error);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -208,12 +159,12 @@ void main() {
expect(find.byKey(errorWidgetKey), findsOneWidget); expect(find.byKey(errorWidgetKey), findsOneWidget);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
}, },
); );
@@ -233,15 +184,16 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
const channels = <Channel>[]; const channels = <Channel>[];
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -256,12 +208,12 @@ void main() {
expect(find.byKey(emptyWidgetKey), findsOneWidget); expect(find.byKey(emptyWidgetKey), findsOneWidget);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
}, },
); );
@@ -281,15 +233,16 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -304,12 +257,12 @@ void main() {
expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.byKey(listWidgetKey), findsOneWidget);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
}, },
); );
@@ -337,15 +290,16 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -364,12 +318,12 @@ void main() {
expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.byKey(listWidgetKey), findsOneWidget);
expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
final channelListCoreState = tester.state<ChannelListCoreState>( final channelListCoreState = tester.state<ChannelListCoreState>(
find.byKey(channelListCoreKey), find.byKey(channelListCoreKey),
@@ -378,12 +332,12 @@ void main() {
final offset = channels.length; final offset = channels.length;
final paginatedChannels = _generateChannels(mockClient, offset: offset); final paginatedChannels = _generateChannels(mockClient, offset: offset);
final updatedPagination = pagination.copyWith(offset: offset); final updatedPagination = pagination.copyWith(offset: offset);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).thenAnswer((_) => Stream.value(paginatedChannels)); )).thenAnswer((_) => Stream.value(paginatedChannels));
await channelListCoreState.paginateData(); await channelListCoreState.paginateData();
@@ -398,12 +352,12 @@ void main() {
findsOneWidget, findsOneWidget,
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).called(1); )).called(1);
}, },
); );
@@ -411,8 +365,8 @@ void main() {
'should rebuild ChannelListCore with updated widget data ' 'should rebuild ChannelListCore with updated widget data '
'on calling setState()', 'on calling setState()',
(tester) async { (tester) async {
StateSetter _stateSetter; StateSetter? _stateSetter;
int limit = pagination.limit; var limit = pagination.limit;
const channelListCoreKey = Key('channelListCore'); const channelListCoreKey = Key('channelListCore');
const listWidgetKey = Key('listWidget'); const listWidgetKey = Key('listWidget');
@@ -435,15 +389,16 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -466,24 +421,24 @@ void main() {
expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.byKey(listWidgetKey), findsOneWidget);
expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget); expect(find.text(channels.map((e) => e.cid).join(',')), findsOneWidget);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
// Rebuilding ChannelListCore with new pagination limit // Rebuilding ChannelListCore with new pagination limit
_stateSetter(() => limit = 6); _stateSetter?.call(() => limit = 6);
final updatedChannels = _generateChannels(mockClient, count: limit); final updatedChannels = _generateChannels(mockClient, count: limit);
final updatedPagination = pagination.copyWith(limit: limit); final updatedPagination = pagination.copyWith(limit: limit);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).thenAnswer((_) => Stream.value(updatedChannels)); )).thenAnswer((_) => Stream.value(updatedChannels));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
@@ -493,12 +448,12 @@ void main() {
findsOneWidget, findsOneWidget,
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).called(1); )).called(1);
}, },
); );
} }
@@ -2,13 +2,17 @@ import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'matchers/channel_matcher.dart'; import 'matchers/channel_matcher.dart';
import 'mocks.dart'; import 'mocks.dart';
void main() { void main() {
setUpAll(() {
registerFallbackValue<PaginationParams>(const PaginationParams());
});
List<Channel> _generateChannels( List<Channel> _generateChannels(
StreamChatClient client, { StreamChatClient client, {
int count = 3, int count = 3,
@@ -28,18 +32,6 @@ void main() {
); );
} }
test(
'should throw assertion error if child is null',
() async {
const channelsBlocKey = Key('channelsBloc');
final channelsBloc = () => ChannelsBloc(
key: channelsBlocKey,
child: null,
);
expect(channelsBloc, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree', 'should throw if ChannelsBloc is used where StreamChat is not present in the widget tree',
(tester) async { (tester) async {
@@ -70,7 +62,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -101,7 +94,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -117,12 +111,12 @@ void main() {
final offlineChannels = _generateChannels(mockClient); final offlineChannels = _generateChannels(mockClient);
final onlineChannels = _generateChannels(mockClient, offset: 3); final onlineChannels = _generateChannels(mockClient, offset: 3);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.fromIterable([offlineChannels, onlineChannels]), (_) => Stream.fromIterable([offlineChannels, onlineChannels]),
); );
@@ -136,12 +130,12 @@ void main() {
]), ]),
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -163,7 +157,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -176,14 +171,14 @@ void main() {
find.byKey(channelsBlocKey), find.byKey(channelsBlocKey),
); );
final error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenThrow(error); )).thenThrow(error);
channelsBlocState.queryChannels(); channelsBlocState.queryChannels();
@@ -192,12 +187,12 @@ void main() {
emitsError(error), emitsError(error),
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -214,7 +209,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -229,38 +225,41 @@ void main() {
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
channelsBlocState.queryChannels(); const pagination = PaginationParams(limit: 3);
channelsBlocState.queryChannels(
paginationParams: pagination,
);
await expectLater( await expectLater(
channelsBlocState.channelsStream, channelsBlocState.channelsStream,
emits(isSameChannelListAs(channels)), emits(isSameChannelListAs(channels)),
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final offset = channels.length; final offset = channels.length;
final paginationParams = PaginationParams(offset: offset); final paginationParams = pagination.copyWith(offset: offset);
final newChannels = _generateChannels(mockClient, offset: offset); final newChannels = _generateChannels(mockClient, offset: offset);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: paginationParams, paginationParams: paginationParams,
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(newChannels), (_) => Stream.value(newChannels),
); );
@@ -277,12 +276,12 @@ void main() {
), ),
]); ]);
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: paginationParams, paginationParams: paginationParams,
)).called(1); )).called(1);
}, },
); );
@@ -299,7 +298,8 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
when(mockClient.on(any, any, any, any)).thenAnswer((_) => Stream.empty()); when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty());
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -313,39 +313,41 @@ void main() {
); );
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
final paginationParams = const PaginationParams(
limit: 3,
);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: paginationParams,
)).thenAnswer((_) => Stream.value(channels)); )).thenAnswer((_) => Stream.value(channels));
channelsBlocState.queryChannels(); channelsBlocState.queryChannels(
paginationParams: paginationParams,
);
await expectLater( await expectLater(
channelsBlocState.channelsStream, channelsBlocState.channelsStream,
emits(isSameChannelListAs(channels)), emits(isSameChannelListAs(channels)),
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: paginationParams,
)).called(1); )).called(1);
final offset = channels.length;
final paginationParams = PaginationParams(offset: offset);
final error = 'Error! Error! Error!'; final error = 'Error! Error! Error!';
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: paginationParams, paginationParams: paginationParams,
)).thenThrow(error); )).thenThrow(error);
channelsBlocState.queryChannels(paginationParams: paginationParams); channelsBlocState.queryChannels(paginationParams: paginationParams);
@@ -354,17 +356,17 @@ void main() {
emitsError(error), emitsError(error),
); );
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: paginationParams, paginationParams: paginationParams,
)).called(1); )).called(1);
}, },
); );
group('event controller test', () { group('event controller test', () {
StreamController<Event> eventController; late StreamController<Event> eventController;
setUp(() { setUp(() {
eventController = StreamController<Event>.broadcast(); eventController = StreamController<Event>.broadcast();
}); });
@@ -379,12 +381,12 @@ void main() {
child: Offstage(), child: Offstage(),
); );
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.channelHidden, EventType.channelHidden,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -399,23 +401,23 @@ void main() {
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final channelHiddenEvent = Event( final channelHiddenEvent = Event(
type: EventType.channelHidden, type: EventType.channelHidden,
@@ -435,7 +437,7 @@ void main() {
]), ]),
); );
verify(mockClient.on(EventType.channelHidden)).called(1); verify(() => mockClient.on(EventType.channelHidden)).called(1);
}, },
); );
@@ -450,13 +452,13 @@ void main() {
child: Offstage(), child: Offstage(),
); );
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.channelDeleted, EventType.channelDeleted,
EventType.notificationRemovedFromChannel, EventType.notificationRemovedFromChannel,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -471,31 +473,38 @@ void main() {
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final channelDeletedOrNotificationRemovedEvent = Event( final channelDeletedOrNotificationRemovedEvent = Event(
channel: EventChannel(cid: channels.first.cid), channel: EventChannel(
cid: channels.first.cid!,
updatedAt: DateTime.now(),
config: ChannelConfig(),
createdAt: DateTime.now(),
memberCount: 1,
),
); );
eventController.add(channelDeletedOrNotificationRemovedEvent); eventController.add(channelDeletedOrNotificationRemovedEvent);
final channelCid = channelDeletedOrNotificationRemovedEvent.channel.cid; final channelCid =
channelDeletedOrNotificationRemovedEvent.channel?.cid;
final newChannels = [...channels] final newChannels = [...channels]
..removeWhere((it) => it.cid == channelCid); ..removeWhere((it) => it.cid == channelCid);
@@ -507,10 +516,10 @@ void main() {
]), ]),
); );
verify(mockClient.on( verify(() => mockClient.on(
EventType.channelDeleted, EventType.channelDeleted,
EventType.notificationRemovedFromChannel, EventType.notificationRemovedFromChannel,
)).called(1); )).called(1);
}, },
); );
@@ -525,12 +534,12 @@ void main() {
child: Offstage(), child: Offstage(),
); );
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -545,23 +554,23 @@ void main() {
final channels = _generateChannels(mockClient); final channels = _generateChannels(mockClient);
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final messageNewEvent = Event( final messageNewEvent = Event(
type: EventType.messageNew, type: EventType.messageNew,
@@ -585,7 +594,7 @@ void main() {
]), ]),
); );
verify(mockClient.on(EventType.messageNew)).called(1); verify(() => mockClient.on(EventType.messageNew)).called(1);
}, },
); );
@@ -609,16 +618,16 @@ void main() {
shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid), shouldAddChannel: (e) => channels.map((it) => it.cid).contains(e.cid),
); );
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.channelHidden, EventType.channelHidden,
)).thenAnswer((_) => hiddenChannelEventController.stream); )).thenAnswer((_) => hiddenChannelEventController.stream);
when(mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
final messageNewEvent = Event( final messageNewEvent = Event(
type: EventType.messageNew, type: EventType.messageNew,
@@ -636,23 +645,23 @@ void main() {
find.byKey(channelsBlocKey), find.byKey(channelsBlocKey),
); );
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final channelHiddenEvent = Event( final channelHiddenEvent = Event(
type: EventType.channelHidden, type: EventType.channelHidden,
@@ -681,8 +690,8 @@ void main() {
]), ]),
); );
verify(mockClient.on(EventType.channelHidden)).called(1); verify(() => mockClient.on(EventType.channelHidden)).called(1);
verify(mockClient.on(EventType.messageNew)).called(1); verify(() => mockClient.on(EventType.messageNew)).called(1);
}, },
); );
@@ -703,14 +712,14 @@ void main() {
shouldAddChannel: (_) => true, shouldAddChannel: (_) => true,
); );
when(mockClient.state.channels).thenReturn(stateChannels); when(() => mockClient.state.channels).thenReturn(stateChannels);
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -723,23 +732,23 @@ void main() {
find.byKey(channelsBlocKey), find.byKey(channelsBlocKey),
); );
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final messageNewEvent = Event( final messageNewEvent = Event(
type: EventType.messageNew, type: EventType.messageNew,
@@ -749,7 +758,7 @@ void main() {
eventController.add(messageNewEvent); eventController.add(messageNewEvent);
final newChannels = [...channels] final newChannels = [...channels]
..insert(0, stateChannels[stateChannels.keys.first]); ..insert(0, stateChannels[stateChannels.keys.first]!);
await expectLater( await expectLater(
channelsBlocState.channelsStream, channelsBlocState.channelsStream,
@@ -759,7 +768,7 @@ void main() {
]), ]),
); );
verify(mockClient.on(EventType.messageNew)).called(1); verify(() => mockClient.on(EventType.messageNew)).called(1);
}, },
); );
@@ -783,12 +792,12 @@ void main() {
channelsComparator: channelComparator, channelsComparator: channelComparator,
); );
when(mockClient.on(any, any, any, any)) when(() => mockClient.on(any(), any(), any(), any()))
.thenAnswer((_) => Stream.empty()); .thenAnswer((_) => Stream.empty());
when(mockClient.on( when(() => mockClient.on(
EventType.messageNew, EventType.messageNew,
)).thenAnswer((_) => eventController.stream); )).thenAnswer((_) => eventController.stream);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -801,23 +810,23 @@ void main() {
find.byKey(channelsBlocKey), find.byKey(channelsBlocKey),
); );
when(mockClient.queryChannels( when(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) => Stream.value(channels), (_) => Stream.value(channels),
); );
await channelsBlocState.queryChannels(); await channelsBlocState.queryChannels();
verify(mockClient.queryChannels( verify(() => mockClient.queryChannels(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final messageNewEvent = Event( final messageNewEvent = Event(
type: EventType.messageNew, type: EventType.messageNew,
@@ -836,7 +845,7 @@ void main() {
]), ]),
); );
verify(mockClient.on(EventType.messageNew)).called(1); verify(() => mockClient.on(EventType.messageNew)).called(1);
}, },
); );
@@ -3,18 +3,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart';
void main() { void main() {
test(
'should throw assertion error if child is null',
() async {
const lazyLoadScrollViewKey = Key('lazyLoadScrollView');
final lazyLoadScrollView = () => LazyLoadScrollView(
key: lazyLoadScrollViewKey,
child: null,
);
expect(lazyLoadScrollView, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should render LazyLoadScrollView if child is provided', 'should render LazyLoadScrollView if child is provided',
(tester) async { (tester) async {
@@ -7,7 +7,7 @@ Matcher isSameChannelAs(Channel targetChannel) =>
class _IsSameChannelAs extends Matcher { class _IsSameChannelAs extends Matcher {
const _IsSameChannelAs({ const _IsSameChannelAs({
@required this.targetChannel, required this.targetChannel,
}) : assert(targetChannel != null, ''); }) : assert(targetChannel != null, '');
final Channel targetChannel; final Channel targetChannel;
@@ -26,7 +26,7 @@ Matcher isSameChannelListAs(List<Channel> targetChannelList) =>
class _IsSameChannelListAs extends Matcher { class _IsSameChannelListAs extends Matcher {
const _IsSameChannelListAs({ const _IsSameChannelListAs({
@required this.targetChannelList, required this.targetChannelList,
}) : assert(targetChannelList != null, ''); }) : assert(targetChannelList != null, '');
final List<Channel> targetChannelList; final List<Channel> targetChannelList;
@@ -1,4 +1,3 @@
import 'package:meta/meta.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -7,15 +6,15 @@ Matcher isSameMessageResponseAs(GetMessageResponse targetResponse) =>
class _IsSameMessageResponseAs extends Matcher { class _IsSameMessageResponseAs extends Matcher {
const _IsSameMessageResponseAs({ const _IsSameMessageResponseAs({
@required this.targetResponse, required this.targetResponse,
}) : assert(targetResponse != null, ''); });
final GetMessageResponse targetResponse; final GetMessageResponse targetResponse;
@override @override
bool matches(covariant GetMessageResponse response, Map matchState) => bool matches(covariant GetMessageResponse response, Map matchState) =>
response.message.id == targetResponse.message.id && response.message.id == targetResponse.message.id &&
response.channel.cid == targetResponse.channel.cid; response.channel?.cid == targetResponse.channel?.cid;
@override @override
Description describe(Description description) => Description describe(Description description) =>
@@ -28,15 +27,15 @@ Matcher isSameMessageResponseListAs(
class _IsSameMessageResponseListAs extends Matcher { class _IsSameMessageResponseListAs extends Matcher {
const _IsSameMessageResponseListAs({ const _IsSameMessageResponseListAs({
@required this.targetResponseList, required this.targetResponseList,
}) : assert(targetResponseList != null, ''); });
final List<GetMessageResponse> targetResponseList; final List<GetMessageResponse> targetResponseList;
@override @override
bool matches( bool matches(
covariant List<GetMessageResponse> responseList, Map matchState) { covariant List<GetMessageResponse> responseList, Map matchState) {
bool matches = true; var matches = true;
for (var i = 0; i < responseList.length; i++) { for (var i = 0; i < responseList.length; i++) {
final response = responseList[i]; final response = responseList[i];
final targetResponse = targetResponseList[i]; final targetResponse = targetResponseList[i];
@@ -7,7 +7,7 @@ Matcher isSameMessageAs(Message targetMessage) =>
class _IsSameMessageAs extends Matcher { class _IsSameMessageAs extends Matcher {
const _IsSameMessageAs({ const _IsSameMessageAs({
@required this.targetMessage, required this.targetMessage,
}) : assert(targetMessage != null, ''); }) : assert(targetMessage != null, '');
final Message targetMessage; final Message targetMessage;
@@ -26,7 +26,7 @@ Matcher isSameMessageListAs(List<Message> targetMessageList) =>
class _IsSameMessageListAs extends Matcher { class _IsSameMessageListAs extends Matcher {
const _IsSameMessageListAs({ const _IsSameMessageListAs({
@required this.targetMessageList, required this.targetMessageList,
}) : assert(targetMessageList != null, ''); }) : assert(targetMessageList != null, '');
final List<Message> targetMessageList; final List<Message> targetMessageList;
@@ -6,7 +6,7 @@ Matcher isSameUserAs(User targetUser) => _IsSameUserAs(targetUser: targetUser);
class _IsSameUserAs extends Matcher { class _IsSameUserAs extends Matcher {
const _IsSameUserAs({ const _IsSameUserAs({
@required this.targetUser, required this.targetUser,
}) : assert(targetUser != null, ''); }) : assert(targetUser != null, '');
final User targetUser; final User targetUser;
@@ -24,7 +24,7 @@ Matcher isSameUserListAs(List<User> targetUserList) =>
class _IsSameUserListAs extends Matcher { class _IsSameUserListAs extends Matcher {
const _IsSameUserListAs({ const _IsSameUserListAs({
@required this.targetUserList, required this.targetUserList,
}) : assert(targetUserList != null, ''); }) : assert(targetUserList != null, '');
final List<User> targetUserList; final List<User> targetUserList;
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/src/message_list_core.dart'; import 'package:stream_chat_flutter_core/src/message_list_core.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -62,61 +62,6 @@ void main() {
return threads ? threadMessages : messages; return threads ? threadMessages : messages;
} }
test(
'should throw assertion error in case messageListBuilder is null',
() {
final messageListCore = () => MessageListCore(
messageListBuilder: null,
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorWidgetBuilder: (BuildContext context, Object error) =>
Offstage(),
);
expect(messageListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case loadingBuilder is null',
() {
final messageListCore = () => MessageListCore(
messageListBuilder: (_, __) => Offstage(),
loadingBuilder: null,
emptyBuilder: (BuildContext context) => Offstage(),
errorWidgetBuilder: (BuildContext context, Object error) =>
Offstage(),
);
expect(messageListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case emptyBuilder is null',
() {
final messageListCore = () => MessageListCore(
messageListBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: null,
errorWidgetBuilder: (BuildContext context, Object error) =>
Offstage(),
);
expect(messageListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case errorWidgetBuilder is null',
() {
final messageListCore = () => MessageListCore(
messageListBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorWidgetBuilder: null,
);
expect(messageListCore, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should throw if MessageListCore is used where StreamChannel is not present ' 'should throw if MessageListCore is used where StreamChannel is not present '
'in the widget tree', 'in the widget tree',
@@ -150,8 +95,11 @@ void main() {
); );
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value([]));
await tester.pumpWidget( await tester.pumpWidget(
StreamChannel( StreamChannel(
@@ -182,7 +130,10 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value([]));
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
await tester.pumpWidget( await tester.pumpWidget(
StreamChannel( StreamChannel(
@@ -213,11 +164,11 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockChannel.state.messagesStream) when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.error(error)); .thenAnswer((_) => Stream.error(error));
await tester.pumpWidget( await tester.pumpWidget(
@@ -252,11 +203,11 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
const messages = <Message>[]; const messages = <Message>[];
when(mockChannel.state.messagesStream) when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages)); .thenAnswer((_) => Stream.value(messages));
await tester.pumpWidget( await tester.pumpWidget(
@@ -291,11 +242,18 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(false); when(() => mockChannel.state.isUpToDate).thenReturn(false);
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
when(() => mockChannel.query(
options: any(named: 'options'),
membersPagination: any(named: 'membersPagination'),
messagesPagination: any(named: 'messagesPagination'),
preferOffline: any(named: 'preferOffline'),
watchersPagination: any(named: 'watchersPagination'),
)).thenAnswer((_) async => ChannelState());
const messages = <Message>[]; const messages = <Message>[];
when(mockChannel.state.messagesStream) when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages)); .thenAnswer((_) => Stream.value(messages));
await tester.pumpWidget( await tester.pumpWidget(
@@ -335,11 +293,11 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
final messages = _generateMessages(); final messages = _generateMessages();
when(mockChannel.state.messagesStream) when(() => mockChannel.state.messagesStream)
.thenAnswer((_) => Stream.value(messages)); .thenAnswer((_) => Stream.value(messages));
await tester.pumpWidget( await tester.pumpWidget(
@@ -382,13 +340,13 @@ void main() {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
when(mockChannel.state.isUpToDate).thenReturn(true); when(() => mockChannel.state.isUpToDate).thenReturn(true);
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
final threads = {parentMessage.id: messages}; final threads = {parentMessage.id: messages};
when(mockChannel.state.threads).thenReturn(threads); when(() => mockChannel.state.threads).thenReturn(threads);
when(mockChannel.state.threadsStream) when(() => mockChannel.state.threadsStream)
.thenAnswer((_) => Stream.value(threads)); .thenAnswer((_) => Stream.value(threads));
await tester.pumpWidget( await tester.pumpWidget(
@@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/src/message_search_bloc.dart'; import 'package:stream_chat_flutter_core/src/message_search_bloc.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -23,24 +23,12 @@ void main() {
text: 'testTextData$index', text: 'testTextData$index',
) )
..channel = ChannelModel( ..channel = ChannelModel(
cid: 'testCid', cid: 'testCid:id',
); );
}, },
); );
} }
test(
'should throw assertion error if child is null',
() async {
const messageSearchBlocKey = Key('messageSearchBloc');
final messageSearchBloc = () => MessageSearchBloc(
key: messageSearchBlocKey,
child: null,
);
expect(messageSearchBloc, throwsA(isA<AssertionError>()));
},
);
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',
@@ -62,7 +50,7 @@ void main() {
); );
try { try {
await usersBlocState.search(); await usersBlocState.search(filter: {});
} catch (e) { } catch (e) {
expect(e, isInstanceOf<Exception>()); expect(e, isInstanceOf<Exception>());
} }
@@ -93,30 +81,30 @@ void main() {
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
messageSearchBlocState.search(); messageSearchBlocState.search(filter: {});
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
emits(isSameMessageResponseListAs(messageResponseList)), emits(isSameMessageResponseListAs(messageResponseList)),
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -144,28 +132,28 @@ void main() {
); );
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenThrow(error); )).thenThrow(error);
messageSearchBlocState.search(); messageSearchBlocState.search(filter: {});
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
emitsError(error), emitsError(error),
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -196,47 +184,47 @@ void main() {
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
messageSearchBlocState.search(); messageSearchBlocState.search(filter: {});
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
emits(isSameMessageResponseListAs(messageResponseList)), emits(isSameMessageResponseListAs(messageResponseList)),
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).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 pagination = PaginationParams(offset: offset);
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer( )).thenAnswer(
(_) async => (_) async =>
SearchMessagesResponse()..results = paginatedMessageResponseList, SearchMessagesResponse()..results = paginatedMessageResponseList,
); );
messageSearchBlocState.search(pagination: pagination); messageSearchBlocState.search(pagination: pagination, filter: {});
await Future.wait([ await Future.wait([
expectLater( expectLater(
@@ -251,13 +239,13 @@ void main() {
), ),
]); ]);
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
}, },
); );
@@ -288,57 +276,57 @@ void main() {
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
messageSearchBlocState.search(); messageSearchBlocState.search(filter: {});
await expectLater( await expectLater(
messageSearchBlocState.messagesStream, messageSearchBlocState.messagesStream,
emits(isSameMessageResponseListAs(messageResponseList)), emits(isSameMessageResponseListAs(messageResponseList)),
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
final offset = messageResponseList.length; final offset = messageResponseList.length;
final pagination = PaginationParams(offset: offset); final pagination = PaginationParams(offset: offset);
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).thenThrow(error); )).thenThrow(error);
messageSearchBlocState.search(pagination: pagination); messageSearchBlocState.search(pagination: pagination, filter: {});
await expectLater( await expectLater(
messageSearchBlocState.queryMessagesLoading, messageSearchBlocState.queryMessagesLoading,
emitsError(error), emitsError(error),
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
}, },
); );
} }
@@ -1,8 +1,8 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter_core/src/message_search_list_core.dart'; import 'package:stream_chat_flutter_core/src/message_search_list_core.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -10,74 +10,21 @@ void main() {
List<GetMessageResponse> _generateMessages({ List<GetMessageResponse> _generateMessages({
int count = 3, int count = 3,
int offset = 0, int offset = 0,
}) { }) =>
return List.generate( List.generate(
count, count,
(index) { (index) {
index = index + offset; index = index + offset;
return GetMessageResponse() return GetMessageResponse()
..message = Message( ..message = Message(
id: 'testId$index', id: 'testId$index',
text: 'testTextData$index', text: 'testTextData$index',
) )
..channel = ChannelModel( ..channel = ChannelModel(
cid: 'testCid', cid: 'test:Cid',
); );
}, },
); );
}
test(
'should throw assertion error in case childBuilder is null',
() {
final messageSearchListCore = () => MessageSearchListCore(
childBuilder: null,
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(messageSearchListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case loadingBuilder is null',
() {
final messageSearchListCore = () => MessageSearchListCore(
childBuilder: (List<GetMessageResponse> messages) => Offstage(),
loadingBuilder: null,
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(messageSearchListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case emptyBuilder is null',
() {
final messageSearchListCore = () => MessageSearchListCore(
childBuilder: (List<GetMessageResponse> messages) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: null,
errorBuilder: (BuildContext context, Object error) => Offstage(),
);
expect(messageSearchListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case errorBuilder is null',
() {
final messageSearchListCore = () => MessageSearchListCore(
childBuilder: (List<GetMessageResponse> messages) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: null,
);
expect(messageSearchListCore, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should throw if MessageSearchListCore is used where MessageSearchBloc ' 'should throw if MessageSearchListCore is used where MessageSearchBloc '
@@ -86,10 +33,11 @@ void main() {
const messageSearchListCoreKey = Key('messageSearchListCore'); const messageSearchListCoreKey = Key('messageSearchListCore');
final messageSearchListCore = MessageSearchListCore( final messageSearchListCore = MessageSearchListCore(
key: messageSearchListCoreKey, key: messageSearchListCoreKey,
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse>? messages) => const Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => const Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => const Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object? error) => const Offstage(),
filters: const {},
); );
await tester.pumpWidget(messageSearchListCore); await tester.pumpWidget(messageSearchListCore);
@@ -109,7 +57,8 @@ void main() {
childBuilder: (List<GetMessageResponse> messages) => Offstage(), childBuilder: (List<GetMessageResponse> messages) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object? error) => Offstage(),
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
@@ -140,6 +89,7 @@ void main() {
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(),
messageSearchListController: controller, messageSearchListController: controller,
filters: {},
); );
expect(controller.loadData, isNull); expect(controller.loadData, isNull);
@@ -175,18 +125,19 @@ void main() {
errorBuilder: (BuildContext context, Object error) => Offstage( errorBuilder: (BuildContext context, Object error) => Offstage(
key: errorWidgetKey, key: errorWidgetKey,
), ),
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenThrow(error); )).thenThrow(error);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -201,13 +152,13 @@ void main() {
expect(find.byKey(errorWidgetKey), findsOneWidget); expect(find.byKey(errorWidgetKey), findsOneWidget);
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -223,18 +174,19 @@ void main() {
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey), emptyBuilder: (BuildContext context) => Offstage(key: emptyWidgetKey),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(),
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
final messageResponseList = <GetMessageResponse>[]; final messageResponseList = <GetMessageResponse>[];
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
@@ -251,13 +203,13 @@ void main() {
expect(find.byKey(emptyWidgetKey), findsOneWidget); expect(find.byKey(emptyWidgetKey), findsOneWidget);
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -274,18 +226,19 @@ void main() {
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(),
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
@@ -302,13 +255,13 @@ void main() {
expect(find.byKey(childWidgetKey), findsOneWidget); expect(find.byKey(childWidgetKey), findsOneWidget);
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: anyNamed('paginationParams'), paginationParams: any(named: 'paginationParams'),
)).called(1); )).called(1);
}, },
); );
@@ -324,25 +277,26 @@ void main() {
childBuilder: (List<GetMessageResponse> messages) => Container( childBuilder: (List<GetMessageResponse> messages) => Container(
key: childWidgetKey, key: childWidgetKey,
child: Text( child: Text(
messages.map((e) => '${e.channel.cid}-${e.message.id}').join(','), messages.map((e) => '${e.channel?.cid}-${e.message.id}').join(','),
), ),
), ),
loadingBuilder: (BuildContext context) => Offstage(), loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(),
paginationParams: pagination, paginationParams: pagination,
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
@@ -364,19 +318,19 @@ void main() {
expect( expect(
find.text( find.text(
messageResponseList messageResponseList
.map((e) => '${e.channel.cid}-${e.message.id}') .map((e) => '${e.channel?.cid}-${e.message.id}')
.join(','), .join(','),
), ),
findsOneWidget, findsOneWidget,
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
final messageSearchListCoreState = final messageSearchListCoreState =
tester.state<MessageSearchListCoreState>( tester.state<MessageSearchListCoreState>(
@@ -386,13 +340,13 @@ void main() {
final offset = messageResponseList.length; final offset = messageResponseList.length;
final paginatedMessageResponseList = _generateMessages(offset: offset); final paginatedMessageResponseList = _generateMessages(offset: offset);
final updatedPagination = pagination.copyWith(offset: offset); final updatedPagination = pagination.copyWith(offset: offset);
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).thenAnswer( )).thenAnswer(
(_) async => (_) async =>
SearchMessagesResponse()..results = paginatedMessageResponseList, SearchMessagesResponse()..results = paginatedMessageResponseList,
); );
@@ -406,17 +360,17 @@ void main() {
find.text([ find.text([
...messageResponseList, ...messageResponseList,
...paginatedMessageResponseList, ...paginatedMessageResponseList,
].map((e) => '${e.channel.cid}-${e.message.id}').join(',')), ].map((e) => '${e.channel?.cid}-${e.message.id}').join(',')),
findsOneWidget, findsOneWidget,
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).called(1); )).called(1);
}, },
); );
@@ -426,8 +380,8 @@ void main() {
(tester) async { (tester) async {
const pagination = PaginationParams(); const pagination = PaginationParams();
StateSetter _stateSetter; StateSetter? _stateSetter;
int limit = pagination.limit; var limit = pagination.limit;
const messageSearchListCoreKey = Key('messageSearchListCore'); const messageSearchListCoreKey = Key('messageSearchListCore');
const childWidgetKey = Key('childWidget'); const childWidgetKey = Key('childWidget');
@@ -438,7 +392,7 @@ void main() {
key: childWidgetKey, key: childWidgetKey,
child: Text( child: Text(
messages messages
.map((e) => '${e.channel.cid}-${e.message.id}') .map((e) => '${e.channel?.cid}-${e.message.id}')
.join(','), .join(','),
), ),
), ),
@@ -446,18 +400,19 @@ void main() {
emptyBuilder: (BuildContext context) => Offstage(), emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (BuildContext context, Object error) => Offstage(), errorBuilder: (BuildContext context, Object error) => Offstage(),
paginationParams: pagination.copyWith(limit: limit), paginationParams: pagination.copyWith(limit: limit),
filters: {},
); );
final mockClient = MockClient(); final mockClient = MockClient();
final messageResponseList = _generateMessages(); final messageResponseList = _generateMessages();
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).thenAnswer( )).thenAnswer(
(_) async => SearchMessagesResponse()..results = messageResponseList, (_) async => SearchMessagesResponse()..results = messageResponseList,
); );
@@ -483,32 +438,32 @@ void main() {
expect( expect(
find.text( find.text(
messageResponseList messageResponseList
.map((e) => '${e.channel.cid}-${e.message.id}') .map((e) => '${e.channel?.cid}-${e.message.id}')
.join(','), .join(','),
), ),
findsOneWidget, findsOneWidget,
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: pagination, paginationParams: pagination,
)).called(1); )).called(1);
// Rebuilding MessageSearchListCore with new pagination limit // Rebuilding MessageSearchListCore with new pagination limit
_stateSetter(() => limit = 6); _stateSetter?.call(() => limit = 6);
final updatedMessageResponseList = _generateMessages(count: limit); final updatedMessageResponseList = _generateMessages(count: limit);
final updatedPagination = pagination.copyWith(limit: limit); final updatedPagination = pagination.copyWith(limit: limit);
when(mockClient.search( when(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).thenAnswer( )).thenAnswer(
(_) async => (_) async =>
SearchMessagesResponse()..results = updatedMessageResponseList, SearchMessagesResponse()..results = updatedMessageResponseList,
); );
@@ -518,18 +473,18 @@ void main() {
expect(find.byKey(childWidgetKey), findsOneWidget); expect(find.byKey(childWidgetKey), findsOneWidget);
expect( expect(
find.text(updatedMessageResponseList find.text(updatedMessageResponseList
.map((e) => '${e.channel.cid}-${e.message.id}') .map((e) => '${e.channel?.cid}-${e.message.id}')
.join(',')), .join(',')),
findsOneWidget, findsOneWidget,
); );
verify(mockClient.search( verify(() => mockClient.search(
any, any(),
query: anyNamed('query'), query: any(named: 'query'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
messageFilters: anyNamed('messageFilters'), messageFilters: any(named: 'messageFilters'),
paginationParams: updatedPagination, paginationParams: updatedPagination,
)).called(1); )).called(1);
}, },
); );
} }
@@ -1,19 +1,20 @@
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
class MockLogger extends Mock implements Logger {} class MockLogger extends Mock implements Logger {}
class MockClient extends Mock implements StreamChatClient { class MockClient extends Mock implements StreamChatClient {
@override
final Logger logger = MockLogger(); final Logger logger = MockLogger();
ClientState _state; ClientState? _state;
@override @override
ClientState get state => _state ??= MockClientState(); ClientState get state => _state ??= MockClientState();
} }
class MockClientState extends Mock implements ClientState { class MockClientState extends Mock implements ClientState {
OwnUser _user; OwnUser? _user;
@override @override
OwnUser get user => _user ??= OwnUser( OwnUser get user => _user ??= OwnUser(
@@ -25,12 +26,12 @@ class MockClientState extends Mock implements ClientState {
} }
class MockChannel extends Mock implements Channel { class MockChannel extends Mock implements Channel {
ChannelClientState _state; ChannelClientState? _state;
@override @override
ChannelClientState get state => _state ??= MockChannelState(); ChannelClientState get state => _state ??= MockChannelState();
StreamChatClient _client; StreamChatClient? _client;
@override @override
StreamChatClient get client => _client ??= MockClient(); StreamChatClient get client => _client ??= MockClient();
@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -62,39 +62,13 @@ void main() {
return threads ? threadMessages : messages; return threads ? threadMessages : messages;
} }
test(
'should throw assertion error if child is null',
() async {
final mockChannel = MockChannel();
const streamChannelKey = Key('streamChannel');
final streamChannel = () => StreamChannel(
key: streamChannelKey,
channel: mockChannel,
child: null,
);
expect(streamChannel, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error if channel is null',
() async {
const streamChannelKey = Key('streamChannel');
final streamChannel = () => StreamChannel(
key: streamChannelKey,
child: Offstage(),
channel: null,
);
expect(streamChannel, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should render StreamChannel if both channel and child is provided', 'should render StreamChannel if both channel and child is provided',
(tester) async { (tester) async {
final mockChannel = MockChannel(); final mockChannel = MockChannel();
const streamChannelKey = Key('streamChannel'); const streamChannelKey = Key('streamChannel');
const childKey = Key('childKey'); const childKey = Key('childKey');
when(() => mockChannel.initialized).thenAnswer((_) => Future.value(true));
final streamChannel = StreamChannel( final streamChannel = StreamChannel(
key: streamChannelKey, key: streamChannelKey,
channel: mockChannel, channel: mockChannel,
@@ -121,8 +95,13 @@ void main() {
); );
final errorMessage = 'Error! Error! Error!'; final errorMessage = 'Error! Error! Error!';
final error = DioError(type: DioErrorType.response, error: errorMessage); final error = DioError(
when(mockChannel.initialized).thenAnswer((_) => Future.error(error)); type: DioErrorType.response,
error: errorMessage,
requestOptions: RequestOptions(path: ''),
);
when(() => mockChannel.initialized)
.thenAnswer((_) => Future.error(error));
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -135,7 +114,7 @@ void main() {
expect(find.text(errorMessage), findsOneWidget); expect(find.text(errorMessage), findsOneWidget);
verify(mockChannel.initialized).called(1); verify(() => mockChannel.initialized).called(1);
}, },
); );
@@ -153,7 +132,7 @@ void main() {
showLoading: true, showLoading: true,
); );
when(mockChannel.initialized).thenAnswer((_) async => false); when(() => mockChannel.initialized).thenAnswer((_) async => false);
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -166,7 +145,7 @@ void main() {
expect(find.byType(CircularProgressIndicator), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsOneWidget);
verify(mockChannel.initialized).called(1); verify(() => mockChannel.initialized).called(1);
}, },
); );
@@ -183,15 +162,15 @@ void main() {
initialMessageId: 'testInitialMessageId', initialMessageId: 'testInitialMessageId',
); );
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
final messages = _generateMessages(); final messages = _generateMessages();
when(mockChannel.query( when(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: anyNamed('messagesPagination'), messagesPagination: any(named: 'messagesPagination'),
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).thenAnswer((_) async => ChannelState(messages: messages)); )).thenAnswer((_) async => ChannelState(messages: messages));
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -202,14 +181,14 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
verify(mockChannel.initialized).called(1); verify(() => mockChannel.initialized).called(1);
verify(mockChannel.query( verify(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: anyNamed('messagesPagination'), messagesPagination: any(named: 'messagesPagination'),
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).called( )).called(
2, // Fetching After messages + Fetching Before messages, 2, // Fetching After messages + Fetching Before messages,
); );
}, },
@@ -219,7 +198,7 @@ void main() {
'should rebuild StreamChannel with updated widget data ' 'should rebuild StreamChannel with updated widget data '
'on calling setState()', 'on calling setState()',
(tester) async { (tester) async {
StateSetter _stateSetter; StateSetter? _stateSetter;
var initialMessageId = 'testInitialMessageId'; var initialMessageId = 'testInitialMessageId';
@@ -244,25 +223,25 @@ void main() {
limit: 20, limit: 20,
); );
when(mockChannel.initialized).thenAnswer((_) async => true); when(() => mockChannel.initialized).thenAnswer((_) async => true);
final messages = _generateMessages(); final messages = _generateMessages();
when(mockChannel.query( when(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: beforePagination, messagesPagination: beforePagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).thenAnswer((_) async => ChannelState(messages: messages)); )).thenAnswer((_) async => ChannelState(messages: messages));
when(mockChannel.query( when(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: afterPagination, messagesPagination: afterPagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).thenAnswer((_) async => ChannelState(messages: messages)); )).thenAnswer((_) async => ChannelState(messages: messages));
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -279,23 +258,23 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
verify(mockChannel.query( verify(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: beforePagination, messagesPagination: beforePagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).called(1); )).called(1);
verify(mockChannel.query( verify(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: afterPagination, messagesPagination: afterPagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).called(1); )).called(1);
_stateSetter(() => initialMessageId = 'testInitialMessageId2'); _stateSetter?.call(() => initialMessageId = 'testInitialMessageId2');
final updatedBeforePagination = beforePagination.copyWith( final updatedBeforePagination = beforePagination.copyWith(
lessThan: initialMessageId, lessThan: initialMessageId,
@@ -305,39 +284,39 @@ void main() {
greaterThanOrEqual: initialMessageId, greaterThanOrEqual: initialMessageId,
); );
when(mockChannel.query( when(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: updatedBeforePagination, messagesPagination: updatedBeforePagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).thenAnswer((_) async => ChannelState(messages: messages)); )).thenAnswer((_) async => ChannelState(messages: messages));
when(mockChannel.query( when(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: updatedAfterPagination, messagesPagination: updatedAfterPagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).thenAnswer((_) async => ChannelState(messages: messages)); )).thenAnswer((_) async => ChannelState(messages: messages));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
verify(mockChannel.query( verify(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: updatedBeforePagination, messagesPagination: updatedBeforePagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).called(1); )).called(1);
verify(mockChannel.query( verify(() => mockChannel.query(
options: anyNamed('options'), options: any(named: 'options'),
messagesPagination: updatedAfterPagination, messagesPagination: updatedAfterPagination,
membersPagination: anyNamed('membersPagination'), membersPagination: any(named: 'membersPagination'),
watchersPagination: anyNamed('watchersPagination'), watchersPagination: any(named: 'watchersPagination'),
preferOffline: anyNamed('preferOffline'), preferOffline: any(named: 'preferOffline'),
)).called(1); )).called(1);
}, },
); );
} }
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -12,29 +12,6 @@ class MockOnBackgroundEventReceived extends Mock {
} }
void main() { void main() {
test(
'should throw assertion error in case client is null',
() {
final streamChatCore = () => StreamChatCore(
client: null,
child: Offstage(),
);
expect(streamChatCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case child is null',
() {
final mockClient = MockClient();
final streamChatCore = () => StreamChatCore(
client: mockClient,
child: null,
);
expect(streamChatCore, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should render StreamChatCore if both client and child is provided', 'should render StreamChatCore if both client and child is provided',
(tester) async { (tester) async {
@@ -92,7 +69,7 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
when(mockClient.disconnect()).thenAnswer((_) async { when(() => mockClient.disconnect()).thenAnswer((_) async {
return; return;
}); });
@@ -102,7 +79,7 @@ void main() {
streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused); streamChatCoreState.didChangeAppLifecycleState(AppLifecycleState.paused);
verify(mockClient.disconnect()).called(1); verify(() => mockClient.disconnect()).called(1);
}, },
); );
@@ -131,8 +108,8 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(); final event = Event();
when(mockClient.on()).thenAnswer((_) => Stream.value(event)); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(mockClient.disconnect()).thenAnswer((_) async { when(() => mockClient.disconnect()).thenAnswer((_) async {
return; return;
}); });
@@ -143,14 +120,14 @@ void main() {
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
await untilCalled(mockOnBackgroundEventReceived.call(event)); await untilCalled(() => mockOnBackgroundEventReceived.call(event));
verify(mockOnBackgroundEventReceived.call(event)).called(1); verify(() => mockOnBackgroundEventReceived.call(event)).called(1);
await Future.delayed(backgroundKeepAlive); await Future.delayed(backgroundKeepAlive);
verify(mockClient.disconnect()).called(1); verify(() => mockClient.disconnect()).called(1);
verifyNever(mockOnBackgroundEventReceived.call(event)); verifyNever(() => mockOnBackgroundEventReceived.call(event));
}); });
}, },
); );
@@ -180,7 +157,7 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(); final event = Event();
when(mockClient.on()).thenAnswer((_) => Stream.value(event)); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
final streamChatCoreState = tester.state<StreamChatCoreState>( final streamChatCoreState = tester.state<StreamChatCoreState>(
find.byKey(streamChatCoreKey), find.byKey(streamChatCoreKey),
@@ -189,14 +166,14 @@ void main() {
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.paused); .didChangeAppLifecycleState(AppLifecycleState.paused);
await untilCalled(mockOnBackgroundEventReceived.call(event)); await untilCalled(() => mockOnBackgroundEventReceived.call(event));
verify(mockOnBackgroundEventReceived.call(event)).called(1); verify(() => mockOnBackgroundEventReceived.call(event)).called(1);
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.resumed); .didChangeAppLifecycleState(AppLifecycleState.resumed);
verifyNever(mockOnBackgroundEventReceived.call(event)); verifyNever(() => mockOnBackgroundEventReceived.call(event));
}); });
}, },
); );
@@ -222,9 +199,10 @@ void main() {
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
final event = Event(); final event = Event();
when(mockClient.on()).thenAnswer((_) => Stream.value(event)); when(() => mockClient.on()).thenAnswer((_) => Stream.value(event));
when(mockClient.connect()).thenAnswer((_) async => event); when(() => mockClient.connect()).thenAnswer((_) async => event);
when(mockClient.wsConnectionStatus) when(mockClient.disconnect).thenAnswer((_) async => null);
when(() => mockClient.wsConnectionStatus)
.thenReturn(ConnectionStatus.disconnected); .thenReturn(ConnectionStatus.disconnected);
final streamChatCoreState = tester.state<StreamChatCoreState>( final streamChatCoreState = tester.state<StreamChatCoreState>(
@@ -239,7 +217,7 @@ void main() {
streamChatCoreState streamChatCoreState
.didChangeAppLifecycleState(AppLifecycleState.resumed); .didChangeAppLifecycleState(AppLifecycleState.resumed);
verify(mockClient.connect()).called(1); verify(() => mockClient.connect()).called(1);
}); });
}, },
); );
@@ -265,7 +243,7 @@ void main() {
expect(find.byKey(streamChatCoreKey), findsOneWidget); expect(find.byKey(streamChatCoreKey), findsOneWidget);
expect(find.byKey(childKey), findsOneWidget); expect(find.byKey(childKey), findsOneWidget);
when(mockClient.state.userStream) when(() => mockClient.state.userStream)
.thenAnswer((_) => userController.stream); .thenAnswer((_) => userController.stream);
final streamChatCoreState = tester.state<StreamChatCoreState>( final streamChatCoreState = tester.state<StreamChatCoreState>(
@@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat_flutter_core/src/user_list_core.dart'; import 'package:stream_chat_flutter_core/src/user_list_core.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
@@ -33,58 +33,6 @@ void main() {
); );
} }
test(
'should throw assertion error in case listBuilder is null',
() {
final userListCore = () => UserListCore(
listBuilder: null,
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (Object error) => Offstage(),
);
expect(userListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case loadingBuilder is null',
() {
final userListCore = () => UserListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: null,
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: (Object error) => Offstage(),
);
expect(userListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case emptyBuilder is null',
() {
final userListCore = () => UserListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: null,
errorBuilder: (Object error) => Offstage(),
);
expect(userListCore, throwsA(isA<AssertionError>()));
},
);
test(
'should throw assertion error in case errorBuilder is null',
() {
final userListCore = () => UserListCore(
listBuilder: (_, __) => Offstage(),
loadingBuilder: (BuildContext context) => Offstage(),
emptyBuilder: (BuildContext context) => Offstage(),
errorBuilder: null,
);
expect(userListCore, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'should throw if UserListCore is used where UsersBloc is not present ' 'should throw if UserListCore is used where UsersBloc is not present '
'in the widget tree', 'in the widget tree',
@@ -183,12 +131,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
const error = 'Error! Error! Error!'; const error = 'Error! Error! Error!';
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenThrow(error); )).thenThrow(error);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -203,12 +151,12 @@ void main() {
expect(find.byKey(errorWidgetKey), findsOneWidget); expect(find.byKey(errorWidgetKey), findsOneWidget);
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -228,12 +176,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
const users = <User>[]; const users = <User>[];
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -248,12 +196,12 @@ void main() {
expect(find.byKey(emptyWidgetKey), findsOneWidget); expect(find.byKey(emptyWidgetKey), findsOneWidget);
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -273,12 +221,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
await tester.pumpWidget( await tester.pumpWidget(
StreamChatCore( StreamChatCore(
@@ -293,12 +241,12 @@ void main() {
expect(find.byKey(listWidgetKey), findsOneWidget); expect(find.byKey(listWidgetKey), findsOneWidget);
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -314,7 +262,7 @@ void main() {
child: ListView( child: ListView(
children: items.map((e) { children: items.map((e) {
return Container( return Container(
key: Key(e.key), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
@@ -332,12 +280,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -359,12 +307,12 @@ void main() {
expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); expect(find.byKey(Key('USER-${user.id}')), findsOneWidget);
} }
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -382,7 +330,7 @@ void main() {
child: ListView( child: ListView(
children: items.map((e) { children: items.map((e) {
return Container( return Container(
key: Key(e.key), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
@@ -401,12 +349,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -428,12 +376,12 @@ void main() {
expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); expect(find.byKey(Key('USER-${user.id}')), findsOneWidget);
} }
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
final userListCoreState = tester.state<UserListCoreState>( final userListCoreState = tester.state<UserListCoreState>(
find.byKey(userListCoreKey), find.byKey(userListCoreKey),
@@ -442,12 +390,14 @@ void main() {
final offset = users.length; final offset = users.length;
final paginatedUsers = _generateUsers(offset: offset); final paginatedUsers = _generateUsers(offset: offset);
final updatedPagination = pagination.copyWith(offset: offset); final updatedPagination = pagination.copyWith(offset: offset);
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: updatedPagination, pagination: updatedPagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); ))
.thenAnswer(
(_) async => QueryUsersResponse()..users = paginatedUsers);
await userListCoreState.paginateData(); await userListCoreState.paginateData();
@@ -458,12 +408,12 @@ void main() {
expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); expect(find.byKey(Key('USER-${user.id}')), findsOneWidget);
} }
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: updatedPagination, pagination: updatedPagination,
)).called(1); )).called(1);
}, },
); );
@@ -473,7 +423,7 @@ void main() {
(tester) async { (tester) async {
const pagination = PaginationParams(); const pagination = PaginationParams();
StateSetter _stateSetter; StateSetter? _stateSetter;
int limit = pagination.limit; int limit = pagination.limit;
const userListCoreKey = Key('userListCore'); const userListCoreKey = Key('userListCore');
@@ -485,7 +435,7 @@ void main() {
child: ListView( child: ListView(
children: items.map((e) { children: items.map((e) {
return Container( return Container(
key: Key(e.key), key: Key(e.key ?? ''),
child: e.when( child: e.when(
headerItem: (heading) => Text(heading), headerItem: (heading) => Text(heading),
userItem: (user) => Text(user.id), userItem: (user) => Text(user.id),
@@ -504,12 +454,12 @@ void main() {
final mockClient = MockClient(); final mockClient = MockClient();
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
await tester.pumpWidget( await tester.pumpWidget(
Directionality( Directionality(
@@ -535,24 +485,25 @@ void main() {
expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); expect(find.byKey(Key('USER-${user.id}')), findsOneWidget);
} }
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
// Rebuilding UserListCore with new pagination limit // Rebuilding UserListCore with new pagination limit
_stateSetter(() => limit = 6); _stateSetter?.call(() => limit = 6);
final updatedUsers = _generateUsers(count: limit); final updatedUsers = _generateUsers(count: limit);
final updatedPagination = pagination.copyWith(limit: limit); final updatedPagination = pagination.copyWith(limit: limit);
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: updatedPagination, pagination: updatedPagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers); ))
.thenAnswer((_) async => QueryUsersResponse()..users = updatedUsers);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
@@ -561,12 +512,12 @@ void main() {
expect(find.byKey(Key('USER-${user.id}')), findsOneWidget); expect(find.byKey(Key('USER-${user.id}')), findsOneWidget);
} }
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: updatedPagination, pagination: updatedPagination,
)).called(1); )).called(1);
}, },
); );
} }
@@ -1,9 +1,9 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; import 'package:stream_chat_flutter_core/src/stream_chat_core.dart';
import 'package:stream_chat_flutter_core/src/users_bloc.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart';
import 'package:mockito/mockito.dart';
import 'matchers/users_matcher.dart'; import 'matchers/users_matcher.dart';
import 'mocks.dart'; import 'mocks.dart';
@@ -31,18 +31,6 @@ void main() {
); );
} }
test(
'should throw assertion error if child is null',
() async {
const usersBlocKey = Key('usersBloc');
final usersBloc = () => UsersBloc(
key: usersBlocKey,
child: null,
);
expect(usersBloc, throwsA(isA<AssertionError>()));
},
);
testWidgets( testWidgets(
'usersBlocState.queryUsers() should throw if used where ' 'usersBlocState.queryUsers() should throw if used where '
'StreamChat is not present in the widget tree', 'StreamChat is not present in the widget tree',
@@ -96,12 +84,12 @@ void main() {
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(); usersBlocState.queryUsers();
@@ -110,12 +98,12 @@ void main() {
emits(isSameUserListAs(users)), emits(isSameUserListAs(users)),
); );
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -145,12 +133,12 @@ void main() {
final error = 'Error! Error! Error!'; final error = 'Error! Error! Error!';
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenThrow(error); )).thenThrow(error);
usersBlocState.queryUsers(); usersBlocState.queryUsers();
@@ -159,12 +147,12 @@ void main() {
emitsError(error), emitsError(error),
); );
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
}, },
); );
@@ -195,12 +183,12 @@ void main() {
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(); usersBlocState.queryUsers();
@@ -209,23 +197,25 @@ void main() {
emits(isSameUserListAs(users)), emits(isSameUserListAs(users)),
); );
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: '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 pagination = PaginationParams(offset: offset);
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: pagination, pagination: pagination,
)).thenAnswer((_) async => QueryUsersResponse()..users = paginatedUsers); ))
.thenAnswer(
(_) async => QueryUsersResponse()..users = paginatedUsers);
usersBlocState.queryUsers(pagination: pagination); usersBlocState.queryUsers(pagination: pagination);
@@ -240,12 +230,12 @@ void main() {
), ),
]); ]);
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: pagination, pagination: pagination,
)).called(1); )).called(1);
}, },
); );
@@ -276,12 +266,12 @@ void main() {
final users = _generateUsers(); final users = _generateUsers();
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).thenAnswer((_) async => QueryUsersResponse()..users = users); )).thenAnswer((_) async => QueryUsersResponse()..users = users);
usersBlocState.queryUsers(); usersBlocState.queryUsers();
@@ -290,24 +280,24 @@ void main() {
emits(isSameUserListAs(users)), emits(isSameUserListAs(users)),
); );
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: anyNamed('pagination'), pagination: any(named: 'pagination'),
)).called(1); )).called(1);
final offset = users.length; final offset = users.length;
final pagination = PaginationParams(offset: offset); final pagination = PaginationParams(offset: offset);
final error = 'Error! Error! Error!'; final error = 'Error! Error! Error!';
when(mockClient.queryUsers( when(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: pagination, pagination: pagination,
)).thenThrow(error); )).thenThrow(error);
usersBlocState.queryUsers(pagination: pagination); usersBlocState.queryUsers(pagination: pagination);
@@ -316,12 +306,12 @@ void main() {
emitsError(error), emitsError(error),
); );
verify(mockClient.queryUsers( verify(() => mockClient.queryUsers(
filter: anyNamed('filter'), filter: any(named: 'filter'),
sort: anyNamed('sort'), sort: any(named: 'sort'),
options: anyNamed('options'), options: any(named: 'options'),
pagination: pagination, pagination: pagination,
)).called(1); )).called(1);
}, },
); );
} }