Skip to content

Commit 7171d71

Browse files
avatar: Show placeholder on image load error.
Fixes #1558
1 parent 71ae691 commit 7171d71

File tree

3 files changed

+89
-12
lines changed

3 files changed

+89
-12
lines changed

lib/widgets/user.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class AvatarImage extends StatelessWidget {
6666
final user = store.getUser(userId);
6767

6868
if (user == null) { // TODO(log)
69-
return const SizedBox.shrink();
69+
return _AvatarPlaceholder(size: size);
7070
}
7171

7272
if (replaceIfMuted && store.isUserMuted(userId)) {
@@ -79,7 +79,7 @@ class AvatarImage extends StatelessWidget {
7979
};
8080

8181
if (resolvedUrl == null) {
82-
return const SizedBox.shrink();
82+
return _AvatarPlaceholder(size: size);
8383
}
8484

8585
final avatarUrl = AvatarUrl.fromUserData(resolvedUrl: resolvedUrl);
@@ -89,6 +89,7 @@ class AvatarImage extends StatelessWidget {
8989
avatarUrl.get(physicalSize),
9090
filterQuality: FilterQuality.medium,
9191
fit: BoxFit.cover,
92+
errorBuilder: (_, _, _) => _AvatarPlaceholder(size: size),
9293
);
9394
}
9495
}

test/test_images.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22
import 'dart:io';
3+
import 'dart:typed_data';
34

45
import 'package:flutter/widgets.dart';
56
import 'package:flutter_test/flutter_test.dart';
@@ -12,12 +13,18 @@ import 'package:flutter_test/flutter_test.dart';
1213
/// before the end of the test.
1314
// TODO(upstream) simplify callers by using addTearDown: https://github.com/flutter/flutter/issues/123189
1415
// See also: https://github.com/flutter/flutter/issues/121917
15-
FakeImageHttpClient prepareBoringImageHttpClient() {
16+
FakeImageHttpClient prepareBoringImageHttpClient({bool success = true}) {
1617
final httpClient = FakeImageHttpClient();
1718
debugNetworkImageHttpClientProvider = () => httpClient;
18-
httpClient.request.response
19-
..statusCode = HttpStatus.ok
20-
..content = kSolidBlueAvatar;
19+
if (success) {
20+
httpClient.request.response
21+
..statusCode = HttpStatus.ok
22+
..content = kSolidBlueAvatar;
23+
} else {
24+
httpClient.request.response
25+
..statusCode = HttpStatus.notFound
26+
..content = Uint8List(0);
27+
}
2128
return httpClient;
2229
}
2330

test/widgets/user_test.dart

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import 'package:checks/checks.dart';
2-
import 'package:flutter/cupertino.dart';
32
import 'package:flutter/material.dart';
4-
import 'package:flutter/rendering.dart';
53
import 'package:flutter_test/flutter_test.dart';
64
import 'package:zulip/model/store.dart';
75
import 'package:zulip/widgets/image.dart';
8-
import 'package:zulip/widgets/store.dart';
6+
import 'package:zulip/widgets/icons.dart';
97
import 'package:zulip/widgets/user.dart';
108

119
import '../example_data.dart' as eg;
1210
import '../model/binding.dart';
1311
import '../model/test_store.dart';
1412
import '../stdlib_checks.dart';
1513
import '../test_images.dart';
14+
import 'test_app.dart';
1615

1716
void main() {
1817
TestZulipBinding.ensureInitialized();
@@ -28,9 +27,12 @@ void main() {
2827
await store.addUser(user);
2928

3029
prepareBoringImageHttpClient();
31-
await tester.pumpWidget(GlobalStoreWidget(
32-
child: PerAccountStoreWidget(accountId: eg.selfAccount.id,
33-
child: AvatarImage(userId: user.userId, size: size ?? 30))));
30+
await tester.pumpWidget(
31+
TestZulipApp(
32+
accountId: eg.selfAccount.id,
33+
child: AvatarImage(userId: user.userId, size: size ?? 30),
34+
)
35+
);
3436
await tester.pump();
3537
await tester.pump();
3638
tester.widget(find.byType(AvatarImage));
@@ -78,5 +80,72 @@ void main() {
7880
check(await actualUrl(tester, avatarUrl)).isNull();
7981
debugNetworkImageHttpClientProvider = null;
8082
});
83+
84+
testWidgets('shows placeholder when image URL gives error', (WidgetTester tester) async {
85+
addTearDown(testBinding.reset);
86+
prepareBoringImageHttpClient(success: false);
87+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
88+
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
89+
final badUser = eg.user(avatarUrl: 'https://zulip.com/avatarinvalid.png');
90+
await store.addUser(badUser);
91+
await tester.pumpWidget(
92+
TestZulipApp(
93+
accountId: eg.selfAccount.id,
94+
child: AvatarImage(userId: badUser.userId, size: 30)));
95+
await tester.pumpAndSettle();
96+
check(
97+
find.descendant(
98+
of: find.byType(AvatarImage),
99+
matching: find.byIcon(ZulipIcons.person),
100+
).evaluate().length
101+
).equals(1);
102+
debugNetworkImageHttpClientProvider = null;
103+
});
104+
105+
testWidgets('shows placeholder when user avatarUrl is null', (WidgetTester tester) async {
106+
addTearDown(testBinding.reset);
107+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
108+
final store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
109+
110+
final userWithNoUrl = eg.user(avatarUrl: null);
111+
await store.addUser(userWithNoUrl);
112+
113+
await tester.pumpWidget(
114+
TestZulipApp(
115+
accountId: eg.selfAccount.id,
116+
child: AvatarImage(userId: userWithNoUrl.userId, size: 30),
117+
),
118+
);
119+
await tester.pumpAndSettle();
120+
121+
check(
122+
find.descendant(
123+
of: find.byType(AvatarImage),
124+
matching: find.byIcon(ZulipIcons.person),
125+
).evaluate().length
126+
).equals(1);
127+
});
128+
129+
testWidgets('shows placeholder when user is not found', (WidgetTester tester) async {
130+
addTearDown(testBinding.reset);
131+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
132+
133+
const nonExistentUserId = 9999999;
134+
135+
await tester.pumpWidget(
136+
TestZulipApp(
137+
accountId: eg.selfAccount.id,
138+
child: AvatarImage(userId: nonExistentUserId, size: 30),
139+
),
140+
);
141+
await tester.pumpAndSettle();
142+
143+
check(
144+
find.descendant(
145+
of: find.byType(AvatarImage),
146+
matching: find.byIcon(ZulipIcons.person),
147+
).evaluate().length
148+
).equals(1);
149+
});
81150
});
82151
}

0 commit comments

Comments
 (0)