Skip to content

dialog: Use Cupertino-flavored alert dialogs on iOS#1017

Merged
gnprice merged 3 commits into
zulip:mainfrom
u7088495:cupertino-dialog-for-ios
Aug 3, 2025
Merged

dialog: Use Cupertino-flavored alert dialogs on iOS#1017
gnprice merged 3 commits into
zulip:mainfrom
u7088495:cupertino-dialog-for-ios

Conversation

@u7088495
Copy link
Copy Markdown
Contributor

@u7088495 u7088495 commented Oct 22, 2024

This pull request closes #996.

In dialog.dart:

Switched alertDialog( to alertDialog.adaptive(, as per #996.

Defined new private widget _adaptiveAction which displays a CupertinoDialogAction for IOS and a TextButton otherwise. _adaptiveAction( is used in place of TextButton( when stating dialog actions.

The new result for IOS:
Screenshot 2024-10-22 at 3 31 13 pm

dialog-checks.dart has been updated to check the text content of the respective dialog types depending on the platform

@u7088495 u7088495 marked this pull request as ready for review October 22, 2024 13:59
Copy link
Copy Markdown
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks! Comments below.

Also, please tidy up the branch's commit history for clear and coherent commits. Each commit should be clean and pass all tests (you can run our tests with tools/check).

Comment thread lib/widgets/dialog.dart Outdated
Comment on lines +19 to +34
/// Sets the dialog action to be platform appropriate
/// by displaying a [CupertinoDialogAction] for IOS platforms
/// and a regular [TextButton] otherwise.
Widget _adaptiveAction(
{required BuildContext context,
required VoidCallback onPressed,
required Widget child}) {
final ThemeData theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.fuchsia:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.linux:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.windows:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.iOS:
return CupertinoDialogAction(onPressed: onPressed, child: child);
case TargetPlatform.macOS:
return CupertinoDialogAction(onPressed: onPressed, child: child);
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be tightened up in a few ways:

  • Use empty cases to fall through. From the doc on Dart switch statements:

    https://dart.dev/language/branches#switch-statements

  • Use defaultTargetPlatform instead of passing through context. When the app is run on a device, defaultTargetPlatform will match the platform (iOS or Android). When we need to simulate a specific platform in tests, we set defaultTargetPlatform; search for debugDefaultTargetPlatformOverride for how we do this.

  • More concise dartdoc

Also, because the logic in _dialogActionText fits and is recommended for the Material-style dialog, let's only apply it on Android. This helper is a fine place for that conditional logic; how about changing its interface so it takes a String instead of a Widget, and applies _dialogActionText in the Android branch. In my proposal below, I've also given _dialogActionText an appropriately specific name, _materialDialogActionText. That helper could even be inlined, perhaps in a followup NFC commit.

So, putting all that together:

Suggested change
/// Sets the dialog action to be platform appropriate
/// by displaying a [CupertinoDialogAction] for IOS platforms
/// and a regular [TextButton] otherwise.
Widget _adaptiveAction(
{required BuildContext context,
required VoidCallback onPressed,
required Widget child}) {
final ThemeData theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.fuchsia:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.linux:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.windows:
return TextButton(onPressed: onPressed, child: child);
case TargetPlatform.iOS:
return CupertinoDialogAction(onPressed: onPressed, child: child);
case TargetPlatform.macOS:
return CupertinoDialogAction(onPressed: onPressed, child: child);
}
}
/// A platform-appropriate action for [AlertDialog.adaptive]'s [actions] param.
Widget _adaptiveAction({required VoidCallback onPressed, required String text}) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return TextButton(onPressed: onPressed, child: _materialDialogActionText(text));
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return CupertinoDialogAction(onPressed: onPressed, child: Text(text));
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Is this the idea for inlining the _materialDialogActionText?

return TextButton(onPressed: onPressed, child: Text(text, textAlign: TextAlign.end));

will update the commit message of the latest commit as well as per the guidelines

Comment thread test/widgets/dialog_checks.dart Outdated
///
/// On success, returns the widget's "OK" button.
/// On success, returns the widget's "OK" button
/// (which is a [CupertinoDialogAction] for OS platforms).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We can leave this dartdoc unchanged. It doesn't matter what specific widget the button is, as long as it responds to taps.

Comment on lines -15 to +46
final dialog = tester.widget<AlertDialog>(find.byType(AlertDialog));
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle)));
if (expectedMessage != null) {
if (defaultTargetPlatform == TargetPlatform.iOS
|| defaultTargetPlatform == TargetPlatform.macOS) {

final dialog = tester.widget<CupertinoAlertDialog>(find.byType(CupertinoAlertDialog));

tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
}
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle)));

