Skip to content

Commit ec71f30

Browse files
committed
actions test: Cover PlatformActions.launchUrl
1 parent 78ef95d commit ec71f30

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

test/model/binding.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,18 @@ class TestZulipBinding extends ZulipBinding {
161161

162162
/// The value that `ZulipBinding.instance.launchUrl()` should return.
163163
///
164-
/// See also [takeLaunchUrlCalls].
164+
/// See also:
165+
/// * [launchUrlException]
166+
/// * [takeLaunchUrlCalls]
165167
bool launchUrlResult = true;
166168

169+
/// The [PlatformException] that `ZulipBinding.instance.launchUrl()` should throw.
170+
///
171+
/// See also:
172+
/// * [launchUrlResult]
173+
/// * [takeLaunchUrlCalls]
174+
PlatformException? launchUrlException;
175+
167176
void _resetLaunchUrl() {
168177
launchUrlResult = true;
169178
_launchUrlCalls = null;
@@ -189,6 +198,22 @@ class TestZulipBinding extends ZulipBinding {
189198
url_launcher.LaunchMode mode = url_launcher.LaunchMode.platformDefault,
190199
}) async {
191200
(_launchUrlCalls ??= []).add((url: url, mode: mode));
201+
202+
if (!launchUrlResult && launchUrlException != null) {
203+
throw FlutterError.fromParts([
204+
ErrorSummary(
205+
'TestZulipBinding.launchUrl called '
206+
'with launchUrlResult: false and non-null launchUrlException'),
207+
ErrorHint(
208+
'Tests should either set launchUrlResult or launchUrlException, '
209+
'but not both.'),
210+
]);
211+
}
212+
213+
if (launchUrlException != null) {
214+
throw launchUrlException!;
215+
}
216+
192217
return launchUrlResult;
193218
}
194219

test/model/settings_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ void main() {
1313
final nonHttpLink = Uri.parse('mailto:[email protected]');
1414

1515
group('getUrlLaunchMode', () {
16+
// See also test/widgets/actions_test.dart, where we test that the setting
17+
// is actually used when we open links, with PlatformActions.launchUrl.
18+
1619
testAndroidIos('globalSettings.browserPreference is null; use our per-platform defaults for HTTP links', () {
1720
final globalStore = eg.globalStore(globalSettings: eg.globalSettings(
1821
browserPreference: null));

test/widgets/actions_test.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22

33
import 'package:checks/checks.dart';
4+
import 'package:flutter/foundation.dart';
45
import 'package:flutter/material.dart';
56
import 'package:flutter/services.dart';
67
import 'package:flutter_test/flutter_test.dart';
@@ -10,8 +11,10 @@ import 'package:zulip/api/model/model.dart';
1011
import 'package:zulip/api/model/narrow.dart';
1112
import 'package:zulip/api/route/messages.dart';
1213
import 'package:zulip/model/binding.dart';
14+
import 'package:zulip/model/database.dart';
1315
import 'package:zulip/model/localizations.dart';
1416
import 'package:zulip/model/narrow.dart';
17+
import 'package:zulip/model/settings.dart';
1518
import 'package:zulip/model/store.dart';
1619
import 'package:zulip/widgets/actions.dart';
1720

@@ -414,5 +417,100 @@ void main() {
414417
await checkSnackBar(tester, expected: true);
415418
});
416419
});
420+
421+
group('launchUrl', () {
422+
Future<void> call(WidgetTester tester, {required Uri url}) async {
423+
await tester.pumpWidget(TestZulipApp(
424+
child: Builder(builder: (context) => Center(
425+
child: ElevatedButton(
426+
onPressed: () async {
427+
await PlatformActions.launchUrl(context, url);
428+
},
429+
child: const Text('link'))))));
430+
await tester.pump();
431+
await tester.tap(find.text('link'));
432+
await tester.pump(Duration.zero);
433+
}
434+
435+
final httpUrl = Uri.parse('http://chat.zulip.org');
436+
final nonHttpUrl = Uri.parse('mailto:[email protected]');
437+
438+
Future<void> runAndCheckSuccess(WidgetTester tester, {
439+
required Uri url,
440+
required UrlLaunchMode expectedModeAndroid,
441+
required UrlLaunchMode expectedModeIos,
442+
}) async {
443+
await call(tester, url: url);
444+
445+
final expectedMode = switch (defaultTargetPlatform) {
446+
TargetPlatform.android => expectedModeAndroid,
447+
TargetPlatform.iOS => expectedModeIos,
448+
_ => throw StateError('attempted to test with $defaultTargetPlatform'),
449+
};
450+
check(testBinding.takeLaunchUrlCalls()).single
451+
.equals((url: url, mode: expectedMode));
452+
}
453+
454+
final androidIosVariant = TargetPlatformVariant({TargetPlatform.iOS, TargetPlatform.android});
455+
456+
testWidgets('globalSettings.browserPreference is null; use our per-platform defaults for HTTP links', (tester) async {
457+
await testBinding.globalStore.updateGlobalSettings(
458+
GlobalSettingsCompanion(browserPreference: Value(null)));
459+
await runAndCheckSuccess(tester,
460+
url: httpUrl,
461+
expectedModeAndroid: UrlLaunchMode.inAppBrowserView,
462+
expectedModeIos: UrlLaunchMode.externalApplication);
463+
}, variant: androidIosVariant);
464+
465+
testWidgets('globalSettings.browserPreference is null; use our per-platform defaults for non-HTTP links', (tester) async {
466+
await testBinding.globalStore.updateGlobalSettings(
467+
GlobalSettingsCompanion(browserPreference: Value(null)));
468+
await runAndCheckSuccess(tester,
469+
url: nonHttpUrl,
470+
expectedModeAndroid: UrlLaunchMode.platformDefault,
471+
expectedModeIos: UrlLaunchMode.externalApplication);
472+
}, variant: androidIosVariant);
473+
474+
testWidgets('globalSettings.browserPreference is inApp; follow the user preference for http links', (tester) async {
475+
await testBinding.globalStore.updateGlobalSettings(
476+
GlobalSettingsCompanion(browserPreference: Value(BrowserPreference.inApp)));
477+
await runAndCheckSuccess(tester,
478+
url: httpUrl,
479+
expectedModeAndroid: UrlLaunchMode.inAppBrowserView,
480+
expectedModeIos: UrlLaunchMode.inAppBrowserView);
481+
}, variant: androidIosVariant);
482+
483+
testWidgets('globalSettings.browserPreference is inApp; use platform default for non-http links', (tester) async {
484+
await testBinding.globalStore.updateGlobalSettings(
485+
GlobalSettingsCompanion(browserPreference: Value(BrowserPreference.inApp)));
486+
await runAndCheckSuccess(tester,
487+
url: nonHttpUrl,
488+
expectedModeAndroid: UrlLaunchMode.platformDefault,
489+
expectedModeIos: UrlLaunchMode.platformDefault);
490+
}, variant: androidIosVariant);
491+
492+
testWidgets('globalSettings.browserPreference is external; follow the user preference', (tester) async {
493+
await testBinding.globalStore.updateGlobalSettings(
494+
GlobalSettingsCompanion(browserPreference: Value(BrowserPreference.external)));
495+
await runAndCheckSuccess(tester,
496+
url: httpUrl,
497+
expectedModeAndroid: UrlLaunchMode.externalApplication,
498+
expectedModeIos: UrlLaunchMode.externalApplication);
499+
}, variant: androidIosVariant);
500+
501+
testWidgets('ZulipBinding.launchUrl returns false', (tester) async {
502+
testBinding.launchUrlResult = false;
503+
await call(tester, url: httpUrl);
504+
checkErrorDialog(tester, expectedTitle: 'Unable to open link');
505+
}, variant: androidIosVariant);
506+
507+
testWidgets('ZulipBinding.launchUrl throws PlatformException', (tester) async {
508+
testBinding.launchUrlException = PlatformException(code: 'code', message: 'error message');
509+
await call(tester, url: httpUrl);
510+
checkErrorDialog(tester,
511+
expectedTitle: 'Unable to open link',
512+
expectedMessage: 'Link could not be opened: ${httpUrl.toString()}\n\nerror message');
513+
}, variant: androidIosVariant);
514+
});
417515
});
418516
}

0 commit comments

Comments
 (0)