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/material.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' ;
@@ -79,6 +83,24 @@ void main() {
79
83
TestZulipBinding .ensureInitialized ();
80
84
final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
81
85
86
+ http.Client makeFakeHttpClient ({http.Response ? response, Exception ? exception}) {
87
+ return http_testing.MockClient ((request) async {
88
+ assert ((response != null ) ^ (exception != null ));
89
+ if (exception != null ) throw exception;
90
+ return response! ; // TODO return 404 on non avatar urls
91
+ });
92
+ }
93
+
94
+ final fakeHttpClientGivingSuccess = makeFakeHttpClient (
95
+ response: http.Response .bytes (kSolidBlueAvatar, HttpStatus .ok));
96
+
97
+ T runWithHttpClient <T >(
98
+ T Function () callback, {
99
+ http.Client Function ()? httpClientFactory,
100
+ }) {
101
+ return http.runWithClient (callback, httpClientFactory ?? () => fakeHttpClientGivingSuccess);
102
+ }
103
+
82
104
Future <void > init () async {
83
105
addTearDown (testBinding.reset);
84
106
testBinding.firebaseMessagingInitialToken = '012abc' ;
@@ -114,6 +136,7 @@ void main() {
114
136
required String expectedTitle,
115
137
required String expectedTagComponent,
116
138
required bool expectedIsGroupConversation,
139
+ List <int >? expectedIconBitmap = kSolidBlueAvatar,
117
140
}) {
118
141
final expectedTag = '${data .realmUri }|${data .userId }|$expectedTagComponent ' ;
119
142
final expectedGroupKey = '${data .realmUri }|${data .userId }' ;
@@ -135,7 +158,8 @@ void main() {
135
158
..text.equals (messageData.content)
136
159
..timestampMs.equals (messageData.time * 1000 )
137
160
..person.which ((it) => it.isNotNull ()
138
- ..iconBitmap.which ((it) => isLast ? it.isNotNull () : it.isNull ())
161
+ ..iconBitmap.which ((it) => (isLast && expectedIconBitmap != null )
162
+ ? it.isNotNull ().deepEquals (expectedIconBitmap) : it.isNull ())
139
163
..key.equals (expectedSenderKey)
140
164
..name.equals (messageData.senderFullName));
141
165
});
@@ -221,17 +245,17 @@ void main() {
221
245
async .flushMicrotasks ();
222
246
}
223
247
224
- test ('stream message' , () => awaitFakeAsync ((async ) async {
248
+ test ('stream message' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
225
249
await init ();
226
250
final stream = eg.stream ();
227
251
final message = eg.streamMessage (stream: stream);
228
252
await checkNotifications (async , messageFcmMessage (message, streamName: stream.name),
229
253
expectedIsGroupConversation: true ,
230
254
expectedTitle: '#${stream .name } > ${message .topic }' ,
231
255
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
232
- }));
256
+ }))) ;
233
257
234
- test ('stream message: multiple messages, same topic' , () => awaitFakeAsync ((async ) async {
258
+ test ('stream message: multiple messages, same topic' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
235
259
await init ();
236
260
final stream = eg.stream ();
237
261
const topic = 'topic 1' ;
@@ -265,54 +289,86 @@ void main() {
265
289
expectedIsGroupConversation: true ,
266
290
expectedTitle: expectedTitle,
267
291
expectedTagComponent: expectedTagComponent);
268
- }));
292
+ }))) ;
269
293
270
- test ('stream message: stream name omitted' , () => awaitFakeAsync ((async ) async {
294
+ test ('stream message: stream name omitted' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
271
295
await init ();
272
296
final stream = eg.stream ();
273
297
final message = eg.streamMessage (stream: stream);
274
298
await checkNotifications (async , messageFcmMessage (message, streamName: null ),
275
299
expectedIsGroupConversation: true ,
276
300
expectedTitle: '#(unknown channel) > ${message .topic }' ,
277
301
expectedTagComponent: 'stream:${message .streamId }:${message .topic }' );
278
- }));
302
+ }))) ;
279
303
280
- test ('group DM: 3 users' , () => awaitFakeAsync ((async ) async {
304
+ test ('group DM: 3 users' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
281
305
await init ();
282
306
final message = eg.dmMessage (from: eg.thirdUser, to: [eg.otherUser, eg.selfUser]);
283
307
await checkNotifications (async , messageFcmMessage (message),
284
308
expectedIsGroupConversation: true ,
285
309
expectedTitle: "${eg .thirdUser .fullName } to you and 1 other" ,
286
310
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
287
- }));
311
+ }))) ;
288
312
289
- test ('group DM: more than 3 users' , () => awaitFakeAsync ((async ) async {
313
+ test ('group DM: more than 3 users' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
290
314
await init ();
291
315
final message = eg.dmMessage (from: eg.thirdUser,
292
316
to: [eg.otherUser, eg.selfUser, eg.fourthUser]);
293
317
await checkNotifications (async , messageFcmMessage (message),
294
318
expectedIsGroupConversation: true ,
295
319
expectedTitle: "${eg .thirdUser .fullName } to you and 2 others" ,
296
320
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
297
- }));
321
+ }))) ;
298
322
299
- test ('1:1 DM' , () => awaitFakeAsync ((async ) async {
323
+ test ('1:1 DM' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
300
324
await init ();
301
325
final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
302
326
await checkNotifications (async , messageFcmMessage (message),
303
327
expectedIsGroupConversation: false ,
304
328
expectedTitle: eg.otherUser.fullName,
305
329
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
306
- }));
307
-
308
- test ('self-DM' , () => awaitFakeAsync ((async ) async {
330
+ })));
331
+
332
+ test ('1:1 DM: sender avatar loading fails, remote error' , () => runWithHttpClient (
333
+ () => awaitFakeAsync ((async ) async {
334
+ await init ();
335
+ final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
336
+ final data = messageFcmMessage (message);
337
+ await receiveFcmMessage (async , data);
338
+ checkNotification (data,
339
+ messageStyleMessages: [data],
340
+ expectedIsGroupConversation: false ,
341
+ expectedTitle: eg.otherUser.fullName,
342
+ expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' ,
343
+ expectedIconBitmap: null ); // Failed to fetch avatar photo
344
+ }),
345
+ httpClientFactory: () => makeFakeHttpClient (
346
+ response: http.Response .bytes ([], HttpStatus .internalServerError))));
347
+
348
+ test ('1:1 DM: sender avatar loading fails, local error' , () => runWithHttpClient (
349
+ () => awaitFakeAsync ((async ) async {
350
+ await init ();
351
+ final message = eg.dmMessage (from: eg.otherUser, to: [eg.selfUser]);
352
+ final data = messageFcmMessage (message);
353
+ await receiveFcmMessage (async , data);
354
+ checkNotification (data,
355
+ messageStyleMessages: [data],
356
+ expectedIsGroupConversation: false ,
357
+ expectedTitle: eg.otherUser.fullName,
358
+ expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' ,
359
+ expectedIconBitmap: null ); // Failed to fetch avatar photo
360
+ }),
361
+ httpClientFactory: () => makeFakeHttpClient (
362
+ exception: http.ClientException ('Network failure' ))));
363
+
364
+ test ('self-DM' , () => runWithHttpClient (() => awaitFakeAsync ((async ) async {
309
365
await init ();
310
366
final message = eg.dmMessage (from: eg.selfUser, to: []);
311
367
await checkNotifications (async , messageFcmMessage (message),
312
368
expectedIsGroupConversation: false ,
313
369
expectedTitle: eg.selfUser.fullName,
314
370
expectedTagComponent: 'dm:${message .allRecipientIds .join ("," )}' );
315
- }));
371
+ }))) ;
316
372
});
317
373
318
374
group ('NotificationDisplayManager open' , () {
0 commit comments