return tester.widget(
find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, 'OK')));
if (expectedMessage != null) {
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
}

return tester.widget(
find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(CupertinoDialogAction, 'OK')));

}
else {
final dialog = tester.widget<Dialog>(find.byType(Dialog));
tester.widget(find.widgetWithText(Dialog, expectedTitle));
if (expectedMessage != null) {
tester.widget(find.widgetWithText(Dialog, expectedMessage));
}
return tester.widget(
find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, 'OK')));
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's use an exhaustive switch on defaultTargetPlatform, like we do elsewhere. Also, there are several formatting nits that makes this code harder to read than it needs to be.

Proposal:

  switch (defaultTargetPlatform) {
    case TargetPlatform.android:
    case TargetPlatform.fuchsia:
    case TargetPlatform.linux:
    case TargetPlatform.windows: {
      final dialog = tester.widget<Dialog>(find.byType(Dialog));
      tester.widget(find.widgetWithText(Dialog, expectedTitle));
      if (expectedMessage != null) {
        tester.widget(find.widgetWithText(Dialog, expectedMessage));
      }
      return tester.widget(
        find.descendant(of: find.byWidget(dialog),
          matching: find.widgetWithText(TextButton, 'OK')));
    }
    case TargetPlatform.iOS:
    case TargetPlatform.macOS: {
      final dialog = tester.widget<CupertinoAlertDialog>(
        find.byType(CupertinoAlertDialog));
      tester.widget(find.descendant(matchRoot: true,
        of: find.byWidget(dialog.title!), matching: find.text(expectedTitle)));
      if (expectedMessage != null) {
        tester.widget(find.descendant(matchRoot: true,
          of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
      }
      return tester.widget(find.descendant(of: find.byWidget(dialog),
        matching: find.widgetWithText(CupertinoDialogAction, 'OK')));
    }
  }

Comment thread pubspec.lock
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The changes to this file don't look related; please remove them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok thankyou for the feedback, will be sure to onboard these points when working on issues in the future. I'll fix these things up in a new commit.

@gnprice gnprice added the maintainer review PR ready for review by Zulip maintainers label Oct 23, 2024
@gnprice gnprice closed this Oct 24, 2024
@gnprice gnprice reopened this Oct 24, 2024
@gnprice
Copy link
Copy Markdown
Member

gnprice commented Oct 24, 2024

(Misclick, sorry)

@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch 4 times, most recently from a255919 to db740f9 Compare October 24, 2024 04:34
Copy link
Copy Markdown
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks. You'll need to tidy up your branch, as I mentioned above, before we can review this again. If you need help, please ask in #git help in the development community.

Comment thread lib/widgets/dialog.dart
Comment thread pubspec.lock
@u7088495
Copy link
Copy Markdown
Contributor Author

Thanks. You'll need to tidy up your branch, as I mentioned above, before we can review this again. If you need help, please ask in #git help in the development community.

yep my bad, will do so. Thanks for your patients with me on this.

@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch 2 times, most recently from ed8abb7 to 9d2c4df Compare October 25, 2024 01:30
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Oct 25, 2024
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 9e6fc98 to 373cd75 Compare October 25, 2024 02:17
Copy link
Copy Markdown
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks, this is much closer! Small comments below.

Also, a few commit message nits:

dialog: display adaptive dialogs and action buttons based on the target platform

AlertDialog was changed to AlertDialog.adaptive to the effect described in #996.
_adaptiveAction was implemented to display a platform appropriate action for
AlertDialog.adaptive's actions param, as was also discussed in #996.
tests in dialog_test were updated to perform platform appropriate tests.
  • This commit fixes an issue (🎉), so let's put a Fixes: #996 line at the end of it.
  • Also, I think the paragraph ("AlertDialog was changed…") doesn't add anything that's not already obvious from reading the code changes and the linked issue, so let's just delete it.
dialog [nfc]: inline _materialDialogActionTest in _adaptiveAction
    
As was suggested in a comment of the pull request https://github.com/zulip/zulip-flutter/pull/1017#discussion_r1813819656.
  • The URL should be on a new line; we try to wrap to 68 columns except where doing so would make things more confusing. How about:

    As suggested at:
      https://github.com/zulip/zulip-flutter/pull/1017#discussion_r1813819656 
    

Then for both commit messages, use initial caps for the part after the prefix, so:

  • dialog: Display adaptive dialogs and action buttons based on the target platform
  • dialog [nfc]: inline _materialDialogActionTest in _adaptiveAction

For examples of commit messages in the project's style, see the project's Git history; I recommend Greg's excellent tip about how to do that.

Comment thread test/widgets/dialog_checks.dart Outdated
Comment thread test/widgets/dialog_checks.dart Outdated
Comment thread lib/widgets/dialog.dart Outdated
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Oct 26, 2024
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 373cd75 to 52d762d Compare October 26, 2024 23:26
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Oct 26, 2024
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 52d762d to 6166a0a Compare October 26, 2024 23:55
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Oct 27, 2024
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 6166a0a to f91a8c5 Compare October 27, 2024 00:03
@u7088495 u7088495 requested a review from chrisbobbe October 27, 2024 00:09
@chrisbobbe chrisbobbe self-assigned this Oct 28, 2024
Copy link
Copy Markdown
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks!

Some new nits below, and also some more commit-message nits that I should have caught last time 🙂:


dialog: Display adaptive dialogs and action buttons based on the target platform

This summary line is too long, at 80 characters. How about:

dialog: Use Cupertino-flavored alert dialogs on iOS

This also has a bit more information: the fact that iOS was the platform getting the wrong-style dialog. (The fact that the dialog's buttons match the rest of the dialog isn't surprising enough to need a mention. 🙂)

For what the length limit actually is, see discussion, which I've just started 🙂. This was a helpful opportunity for me to spot a change in the zulip/zulip documentation that I'd missed!


dialog [nfc]: Inline _materialDialogActionTest in _adaptiveAction

The function's name is _materialDialogActionText, not _materialDialogActionTest.

Comment thread lib/widgets/dialog.dart Outdated
Comment thread lib/widgets/dialog.dart Outdated
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Oct 30, 2024
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from f91a8c5 to 0a44828 Compare October 30, 2024 04:56
@u7088495
Copy link
Copy Markdown
Contributor Author

Thanks!

Some new nits below, and also some more commit-message nits that I should have caught last time 🙂:

dialog: Display adaptive dialogs and action buttons based on the target platform

This summary line is too long, at 80 characters. How about:

dialog: Use Cupertino-flavored alert dialogs on iOS

This also has a bit more information: the fact that iOS was the platform getting the wrong-style dialog. (The fact that the dialog's buttons match the rest of the dialog isn't surprising enough to need a mention. 🙂)

For what the length limit actually is, see discussion, which I've just started 🙂. This was a helpful opportunity for me to spot a change in the zulip/zulip documentation that I'd missed!

dialog [nfc]: Inline _materialDialogActionTest in _adaptiveAction

The function's name is _materialDialogActionText, not _materialDialogActionTest.

all sorted :)

u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Mar 3, 2025
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from f2739dd to 2641349 Compare March 3, 2025 22:31
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Mar 3, 2025
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 2641349 to 1fbdb29 Compare March 3, 2025 22:55
@u7088495
Copy link
Copy Markdown
Contributor Author

u7088495 commented Mar 3, 2025

Alright @gnprice I think it's ready for review now, though I'm not entirely sure about the way I've laid out the tests so there may be some things to improve there.

Copy link
Copy Markdown
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! Comments below.

Comment thread test/widgets/dialog_test.dart Outdated
Comment on lines +33 to +36
checkErrorDialog(tester, expectedTitle: title, expectedMessage: message);

}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: no blank line at end of block

