From 560a4e08ee87d8011adcbcecc8afd4a21aadc656 Mon Sep 17 00:00:00 2001 From: Vatsal Date: Tue, 2 Apr 2024 17:09:02 +0530 Subject: [PATCH] login: Link to doc for what "server URL" is and how to find it Fixes: #109 --- assets/l10n/app_en.arb | 4 ++++ lib/widgets/login.dart | 17 +++++++++++++-- test/widgets/login_test.dart | 42 +++++++++++++++++++++++++++++++++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 31980b931a..7a62a4d92f 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -308,6 +308,10 @@ "@loginServerUrlInputLabel": { "description": "Input label in login page for Zulip server URL entry." }, + "serverUrlDocLinkLabel": "What's this?", + "@serverUrlDocLinkLabel": { + "description": "Link to doc to help users understand what a server URL is and how to find theirs." + }, "errorUnableToOpenLinkTitle": "Unable to open link", "@errorUnableToOpenLinkTitle": { "description": "Error title when a link fails to open." diff --git a/lib/widgets/login.dart b/lib/widgets/login.dart index 3f018ff9d9..a309a31af9 100644 --- a/lib/widgets/login.dart +++ b/lib/widgets/login.dart @@ -15,6 +15,7 @@ import '../model/store.dart'; import 'app.dart'; import 'dialog.dart'; import 'input.dart'; +import 'launch_url.dart'; import 'page.dart'; import 'store.dart'; import 'text.dart'; @@ -108,6 +109,9 @@ class ServerUrlTextEditingController extends TextEditingController { class AddAccountPage extends StatefulWidget { const AddAccountPage({super.key}); + static final Uri serverUrlHelpUrl = + Uri.parse('https://zulip.com/help/logging-in#find-the-zulip-log-in-url'); + static Route buildRoute() { return _LoginSequenceRoute(page: const AddAccountPage()); } @@ -213,7 +217,6 @@ class _AddAccountPageState extends State { child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - // TODO(#109) Link to doc about what a "server URL" is and how to find it // TODO(#111) Perhaps give tappable realm URL suggestions based on text typed so far TextField( controller: _controller, @@ -229,7 +232,17 @@ class _AddAccountPageState extends State { decoration: InputDecoration( labelText: zulipLocalizations.loginServerUrlInputLabel, errorText: errorText, - helperText: kLayoutPinningHelperText, + helper: GestureDetector( + onTap: () { + launchUrlWithoutRealm(context, AddAccountPage.serverUrlHelpUrl); + }, + child: Text( + // Restate Material default + // (`_InputDecoratorDefaultsM3.helperText` upstream)… + style: Theme.of(context).textTheme.bodySmall! + // …but use blue because this is a link. + .apply(color: const HSLColor.fromAHSL(1, 200, 1, 0.4).toColor()), + zulipLocalizations.serverUrlDocLinkLabel)), hintText: 'your-org.zulipchat.com')), const SizedBox(height: 8), ElevatedButton( diff --git a/test/widgets/login_test.dart b/test/widgets/login_test.dart index 9fc655d28b..bd9ebfb2df 100644 --- a/test/widgets/login_test.dart +++ b/test/widgets/login_test.dart @@ -1,8 +1,11 @@ import 'package:checks/checks.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/zulip_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; +import 'package:url_launcher/url_launcher.dart'; + import 'package:zulip/api/model/web_auth.dart'; import 'package:zulip/api/route/account.dart'; import 'package:zulip/api/route/realm.dart'; @@ -61,7 +64,44 @@ void main() { expectErrorFromText('email@example.com', ServerUrlValidationError.noUseEmail); }); - // TODO test AddAccountPage + group('AddAccountPage server URL helper text', () { + Future prepareAddAccountPage(WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + localizationsDelegates: ZulipLocalizations.localizationsDelegates, + supportedLocales: ZulipLocalizations.supportedLocales, + home: AddAccountPage(), + )); + } + + final zulipLocalizations = GlobalLocalizations.zulipLocalizations; + + Future findHelperText(WidgetTester tester) async { + return find.text(zulipLocalizations.serverUrlDocLinkLabel); + } + + testWidgets('launches URL when helper text is tapped', (tester) async { + await prepareAddAccountPage(tester); + await tester.tap(await findHelperText(tester)); + + check(testBinding.takeLaunchUrlCalls()).single.equals(( + url: AddAccountPage.serverUrlHelpUrl, + mode: LaunchMode.platformDefault, + )); + }); + + testWidgets('shows error dialog when URL fails to open', (tester) async { + await prepareAddAccountPage(tester); + testBinding.launchUrlResult = false; + await tester.tap(await findHelperText(tester)); + await tester.pump(); + + await tester.tap(find.byWidget(checkErrorDialog(tester, + expectedTitle: zulipLocalizations.errorUnableToOpenLinkTitle, + expectedMessage: zulipLocalizations.errorUnableToOpenLinkMessage( + AddAccountPage.serverUrlHelpUrl.toString(), + )))); + }); + }); group('LoginPage', () { late FakeApiConnection connection;