@@ -8,6 +8,7 @@ import 'package:zulip/api/model/events.dart';
88import 'package:zulip/api/model/model.dart' ;
99import 'package:zulip/api/route/events.dart' ;
1010import 'package:zulip/api/route/messages.dart' ;
11+ import 'package:zulip/log.dart' ;
1112import 'package:zulip/model/store.dart' ;
1213import 'package:zulip/notifications/receive.dart' ;
1314
@@ -391,6 +392,20 @@ void main() {
391392 check (store.userSettings! .twentyFourHourTime).isTrue ();
392393 }));
393394
395+ String ? lastReportedError;
396+ String ? takeLastReportedError () {
397+ final result = lastReportedError;
398+ lastReportedError = null ;
399+ return result;
400+ }
401+
402+ Future <void > logAndReportErrorToUserBriefly (String ? message, {
403+ String ? details,
404+ }) async {
405+ if (message == null ) return ;
406+ lastReportedError = '$message \n $details ' ;
407+ }
408+
394409 test ('handles expired queue' , () => awaitFakeAsync ((async ) async {
395410 await prepareStore ();
396411 updateMachine.debugPauseLoop ();
@@ -428,19 +443,44 @@ void main() {
428443 }));
429444
430445 group ('retries on errors' , () {
431- void checkRetry (void Function () prepareError) {
446+ void checkRetry (void Function () prepareError, {
447+ int expectedFailureCountNotifyThreshold = 0 ,
448+ }) {
449+ reportErrorToUserBriefly = logAndReportErrorToUserBriefly;
450+ addTearDown (() => reportErrorToUserBriefly = defaultReportErrorToUserBriefly);
451+
452+ final expectedErrorMessage =
453+ 'Error connecting to Zulip. Retrying…\n '
454+ 'Error connecting to Zulip at ${eg .realmUrl .origin }. Will retry' ;
455+
432456 awaitFakeAsync ((async ) async {
433457 await prepareStore (lastEventId: 1 );
434458 updateMachine.debugPauseLoop ();
435459 updateMachine.poll ();
436460 check (async .pendingTimers).length.equals (0 );
437461
438- // Make the request, inducing an error in it.
439- prepareError ();
440- updateMachine.debugAdvanceLoop ();
441- async .elapse (Duration .zero);
442- checkLastRequest (lastEventId: 1 );
443- check (store).isLoading.isTrue ();
462+ // Need to add 1 to the upperbound for that one additional request
463+ // to trigger error reporting.
464+ for (int i = 0 ; i < expectedFailureCountNotifyThreshold + 1 ; i++ ) {
465+ // Make the request, inducing an error in it.
466+ prepareError ();
467+ if (i > 0 ) {
468+ // End polling backoff from the previous iteration.
469+ async .flushTimers ();
470+ }
471+ updateMachine.debugAdvanceLoop ();
472+ check (lastReportedError).isNull ();
473+ async .elapse (Duration .zero);
474+ if (i < expectedFailureCountNotifyThreshold) {
475+ // The error message should not appear until the `updateMachine`
476+ // has retried the given number of times.
477+ check (takeLastReportedError ()).isNull ();
478+ } else {
479+ check (takeLastReportedError ()).isNotNull ().contains (expectedErrorMessage);
480+ }
481+ checkLastRequest (lastEventId: 1 );
482+ check (store).isLoading.isTrue ();
483+ }
444484
445485 // Polling doesn't resume immediately; there's a timer.
446486 check (async .pendingTimers).length.equals (1 );
@@ -461,11 +501,13 @@ void main() {
461501 }
462502
463503 test ('Server5xxException' , () {
464- checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ));
504+ checkRetry (() => connection.prepare (httpStatus: 500 , body: 'splat' ),
505+ expectedFailureCountNotifyThreshold: 5 );
465506 });
466507
467508 test ('NetworkException' , () {
468- checkRetry (() => connection.prepare (exception: Exception ("failed" )));
509+ checkRetry (() => connection.prepare (exception: Exception ("failed" )),
510+ expectedFailureCountNotifyThreshold: 5 );
469511 });
470512
471513 test ('ZulipApiException' , () {
0 commit comments