Suggested change
checkErrorDialog(tester, expectedTitle: title, expectedMessage: message);
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
checkErrorDialog(tester, expectedTitle: title, expectedMessage: message);
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));

Comment thread test/widgets/dialog_test.dart Outdated
showErrorDialog(context: context, title: title, message: message);
await tester.pump();

Widget button = checkErrorDialog(tester, expectedTitle: title);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

same point as #1017 (comment)

Comment thread test/widgets/dialog_test.dart Outdated
Comment on lines +67 to +68
late String actionText;
const String expectedActionText = "Action performed!";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can be simplified, because the string isn't doing any work here:

Suggested change
late String actionText;
const String expectedActionText = "Action performed!";
bool wasPressed = false;

Comment thread test/widgets/dialog_test.dart Outdated
actionButtonText: actionButtonText, onActionButtonPress: onActionButtonPress);
await tester.pump();

final (Widget actionButton, _) = checkSuggestedActionDialog(tester, expectedTitle: title, expectedMessage: message,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: omit obvious types

Suggested change
final (Widget actionButton, _) = checkSuggestedActionDialog(tester, expectedTitle: title, expectedMessage: message,
final (actionButton, _) = checkSuggestedActionDialog(tester, expectedTitle: title, expectedMessage: message,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Do things like this also count as obvious types?
const String title = "Dialog Title";

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

and so should var be used for
bool wasPressed = false;?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

const String title = "Dialog Title";

Yeah, just const title = ….

and so should var be used for
bool wasPressed = false;?

Ah good question. No, when the variable is going to be mutated we use the type instead of var.

One reason for that is that it's very common for the type a mutable variable needs to be more general than the one it's initialized with — for example it gets initialized to null but later mutated to some other value. Dart's type inference would use only the initializer to decide the value, so would give it the narrower type (and then a type error when later trying to store a value that doesn't fit that narrow type). By contrast for final or const variables, the type chosen by type inference is almost always the type we want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok interesting, good to know, thanks

Comment thread test/widgets/dialog_test.dart Outdated
actionButtonText: actionButtonText, onActionButtonPress: onActionButtonPress);
await tester.pump();

final (Widget actionButton, _) = checkSuggestedActionDialog(tester, expectedTitle: title, expectedMessage: message,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: line too long — keep all the relevant information within 80 columns

Comment thread test/widgets/dialog_test.dart Outdated

}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));

