-
Notifications
You must be signed in to change notification settings - Fork 320
Show user status in UI #1702
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
base: main
Are you sure you want to change the base?
Show user status in UI #1702
Conversation
Copying the review status from #1629 (per #1629 (review)): I've read the first commit except for its tests: and the comments I had there have been resolved. Remaining to review are those tests, and the other 4 commits: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for building this! Comments below.
I've read through the whole of the first commit:
463b938 msglist: Show user status emoji
and the non-test changes in the other commits:
14093b2 recent-dms: Show user status emoji in recent DMs page
646f9d9 new-dm: Show user status emoji
846261a autocomplete: Show user status emoji in user-mention autocomplete
e9f682e profile: Show user status
For this round, I skipped the tests in the later commits because I think a number of my comments on the first commit's tests will apply to those too. So please go ahead and revise the later tests too where applicable.
test/widgets/message_list_test.dart
Outdated
/// Finder for [UserStatusEmoji] widget. | ||
/// | ||
/// Use [type] to specify the exact emoji child widget. It can be either | ||
/// [UnicodeEmojiWidget] or [ImageEmojiWidget]. | ||
Finder findStatusEmoji(Type type) { | ||
assert(type == UnicodeEmojiWidget || type == ImageEmojiWidget); | ||
return find.ancestor( | ||
of: find.byType(type), | ||
matching: find.byType(UserStatusEmoji)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like it's clearer if just inlined at its call sites.
The implementation (minus the assert, which becomes unnecessary) isn't much longer than the call; and it's more transparent about what it's doing. I also don't feel like this is particularly encapsulating any knowledge of the details of how the UserStatusEmoji widget works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, after doing that: how about just find.byType(UserStatusEmoji)
? It's not clear to me what's gained by adding the condition that the widget have a UnicodeEmojiWidget descendant, or an ImageEmojiWidget descendant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, after doing that: how about just
find.byType(UserStatusEmoji)
?
Copying #1629 (comment) here: 🙂
As we discussed in this week’s check-in call,
UserStatusEmoji
for many cases can contain onlySizedBox.shrink()
, including when there’s no status set for a user. So only usingfind.byType(UserStatusEmoji)
will pass the tests even when we don’t pass status for a user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(continued in #1702 (comment) below)
test/widgets/message_list_test.dart
Outdated
void checkUserStatusEmoji(Finder emojiFinder, {required bool isAnimated}) { | ||
check((emojiFinder | ||
.evaluate().first.widget as UserStatusEmoji).neverAnimate).equals(!isAnimated); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also feels like it'd be clearer inlined. The actual check is all about neverAnimate
, but the name doesn't sound like that — it sounds like it's going to be doing some other, more general, check.
test/widgets/message_list_test.dart
Outdated
check((emojiFinder | ||
.evaluate().first.widget as UserStatusEmoji).neverAnimate).equals(!isAnimated); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of finder.evaluate()
, use tester
explicitly:
check((emojiFinder | |
.evaluate().first.widget as UserStatusEmoji).neverAnimate).equals(!isAnimated); | |
check(tester.firstWidget<UserStatusEmoji>(emojiFinder).neverAnimate) | |
.equals(!isAnimated); |
That way it's conceptually clearer what's going on. (The implementation of finder.evaluate
will end up using the same WidgetTester instance anyway, getting hold of it behind the scenes via a singleton.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main motivation for not using tester
was to minimize the number of params passed to the helper methods (checkStatusEmoji
and others in the next commits). Now that it helps in making the tests clearer, so going to use that in the new revision.🙂
test/widgets/message_list_test.dart
Outdated
@@ -91,6 +110,7 @@ void main() { | |||
if (mutedUserIds != null) { | |||
await store.setMutedUsers(mutedUserIds); | |||
} | |||
await store.changeUserStatuses(userStatuses ?? []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about having the individual test cases do this? That way we avoid adding yet another feature to this shared helper. (It probably should have fewer features already — that'd help make fewer things for the reader of each test case to potentially have to think about.)
test/widgets/message_list_test.dart
Outdated
userStatuses: [ | ||
( | ||
user1.userId, | ||
UserStatusChange( | ||
text: OptionSome('Busy'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, these tuples are a bit of a pain to read.
How about making changeUserStatuses
instead take a Map? That seems semantically appropriate, as there's no reason to have the same user ID more than once in the same call. Then:
userStatuses: [ | |
( | |
user1.userId, | |
UserStatusChange( | |
text: OptionSome('Busy'), | |
userStatuses: { | |
user1.userId: UserStatusChange( | |
text: OptionSome('Busy'), |
lib/widgets/autocomplete.dart
Outdated
Flexible(child: labelWidget), | ||
if (option case UserMentionAutocompleteResult(:var userId)) | ||
UserStatusEmoji(userId: userId, size: 18, | ||
padding: const EdgeInsetsDirectional.only(start: 5.0))]), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: similarly:
padding: const EdgeInsetsDirectional.only(start: 5.0))]), | |
padding: const EdgeInsetsDirectional.only(start: 5.0)), | |
]), |
lib/widgets/autocomplete.dart
Outdated
children: [ | ||
labelWidget, | ||
Row(children: [ | ||
Flexible(child: labelWidget), | ||
if (option case UserMentionAutocompleteResult(:var userId)) | ||
UserStatusEmoji(userId: userId, size: 18, | ||
padding: const EdgeInsetsDirectional.only(start: 5.0))]), | ||
if (sublabelWidget != null) sublabelWidget, | ||
])), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this new logic is best included as part of labelWidget
. Conceptually it's closely tied to the user's name, which is part of the label.
Probably in fact the cleanest home for this is in the switch (option)
above, which is already the place where we inspect the details of option
.
lib/widgets/profile.dart
Outdated
@@ -73,17 +75,28 @@ class ProfilePage extends StatelessWidget { | |||
), | |||
// TODO write a test where the user is muted; check this and avatar | |||
TextSpan(text: store.userDisplayName(userId, replaceIfMuted: false)), | |||
UserStatusEmoji.asWidgetSpan( | |||
userId: userId, | |||
fontSize: 20, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this says fontSize: 20
but the presence circle above says fontSize: nameStyle.fontSize!
; should say the same thing both places, since the intent is for them to be the same
lib/widgets/profile.dart
Outdated
@@ -47,6 +48,7 @@ class ProfilePage extends StatelessWidget { | |||
if (user == null) { | |||
return const _ProfileErrorPage(); | |||
} | |||
final userStatus = store.getUserStatus(userId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: put this next to displayEmail
; conceptually they're here for the very same reason
lib/widgets/profile.dart
Outdated
color: DesignVariables.of(context).userStatusText)), | ||
|
||
if (displayEmail != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this blank line intended?
The items just above here feel to me closely related to the items just below, and in particular just as closely related as other items that are grouped next to each other.
eccaa7a
to
686130f
Compare
Thanks @gnprice for the detailed review. Revision pushed, PTAL. Note: CI is failing with the following issue:
I tried pushing several times, but it didn't solve the problem. |
Yeah, that CI failure is unrelated. Filed as #1710, and I'll fix it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! Just a handful of comments this time. I've read the same portions of the branch as last round, plus the two small new commits.
test/widgets/message_list_test.dart
Outdated
await store.changeUserStatuses({ | ||
user.userId: UserStatusChange( | ||
text: OptionSome('Busy'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can be a bit simpler and more compact by calling changeUserStatus
directly:
await store.changeUserStatuses({ | |
user.userId: UserStatusChange( | |
text: OptionSome('Busy'), | |
await store.changeUserStatus(user.userId, UserStatusChange( | |
text: OptionSome('Busy'), |
Also nice is that that corresponds more directly to what happens in the real server API: each user's status comes in a separate UserStatusEvent.
test/widgets/message_list_test.dart
Outdated
await setupMessageListPage(tester, | ||
users: [user], | ||
messages: [eg.streamMessage(sender: user)], | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can compact a bit vertically:
await setupMessageListPage(tester, | |
users: [user], | |
messages: [eg.streamMessage(sender: user)], | |
); | |
await setupMessageListPage(tester, | |
users: [user], messages: [eg.streamMessage(sender: user)]); |
Making each test case more compact is helpful for the reader scanning through a given test case to fully understand what it's doing, and especially for scanning through several neighboring test cases to compare them and to think about what situations are covered and what situations there might be that aren't yet.
test/widgets/message_list_test.dart
Outdated
}); | ||
await tester.pump(); | ||
|
||
checkStatusEmoji(tester, type: ImageEmojiWidget, isPresent: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's kind of puzzling to me that this isn't giving an error, actually: if there's an image emoji here, that should be a RealmContentNetworkImage, which should mean the widget tries to load an image from the network. And I'd expect that to throw an error, given that this test case hasn't set debugNetworkImageHttpClientProvider (e.g. by calling prepareBoringImageHttpClient).
Would you try to get to the bottom of that? I wonder if perhaps this widget tree isn't getting fully built in one frame, and another tester.pump
is needed in order to fully exercise the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first problem is that it needs another tester.pump
, but even with that, it will not throw an error because we have ImageEmojiWidget.errorBuilder
set. Nevertheless, added prepareBoringImageHttpClient
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see.
How did you determine that it needs another tester.pump
— what's missing after the first pump?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Examining the code again, if ImageEmojiWidget.errorBuilder
is not provided, there is no need for the second pump. Only with the first pump will the code throw the error.
The last time that I concluded that it needs two pumps is because I used a print
statement inside ImageEmojiWidget.errorBuilder
with the assumption that if there is an error, then errorBuilder
is called. But it is triggered after a setState
, which requires the second pump in the test code to see if it is called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool; thanks for the explanation!
test/widgets/message_list_test.dart
Outdated
}); | ||
await tester.pump(); | ||
|
||
checkStatusEmoji(tester, type: UnicodeEmojiWidget, isPresent: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of find.byType(UnicodeEmojiWidget)
, how about something like find.text("\u{1f6e0}")
? (Continuing from #1702 (comment) .)
That's more nicely end-to-end — it expresses what we're looking for in terms of what the user sees, rather than the way we've organized our code. In this case it makes it a bit more specific; it also means that if we refactor how those widgets work, these tests wouldn't need to change. (Except insofar as we changed how the UI actually looks to the user in ways this is checking for, which is a good reason to need to change it.)
Then I think that opens up a further refactor that would make these a bit more transparent:
checkStatusEmoji(tester, type: UnicodeEmojiWidget, isPresent: true); | |
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); |
The isPresent: false
call sites could become like:
check(find.text('\u{1f6e0}')).findsNothing();
…. But actually, looking at them, the test data in those test cases doesn't supply any emoji in the first place. I think it's fine to not have any such check in that case, then — instead we can rely on an assumption that our code isn't going to invent out of whole cloth an emoji to display as the user's status emoji.
test/widgets/message_list_test.dart
Outdated
final senderRowFinder = find.ancestor(of: nameFinder, | ||
matching: find.ancestor(of: statusEmojiFinder, | ||
matching: find.byType(Row))); | ||
isPresent | ||
? check(senderRowFinder).findsAny() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, that works.
It does introduce some degree of dependence on the internals of how we've organized our widgets (the existence of a SenderRow widget class, and the fact that it should contain the status emoji). But I think the concept of a "sender row" is a pretty stable aspect of our UI, so that the only way that assumption is likely to break is if we're changing the real user-visible UI in a way that alters the user-facing fact that the sender's status emoji belongs on a row together with their name etc.
title = TextSpan(children: [ | ||
TextSpan(text: store.selfUser.fullName), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can simplify these a bit by using both text
and children
:
title = TextSpan(children: [ | |
TextSpan(text: store.selfUser.fullName), | |
title = TextSpan(text: store.selfUser.fullName, children: [ |
(See the docs for those two TextSpan fields.)
Looking at the TextSpan implementation, I think that saves a small amount of work by eliminating that indirection. There's potentially a lot of these on the screen, so that savings is potentially useful too.
lib/widgets/autocomplete.dart
Outdated
label = store.userDisplayName(userId); | ||
emoji = UserStatusEmoji(userId: userId, size: 18, | ||
padding: const EdgeInsetsDirectional.only(start: 5.0)); | ||
sublabel = store.userDisplayEmail(userId); | ||
case WildcardMentionAutocompleteResult(:var wildcardOption): | ||
avatar = SizedBox.square(dimension: 36, | ||
child: const Icon(ZulipIcons.three_person, size: 24)); | ||
emoji = null; | ||
label = wildcardOption.canonicalString; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: order label vs. emoji consistently
(probably label then emoji, to correspond to how they're displayed)
b14ee65
to
cd4a1a6
Compare
Thanks for the review. New revision pushed. |
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji)))); | ||
await tester.pump(); | ||
|
||
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this commit and the following ones, should we make find.text('\u{1f6e0}')
an implementation detail of checkFindsStatusEmoji
as it stays the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I thought I replied to this in the last round, but I don't see the reply.
It's an important part of how this test works that the emoji it's looking for here is the same one as the emoji the user has as their status emoji. So this line and the emojiCode: '1f6e0'
a few lines above need to agree.
If this were moved inside checkFindsStatusEmoji
, then someone reading that function would see a mysterious \u{1f6e0}
and have to wonder why that particular code point was the one to check for; and someone reading this test case would be unaware that the checkFindsStatusEmoji
function it's calling has a secret dependence on this detail of how the test set up its data. So both sides of that division would become impossible for a reader to properly understand without going and reading the other side too.
In short, it wouldn't really be an implementation detail — it'd be an essential part of the interface that checkFindsStatusEmoji
presents to its callers.
If on the other hand we had a helper function that included both the work of checkFindsStatusEmoji
and the setup step above, then it'd make sense for that helper function's source to specify which emoji to use, because that really would be an implementation detail.
There are a few ways one could organize the code so as to still ensure that both the test cases and the helpers are readable and understandable without having to go search for related code and look for hidden dependencies. But I think the organization in the current revision works well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks for the explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Comments below on the changes.
lib/widgets/new_dm_sheet.dart
Outdated
TextSpan(children: [ | ||
TextSpan(text: store.userDisplayName(userId)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as #1702 (comment)
test/widgets/message_list_test.dart
Outdated
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); | ||
check(find.descendant(of: find.byType(SenderRow), | ||
matching: find.text('Busy'))).findsNothing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is a bit stronger if the "finds nothing" check isn't limited by SenderRow:
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); | |
check(find.descendant(of: find.byType(SenderRow), | |
matching: find.text('Busy'))).findsNothing(); | |
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); | |
check(find.text('Busy')).findsNothing(); |
With the SenderRow restriction, the reader has to wonder if perhaps the status text is getting shown after all, and just doesn't happen to be within a widget called SenderRow. (Perhaps something changed in the organization of the UI code.)
This is a bit different from the situation at #1702 (comment) (where introducing SenderRow was OK), because the check has the opposite polarity. In that test, adding a SenderRow condition makes the check more demanding — so if the code gets reorganized in a way that breaks the test's implicit assumptions about how it's organized, then the test will fail and we'll know we need to update it. But here, adding SenderRow makes it less demanding — so in that situation the check wouldn't fail, but instead would become vacuous, less able to fail when it should.
test/widgets/message_list_test.dart
Outdated
text: OptionSome('Coding'), | ||
emoji: OptionSome(StatusEmoji(emojiName: 'zulip', | ||
emojiCode: 'zulip', reactionType: ReactionType.zulipExtraEmoji)))); | ||
await tester.pumpAndSettle(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best to avoid pumpAndSettle: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#avoid-using-pumpandsettle
Can this be two pumps? Better to be explicit that way.
test/widgets/message_list_test.dart
Outdated
}); | ||
await tester.pump(); | ||
|
||
checkStatusEmoji(tester, type: ImageEmojiWidget, isPresent: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I see.
How did you determine that it needs another tester.pump
— what's missing after the first pump?
test/widgets/message_list_test.dart
Outdated
final senderRowFinder = find.ancestor(of: statusEmojiFinder, | ||
matching: find.byType(SenderRow)); | ||
check(senderRowFinder).findsOne(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think this would be just as clear if inlined, and a bit shorter. It's saying the status emoji has a SenderRow as an ancestor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then also can drop the blank line above, and join this function's whole body in one stanza.
test/widgets/message_list_test.dart
Outdated
check(statusEmojiFinder).findsOne(); | ||
check(tester.firstWidget<UserStatusEmoji>(statusEmojiFinder) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: since we're expecting this to find just one widget anyway, can simplify firstWidget
to widget
:
check(statusEmojiFinder).findsOne(); | |
check(tester.firstWidget<UserStatusEmoji>(statusEmojiFinder) | |
check(statusEmojiFinder).findsOne(); | |
check(tester.widget<UserStatusEmoji>(statusEmojiFinder) |
a238b1c
to
032110b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank @gnprice for the review. New changes pushed.
await tester.pump(); | ||
|
||
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); | ||
check(find.textContaining('Busy')).findsNothing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In places where status emoji is used as a span, I used find.textContaining
instead of find.text
, so if status text is shown in a text span, these tests can detect it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool — that's good for these negative checks (.findsNothing()
), as it makes them stronger.
test/widgets/new_dm_sheet_test.dart
Outdated
checkFindsTileStatusEmoji(tester, user, find.text('\u{1f6e0}')); | ||
check(find.descendant(of: findUserTile(user), | ||
matching: find.textContaining('Busy'))).findsNothing(); | ||
check(findUserChip(user)).findsOne(); | ||
checkFindsChipStatusEmoji(tester, user, find.text('\u{1f6e0}')); | ||
check(find.descendant(of: findUserChip(user), | ||
matching: find.text('Busy'))).findsNothing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still kept this check for the status texts the same as before (didn't remove find.descendant
), as there could be status texts in two places on the same page (one for the user tile, another for the user chip).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. But these are still both negative checks — so it's still true that there shouldn't be "Busy" anywhere at all, right?
And saying "no 'Busy' anywhere" is a stronger requirement than "no 'Busy' in place A and no 'Busy' in place B". So I think all the same logic in #1702 (comment) still applies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the revision! I've now read through the whole thing.
Comments below on a few things that came up only in those remaining tests. I've also just pushed a couple of additional commits, as described in one of those below comments:
9aa3c6293 test: Add find.text analogue with includePlaceholders option
4eca2897e (squash) Demo using the new findText and includePlaceholders
The first should get reordered to earlier in the branch, before the point it's needed; the second does part of the conversion to use the new finder, which you can squash into the appropriate commits before doing the rest.
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji)))); | ||
await tester.pump(); | ||
|
||
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I thought I replied to this in the last round, but I don't see the reply.
It's an important part of how this test works that the emoji it's looking for here is the same one as the emoji the user has as their status emoji. So this line and the emojiCode: '1f6e0'
a few lines above need to agree.
If this were moved inside checkFindsStatusEmoji
, then someone reading that function would see a mysterious \u{1f6e0}
and have to wonder why that particular code point was the one to check for; and someone reading this test case would be unaware that the checkFindsStatusEmoji
function it's calling has a secret dependence on this detail of how the test set up its data. So both sides of that division would become impossible for a reader to properly understand without going and reading the other side too.
In short, it wouldn't really be an implementation detail — it'd be an essential part of the interface that checkFindsStatusEmoji
presents to its callers.
If on the other hand we had a helper function that included both the work of checkFindsStatusEmoji
and the setup step above, then it'd make sense for that helper function's source to specify which emoji to use, because that really would be an implementation detail.
There are a few ways one could organize the code so as to still ensure that both the test cases and the helpers are readable and understandable without having to go search for related code and look for hidden dependencies. But I think the organization in the current revision works well.
matching: find.text(expectedText), | ||
matching: find.textContaining(expectedText), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it's unfortunate to make all the existing tests here less specific. We do want the title that the user sees to be the title we intend — and not, for example, the intended title plus some whitespace or other added text.
I'm guessing that the reason you made this less specific is because the title now has a WidgetSpan in it for the status emoji, and that doesn't match. In that case, it'd be good to keep the check so that it allows that part to differ, but no more.
It looks like Flutter upstream already has most of the work done to support ignoring WidgetSpans in these matches — it's just missing the last step. I just drafted up an implementation of that last step, and I'll push it as an added commit to this branch. (I'll also see about sending the same functionality as a PR upstream.) Then this line can say:
matching: findText(expectedText, includePlaceholders: false),
And after the functionality is upstream, it can be:
matching: find.text(expectedText, includePlaceholders: false),
await tester.pump(); | ||
|
||
checkFindsStatusEmoji(tester, find.text('\u{1f6e0}')); | ||
check(find.textContaining('Busy')).findsNothing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool — that's good for these negative checks (.findsNothing()
), as it makes them stronger.
test/widgets/new_dm_sheet_test.dart
Outdated
checkFindsTileStatusEmoji(tester, user, find.text('\u{1f6e0}')); | ||
check(find.descendant(of: findUserTile(user), | ||
matching: find.textContaining('Busy'))).findsNothing(); | ||
check(findUserChip(user)).findsOne(); | ||
checkFindsChipStatusEmoji(tester, user, find.text('\u{1f6e0}')); | ||
check(find.descendant(of: findUserChip(user), | ||
matching: find.text('Busy'))).findsNothing(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. But these are still both negative checks — so it's still true that there shouldn't be "Busy" anywhere at all, right?
And saying "no 'Busy' anywhere" is a stronger requirement than "no 'Busy' in place A and no 'Busy' in place B". So I think all the same logic in #1702 (comment) still applies.
test/widgets/profile_test.dart
Outdated
await store.changeUserStatus(user.userId, UserStatusChange( | ||
text: OptionSome('Busy'), | ||
emoji: OptionSome(StatusEmoji(emojiName: 'working_on_it', | ||
emojiCode: '1f6e0', reactionType: ReactionType.unicodeEmoji)))); | ||
await tester.pump(); | ||
|
||
check(because: 'find user avatar', find.byType(Avatar).evaluate()).length.equals(1); | ||
check(because: 'find user name', find.text('test user').evaluate()).isNotEmpty(); | ||
final statusEmojiFinder = find.ancestor(of: find.text('\u{1f6e0}'), | ||
matching: find.byType(UserStatusEmoji)); | ||
check(because: 'find user status emoji', statusEmojiFinder).findsOne(); | ||
check(tester.widget<UserStatusEmoji>(statusEmojiFinder) | ||
.neverAnimate).isFalse(); | ||
check(because: 'find user status text', find.text('Busy')).findsOne(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's put these checks into their own new test case, and leave this one as it is.
That will make both sets of test steps easier to understand than when they're combined into one does-everything test scenario like this.
That will also make this test a bit stronger: the existing test case will effectively check that if there is no user status, that doesn't cause this page to crash (e.g. because of a misplaced !
, as in userStatus.text!
).
The bulk of this code is copied verbatim from the implementation of `find.text`, in `package:flutter_test/src/finders.dart`. The new logic is just the includePlaceholders flag, and passing it down to where InlineSpan.toPlainText gets called. The latter already has a corresponding flag, which does the rest.
Status emojis are only shown for self-1:1 and 1:1 conversation items. They're ignored for group conversations as that's what the Web does.
Thanks for the review @gnprice. New revision pushed; PTAL. |
The remaining half of #1629. For related images, please see #1629 description.
Fixes: #197