Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 39 additions & 25 deletions lib/widgets/dialog.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import '../generated/l10n/zulip_localizations.dart';
Expand All @@ -7,18 +9,29 @@ import 'app.dart';
import 'content.dart';
import 'store.dart';

Widget _dialogActionText(String text) {
return Text(
text,

// As suggested by
// https://api.flutter.dev/flutter/material/AlertDialog/actions.html :
// > It is recommended to set the Text.textAlign to TextAlign.end
// > for the Text within the TextButton, so that buttons whose
// > labels wrap to an extra line align with the overall
// > OverflowBar's alignment within the dialog.
textAlign: TextAlign.end,
);
/// 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: Text(
text,
// As suggested by
// https://api.flutter.dev/flutter/material/AlertDialog/actions.html :
// > It is recommended to set the Text.textAlign to TextAlign.end
// > for the Text within the TextButton, so that buttons whose
// > labels wrap to an extra line align with the overall
// > OverflowBar's alignment within the dialog.
textAlign: TextAlign.end));

case TargetPlatform.iOS:
case TargetPlatform.macOS:
return CupertinoDialogAction(onPressed: onPressed, child: Text(text));
}
}

/// Tracks the status of a dialog, in being still open or already closed.
Expand Down Expand Up @@ -71,17 +84,17 @@ DialogStatus<void> showErrorDialog({
final zulipLocalizations = ZulipLocalizations.of(context);
final future = showDialog<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
builder: (BuildContext context) => AlertDialog.adaptive(
title: Text(title),
content: message != null ? SingleChildScrollView(child: Text(message)) : null,
actions: [
if (learnMoreButtonUrl != null)
TextButton(
_adaptiveAction(
onPressed: () => PlatformActions.launchUrl(context, learnMoreButtonUrl),
child: _dialogActionText(zulipLocalizations.errorDialogLearnMore)),
TextButton(
text: zulipLocalizations.errorDialogLearnMore),
_adaptiveAction(
onPressed: () => Navigator.pop(context),
child: _dialogActionText(zulipLocalizations.errorDialogContinue)),
text: zulipLocalizations.errorDialogContinue),
]));
return DialogStatus(future);
}
Expand All @@ -103,16 +116,16 @@ DialogStatus<bool> showSuggestedActionDialog({
final zulipLocalizations = ZulipLocalizations.of(context);
final future = showDialog<bool>(
context: context,
builder: (BuildContext context) => AlertDialog(
builder: (BuildContext context) => AlertDialog.adaptive(
title: Text(title),
content: SingleChildScrollView(child: Text(message)),
actions: [
TextButton(
_adaptiveAction(
onPressed: () => Navigator.pop<bool>(context, null),
child: _dialogActionText(zulipLocalizations.dialogCancel)),
TextButton(
text: zulipLocalizations.dialogCancel),
_adaptiveAction(
onPressed: () => Navigator.pop<bool>(context, true),
child: _dialogActionText(actionButtonText ?? zulipLocalizations.dialogContinue)),
text: actionButtonText ?? zulipLocalizations.dialogContinue),
]));
return DialogStatus(future);
}
Expand Down Expand Up @@ -164,7 +177,7 @@ class UpgradeWelcomeDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
final zulipLocalizations = ZulipLocalizations.of(context);
return AlertDialog(
return AlertDialog.adaptive(
title: Text(zulipLocalizations.upgradeWelcomeDialogTitle),
content: SingleChildScrollView(
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Expand All @@ -177,8 +190,9 @@ class UpgradeWelcomeDialog extends StatelessWidget {
zulipLocalizations.upgradeWelcomeDialogLinkText)),
])),
actions: [
TextButton(onPressed: () => Navigator.pop(context),
child: Text(zulipLocalizations.upgradeWelcomeDialogDismiss)),
_adaptiveAction(
onPressed: () => Navigator.pop(context),
text: zulipLocalizations.upgradeWelcomeDialogDismiss)
]);
}
}
10 changes: 5 additions & 5 deletions test/widgets/action_sheet_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ void main() {
await tester.tap(findButtonForLabel('Mark channel as read'));
await tester.pumpAndSettle();
checkRequest(someChannel.streamId);
checkNoErrorDialog(tester);
checkNoDialog(tester);
});

testWidgets('request fails', (tester) async {
Expand Down Expand Up @@ -776,7 +776,7 @@ void main() {
await tester.tap(findButtonForLabel('Mark as resolved'));
await tester.pumpAndSettle();

checkNoErrorDialog(tester);
checkNoDialog(tester);
checkRequest(message.id, '✔ zulip');
});

Expand All @@ -791,7 +791,7 @@ void main() {
await tester.tap(findButtonForLabel('Mark as resolved'));
await tester.pumpAndSettle();

checkNoErrorDialog(tester);
checkNoDialog(tester);
checkRequest(message.id, '✔ zulip');
});

Expand All @@ -805,7 +805,7 @@ void main() {
await tester.tap(findButtonForLabel('Mark as unresolved'));
await tester.pumpAndSettle();

checkNoErrorDialog(tester);
checkNoDialog(tester);
checkRequest(message.id, 'zulip');
});

Expand All @@ -819,7 +819,7 @@ void main() {
await tester.tap(findButtonForLabel('Mark as unresolved'));
await tester.pumpAndSettle();

checkNoErrorDialog(tester);
checkNoDialog(tester);
checkRequest(message.id, 'zulip');
});