testWidgets('user closes error dialog', (tester) async {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test should cover both platforms too.

Comment on lines 55 to +53
check(find.byType(AlertDialog)).findsNothing();
check(find.bySubtype<AlertDialog>()).findsNothing();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These two are redundant.

Widget button = checkErrorDialog(tester, expectedTitle: title);
await tester.tap(find.byWidget(button));
await tester.pump();
checkNoDialog(tester);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the current version, the analyzer fails here after the first or second commit. A PR should pass all tests at each commit of the PR, in order to keep the commits fully coherent:
https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html#each-commit-must-be-coherent

Probably cleanest is to have the rename of checkNoDialog, and accompanying changes, happen as the first commit.

u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Mar 4, 2025
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from 1fbdb29 to dca0970 Compare March 4, 2025 03:42
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Mar 4, 2025
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from dca0970 to d4000cc Compare March 4, 2025 03:47
u7088495 added a commit to u7088495/zulip-flutter that referenced this pull request Mar 4, 2025
@u7088495 u7088495 force-pushed the cupertino-dialog-for-ios branch from d4000cc to 85d4820 Compare March 4, 2025 22:55
@u7088495
Copy link
Copy Markdown
Contributor Author

u7088495 commented Mar 4, 2025

@gnprice ok I think I've made all those changes and cleaned up the commit history now

Copy link
Copy Markdown
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. This looks good except one comment below.

One other question: see this message about how we'll be crediting commits in the upcoming Zulip release blog post:
#general > Zulip 10.0 release credits: code contributions @ 💬

As is, this PR would cause you to be credited like so (either in the upcoming release blog post, or in the one after that if it's merged after that release):

     3  Brynly Mitchell - u7088495

If that's how you'd like your name to appear, there's nothing more for you to do. If you'd like it written differently, you can use git commit --amend --author=… to change it in a rebase. Then you can set your name in your Git config to control the name that future commits get automatically.


// TODO(#996) update this to check for per-platform flavors of alert dialog
void checkNoErrorDialog(WidgetTester tester) {
check(find.byType(AlertDialog)).findsNothing();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, in #1017 (comment) I meant those two checks were redundant with each other. If there's no widget that's of a subtype of AlertDialog, there's certainly none that's of the exact type AlertDialog.

@gnprice
Copy link
Copy Markdown
Member

gnprice commented Mar 24, 2025

I see you've pushed a revision since my last review. Is this intended to be ready again for merge/review?

(It's useful to make that clear with a comment, because people often push to a branch with intermediate revisions before pushing one they consider ready.)

@BrynMtchll
Copy link
Copy Markdown

Ah yep It's just about ready but I just hadn't gotten around to doing a once-over to check everything looks alright.

@BrynMtchll
Copy link
Copy Markdown

ok should be all good to go now :)

@gnprice
Copy link
Copy Markdown
Member

gnprice commented Apr 2, 2025

@BrynMtchll I'm guessing you're the same person as @u7088495? Please pick a single account to stick to, at least for interacting with any given project (like Zulip) — it makes things less confusing 🙂

It looks like this has some merge/rebase conflicts after 0094978 / #1410. Would you rebase and resolve those? Then I think this will be all ready for merge.

@u7088495
Copy link
Copy Markdown
Contributor Author

u7088495 commented Apr 3, 2025

Sorry yes that's my other account, my mistake - I forgot that I switched over for a different project

@u7088495
Copy link
Copy Markdown
Contributor Author

u7088495 commented Apr 4, 2025

Alright I think it's ready; I refactored the learn more test logic into checkErrorDialog as well.

Copy link
Copy Markdown
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 the revision! There's one substantive change needed in the new test code, and a couple of easy nits.

Comment thread lib/widgets/dialog.dart Outdated
Comment on lines +68 to +69
text: zulipLocalizations.errorDialogLearnMore,
),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: preserve formatting

Suggested change
text: zulipLocalizations.errorDialogLearnMore,
),
text: zulipLocalizations.errorDialogLearnMore),

Comment thread lib/widgets/dialog.dart
Comment on lines +25 to +31
textAlign: TextAlign.end));
case TargetPlatform.iOS:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: a blank line to separate these cases helps make the structure easier to see:

