Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

msglist: Use [User.avatarUrl] instead of [Message.avatarUrl] #246

Merged
merged 3 commits into from
Aug 3, 2023

Conversation

chrisbobbe
Copy link
Collaborator

Now, as demonstrated in the new test, if the author of a message
changes their avatar, we'll see the update in the message list as
soon as we get the event.

While we're at it, comment out the property on [Message] to guide
future consumers to [User].

Related: #135

@chrisbobbe chrisbobbe added a-msglist The message-list screen, except what's label:a-content a-model Implementing our data model (PerAccountStore, etc.) labels Aug 1, 2023
@chrisbobbe chrisbobbe requested a review from gnprice August 1, 2023 21:49
import 'package:flutter_test/flutter_test.dart';


class FakeHttpClient extends Fake implements HttpClient {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test [nfc]: Move fake-image-request code to its own file

N.B.: This now has the same name as FakeHttpClient in test/api/fake_api.dart, which causes awkwardness when a file wants import lines for both files. Later in this branch, this awkwardness shows up like this:

-import '../api/fake_api.dart';
+import '../api/fake_api.dart' as fake_api;

Maybe there's a better name for this class, like FakeImageHttpClient? (Then maybe also its friends, like FakeHttpClientResponse, get renamed correspondingly?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think if this is going to be a public name that's imported in various tests (rather than a private name in one test file), it needs a more specific name.

The FakeImageFoo names sound good. It's likely that if we wanted to use these for faking something other than fetching a network image, we'd need to add other features to them.

@chrisbobbe chrisbobbe force-pushed the pr-msglist-avatar-live-update branch from a75fcb1 to 278acdc Compare August 1, 2023 21:55
@chrisbobbe
Copy link
Collaborator Author

chrisbobbe commented Aug 2, 2023

This came up when thinking about avatar URLs for #119.

@chrisbobbe chrisbobbe force-pushed the pr-msglist-avatar-live-update branch from 278acdc to 5f18921 Compare August 2, 2023 21:45
@chrisbobbe
Copy link
Collaborator Author

(Just rebased onto current main.)

Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Small comments, all in the tests.

import 'package:flutter_test/flutter_test.dart';


class FakeHttpClient extends Fake implements HttpClient {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think if this is going to be a public name that's imported in various tests (rather than a private name in one test file), it needs a more specific name.

The FakeImageFoo names sound good. It's likely that if we wanted to use these for faking something other than fetching a network image, we'd need to add other features to them.

import 'package:zulip/widgets/message_list.dart';
import 'package:zulip/widgets/sticky_header.dart';
import 'package:zulip/widgets/store.dart';

import '../api/fake_api.dart';
import '../api/fake_api.dart' as fake_api;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we were keeping the generic FakeHttpClient name so did have to deal with a collision here, I think I'd prefer in this case to use show to just expose the one name we want from this library:

Suggested change
import '../api/fake_api.dart' as fake_api;
import '../api/fake_api.dart' show FakeApiConnection;

That way we don't have to say fake_api.FakeApiConnection, which looks awfully redundant.

@@ -51,6 +59,58 @@ Future<void> setupMessageListPage(WidgetTester tester, {
void main() {
TestZulipBinding.ensureInitialized();

group('MessageWithSender', () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: let's put this after the ScrollToBottomButton group, so the tests are in roughly the order of the code they respectively focus on

addTearDown(TestZulipBinding.instance.reset);

RealmContentNetworkImage? findAvatarImageWidget(WidgetTester tester) {
final firstMessageWithSender = tester.widgetList(find.byType(MessageWithSender)).first;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a tester.firstWidget; can this use that?

final firstMessageWithSender = tester.widgetList(find.byType(MessageWithSender)).first;
return tester.widgetList<RealmContentNetworkImage>(
find.descendant(
of: find.byWidget(firstMessageWithSender),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this could just be the find.byType(MessageWithSender) directly, and then firstWidget around the whole thing.

I think the difference in behavior would just be: if the first MessageWithSender has several RealmContentNetworkImage descendants, then this version would throw and that version wouldn't. Which… hmm, in fact it's bad to throw in that case, right? The message content could legitimately have an image, though it doesn't actually in this test.

In any event it doesn't seem helpful to throw in that case. If we have a bug where MessageWithSender shows the avatar twice or whatever, this isn't the test to detect that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whichever way it's organized, I don't love that we have to look for RealmContentNetworkImage here to find the avatar. That feels like a bit of an implementation detail of the message list, and also a bit fragile because that widget type could appear in the content. I'd rather have something we can look for that more specifically marks the avatar.

I guess with an Avatar widget that'd provide an answer. Though that also feels like a bit of an implementation detail.

Another version would be to look for a semantic label. That'd be appealing because it's not an implementation detail — it's something user-facing. Not sure what the right semantic label would be in this case, though. In fact I kind of think for the semantics tree we likely want to hide the avatar — there's nothing it communicates that isn't already covered by the sender name, other than the visual of the image itself.

Anyway, using RealmContentNetworkImage is fine for now. We can switch to the Avatar widget upon adding that, and possibly to semantics later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Yeah, I felt kind of uneasy having it look for RealmContentNetworkImage too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this could just be the find.byType(MessageWithSender) directly, and then firstWidget around the whole thing.

Ah: I tried this just now, but then a test failure reminded me that we'd need it to be firstWidgetOrNull, and that isn't offered.

Comment on lines 78 to 80
case String(): {
check(findAvatarImageWidget(tester)).isNotNull().src.equals(resolveUrl(avatarUrl, eg.selfAccount));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for the braces:

Suggested change
case String(): {
check(findAvatarImageWidget(tester)).isNotNull().src.equals(resolveUrl(avatarUrl, eg.selfAccount));
}
case String():
check(findAvatarImageWidget(tester)).isNotNull().src.equals(resolveUrl(avatarUrl, eg.selfAccount));

In fact in Dart (unlike JS) one can even declare variables in a switch case, with no need for extra braces.

Comment on lines 77 to 78
switch (avatarUrl) {
case String(): {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also when the switch is just "null or not" it's probably simplest to just use an if/else 🙂

final avatarUrl = sender.avatarUrl;
switch (avatarUrl) {
case String(): {
check(findAvatarImageWidget(tester)).isNotNull().src.equals(resolveUrl(avatarUrl, eg.selfAccount));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
check(findAvatarImageWidget(tester)).isNotNull().src.equals(resolveUrl(avatarUrl, eg.selfAccount));
check(findAvatarImageWidget(tester)).isNotNull()
.src.equals(resolveUrl(avatarUrl, eg.selfAccount));

Comment on lines 103 to 104
// TODO only vary [avatarUrl], not other fields
checkResultForSender(eg.user(userId: eg.selfUser.userId, avatarUrl: '/foo.png'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This checkResultForSender is a private helper for this test, right? And it only consults the avatarUrl.

So it seems like you could resolve this TODO by just passing the avatarUrl value itself.

@chrisbobbe chrisbobbe force-pushed the pr-msglist-avatar-live-update branch from 5f18921 to 6141ee3 Compare August 3, 2023 00:39
@chrisbobbe
Copy link
Collaborator Author

Thanks for the review! Revision pushed.

Now, as demonstrated in the new test, if the author of a message
changes their avatar, we'll see the update in the message list as
soon as we get the event.

While we're at it, comment out the property on [Message] to guide
future consumers to [User].

Related: zulip#135
@gnprice
Copy link
Member

gnprice commented Aug 3, 2023

Thanks! LGTM; merging.

@gnprice gnprice force-pushed the pr-msglist-avatar-live-update branch from 6141ee3 to aade783 Compare August 3, 2023 20:20
@gnprice gnprice merged commit aade783 into zulip:main Aug 3, 2023
@chrisbobbe chrisbobbe deleted the pr-msglist-avatar-live-update branch August 3, 2023 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-model Implementing our data model (PerAccountStore, etc.) a-msglist The message-list screen, except what's label:a-content
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants