1
1
import 'dart:convert' ;
2
+ import 'dart:io' ;
2
3
import 'dart:typed_data' ;
3
4
4
5
import 'package:checks/checks.dart' ;
@@ -8,6 +9,8 @@ import 'package:firebase_messaging/firebase_messaging.dart';
8
9
import 'package:flutter/widgets.dart' ;
9
10
import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message, Person;
10
11
import 'package:flutter_test/flutter_test.dart' ;
12
+ import 'package:http/http.dart' as http;
13
+ import 'package:http/testing.dart' as http_testing;
11
14
import 'package:zulip/api/model/model.dart' ;
12
15
import 'package:zulip/api/notifications.dart' ;
13
16
import 'package:zulip/host/android_notifications.dart' ;
@@ -25,6 +28,7 @@ import 'package:zulip/widgets/theme.dart';
25
28
import '../fake_async.dart' ;
26
29
import '../model/binding.dart' ;
27
30
import '../example_data.dart' as eg;
31
+ import '../test_images.dart' ;
28
32
import '../test_navigation.dart' ;
29
33
import '../widgets/message_list_checks.dart' ;
30
34
import '../widgets/page_checks.dart' ;
@@ -74,6 +78,24 @@ void main() {
74
78
TestZulipBinding .ensureInitialized ();
75
79
final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
76
80
81
+ http.Client makeFakeHttpClient ({http.Response ? response, Exception ? exception}) {
82
+ return http_testing.MockClient ((request) async {
83
+ assert ((response != null ) ^ (exception != null ));
84
+ if (exception != null ) throw exception;
85
+ return response! ; // TODO return 404 on non avatar urls
86
+ });
87
+ }
88
+
89
+ final fakeHttpClientGivingSuccess = makeFakeHttpClient (
90
+ response: http.Response .bytes (kSolidBlueAvatar, HttpStatus .ok));
91
+
92
+ T runWithHttpClient <T >(
93
+ T Function () callback, {
94
+ http.Client Function ()? httpClientFactory,
95
+ }) {
96
+ return http.runWithClient (callback, httpClientFactory ?? () => fakeHttpClientGivingSuccess);
97
+ }
98
+
77
99
Future <void > init () async {
78
100
addTearDown (testBinding.reset);
79
101
testBinding.firebaseMessagingInitialToken = '012abc' ;
@@ -102,6 +124,7 @@ void main() {
102
124
required String expectedTitle,
103
125
required String expectedTagComponent,
104
126
required bool expectedIsGroupConversation,
127
+ List <int >? expectedIconBitmap = kSolidBlueAvatar,
105
128
}) {
106
129
final expectedTag = '${data .realmUri }|${data .userId }|$expectedTagComponent ' ;
107
130
final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
@@ -123,7 +146,8 @@ void main() {
123
146
..text.equals (messageData.content)
124
147
..timestampMs.equals (messageData.time * 1000 )
125
148
..person.which ((it) => it.isNotNull ()
126
- ..iconBitmap.which ((it) => isLast ? it.isNotNull () : it.isNull ())
149
+ ..iconBitmap.which ((it) => (isLast && expectedIconBitmap != null )
150
+ ? it.isNotNull ().deepEquals (expectedIconBitmap) : it.isNull ())
127
151
..key.equals (expectedSenderKey)
128
152
..name.equals (messageData.senderFullName));
129
153
});
@@ -209,17 +233,17 @@ void main() {
209
233
async .flushMicrotasks ();
210
234
}
211
235
212
- test ('stream message' , () => awaitFakeAsync ((async ) async {
236
+ test ('stream message' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
213
237
await init ();
214
238
final stream = eg.stream ();
215
239
final message = eg.streamMessage (stream: stream);
216
240
await checkNotifications (async , messageFcmMessage (message, streamName: stream.name),
217
241
expectedIsGroupConversation: true ,
218
242
expectedTitle: '#${stream .name } > ${message .topic }' ,
219
243
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
220
- }));
244
+ }))) ;
221
245
222
- test ('stream message: multiple messages, same topic' , () => awaitFakeAsync ((async ) async {
246
+ test ('stream message: multiple messages, same topic' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
223
247
await init ();
224
248
final stream = eg.stream ();
225
249
const topic = 'topic 1' ;
@@ -253,54 +277,86 @@ void main() {
253
277
expectedIsGroupConversation: true ,
254
278
expectedTitle: expectedTitle,
255
279
expectedTagComponent: expectedTagComponent);
256
- }));
280
+ }))) ;
257
281
258
- test ('stream message: stream name omitted' , () => awaitFakeAsync ((async ) async {
282
+ test ('stream message: stream name omitted' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
259
283
await init ();
260
284
final stream = eg.stream ();
261
285
final message = eg.streamMessage (stream: stream);
262
286
await checkNotifications (async , messageFcmMessage (message, streamName: null ),
263
287
expectedIsGroupConversation: true ,
264
288
expectedTitle: '#(unknown channel) > ${message .topic }' ,
265
289
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
266
- }));
290
+ }))) ;
267
291
268
- test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
292
+ test ('group DM: 3 users' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
269
293
await init ();
270
294
final message = eg.dmMessage (from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
271
295
await checkNotifications (async , messageFcmMessage (message),
272
296
expectedIsGroupConversation: true ,
273
297
expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
274
298
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
275
- }));
299
+ }))) ;
276
300
277
- test ('group DM: more than 3 users' , () => awaitFakeAsync ((async ) async {
301
+ test ('group DM: more than 3 users' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
278
302
await init ();
279
303
final message = eg.dmMessage (from: eg.thirdUser,
280
304
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
281
305
await checkNotifications (async , messageFcmMessage (message),
282
306
expectedIsGroupConversation: true ,
283
307
expectedTitle: "${eg .thirdUser .fullName } to you and 2 others" ,
284
308
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
285
- }));
309
+ }))) ;
286
310
287
- test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
311
+ test ('1:1 DM' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
288
312
await init ();
289
313
final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
290
314
await checkNotifications (async , messageFcmMessage (message),
291
315
expectedIsGroupConversation: false ,
292
316
expectedTitle: eg.otherUser.fullName,
293
317
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
294
- }));
295
-
296
- test ('self-DM' , () => awaitFakeAsync ((async ) async {
318
+ })));
319
+
320
+ test ('1:1 DM: sender avatar loading fails, remote error' , () => runWithHttpClient (
321
+ () => awaitFakeAsync ((async ) async {
322
+ await init ();
323
+ final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
324
+ final data = messageFcmMessage (message);
325
+ await receiveFcmMessage (async , data);
326
+ checkNotification (data,
327
+ messageStyleMessages: [data],
328
+ expectedIsGroupConversation: false ,
329
+ expectedTitle: eg.otherUser.fullName,
330
+ expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' ,
331
+ expectedIconBitmap: null ); // Failed to fetch avatar photo
332
+ }),
333
+ httpClientFactory: () => makeFakeHttpClient (
334
+ response: http.Response .bytes ([], HttpStatus .internalServerError))));
335
+
336
+ test ('1:1 DM: sender avatar loading fails, local error' , () => runWithHttpClient (
337
+ () => awaitFakeAsync ((async ) async {
338
+ await init ();
339
+ final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
340
+ final data = messageFcmMessage (message);
341
+ await receiveFcmMessage (async , data);
342
+ checkNotification (data,
343
+ messageStyleMessages: [data],
344
+ expectedIsGroupConversation: false ,
345
+ expectedTitle: eg.otherUser.fullName,
346
+ expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' ,
347
+ expectedIconBitmap: null ); // Failed to fetch avatar photo
348
+ }),
349
+ httpClientFactory: () => makeFakeHttpClient (
350
+ exception: http.ClientException ('Network failure' ))));
351
+
352
+ test ('self-DM' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
297
353
await init ();
298
354
final message = eg.dmMessage (from: eg.selfUser, to: []);
299
355
await checkNotifications (async , messageFcmMessage (message),
300
356
expectedIsGroupConversation: false ,
301
357
expectedTitle: eg.selfUser.fullName,
302
358
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
303
- }));
359
+ }))) ;
304
360
});
305
361
306
362
group ('NotificationDisplayManager open' , () {
0 commit comments