diff --git a/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart b/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart index 60e1e98bb..26e210dca 100644 --- a/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart +++ b/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart @@ -13,6 +13,7 @@ import 'package:ubuntupro/core/environment.dart'; import 'package:ubuntupro/launch_agent.dart'; import 'package:ubuntupro/main.dart' as app; import 'package:ubuntupro/pages/landscape/landscape_page.dart'; +import 'package:ubuntupro/pages/landscape_skip/landscape_skip_page.dart'; import 'package:ubuntupro/pages/startup/startup_page.dart'; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_page.dart'; import 'package:ubuntupro/pages/subscription_status/subscription_status_page.dart'; @@ -128,18 +129,15 @@ void main() { await tester.tap(button); await tester.pumpAndSettle(); - // check that we transitioned to the LandscapePage - l10n = tester.l10n(); + // check we transition to skip page + l10n = tester.l10n(); final radios = find.byType(YaruSelectableContainer); - expect(radios, findsNWidgets(3)); - await tester.tap(radios.at(1)); - await tester.pump(); - await tester.tap(radios.at(0)); - await tester.pump(); - await tester.tap(radios.at(2)); - await tester.pump(); - final skip = find.button(l10n.buttonSkip); + expect(radios, findsNWidgets(2)); + final skip = find.text(l10n.landscapeSkip); await tester.tap(skip); + await tester.pump(); + final next = find.button(l10n.buttonNext); + await tester.tap(next); await tester.pumpAndSettle(); // checks that we transitioned to the SubscriptionStatusPage @@ -172,6 +170,17 @@ void main() { await tester.tap(button); await tester.pumpAndSettle(); + // check we transition to skip page + l10n = tester.l10n(); + final radios = find.byType(YaruSelectableContainer); + expect(radios, findsNWidgets(2)); + final register = find.text(l10n.landscapeSkipRegister); + await tester.tap(register); + await tester.pump(); + final next = find.button(l10n.buttonNext); + await tester.tap(next); + await tester.pumpAndSettle(); + // check that we transitioned to the LandscapePage l10n = tester.l10n(); final selfHostedRadio = find.ancestor( diff --git a/gui/packages/ubuntupro/lib/app.dart b/gui/packages/ubuntupro/lib/app.dart index adaf848a2..b0eb64334 100644 --- a/gui/packages/ubuntupro/lib/app.dart +++ b/gui/packages/ubuntupro/lib/app.dart @@ -11,6 +11,7 @@ import 'core/agent_connection.dart'; import 'core/agent_monitor.dart'; import 'core/settings.dart'; import 'pages/landscape/landscape_page.dart'; +import 'pages/landscape_skip/landscape_skip_page.dart'; import 'pages/startup/startup_page.dart'; import 'pages/subscribe_now/subscribe_now_page.dart'; import 'pages/subscription_status/subscription_status_page.dart'; @@ -76,6 +77,17 @@ class Pro4WSLApp extends StatelessWidget { }, ), if (settings.isLandscapeConfigurationEnabled) ...{ + Routes.skipLandscape: WizardRoute( + builder: (_) => const LandscapeSkipPage(), + onNext: (settings) { + switch (settings.arguments as SkipEnum) { + case SkipEnum.skip: + return Routes.subscriptionStatus; + default: + return null; + } + }, + ), Routes.configureLandscape: const WizardRoute(builder: LandscapePage.create), Routes.subscriptionStatus: WizardRoute( diff --git a/gui/packages/ubuntupro/lib/constants.dart b/gui/packages/ubuntupro/lib/constants.dart index ae46f9372..eda5ff338 100644 --- a/gui/packages/ubuntupro/lib/constants.dart +++ b/gui/packages/ubuntupro/lib/constants.dart @@ -12,3 +12,5 @@ const kVersion = String.fromEnvironment( 'UP4W_FULL_VERSION', defaultValue: 'Dev', ); + +const kLandscapeTitle = 'Landscape'; diff --git a/gui/packages/ubuntupro/lib/l10n/app_en.arb b/gui/packages/ubuntupro/lib/l10n/app_en.arb index 1cbdf00a9..65fc1a72c 100644 --- a/gui/packages/ubuntupro/lib/l10n/app_en.arb +++ b/gui/packages/ubuntupro/lib/l10n/app_en.arb @@ -57,7 +57,11 @@ "purchaseStatusServer":"Something went wrong with Microsoft Store. Please try again later.", "purchaseStatusUnknown": "Unknown error when trying to purchase the subscription. Consider restarting this app.", - "landscapeHeading": "Configure the connection to {landscapeLink} to manage your Ubuntu WSL instances remotely.", + "landscapeSkip": "Skip for now", + "landscapeSkipDescription": "You can always configure Landscape later in this app", + "landscapeSkipRegister": "Register with Landscape", + + "landscapeHeading": "Configure the connection to Landscape to manage your Ubuntu WSL instances remotely. {landscapeLink}", "@landscapeHeading": { "placeholders": { "landscapeLink": { @@ -95,7 +99,7 @@ } }, - "buttonNext": "Continue", + "buttonNext": "Next", "buttonSkip": "Skip", "buttonBack": "Back", diff --git a/gui/packages/ubuntupro/lib/pages/landscape/landscape_model.dart b/gui/packages/ubuntupro/lib/pages/landscape/landscape_model.dart index a3f6426d5..0e3cc6145 100644 --- a/gui/packages/ubuntupro/lib/pages/landscape/landscape_model.dart +++ b/gui/packages/ubuntupro/lib/pages/landscape/landscape_model.dart @@ -19,7 +19,7 @@ class LandscapeModel extends ChangeNotifier { LandscapeModel(this.client); /// The URL to be shown in the UI. - final landscapeURI = Uri.https('ubuntu.com', '/landscape'); + static final landscapeURI = Uri.https('ubuntu.com', '/landscape'); /// Whether the current form is complete (ready to be submitted). bool get isComplete => _active.isComplete; diff --git a/gui/packages/ubuntupro/lib/pages/landscape/landscape_page.dart b/gui/packages/ubuntupro/lib/pages/landscape/landscape_page.dart index c2df76073..f181ae92f 100644 --- a/gui/packages/ubuntupro/lib/pages/landscape/landscape_page.dart +++ b/gui/packages/ubuntupro/lib/pages/landscape/landscape_page.dart @@ -12,6 +12,7 @@ import 'package:wizard_router/wizard_router.dart'; import 'package:yaru/widgets.dart'; import 'package:yaru/yaru.dart'; +import '/constants.dart'; import '/core/agent_api_client.dart'; import '/pages/widgets/page_widgets.dart'; import 'landscape_model.dart'; @@ -25,7 +26,6 @@ class LandscapePage extends StatelessWidget { super.key, required this.onApplyConfig, required this.onBack, - required this.onSkip, }); /// Callable invoked when this page successfully applies the configuration. @@ -34,9 +34,6 @@ class LandscapePage extends StatelessWidget { /// Callable invoked when the user navigates back. final void Function() onBack; - /// Callable invoked when the user skips this page. - final void Function() onSkip; - @override Widget build(BuildContext context) { final lang = AppLocalizations.of(context); @@ -49,21 +46,17 @@ class LandscapePage extends StatelessWidget { ), ), ), - ).copyWith( - a: const TextStyle( - decoration: TextDecoration.underline, - ), ); return LandingPage( svgAsset: 'assets/Landscape-tag.svg', - title: 'Landscape', + title: kLandscapeTitle, children: [ // Only rebuilds if the value of model.landscapeURI changes (never in production) Selector( - selector: (_, model) => model.landscapeURI, + selector: (_, model) => LandscapeModel.landscapeURI, builder: (context, uri, _) => MarkdownBody( - data: lang.landscapeHeading('[Landscape]($uri)'), + data: lang.landscapeHeading('[${lang.learnMore}]($uri)'), onTapLink: (_, href, __) => launchUrl(uri), styleSheet: linkStyle, ), @@ -79,7 +72,6 @@ class LandscapePage extends StatelessWidget { selector: (_, model) => model.isComplete, builder: (context, isComplete, _) => _NavigationButtonRow( onBack: onBack, - onSkip: onSkip, onNext: isComplete ? () => _tryApplyConfig(context) : null, ), ), @@ -114,13 +106,11 @@ class LandscapePage extends StatelessWidget { landscapePage = LandscapePage( onApplyConfig: Wizard.of(context).back, onBack: Wizard.of(context).back, - onSkip: Wizard.of(context).back, ); } else { landscapePage = LandscapePage( onApplyConfig: Wizard.of(context).next, onBack: Wizard.of(context).back, - onSkip: Wizard.of(context).next, ); } @@ -242,12 +232,10 @@ class LandscapeConfigForm extends StatelessWidget { class _NavigationButtonRow extends StatelessWidget { const _NavigationButtonRow({ this.onBack, - this.onSkip, this.onNext, }); final void Function()? onBack; - final void Function()? onSkip; final void Function()? onNext; @override @@ -261,13 +249,6 @@ class _NavigationButtonRow extends StatelessWidget { child: Text(lang.buttonBack), ), const Spacer(), - FilledButton( - onPressed: onSkip, - child: Text(lang.buttonSkip), - ), - const SizedBox( - width: 16.0, - ), ElevatedButton( onPressed: onNext, child: Text(lang.buttonNext), diff --git a/gui/packages/ubuntupro/lib/pages/landscape_skip/landscape_skip_page.dart b/gui/packages/ubuntupro/lib/pages/landscape_skip/landscape_skip_page.dart new file mode 100644 index 000000000..0ab232067 --- /dev/null +++ b/gui/packages/ubuntupro/lib/pages/landscape_skip/landscape_skip_page.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:wizard_router/wizard_router.dart'; + +import '../../constants.dart'; +import '../landscape/landscape_model.dart'; +import '../widgets/navigation_row.dart'; +import '../widgets/page_widgets.dart'; +import '../widgets/radio_tile.dart'; + +enum SkipEnum { + skip, + register, +} + +class LandscapeSkipPage extends StatefulWidget { + const LandscapeSkipPage({super.key}); + + @override + State createState() => _LandscapeSkipPageState(); +} + +class _LandscapeSkipPageState extends State { + SkipEnum groupValue = SkipEnum.skip; + + @override + Widget build(BuildContext context) { + final lang = AppLocalizations.of(context); + final wizard = Wizard.of(context); + + return ColumnPage( + svgAsset: 'assets/Landscape-tag.svg', + title: kLandscapeTitle, + left: [ + MarkdownBody( + data: lang.landscapeHeading( + '[${lang.learnMore}](${LandscapeModel.landscapeURI})', + ), + onTapLink: (_, href, __) => launchUrl(LandscapeModel.landscapeURI), + ), + ], + right: [ + RadioTile( + value: SkipEnum.skip, + title: lang.landscapeSkip, + subtitle: lang.landscapeSkipDescription, + groupValue: groupValue, + onChanged: (v) => setState(() { + groupValue = v!; + }), + ), + RadioTile( + value: SkipEnum.register, + title: lang.landscapeSkipRegister, + groupValue: groupValue, + onChanged: (v) => setState(() { + groupValue = v!; + }), + ), + ], + navigationRow: NavigationRow( + onBack: wizard.back, + onNext: () => wizard.next(arguments: groupValue), + ), + ); + } +} diff --git a/gui/packages/ubuntupro/lib/pages/widgets/navigation_row.dart b/gui/packages/ubuntupro/lib/pages/widgets/navigation_row.dart new file mode 100644 index 000000000..894f49784 --- /dev/null +++ b/gui/packages/ubuntupro/lib/pages/widgets/navigation_row.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class NavigationRow extends StatelessWidget { + const NavigationRow({ + required this.onBack, + required this.onNext, + this.backText, + this.nextText, + super.key, + }); + + final void Function()? onBack; + final String? backText; + final void Function()? onNext; + final String? nextText; + + @override + Widget build(BuildContext context) { + final lang = AppLocalizations.of(context); + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + OutlinedButton( + onPressed: onBack, + child: Text(backText ?? lang.buttonBack), + ), + FilledButton( + onPressed: onNext, + child: Text(nextText ?? lang.buttonNext), + ), + ], + ); + } +} diff --git a/gui/packages/ubuntupro/lib/pages/widgets/page_widgets.dart b/gui/packages/ubuntupro/lib/pages/widgets/page_widgets.dart index a27003310..432313c5e 100644 --- a/gui/packages/ubuntupro/lib/pages/widgets/page_widgets.dart +++ b/gui/packages/ubuntupro/lib/pages/widgets/page_widgets.dart @@ -27,6 +27,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:yaru/yaru.dart'; +import 'navigation_row.dart'; import 'status_bar.dart'; /// The simplest material page that covers most of the use cases in this app, @@ -168,3 +169,91 @@ class _PageContent extends StatelessWidget { ); } } + +/// Two-column, vertically centered page. The left column always contains the +/// svg image and title, with the left children below it. Both columns are equal +/// in width. Optionally, a [NavigationRow] may be provided that will span the +/// width below both columns. +class ColumnPage extends StatelessWidget { + const ColumnPage({ + required this.left, + required this.right, + this.svgAsset = 'assets/Ubuntu-tag.svg', + this.title = 'Ubuntu Pro', + this.navigationRow, + super.key, + }); + + final List left; + final List right; + final String svgAsset; + final String title; + final NavigationRow? navigationRow; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Pro4WSLPage( + body: Padding( + padding: const EdgeInsets.fromLTRB(32.0, 32.0, 32.0, 32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Left column + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: SvgPicture.asset( + svgAsset, + height: 70, + ), + ), + const WidgetSpan( + child: SizedBox( + width: 8, + ), + ), + TextSpan( + text: title, + style: theme.textTheme.displaySmall + ?.copyWith(fontWeight: FontWeight.w100), + ), + ], + ), + ), + const SizedBox(height: 24), + ...left, + ], + ), + ), + // Spacer + const SizedBox(width: 32), + // Right column + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: right, + ), + ), + ], + ), + ), + if (navigationRow != null) navigationRow!, + ], + ), + ), + ); + } +} diff --git a/gui/packages/ubuntupro/lib/pages/widgets/radio_tile.dart b/gui/packages/ubuntupro/lib/pages/widgets/radio_tile.dart new file mode 100644 index 000000000..6220ca6a3 --- /dev/null +++ b/gui/packages/ubuntupro/lib/pages/widgets/radio_tile.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:yaru/yaru.dart'; + +class RadioTile extends StatelessWidget { + const RadioTile({ + required this.value, + required this.title, + required this.groupValue, + required this.onChanged, + this.subtitle, + super.key, + }); + + final String title; + final String? subtitle; + final T value, groupValue; + final Function(T?)? onChanged; + + @override + Widget build(BuildContext context) { + // Adds a nice visual clue that the tile is selected. + return YaruSelectableContainer( + selected: groupValue == value, + selectionColor: Theme.of(context).colorScheme.tertiaryContainer, + child: YaruRadioListTile( + contentPadding: EdgeInsets.zero, + visualDensity: VisualDensity.comfortable, + dense: true, + title: Text(title), + subtitle: subtitle != null ? Text(subtitle!) : null, + value: value, + groupValue: groupValue, + onChanged: onChanged, + ), + ); + } +} diff --git a/gui/packages/ubuntupro/lib/routes.dart b/gui/packages/ubuntupro/lib/routes.dart index 28a9c61cd..248b69226 100644 --- a/gui/packages/ubuntupro/lib/routes.dart +++ b/gui/packages/ubuntupro/lib/routes.dart @@ -1,6 +1,7 @@ abstract class Routes { static const startup = '/startup'; static const subscribeNow = '/subscribe-now'; + static const skipLandscape = '/skip-landscape'; static const configureLandscape = '/configure-landscape'; static const subscriptionStatus = '/subscription-status'; static const configureLandscapeLate = '/configure-landscape-late'; diff --git a/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.dart b/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.dart index 65e34df52..5a25d63e5 100644 --- a/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.dart +++ b/gui/packages/ubuntupro/test/pages/landscape/landscape_page_test.dart @@ -12,11 +12,13 @@ import 'package:ubuntupro/core/agent_api_client.dart'; import 'package:ubuntupro/pages/landscape/landscape_model.dart'; import 'package:ubuntupro/pages/landscape/landscape_page.dart'; import 'package:ubuntupro/pages/widgets/page_widgets.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:wizard_router/wizard_router.dart'; import 'package:yaru/yaru.dart'; import 'package:yaru_test/yaru_test.dart'; import '../../utils/build_multiprovider_app.dart'; +import '../../utils/url_launcher_mock.dart'; import 'landscape_page_test.mocks.dart'; @GenerateMocks([AgentApiClient]) @@ -29,6 +31,22 @@ void main() { binding.platformDispatcher.textScaleFactorTestValue = 0.6; FilePicker.platform = FakeFilePicker([caCert]); + final launcher = FakeUrlLauncher(); + UrlLauncherPlatform.instance = launcher; + + testWidgets('launch web page', (tester) async { + final model = LandscapeModel(MockAgentApiClient()); + final app = buildApp(model); + await tester.pumpWidget(app); + final context = tester.element(find.byType(LandscapePage)); + final lang = AppLocalizations.of(context); + + expect(launcher.launched, isFalse); + await tester.tapOnText(find.textRange.ofSubstring(lang.learnMore)); + await tester.pump(); + expect(launcher.launched, isTrue); + }); + group('input sections', () { testWidgets('default state', (tester) async { final model = LandscapeModel(MockAgentApiClient()); @@ -403,7 +421,6 @@ Widget buildApp( return buildSingleRouteMultiProviderApp( child: LandscapePage( onApplyConfig: () {}, - onSkip: () {}, onBack: () {}, ), providers: [ChangeNotifierProvider.value(value: model)], diff --git a/gui/packages/ubuntupro/test/pages/landscape_skip/landscape_skip_page_test.dart b/gui/packages/ubuntupro/test/pages/landscape_skip/landscape_skip_page_test.dart new file mode 100644 index 000000000..1372d8551 --- /dev/null +++ b/gui/packages/ubuntupro/test/pages/landscape_skip/landscape_skip_page_test.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ubuntupro/pages/landscape_skip/landscape_skip_page.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import 'package:wizard_router/wizard_router.dart'; +import 'package:yaru/yaru.dart'; +import 'package:yaru_test/yaru_test.dart'; + +import '../../utils/build_multiprovider_app.dart'; +import '../../utils/url_launcher_mock.dart'; + +void main() { + final launcher = FakeUrlLauncher(); + UrlLauncherPlatform.instance = launcher; + + testWidgets('default state', (tester) async { + final app = buildApp(); + await tester.pumpWidget(app); + final context = tester.element(find.byType(LandscapeSkipPage)); + final lang = AppLocalizations.of(context); + + final backButton = find.button(lang.buttonBack); + expect(backButton, findsOne); + // for the purposes of these tests, we don't really care what kind of button + // it is, just that it's enabled + expect(tester.widget(backButton).enabled, isTrue); + + final nextButton = find.button(lang.buttonNext); + expect(nextButton, findsOne); + expect(tester.widget(nextButton).enabled, isTrue); + + final skipRadioTile = find.ancestor( + of: find.text(lang.landscapeSkip), + matching: find.byType(YaruSelectableContainer), + ); + expect( + tester.widget(skipRadioTile).selected, + isTrue, + ); + + final registerRadioTile = find.ancestor( + of: find.text(lang.landscapeSkipRegister), + matching: find.byType(YaruSelectableContainer), + ); + expect( + tester.widget(registerRadioTile).selected, + isFalse, + ); + }); + + testWidgets('tiles selectable', (tester) async { + final app = buildApp(); + await tester.pumpWidget(app); + final context = tester.element(find.byType(LandscapeSkipPage)); + final lang = AppLocalizations.of(context); + + final skipRadioTile = find.ancestor( + of: find.text(lang.landscapeSkip), + matching: find.byType(YaruSelectableContainer), + ); + final registerRadioTile = find.ancestor( + of: find.text(lang.landscapeSkipRegister), + matching: find.byType(YaruSelectableContainer), + ); + + await tester.tap(registerRadioTile); + await tester.pump(); + expect( + tester.widget(registerRadioTile).selected, + isTrue, + ); + expect( + tester.widget(skipRadioTile).selected, + isFalse, + ); + + await tester.tap(skipRadioTile); + await tester.pump(); + expect( + tester.widget(registerRadioTile).selected, + isFalse, + ); + expect( + tester.widget(skipRadioTile).selected, + isTrue, + ); + }); + + testWidgets('launch web page', (tester) async { + final app = buildApp(); + await tester.pumpWidget(app); + final context = tester.element(find.byType(LandscapeSkipPage)); + final lang = AppLocalizations.of(context); + + expect(launcher.launched, isFalse); + await tester.tapOnText(find.textRange.ofSubstring(lang.learnMore)); + await tester.pump(); + expect(launcher.launched, isTrue); + }); +} + +Widget buildApp() { + return buildMultiProviderWizardApp( + routes: { + '/': WizardRoute(builder: (_) => const LandscapeSkipPage()), + }, + ); +} diff --git a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_page_test.dart b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_page_test.dart index 0fd25844a..92f5595f5 100644 --- a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_page_test.dart +++ b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_page_test.dart @@ -11,11 +11,11 @@ import 'package:ubuntu_service/ubuntu_service.dart'; import 'package:ubuntupro/core/agent_api_client.dart'; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_model.dart'; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_page.dart'; -import 'package:url_launcher_platform_interface/link.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:wizard_router/wizard_router.dart'; import '../../utils/build_multiprovider_app.dart'; +import '../../utils/url_launcher_mock.dart'; import 'subscribe_now_page_test.mocks.dart'; @GenerateMocks([SubscribeNowModel]) @@ -36,9 +36,11 @@ void main() { final app = buildApp(model, onSubscribeNoop); await tester.pumpWidget(app); + final context = tester.element(find.byType(SubscribeNowPage)); + final lang = AppLocalizations.of(context); expect(launcher.launched, isFalse); - await tester.tapOnText(find.textRange.ofSubstring('Learn more')); + await tester.tapOnText(find.textRange.ofSubstring(lang.learnMore)); await tester.pump(); expect(launcher.launched, isTrue); }); @@ -177,49 +179,3 @@ Widget buildApp( void onSubscribeNoop(SubscriptionInfo _) {} class FakeAgentApiClient extends Fake implements AgentApiClient {} - -class FakeUrlLauncher extends UrlLauncherPlatform { - bool launched = false; - - @override - Future canLaunch(String url) async { - return true; - } - - @override - Future closeWebView() async {} - - @override - Future launchUrl(String url, LaunchOptions options) async { - launched = true; - return true; - } - - @override - Future supportsCloseForMode(PreferredLaunchMode mode) async { - return true; - } - - @override - Future supportsMode(PreferredLaunchMode mode) async { - return true; - } - - @override - Future launch( - String url, { - required bool useSafariVC, - required bool useWebView, - required bool enableJavaScript, - required bool enableDomStorage, - required bool universalLinksOnly, - required Map headers, - String? webOnlyWindowName, - }) async { - launched = true; - return true; - } - - @override - LinkDelegate? get linkDelegate => null; -} diff --git a/gui/packages/ubuntupro/test/utils/url_launcher_mock.dart b/gui/packages/ubuntupro/test/utils/url_launcher_mock.dart new file mode 100644 index 000000000..33c53358e --- /dev/null +++ b/gui/packages/ubuntupro/test/utils/url_launcher_mock.dart @@ -0,0 +1,48 @@ +import 'package:url_launcher_platform_interface/link.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +class FakeUrlLauncher extends UrlLauncherPlatform { + bool launched = false; + + @override + Future canLaunch(String url) async { + return true; + } + + @override + Future closeWebView() async {} + + @override + Future launchUrl(String url, LaunchOptions options) async { + launched = true; + return true; + } + + @override + Future supportsCloseForMode(PreferredLaunchMode mode) async { + return true; + } + + @override + Future supportsMode(PreferredLaunchMode mode) async { + return true; + } + + @override + Future launch( + String url, { + required bool useSafariVC, + required bool useWebView, + required bool enableJavaScript, + required bool enableDomStorage, + required bool universalLinksOnly, + required Map headers, + String? webOnlyWindowName, + }) async { + launched = true; + return true; + } + + @override + LinkDelegate? get linkDelegate => null; +}