From d340fa5f3877d1e6fd61596ba41e2697ff16387a Mon Sep 17 00:00:00 2001 From: Nic Ford Date: Fri, 31 Oct 2025 13:52:01 +0000 Subject: [PATCH 1/4] feat: add integration tests to clerk_flutter [#297] --- packages/clerk_flutter/.gitignore | 2 + .../generated/clerk_sdk_localizations.dart | 39 +++++----- .../generated/clerk_sdk_localizations_en.dart | 42 ++++------ packages/clerk_flutter/lib/logging.dart | 3 +- packages/clerk_flutter/pubspec.yaml | 5 +- .../with_email_and_password/001.json | 1 + .../with_email_and_password/002.json | 1 + .../test/flows/sign_in_flow_test.dart | 37 +++++++++ packages/clerk_flutter/test/test_helpers.dart | 78 +++++++++++++++++++ packages/clerk_flutter/test/test_support | 1 + .../widgets/clerk_loading_overlay_test.dart | 6 +- 11 files changed, 160 insertions(+), 55 deletions(-) create mode 100644 packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json create mode 100644 packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json create mode 100644 packages/clerk_flutter/test/flows/sign_in_flow_test.dart create mode 100644 packages/clerk_flutter/test/test_helpers.dart create mode 120000 packages/clerk_flutter/test/test_support diff --git a/packages/clerk_flutter/.gitignore b/packages/clerk_flutter/.gitignore index ace65b6f..4119594a 100644 --- a/packages/clerk_flutter/.gitignore +++ b/packages/clerk_flutter/.gitignore @@ -18,3 +18,5 @@ build/ # Flutter auto-generated files .flutter-plugins .flutter-plugins-dependencies + +.env.* \ No newline at end of file diff --git a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart index 98ac5a9c..9da6059c 100644 --- a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart +++ b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations.dart @@ -62,18 +62,15 @@ import 'clerk_sdk_localizations_en.dart'; /// be consistent with the languages listed in the ClerkSdkLocalizations.supportedLocales /// property. abstract class ClerkSdkLocalizations { - ClerkSdkLocalizations(String locale) - : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + ClerkSdkLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; static ClerkSdkLocalizations? of(BuildContext context) { - return Localizations.of( - context, ClerkSdkLocalizations); + return Localizations.of(context, ClerkSdkLocalizations); } - static const LocalizationsDelegate delegate = - _ClerkSdkLocalizationsDelegate(); + static const LocalizationsDelegate delegate = _ClerkSdkLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -85,8 +82,7 @@ abstract class ClerkSdkLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = - >[ + static const List> localizationsDelegates = >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -94,7 +90,9 @@ abstract class ClerkSdkLocalizations { ]; /// A list of this localizations delegate's supported locales. - static const List supportedLocales = [Locale('en')]; + static const List supportedLocales = [ + Locale('en') + ]; /// No description provided for @aLengthOfBetweenMINAndMAX. /// @@ -925,34 +923,33 @@ abstract class ClerkSdkLocalizations { String get welcomePleaseFillInTheDetailsToGetStarted; } -class _ClerkSdkLocalizationsDelegate - extends LocalizationsDelegate { +class _ClerkSdkLocalizationsDelegate extends LocalizationsDelegate { const _ClerkSdkLocalizationsDelegate(); @override Future load(Locale locale) { - return SynchronousFuture( - lookupClerkSdkLocalizations(locale)); + return SynchronousFuture(lookupClerkSdkLocalizations(locale)); } @override - bool isSupported(Locale locale) => - ['en'].contains(locale.languageCode); + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); @override bool shouldReload(_ClerkSdkLocalizationsDelegate old) => false; } ClerkSdkLocalizations lookupClerkSdkLocalizations(Locale locale) { + + // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'en': - return ClerkSdkLocalizationsEn(); + case 'en': return ClerkSdkLocalizationsEn(); } throw FlutterError( - 'ClerkSdkLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.'); + 'ClerkSdkLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); } diff --git a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart index bc1a1b35..2894b8c8 100644 --- a/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart +++ b/packages/clerk_flutter/lib/generated/clerk_sdk_localizations_en.dart @@ -33,12 +33,10 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get abandoned => 'abandoned'; @override - String get acceptTerms => - 'I agree to the Terms of Service and Privacy Policy'; + String get acceptTerms => 'I agree to the Terms of Service and Privacy Policy'; @override - String get actionNotTimely => - 'Awaited user action not completed in required timeframe'; + String get actionNotTimely => 'Awaited user action not completed in required timeframe'; @override String get active => 'active'; @@ -144,8 +142,7 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get enterTheCodeSentToYou => 'Enter the code sent to you'; @override - String get enterYourOrganizationDetailsToContinue => - 'Enter your organization details to continue'; + String get enterYourOrganizationDetailsToContinue => 'Enter your organization details to continue'; @override String get expired => 'expired'; @@ -282,15 +279,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get password => 'Password'; @override - String get passwordAndPasswordConfirmationMustMatch => - 'Password and password confirmation must match'; + String get passwordAndPasswordConfirmationMustMatch => 'Password and password confirmation must match'; @override String get passwordConfirmation => 'confirm password'; @override - String get passwordMatchError => - 'Password and password confirmation must match'; + String get passwordMatchError => 'Password and password confirmation must match'; @override String get passwordMustBeSupplied => 'A password must be supplied'; @@ -311,12 +306,10 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get phoneNumberConcise => 'phone'; @override - String get pleaseAddRequiredInformation => - 'Something seems to be missing. Please add the required information'; + String get pleaseAddRequiredInformation => 'Something seems to be missing. Please add the required information'; @override - String get pleaseChooseAnAccountToConnect => - 'Please choose an account to connect'; + String get pleaseChooseAnAccountToConnect => 'Please choose an account to connect'; @override String get pleaseEnterYourIdentifier => 'Please enter your identifier'; @@ -346,15 +339,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get resend => 'Resend'; @override - String get resetFailed => - 'That password reset attempt failed. A new code has been sent.'; + String get resetFailed => 'That password reset attempt failed. A new code has been sent.'; @override String get resetPassword => 'Reset password and sign in'; @override - String get selectAccount => - 'Select the account with which you wish to continue'; + String get selectAccount => 'Select the account with which you wish to continue'; @override String get sendMeTheCode => 'Send me the reset code'; @@ -366,16 +357,13 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get signIn => 'Sign in'; @override - String get signInByClickingALinkSentToYouByEmail => - 'Sign in by clicking a link sent to you by email'; + String get signInByClickingALinkSentToYouByEmail => 'Sign in by clicking a link sent to you by email'; @override - String get signInByEnteringACodeSentToYouByEmail => - 'Sign in by entering a code sent to you by email'; + String get signInByEnteringACodeSentToYouByEmail => 'Sign in by entering a code sent to you by email'; @override - String get signInByEnteringACodeSentToYouByTextMessage => - 'Sign in by entering a code sent to you by text message'; + String get signInByEnteringACodeSentToYouByTextMessage => 'Sign in by entering a code sent to you by text message'; @override String signInError(String arg) { @@ -471,10 +459,8 @@ class ClerkSdkLocalizationsEn extends ClerkSdkLocalizations { String get web3Wallet => 'web3 wallet'; @override - String get welcomeBackPleaseSignInToContinue => - 'Welcome back! Please sign in to continue'; + String get welcomeBackPleaseSignInToContinue => 'Welcome back! Please sign in to continue'; @override - String get welcomePleaseFillInTheDetailsToGetStarted => - 'Welcome! Please fill in the details to get started'; + String get welcomePleaseFillInTheDetailsToGetStarted => 'Welcome! Please fill in the details to get started'; } diff --git a/packages/clerk_flutter/lib/logging.dart b/packages/clerk_flutter/lib/logging.dart index 1e6682d5..db0d6837 100644 --- a/packages/clerk_flutter/lib/logging.dart +++ b/packages/clerk_flutter/lib/logging.dart @@ -1,4 +1,5 @@ /// Enables the example code to use the clerk_auth logging facility library; -export 'package:clerk_auth/clerk_auth.dart' show setUpLogging, Logging, Printer; +export 'package:clerk_auth/clerk_auth.dart' + show setUpLogging, Level, Logging, Printer; diff --git a/packages/clerk_flutter/pubspec.yaml b/packages/clerk_flutter/pubspec.yaml index 76c1bfe8..8a0e8286 100644 --- a/packages/clerk_flutter/pubspec.yaml +++ b/packages/clerk_flutter/pubspec.yaml @@ -7,7 +7,6 @@ issue_tracker: https://github.com/clerk/clerk-sdk-flutter/labels/clerk_flutter topics: - authentication - environment: sdk: '>=3.6.2 <4.0.0' flutter: '>=3.27.4' @@ -33,6 +32,10 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter_test: sdk: flutter + dart_dotenv: ^1.0.1 + test: ^1.25.5 + integration_test: + sdk: flutter flutter: assets: diff --git a/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json b/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json b/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/test/flows/sign_in_flow_test.dart new file mode 100644 index 00000000..f6d25160 --- /dev/null +++ b/packages/clerk_flutter/test/flows/sign_in_flow_test.dart @@ -0,0 +1,37 @@ +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:clerk_flutter/logging.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../test_helpers.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late final TestEnv env; + + setUpAll(() async { + env = TestEnv('.env.test'); + await setUpLogging(printer: TestLogPrinter(), level: Level.INFO); + }); + + Future initialiseForTest(String testName) async { + final httpService = TestHttpService('flow/sign_in_flow_test', env) + ..recordPath = testName; + + return TestAuthConfig( + publishableKey: env.publishableKey, + httpService: httpService, + ).toClerkAuthConfig(); + } + + group('sign in: ', () { + testWidgets('with email and password', (tester) async { + // Load app widget. + final config = await initialiseForTest('with_email_and_password'); + await tester.pumpWidget(ClerkFlutterTestApp(config: config)); + await tester.pumpAndSettle(); + + expect(find.text('Email address or username'), findsOneWidget); + }); + }); +} diff --git a/packages/clerk_flutter/test/test_helpers.dart b/packages/clerk_flutter/test/test_helpers.dart new file mode 100644 index 00000000..2c4d1d66 --- /dev/null +++ b/packages/clerk_flutter/test/test_helpers.dart @@ -0,0 +1,78 @@ +import 'dart:io'; + +import 'package:clerk_auth/clerk_auth.dart' as clerk show AuthConfig; +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:clerk_flutter/src/utils/clerk_file_cache.dart'; +import 'package:flutter/material.dart'; +import 'package:test/test.dart' as test show expect; + +export 'package:flutter_test/flutter_test.dart'; + +export 'test_support/test_support.dart'; + +void expectThat(bool result, {String? reason}) => + test.expect(result, true, reason: reason); + +extension AuthConfigExt on clerk.AuthConfig { + ClerkAuthConfig toClerkAuthConfig() { + return ClerkAuthConfig( + publishableKey: publishableKey, + sessionTokenPolling: sessionTokenPolling, + isTestMode: isTestMode, + telemetryEndpoint: telemetryEndpoint, + telemetryPeriod: telemetryPeriod, + clientRefreshPeriod: clientRefreshPeriod, + httpService: httpService, + httpConnectionTimeout: httpConnectionTimeout, + persistor: persistor, + fileCache: const _NoneFileCache(), + ); + } +} + +class _NoneFileCache implements ClerkFileCache { + const _NoneFileCache(); + + @override + Future initialize() async {} + + @override + void terminate() {} + + @override + Stream stream( + Uri uri, { + Duration ttl = ClerkFileCache.defaultTTL, + Map? headers, + }) async* {} +} + +class ClerkFlutterTestApp extends StatelessWidget { + const ClerkFlutterTestApp({super.key, required this.config}); + + final ClerkAuthConfig config; + + @override + Widget build(BuildContext context) { + return ClerkAuth( + config: config, + child: MaterialApp( + theme: ThemeData.light(), + debugShowCheckedModeBanner: false, + home: Scaffold( + body: SafeArea( + child: ClerkErrorListener( + child: Padding( + padding: const EdgeInsets.all(16), + child: ClerkAuthBuilder( + signedInBuilder: (_, __) => const ClerkUserButton(), + signedOutBuilder: (_, __) => const ClerkAuthentication(), + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/clerk_flutter/test/test_support b/packages/clerk_flutter/test/test_support new file mode 120000 index 00000000..1a91a1f0 --- /dev/null +++ b/packages/clerk_flutter/test/test_support @@ -0,0 +1 @@ +../../../test_support/test \ No newline at end of file diff --git a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart index e2d830c3..10bcdfc1 100644 --- a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart +++ b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart @@ -5,7 +5,8 @@ import 'package:clerk_flutter/src/utils/clerk_auth_config.dart'; import 'package:clerk_flutter/src/widgets/ui/clerk_loading_overlay.dart'; import 'package:clerk_flutter/src/widgets/ui/clerk_overlay_host.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; + +import '../test_helpers.dart'; const startupDuration = ClerkLoadingOverlay.startupDuration; const minOnScreenTime = ClerkLoadingOverlay.minimumOnScreenDuration; @@ -19,9 +20,6 @@ extension on DateTime { int operator -(DateTime other) => difference(other).inMilliseconds; } -void expectThat(bool result, {String? reason}) => - expect(result, true, reason: reason); - void main() { group('ClerkLoadingOverlay:', skip: true, () { final config = ClerkAuthConfig( From e70eed0a423d04a297c4ff3cc90e519bbd7056e2 Mon Sep 17 00:00:00 2001 From: Nic Ford Date: Mon, 3 Nov 2025 12:28:13 +0000 Subject: [PATCH 2/4] feat: add email-password integration test [#297] --- packages/clerk_flutter/example/lib/main.dart | 70 +++++++++--------- packages/clerk_flutter/example/pubspec.yaml | 5 ++ .../with_email_and_password/001.json | 2 +- .../with_email_and_password/002.json | 0 .../with_email_and_password/003.json | 1 + .../with_email_and_password/004.json | 1 + .../with_email_and_password/005.json | 1 + .../example/test/flows/sign_in_flow_test.dart | 72 +++++++++++++++++++ .../{ => example}/test/test_helpers.dart | 57 +++++++-------- .../clerk_flutter/example/test/test_support | 1 + packages/clerk_flutter/lib/clerk_flutter.dart | 1 + .../authentication/clerk_sign_in_panel.dart | 1 + .../lib/src/widgets/clerk_keys.dart | 10 +++ .../widgets/ui/clerk_identifier_input.dart | 4 +- .../test/flows/sign_in_flow_test.dart | 37 ---------- packages/clerk_flutter/test/test_support | 1 - .../widgets/clerk_loading_overlay_test.dart | 2 +- 17 files changed, 158 insertions(+), 108 deletions(-) rename packages/clerk_flutter/{ => example}/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json (72%) rename packages/clerk_flutter/{ => example}/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json (100%) create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json create mode 100644 packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart rename packages/clerk_flutter/{ => example}/test/test_helpers.dart (61%) create mode 120000 packages/clerk_flutter/example/test/test_support create mode 100644 packages/clerk_flutter/lib/src/widgets/clerk_keys.dart delete mode 100644 packages/clerk_flutter/test/flows/sign_in_flow_test.dart delete mode 120000 packages/clerk_flutter/test/test_support diff --git a/packages/clerk_flutter/example/lib/main.dart b/packages/clerk_flutter/example/lib/main.dart index b3f93e08..f7335e2b 100644 --- a/packages/clerk_flutter/example/lib/main.dart +++ b/packages/clerk_flutter/example/lib/main.dart @@ -27,55 +27,55 @@ Future main() async { } runApp( - const ExampleApp( - publishableKey: publishableKey, + ExampleApp( + config: ClerkAuthConfig( + publishableKey: publishableKey, + redirectionGenerator: generateDeepLink, + deepLinkStream: AppLinks().allUriLinkStream.map(createClerkLink), + ), ), ); } -/// Example App -class ExampleApp extends StatelessWidget { - /// Constructs an instance of Example App - const ExampleApp({super.key, required this.publishableKey}); +/// This function maps a [Uri] into a [ClerkDeepLink], which is essentially +/// just a container for the [Uri]. The [ClerkDeepLink] can also +/// contain a [clerk.Strategy], to use in preference to a strategy +/// inferred from the [Uri] +ClerkDeepLink? createClerkLink(Uri uri) { + if (uri.pathSegments.first == 'auth') { + return ClerkDeepLink(uri: uri); + } - /// Publishable Key - final String publishableKey; + // If the host app deems the deep link to be not relevant to the Clerk SDK, + // we return [null] instead of a [ClerkDeepLink] to inhibit its processing. + return null; +} - /// This function maps a [Uri] into a [ClerkDeepLink], which is essentially - /// just a container for the [Uri]. The [ClerkDeepLink] can also - /// contain a [clerk.Strategy], to use in preference to a strategy - /// inferred from the [Uri] - ClerkDeepLink? createClerkLink(Uri uri) { - if (uri.pathSegments.first == 'auth') { - return ClerkDeepLink(uri: uri); - } +/// A function that returns an appropriate deep link [Uri] for the oauth +/// redirect for a given [clerk.Strategy], or [null] if redirection should +/// be handled in-app +Uri? generateDeepLink(BuildContext context, clerk.Strategy strategy) { + return Uri.parse('clerk://example.com/auth/$strategy'); - // If the host app deems the deep link to be not relevant to the Clerk SDK, - // we return [null] instead of a [ClerkDeepLink] to inhibit its processing. - return null; - } + // if you want to use the default in-app SSO, just remove the + // [redirectionGenerator] parameter from the [ClerkAuthConfig] object + // below, or... - /// A function that returns an appropriate deep link [Uri] for the oauth - /// redirect for a given [clerk.Strategy], or [null] if redirection should - /// be handled in-app - Uri? generateDeepLink(BuildContext context, clerk.Strategy strategy) { - return Uri.parse('clerk://example.com/auth/$strategy'); + // return null; +} - // if you want to use the default in-app SSO, just remove the - // [redirectionGenerator] parameter from the [ClerkAuthConfig] object - // below, or... +/// Example App +class ExampleApp extends StatelessWidget { + /// Constructs an instance of Example App + const ExampleApp({super.key, required this.config}); - // return null; - } + /// Publishable Key + final ClerkAuthConfig config; @override Widget build(BuildContext context) { return ClerkAuth( - config: ClerkAuthConfig( - publishableKey: publishableKey, - redirectionGenerator: generateDeepLink, - deepLinkStream: AppLinks().allUriLinkStream.map(createClerkLink), - ), + config: config, child: MaterialApp( theme: ThemeData.light(), debugShowCheckedModeBanner: false, diff --git a/packages/clerk_flutter/example/pubspec.yaml b/packages/clerk_flutter/example/pubspec.yaml index 969fbd5a..34eebd10 100644 --- a/packages/clerk_flutter/example/pubspec.yaml +++ b/packages/clerk_flutter/example/pubspec.yaml @@ -22,6 +22,11 @@ dev_dependencies: flutter_lints: ^3.0.1 flutter_test: sdk: flutter + dart_dotenv: ^1.0.1 + test: ^1.25.5 + http: ^1.1.0 + integration_test: + sdk: flutter flutter: uses-material-design: true diff --git a/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json similarity index 72% rename from packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json rename to packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json index 31f21971..f80e9ff1 100644 --- a/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json @@ -1 +1 @@ -{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -1%%\",\"updated_at\":\"%%DATETIME 1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json similarity index 100% rename from packages/clerk_flutter/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json rename to packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/002.json diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json new file mode 100644 index 00000000..d00b0874 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json new file mode 100644 index 00000000..50a94a8e --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%EMAIL%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%EMAIL%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json new file mode 100644 index 00000000..882fc252 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -1%%\",\"updated_at\":\"%%DATETIME 1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart new file mode 100644 index 00000000..5b0b1815 --- /dev/null +++ b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart @@ -0,0 +1,72 @@ +import 'package:clerk_flutter/clerk_flutter.dart'; +import 'package:clerk_flutter/logging.dart'; +import 'package:clerk_flutter_example/main.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../test_helpers.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + late final TestEnv env; + + setUpAll(() async { + env = TestEnv('.env.test'); + await setUpLogging(printer: TestLogPrinter(), level: Level.INFO); + }); + + Future initialiseForTest(String testName) async { + FlutterError.onError = setIgnoreOverflowErrors(FlutterError.onError); + + final httpService = TestHttpService('flow/sign_in_flow_test', env) + ..recordPath = testName; + + return TestAuthConfig( + publishableKey: env.publishableKey, + httpService: httpService, + ).toClerkAuthConfig(); + } + + group('sign in: ', () { + testWidgets('with email and password', (tester) async { + // Load app widget. + final config = await initialiseForTest('with_email_and_password'); + await tester.pumpWidget(ExampleApp(config: config)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Clerk UI Sign In')); + await tester.pumpAndSettle(); + + final emailInput = find.byKey(kEmailInputField); + await tester.tap(emailInput); + tester.testTextInput.enterText(env.email); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + final passwordInput = find.byKey(kPasswordInputField); + await tester.tap(passwordInput); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + final signOutButton = find.text('Sign out'); + expect(signOutButton, findsOneWidget); + await tester.tap(signOutButton); + await tester.pumpAndSettle(); + + expect(find.text('Are you sure?'), findsOneWidget); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect( + find.text('Welcome back! Please sign in to continue'), + findsOneWidget, + ); + }); + }); +} diff --git a/packages/clerk_flutter/test/test_helpers.dart b/packages/clerk_flutter/example/test/test_helpers.dart similarity index 61% rename from packages/clerk_flutter/test/test_helpers.dart rename to packages/clerk_flutter/example/test/test_helpers.dart index 2c4d1d66..b50c0fbe 100644 --- a/packages/clerk_flutter/test/test_helpers.dart +++ b/packages/clerk_flutter/example/test/test_helpers.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:clerk_auth/clerk_auth.dart' as clerk show AuthConfig; import 'package:clerk_flutter/clerk_flutter.dart'; import 'package:clerk_flutter/src/utils/clerk_file_cache.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:test/test.dart' as test show expect; export 'package:flutter_test/flutter_test.dart'; @@ -30,6 +30,31 @@ extension AuthConfigExt on clerk.AuthConfig { } } +FlutterExceptionHandler setIgnoreOverflowErrors( + FlutterExceptionHandler? override, +) { + return (FlutterErrorDetails details, {bool forceReport = false}) { + final hasReportableError = switch (details.exception) { + FlutterError exception => exception.diagnostics.any( + (e) { + final value = e.toString(); + return value.startsWith('A RenderFlex overflowed by') == false && + value.startsWith('Unable to load asset') == false; + }, + ), + _ => true, + }; + + if (hasReportableError) { + if (override case FlutterExceptionHandler override) { + override(details); + } else { + FlutterError.dumpErrorToConsole(details, forceReport: forceReport); + } + } + }; +} + class _NoneFileCache implements ClerkFileCache { const _NoneFileCache(); @@ -46,33 +71,3 @@ class _NoneFileCache implements ClerkFileCache { Map? headers, }) async* {} } - -class ClerkFlutterTestApp extends StatelessWidget { - const ClerkFlutterTestApp({super.key, required this.config}); - - final ClerkAuthConfig config; - - @override - Widget build(BuildContext context) { - return ClerkAuth( - config: config, - child: MaterialApp( - theme: ThemeData.light(), - debugShowCheckedModeBanner: false, - home: Scaffold( - body: SafeArea( - child: ClerkErrorListener( - child: Padding( - padding: const EdgeInsets.all(16), - child: ClerkAuthBuilder( - signedInBuilder: (_, __) => const ClerkUserButton(), - signedOutBuilder: (_, __) => const ClerkAuthentication(), - ), - ), - ), - ), - ), - ), - ); - } -} diff --git a/packages/clerk_flutter/example/test/test_support b/packages/clerk_flutter/example/test/test_support new file mode 120000 index 00000000..e03027e8 --- /dev/null +++ b/packages/clerk_flutter/example/test/test_support @@ -0,0 +1 @@ +../../../../test_support/test \ No newline at end of file diff --git a/packages/clerk_flutter/lib/clerk_flutter.dart b/packages/clerk_flutter/lib/clerk_flutter.dart index 8d0dd0a7..f8044706 100644 --- a/packages/clerk_flutter/lib/clerk_flutter.dart +++ b/packages/clerk_flutter/lib/clerk_flutter.dart @@ -7,6 +7,7 @@ export 'src/clerk_user_action.dart'; export 'src/utils/clerk_auth_config.dart'; export 'src/utils/clerk_sdk_flags.dart'; export 'src/widgets/authentication/clerk_authentication.dart'; +export 'src/widgets/clerk_keys.dart'; export 'src/widgets/control/clerk_auth.dart'; export 'src/widgets/control/clerk_auth_builder.dart'; export 'src/widgets/control/clerk_error_listener.dart'; diff --git a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart index db471173..f75ad24c 100644 --- a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart +++ b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart @@ -275,6 +275,7 @@ class _FactorList extends StatelessWidget { Padding( padding: topPadding8 + bottomPadding2, child: ClerkTextFormField( + key: kPasswordInputField, label: l10ns.password, obscureText: true, onChanged: onPasswordChanged, diff --git a/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart new file mode 100644 index 00000000..57a418b7 --- /dev/null +++ b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +/// Email identifier +const kEmailInputField = Key('emailInputField'); + +/// Phone identifier +const kPhoneInputField = Key('phoneInputField'); + +/// Password +const kPasswordInputField = Key('passwordInputField'); diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart index 448576e1..bdd30e8a 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart @@ -108,7 +108,7 @@ class _ClerkIdentifierInputState extends State { Closeable( closed: widget.identifierType.value.isPhoneNumber, child: ClerkTextFormField( - key: const Key('identifier'), + key: kEmailInputField, initial: widget.initialValue, label: l10ns.grammar.toSentence( l10ns.grammar.toLitany( @@ -134,7 +134,7 @@ class _ClerkIdentifierInputState extends State { Closeable( closed: widget.identifierType.value.isEmailAddress, child: ClerkPhoneNumberFormField( - key: const Key('phoneIdentifier'), + key: kPhoneInputField, label: l10ns.grammar.toSentence( l10ns.grammar.toLitany( phoneStrategies diff --git a/packages/clerk_flutter/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/test/flows/sign_in_flow_test.dart deleted file mode 100644 index f6d25160..00000000 --- a/packages/clerk_flutter/test/flows/sign_in_flow_test.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:clerk_flutter/clerk_flutter.dart'; -import 'package:clerk_flutter/logging.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../test_helpers.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - late final TestEnv env; - - setUpAll(() async { - env = TestEnv('.env.test'); - await setUpLogging(printer: TestLogPrinter(), level: Level.INFO); - }); - - Future initialiseForTest(String testName) async { - final httpService = TestHttpService('flow/sign_in_flow_test', env) - ..recordPath = testName; - - return TestAuthConfig( - publishableKey: env.publishableKey, - httpService: httpService, - ).toClerkAuthConfig(); - } - - group('sign in: ', () { - testWidgets('with email and password', (tester) async { - // Load app widget. - final config = await initialiseForTest('with_email_and_password'); - await tester.pumpWidget(ClerkFlutterTestApp(config: config)); - await tester.pumpAndSettle(); - - expect(find.text('Email address or username'), findsOneWidget); - }); - }); -} diff --git a/packages/clerk_flutter/test/test_support b/packages/clerk_flutter/test/test_support deleted file mode 120000 index 1a91a1f0..00000000 --- a/packages/clerk_flutter/test/test_support +++ /dev/null @@ -1 +0,0 @@ -../../../test_support/test \ No newline at end of file diff --git a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart index 10bcdfc1..f441b4b4 100644 --- a/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart +++ b/packages/clerk_flutter/test/widgets/clerk_loading_overlay_test.dart @@ -6,7 +6,7 @@ import 'package:clerk_flutter/src/widgets/ui/clerk_loading_overlay.dart'; import 'package:clerk_flutter/src/widgets/ui/clerk_overlay_host.dart'; import 'package:flutter/widgets.dart'; -import '../test_helpers.dart'; +import '../../example/test/test_helpers.dart'; const startupDuration = ClerkLoadingOverlay.startupDuration; const minOnScreenTime = ClerkLoadingOverlay.minimumOnScreenDuration; From ea6cf8ec7d6846bfcfd5b82571b8a5627c19aefd Mon Sep 17 00:00:00 2001 From: Nic Ford Date: Mon, 3 Nov 2025 17:03:14 +0000 Subject: [PATCH 3/4] feat: added tests for username and phone number sign in [#297] --- .../lib/src/clerk_auth/auth_config.dart | 4 + .../clerk_flutter/example/ios/Podfile.lock | 6 + .../with_email_and_password/001.json | 2 +- .../with_email_and_password/005.json | 2 +- .../with_phone_number_and_password/001.json | 1 + .../with_phone_number_and_password/002.json | 1 + .../with_phone_number_and_password/003.json | 1 + .../with_phone_number_and_password/004.json | 1 + .../with_phone_number_and_password/005.json | 1 + .../with_username_and_password/001.json | 1 + .../with_username_and_password/002.json | 1 + .../with_username_and_password/003.json | 1 + .../with_username_and_password/004.json | 1 + .../with_username_and_password/005.json | 1 + .../example/test/flows/sign_in_flow_test.dart | 104 ++++++++++++++++-- .../lib/src/widgets/clerk_keys.dart | 2 +- .../widgets/ui/clerk_identifier_input.dart | 2 +- .../ui/clerk_phone_number_form_field.dart | 11 +- test_support/test/src/test_auth_config.dart | 9 +- 19 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json create mode 100644 packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json diff --git a/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart b/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart index 7af0ae9b..c8f48654 100644 --- a/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart +++ b/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart @@ -90,6 +90,10 @@ class AuthConfig { /// The [HttpService] used to communicate with the backend. final HttpService httpService; + /// Phone number whitelist, for testing purposes. Must be overridden + /// by e.g. test config to change from empty list + final List phoneNumberWhiteList = const []; + /// Initialise Future initialize() async { await persistor.initialize(); diff --git a/packages/clerk_flutter/example/ios/Podfile.lock b/packages/clerk_flutter/example/ios/Podfile.lock index 5884e82c..985a1ee8 100644 --- a/packages/clerk_flutter/example/ios/Podfile.lock +++ b/packages/clerk_flutter/example/ios/Podfile.lock @@ -41,6 +41,8 @@ PODS: - GTMSessionFetcher/Core - image_picker_ios (0.0.1): - Flutter + - integration_test (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -55,6 +57,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -78,6 +81,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_sign_in_ios/darwin" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" url_launcher_ios: @@ -96,6 +101,7 @@ SPEC CHECKSUMS: GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238 GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 image_picker_ios: 85f2b3c9fb98c09d63725c4d12ebd585b56ec35d + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 url_launcher_ios: 9d5365b30ff416ba8e3d917bae5f7fb25f6a4a89 diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json index f80e9ff1..31f21971 100644 --- a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/001.json @@ -1 +1 @@ -{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -1%%\",\"updated_at\":\"%%DATETIME 1%%\"},\"client\":null}"} \ No newline at end of file +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json index 882fc252..6e1ebe1a 100644 --- a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_email_and_password/005.json @@ -1 +1 @@ -{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -1%%\",\"updated_at\":\"%%DATETIME 1%%\"},\"client\":null}"} \ No newline at end of file +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json new file mode 100644 index 00000000..ee371264 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json new file mode 100644 index 00000000..edf043d4 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%PHONE%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%PHONE%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json new file mode 100644 index 00000000..6e1ebe1a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_phone_number_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json new file mode 100644 index 00000000..31f21971 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/001.json @@ -0,0 +1 @@ +{"key":"POST /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json new file mode 100644 index 00000000..b910570a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/002.json @@ -0,0 +1 @@ +{"key":"GET /v1/environment","body":"{\"auth_config\":{\"object\":\"auth_config\",\"id\":\"AUTH_CONFIG_ID\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"email_address\":\"%%EMAIL%%\",\"phone_number\":\"on\",\"username\":\"on\",\"password\":\"required\",\"identification_requirements\":[[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"phone_number\"],[\"username\"]],\"identification_strategies\":[\"email_address\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"first_factors\":[\"email_code\",\"email_link\",\"enterprise_sso\",\"google_one_tap\",\"oauth_apple\",\"oauth_facebook\",\"oauth_github\",\"oauth_google\",\"oauth_linkedin_oidc\",\"oauth_token_apple\",\"passkey\",\"password\",\"phone_code\",\"reset_password_email_code\",\"reset_password_phone_code\",\"saml\",\"ticket\"],\"second_factors\":[\"phone_code\",\"totp\"],\"email_address_verification_strategies\":[\"email_code\",\"email_link\"],\"single_session_mode\":false,\"enhanced_email_deliverability\":false,\"test_mode\":true,\"cookieless_dev\":true,\"url_based_session_syncing\":true,\"claimed_at\":null,\"reverification\":false},\"display_config\":{\"object\":\"display_config\",\"id\":\"DISPLAY_CONFIG_ID\",\"instance_environment_type\":\"development\",\"application_name\":\"APPLICATION_NAME\",\"theme\":{\"buttons\":{\"font_color\":\"#ffffff\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"font_weight\":\"600\"},\"general\":{\"color\":\"#6c47ff\",\"padding\":\"1em\",\"box_shadow\":\"0 2px 8px rgba(0, 0, 0, 0.2)\",\"font_color\":\"#151515\",\"font_family\":\"\\\"Source Sans Pro\\\", sans-serif\",\"border_radius\":\"0.5em\",\"background_color\":\"#ffffff\",\"label_font_weight\":\"600\"},\"accounts\":{\"background_color\":\"#ffffff\"}},\"preferred_sign_in_strategy\":\"password\",\"logo_image_url\":\"URL\",\"favicon_image_url\":\"\",\"home_url\":\"URL\",\"sign_in_url\":\"URL\",\"sign_up_url\":\"URL\",\"user_profile_url\":\"URL\",\"waitlist_url\":\"URL\",\"after_sign_in_url\":\"URL\",\"after_sign_up_url\":\"URL\",\"after_sign_out_one_url\":\"URL\",\"after_sign_out_all_url\":\"URL\",\"after_switch_session_url\":\"URL\",\"after_join_waitlist_url\":\"URL\",\"organization_profile_url\":\"URL\",\"create_organization_url\":\"URL\",\"after_leave_organization_url\":\"URL\",\"after_create_organization_url\":\"URL\",\"logo_link_url\":\"URL\",\"support_email\":null,\"branded\":true,\"experimental_force_oauth_first\":false,\"clerk_js_version\":\"5\",\"show_devmode_warning\":true,\"google_one_tap_client_id\":\"GOOGLE_ONE_TAP_CLIENT_ID\",\"help_url\":null,\"privacy_policy_url\":\"URL\",\"terms_url\":\"URL\",\"logo_url\":\"URL\",\"favicon_url\":null,\"logo_image\":{\"object\":\"image\",\"id\":\"IMAGE_ID\",\"public_url\":\"URL\"},\"favicon_image\":null,\"captcha_public_key\":null,\"captcha_widget_type\":null,\"captcha_public_key_invisible\":null,\"captcha_provider\":null,\"captcha_oauth_bypass\":[]},\"user_settings\":{\"attributes\":{\"email_address\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"email_code\",\"email_link\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"email_code\",\"email_link\"],\"verify_at_sign_up\":true},\"phone_number\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[\"phone_code\"],\"used_for_second_factor\":true,\"second_factors\":[\"phone_code\"],\"verifications\":[\"phone_code\"],\"verify_at_sign_up\":true},\"username\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"web3_wallet\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"first_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"last_name\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"password\":{\"enabled\":true,\"required\":true,\"used_for_first_factor\":true,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"authenticator_app\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":true,\"second_factors\":[\"totp\"],\"verifications\":[\"totp\"],\"verify_at_sign_up\":false},\"ticket\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"backup_code\":{\"enabled\":false,\"required\":false,\"used_for_first_factor\":false,\"first_factors\":[],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[],\"verify_at_sign_up\":false},\"passkey\":{\"enabled\":true,\"required\":false,\"used_for_first_factor\":true,\"first_factors\":[\"passkey\"],\"used_for_second_factor\":false,\"second_factors\":[],\"verifications\":[\"passkey\"],\"verify_at_sign_up\":false}},\"sign_in\":{\"second_factor\":{\"required\":false}},\"sign_up\":{\"captcha_enabled\":false,\"captcha_widget_type\":\"smart\",\"custom_action_required\":false,\"progressive\":true,\"mode\":\"public\",\"legal_consent_enabled\":true},\"restrictions\":{\"allowlist\":{\"enabled\":false},\"blocklist\":{\"enabled\":false},\"allowlist_blocklist_disabled_on_sign_in\":{\"enabled\":false},\"block_email_subaddresses\":{\"enabled\":false},\"block_disposable_email_domains\":{\"enabled\":false}},\"username_settings\":{\"min_length\":4,\"max_length\":64,\"allow_extended_special_characters\":false},\"actions\":{\"delete_self\":true,\"create_organization\":true,\"create_organizations_limit\":null},\"attack_protection\":{\"user_lockout\":{\"enabled\":true,\"max_attempts\":100,\"duration_in_minutes\":60},\"pii\":{\"enabled\":true},\"email_link\":{\"require_same_client\":false},\"enumeration_protection\":{\"enabled\":false}},\"passkey_settings\":{\"allow_autofill\":true,\"show_sign_in_button\":true},\"social\":{\"oauth_apple\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_apple\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Apple\",\"logo_url\":\"URL\"},\"oauth_custom_spung\":{\"enabled\":false,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_custom_spung\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"spung\"},\"oauth_facebook\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_facebook\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Facebook\",\"logo_url\":\"URL\"},\"oauth_github\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_github\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"GitHub\",\"logo_url\":\"URL\"},\"oauth_google\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":true,\"strategy\":\"oauth_google\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"Google\",\"logo_url\":\"URL\"},\"oauth_linkedin_oidc\":{\"enabled\":true,\"required\":false,\"authenticatable\":true,\"block_email_subaddresses\":false,\"strategy\":\"oauth_linkedin_oidc\",\"not_selectable\":false,\"deprecated\":false,\"name\":\"LinkedIn\",\"logo_url\":\"URL\"}},\"password_settings\":{\"disable_hibp\":false,\"min_length\":8,\"max_length\":0,\"require_special_char\":true,\"require_numbers\":true,\"require_uppercase\":true,\"require_lowercase\":true,\"show_zxcvbn\":false,\"min_zxcvbn_strength\":0,\"enforce_hibp_on_sign_in\":false,\"allowed_special_characters\":\"+$-_\"}},\"commerce_settings\":{\"billing\":{\"enabled\":false,\"has_paid_user_plans\":false,\"has_paid_org_plans\":false,\"free_trial_requires_payment_method\":true,\"user\":{\"enabled\":false,\"has_paid_plans\":false},\"organization\":{\"enabled\":false,\"has_paid_plans\":false}}},\"api_keys_settings\":{\"enabled\":false},\"maintenance_mode\":false,\"client_debug_mode\":false}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json new file mode 100644 index 00000000..e265c271 --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/003.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins identifier","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"needs_first_factor\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":[{\"strategy\":\"password\"},{\"strategy\":\"phone_code\",\"safe_identifier\":\"%%PHONE%%\",\"phone_number_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"email_link\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true},{\"strategy\":\"reset_password_email_code\",\"safe_identifier\":\"%%EMAIL%%\",\"email_address_id\":\"IDENTIFIER_ID\",\"primary\":true}],\"supported_second_factors\":null,\"first_factor_verification\":null,\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":null,\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json new file mode 100644 index 00000000..d7e83cbb --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/004.json @@ -0,0 +1 @@ +{"key":"POST /v1/client/sign_ins/SIGN_IN_ID/attempt_first_factor password&strategy=password","body":"{\"response\":{\"object\":\"sign_in_attempt\",\"id\":\"SIGN_IN_ID\",\"status\":\"complete\",\"supported_identifiers\":[\"email_address\",\"passkey\",\"password\",\"phone_number\",\"username\"],\"supported_first_factors\":null,\"supported_second_factors\":null,\"first_factor_verification\":{\"object\":\"verification_password\",\"status\":\"verified\",\"strategy\":\"password\",\"attempts\":1,\"expire_at\":null},\"second_factor_verification\":null,\"identifier\":\"%%USERNAME%%\",\"user_data\":null,\"created_session_id\":\"SESSION_ID\",\"abandon_at\":\"%%DATETIME 1%%\",\"locale\":null},\"client\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[{\"object\":\"session\",\"id\":\"SESSION_ID\",\"status\":\"active\",\"expire_at\":\"%%DATETIME 2%%\",\"abandon_at\":\"%%DATETIME 3%%\",\"last_active_at\":\"%%DATETIME -4%%\",\"last_active_organization_id\":null,\"actor\":null,\"user\":{\"id\":\"USER_ID\",\"object\":\"user\",\"username\":\"userfortests\",\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"locale\":null,\"image_url\":\"URL\",\"has_image\":false,\"primary_email_address_id\":\"IDENTIFIER_ID\",\"primary_phone_number_id\":\"IDENTIFIER_ID\",\"primary_web3_wallet_id\":null,\"password_enabled\":true,\"two_factor_enabled\":false,\"totp_enabled\":false,\"backup_code_enabled\":false,\"email_addresses\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"email_address\",\"email_address\":\"%%EMAIL%%\",\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"matches_sso_connection\":false,\"created_at\":\"%%DATETIME -8%%\",\"updated_at\":\"%%DATETIME -8%%\"}],\"phone_numbers\":[{\"id\":\"IDENTIFIER_ID\",\"object\":\"phone_number\",\"phone_number\":\"+15555550105\",\"reserved_for_second_factor\":false,\"default_second_factor\":false,\"reserved\":false,\"verification\":{\"object\":\"verification_admin\",\"status\":\"verified\",\"strategy\":\"admin\",\"attempts\":null,\"expire_at\":null},\"linked_to\":[],\"backup_codes\":null,\"created_at\":\"%%DATETIME -7%%\",\"updated_at\":\"%%DATETIME -7%%\"}],\"web3_wallets\":[],\"passkeys\":[],\"external_accounts\":[],\"saml_accounts\":[],\"enterprise_accounts\":[],\"public_metadata\":{},\"unsafe_metadata\":{},\"external_id\":null,\"last_sign_in_at\":\"%%DATETIME -4%%\",\"banned\":false,\"locked\":false,\"lockout_expires_in_seconds\":null,\"verification_attempts_remaining\":100,\"created_at\":\"%%DATETIME -9%%\",\"updated_at\":\"%%DATETIME -3%%\",\"delete_self_enabled\":true,\"create_organization_enabled\":true,\"last_active_at\":\"%%DATETIME -6%%\",\"mfa_enabled_at\":null,\"mfa_disabled_at\":null,\"legal_accepted_at\":\"%%DATETIME -10%%\",\"profile_image_url\":\"URL\",\"organization_memberships\":[]},\"public_user_data\":{\"first_name\":\"%%FIRSTNAME%%\",\"last_name\":\"%%LASTNAME%%\",\"image_url\":\"URL\",\"has_image\":false,\"identifier\":\"%%USERNAME%%\",\"username\":\"userfortests\",\"profile_image_url\":\"URL\"},\"factor_verification_age\":[0,-1],\"created_at\":\"%%DATETIME -4%%\",\"updated_at\":\"%%DATETIME -1%%\",\"last_active_token\":{\"object\":\"token\",\"jwt\":\"e30=.e30=.e30=\"}}],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":\"SESSION_ID\",\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -5%%\",\"updated_at\":\"%%DATETIME -2%%\"}}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json new file mode 100644 index 00000000..6e1ebe1a --- /dev/null +++ b/packages/clerk_flutter/example/test/_responses/flow/sign_in_flow_test/with_username_and_password/005.json @@ -0,0 +1 @@ +{"key":"DELETE /v1/client","body":"{\"response\":{\"object\":\"client\",\"id\":\"CLIENT_ID\",\"sessions\":[],\"sign_in\":null,\"sign_up\":null,\"last_active_session_id\":null,\"last_authentication_strategy\":null,\"cookie_expires_at\":null,\"captcha_bypass\":false,\"created_at\":\"%%DATETIME -2%%\",\"updated_at\":\"%%DATETIME -1%%\"},\"client\":null}"} \ No newline at end of file diff --git a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart index 5b0b1815..7dc107ff 100644 --- a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart +++ b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart @@ -6,6 +6,22 @@ import 'package:integration_test/integration_test.dart'; import '../test_helpers.dart'; +Future _signOut(WidgetTester tester) async { + final signOutButton = find.text('Sign out'); + expect(signOutButton, findsOneWidget); + await tester.tap(signOutButton); + await tester.pumpAndSettle(); + + expect(find.text('Are you sure?'), findsOneWidget); + await tester.tap(find.text('OK')); + await tester.pumpAndSettle(); + + expect( + find.text('Welcome back! Please sign in to continue'), + findsOneWidget, + ); +} + void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -25,6 +41,7 @@ void main() { return TestAuthConfig( publishableKey: env.publishableKey, httpService: httpService, + phoneNumberWhiteList: [env.phoneNumber], ).toClerkAuthConfig(); } @@ -38,15 +55,45 @@ void main() { await tester.tap(find.text('Clerk UI Sign In')); await tester.pumpAndSettle(); - final emailInput = find.byKey(kEmailInputField); + final emailInput = find.byKey(kIdentifierInputField); await tester.tap(emailInput); tester.testTextInput.enterText(env.email); await tester.tap(find.text('Continue')); await tester.pumpAndSettle(); - final passwordInput = find.byKey(kPasswordInputField); - await tester.tap(passwordInput); + expect(find.text(env.email), findsOneWidget); + + await tester.tap(find.byKey(kPasswordInputField)); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); + + await _signOut(tester); + }); + + testWidgets('with username and password', (tester) async { + // Load app widget. + final config = await initialiseForTest('with_username_and_password'); + await tester.pumpWidget(ExampleApp(config: config)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Clerk UI Sign In')); + await tester.pumpAndSettle(); + + final usernameInput = find.byKey(kIdentifierInputField); + await tester.tap(usernameInput); + tester.testTextInput.enterText(env.username); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.username), findsOneWidget); + + await tester.tap(find.byKey(kPasswordInputField)); tester.testTextInput.enterText(env.password); await tester.tap(find.text('Continue')); @@ -54,19 +101,52 @@ void main() { expect(find.text(env.email), findsOneWidget); - final signOutButton = find.text('Sign out'); - expect(signOutButton, findsOneWidget); - await tester.tap(signOutButton); + await _signOut(tester); + }); + + testWidgets('with phone number and password', (tester) async { + // Load app widget. + final config = await initialiseForTest('with_phone_number_and_password'); + await tester.pumpWidget(ExampleApp(config: config)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Clerk UI Sign In')); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Switch to phone')); + await tester.pumpAndSettle(); + + final phoneInput = find.byKey(kPhoneInputField); + expect(phoneInput, findsOneWidget); + + // try changing zones away from the default +1/US to +44/UK... + await tester.tap(find.text('+ 1')); + await tester.pumpAndSettle(); + await tester.tap(find.text('United Kingdom')); await tester.pumpAndSettle(); - expect(find.text('Are you sure?'), findsOneWidget); - await tester.tap(find.text('OK')); + // ...and back + await tester.tap(find.text('+ 44')); await tester.pumpAndSettle(); + await tester.tap(find.text('United States')); + await tester.pumpAndSettle(); + + await tester.tap(phoneInput); + final local = env.phoneNumber.replaceFirst('+1', ''); + tester.testTextInput.enterText(local); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(kPasswordInputField)); + tester.testTextInput.enterText(env.password); + + await tester.tap(find.text('Continue')); + await tester.pumpAndSettle(); + + expect(find.text(env.email), findsOneWidget); - expect( - find.text('Welcome back! Please sign in to continue'), - findsOneWidget, - ); + await _signOut(tester); }); }); } diff --git a/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart index 57a418b7..d287a01e 100644 --- a/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart +++ b/packages/clerk_flutter/lib/src/widgets/clerk_keys.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; /// Email identifier -const kEmailInputField = Key('emailInputField'); +const kIdentifierInputField = Key('emailInputField'); /// Phone identifier const kPhoneInputField = Key('phoneInputField'); diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart index bdd30e8a..be301c71 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_identifier_input.dart @@ -108,7 +108,7 @@ class _ClerkIdentifierInputState extends State { Closeable( closed: widget.identifierType.value.isPhoneNumber, child: ClerkTextFormField( - key: kEmailInputField, + key: kIdentifierInputField, initial: widget.initialValue, label: l10ns.grammar.toSentence( l10ns.grammar.toLitany( diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart index 020014da..abdde891 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart @@ -95,7 +95,7 @@ class _PhoneInputState extends State<_PhoneInput> { late PhoneNumber _phoneNumber; - // widget.initial is String ? PhoneNumber.parse(widget.initial!) : null; + late List _whiteList; late bool _isValid = _phoneNumber.isValid() == true; late IsoCode _isoCode; @@ -108,7 +108,9 @@ class _PhoneInputState extends State<_PhoneInput> { } Future _getPhoneNumber() async { - final persistor = ClerkAuth.of(context, listen: false).config.persistor; + final config = ClerkAuth.of(context, listen: false).config; + final persistor = config.persistor; + _whiteList = config.phoneNumberWhiteList; if (widget.initial case String initial) { _phoneNumber = PhoneNumber.parse(initial); _isoCode = _phoneNumber.isoCode; @@ -130,6 +132,9 @@ class _PhoneInputState extends State<_PhoneInput> { } } + bool _validate(PhoneNumber phoneNumber) => + _whiteList.contains(phoneNumber.international) || phoneNumber.isValid(); + @override Widget build(BuildContext context) { return FutureBuilder( @@ -148,7 +153,7 @@ class _PhoneInputState extends State<_PhoneInput> { focusNode: widget.focusNode, onChanged: (phoneNumber) { if (phoneNumber case PhoneNumber phoneNumber) { - final valid = phoneNumber.isValid(); + final valid = _validate(phoneNumber); if (valid != _isValid) setState(() => _isValid = valid); if (valid) { widget.onChanged(phoneNumber.international); diff --git a/test_support/test/src/test_auth_config.dart b/test_support/test/src/test_auth_config.dart index e3918805..c08fd446 100644 --- a/test_support/test/src/test_auth_config.dart +++ b/test_support/test/src/test_auth_config.dart @@ -4,8 +4,10 @@ import 'package:http/http.dart' show ByteStream, Response; class TestAuthConfig extends AuthConfig { const TestAuthConfig({ required super.publishableKey, + List phoneNumberWhiteList = const [], super.httpService = const _NoneHttpService(), - }) : super( + }) : _phoneNumberWhiteList = phoneNumberWhiteList, + super( sessionTokenPolling: false, localesLookup: _localesLookup, persistor: Persistor.none, @@ -13,6 +15,11 @@ class TestAuthConfig extends AuthConfig { telemetryPeriod: Duration.zero, ); + final List _phoneNumberWhiteList; + + @override + List get phoneNumberWhiteList => _phoneNumberWhiteList; + static List _localesLookup() => const ['en']; } From 22ac3d278e06d74b7ffae08f300c5125814f1fe7 Mon Sep 17 00:00:00 2001 From: Nic Ford Date: Mon, 3 Nov 2025 19:22:40 +0000 Subject: [PATCH 4/4] fix: make test phone numbers work [#297] --- .../lib/src/clerk_auth/auth_config.dart | 4 -- .../example/test/flows/sign_in_flow_test.dart | 68 ++++++++++++------- .../example/test/test_helpers.dart | 44 ++++++------ .../authentication/clerk_sign_in_panel.dart | 4 +- .../ui/clerk_phone_number_form_field.dart | 17 +++-- test_support/test/src/test_auth_config.dart | 10 +-- 6 files changed, 84 insertions(+), 63 deletions(-) diff --git a/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart b/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart index c8f48654..7af0ae9b 100644 --- a/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart +++ b/packages/clerk_auth/lib/src/clerk_auth/auth_config.dart @@ -90,10 +90,6 @@ class AuthConfig { /// The [HttpService] used to communicate with the backend. final HttpService httpService; - /// Phone number whitelist, for testing purposes. Must be overridden - /// by e.g. test config to change from empty list - final List phoneNumberWhiteList = const []; - /// Initialise Future initialize() async { await persistor.initialize(); diff --git a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart index 7dc107ff..9fcd13ce 100644 --- a/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart +++ b/packages/clerk_flutter/example/test/flows/sign_in_flow_test.dart @@ -1,7 +1,6 @@ import 'package:clerk_flutter/clerk_flutter.dart'; import 'package:clerk_flutter/logging.dart'; import 'package:clerk_flutter_example/main.dart'; -import 'package:flutter/cupertino.dart'; import 'package:integration_test/integration_test.dart'; import '../test_helpers.dart'; @@ -32,28 +31,28 @@ void main() { await setUpLogging(printer: TestLogPrinter(), level: Level.INFO); }); - Future initialiseForTest(String testName) async { - FlutterError.onError = setIgnoreOverflowErrors(FlutterError.onError); + Future initialiseForTest(WidgetTester tester) async { + setIgnoreOverflowErrors(); final httpService = TestHttpService('flow/sign_in_flow_test', env) - ..recordPath = testName; + ..recordPath = tester.testDescription.toLowerCase().replaceAll(' ', '_'); - return TestAuthConfig( + final config = TestAuthConfig( publishableKey: env.publishableKey, httpService: httpService, - phoneNumberWhiteList: [env.phoneNumber], ).toClerkAuthConfig(); + + await tester.pumpWidget(ExampleApp(config: config)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('Clerk UI Sign In')); + await tester.pumpAndSettle(); } - group('sign in: ', () { + group('sign in with password: ', () { testWidgets('with email and password', (tester) async { // Load app widget. - final config = await initialiseForTest('with_email_and_password'); - await tester.pumpWidget(ExampleApp(config: config)); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Clerk UI Sign In')); - await tester.pumpAndSettle(); + await initialiseForTest(tester); final emailInput = find.byKey(kIdentifierInputField); await tester.tap(emailInput); @@ -77,12 +76,7 @@ void main() { testWidgets('with username and password', (tester) async { // Load app widget. - final config = await initialiseForTest('with_username_and_password'); - await tester.pumpWidget(ExampleApp(config: config)); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Clerk UI Sign In')); - await tester.pumpAndSettle(); + await initialiseForTest(tester); final usernameInput = find.byKey(kIdentifierInputField); await tester.tap(usernameInput); @@ -106,12 +100,7 @@ void main() { testWidgets('with phone number and password', (tester) async { // Load app widget. - final config = await initialiseForTest('with_phone_number_and_password'); - await tester.pumpWidget(ExampleApp(config: config)); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Clerk UI Sign In')); - await tester.pumpAndSettle(); + await initialiseForTest(tester); await tester.tap(find.text('Switch to phone')); await tester.pumpAndSettle(); @@ -149,4 +138,33 @@ void main() { await _signOut(tester); }); }); + + // group('sign in with code: ', () { + // testWidgets('with email and code', (tester) async { + // // Load app widget. + // await initialiseForTest(tester); + // + // final emailInput = find.byKey(kIdentifierInputField); + // await tester.tap(emailInput); + // tester.testTextInput.enterText(env.email); + // + // await tester.tap(find.text('Continue')); + // await tester.pumpAndSettle(); + // + // expect(find.text(env.email), findsOneWidget); + // + // await tester.tap( + // find.text('Sign in by entering a code sent to you by email'), + // ); + // await tester.pumpAndSettle(); + // + // expect(find.text('Enter the code sent to ${env.email}'), findsOneWidget); + // tester.testTextInput.enterText(env.code); + // await tester.pumpAndSettle(); + // + // expect(find.text(env.email), findsOneWidget); + // + // await _signOut(tester); + // }); + // }); } diff --git a/packages/clerk_flutter/example/test/test_helpers.dart b/packages/clerk_flutter/example/test/test_helpers.dart index b50c0fbe..6ba8bc5a 100644 --- a/packages/clerk_flutter/example/test/test_helpers.dart +++ b/packages/clerk_flutter/example/test/test_helpers.dart @@ -30,29 +30,31 @@ extension AuthConfigExt on clerk.AuthConfig { } } -FlutterExceptionHandler setIgnoreOverflowErrors( - FlutterExceptionHandler? override, -) { - return (FlutterErrorDetails details, {bool forceReport = false}) { - final hasReportableError = switch (details.exception) { - FlutterError exception => exception.diagnostics.any( - (e) { - final value = e.toString(); - return value.startsWith('A RenderFlex overflowed by') == false && - value.startsWith('Unable to load asset') == false; - }, - ), - _ => true, - }; +void setIgnoreOverflowErrors() { + final originalOnError = FlutterError.onError; + if (originalOnError != setIgnoreOverflowErrors) { + FlutterError.onError = + (FlutterErrorDetails details, {bool forceReport = false}) { + final hasReportableError = switch (details.exception) { + FlutterError exception => exception.diagnostics.any( + (e) { + final value = e.toString(); + return value.startsWith('A RenderFlex overflowed by') == false && + value.startsWith('Unable to load asset') == false; + }, + ), + _ => true, + }; - if (hasReportableError) { - if (override case FlutterExceptionHandler override) { - override(details); - } else { - FlutterError.dumpErrorToConsole(details, forceReport: forceReport); + if (hasReportableError) { + if (originalOnError case FlutterExceptionHandler onError) { + onError(details); + } else { + FlutterError.dumpErrorToConsole(details, forceReport: forceReport); + } } - } - }; + }; + } } class _NoneFileCache implements ClerkFileCache { diff --git a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart index f75ad24c..bf99f16c 100644 --- a/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart +++ b/packages/clerk_flutter/lib/src/widgets/authentication/clerk_sign_in_panel.dart @@ -291,7 +291,9 @@ class _FactorList extends StatelessWidget { child: StrategyButton( key: ValueKey(factor), strategy: factor.strategy, - onClick: () => onSubmit(factor.strategy), + onClick: () { + onSubmit(factor.strategy); + }, ), ), ], diff --git a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart index abdde891..34289239 100644 --- a/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart +++ b/packages/clerk_flutter/lib/src/widgets/ui/clerk_phone_number_form_field.dart @@ -92,10 +92,11 @@ class _PhoneInput extends StatefulWidget { class _PhoneInputState extends State<_PhoneInput> { static const _kIsoCode = 'phone_number.iso_code'; + static const _testNumberPrefix = '+155555501'; late PhoneNumber _phoneNumber; - late List _whiteList; + late final bool _isTestMode; late bool _isValid = _phoneNumber.isValid() == true; late IsoCode _isoCode; @@ -109,8 +110,8 @@ class _PhoneInputState extends State<_PhoneInput> { Future _getPhoneNumber() async { final config = ClerkAuth.of(context, listen: false).config; + _isTestMode = config.isTestMode; final persistor = config.persistor; - _whiteList = config.phoneNumberWhiteList; if (widget.initial case String initial) { _phoneNumber = PhoneNumber.parse(initial); _isoCode = _phoneNumber.isoCode; @@ -132,8 +133,16 @@ class _PhoneInputState extends State<_PhoneInput> { } } - bool _validate(PhoneNumber phoneNumber) => - _whiteList.contains(phoneNumber.international) || phoneNumber.isValid(); + bool _validate(PhoneNumber phoneNumber) { + if (_isTestMode) { + final number = phoneNumber.international; + if (number.startsWith(_testNumberPrefix) && + number.length == _testNumberPrefix.length + 2) { + return true; + } + } + return phoneNumber.isValid(); + } @override Widget build(BuildContext context) { diff --git a/test_support/test/src/test_auth_config.dart b/test_support/test/src/test_auth_config.dart index c08fd446..3a7d4a3b 100644 --- a/test_support/test/src/test_auth_config.dart +++ b/test_support/test/src/test_auth_config.dart @@ -4,22 +4,16 @@ import 'package:http/http.dart' show ByteStream, Response; class TestAuthConfig extends AuthConfig { const TestAuthConfig({ required super.publishableKey, - List phoneNumberWhiteList = const [], super.httpService = const _NoneHttpService(), - }) : _phoneNumberWhiteList = phoneNumberWhiteList, - super( + }) : super( sessionTokenPolling: false, localesLookup: _localesLookup, persistor: Persistor.none, clientRefreshPeriod: Duration.zero, telemetryPeriod: Duration.zero, + isTestMode: true, ); - final List _phoneNumberWhiteList; - - @override - List get phoneNumberWhiteList => _phoneNumberWhiteList; - static List _localesLookup() => const ['en']; }