Skip to content

settings: Migrate to new RadioGroup API; write widget tests for two just-added settings #1602

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

chrisbobbe
Copy link
Collaborator

This tests the observable behavior more directly.
RadioListTile.checked has been deprecated (zulip#1545), so we'd like this
test to stop relying on that implementation detail.
"Open message feeds at" and "Mark messages as read on scroll".

Related: zulip#1571
Related: zulip#1583
This migration is verified by the tests that we touched in the
previous commits; they ensure that the buttons' checked state still
updates visibly.

Fixes: zulip#1545
@chrisbobbe chrisbobbe requested a review from gnprice June 17, 2025 21:23
@chrisbobbe chrisbobbe added the integration review Added by maintainers when PR may be ready for integration label Jun 17, 2025
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 for taking care of this (including writing those tests)! All looks good; some nits and for-the-future musings below.

This also seems like definitely a good change in the upstream API, too ­— better structured to match how the UI really works (or at least should work, for radio buttons to be the right fit).

Comment on lines +177 to +180
for (final setting in VisitFirstUnreadSetting.values) {
final thisSettingTitle = settingTitle(setting);
checkRadioButtonAppearsChecked<VisitFirstUnreadSetting>(tester,
thisSettingTitle, thisSettingTitle == settingTitle(expectedSetting));
Copy link
Member

Choose a reason for hiding this comment

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

This comparison can be on the values directly instead of the labels, right?

Suggested change
for (final setting in VisitFirstUnreadSetting.values) {
final thisSettingTitle = settingTitle(setting);
checkRadioButtonAppearsChecked<VisitFirstUnreadSetting>(tester,
thisSettingTitle, thisSettingTitle == settingTitle(expectedSetting));
for (final setting in VisitFirstUnreadSetting.values) {
final thisSettingTitle = settingTitle(setting);
checkRadioButtonAppearsChecked<VisitFirstUnreadSetting>(tester,
thisSettingTitle, setting == expectedSetting);

Comment on lines +30 to +34
testNavObserver = TestNavigatorObserver()
..onPushed = ((route, _) => lastPushedRoute = route)
..onPopped = ((route, _) => lastPoppedRoute = route);
lastPushedRoute = null;
lastPoppedRoute = null;
Copy link
Member

Choose a reason for hiding this comment

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

Not for this PR, but: given how much this pattern is recurring now, I'm thinking it'd be good to centralize the code for it.

Perhaps by giving TestNavigatorObserver (or a subclass) some more structure, to track this sort of state itself.

await tester.pump();
check(lastPushedRoute).isA<MaterialWidgetRoute>()
.page.isA<VisitFirstUnreadSettingPage>();
await tester.pump((lastPushedRoute as TransitionRoute).transitionDuration);
Copy link
Member

Choose a reason for hiding this comment

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

(again not for this PR)

If the nav observer tracks a log of both pushes and pops in a single list (or I guess even just tracks the last single such action), then this could perhaps even look something like:

Suggested change
await tester.pump((lastPushedRoute as TransitionRoute).transitionDuration);
await tester.pump(navObserver.lastTransitionDuration);

and exactly the same in the pop case (when the last thing that happened was a pop, the lastTransitionDuration getter would look at its reverseTransitionDuration).


await tester.tap(findRadioListTileWithTitle<VisitFirstUnreadSetting>(
settingTitle(VisitFirstUnreadSetting.always)));
await tester.pumpAndSettle(); // TODO why doesn't just `pump` work?
Copy link
Member

Choose a reason for hiding this comment

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

Hmm, dunno. Does pump(Duration.zero) work?

Looking at the implementation of setThemeSetting, it'll wait for the (simulated) DB write to complete before it updates the within-Dart store. In real life that's some IO that will take a small but nonzero amount of time, so its completion callback won't happen in a microtask, which is what pump() does. So it fits that that also wouldn't suffice in the test.

await tester.pumpAndSettle();
checkPage(tester, expectedSetting: VisitFirstUnreadSetting.never);

await tester.tap(find.byType(BackButton));
Copy link
Member

Choose a reason for hiding this comment

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

Fun alternative (see its docs) which I happened across recently while browsing other code:

Suggested change
await tester.tap(find.byType(BackButton));
await tester.tap(find.backButton());

});
});

group('MarkReadOnScrollSetting', () {
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 add one or two more of these, we'll likely want to try abstracting them out a bit more — the body of the test case is repetitive enough that it risks getting hard to read without glossing over.

But explicit is always a good way to write the first version, and I think it works fine for two of these.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration review Added by maintainers when PR may be ready for integration
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Resolve deprecation warnings about radio buttons
2 participants