diff --git a/lib/widgets/user.dart b/lib/widgets/user.dart index 182a073d30..905eb2ee71 100644 --- a/lib/widgets/user.dart +++ b/lib/widgets/user.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import '../api/model/model.dart'; import '../model/avatar_url.dart'; import '../model/binding.dart'; @@ -90,6 +89,7 @@ class AvatarImage extends StatelessWidget { avatarUrl.get(physicalSize), filterQuality: FilterQuality.medium, fit: BoxFit.cover, + errorBuilder: (_, _, _) => _AvatarPlaceholder(size: size), ); } } diff --git a/test/test_images.dart b/test/test_images.dart index c7a04c264f..8a6eb53677 100644 --- a/test/test_images.dart +++ b/test/test_images.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'dart:io'; - +import 'dart:typed_data'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -12,15 +12,20 @@ import 'package:flutter_test/flutter_test.dart'; /// before the end of the test. // TODO(upstream) simplify callers by using addTearDown: https://github.com/flutter/flutter/issues/123189 // See also: https://github.com/flutter/flutter/issues/121917 -FakeImageHttpClient prepareBoringImageHttpClient() { +FakeImageHttpClient prepareBoringImageHttpClient({bool success = true}) { final httpClient = FakeImageHttpClient(); debugNetworkImageHttpClientProvider = () => httpClient; - httpClient.request.response - ..statusCode = HttpStatus.ok - ..content = kSolidBlueAvatar; + if (success) { + httpClient.request.response + ..statusCode = HttpStatus.ok + ..content = kSolidBlueAvatar; + } else { + httpClient.request.response + ..statusCode = HttpStatus.notFound + ..content = Uint8List(0); + } return httpClient; } - class FakeImageHttpClient extends Fake implements HttpClient { final FakeImageHttpClientRequest request = FakeImageHttpClientRequest(); diff --git a/test/widgets/user_test.dart b/test/widgets/user_test.dart index 5078da0497..d5ca4c83e0 100644 --- a/test/widgets/user_test.dart +++ b/test/widgets/user_test.dart @@ -5,14 +5,15 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zulip/model/store.dart'; import 'package:zulip/widgets/content.dart'; +import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/store.dart'; import 'package:zulip/widgets/user.dart'; - import '../example_data.dart' as eg; import '../model/binding.dart'; import '../model/test_store.dart'; import '../stdlib_checks.dart'; import '../test_images.dart'; +import 'test_app.dart'; void main() { TestZulipBinding.ensureInitialized(); @@ -78,5 +79,26 @@ void main() { check(await actualUrl(tester, avatarUrl)).isNull(); debugNetworkImageHttpClientProvider = null; }); + testWidgets('shows placeholder when image URL gives error', (WidgetTester tester) async { + addTearDown(testBinding.reset); + prepareBoringImageHttpClient(success: false); + await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot()); + final store = await testBinding.globalStore.perAccount(eg.selfAccount.id); + final badUser = eg.user(avatarUrl: 'https://zulip.com/avatarinvalid.png'); + await store.addUser(badUser); + await tester.pumpWidget(TestZulipApp( + child: PerAccountStoreWidget( + accountId: eg.selfAccount.id, + child: AvatarImage(userId: badUser.userId, size: 30)))); + await tester.pumpAndSettle(); + expect( + find.descendant( + of: find.byType(AvatarImage), + matching: find.byIcon(ZulipIcons.person), + ), + findsOneWidget, + ); + debugNetworkImageHttpClientProvider = null; + }); }); }