11import 'dart:convert' ;
22
33import 'package:checks/checks.dart' ;
4+ import 'package:flutter/foundation.dart' ;
45import 'package:flutter/material.dart' ;
56import 'package:flutter/services.dart' ;
67import 'package:flutter_test/flutter_test.dart' ;
@@ -10,8 +11,10 @@ import 'package:zulip/api/model/model.dart';
1011import 'package:zulip/api/model/narrow.dart' ;
1112import 'package:zulip/api/route/messages.dart' ;
1213import 'package:zulip/model/binding.dart' ;
14+ import 'package:zulip/model/database.dart' ;
1315import 'package:zulip/model/localizations.dart' ;
1416import 'package:zulip/model/narrow.dart' ;
17+ import 'package:zulip/model/settings.dart' ;
1518import 'package:zulip/model/store.dart' ;
1619import '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\n error message' );
513+ }, variant: androidIosVariant);
514+ });
417515 });
418516}
0 commit comments