Expand Down
6 changes: 3 additions & 3 deletions test/widgets/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -401,14 +401,14 @@ void main() {
check(ZulipApp.ready).value.isFalse();
await tester.pump();
check(findSnackBarByText(message).evaluate()).isEmpty();
checkNoErrorDialog(tester);
checkNoDialog(tester);

check(ZulipApp.ready).value.isTrue();
// After app startup, reportErrorToUserBriefly displays a SnackBar.
reportErrorToUserBriefly(message, details: details);
await tester.pumpAndSettle();
check(findSnackBarByText(message).evaluate()).single;
checkNoErrorDialog(tester);
checkNoDialog(tester);

// Open the error details dialog.
await tester.tap(find.text('Details'));
Expand Down Expand Up @@ -493,7 +493,7 @@ void main() {
reportErrorToUserModally(title, message: message);
check(ZulipApp.ready).value.isFalse();
await tester.pump();
checkNoErrorDialog(tester);
checkNoDialog(tester);

check(ZulipApp.ready).value.isTrue();
// After app startup, reportErrorToUserModally displays an [AlertDialog].
Expand Down
12 changes: 6 additions & 6 deletions test/widgets/compose_box_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ void main() {
await prepareWithContent(tester,
makeStringWithCodePoints(kMaxMessageLengthCodePoints));
await tapSendButton(tester);
checkNoErrorDialog(tester);
checkNoDialog(tester);
});

testWidgets('code points not counted unnecessarily', (tester) async {
Expand Down Expand Up @@ -434,7 +434,7 @@ void main() {
await prepareWithTopic(tester,
makeStringWithCodePoints(kMaxTopicLengthCodePoints));
await tapSendButton(tester);
checkNoErrorDialog(tester);
checkNoDialog(tester);
});

testWidgets('code points not counted unnecessarily', (tester) async {
Expand Down Expand Up @@ -938,7 +938,7 @@ void main() {
await setupAndTapSend(tester, prepareResponse: (int messageId) {
connection.prepare(json: SendMessageResult(id: messageId).toJson());
});
checkNoErrorDialog(tester);
checkNoDialog(tester);
});

testWidgets('ZulipApiException', (tester) async {
Expand Down Expand Up @@ -1078,7 +1078,7 @@ void main() {
check(call.allowMultiple).equals(true);
check(call.type).equals(FileType.media);

checkNoErrorDialog(tester);
checkNoDialog(tester);

check(controller!.content.text)
.equals('see image: [Uploading image.jpg…]()\n\n');
Expand Down Expand Up @@ -1137,7 +1137,7 @@ void main() {
check(call.source).equals(ImageSource.camera);
check(call.requestFullMetadata).equals(false);

checkNoErrorDialog(tester);
checkNoDialog(tester);

check(controller!.content.text)
.equals('see image: [Uploading image.jpg…]()\n\n');
Expand Down Expand Up @@ -1864,7 +1864,7 @@ void main() {
UploadFileResult(url: '/path/file.jpg').toJson());
await tester.tap(find.byIcon(ZulipIcons.attach_file), warnIfMissed: false);
await tester.pump(Duration.zero);
checkNoErrorDialog(tester);
checkNoDialog(tester);
check(testBinding.takePickFilesCalls()).length.equals(1);
connection.takeRequests(); // upload request

Expand Down
91 changes: 63 additions & 28 deletions test/widgets/dialog_checks.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:checks/checks.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_checks/flutter_checks.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:zulip/widgets/dialog.dart';

/// In a widget test, check that showErrorDialog was called with the right text.
/// In a widget test, check that [showErrorDialog] was called with the right text.
///
/// Checks for an error dialog matching an expected title
/// and, optionally, matching an expected message. Fails if none is found.
Expand All @@ -15,24 +17,41 @@ Widget checkErrorDialog(WidgetTester tester, {
required String expectedTitle,
String? expectedMessage,
}) {
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) {
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
}

// TODO check "Learn more" button?
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
final dialog = tester.widget<AlertDialog>(find.bySubtype<AlertDialog>());
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(TextButton, 'OK')));

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')));
}
}

// TODO(#996) update this to check for per-platform flavors of alert dialog
void checkNoErrorDialog(WidgetTester tester) {
check(find.byType(AlertDialog)).findsNothing();
Copy link
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.

/// Checks that there is no dialog.
/// Fails if one is found.
void checkNoDialog(WidgetTester tester) {
check(find.byType(Dialog)).findsNothing();
check(find.bySubtype<AlertDialog>()).findsNothing();
check(find.byType(CupertinoAlertDialog)).findsNothing();
}

/// In a widget test, check that [showSuggestedActionDialog] was called
Expand All @@ -49,19 +68,35 @@ void checkNoErrorDialog(WidgetTester tester) {
required String expectedMessage,
String? expectedActionButtonText,
}) {
final dialog = tester.widget<AlertDialog>(find.byType(AlertDialog));
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle)));
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
final dialog = tester.widget<AlertDialog>(find.bySubtype<AlertDialog>());
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.title!), matching: find.text(expectedTitle)));
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));

final actionButton = tester.widget(
find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, expectedActionButtonText ?? 'Continue')));
final actionButton = tester.widget(find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, expectedActionButtonText ?? 'Continue')));
final cancelButton = tester.widget(find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, 'Cancel')));
return (actionButton, cancelButton);

final cancelButton = tester.widget(
find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(TextButton, 'Cancel')));
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)));
tester.widget(find.descendant(matchRoot: true,
of: find.byWidget(dialog.content!), matching: find.text(expectedMessage)));

return (actionButton, cancelButton);
final actionButton = tester.widget(find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(CupertinoDialogAction, expectedActionButtonText ?? 'Continue')));
final cancelButton = tester.widget(find.descendant(of: find.byWidget(dialog),
matching: find.widgetWithText(CupertinoDialogAction, 'Cancel')));
return (actionButton, cancelButton);
}
}
Loading