@@ -5,7 +5,7 @@ import 'package:checks/checks.dart';
55import 'package:fake_async/fake_async.dart' ;
66import 'package:firebase_messaging/firebase_messaging.dart' ;
77import 'package:flutter/material.dart' ;
8- import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message;
8+ import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message, Person ;
99import 'package:flutter_test/flutter_test.dart' ;
1010import 'package:zulip/api/model/model.dart' ;
1111import 'package:zulip/api/notifications.dart' ;
@@ -106,26 +106,54 @@ void main() {
106106 });
107107
108108 group ('NotificationDisplayManager show' , () {
109- void checkNotification (MessageFcmMessage data , {
109+ void checkNotification (List < MessageFcmMessage > messages , {
110110 required String expectedTitle,
111111 required String expectedTagComponent,
112+ required bool expectedGroup,
112113 }) {
113- final expectedTag = '${data .realmUri }|${data .userId }|$expectedTagComponent ' ;
114- final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
115- final expectedId =
116- NotificationDisplayManager .notificationIdAsHashOf (expectedTag);
117114 const expectedIntentFlags =
118115 PendingIntentFlag .immutable | PendingIntentFlag .updateCurrent;
119116
120- check (testBinding.androidNotificationHost.takeNotifyCalls ())
121- ..length.equals (2 )
122- ..containsInOrder (< Condition <AndroidNotificationHostApiNotifyCall >> [
117+ final notifyCallsChecks = < Condition <AndroidNotificationHostApiNotifyCall >> [];
118+ for (int i = 0 ; i < messages.length; i++ ) {
119+ final data = messages[i];
120+ final expectedTag =
121+ '${data .realmUri }|${data .userId }|$expectedTagComponent ' ;
122+ final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
123+ final expectedId =
124+ NotificationDisplayManager .notificationIdAsHashOf (expectedTag);
125+
126+ // List of all the checks for messages on each notify calls.
127+ final messagesChecks = < Condition <MessagingStyleMessage ?>> [];
128+ for (int j = 0 ; j < (i + 1 ); j++ ) {
129+ final data = messages[j];
130+ messagesChecks.add ((it) => it.isNotNull ()
131+ ..text.equals (data.content)
132+ ..timestampMs.equals (data.time * 1000 )
133+ ..person.which ((it) => it.isNotNull ()
134+ ..iconData.isNotNull ()
135+ ..key.equals (data.senderId.toString ())
136+ ..name.equals (data.senderFullName)));
137+ }
138+
139+ notifyCallsChecks.addAll (< Condition <AndroidNotificationHostApiNotifyCall >> [
123140 (it) => it
124141 ..id.equals (expectedId)
125142 ..tag.equals (expectedTag)
126143 ..channelId.equals (NotificationChannelManager .kChannelId)
127- ..contentTitle.equals (expectedTitle)
128- ..contentText.equals (data.content)
144+ ..contentTitle.isNull ()
145+ ..contentText.isNull ()
146+ ..messagingStyle.which ((it) => it.isNotNull ()
147+ ..user.which ((it) => it.isNotNull ()
148+ ..iconData.isNull ()
149+ ..key.equals (data.userId.toString ())
150+ ..name.equals ('You' ))
151+ ..isGroupConversation.equals (expectedGroup)
152+ ..conversationTitle.equals (expectedTitle)
153+ ..messages.which ((it) => it.isNotNull ()
154+ ..length.equals (messagesChecks.length)
155+ ..containsInOrder (messagesChecks)))
156+ ..number.equals (messagesChecks.length)
129157 ..color.equals (kZulipBrandColor.value)
130158 ..smallIconResourceName.equals ('zulip_notification' )
131159 ..extras.isNull ()
@@ -151,53 +179,86 @@ void main() {
151179 ..inboxStyle.which ((it) => it.isNotNull ()
152180 ..summaryText.equals (data.realmUri.toString ()))
153181 ..autoCancel.equals (true )
154- ..contentIntent.isNull ()
182+ ..contentIntent.isNull (),
155183 ]);
184+ }
185+
186+ check (testBinding.androidNotificationHost.takeNotifyCalls ())
187+ ..length.equals (messages.length * 2 )
188+ ..containsInOrder (notifyCallsChecks);
156189 }
157190
158- Future <void > checkNotifications (FakeAsync async , MessageFcmMessage data , {
191+ Future <void > checkNotifications (FakeAsync async , List < MessageFcmMessage > messages , {
159192 required String expectedTitle,
160193 required String expectedTagComponent,
194+ required bool expectedGroup,
161195 }) async {
162196 // We could just call `NotificationDisplayManager.onFcmMessage`.
163197 // But this way is cheap, and it provides our test coverage of
164198 // the logic in `NotificationService` that listens for these FCM messages.
165199
166- testBinding.firebaseMessaging.onMessage.add (
167- RemoteMessage (data: data.toJson ()));
168- async .flushMicrotasks ();
169- checkNotification (data, expectedTitle: expectedTitle,
200+ for (final data in messages) {
201+ testBinding.firebaseMessaging.onMessage.add (
202+ RemoteMessage (data: data.toJson ()));
203+ async .flushMicrotasks ();
204+ }
205+ checkNotification (messages,
206+ expectedGroup: expectedGroup,
207+ expectedTitle: expectedTitle,
170208 expectedTagComponent: expectedTagComponent);
171209
172- testBinding.firebaseMessaging.onBackgroundMessage.add (
173- RemoteMessage (data: data.toJson ()));
174- async .flushMicrotasks ();
175- checkNotification (data, expectedTitle: expectedTitle,
210+ for (final data in messages) {
211+ testBinding.firebaseMessaging.onBackgroundMessage.add (
212+ RemoteMessage (data: data.toJson ()));
213+ async .flushMicrotasks ();
214+ }
215+ checkNotification (messages,
216+ expectedGroup: expectedGroup,
217+ expectedTitle: expectedTitle,
176218 expectedTagComponent: expectedTagComponent);
177219 }
178220
179221 test ('stream message' , () => awaitFakeAsync ((async ) async {
180222 await init ();
181223 final stream = eg.stream ();
182224 final message = eg.streamMessage (stream: stream);
183- await checkNotifications (async , messageFcmMessage (message, streamName: stream.name),
225+ await checkNotifications (async , [messageFcmMessage (message, streamName: stream.name)],
226+ expectedGroup: true ,
184227 expectedTitle: '#${stream .name } > ${message .topic }' ,
185228 expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
186229 }));
187230
231+ test ('multiple stream messages' , () => awaitFakeAsync ((async ) async {
232+ await init ();
233+ final stream = eg.stream (streamId: 1 , name: 'stream 1' );
234+ final message1 = eg.streamMessage (id: 101 , stream: stream);
235+ final messageData1 = messageFcmMessage (message1, streamName: stream.name);
236+ final message2 = eg.streamMessage (id: 102 , stream: stream);
237+ final messageData2 = messageFcmMessage (message2, streamName: stream.name);
238+ final message3 = eg.streamMessage (id: 102 , stream: stream);
239+ final messageData3 = messageFcmMessage (message3, streamName: stream.name);
240+
241+ await checkNotifications (async , [messageData1, messageData2, messageData3],
242+ expectedGroup: true ,
243+ expectedTitle: '#${stream .name } > ${message2 .topic }' ,
244+ expectedTagComponent: 'stream:${message2 .streamId }:${message2 .topic }' );
245+ }));
246+
188247 test ('stream message, stream name omitted' , () => awaitFakeAsync ((async ) async {
189248 await init ();
190249 final stream = eg.stream ();
191250 final message = eg.streamMessage (stream: stream);
192- await checkNotifications (async , messageFcmMessage (message, streamName: null ),
251+ await checkNotifications (async , [messageFcmMessage (message, streamName: null )],
252+ expectedGroup: true ,
193253 expectedTitle: '#(unknown channel) > ${message .topic }' ,
194254 expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
195255 }));
196256
197257 test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
198258 await init ();
199259 final message = eg.dmMessage (from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
200- await checkNotifications (async , messageFcmMessage (message),
260+ await checkNotifications (async , [messageFcmMessage (message)],
261+ expectedGroup: true ,
201262 expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
202263 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
203264 }));
@@ -206,23 +267,26 @@ void main() {
206267 await init ();
207268 final message = eg.dmMessage (from: eg.thirdUser,
208269 to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
209- await checkNotifications (async , messageFcmMessage (message),
270+ await checkNotifications (async , [messageFcmMessage (message)],
271+ expectedGroup: true ,
210272 expectedTitle: "${eg .thirdUser .fullName } to you and 2 others" ,
211273 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
212274 }));
213275
214276 test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
215277 await init ();
216278 final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
217- await checkNotifications (async , messageFcmMessage (message),
279+ await checkNotifications (async , [messageFcmMessage (message)],
280+ expectedGroup: false ,
218281 expectedTitle: eg.otherUser.fullName,
219282 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
220283 }));
221284
222285 test ('self-DM' , () => awaitFakeAsync ((async ) async {
223286 await init ();
224287 final message = eg.dmMessage (from: eg.selfUser, to: []);
225- await checkNotifications (async , messageFcmMessage (message),
288+ await checkNotifications (async , [messageFcmMessage (message)],
289+ expectedGroup: false ,
226290 expectedTitle: eg.selfUser.fullName,
227291 expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
228292 }));
@@ -403,6 +467,8 @@ extension on Subject<AndroidNotificationHostApiNotifyCall> {
403467 Subject <String ?> get groupKey => has ((x) => x.groupKey, 'groupKey' );
404468 Subject <InboxStyle ?> get inboxStyle => has ((x) => x.inboxStyle, 'inboxStyle' );
405469 Subject <bool ?> get isGroupSummary => has ((x) => x.isGroupSummary, 'isGroupSummary' );
470+ Subject <MessagingStyle ?> get messagingStyle => has ((x) => x.messagingStyle, 'messagingStyle' );
471+ Subject <int ?> get number => has ((x) => x.number, 'number' );
406472 Subject <String ?> get smallIconResourceName => has ((x) => x.smallIconResourceName, 'smallIconResourceName' );
407473}
408474
@@ -415,3 +481,22 @@ extension on Subject<PendingIntent> {
415481extension on Subject <InboxStyle > {
416482 Subject <String > get summaryText => has ((x) => x.summaryText, 'summaryText' );
417483}
484+
485+ extension on Subject <MessagingStyle > {
486+ Subject <Person > get user => has ((x) => x.user, 'user' );
487+ Subject <String ?> get conversationTitle => has ((x) => x.conversationTitle, 'conversationTitle' );
488+ Subject <List <MessagingStyleMessage ?>?> get messages => has ((x) => x.messages, 'messages' );
489+ Subject <bool > get isGroupConversation => has ((x) => x.isGroupConversation, 'isGroupConversation' );
490+ }
491+
492+ extension on Subject <Person > {
493+ Subject <Uint8List ?> get iconData => has ((x) => x.iconData, 'iconData' );
494+ Subject <String > get key => has ((x) => x.key, 'key' );
495+ Subject <String > get name => has ((x) => x.name, 'name' );
496+ }
497+
498+ extension on Subject <MessagingStyleMessage > {
499+ Subject <String > get text => has ((x) => x.text, 'text' );
500+ Subject <int > get timestampMs => has ((x) => x.timestampMs, 'timestampMs' );
501+ Subject <Person > get person => has ((x) => x.person, 'person' );
502+ }
0 commit comments