Suggested change
textAlign: TextAlign.end));
case TargetPlatform.iOS:
textAlign: TextAlign.end));
case TargetPlatform.iOS:

Comment thread test/widgets/dialog_checks.dart Outdated
Comment on lines +36 to +40
if (expectedLearnMoreButtonUrl != null) {
check(testBinding.takeLaunchUrlCalls()).single.equals((
url: expectedLearnMoreButtonUrl,
mode: LaunchMode.inAppBrowserView));
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Checking for this button is useful, but this check doesn't work in this context. The check will work only if the caller has already gone and tapped on the button. There's nothing in this function's dartdoc that says the caller should do that, and it's not something a reader would naturally assume they need to do.

If a caller is going and tapping on the button themself, then the caller can also easily go and do this check — the check isn't using any specific knowledge about how the error dialogs work.

It looks like there's one call site that passes this option. So let's move this check to that call site.

@u7088495
Copy link
Copy Markdown
Contributor Author

hey @gnprice, sorry I've stepped away from this - I've come back to it and think it's ready for another review now :).

Copy link
Copy Markdown
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 the revision! Small comments.

Comment thread test/widgets/dialog_test.dart Outdated
Comment on lines +60 to +72
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
check(testBinding.takeLaunchUrlCalls()).single.equals((
url: learnMoreButtonUrl,
mode: LaunchMode.inAppBrowserView));
case TargetPlatform.iOS:
case TargetPlatform.macOS:
check(testBinding.takeLaunchUrlCalls()).single.equals((
url: learnMoreButtonUrl,
mode: LaunchMode.externalApplication));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is fairly verbose, in a way that obscures the fact that the only difference between the cases is the value of mode.

Instead, let's first determine teh mode we expect; then have just one, more focused, check that calls takeLaunchUrlCalls.

For examples, search the existing tests in test/ for "LaunchMode.externalApplication".

Comment thread test/widgets/dialog_test.dart Outdated
Comment on lines +56 to +60
await tester.tap(find.text('Learn more'));
check(testBinding.takeLaunchUrlCalls()).single.equals((
url: Uri.parse('https://foo.example'),
mode: LaunchMode.inAppBrowserView));
});

