@@ -5,7 +5,7 @@ import 'package:checks/checks.dart';
5
5
import 'package:fake_async/fake_async.dart' ;
6
6
import 'package:firebase_messaging/firebase_messaging.dart' ;
7
7
import '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 ;
9
9
import 'package:flutter_test/flutter_test.dart' ;
10
10
import 'package:zulip/api/model/model.dart' ;
11
11
import 'package:zulip/api/notifications.dart' ;
@@ -106,26 +106,54 @@ void main() {
106
106
});
107
107
108
108
group ('NotificationDisplayManager show' , () {
109
- void checkNotification (MessageFcmMessage data , {
109
+ void checkNotification (List < MessageFcmMessage > messages , {
110
110
required String expectedTitle,
111
111
required String expectedTagComponent,
112
+ required bool expectedGroup,
112
113
}) {
113
- final expectedTag = '${data .realmUri }|${data .userId }|$expectedTagComponent ' ;
114
- final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
115
- final expectedId =
116
- NotificationDisplayManager .notificationIdAsHashOf (expectedTag);
117
114
const expectedIntentFlags =
118
115
PendingIntentFlag .immutable | PendingIntentFlag .updateCurrent;
119
116
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 >> [
123
140
(it) => it
124
141
..id.equals (expectedId)
125
142
..tag.equals (expectedTag)
126
143
..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)
129
157
..color.equals (kZulipBrandColor.value)
130
158
..smallIconResourceName.equals ('zulip_notification' )
131
159
..extras.isNull ()
@@ -151,53 +179,86 @@ void main() {
151
179
..inboxStyle.which ((it) => it.isNotNull ()
152
180
..summaryText.equals (data.realmUri.toString ()))
153
181
..autoCancel.equals (true )
154
- ..contentIntent.isNull ()
182
+ ..contentIntent.isNull (),
155
183
]);
184
+ }
185
+
186
+ check (testBinding.androidNotificationHost.takeNotifyCalls ())
187
+ ..length.equals (messages.length * 2 )
188
+ ..containsInOrder (notifyCallsChecks);
156
189
}
157
190
158
- Future <void > checkNotifications (FakeAsync async , MessageFcmMessage data , {
191
+ Future <void > checkNotifications (FakeAsync async , List < MessageFcmMessage > messages , {
159
192
required String expectedTitle,
160
193
required String expectedTagComponent,
194
+ required bool expectedGroup,
161
195
}) async {
162
196
// We could just call `NotificationDisplayManager.onFcmMessage`.
163
197
// But this way is cheap, and it provides our test coverage of
164
198
// the logic in `NotificationService` that listens for these FCM messages.
165
199
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,
170
208
expectedTagComponent: expectedTagComponent);
171
209
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,
176
218
expectedTagComponent: expectedTagComponent);
177
219
}
178
220
179
221
test ('stream message' , () => awaitFakeAsync ((async ) async {
180
222
await init ();
181
223
final stream = eg.stream ();
182
224
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 ,
184
227
expectedTitle: '#${stream .name } > ${message .topic }' ,
185
228
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
186
229
}));
187
230
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
+
188
247
test ('stream message, stream name omitted' , () => awaitFakeAsync ((async ) async {
189
248
await init ();
190
249
final stream = eg.stream ();
191
250
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 ,
193
253
expectedTitle: '#(unknown channel) > ${message .topic }' ,
194
254
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
195
255
}));
196
256
197
257
test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
198
258
await init ();
199
259
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 ,
201
262
expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
202
263
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
203
264
}));
@@ -206,23 +267,26 @@ void main() {
206
267
await init ();
207
268
final message = eg.dmMessage (from: eg.thirdUser,
208
269
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
209
- await checkNotifications (async , messageFcmMessage (message),
270
+ await checkNotifications (async , [messageFcmMessage (message)],
271
+ expectedGroup: true ,
210
272
expectedTitle: "${eg .thirdUser .fullName } to you and 2 others" ,
211
273
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
212
274
}));
213
275
214
276
test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
215
277
await init ();
216
278
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 ,
218
281
expectedTitle: eg.otherUser.fullName,
219
282
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
220
283
}));
221
284
222
285
test ('self-DM' , () => awaitFakeAsync ((async ) async {
223
286
await init ();
224
287
final message = eg.dmMessage (from: eg.selfUser, to: []);
225
- await checkNotifications (async , messageFcmMessage (message),
288
+ await checkNotifications (async , [messageFcmMessage (message)],
289
+ expectedGroup: false ,
226
290
expectedTitle: eg.selfUser.fullName,
227
291
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
228
292
}));
@@ -403,6 +467,8 @@ extension on Subject<AndroidNotificationHostApiNotifyCall> {
403
467
Subject <String ?> get groupKey => has ((x) => x.groupKey, 'groupKey' );
404
468
Subject <InboxStyle ?> get inboxStyle => has ((x) => x.inboxStyle, 'inboxStyle' );
405
469
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' );
406
472
Subject <String ?> get smallIconResourceName => has ((x) => x.smallIconResourceName, 'smallIconResourceName' );
407
473
}
408
474
@@ -415,3 +481,22 @@ extension on Subject<PendingIntent> {
415
481
extension on Subject <InboxStyle > {
416
482
Subject <String > get summaryText => has ((x) => x.summaryText, 'summaryText' );
417
483
}
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