@@ -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,56 @@ 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
+ final expectedSelfUserKey = '${data .realmUri }|${data .userId }' ;
126
+
127
+ // List of all the checks for messages on each notify calls.
128
+ final messagesChecks = < Condition <MessagingStyleMessage ?>> [];
129
+ for (int j = 0 ; j < (i + 1 ); j++ ) {
130
+ final data = messages[j];
131
+ final expectedSenderKey = '${data .realmUri }|${data .senderId }' ;
132
+ messagesChecks.add ((it) => it.isNotNull ()
133
+ ..text.equals (data.content)
134
+ ..timestampMs.equals (data.time * 1000 )
135
+ ..person.which ((it) => it.isNotNull ()
136
+ ..iconBitmap.isNotNull ()
137
+ ..key.equals (expectedSenderKey)
138
+ ..name.equals (data.senderFullName)));
139
+ }
140
+
141
+ notifyCallsChecks.addAll (< Condition <AndroidNotificationHostApiNotifyCall >> [
123
142
(it) => it
124
143
..id.equals (expectedId)
125
144
..tag.equals (expectedTag)
126
145
..channelId.equals (NotificationChannelManager .kChannelId)
127
- ..contentTitle.equals (expectedTitle)
128
- ..contentText.equals (data.content)
146
+ ..contentTitle.isNull ()
147
+ ..contentText.isNull ()
148
+ ..messagingStyle.which ((it) => it.isNotNull ()
149
+ ..user.which ((it) => it.isNotNull ()
150
+ ..iconBitmap.isNull ()
151
+ ..key.equals (expectedSelfUserKey)
152
+ ..name.equals ('You' )) // TODO(i18n)
153
+ ..isGroupConversation.equals (expectedGroup)
154
+ ..conversationTitle.equals (expectedTitle)
155
+ ..messages.which ((it) => it
156
+ ..length.equals (messagesChecks.length)
157
+ ..containsInOrder (messagesChecks)))
158
+ ..number.equals (messagesChecks.length)
129
159
..color.equals (kZulipBrandColor.value)
130
160
..smallIconResourceName.equals ('zulip_notification' )
131
161
..extras.isNull ()
@@ -151,53 +181,87 @@ void main() {
151
181
..inboxStyle.which ((it) => it.isNotNull ()
152
182
..summaryText.equals (data.realmUri.toString ()))
153
183
..autoCancel.equals (true )
154
- ..contentIntent.isNull ()
184
+ ..contentIntent.isNull (),
155
185
]);
186
+ }
187
+
188
+ check (testBinding.androidNotificationHost.takeNotifyCalls ())
189
+ ..length.equals (messages.length * 2 )
190
+ ..containsInOrder (notifyCallsChecks);
191
+ testBinding.androidNotificationHost.clearActiveNotifications ();
156
192
}
157
193
158
- Future <void > checkNotifications (FakeAsync async , MessageFcmMessage data , {
194
+ Future <void > checkNotifications (FakeAsync async , List < MessageFcmMessage > messages , {
159
195
required String expectedTitle,
160
196
required String expectedTagComponent,
197
+ required bool expectedGroup,
161
198
}) async {
162
199
// We could just call `NotificationDisplayManager.onFcmMessage`.
163
200
// But this way is cheap, and it provides our test coverage of
164
201
// the logic in `NotificationService` that listens for these FCM messages.
165
202
166
- testBinding.firebaseMessaging.onMessage.add (
167
- RemoteMessage (data: data.toJson ()));
168
- async .flushMicrotasks ();
169
- checkNotification (data, expectedTitle: expectedTitle,
203
+ for (final data in messages) {
204
+ testBinding.firebaseMessaging.onMessage.add (
205
+ RemoteMessage (data: data.toJson ()));
206
+ async .flushMicrotasks ();
207
+ }
208
+ checkNotification (messages,
209
+ expectedGroup: expectedGroup,
210
+ expectedTitle: expectedTitle,
170
211
expectedTagComponent: expectedTagComponent);
171
212
172
- testBinding.firebaseMessaging.onBackgroundMessage.add (
173
- RemoteMessage (data: data.toJson ()));
174
- async .flushMicrotasks ();
175
- checkNotification (data, expectedTitle: expectedTitle,
213
+ for (final data in messages) {
214
+ testBinding.firebaseMessaging.onBackgroundMessage.add (
215
+ RemoteMessage (data: data.toJson ()));
216
+ async .flushMicrotasks ();
217
+ }
218
+ checkNotification (messages,
219
+ expectedGroup: expectedGroup,
220
+ expectedTitle: expectedTitle,
176
221
expectedTagComponent: expectedTagComponent);
177
222
}
178
223
179
224
test ('stream message' , () => awaitFakeAsync ((async ) async {
180
225
await init ();
181
226
final stream = eg.stream ();
182
227
final message = eg.streamMessage (stream: stream);
183
- await checkNotifications (async , messageFcmMessage (message, streamName: stream.name),
228
+ await checkNotifications (async , [messageFcmMessage (message, streamName: stream.name)],
229
+ expectedGroup: true ,
184
230
expectedTitle: '#${stream .name } > ${message .topic }' ,
185
231
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
186
232
}));
187
233
234
+ test ('multiple stream messages' , () => awaitFakeAsync ((async ) async {
235
+ await init ();
236
+ final stream = eg.stream (streamId: 1 , name: 'stream 1' );
237
+ final message1 = eg.streamMessage (id: 101 , stream: stream, timestamp: 1 );
238
+ final messageData1 = messageFcmMessage (message1, streamName: stream.name);
239
+ final message2 = eg.streamMessage (id: 102 , stream: stream, timestamp: 2 );
240
+ final messageData2 = messageFcmMessage (message2, streamName: stream.name);
241
+ final message3 = eg.streamMessage (id: 103 , stream: stream, timestamp: 3 );
242
+ final messageData3 = messageFcmMessage (message3, streamName: stream.name);
243
+
244
+ await checkNotifications (async , [messageData1, messageData2, messageData3],
245
+ expectedGroup: true ,
246
+ expectedTitle: '#${stream .name } > ${message3 .topic }' ,
247
+ expectedTagComponent: 'stream:${message3 .streamId }:${message3 .topic }' );
248
+ }));
249
+
188
250
test ('stream message, stream name omitted' , () => awaitFakeAsync ((async ) async {
189
251
await init ();
190
252
final stream = eg.stream ();
191
253
final message = eg.streamMessage (stream: stream);
192
- await checkNotifications (async , messageFcmMessage (message, streamName: null ),
254
+ await checkNotifications (async , [messageFcmMessage (message, streamName: null )],
255
+ expectedGroup: true ,
193
256
expectedTitle: '#(unknown channel) > ${message .topic }' ,
194
257
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
195
258
}));
196
259
197
260
test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
198
261
await init ();
199
262
final message = eg.dmMessage (from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
200
- await checkNotifications (async , messageFcmMessage (message),
263
+ await checkNotifications (async , [messageFcmMessage (message)],
264
+ expectedGroup: true ,
201
265
expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
202
266
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
203
267
}));
@@ -206,23 +270,26 @@ void main() {
206
270
await init ();
207
271
final message = eg.dmMessage (from: eg.thirdUser,
208
272
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
209
- await checkNotifications (async , messageFcmMessage (message),
273
+ await checkNotifications (async , [messageFcmMessage (message)],
274
+ expectedGroup: true ,
210
275
expectedTitle: "${eg .thirdUser .fullName } to you and 2 others" ,
211
276
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
212
277
}));
213
278
214
279
test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
215
280
await init ();
216
281
final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
217
- await checkNotifications (async , messageFcmMessage (message),
282
+ await checkNotifications (async , [messageFcmMessage (message)],
283
+ expectedGroup: false ,
218
284
expectedTitle: eg.otherUser.fullName,
219
285
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
220
286
}));
221
287
222
288
test ('self-DM' , () => awaitFakeAsync ((async ) async {
223
289
await init ();
224
290
final message = eg.dmMessage (from: eg.selfUser, to: []);
225
- await checkNotifications (async , messageFcmMessage (message),
291
+ await checkNotifications (async , [messageFcmMessage (message)],
292
+ expectedGroup: false ,
226
293
expectedTitle: eg.selfUser.fullName,
227
294
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
228
295
}));
@@ -403,6 +470,8 @@ extension on Subject<AndroidNotificationHostApiNotifyCall> {
403
470
Subject <String ?> get groupKey => has ((x) => x.groupKey, 'groupKey' );
404
471
Subject <InboxStyle ?> get inboxStyle => has ((x) => x.inboxStyle, 'inboxStyle' );
405
472
Subject <bool ?> get isGroupSummary => has ((x) => x.isGroupSummary, 'isGroupSummary' );
473
+ Subject <MessagingStyle ?> get messagingStyle => has ((x) => x.messagingStyle, 'messagingStyle' );
474
+ Subject <int ?> get number => has ((x) => x.number, 'number' );
406
475
Subject <String ?> get smallIconResourceName => has ((x) => x.smallIconResourceName, 'smallIconResourceName' );
407
476
}
408
477
@@ -415,3 +484,22 @@ extension on Subject<PendingIntent> {
415
484
extension on Subject <InboxStyle > {
416
485
Subject <String > get summaryText => has ((x) => x.summaryText, 'summaryText' );
417
486
}
487
+
488
+ extension on Subject <MessagingStyle > {
489
+ Subject <Person > get user => has ((x) => x.user, 'user' );
490
+ Subject <String ?> get conversationTitle => has ((x) => x.conversationTitle, 'conversationTitle' );
491
+ Subject <List <MessagingStyleMessage ?>> get messages => has ((x) => x.messages, 'messages' );
492
+ Subject <bool > get isGroupConversation => has ((x) => x.isGroupConversation, 'isGroupConversation' );
493
+ }
494
+
495
+ extension on Subject <Person > {
496
+ Subject <Uint8List ?> get iconBitmap => has ((x) => x.iconBitmap, 'iconBitmap' );
497
+ Subject <String > get key => has ((x) => x.key, 'key' );
498
+ Subject <String > get name => has ((x) => x.name, 'name' );
499
+ }
500
+
501
+ extension on Subject <MessagingStyleMessage > {
502
+ Subject <String > get text => has ((x) => x.text, 'text' );
503
+ Subject <int > get timestampMs => has ((x) => x.timestampMs, 'timestampMs' );
504
+ Subject <Person > get person => has ((x) => x.person, 'person' );
505
+ }
0 commit comments