checkErrorDialog(tester, expectedTitle: title);

switch (defaultTargetPlatform) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These steps are out of their logical order. The dialog must be there before the tester.tap on one of its buttons can work; so let's check for the dialog before trying to tap one of its buttons.

@u7088495
Copy link
Copy Markdown
Contributor Author

u7088495 commented Aug 2, 2025

alright should be ready for another revision :)

Copy link
Copy Markdown
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 @u7088495 for all your work on this! All looks good now except a couple of nits below. I'll fix those and merge.

.equals((url: learnMoreButtonUrl, mode: expectedMode));

}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: no blank line at end of block

Suggested change
.equals((url: learnMoreButtonUrl, mode: expectedMode));
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));
.equals((url: learnMoreButtonUrl, mode: expectedMode));
}, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));

Comment on lines +56 to +64
checkErrorDialog(tester, expectedTitle: title);
await tester.tap(find.text('Learn more'));
check(testBinding.takeLaunchUrlCalls()).single.equals((
url: Uri.parse('https://foo.example'),
mode: LaunchMode.inAppBrowserView));
});

final expectedMode = switch (defaultTargetPlatform) {
TargetPlatform.android => LaunchMode.inAppBrowserView,
TargetPlatform.iOS => LaunchMode.externalApplication,
_ => throw StateError('attempted to test with $defaultTargetPlatform'),
};

check(testBinding.takeLaunchUrlCalls()).single
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: use blank lines to group test steps into stanzas of set up, then check.

See #1317 (comment) and the previous comments linked from there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here's the adjusted version of this test which I just merged:

    testWidgets('tap "Learn more" button', (tester) async {
      await prepare(tester);

      final learnMoreButtonUrl = Uri.parse('https://foo.example');
      showErrorDialog(context: context, title: title, learnMoreButtonUrl: learnMoreButtonUrl);
      await tester.pump();
      checkErrorDialog(tester, expectedTitle: title);

      await tester.tap(find.text('Learn more'));
      final expectedMode = switch (defaultTargetPlatform) {
        TargetPlatform.android => LaunchMode.inAppBrowserView,
        TargetPlatform.iOS =>     LaunchMode.externalApplication,
        _ => throw StateError('attempted to test with $defaultTargetPlatform'),
      };
      check(testBinding.takeLaunchUrlCalls()).single
        .equals((url: learnMoreButtonUrl, mode: expectedMode));
    }, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}));

Note how each stanza (each group separated by blank lines) first sets up a situation, then checks that it's as expected.

@u7088495
Copy link
Copy Markdown
Contributor Author

Thanks @u7088495 for all your work on this! All looks good now except a couple of nits below. I'll fix those and merge.

Amazing, thank you so much for all of your help through this @gnprice, I really appreciate it, it's been a great experience for me.

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.

dialog: Use Cupertino-flavored alert dialogs on iOS

5 participants