From 212ec251e41d7278e52029df706aaf4bda7a3f67 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Sun, 12 Oct 2025 00:14:34 +0100 Subject: [PATCH 1/7] feat: EmailAuthScreen integration tests - localize strings and default icon based on validator - setup integration tests to run in CI - add execute permission to start-firebase-emulator.sh script - CI use fake project id for auth emulator --- .github/workflows/android.yml | 23 +- .../ui/auth/compose/FirebaseAuthUI.kt | 2 +- .../compose/configuration/PasswordRule.kt | 2 +- .../string_provider/AuthUIStringProvider.kt | 25 +- .../DefaultAuthUIStringProvider.kt | 32 +- .../compose/ui/components/AuthTextField.kt | 22 +- .../compose/ui/screens/ResetPasswordUI.kt | 28 +- .../ui/auth/compose/ui/screens/SignInUI.kt | 121 ++-- .../ui/auth/compose/ui/screens/SignUpUI.kt | 52 +- auth/src/main/res/values/strings.xml | 1 + .../auth/compose/testutil/TaskExtensions.kt | 67 ++ .../ui/components/AuthTextFieldTest.kt | 4 +- .../compose/ui/screens/EmailAuthScreenTest.kt | 625 ++++++++++++++++++ composeapp/src/main/AndroidManifest.xml | 3 +- .../com/firebase/composeapp/MainActivity.kt | 2 +- library/.firebaserc | 5 + library/firebase.json | 11 + scripts/start-firebase-emulator.sh | 65 ++ 18 files changed, 952 insertions(+), 138 deletions(-) create mode 100644 auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt create mode 100644 auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt create mode 100644 library/.firebaserc create mode 100644 library/firebase.json create mode 100755 scripts/start-firebase-emulator.sh diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index aa9de475c..10ed10c88 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -7,6 +7,7 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 45 steps: - uses: actions/checkout@v4 @@ -18,12 +19,30 @@ jobs: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - - name: Set up JDK 17 + - name: Firebase Emulator Cache + uses: actions/cache@v4 + with: + path: ~/.cache/firebase/emulators + key: firebase-emulators-v3-${{ runner.os }} + + - name: Install Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' + - name: Install Tools + run: | + npm i -g firebase-tools + + - name: Start Firebase Auth Emulator + run: ./scripts/start-firebase-emulator.sh + - name: Build with Gradle run: ./scripts/build.sh diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt index 82a2b4f3d..1a11ee2b4 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt @@ -257,7 +257,7 @@ class FirebaseAuthUI private constructor( * @throws AuthException.UnknownException for other errors * @since 10.0.0 */ - suspend fun signOut(context: Context) { + fun signOut(context: Context) { try { // Update state to loading updateAuthState(AuthState.Loading("Signing out...")) diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt index 383265501..5187f96a5 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/PasswordRule.kt @@ -30,7 +30,7 @@ abstract class PasswordRule { } override fun getErrorMessage(stringProvider: AuthUIStringProvider): String { - return stringProvider.passwordTooShort.format(value) + return stringProvider.passwordTooShort(value) } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt index 2420fcf9a..30cc33257 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/AuthUIStringProvider.kt @@ -86,7 +86,7 @@ interface AuthUIStringProvider { val passwordsDoNotMatch: String /** Error message when password doesn't meet minimum length requirement. Should support string formatting with minimum length parameter. */ - val passwordTooShort: String + fun passwordTooShort(minimumLength: Int): String /** Error message when password is missing at least one uppercase letter (A-Z) */ val passwordMissingUppercase: String @@ -102,7 +102,7 @@ interface AuthUIStringProvider { // Email Authentication Strings /** Title for email signup form */ - val titleRegisterEmail: String + val signupPageTitle: String /** Hint for email input field */ val emailHint: String @@ -110,6 +110,9 @@ interface AuthUIStringProvider { /** Hint for password input field */ val passwordHint: String + /** Hint for confirm password input field */ + val confirmPasswordHint: String + /** Hint for new password input field */ val newPasswordHint: String @@ -125,6 +128,24 @@ interface AuthUIStringProvider { /** Trouble signing in link text */ val troubleSigningIn: String + /** Title for recover password page */ + val recoverPasswordPageTitle: String + + /** Button text for reset password */ + val sendButtonText: String + + /** Title for recover password link sent dialog */ + val recoverPasswordLinkSentDialogTitle: String + + /** Body for recover password link sent dialog */ + fun recoverPasswordLinkSentDialogBody(email: String): String + + /** Title for email sign in link sent dialog */ + val emailSignInLinkSentDialogTitle: String + + /** Body for email sign in link sent dialog */ + fun emailSignInLinkSentDialogBody(email: String): String + // Phone Authentication Strings /** Phone number entry form title */ val verifyPhoneNumberTitle: String diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt index 829f8f6f2..5016d7482 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/configuration/string_provider/DefaultAuthUIStringProvider.kt @@ -20,8 +20,8 @@ import com.firebase.ui.auth.R import java.util.Locale class DefaultAuthUIStringProvider( - private val context: Context, - private val locale: Locale? = null, + context: Context, + locale: Locale? = null, ) : AuthUIStringProvider { /** * Allows overriding locale. @@ -95,8 +95,10 @@ class DefaultAuthUIStringProvider( get() = localizedContext.getString(R.string.fui_error_invalid_password) override val passwordsDoNotMatch: String get() = localizedContext.getString(R.string.fui_passwords_do_not_match) - override val passwordTooShort: String - get() = localizedContext.getString(R.string.fui_error_password_too_short) + + override fun passwordTooShort(minimumLength: Int): String = + localizedContext.getString(R.string.fui_error_password_too_short, minimumLength) + override val passwordMissingUppercase: String get() = localizedContext.getString(R.string.fui_error_password_missing_uppercase) override val passwordMissingLowercase: String @@ -109,12 +111,14 @@ class DefaultAuthUIStringProvider( /** * Email Authentication Strings */ - override val titleRegisterEmail: String + override val signupPageTitle: String get() = localizedContext.getString(R.string.fui_title_register_email) override val emailHint: String get() = localizedContext.getString(R.string.fui_email_hint) override val passwordHint: String get() = localizedContext.getString(R.string.fui_password_hint) + override val confirmPasswordHint: String + get() = localizedContext.getString(R.string.fui_confirm_password_hint) override val newPasswordHint: String get() = localizedContext.getString(R.string.fui_new_password_hint) override val nameHint: String @@ -126,6 +130,24 @@ class DefaultAuthUIStringProvider( override val troubleSigningIn: String get() = localizedContext.getString(R.string.fui_trouble_signing_in) + override val recoverPasswordPageTitle: String + get() = localizedContext.getString(R.string.fui_title_recover_password_activity) + + override val sendButtonText: String + get() = localizedContext.getString(R.string.fui_button_text_send) + + override val recoverPasswordLinkSentDialogTitle: String + get() = localizedContext.getString(R.string.fui_title_confirm_recover_password) + + override fun recoverPasswordLinkSentDialogBody(email: String): String = + localizedContext.getString(R.string.fui_confirm_recovery_body, email) + + override val emailSignInLinkSentDialogTitle: String + get() = localizedContext.getString(R.string.fui_email_link_header) + + override fun emailSignInLinkSentDialogBody(email: String): String = + localizedContext.getString(R.string.fui_email_link_email_sent, email) + /** * Phone Authentication Strings */ diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt index 200dd0ece..7e0e911d0 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/components/AuthTextField.kt @@ -125,7 +125,27 @@ fun AuthTextField( keyboardActions = keyboardActions, visualTransformation = if (isSecureTextField && !passwordVisible) PasswordVisualTransformation() else visualTransformation, - leadingIcon = leadingIcon, + leadingIcon = leadingIcon ?: when { + validator is EmailValidator -> { + { + Icon( + imageVector = Icons.Default.Email, + contentDescription = "" + ) + } + } + + isSecureTextField -> { + { + Icon( + imageVector = Icons.Default.Lock, + contentDescription = "" + ) + } + } + + else -> null + }, trailingIcon = trailingIcon ?: { if (isSecureTextField) { IconButton( diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt index 79177c085..3b7deb599 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/ResetPasswordUI.kt @@ -17,24 +17,22 @@ package com.firebase.ui.auth.compose.ui.screens import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Email import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf @@ -83,13 +81,13 @@ fun ResetPasswordUI( AlertDialog( title = { Text( - text = "Reset Link Sent", + text = stringProvider.recoverPasswordLinkSentDialogTitle, style = MaterialTheme.typography.headlineSmall ) }, text = { Text( - text = "Check your email $email", + text = stringProvider.recoverPasswordLinkSentDialogBody(email), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Start ) @@ -97,10 +95,11 @@ fun ResetPasswordUI( confirmButton = { TextButton( onClick = { + onGoToSignIn() isDialogVisible.value = false } ) { - Text("Dismiss") + Text(stringProvider.dismissAction) } }, onDismissRequest = { @@ -114,7 +113,7 @@ fun ResetPasswordUI( topBar = { TopAppBar( title = { - Text("Recover Password") + Text(stringProvider.recoverPasswordPageTitle) }, colors = AuthUITheme.topAppBarColors ) @@ -124,7 +123,8 @@ fun ResetPasswordUI( modifier = Modifier .padding(innerPadding) .safeDrawingPadding() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), ) { AuthTextField( value = email, @@ -135,12 +135,6 @@ fun ResetPasswordUI( }, onValueChange = { text -> onEmailChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Email, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(8.dp)) @@ -154,7 +148,7 @@ fun ResetPasswordUI( }, enabled = !isLoading, ) { - Text("Sign In") + Text(stringProvider.signInDefault.uppercase()) } Spacer(modifier = Modifier.width(16.dp)) Button( @@ -169,7 +163,7 @@ fun ResetPasswordUI( .size(16.dp) ) } else { - Text("Send") + Text(stringProvider.sendButtonText.uppercase()) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt index edc7ffa9e..cb822e647 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignInUI.kt @@ -23,14 +23,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Email -import androidx.compose.material.icons.filled.Lock +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -90,37 +88,39 @@ fun SignInUI( } } - val isDialogVisible = - remember(emailSignInLinkSent) { mutableStateOf(emailSignInLinkSent) } + if (provider.isEmailLinkSignInEnabled) { + val isDialogVisible = + remember(emailSignInLinkSent) { mutableStateOf(emailSignInLinkSent) } - if (isDialogVisible.value) { - AlertDialog( - title = { - Text( - text = "Email Sign In Link Sent", - style = MaterialTheme.typography.headlineSmall - ) - }, - text = { - Text( - text = "Check your email $email", - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Start - ) - }, - confirmButton = { - TextButton( - onClick = { - isDialogVisible.value = false + if (isDialogVisible.value) { + AlertDialog( + title = { + Text( + text = stringProvider.emailSignInLinkSentDialogTitle, + style = MaterialTheme.typography.headlineSmall + ) + }, + text = { + Text( + text = stringProvider.emailSignInLinkSentDialogBody(email), + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Start + ) + }, + confirmButton = { + TextButton( + onClick = { + isDialogVisible.value = false + } + ) { + Text(stringProvider.dismissAction) } - ) { - Text("Dismiss") - } - }, - onDismissRequest = { - isDialogVisible.value = false - }, - ) + }, + onDismissRequest = { + isDialogVisible.value = false + }, + ) + } } Scaffold( @@ -138,7 +138,8 @@ fun SignInUI( modifier = Modifier .padding(innerPadding) .safeDrawingPadding() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), ) { AuthTextField( value = email, @@ -149,12 +150,6 @@ fun SignInUI( }, onValueChange = { text -> onEmailChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Email, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(16.dp)) @@ -169,34 +164,30 @@ fun SignInUI( }, onValueChange = { text -> onPasswordChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Lock, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(8.dp)) } - TextButton( - modifier = Modifier - .align(Alignment.Start), - onClick = { - onGoToResetPassword() - }, - enabled = !isLoading, - contentPadding = PaddingValues.Zero - ) { - Text( - modifier = modifier, - text = stringProvider.troubleSigningIn, - style = MaterialTheme.typography.bodyMedium, - textAlign = TextAlign.Center, - textDecoration = TextDecoration.Underline - ) + if (!provider.isEmailLinkSignInEnabled) { + TextButton( + modifier = Modifier + .align(Alignment.Start), + onClick = { + onGoToResetPassword() + }, + enabled = !isLoading, + contentPadding = PaddingValues.Zero + ) { + Text( + modifier = modifier, + text = stringProvider.troubleSigningIn, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + textDecoration = TextDecoration.Underline + ) + } + Spacer(modifier = Modifier.height(8.dp)) } - Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier .align(Alignment.End), @@ -209,7 +200,7 @@ fun SignInUI( }, enabled = !isLoading, ) { - Text(stringProvider.titleRegisterEmail) + Text(stringProvider.signupPageTitle.uppercase()) } Spacer(modifier = Modifier.width(16.dp)) } @@ -228,7 +219,7 @@ fun SignInUI( .size(16.dp) ) } else { - Text(stringProvider.signInDefault) + Text(stringProvider.signInDefault.uppercase()) } } } diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt index 9d138e8a8..d2ccc4b0a 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/ui/screens/SignUpUI.kt @@ -22,19 +22,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AccountCircle -import androidx.compose.material.icons.filled.Email -import androidx.compose.material.icons.filled.Lock +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.remember @@ -48,8 +43,8 @@ import com.firebase.ui.auth.compose.configuration.authUIConfiguration import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider import com.firebase.ui.auth.compose.configuration.theme.AuthUITheme -import com.firebase.ui.auth.compose.configuration.validators.GeneralFieldValidator import com.firebase.ui.auth.compose.configuration.validators.EmailValidator +import com.firebase.ui.auth.compose.configuration.validators.GeneralFieldValidator import com.firebase.ui.auth.compose.configuration.validators.PasswordValidator import com.firebase.ui.auth.compose.ui.components.AuthTextField import com.firebase.ui.auth.compose.ui.components.TermsAndPrivacyForm @@ -108,7 +103,7 @@ fun SignUpUI( topBar = { TopAppBar( title = { - Text("Sign up") + Text(stringProvider.signupPageTitle) }, colors = AuthUITheme.topAppBarColors ) @@ -118,23 +113,18 @@ fun SignUpUI( modifier = Modifier .padding(innerPadding) .safeDrawingPadding() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()), ) { AuthTextField( value = email, validator = emailValidator, enabled = !isLoading, label = { - Text("Email") + Text(stringProvider.emailHint) }, onValueChange = { text -> onEmailChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Email, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(16.dp)) @@ -144,16 +134,10 @@ fun SignUpUI( validator = displayNameValidator, enabled = !isLoading, label = { - Text("First & last Name") + Text(stringProvider.nameHint) }, onValueChange = { text -> onDisplayNameChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.AccountCircle, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(16.dp)) @@ -164,16 +148,10 @@ fun SignUpUI( enabled = !isLoading, isSecureTextField = true, label = { - Text("Password") + Text(stringProvider.passwordHint) }, onValueChange = { text -> onPasswordChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Lock, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(16.dp)) @@ -183,16 +161,10 @@ fun SignUpUI( enabled = !isLoading, isSecureTextField = true, label = { - Text("Confirm Password") + Text(stringProvider.confirmPasswordHint) }, onValueChange = { text -> onConfirmPasswordChange(text) - }, - leadingIcon = { - Icon( - imageVector = Icons.Default.Lock, - contentDescription = "" - ) } ) Spacer(modifier = Modifier.height(8.dp)) @@ -206,7 +178,7 @@ fun SignUpUI( }, enabled = !isLoading, ) { - Text("Sign In") + Text(stringProvider.signInDefault.uppercase()) } Spacer(modifier = Modifier.width(16.dp)) Button( @@ -221,7 +193,7 @@ fun SignUpUI( .size(16.dp) ) } else { - Text("Sign Up") + Text(stringProvider.signupPageTitle.uppercase()) } } } diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml index fa3ec8cb5..511a8d7a8 100644 --- a/auth/src/main/res/values/strings.xml +++ b/auth/src/main/res/values/strings.xml @@ -37,6 +37,7 @@ Phone Number Country Password + Confirm Password New password You can\'t leave this empty. That email address isn\'t correct diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt b/auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt new file mode 100644 index 000000000..7c7e558fc --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.auth.compose.testutil + +import android.os.Looper +import com.google.android.gms.tasks.Task +import org.robolectric.Shadows.shadowOf + +/** + * Awaits the completion of a Firebase [Task] in a Robolectric test environment. + * + * This extension function is specifically designed for Robolectric tests where the main looper + * must be manually pumped to allow Firebase task callbacks to execute. Unlike the standard + * `await()` from kotlinx-coroutines-play-services, this function uses a blocking approach + * that works correctly with Robolectric's shadow looper. + * + * **Why this is needed:** + * - Firebase's standard `await()` from kotlinx-coroutines-play-services can hang in Robolectric + * because the looper doesn't auto-advance + * - This function manually pumps the looper in a blocking manner + * - It properly handles task cancellation and exceptions + * + * **Usage:** + * ```kotlin + * @Test + * fun myTest() = runBlocking { + * val result = auth.signInWithEmailAndPassword(email, password).awaitWithLooper() + * // result is now available + * } + * ``` + * + * @return The result of the task if successful + * @throws Exception if the task fails or is cancelled + */ +fun Task.awaitWithLooper(timeoutMs: Long = 10000): T { + val startTime = System.currentTimeMillis() + + // Pump the looper until the task completes + while (!isComplete) { + shadowOf(Looper.getMainLooper()).idle() + Thread.sleep(5) + + // Check for timeout + if (System.currentTimeMillis() - startTime > timeoutMs) { + throw Exception("Task timed out after ${timeoutMs}ms") + } + } + + // Task is complete, return result or throw exception + return when { + isCanceled -> throw Exception("Task was cancelled") + isSuccessful -> result + else -> throw (exception ?: Exception("Unknown Firebase Task failure")) + } +} diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthTextFieldTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthTextFieldTest.kt index 101317483..8c22818c3 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthTextFieldTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/components/AuthTextFieldTest.kt @@ -310,7 +310,7 @@ class AuthTextFieldTest { composeTestRule.waitForIdle() composeTestRule - .onNodeWithText(stringProvider.passwordTooShort.format(8)) + .onNodeWithText(stringProvider.passwordTooShort(8)) .assertIsDisplayed() composeTestRule @@ -340,7 +340,7 @@ class AuthTextFieldTest { composeTestRule.waitForIdle() composeTestRule - .onNodeWithText(stringProvider.passwordTooShort.format(8)) + .onNodeWithText(stringProvider.passwordTooShort(8)) .assertIsNotDisplayed() composeTestRule .onNodeWithText(stringProvider.passwordMissingLowercase) diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt new file mode 100644 index 000000000..996e1bee7 --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt @@ -0,0 +1,625 @@ +package com.firebase.ui.auth.compose.ui.screens + +import android.content.Context +import android.os.Looper +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performScrollTo +import androidx.compose.ui.test.performTextInput +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.compose.AuthException +import com.firebase.ui.auth.compose.AuthState +import com.firebase.ui.auth.compose.FirebaseAuthUI +import com.firebase.ui.auth.compose.configuration.AuthUIConfiguration +import com.firebase.ui.auth.compose.configuration.PasswordRule +import com.firebase.ui.auth.compose.configuration.authUIConfiguration +import com.firebase.ui.auth.compose.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.compose.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.compose.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.compose.testutil.awaitWithLooper +import com.google.common.truth.Truth.assertThat +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.auth.AuthResult +import com.google.firebase.auth.FirebaseUser +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config +import java.net.HttpURLConnection +import java.net.URL + +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class EmailAuthScreenTest { + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var applicationContext: Context + + private lateinit var stringProvider: AuthUIStringProvider + + lateinit var authUI: FirebaseAuthUI + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + + applicationContext = ApplicationProvider.getApplicationContext() + + stringProvider = DefaultAuthUIStringProvider(applicationContext) + + // Clear any existing Firebase apps + FirebaseApp.getApps(applicationContext).forEach { app -> + app.delete() + } + + // Initialize default FirebaseApp + FirebaseApp.initializeApp( + applicationContext, + FirebaseOptions.Builder() + .setApiKey("fake-api-key") + .setApplicationId("fake-app-id") + .setProjectId("fake-project-id") + .build() + ) + + authUI = FirebaseAuthUI.getInstance() + authUI.auth.useEmulator("127.0.0.1", 9099) + + // Clear emulator data + clearEmulatorData() + } + + @After + fun tearDown() { + // Clean up after each test to prevent test pollution + FirebaseAuthUI.clearInstanceCache() + } + + @Test + fun `initial EmailAuthMode is SignIn`() { + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + } + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + } + + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + } + + @Test + fun `unverified email sign-in emits RequiresEmailVerification auth state`() { + val email = "test@example.com" + val password = "test123" + + // Setup: Create a fresh unverified user + ensureFreshUser(email, password) + + // Sign out + authUI.auth.signOut() + shadowOf(Looper.getMainLooper()).idle() + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + composeTestRule.onNodeWithText(stringProvider.emailHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(email) + composeTestRule.onNodeWithText(stringProvider.passwordHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(password) + composeTestRule.onNodeWithText(stringProvider.signInDefault.uppercase()) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for auth state to transition to RequiresEmailVerification + println("TEST: Waiting for auth state change... Current state: $currentAuthState") + composeTestRule.waitUntil { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState") + currentAuthState is AuthState.RequiresEmailVerification + } + + // Ensure final recomposition is complete before assertions + shadowOf(Looper.getMainLooper()).idle() + + // Verify the auth state and user properties + println("TEST: Verifying final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.RequiresEmailVerification::class.java) + assertThat(authUI.auth.currentUser).isNotNull() + assertThat(authUI.auth.currentUser!!.isEmailVerified).isEqualTo(false) + assertThat(authUI.auth.currentUser!!.email).isEqualTo(email) + } + + @Test + fun `verified email sign-in emits Success auth state`() { + val email = "test@example.com" + val password = "test123" + + // Setup: Create a fresh unverified user + val user = ensureFreshUser(email, password) + + requireNotNull(user) { "Failed to create user" } + + // Verify email using Firebase Auth Emulator OOB codes flow + verifyEmailInEmulator(user = user) + + // Sign out + authUI.auth.signOut() + shadowOf(Looper.getMainLooper()).idle() + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + composeTestRule.onNodeWithText(stringProvider.emailHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(email) + composeTestRule.onNodeWithText(stringProvider.passwordHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(password) + composeTestRule.onNodeWithText(stringProvider.signInDefault.uppercase()) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for auth state to transition to Success (since email is verified) + println("TEST: Waiting for auth state change... Current state: $currentAuthState") + composeTestRule.waitUntil(timeoutMillis = 5000) { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState") + currentAuthState is AuthState.Success + } + + // Ensure final recomposition is complete before assertions + shadowOf(Looper.getMainLooper()).idle() + + // Verify the auth state and user properties + println("TEST: Verifying final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.Success::class.java) + assertThat(authUI.auth.currentUser).isNotNull() + assertThat(authUI.auth.currentUser!!.isEmailVerified).isEqualTo(true) + assertThat(authUI.auth.currentUser!!.email).isEqualTo(email) + } + + @Test + fun `new email sign-up emits RequiresEmailVerification auth state`() { + val name = "Test User" + val email = "test@example.com" + val password = "Test@123" + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = listOf( + PasswordRule.MinimumLength(8), + PasswordRule.RequireLowercase, + PasswordRule.RequireUppercase + ) + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase()) + .assertIsDisplayed() + .performClick() + composeTestRule.onNodeWithText(stringProvider.signupPageTitle) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.emailHint) + .assertIsDisplayed() + .performTextInput(email) + composeTestRule.onNodeWithText(stringProvider.nameHint) + .assertIsDisplayed() + .performTextInput(name) + composeTestRule.onNodeWithText(stringProvider.passwordHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(password) + composeTestRule.onNodeWithText(stringProvider.confirmPasswordHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(password) + composeTestRule.onNodeWithText(stringProvider.signupPageTitle.uppercase()) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for auth state to transition to RequiresEmailVerification + println("TEST: Waiting for auth state change... Current state: $currentAuthState") + composeTestRule.waitUntil { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState") + currentAuthState is AuthState.RequiresEmailVerification + } + + // Ensure final recomposition is complete before assertions + shadowOf(Looper.getMainLooper()).idle() + + // Verify the auth state and user properties + println("TEST: Verifying final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.RequiresEmailVerification::class.java) + assertThat(authUI.auth.currentUser).isNotNull() + assertThat(authUI.auth.currentUser!!.isEmailVerified).isEqualTo(false) + assertThat(authUI.auth.currentUser!!.email).isEqualTo(email) + } + + @Test + fun `trouble signing in emits PasswordResetLinkSent auth state and shows dialog`() { + val email = "test@example.com" + val password = "test123" + + // Setup: Create a fresh user + ensureFreshUser(email, password) + + // Sign out + authUI.auth.signOut() + shadowOf(Looper.getMainLooper()).idle() + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.troubleSigningIn) + .assertIsDisplayed() + .performClick() + composeTestRule.onNodeWithText(stringProvider.recoverPasswordPageTitle) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.emailHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(email) + composeTestRule.onNodeWithText(stringProvider.sendButtonText.uppercase()) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for auth state to transition to PasswordResetLinkSent + println("TEST: Waiting for auth state change... Current state: $currentAuthState") + composeTestRule.waitUntil { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState") + currentAuthState is AuthState.PasswordResetLinkSent + } + + // Ensure final recomposition is complete before assertions + shadowOf(Looper.getMainLooper()).idle() + + // Verify the auth state and user properties + println("TEST: Verifying final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.PasswordResetLinkSent::class.java) + assertThat(authUI.auth.currentUser).isNull() + composeTestRule.onNodeWithText(stringProvider.recoverPasswordLinkSentDialogTitle) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.dismissAction) + .assertIsDisplayed() + .performClick() + composeTestRule.waitForIdle() + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + } + + @Composable + private fun FirebaseAuthScreen( + configuration: AuthUIConfiguration, + onSuccess: ((AuthResult) -> Unit) = {}, + onError: ((AuthException) -> Unit) = {}, + onCancel: (() -> Unit) = {} + ) { + EmailAuthScreen( + context = applicationContext, + configuration = configuration, + authUI = authUI, + onSuccess = onSuccess, + onError = onError, + onCancel = onCancel, + ) { state -> + when (state.mode) { + EmailAuthMode.SignIn -> { + SignInUI( + configuration = configuration, + email = state.email, + isLoading = state.isLoading, + emailSignInLinkSent = state.emailSignInLinkSent, + password = state.password, + onEmailChange = state.onEmailChange, + onPasswordChange = state.onPasswordChange, + onSignInClick = state.onSignInClick, + onGoToSignUp = state.onGoToSignUp, + onGoToResetPassword = state.onGoToResetPassword, + ) + } + + EmailAuthMode.SignUp -> { + SignUpUI( + configuration = configuration, + isLoading = state.isLoading, + displayName = state.displayName, + email = state.email, + password = state.password, + confirmPassword = state.confirmPassword, + onDisplayNameChange = state.onDisplayNameChange, + onEmailChange = state.onEmailChange, + onPasswordChange = state.onPasswordChange, + onConfirmPasswordChange = state.onConfirmPasswordChange, + onSignUpClick = state.onSignUpClick, + onGoToSignIn = state.onGoToSignIn, + ) + } + + EmailAuthMode.ResetPassword -> { + ResetPasswordUI( + configuration = configuration, + isLoading = state.isLoading, + email = state.email, + resetLinkSent = state.resetLinkSent, + onEmailChange = state.onEmailChange, + onSendResetLink = state.onSendResetLinkClick, + onGoToSignIn = state.onGoToSignIn + ) + } + } + } + } + + /** + * Clears all data from the Firebase Auth Emulator. + * + * This function calls the emulator's clear data endpoint to remove all accounts, + * OOB codes, and other authentication data. This ensures test isolation by providing + * a clean slate for each test. + * + * @param emulatorHost The emulator host (default: "127.0.0.1") + * @param emulatorPort The emulator port (default: 9099) + * + * @throws Exception if the clear operation fails + */ + private fun clearEmulatorData( + projectId: String = "fake-project-id", + emulatorHost: String = "127.0.0.1", + emulatorPort: Int = 9099 + ) { + val clearUrl = + URL("http://$emulatorHost:$emulatorPort/emulator/v1/projects/$projectId/accounts") + val clearConnection = clearUrl.openConnection() as HttpURLConnection + + try { + clearConnection.requestMethod = "DELETE" + clearConnection.connectTimeout = 5000 + clearConnection.readTimeout = 5000 + + val responseCode = clearConnection.responseCode + if (responseCode !in 200..299) { + println("WARNING: Failed to clear emulator data: HTTP $responseCode") + } else { + println("TEST: Cleared emulator data") + } + } catch (e: Exception) { + println("WARNING: Exception while clearing emulator data: ${e.message}") + } finally { + clearConnection.disconnect() + } + } + + /** + * Ensures a fresh user exists in the Firebase emulator with the given credentials. + * If a user already exists, they will be deleted first. + * The user will be signed out after creation, leaving an unverified account ready for testing. + * + * This function uses coroutines and automatically handles Robolectric's main looper. + */ + private fun ensureFreshUser(email: String, password: String): FirebaseUser? { + println("TEST: Ensuring fresh user for $email") + // Try to sign in - if successful, user exists and should be deleted + try { + authUI.auth.signInWithEmailAndPassword(email, password).awaitWithLooper() + .also { result -> + println("TEST: User exists (${result.user?.uid}), deleting...") + // User exists, delete them + result.user?.delete()?.awaitWithLooper() + println("TEST: User deleted") + } + } catch (_: Exception) { + // User doesn't exist - this is expected + } + + // Create fresh user + return authUI.auth.createUserWithEmailAndPassword(email, password).awaitWithLooper() + .user + } + + /** + * Verifies a user's email in the Firebase Auth Emulator by simulating the complete + * email verification flow. + * + * This function: + * 1. Sends a verification email using sendEmailVerification() + * 2. Retrieves the OOB (out-of-band) code from the emulator's OOB codes endpoint + * 3. Applies the action code to complete email verification + * + * This approach works with the Firebase Auth Emulator's documented API and simulates + * the real email verification flow that would occur in production. + * + * @param user The FirebaseUser whose email should be verified + * @param emulatorHost The emulator host (default: "127.0.0.1") + * @param emulatorPort The emulator port (default: 9099) + * + * @throws Exception if the verification flow fails + */ + private fun verifyEmailInEmulator( + user: FirebaseUser, + emulatorHost: String = "127.0.0.1", + emulatorPort: Int = 9099 + ) { + println("TEST: Starting email verification for user ${user.uid}") + + // Step 1: Send verification email to generate an OOB code + user.sendEmailVerification().awaitWithLooper() + println("TEST: Sent email verification request") + + // Give the emulator time to process and store the OOB code + shadowOf(Looper.getMainLooper()).idle() + Thread.sleep(100) + + // Step 2: Retrieve OOB codes from the emulator + val projectId = authUI.app.options.projectId + ?: throw IllegalStateException("Project ID is required") + println("TEST: Using project ID: $projectId") + + val oobUrl = + URL("http://$emulatorHost:$emulatorPort/emulator/v1/projects/$projectId/oobCodes") + val oobConnection = oobUrl.openConnection() as HttpURLConnection + + val oobCodesJson = try { + oobConnection.requestMethod = "GET" + oobConnection.connectTimeout = 5000 + oobConnection.readTimeout = 5000 + + val responseCode = oobConnection.responseCode + if (responseCode != 200) { + throw Exception("Failed to get OOB codes: HTTP $responseCode") + } + + oobConnection.inputStream.bufferedReader().readText() + } finally { + oobConnection.disconnect() + } + + println("TEST: OOB codes response: $oobCodesJson") + + // Step 3: Parse the response to find the VERIFY_EMAIL code for this user's email + // Response format: {"oobCodes": [{"email": "...", "oobCode": "...", "oobLink": "...", "requestType": "..."}]} + // We need to find an entry with both matching email AND requestType: "VERIFY_EMAIL" + val verifyEmailPattern = + """"email":"${user.email}","requestType":"VERIFY_EMAIL","oobCode":"([^"]+)"""".toRegex() + + val oobCodeMatch = verifyEmailPattern.find(oobCodesJson) + val oobCode = oobCodeMatch?.groupValues?.get(1) + ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: ${user.email}") + + println("TEST: Found OOB code: $oobCode") + + // Step 4: Apply the action code to verify the email + authUI.auth.applyActionCode(oobCode).awaitWithLooper() + println("TEST: Applied action code") + + // Step 5: Reload the user to refresh their email verification status + authUI.auth.currentUser?.reload()?.awaitWithLooper() + shadowOf(Looper.getMainLooper()).idle() + + println("TEST: Email verified successfully for user ${user.uid}") + println("TEST: User isEmailVerified: ${authUI.auth.currentUser?.isEmailVerified}") + } + +} \ No newline at end of file diff --git a/composeapp/src/main/AndroidManifest.xml b/composeapp/src/main/AndroidManifest.xml index 99b6bad7d..54498775b 100644 --- a/composeapp/src/main/AndroidManifest.xml +++ b/composeapp/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@style/Theme.FirebaseUIAndroid"> + android:theme="@style/Theme.FirebaseUIAndroid" + android:usesCleartextTraffic="true"> Date: Sun, 12 Oct 2025 10:36:16 +0100 Subject: [PATCH 2/7] test: email link sign in dialog --- .../compose/ui/screens/EmailAuthScreenTest.kt | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt index 996e1bee7..2fd82fcb4 100644 --- a/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick @@ -27,6 +28,7 @@ import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseOptions import com.google.firebase.auth.AuthResult import com.google.firebase.auth.FirebaseUser +import com.google.firebase.auth.actionCodeSettings import org.junit.After import org.junit.Before import org.junit.Rule @@ -85,6 +87,9 @@ class EmailAuthScreenTest { fun tearDown() { // Clean up after each test to prevent test pollution FirebaseAuthUI.clearInstanceCache() + + // Clear emulator data + clearEmulatorData() } @Test @@ -402,10 +407,102 @@ class EmailAuthScreenTest { assertThat(authUI.auth.currentUser).isNull() composeTestRule.onNodeWithText(stringProvider.recoverPasswordLinkSentDialogTitle) .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.recoverPasswordLinkSentDialogBody(email)) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.dismissAction) + .assertIsDisplayed() + .performClick() + composeTestRule.waitForIdle() + composeTestRule.onNodeWithText(stringProvider.recoverPasswordLinkSentDialogTitle) + .assertIsNotDisplayed() + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + } + + @Test + fun `email link sign in emits EmailSignInLinkSent auth state and shows dialog`() { + val email = "test@example.com" + val password = "test123" + + // Setup: Create a fresh user + ensureFreshUser(email, password) + + // Sign out + authUI.auth.signOut() + shadowOf(Looper.getMainLooper()).idle() + + val configuration = authUIConfiguration { + context = applicationContext + providers { + provider( + AuthProvider.Email( + isEmailLinkSignInEnabled = true, + isEmailLinkForceSameDeviceEnabled = true, + emailLinkActionCodeSettings = actionCodeSettings { + // The continue URL - where to redirect after email link is clicked + url = "https://fake-project-id.firebaseapp.com" + handleCodeInApp = true + setAndroidPackageName( + "fake.project.id", + true, + null + ) + }, + passwordValidationRules = emptyList() + ) + ) + } + } + + // Track auth state changes + var currentAuthState: AuthState = AuthState.Idle + + composeTestRule.setContent { + FirebaseAuthScreen(configuration = configuration) + val authState by authUI.authStateFlow().collectAsState(AuthState.Idle) + currentAuthState = authState + } + + composeTestRule.onNodeWithText(stringProvider.signInDefault) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.emailHint) + .performScrollTo() + .assertIsDisplayed() + .performTextInput(email) + composeTestRule.onNodeWithText(stringProvider.signInDefault.uppercase()) + .performScrollTo() + .assertIsDisplayed() + .performClick() + + println("TEST: Pumping looper after click...") + shadowOf(Looper.getMainLooper()).idle() + + // Wait for auth state to transition to EmailSignInLinkSent + println("TEST: Waiting for auth state change... Current state: $currentAuthState") + composeTestRule.waitUntil { + shadowOf(Looper.getMainLooper()).idle() + println("TEST: Auth state during wait: $currentAuthState") + currentAuthState is AuthState.EmailSignInLinkSent + } + + // Ensure final recomposition is complete before assertions + shadowOf(Looper.getMainLooper()).idle() + + // Verify the auth state and user properties + println("TEST: Verifying final auth state: $currentAuthState") + assertThat(currentAuthState) + .isInstanceOf(AuthState.EmailSignInLinkSent::class.java) + assertThat(authUI.auth.currentUser).isNull() + composeTestRule.onNodeWithText(stringProvider.emailSignInLinkSentDialogTitle) + .assertIsDisplayed() + composeTestRule.onNodeWithText(stringProvider.emailSignInLinkSentDialogBody(email)) + .assertIsDisplayed() composeTestRule.onNodeWithText(stringProvider.dismissAction) .assertIsDisplayed() .performClick() composeTestRule.waitForIdle() + composeTestRule.onNodeWithText(stringProvider.emailSignInLinkSentDialogTitle) + .assertIsNotDisplayed() composeTestRule.onNodeWithText(stringProvider.signInDefault) .assertIsDisplayed() } From e98d79d72961dae63882d9deccbb23a9cecd5947 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Sun, 12 Oct 2025 12:41:12 +0100 Subject: [PATCH 3/7] refactor: move e2e tests to separate module --- .github/workflows/android.yml | 20 +---- .github/workflows/e2e_test.yml | 52 ++++++++++++ .../ui/auth/compose/FirebaseAuthUI.kt | 2 +- {library => e2eTest}/.firebaserc | 0 e2eTest/build.gradle.kts | 83 +++++++++++++++++++ {library => e2eTest}/firebase.json | 0 e2eTest/src/main/AndroidManifest.xml | 3 + e2eTest/src/test/AndroidManifest.xml | 24 ++++++ .../auth/compose/testutil/TaskExtensions.kt | 0 .../compose/ui/screens/EmailAuthScreenTest.kt | 0 .../src/test/resources/robolectric.properties | 1 + scripts/build.sh | 4 +- scripts/start-firebase-emulator.sh | 4 +- settings.gradle | 1 + 14 files changed, 170 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/e2e_test.yml rename {library => e2eTest}/.firebaserc (100%) create mode 100644 e2eTest/build.gradle.kts rename {library => e2eTest}/firebase.json (100%) create mode 100644 e2eTest/src/main/AndroidManifest.xml create mode 100644 e2eTest/src/test/AndroidManifest.xml rename {auth => e2eTest}/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt (100%) rename {auth => e2eTest}/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt (100%) create mode 100644 e2eTest/src/test/resources/robolectric.properties diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 10ed10c88..0e948843f 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -19,31 +19,13 @@ jobs: ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - - name: Firebase Emulator Cache - uses: actions/cache@v4 - with: - path: ~/.cache/firebase/emulators - key: firebase-emulators-v3-${{ runner.os }} - - - name: Install Node.js 20 - uses: actions/setup-node@v4 - with: - node-version: '20' - - name: Set up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' - - name: Install Tools - run: | - npm i -g firebase-tools - - - name: Start Firebase Auth Emulator - run: ./scripts/start-firebase-emulator.sh - - - name: Build with Gradle + - name: Build and Test run: ./scripts/build.sh - name: Print Logs diff --git a/.github/workflows/e2e_test.yml b/.github/workflows/e2e_test.yml new file mode 100644 index 000000000..ce4bd9e02 --- /dev/null +++ b/.github/workflows/e2e_test.yml @@ -0,0 +1,52 @@ +name: E2E Tests (Firebase Emulator) + +on: + - pull_request + - push + +jobs: + e2e-tests: + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + + - name: Firebase Emulator Cache + uses: actions/cache@v4 + with: + path: ~/.cache/firebase/emulators + key: firebase-emulators-v3-${{ runner.os }} + + - name: Install Node.js 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Install Firebase Tools + run: | + npm i -g firebase-tools + + - name: Start Firebase Auth Emulator + run: ./scripts/start-firebase-emulator.sh + + - name: Run E2E Tests + run: | + ./gradlew e2eTest + + - name: Print Logs + if: failure() + run: ./scripts/print_build_logs.sh diff --git a/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt b/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt index 1a11ee2b4..cf6b54b50 100644 --- a/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/compose/FirebaseAuthUI.kt @@ -452,7 +452,7 @@ class FirebaseAuthUI private constructor( */ @JvmStatic @RestrictTo(RestrictTo.Scope.TESTS) - internal fun clearInstanceCache() { + fun clearInstanceCache() { instanceCache.clear() } diff --git a/library/.firebaserc b/e2eTest/.firebaserc similarity index 100% rename from library/.firebaserc rename to e2eTest/.firebaserc diff --git a/e2eTest/build.gradle.kts b/e2eTest/build.gradle.kts new file mode 100644 index 000000000..b54322dc2 --- /dev/null +++ b/e2eTest/build.gradle.kts @@ -0,0 +1,83 @@ +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.plugin.compose") version Config.kotlinVersion +} + +android { + compileSdk = Config.SdkVersions.compile + namespace = "com.firebase.ui.auth.e2etest" + + defaultConfig { + minSdk = Config.SdkVersions.min + targetSdk = Config.SdkVersions.target + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + compose = true + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +dependencies { + // Depend on the auth module to test it + implementation(project(":auth")) + + // Compose dependencies + implementation(platform(Config.Libs.Androidx.Compose.bom)) + implementation(Config.Libs.Androidx.Compose.ui) + implementation(Config.Libs.Androidx.Compose.material3) + + // Firebase dependencies + implementation(platform(Config.Libs.Firebase.bom)) + implementation(Config.Libs.Firebase.auth) + + // Test dependencies + testImplementation(Config.Libs.Test.junit) + testImplementation(Config.Libs.Test.truth) + testImplementation(Config.Libs.Test.core) + testImplementation(Config.Libs.Test.robolectric) + testImplementation(Config.Libs.Test.kotlinReflect) + testImplementation(Config.Libs.Test.mockitoCore) + testImplementation(Config.Libs.Test.mockitoInline) + testImplementation(Config.Libs.Test.mockitoKotlin) + testImplementation(Config.Libs.Androidx.credentials) + testImplementation(Config.Libs.Test.composeUiTestJunit4) +} + +val mockitoAgent by configurations.creating + +dependencies { + mockitoAgent(Config.Libs.Test.mockitoCore) { + isTransitive = false + } +} + +tasks.withType().configureEach { + jvmArgs("-javaagent:${mockitoAgent.asPath}") +} + +tasks.register("e2eTest") { + description = "Runs e2e emulator tests" + group = "verification" + + val debug = tasks.named("testDebugUnitTest").get() + testClassesDirs = debug.testClassesDirs + classpath = debug.classpath + + doNotTrackState("Always run e2e emulator tests to mirror Android Studio") +} diff --git a/library/firebase.json b/e2eTest/firebase.json similarity index 100% rename from library/firebase.json rename to e2eTest/firebase.json diff --git a/e2eTest/src/main/AndroidManifest.xml b/e2eTest/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9a40236b9 --- /dev/null +++ b/e2eTest/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/e2eTest/src/test/AndroidManifest.xml b/e2eTest/src/test/AndroidManifest.xml new file mode 100644 index 000000000..512ab1372 --- /dev/null +++ b/e2eTest/src/test/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt similarity index 100% rename from auth/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt rename to e2eTest/src/test/java/com/firebase/ui/auth/compose/testutil/TaskExtensions.kt diff --git a/auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt similarity index 100% rename from auth/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt rename to e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt diff --git a/e2eTest/src/test/resources/robolectric.properties b/e2eTest/src/test/resources/robolectric.properties new file mode 100644 index 000000000..932b01b9e --- /dev/null +++ b/e2eTest/src/test/resources/robolectric.properties @@ -0,0 +1 @@ +sdk=28 diff --git a/scripts/build.sh b/scripts/build.sh index 63e5eaa7a..d4c36e501 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -10,5 +10,5 @@ cp library/google-services.json proguard-tests/google-services.json ./gradlew $GRADLE_ARGS assembleDebug # TODO(thatfiredev): re-enable before release # ./gradlew $GRADLE_ARGS proguard-tests:build -./gradlew $GRADLE_ARGS checkstyle -./gradlew $GRADLE_ARGS testDebugUnitTest +./gradlew $GRADLE_ARGS checkstyle +./gradlew $GRADLE_ARGS testDebugUnitTest -x :e2eTest:test diff --git a/scripts/start-firebase-emulator.sh b/scripts/start-firebase-emulator.sh index 94b90f820..99ec3bc8c 100755 --- a/scripts/start-firebase-emulator.sh +++ b/scripts/start-firebase-emulator.sh @@ -15,10 +15,10 @@ if ! [ -x "$(command -v npm)" ]; then fi # Extract project ID from .firebaserc -export FIREBASE_PROJECT_ID=$(cat library/.firebaserc | jq -r '.projects.default') +export FIREBASE_PROJECT_ID=$(cat e2eTest/.firebaserc | jq -r '.projects.default') # Extract auth port from firebase.json -AUTH_PORT=$(cat library/firebase.json | jq -r '.emulators.auth.port') +AUTH_PORT=$(cat e2eTest/firebase.json | jq -r '.emulators.auth.port') export FIREBASE_AUTH_EMULATOR_URL="http://127.0.0.1:${AUTH_PORT}" # Starts firebase auth emulator only diff --git a/settings.gradle b/settings.gradle index 275ad6d0a..fc63ab6aa 100644 --- a/settings.gradle +++ b/settings.gradle @@ -26,6 +26,7 @@ include( ":composeapp", ":library", ":auth", + ":e2eTest", ":common", ":database", ":firestore", From e2a65b16b8990ab2b0c644d5bf9a4e5f97865e8b Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Sun, 12 Oct 2025 12:55:52 +0100 Subject: [PATCH 4/7] fix CI test errors --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index d4c36e501..946447a36 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -11,4 +11,4 @@ cp library/google-services.json proguard-tests/google-services.json # TODO(thatfiredev): re-enable before release # ./gradlew $GRADLE_ARGS proguard-tests:build ./gradlew $GRADLE_ARGS checkstyle -./gradlew $GRADLE_ARGS testDebugUnitTest -x :e2eTest:test +./gradlew $GRADLE_ARGS testDebugUnitTest -x :e2eTest:e2eTest From 3d63799f9333524d1e75ac67050a367c19a1754f Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Sun, 12 Oct 2025 13:41:09 +0100 Subject: [PATCH 5/7] fix CI test errors --- scripts/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index 946447a36..6d1a1e8f7 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -11,4 +11,4 @@ cp library/google-services.json proguard-tests/google-services.json # TODO(thatfiredev): re-enable before release # ./gradlew $GRADLE_ARGS proguard-tests:build ./gradlew $GRADLE_ARGS checkstyle -./gradlew $GRADLE_ARGS testDebugUnitTest -x :e2eTest:e2eTest +./gradlew $GRADLE_ARGS testDebugUnitTest -x :e2eTest:testDebugUnitTest From d2becd65806ef1d46cb931c3c1f6d58769ef1c6a Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Sun, 12 Oct 2025 15:41:38 +0100 Subject: [PATCH 6/7] chore: improve test utils --- .../compose/ui/screens/EmailAuthScreenTest.kt | 188 +++++++++++------- 1 file changed, 111 insertions(+), 77 deletions(-) diff --git a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt index 2fd82fcb4..57e0e125e 100644 --- a/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt +++ b/e2eTest/src/test/java/com/firebase/ui/auth/compose/ui/screens/EmailAuthScreenTest.kt @@ -29,6 +29,8 @@ import com.google.firebase.FirebaseOptions import com.google.firebase.auth.AuthResult import com.google.firebase.auth.FirebaseUser import com.google.firebase.auth.actionCodeSettings +import org.json.JSONArray +import org.json.JSONObject import org.junit.After import org.junit.Before import org.junit.Rule @@ -41,6 +43,8 @@ import org.robolectric.annotation.Config import java.net.HttpURLConnection import java.net.URL +private const val AUTH_STATE_WAIT_TIMEOUT_MS = 5_000L + @Config(sdk = [34]) @RunWith(RobolectricTestRunner::class) class EmailAuthScreenTest { @@ -52,6 +56,7 @@ class EmailAuthScreenTest { private lateinit var stringProvider: AuthUIStringProvider lateinit var authUI: FirebaseAuthUI + private lateinit var emulatorApi: EmulatorAuthApi @Before fun setUp() { @@ -67,7 +72,7 @@ class EmailAuthScreenTest { } // Initialize default FirebaseApp - FirebaseApp.initializeApp( + val firebaseApp = FirebaseApp.initializeApp( applicationContext, FirebaseOptions.Builder() .setApiKey("fake-api-key") @@ -79,6 +84,13 @@ class EmailAuthScreenTest { authUI = FirebaseAuthUI.getInstance() authUI.auth.useEmulator("127.0.0.1", 9099) + emulatorApi = EmulatorAuthApi( + projectId = firebaseApp.options.projectId + ?: throw IllegalStateException("Project ID is required for emulator interactions"), + emulatorHost = "127.0.0.1", + emulatorPort = 9099 + ) + // Clear emulator data clearEmulatorData() } @@ -165,7 +177,7 @@ class EmailAuthScreenTest { // Wait for auth state to transition to RequiresEmailVerification println("TEST: Waiting for auth state change... Current state: $currentAuthState") - composeTestRule.waitUntil { + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { shadowOf(Looper.getMainLooper()).idle() println("TEST: Auth state during wait: $currentAuthState") currentAuthState is AuthState.RequiresEmailVerification @@ -239,7 +251,7 @@ class EmailAuthScreenTest { // Wait for auth state to transition to Success (since email is verified) println("TEST: Waiting for auth state change... Current state: $currentAuthState") - composeTestRule.waitUntil(timeoutMillis = 5000) { + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { shadowOf(Looper.getMainLooper()).idle() println("TEST: Auth state during wait: $currentAuthState") currentAuthState is AuthState.Success @@ -319,7 +331,7 @@ class EmailAuthScreenTest { // Wait for auth state to transition to RequiresEmailVerification println("TEST: Waiting for auth state change... Current state: $currentAuthState") - composeTestRule.waitUntil { + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { shadowOf(Looper.getMainLooper()).idle() println("TEST: Auth state during wait: $currentAuthState") currentAuthState is AuthState.RequiresEmailVerification @@ -391,7 +403,7 @@ class EmailAuthScreenTest { // Wait for auth state to transition to PasswordResetLinkSent println("TEST: Waiting for auth state change... Current state: $currentAuthState") - composeTestRule.waitUntil { + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { shadowOf(Looper.getMainLooper()).idle() println("TEST: Auth state during wait: $currentAuthState") currentAuthState is AuthState.PasswordResetLinkSent @@ -479,7 +491,7 @@ class EmailAuthScreenTest { // Wait for auth state to transition to EmailSignInLinkSent println("TEST: Waiting for auth state change... Current state: $currentAuthState") - composeTestRule.waitUntil { + composeTestRule.waitUntil(timeoutMillis = AUTH_STATE_WAIT_TIMEOUT_MS) { shadowOf(Looper.getMainLooper()).idle() println("TEST: Auth state during wait: $currentAuthState") currentAuthState is AuthState.EmailSignInLinkSent @@ -576,36 +588,14 @@ class EmailAuthScreenTest { * This function calls the emulator's clear data endpoint to remove all accounts, * OOB codes, and other authentication data. This ensures test isolation by providing * a clean slate for each test. - * - * @param emulatorHost The emulator host (default: "127.0.0.1") - * @param emulatorPort The emulator port (default: 9099) - * - * @throws Exception if the clear operation fails */ - private fun clearEmulatorData( - projectId: String = "fake-project-id", - emulatorHost: String = "127.0.0.1", - emulatorPort: Int = 9099 - ) { - val clearUrl = - URL("http://$emulatorHost:$emulatorPort/emulator/v1/projects/$projectId/accounts") - val clearConnection = clearUrl.openConnection() as HttpURLConnection - - try { - clearConnection.requestMethod = "DELETE" - clearConnection.connectTimeout = 5000 - clearConnection.readTimeout = 5000 - - val responseCode = clearConnection.responseCode - if (responseCode !in 200..299) { - println("WARNING: Failed to clear emulator data: HTTP $responseCode") - } else { - println("TEST: Cleared emulator data") + private fun clearEmulatorData() { + if (::emulatorApi.isInitialized) { + try { + emulatorApi.clearAccounts() + } catch (e: Exception) { + println("WARNING: Exception while clearing emulator data: ${e.message}") } - } catch (e: Exception) { - println("WARNING: Exception while clearing emulator data: ${e.message}") - } finally { - clearConnection.disconnect() } } @@ -649,16 +639,9 @@ class EmailAuthScreenTest { * the real email verification flow that would occur in production. * * @param user The FirebaseUser whose email should be verified - * @param emulatorHost The emulator host (default: "127.0.0.1") - * @param emulatorPort The emulator port (default: 9099) - * * @throws Exception if the verification flow fails */ - private fun verifyEmailInEmulator( - user: FirebaseUser, - emulatorHost: String = "127.0.0.1", - emulatorPort: Int = 9099 - ) { + private fun verifyEmailInEmulator(user: FirebaseUser) { println("TEST: Starting email verification for user ${user.uid}") // Step 1: Send verification email to generate an OOB code @@ -669,54 +652,105 @@ class EmailAuthScreenTest { shadowOf(Looper.getMainLooper()).idle() Thread.sleep(100) - // Step 2: Retrieve OOB codes from the emulator - val projectId = authUI.app.options.projectId - ?: throw IllegalStateException("Project ID is required") - println("TEST: Using project ID: $projectId") + // Step 2: Retrieve the VERIFY_EMAIL OOB code for this user from the emulator + val email = requireNotNull(user.email) { "User email is required for OOB code lookup" } + val oobCode = emulatorApi.fetchVerifyEmailCode(email) + + println("TEST: Found OOB code: $oobCode") + + // Step 3: Apply the action code to verify the email + authUI.auth.applyActionCode(oobCode).awaitWithLooper() + println("TEST: Applied action code") + + // Step 4: Reload the user to refresh their email verification status + authUI.auth.currentUser?.reload()?.awaitWithLooper() + shadowOf(Looper.getMainLooper()).idle() + + println("TEST: Email verified successfully for user ${user.uid}") + println("TEST: User isEmailVerified: ${authUI.auth.currentUser?.isEmailVerified}") + } - val oobUrl = - URL("http://$emulatorHost:$emulatorPort/emulator/v1/projects/$projectId/oobCodes") - val oobConnection = oobUrl.openConnection() as HttpURLConnection +} - val oobCodesJson = try { - oobConnection.requestMethod = "GET" - oobConnection.connectTimeout = 5000 - oobConnection.readTimeout = 5000 +private class EmulatorAuthApi( + private val projectId: String, + emulatorHost: String, + emulatorPort: Int +) { - val responseCode = oobConnection.responseCode - if (responseCode != 200) { + private val httpClient = HttpClient(host = emulatorHost, port = emulatorPort) + + fun clearAccounts() { + httpClient.delete("/emulator/v1/projects/$projectId/accounts") { connection -> + val responseCode = connection.responseCode + if (responseCode !in 200..299) { + println("WARNING: Failed to clear emulator data: HTTP $responseCode") + } else { + println("TEST: Cleared emulator data") + } + } + } + + fun fetchVerifyEmailCode(email: String): String { + val oobCodes = fetchOobCodes() + return (0 until oobCodes.length()) + .asSequence() + .mapNotNull { index -> oobCodes.optJSONObject(index) } + .firstOrNull { json -> + json.optString("email") == email && + json.optString("requestType") == "VERIFY_EMAIL" + } + ?.optString("oobCode") + ?.takeIf { it.isNotBlank() } + ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: $email") + } + + private fun fetchOobCodes(): JSONArray { + val payload = httpClient.get("/emulator/v1/projects/$projectId/oobCodes") { connection -> + val responseCode = connection.responseCode + if (responseCode != HttpURLConnection.HTTP_OK) { throw Exception("Failed to get OOB codes: HTTP $responseCode") } - oobConnection.inputStream.bufferedReader().readText() - } finally { - oobConnection.disconnect() + connection.inputStream.bufferedReader().use { it.readText() } } - println("TEST: OOB codes response: $oobCodesJson") + return JSONObject(payload).optJSONArray("oobCodes") ?: JSONArray() + } +} - // Step 3: Parse the response to find the VERIFY_EMAIL code for this user's email - // Response format: {"oobCodes": [{"email": "...", "oobCode": "...", "oobLink": "...", "requestType": "..."}]} - // We need to find an entry with both matching email AND requestType: "VERIFY_EMAIL" - val verifyEmailPattern = - """"email":"${user.email}","requestType":"VERIFY_EMAIL","oobCode":"([^"]+)"""".toRegex() +private class HttpClient( + private val host: String, + private val port: Int, + private val timeoutMs: Int = DEFAULT_TIMEOUT_MS +) { - val oobCodeMatch = verifyEmailPattern.find(oobCodesJson) - val oobCode = oobCodeMatch?.groupValues?.get(1) - ?: throw Exception("No VERIFY_EMAIL OOB code found for user email: ${user.email}") + companion object { + const val DEFAULT_TIMEOUT_MS = 5_000 + } - println("TEST: Found OOB code: $oobCode") + fun delete(path: String, block: (HttpURLConnection) -> Unit) { + execute(path = path, method = "DELETE", block = block) + } - // Step 4: Apply the action code to verify the email - authUI.auth.applyActionCode(oobCode).awaitWithLooper() - println("TEST: Applied action code") + fun get(path: String, block: (HttpURLConnection) -> T): T { + return execute(path = path, method = "GET", block = block) + } - // Step 5: Reload the user to refresh their email verification status - authUI.auth.currentUser?.reload()?.awaitWithLooper() - shadowOf(Looper.getMainLooper()).idle() + private fun execute(path: String, method: String, block: (HttpURLConnection) -> T): T { + val connection = buildUrl(path).openConnection() as HttpURLConnection + connection.requestMethod = method + connection.connectTimeout = timeoutMs + connection.readTimeout = timeoutMs - println("TEST: Email verified successfully for user ${user.uid}") - println("TEST: User isEmailVerified: ${authUI.auth.currentUser?.isEmailVerified}") + return try { + block(connection) + } finally { + connection.disconnect() + } } -} \ No newline at end of file + private fun buildUrl(path: String): URL { + return URL("http://$host:$port$path") + } +} From dc53d19e257a95b6754afdd910a1bcb180c76643 Mon Sep 17 00:00:00 2001 From: Ademola Fadumo Date: Tue, 14 Oct 2025 09:40:11 +0100 Subject: [PATCH 7/7] chore: add missing string translations --- auth/src/main/res/values-ar/strings.xml | 7 +++++++ auth/src/main/res/values-b+es+419/strings.xml | 7 +++++++ auth/src/main/res/values-bg/strings.xml | 7 +++++++ auth/src/main/res/values-bn/strings.xml | 7 +++++++ auth/src/main/res/values-ca/strings.xml | 7 +++++++ auth/src/main/res/values-cs/strings.xml | 7 +++++++ auth/src/main/res/values-da/strings.xml | 7 +++++++ auth/src/main/res/values-de-rAT/strings.xml | 7 +++++++ auth/src/main/res/values-de-rCH/strings.xml | 7 +++++++ auth/src/main/res/values-de/strings.xml | 7 +++++++ auth/src/main/res/values-el/strings.xml | 7 +++++++ auth/src/main/res/values-en-rAU/strings.xml | 7 +++++++ auth/src/main/res/values-en-rCA/strings.xml | 7 +++++++ auth/src/main/res/values-en-rGB/strings.xml | 7 +++++++ auth/src/main/res/values-es/strings.xml | 7 +++++++ auth/src/main/res/values-fa/strings.xml | 7 +++++++ auth/src/main/res/values-fi/strings.xml | 7 +++++++ auth/src/main/res/values-fil/strings.xml | 7 +++++++ auth/src/main/res/values-fr/strings.xml | 7 +++++++ auth/src/main/res/values-gsw/strings.xml | 7 +++++++ auth/src/main/res/values-gu/strings.xml | 7 +++++++ auth/src/main/res/values-hi/strings.xml | 7 +++++++ auth/src/main/res/values-hr/strings.xml | 7 +++++++ auth/src/main/res/values-hu/strings.xml | 7 +++++++ auth/src/main/res/values-in/strings.xml | 7 +++++++ auth/src/main/res/values-it/strings.xml | 7 +++++++ auth/src/main/res/values-iw/strings.xml | 7 +++++++ auth/src/main/res/values-ja/strings.xml | 7 +++++++ auth/src/main/res/values-kn/strings.xml | 7 +++++++ auth/src/main/res/values-ko/strings.xml | 7 +++++++ auth/src/main/res/values-ln/strings.xml | 7 +++++++ auth/src/main/res/values-lt/strings.xml | 7 +++++++ auth/src/main/res/values-lv/strings.xml | 7 +++++++ auth/src/main/res/values-mo/strings.xml | 7 +++++++ auth/src/main/res/values-mr/strings.xml | 7 +++++++ auth/src/main/res/values-ms/strings.xml | 7 +++++++ auth/src/main/res/values-nb/strings.xml | 7 +++++++ auth/src/main/res/values-nl/strings.xml | 7 +++++++ auth/src/main/res/values-no/strings.xml | 7 +++++++ auth/src/main/res/values-pl/strings.xml | 7 +++++++ auth/src/main/res/values-pt-rBR/strings.xml | 7 +++++++ auth/src/main/res/values-pt-rPT/strings.xml | 7 +++++++ auth/src/main/res/values-pt/strings.xml | 7 +++++++ auth/src/main/res/values-ro/strings.xml | 7 +++++++ auth/src/main/res/values-ru/strings.xml | 7 +++++++ auth/src/main/res/values-sk/strings.xml | 7 +++++++ auth/src/main/res/values-sl/strings.xml | 7 +++++++ auth/src/main/res/values-sr/strings.xml | 7 +++++++ auth/src/main/res/values-sv/strings.xml | 7 +++++++ auth/src/main/res/values-ta/strings.xml | 7 +++++++ auth/src/main/res/values-th/strings.xml | 7 +++++++ auth/src/main/res/values-tl/strings.xml | 7 +++++++ auth/src/main/res/values-tr/strings.xml | 7 +++++++ auth/src/main/res/values-uk/strings.xml | 7 +++++++ auth/src/main/res/values-ur/strings.xml | 7 +++++++ auth/src/main/res/values-vi/strings.xml | 7 +++++++ auth/src/main/res/values-zh-rCN/strings.xml | 7 +++++++ auth/src/main/res/values-zh-rHK/strings.xml | 7 +++++++ auth/src/main/res/values-zh-rTW/strings.xml | 7 +++++++ auth/src/main/res/values-zh/strings.xml | 7 +++++++ 60 files changed, 420 insertions(+) diff --git a/auth/src/main/res/values-ar/strings.xml b/auth/src/main/res/values-ar/strings.xml index 26017c8b6..6d91d65bc 100755 --- a/auth/src/main/res/values-ar/strings.xml +++ b/auth/src/main/res/values-ar/strings.xml @@ -110,4 +110,11 @@ أدخل رمز التحقق احفظ هذه الرموز في مكان آمن. يمكنك استخدامها لتسجيل الدخول إذا فقدت الوصول إلى طريقة المصادقة. + تأكيد كلمة المرور + كلمتا المرور غير متطابقتين + يجب أن تتكون كلمة المرور من %1$d حرفًا على الأقل + يجب أن تحتوي كلمة المرور على حرف كبير واحد على الأقل + يجب أن تحتوي كلمة المرور على حرف صغير واحد على الأقل + يجب أن تحتوي كلمة المرور على رقم واحد على الأقل + يجب أن تحتوي كلمة المرور على حرف خاص واحد على الأقل diff --git a/auth/src/main/res/values-b+es+419/strings.xml b/auth/src/main/res/values-b+es+419/strings.xml index 595813d95..848171d77 100755 --- a/auth/src/main/res/values-b+es+419/strings.xml +++ b/auth/src/main/res/values-b+es+419/strings.xml @@ -110,4 +110,11 @@ Enter your verification code Store these codes in a safe place. You can use them to sign in if you lose access to your authentication method. + Confirmar contraseña + Las contraseñas no coinciden + La contraseña debe tener al menos %1$d caracteres + La contraseña debe contener al menos una letra mayúscula + La contraseña debe contener al menos una letra minúscula + La contraseña debe contener al menos un número + La contraseña debe contener al menos un carácter especial diff --git a/auth/src/main/res/values-bg/strings.xml b/auth/src/main/res/values-bg/strings.xml index 175a7c60a..24057a7e2 100755 --- a/auth/src/main/res/values-bg/strings.xml +++ b/auth/src/main/res/values-bg/strings.xml @@ -110,4 +110,11 @@ Въведете кода си за проверка Съхранявайте тези кодове на сигурно място. Можете да ги използвате за влизане, ако загубите достъп до метода си за удостоверяване. + Потвърдете паролата + Паролите не съвпадат + Паролата трябва да е с дължина поне %1$d знака + Паролата трябва да съдържа поне една главна буква + Паролата трябва да съдържа поне една малка буква + Паролата трябва да съдържа поне една цифра + Паролата трябва да съдържа поне един специален знак diff --git a/auth/src/main/res/values-bn/strings.xml b/auth/src/main/res/values-bn/strings.xml index 282db51a6..bdd03d0a4 100755 --- a/auth/src/main/res/values-bn/strings.xml +++ b/auth/src/main/res/values-bn/strings.xml @@ -111,4 +111,11 @@ আপনার যাচাইকরণ কোড লিখুন এই কোডগুলি একটি নিরাপদ স্থানে সংরক্ষণ করুন। আপনি যদি আপনার প্রমাণীকরণ পদ্ধতিতে অ্যাক্সেস হারান তবে সাইন ইন করতে এগুলি ব্যবহার করতে পারেন। + পাসওয়ার্ড নিশ্চিত করুন + পাসওয়ার্ড মিলছে না + পাসওয়ার্ডে অন্তত %1$dটি অক্ষর থাকতে হবে + পাসওয়ার্ডে অন্তত একটি বড় হাতের অক্ষর থাকতে হবে + পাসওয়ার্ডে অন্তত একটি ছোট হাতের অক্ষর থাকতে হবে + পাসওয়ার্ডে অন্তত একটি সংখ্যা থাকতে হবে + পাসওয়ার্ডে অন্তত একটি বিশেষ অক্ষর থাকতে হবে diff --git a/auth/src/main/res/values-ca/strings.xml b/auth/src/main/res/values-ca/strings.xml index 445d0cdfa..97c4fe56f 100755 --- a/auth/src/main/res/values-ca/strings.xml +++ b/auth/src/main/res/values-ca/strings.xml @@ -111,4 +111,11 @@ Introduïu el vostre codi de verificació Deseu aquests codis en un lloc segur. Podeu utilitzar-los per iniciar sessió si perdeu l\'accés al vostre mètode d\'autenticació. + Confirma la contrasenya + Les contrasenyes no coincideixen + La contrasenya ha de tenir almenys %1$d caràcters + La contrasenya ha de contenir almenys una lletra majúscula + La contrasenya ha de contenir almenys una lletra minúscula + La contrasenya ha de contenir almenys un número + La contrasenya ha de contenir almenys un caràcter especial diff --git a/auth/src/main/res/values-cs/strings.xml b/auth/src/main/res/values-cs/strings.xml index 56e5d5edf..04ea9a9e6 100755 --- a/auth/src/main/res/values-cs/strings.xml +++ b/auth/src/main/res/values-cs/strings.xml @@ -110,4 +110,11 @@ Zadejte ověřovací kód Uložte tyto kódy na bezpečném místě. Můžete je použít k přihlášení, pokud ztratíte přístup k metodě ověření. + Potvrďte heslo + Hesla se neshodují + Heslo musí mít alespoň %1$d znaků + Heslo musí obsahovat alespoň jedno velké písmeno + Heslo musí obsahovat alespoň jedno malé písmeno + Heslo musí obsahovat alespoň jednu číslici + Heslo musí obsahovat alespoň jeden speciální znak diff --git a/auth/src/main/res/values-da/strings.xml b/auth/src/main/res/values-da/strings.xml index db790ab5d..6d79e8a97 100755 --- a/auth/src/main/res/values-da/strings.xml +++ b/auth/src/main/res/values-da/strings.xml @@ -110,4 +110,11 @@ Indtast din bekræftelseskode Gem disse koder et sikkert sted. Du kan bruge dem til at logge ind, hvis du mister adgang til din godkendelsesmetode. + Bekræft adgangskode + Adgangskoderne stemmer ikke overens + Adgangskoden skal være på mindst %1$d tegn + Adgangskoden skal indeholde mindst ét stort bogstav + Adgangskoden skal indeholde mindst ét lille bogstav + Adgangskoden skal indeholde mindst ét tal + Adgangskoden skal indeholde mindst ét specialtegn diff --git a/auth/src/main/res/values-de-rAT/strings.xml b/auth/src/main/res/values-de-rAT/strings.xml index 0f04c46de..64b9ac2ff 100755 --- a/auth/src/main/res/values-de-rAT/strings.xml +++ b/auth/src/main/res/values-de-rAT/strings.xml @@ -110,4 +110,11 @@ Geben Sie Ihren Bestätigungscode ein Bewahren Sie diese Codes an einem sicheren Ort auf. Sie können sie zum Anmelden verwenden, wenn Sie den Zugriff auf Ihre Authentifizierungsmethode verlieren. + Passwort bestätigen + Passwörter stimmen nicht überein + Das Passwort muss mindestens %1$d Zeichen lang sein + Das Passwort muss mindestens einen Großbuchstaben enthalten + Das Passwort muss mindestens einen Kleinbuchstaben enthalten + Das Passwort muss mindestens eine Ziffer enthalten + Das Passwort muss mindestens ein Sonderzeichen enthalten diff --git a/auth/src/main/res/values-de-rCH/strings.xml b/auth/src/main/res/values-de-rCH/strings.xml index a41c9fe55..00278a40e 100755 --- a/auth/src/main/res/values-de-rCH/strings.xml +++ b/auth/src/main/res/values-de-rCH/strings.xml @@ -111,4 +111,11 @@ Geben Sie Ihren Bestätigungscode ein Bewahren Sie diese Codes an einem sicheren Ort auf. Sie können sie zum Anmelden verwenden, wenn Sie den Zugriff auf Ihre Authentifizierungsmethode verlieren. + Passwort bestätigen + Passwörter stimmen nicht überein + Das Passwort muss mindestens %1$d Zeichen lang sein + Das Passwort muss mindestens einen Grossbuchstaben enthalten + Das Passwort muss mindestens einen Kleinbuchstaben enthalten + Das Passwort muss mindestens eine Ziffer enthalten + Das Passwort muss mindestens ein Sonderzeichen enthalten diff --git a/auth/src/main/res/values-de/strings.xml b/auth/src/main/res/values-de/strings.xml index 9fb192397..90b7458a2 100755 --- a/auth/src/main/res/values-de/strings.xml +++ b/auth/src/main/res/values-de/strings.xml @@ -110,4 +110,11 @@ Geben Sie Ihren Bestätigungscode ein Bewahren Sie diese Codes an einem sicheren Ort auf. Sie können sie zum Anmelden verwenden, wenn Sie den Zugriff auf Ihre Authentifizierungsmethode verlieren. + Passwort bestätigen + Passwörter stimmen nicht überein + Das Passwort muss mindestens %1$d Zeichen lang sein + Das Passwort muss mindestens einen Großbuchstaben enthalten + Das Passwort muss mindestens einen Kleinbuchstaben enthalten + Das Passwort muss mindestens eine Ziffer enthalten + Das Passwort muss mindestens ein Sonderzeichen enthalten diff --git a/auth/src/main/res/values-el/strings.xml b/auth/src/main/res/values-el/strings.xml index ae743c298..5853da17c 100755 --- a/auth/src/main/res/values-el/strings.xml +++ b/auth/src/main/res/values-el/strings.xml @@ -111,4 +111,11 @@ Εισαγάγετε τον κωδικό επαλήθευσης Αποθηκεύστε αυτούς τους κωδικούς σε ασφαλές μέρος. Μπορείτε να τους χρησιμοποιήσετε για σύνδεση εάν χάσετε την πρόσβαση στη μέθοδο ελέγχου ταυτότητας. + Επιβεβαίωση κωδικού πρόσβασης + Οι κωδικοί πρόσβασης δεν ταιριάζουν + Ο κωδικός πρόσβασης πρέπει να αποτελείται από τουλάχιστον %1$d χαρακτήρες + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον ένα κεφαλαίο γράμμα + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον ένα μικρό γράμμα + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον έναν αριθμό + Ο κωδικός πρόσβασης πρέπει να περιέχει τουλάχιστον έναν ειδικό χαρακτήρα diff --git a/auth/src/main/res/values-en-rAU/strings.xml b/auth/src/main/res/values-en-rAU/strings.xml index 7c76fbdea..3d68aaf3a 100755 --- a/auth/src/main/res/values-en-rAU/strings.xml +++ b/auth/src/main/res/values-en-rAU/strings.xml @@ -110,4 +110,11 @@ Enter your verification code Store these codes in a safe place. You can use them to sign in if you lose access to your authentication method. + Confirm password + Passwords do not match + Password must be at least %1$d characters long + Password must contain at least one uppercase letter + Password must contain at least one lowercase letter + Password must contain at least one number + Password must contain at least one special character diff --git a/auth/src/main/res/values-en-rCA/strings.xml b/auth/src/main/res/values-en-rCA/strings.xml index 7c76fbdea..2cfa96ac2 100755 --- a/auth/src/main/res/values-en-rCA/strings.xml +++ b/auth/src/main/res/values-en-rCA/strings.xml @@ -110,4 +110,11 @@ Enter your verification code Store these codes in a safe place. You can use them to sign in if you lose access to your authentication method. + Confirm password + Passwords do not match + Password must be at least %1$d characters long + Password must contain at least one upper-case letter + Password must contain at least one lower-case letter + Password must contain at least one number + Password must contain at least one special character diff --git a/auth/src/main/res/values-en-rGB/strings.xml b/auth/src/main/res/values-en-rGB/strings.xml index 7c76fbdea..2cfa96ac2 100755 --- a/auth/src/main/res/values-en-rGB/strings.xml +++ b/auth/src/main/res/values-en-rGB/strings.xml @@ -110,4 +110,11 @@ Enter your verification code Store these codes in a safe place. You can use them to sign in if you lose access to your authentication method. + Confirm password + Passwords do not match + Password must be at least %1$d characters long + Password must contain at least one upper-case letter + Password must contain at least one lower-case letter + Password must contain at least one number + Password must contain at least one special character diff --git a/auth/src/main/res/values-es/strings.xml b/auth/src/main/res/values-es/strings.xml index b7359f091..40f100b74 100755 --- a/auth/src/main/res/values-es/strings.xml +++ b/auth/src/main/res/values-es/strings.xml @@ -110,4 +110,11 @@ Introduce tu código de verificación Guarda estos códigos en un lugar seguro. Puedes usarlos para iniciar sesión si pierdes el acceso a tu método de autenticación. + Confirmar contraseña + Las contraseñas no coinciden + La contraseña debe tener al menos %1$d caracteres + La contraseña debe contener al menos una letra mayúscula + La contraseña debe contener al menos una letra minúscula + La contraseña debe contener al menos un número + La contraseña debe contener al menos un carácter especial diff --git a/auth/src/main/res/values-fa/strings.xml b/auth/src/main/res/values-fa/strings.xml index c0629e247..dcadfa791 100755 --- a/auth/src/main/res/values-fa/strings.xml +++ b/auth/src/main/res/values-fa/strings.xml @@ -111,4 +111,11 @@ کد تأیید خود را وارد کنید این کدها را در مکانی امن ذخیره کنید. اگر دسترسی به روش احراز هویت خود را از دست دادید، می‌توانید از آن‌ها برای ورود استفاده کنید. + تأیید گذرواژه + گذرواژه‌ها مطابقت ندارند + گذرواژه باید حداقل %1$d نویسه باشد + گذرواژه باید حداقل یک حرف بزرگ داشته باشد + گذرواژه باید حداقل یک حرف کوچک داشته باشد + گذرواژه باید حداقل یک عدد داشته باشد + گذرواژه باید حداقل یک نویسه خاص داشته باشد diff --git a/auth/src/main/res/values-fi/strings.xml b/auth/src/main/res/values-fi/strings.xml index 864dc7742..01e38ce7b 100755 --- a/auth/src/main/res/values-fi/strings.xml +++ b/auth/src/main/res/values-fi/strings.xml @@ -110,4 +110,11 @@ Anna vahvistuskoodisi Tallenna nämä koodit turvalliseen paikkaan. Voit käyttää niitä kirjautumiseen, jos menetät pääsyn todennusmenetelmääsi. + Vahvista salasana + Salasanat eivät täsmää + Salasanan on oltava vähintään %1$d merkkiä pitkä + Salasanan on sisällettävä vähintään yksi iso kirjain + Salasanan on sisällettävä vähintään yksi pieni kirjain + Salasanan on sisällettävä vähintään yksi numero + Salasanan on sisällettävä vähintään yksi erikoismerkki diff --git a/auth/src/main/res/values-fil/strings.xml b/auth/src/main/res/values-fil/strings.xml index 0a5ea38ac..066f65c87 100755 --- a/auth/src/main/res/values-fil/strings.xml +++ b/auth/src/main/res/values-fil/strings.xml @@ -110,4 +110,11 @@ Ilagay ang iyong verification code I-imbak ang mga code na ito sa ligtas na lugar. Magagamit mo ang mga ito para mag-sign in kung mawawala ang access sa iyong paraan ng authentication. + Kumpirmahin ang password + Hindi tugma ang mga password + Dapat may hindi bababa sa %1$d na character ang password + Dapat may kahit isang malaking titik ang password + Dapat may kahit isang maliit na titik ang password + Dapat may kahit isang numero ang password + Dapat may kahit isang espesyal na character ang password diff --git a/auth/src/main/res/values-fr/strings.xml b/auth/src/main/res/values-fr/strings.xml index 43154dc3d..8fd57e38f 100755 --- a/auth/src/main/res/values-fr/strings.xml +++ b/auth/src/main/res/values-fr/strings.xml @@ -110,4 +110,11 @@ Saisissez votre code de vérification Conservez ces codes en lieu sûr. Vous pouvez les utiliser pour vous connecter si vous perdez l\'accès à votre méthode d\'authentification. + Confirmer le mot de passe + Les mots de passe ne correspondent pas + Le mot de passe doit contenir au moins %1$d caractères + Le mot de passe doit contenir au moins une lettre majuscule + Le mot de passe doit contenir au moins une lettre minuscule + Le mot de passe doit contenir au moins un chiffre + Le mot de passe doit contenir au moins un caractère spécial diff --git a/auth/src/main/res/values-gsw/strings.xml b/auth/src/main/res/values-gsw/strings.xml index 5f640d6b6..3f2ddc6ec 100755 --- a/auth/src/main/res/values-gsw/strings.xml +++ b/auth/src/main/res/values-gsw/strings.xml @@ -110,4 +110,11 @@ Gäbe Sie Ihre Bestätigungscode ii Bewahre Sie die Codes a eme sichere Ort uuf. Sie chönd si zum Aamälde benutze, falls Sie de Zuegriff uf Ihri Authentifizierungsmethode verliere. + Passwort bestätige + Passwörter stimmed nöd überiin + S Passwort muess mindischtens %1$d Zeiche lang sii + S Passwort muess mindischtens en Grossbuechschtabe enthalte + S Passwort muess mindischtens en Chliibuechschtabe enthalte + S Passwort muess mindischtens e Ziffere enthalte + S Passwort muess mindischtens es Sonderzeiche enthalte diff --git a/auth/src/main/res/values-gu/strings.xml b/auth/src/main/res/values-gu/strings.xml index d149e2ba7..8bcd09b87 100755 --- a/auth/src/main/res/values-gu/strings.xml +++ b/auth/src/main/res/values-gu/strings.xml @@ -111,4 +111,11 @@ તમારો ચકાસણી કોડ દાખલ કરો આ કોડને સુરક્ષિત સ્થળે સંગ્રહિત કરો. જો તમે તમારી પ્રમાણીકરણ પદ્ધતિની ઍક્સેસ ગુમાવો છો, તો તમે સાઇન ઇન કરવા માટે તેનો ઉપયોગ કરી શકો છો. + પાસવર્ડ કન્ફર્મ કરો + પાસવર્ડ મેળ ખાતા નથી + પાસવર્ડમાં ઓછામાં ઓછા %1$d અક્ષરો હોવા આવશ્યક છે + પાસવર્ડમાં ઓછામાં ઓછો એક અપકેસ અક્ષર હોવો આવશ્યક છે + પાસવર્ડમાં ઓછામાં ઓછો એક લોઅરકેસ અક્ષર હોવો આવશ્યક છે + પાસવર્ડમાં ઓછામાં ઓછો એક નંબર હોવો આવશ્યક છે + પાસવર્ડમાં ઓછામાં ઓછો એક વિશેષ અક્ષર હોવો આવશ્યક છે diff --git a/auth/src/main/res/values-hi/strings.xml b/auth/src/main/res/values-hi/strings.xml index 443c84825..980e9f4d2 100755 --- a/auth/src/main/res/values-hi/strings.xml +++ b/auth/src/main/res/values-hi/strings.xml @@ -111,4 +111,11 @@ अपना सत्यापन कोड दर्ज करें इन कोड को सुरक्षित स्थान पर संग्रहीत करें। यदि आप अपनी प्रमाणीकरण विधि तक पहुंच खो देते हैं, तो आप साइन इन करने के लिए इनका उपयोग कर सकते हैं। + पासवर्ड की पुष्टि करें + पासवर्ड मेल नहीं खाते + पासवर्ड कम से कम %1$d वर्णों का होना चाहिए + पासवर्ड में कम से कम एक बड़ा अक्षर होना चाहिए + पासवर्ड में कम से कम एक छोटा अक्षर होना चाहिए + પાસવર્ડમાં ઓછામાં ઓછો એક નંબર હોવો આવશ્યક છે + પાસવર્ડમાં ઓછામાં ઓછો એક વિશેષ અક્ષર હોવો આવશ્યક છે diff --git a/auth/src/main/res/values-hr/strings.xml b/auth/src/main/res/values-hr/strings.xml index 978523e89..6e5c9dad9 100755 --- a/auth/src/main/res/values-hr/strings.xml +++ b/auth/src/main/res/values-hr/strings.xml @@ -110,4 +110,11 @@ Unesite svoju šifru za potvrdu Pohranite ove šifre na sigurno mjesto. Možete ih koristiti za prijavu ako izgubite pristup svojoj metodi provjere autentičnosti. + Potvrdite zaporku + Zaporke se ne podudaraju + Zaporka mora imati najmanje %1$d znakova + Zaporka mora sadržavati barem jedno veliko slovo + Zaporka mora sadržavati barem jedno malo slovo + Zaporka mora sadržavati barem jedan broj + Zaporka mora sadržavati barem jedan poseban znak diff --git a/auth/src/main/res/values-hu/strings.xml b/auth/src/main/res/values-hu/strings.xml index 8e1d79de1..7d342ba1d 100755 --- a/auth/src/main/res/values-hu/strings.xml +++ b/auth/src/main/res/values-hu/strings.xml @@ -110,4 +110,11 @@ Adja meg ellenőrző kódját Tárolja ezeket a kódokat biztonságos helyen. Ezekkel jelentkezhet be, ha elveszti a hitelesítési módszeréhez való hozzáférést. + Jelszó megerősítése + A jelszavak nem egyeznek + A jelszónak legalább %1$d karakter hosszúnak kell lennie + A jelszónak tartalmaznia kell legalább egy nagybetűt + A jelszónak tartalmaznia kell legalább egy kisbetűt + A jelszónak tartalmaznia kell legalább egy számot + A jelszónak tartalmaznia kell legalább egy speciális karaktert diff --git a/auth/src/main/res/values-in/strings.xml b/auth/src/main/res/values-in/strings.xml index d7fa60d30..124bb90dd 100755 --- a/auth/src/main/res/values-in/strings.xml +++ b/auth/src/main/res/values-in/strings.xml @@ -111,4 +111,11 @@ Masukkan kode verifikasi Anda Simpan kode ini di tempat yang aman. Anda dapat menggunakannya untuk masuk jika kehilangan akses ke metode autentikasi Anda. + Konfirmasi sandi + Sandi tidak cocok + Sandi minimal harus terdiri dari %1$d karakter + Sandi harus berisi setidaknya satu huruf besar + Sandi harus berisi setidaknya satu huruf kecil + Sandi harus berisi setidaknya satu angka + Sandi harus berisi setidaknya satu karakter khusus diff --git a/auth/src/main/res/values-it/strings.xml b/auth/src/main/res/values-it/strings.xml index 498a725d3..cfcca546d 100755 --- a/auth/src/main/res/values-it/strings.xml +++ b/auth/src/main/res/values-it/strings.xml @@ -110,4 +110,11 @@ Inserisci il codice di verifica Conserva questi codici in un luogo sicuro. Puoi usarli per accedere se perdi l\'accesso al tuo metodo di autenticazione. + Conferma password + Le password non corrispondono + La password deve contenere almeno %1$d caratteri + La password deve contenere almeno una lettera maiuscola + La password deve contenere almeno una lettera minuscola + La password deve contenere almeno un numero + La password deve contenere almeno un carattere speciale diff --git a/auth/src/main/res/values-iw/strings.xml b/auth/src/main/res/values-iw/strings.xml index 9e3eb2b6f..199692586 100755 --- a/auth/src/main/res/values-iw/strings.xml +++ b/auth/src/main/res/values-iw/strings.xml @@ -111,4 +111,11 @@ הזן את קוד האימות שלך שמור קודים אלה במקום בטוח. תוכל להשתמש בהם כדי להיכנס אם תאבד גישה לשיטת האימות שלך. + אישור סיסמה + הסיסמאות אינן תואמות + הסיסמה חייבת להכיל לפחות %1$d תווים + הסיסמה חייבת להכיל לפחות אות גדולה אחת + הסיסמה חייבת להכיל לפחות אות קטנה אחת + הסיסמה חייבת להכיל לפחות מספר אחד + הסיסמה חייבת להכיל לפחות תו מיוחד אחד diff --git a/auth/src/main/res/values-ja/strings.xml b/auth/src/main/res/values-ja/strings.xml index 8bd658629..b90a233bc 100755 --- a/auth/src/main/res/values-ja/strings.xml +++ b/auth/src/main/res/values-ja/strings.xml @@ -110,4 +110,11 @@ 確認コードを入力してください これらのコードを安全な場所に保管してください。認証方法にアクセスできなくなった場合、これらを使用してログインできます。 + パスワードの確認 + パスワードが一致しません + パスワードは%1$d文字以上にする必要があります + パスワードには大文字が1文字以上必要です + パスワードには小文字が1文字以上必要です + パスワードには数字が1文字以上必要です + パスワードには特殊文字が1文字以上必要です diff --git a/auth/src/main/res/values-kn/strings.xml b/auth/src/main/res/values-kn/strings.xml index 6dd2e1a8c..27bff3ff6 100755 --- a/auth/src/main/res/values-kn/strings.xml +++ b/auth/src/main/res/values-kn/strings.xml @@ -111,4 +111,11 @@ ನಿಮ್ಮ ಪರಿಶೀಲನೆ ಕೋಡ್ ಅನ್ನು ನಮೂದಿಸಿ ಈ ಕೋಡ್‌ಗಳನ್ನು ಸುರಕ್ಷಿತ ಸ್ಥಳದಲ್ಲಿ ಸಂಗ್ರಹಿಸಿ. ನಿಮ್ಮ ದೃಢೀಕರಣ ವಿಧಾನಕ್ಕೆ ಪ್ರವೇಶವನ್ನು ಕಳೆದುಕೊಂಡರೆ ಸೈನ್ ಇನ್ ಮಾಡಲು ಅವುಗಳನ್ನು ಬಳಸಬಹುದು. + ಪಾಸ್‌ವರ್ಡ್ ದೃಢೀಕರಿಸಿ + ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಹೊಂದಿಕೆಯಾಗುತ್ತಿಲ್ಲ + ಪಾಸ್‌ವರ್ಡ್ ಕನಿಷ್ಠ %1$d ಅಕ್ಷರಗಳಷ್ಟು ಉದ್ದವಿರಬೇಕು + ಪಾಸ್‌ವರ್ಡ್ ಕನಿಷ್ಠ ಒಂದು ದೊಡ್ಡಕ್ಷರವನ್ನು ಹೊಂದಿರಬೇಕು + ಪಾಸ್‌ವರ್ಡ್ ಕನಿಷ್ಠ ಒಂದು ಸಣ್ಣಕ್ಷರವನ್ನು ಹೊಂದಿರಬೇಕು + ಪಾಸ್‌ವರ್ಡ್ ಕನಿಷ್ಠ ಒಂದು ಸಂಖ್ಯೆಯನ್ನು ಹೊಂದಿರಬೇಕು + ಪಾಸ್‌ವರ್ಡ್ ಕನಿಷ್ಠ ಒಂದು ವಿಶೇಷ ಅಕ್ಷರವನ್ನು ಹೊಂದಿರಬೇಕು diff --git a/auth/src/main/res/values-ko/strings.xml b/auth/src/main/res/values-ko/strings.xml index 9aa109ed0..53509f24f 100755 --- a/auth/src/main/res/values-ko/strings.xml +++ b/auth/src/main/res/values-ko/strings.xml @@ -109,4 +109,11 @@ 인증 코드를 입력하세요 이 코드를 안전한 곳에 보관하세요. 인증 방법에 액세스할 수 없게 되면 이 코드를 사용하여 로그인할 수 있습니다. + 비밀번호 확인 + 비밀번호가 일치하지 않습니다 + 비밀번호는 %1$d자 이상이어야 합니다 + 비밀번호에 대문자가 하나 이상 포함되어야 합니다 + 비밀번호에 소문자가 하나 이상 포함되어야 합니다 + 비밀번호에 숫자가 하나 이상 포함되어야 합니다 + 비밀번호에 특수문자가 하나 이상 포함되어야 합니다 diff --git a/auth/src/main/res/values-ln/strings.xml b/auth/src/main/res/values-ln/strings.xml index 7c7a59dfc..b2991e5a4 100755 --- a/auth/src/main/res/values-ln/strings.xml +++ b/auth/src/main/res/values-ln/strings.xml @@ -111,4 +111,11 @@ Kota kode na yo ya bondimi Bomba ba kode oyo na esika moko ya libateli. Okoki kosalela yango po na kokota soki obungi nzela ya bondimi na yo. + Ndima mot de passe + Ba mot de passe ekokani te + Mot de passe esengeli kozala na ba caractères %1$d na molayi + Mot de passe esengeli kozala na ata lettre moko ya monene + Mot de passe esengeli kozala na ata lettre moko ya moke + Mot de passe esengeli kozala na ata nimero moko + Mot de passe esengeli kozala na ata caractère moko ya spécial diff --git a/auth/src/main/res/values-lt/strings.xml b/auth/src/main/res/values-lt/strings.xml index 0bb64fd78..c6e0dc59a 100755 --- a/auth/src/main/res/values-lt/strings.xml +++ b/auth/src/main/res/values-lt/strings.xml @@ -111,4 +111,11 @@ Įveskite patvirtinimo kodą Išsaugokite šiuos kodus saugioje vietoje. Galite juos naudoti prisijungti, jei prarasite prieigą prie autentifikavimo metodo. + Patvirtinti slaptažodį + Slaptažodžiai nesutampa + Slaptažodis turi būti ne trumpesnis kaip %1$d simbolių + Slaptažodyje turi būti bent viena didžioji raidė + Slaptažodyje turi būti bent viena mažoji raidė + Slaptažodyje turi būti bent vienas skaičius + Slaptažodyje turi būti bent vienas specialusis simbolis diff --git a/auth/src/main/res/values-lv/strings.xml b/auth/src/main/res/values-lv/strings.xml index b10b66de6..dff9a3f78 100755 --- a/auth/src/main/res/values-lv/strings.xml +++ b/auth/src/main/res/values-lv/strings.xml @@ -111,4 +111,11 @@ Ievadiet verificēšanas kodu Glabājiet šos kodus drošā vietā. Varat tos izmantot, lai pierakstītos, ja zaudējat piekļuvi autentifikācijas metodei. + Apstipriniet paroli + Paroles nesakrīt + Parolei jābūt vismaz %1$d rakstzīmes garai + Parolei jāsatur vismaz viens lielais burts + Parolei jāsatur vismaz viens mazais burts + Parolei jāsatur vismaz viens cipars + Parolei jāsatur vismaz viena īpašā rakstzīme diff --git a/auth/src/main/res/values-mo/strings.xml b/auth/src/main/res/values-mo/strings.xml index 09a3acb87..16ccd10c1 100755 --- a/auth/src/main/res/values-mo/strings.xml +++ b/auth/src/main/res/values-mo/strings.xml @@ -111,4 +111,11 @@ Introduceți codul de verificare Stocați aceste coduri într-un loc sigur. Le puteți folosi pentru a vă conecta dacă pierdeți accesul la metoda de autentificare. + Confirmați parola + Parolele nu se potrivesc + Parola trebuie să aibă cel puțin %1$d caractere + Parola trebuie să conțină cel puțin o literă mare + Parola trebuie să conțină cel puțin o literă mică + Parola trebuie să conțină cel puțin un număr + Parola trebuie să conțină cel puțin un caracter special diff --git a/auth/src/main/res/values-mr/strings.xml b/auth/src/main/res/values-mr/strings.xml index f9837e7f5..179b854f2 100755 --- a/auth/src/main/res/values-mr/strings.xml +++ b/auth/src/main/res/values-mr/strings.xml @@ -111,4 +111,11 @@ तुमचा पडताळणी कोड प्रविष्ट करा हे कोड सुरक्षित ठिकाणी संग्रहित करा. तुम्ही तुमच्या प्रमाणीकरण पद्धतीचा प्रवेश गमावल्यास साइन इन करण्यासाठी त्यांचा वापर करू शकता. + पासवर्डची पुष्टी करा + पासवर्ड जुळत नाहीत + पासवर्ड किमान %1$d वर्णांचा असावा + पासवर्डमध्ये किमान एक मोठे अक्षर असावे + पासवर्डमध्ये किमान एक लहान अक्षर असावे + पासवर्डमध्ये किमान एक अंक असावा + पासवर्डमध्ये किमान एक विशेष वर्ण असावा diff --git a/auth/src/main/res/values-ms/strings.xml b/auth/src/main/res/values-ms/strings.xml index e23164061..2a65ad8fc 100755 --- a/auth/src/main/res/values-ms/strings.xml +++ b/auth/src/main/res/values-ms/strings.xml @@ -111,4 +111,11 @@ Masukkan kod pengesahan anda Simpan kod ini di tempat yang selamat. Anda boleh menggunakannya untuk log masuk jika anda kehilangan akses kepada kaedah pengesahan anda. + Sahkan Kata Laluan + Kata laluan tidak sepadan + Kata laluan mestilah sekurang-kurangnya %1$d aksara panjang + Kata laluan mestilah mengandungi sekurang-kurangnya satu huruf besar + Kata laluan mestilah mengandungi sekurang-kurangnya satu huruf kecil + Kata laluan mestilah mengandungi sekurang-kurangnya satu nombor + Kata laluan mestilah mengandungi sekurang-kurangnya satu aksara khas diff --git a/auth/src/main/res/values-nb/strings.xml b/auth/src/main/res/values-nb/strings.xml index 93b327736..0cb1e2775 100755 --- a/auth/src/main/res/values-nb/strings.xml +++ b/auth/src/main/res/values-nb/strings.xml @@ -110,4 +110,11 @@ Skriv inn bekreftelseskoden din Lagre disse kodene på et sikkert sted. Du kan bruke dem til å logge inn hvis du mister tilgang til autentiseringsmetoden din. + Bekreft passord + Passordene stemmer ikke overens + Passordet må være på minst %1$d tegn + Passordet må inneholde minst én stor bokstav + Passordet må inneholde minst én liten bokstav + Passordet må inneholde minst ett tall + Passordet må inneholde minst ett spesialtegn diff --git a/auth/src/main/res/values-nl/strings.xml b/auth/src/main/res/values-nl/strings.xml index e48023c7d..f51181de3 100755 --- a/auth/src/main/res/values-nl/strings.xml +++ b/auth/src/main/res/values-nl/strings.xml @@ -110,4 +110,11 @@ Voer je verificatiecode in Bewaar deze codes op een veilige plek. Je kunt ze gebruiken om in te loggen als je geen toegang meer hebt tot je authenticatiemethode. + Bevestig wachtwoord + Wachtwoorden komen niet overeen + Wachtwoord moet minimaal %1$d tekens lang zijn + Wachtwoord moet minimaal één hoofdletter bevatten + Wachtwoord moet minimaal één kleine letter bevatten + Wachtwoord moet minimaal één cijfer bevatten + Wachtwoord moet minimaal één speciaal teken bevatten diff --git a/auth/src/main/res/values-no/strings.xml b/auth/src/main/res/values-no/strings.xml index 373f3dfe6..76e979935 100755 --- a/auth/src/main/res/values-no/strings.xml +++ b/auth/src/main/res/values-no/strings.xml @@ -111,4 +111,11 @@ Skriv inn bekreftelseskoden din Lagre disse kodene på et sikkert sted. Du kan bruke dem til å logge inn hvis du mister tilgang til autentiseringsmetoden din. + Bekreft passord + Passordene stemmer ikke overens + Passordet må være på minst %1$d tegn + Passordet må inneholde minst én stor bokstav + Passordet må inneholde minst én liten bokstav + Passordet må inneholde minst ett tall + Passordet må inneholde minst ett spesialtegn diff --git a/auth/src/main/res/values-pl/strings.xml b/auth/src/main/res/values-pl/strings.xml index 1951b47e6..4dd79cc1a 100755 --- a/auth/src/main/res/values-pl/strings.xml +++ b/auth/src/main/res/values-pl/strings.xml @@ -110,4 +110,11 @@ Wprowadź kod weryfikacyjny Przechowuj te kody w bezpiecznym miejscu. Możesz ich użyć do zalogowania się, jeśli stracisz dostęp do metody uwierzytelniania. + Potwierdź hasło + Hasła nie są takie same + Hasło musi mieć co najmniej %1$d znaków + Hasło musi zawierać co najmniej jedną wielką literę + Hasło musi zawierać co najmniej jedną małą literę + Hasło musi zawierać co najmniej jedną cyfrę + Hasło musi zawierać co najmniej jeden znak specjalny diff --git a/auth/src/main/res/values-pt-rBR/strings.xml b/auth/src/main/res/values-pt-rBR/strings.xml index ae9215fc2..70709794f 100755 --- a/auth/src/main/res/values-pt-rBR/strings.xml +++ b/auth/src/main/res/values-pt-rBR/strings.xml @@ -111,4 +111,11 @@ Digite seu código de verificação Armazene esses códigos em um local seguro. Você pode usá-los para fazer login se perder o acesso ao seu método de autenticação. + Confirmar senha + As senhas não correspondem + A senha precisa ter no mínimo %1$d caracteres + A senha precisa conter pelo menos uma letra maiúscula + A senha precisa conter pelo menos uma letra minúscula + A senha precisa conter pelo menos um número + A senha precisa conter pelo menos um caractere especial diff --git a/auth/src/main/res/values-pt-rPT/strings.xml b/auth/src/main/res/values-pt-rPT/strings.xml index 236d9d5af..b8592da53 100755 --- a/auth/src/main/res/values-pt-rPT/strings.xml +++ b/auth/src/main/res/values-pt-rPT/strings.xml @@ -111,4 +111,11 @@ Digite seu código de verificação Armazene esses códigos em um local seguro. Você pode usá-los para fazer login se perder o acesso ao seu método de autenticação. + Confirmar palavra-passe + As palavras-passe não correspondem + A palavra-passe tem de ter, pelo menos, %1$d caracteres + A palavra-passe tem de conter, pelo menos, uma letra maiúscula + A palavra-passe tem de conter, pelo menos, uma letra minúscula + A palavra-passe tem de conter, pelo menos, um número + A palavra-passe tem de conter, pelo menos, um caráter especial diff --git a/auth/src/main/res/values-pt/strings.xml b/auth/src/main/res/values-pt/strings.xml index 336a32898..9d312c557 100755 --- a/auth/src/main/res/values-pt/strings.xml +++ b/auth/src/main/res/values-pt/strings.xml @@ -110,4 +110,11 @@ Digite seu código de verificação Armazene esses códigos em um local seguro. Você pode usá-los para fazer login se perder o acesso ao seu método de autenticação. + Confirmar senha + As senhas não correspondem + A senha deve ter no mínimo %1$d caracteres + A senha deve conter pelo menos uma letra maiúscula + A senha deve conter pelo menos uma letra minúscula + A senha deve conter pelo menos um número + A senha deve conter pelo menos um caractere especial diff --git a/auth/src/main/res/values-ro/strings.xml b/auth/src/main/res/values-ro/strings.xml index 0037d51ac..a956eb95a 100755 --- a/auth/src/main/res/values-ro/strings.xml +++ b/auth/src/main/res/values-ro/strings.xml @@ -110,4 +110,11 @@ Introduceți codul de verificare Stocați aceste coduri într-un loc sigur. Le puteți folosi pentru a vă conecta dacă pierdeți accesul la metoda de autentificare. + Confirmați parola + Parolele nu se potrivesc + Parola trebuie să aibă cel puțin %1$d caractere + Parola trebuie să conțină cel puțin o literă mare + Parola trebuie să conțină cel puțin o literă mică + Parola trebuie să conțină cel puțin un număr + Parola trebuie să conțină cel puțin un caracter special diff --git a/auth/src/main/res/values-ru/strings.xml b/auth/src/main/res/values-ru/strings.xml index b8b45952e..26576e9d8 100755 --- a/auth/src/main/res/values-ru/strings.xml +++ b/auth/src/main/res/values-ru/strings.xml @@ -110,4 +110,11 @@ Введите код подтверждения Сохраните эти коды в безопасном месте. Вы можете использовать их для входа, если потеряете доступ к способу аутентификации. + Подтвердите пароль + Пароли не совпадают + Пароль должен содержать не менее %1$d символов + Пароль должен содержать хотя бы одну заглавную букву + Пароль должен содержать хотя бы одну строчную букву + Пароль должен содержать хотя бы одну цифру + Пароль должен содержать хотя бы один специальный символ diff --git a/auth/src/main/res/values-sk/strings.xml b/auth/src/main/res/values-sk/strings.xml index 89ee537cf..ec5c4ecab 100755 --- a/auth/src/main/res/values-sk/strings.xml +++ b/auth/src/main/res/values-sk/strings.xml @@ -110,4 +110,11 @@ Zadajte overovací kód Uložte tieto kódy na bezpečnom mieste. Môžete ich použiť na prihlásenie, ak stratíte prístup k metóde overenia. + Potvrdiť heslo + Heslá sa nezhodujú + Heslo musí mať aspoň %1$d znakov + Heslo musí obsahovať aspoň jedno veľké písmeno + Heslo musí obsahovať aspoň jedno malé písmeno + Heslo musí obsahovať aspoň jedno číslo + Heslo musí obsahovať aspoň jeden špeciálny znak diff --git a/auth/src/main/res/values-sl/strings.xml b/auth/src/main/res/values-sl/strings.xml index 84002e9d9..d8773274e 100755 --- a/auth/src/main/res/values-sl/strings.xml +++ b/auth/src/main/res/values-sl/strings.xml @@ -111,4 +111,11 @@ Vnesite kodo za preverjanje Shranite te kode na varno mesto. Uporabite jih lahko za prijavo, če izgubite dostop do načina preverjanja pristnosti. + Potrdite geslo + Gesli se ne ujemata + Geslo mora vsebovati vsaj %1$d znakov + Geslo mora vsebovati vsaj eno veliko črko + Geslo mora vsebovati vsaj eno malo črko + Geslo mora vsebovati vsaj eno števko + Geslo mora vsebovati vsaj en poseben znak diff --git a/auth/src/main/res/values-sr/strings.xml b/auth/src/main/res/values-sr/strings.xml index 346ca0c0a..db29b6630 100755 --- a/auth/src/main/res/values-sr/strings.xml +++ b/auth/src/main/res/values-sr/strings.xml @@ -111,4 +111,11 @@ Унесите свој код за верификацију Похраните ове кодове на сигурно место. Можете их користити за пријаву ако изгубите приступ методу аутентификације. + Потврди лозинку + Лозинке се не подударају + Лозинка мора да има најмање %1$d знакова + Лозинка мора да садржи најмање једно велико слово + Лозинка мора да садржи најмање једно мало слово + Лозинка мора да садржи најмање један број + Лозинка мора да садржи најмање један посебан знак diff --git a/auth/src/main/res/values-sv/strings.xml b/auth/src/main/res/values-sv/strings.xml index f62f30104..57a8ec314 100755 --- a/auth/src/main/res/values-sv/strings.xml +++ b/auth/src/main/res/values-sv/strings.xml @@ -110,4 +110,11 @@ Ange din verifieringskod Förvara dessa koder på en säker plats. Du kan använda dem för att logga in om du förlorar åtkomst till din autentiseringsmetod. + Bekräfta lösenord + Lösenorden matchar inte + Lösenordet måste vara minst %1$d tecken långt + Lösenordet måste innehålla minst en stor bokstav + Lösenordet måste innehålla minst en liten bokstav + Lösenordet måste innehålla minst en siffra + Lösenordet måste innehålla minst ett specialtecken diff --git a/auth/src/main/res/values-ta/strings.xml b/auth/src/main/res/values-ta/strings.xml index 0a26e5578..02d7f382e 100755 --- a/auth/src/main/res/values-ta/strings.xml +++ b/auth/src/main/res/values-ta/strings.xml @@ -111,4 +111,11 @@ உங்கள் சரிபார்ப்புக் குறியீட்டை உள்ளிடவும் இந்தக் குறியீடுகளைப் பாதுகாப்பான இடத்தில் சேமிக்கவும். உங்கள் அங்கீகார முறைக்கான அணுகலை இழந்தால் உள்நுழைய இவற்றைப் பயன்படுத்தலாம். + கடவுச்சொல்லை உறுதிப்படுத்தவும் + கடவுச்சொற்கள் பொருந்தவில்லை + கடவுச்சொல் குறைந்தது %1$d எழுத்துக்குறிகளைக் கொண்டிருக்க வேண்டும் + கடவுச்சொல்லில் குறைந்தது ஒரு பெரிய எழுத்து இருக்க வேண்டும் + கடவுச்சொல்லில் குறைந்தது ஒரு சிறிய எழுத்து இருக்க வேண்டும் + கடவுச்சொல்லில் குறைந்தது ஒரு எண் இருக்க வேண்டும் + கடவுச்சொல்லில் குறைந்தது ஒரு சிறப்பு எழுத்துக்குறி இருக்க வேண்டும் diff --git a/auth/src/main/res/values-th/strings.xml b/auth/src/main/res/values-th/strings.xml index 8744bec0f..611a3bc22 100755 --- a/auth/src/main/res/values-th/strings.xml +++ b/auth/src/main/res/values-th/strings.xml @@ -111,4 +111,11 @@ ป้อนรหัสยืนยันของคุณ เก็บรหัสเหล่านี้ไว้ในที่ปลอดภัย คุณสามารถใช้รหัสเหล่านี้ลงชื่อเข้าใช้หากคุณไม่สามารถเข้าถึงวิธีการตรวจสอบสิทธิ์ของคุณ + ยืนยันรหัสผ่าน + รหัสผ่านไม่ตรงกัน + รหัสผ่านต้องมีความยาวอย่างน้อย %1$d อักขระ + รหัสผ่านต้องมีตัวพิมพ์ใหญ่อย่างน้อยหนึ่งตัว + รหัสผ่านต้องมีตัวพิมพ์เล็กอย่างน้อยหนึ่งตัว + รหัสผ่านต้องมีตัวเลขอย่างน้อยหนึ่งตัว + รหัสผ่านต้องมีอักขระพิเศษอย่างน้อยหนึ่งตัว diff --git a/auth/src/main/res/values-tl/strings.xml b/auth/src/main/res/values-tl/strings.xml index 0a5ea38ac..f83fbe549 100755 --- a/auth/src/main/res/values-tl/strings.xml +++ b/auth/src/main/res/values-tl/strings.xml @@ -110,4 +110,11 @@ Ilagay ang iyong verification code I-imbak ang mga code na ito sa ligtas na lugar. Magagamit mo ang mga ito para mag-sign in kung mawawala ang access sa iyong paraan ng authentication. + Kumpirmahin ang password + Hindi magkatugma ang mga password + Dapat ay may hindi bababa sa %1$d (na) character ang password + Dapat ay may hindi bababa sa isang malaking titik ang password + Dapat ay may hindi bababa sa isang maliit na titik ang password + Dapat ay may hindi bababa sa isang numero ang password + Dapat ay may hindi bababa sa isang espesyal na character ang password diff --git a/auth/src/main/res/values-tr/strings.xml b/auth/src/main/res/values-tr/strings.xml index 5821d9da2..50feaa6d0 100755 --- a/auth/src/main/res/values-tr/strings.xml +++ b/auth/src/main/res/values-tr/strings.xml @@ -111,4 +111,11 @@ Doğrulama kodunuzu girin Bu kodları güvenli bir yerde saklayın. Kimlik doğrulama yönteminize erişimi kaybederseniz oturum açmak için bunları kullanabilirsiniz. + Şifreyi onayla + Şifreler eşleşmiyor + Şifre en az %1$d karakter uzunluğunda olmalıdır + Şifre en az bir büyük harf içermelidir + Şifre en az bir küçük harf içermelidir + Şifre en az bir rakam içermelidir + Şifre en az bir özel karakter içermelidir diff --git a/auth/src/main/res/values-uk/strings.xml b/auth/src/main/res/values-uk/strings.xml index 14919eb0e..e7d4cb54d 100755 --- a/auth/src/main/res/values-uk/strings.xml +++ b/auth/src/main/res/values-uk/strings.xml @@ -111,4 +111,11 @@ Введіть код підтвердження Збережіть ці коди в безпечному місці. Ви можете використовувати їх для входу, якщо втратите доступ до способу автентифікації. + Підтвердьте пароль + Паролі не збігаються + Пароль має містити щонайменше %1$d символів + Пароль має містити щонайменше одну велику літеру + Пароль має містити щонайменше одну малу літеру + Пароль має містити щонайменше одну цифру + Пароль має містити щонайменше один спеціальний символ diff --git a/auth/src/main/res/values-ur/strings.xml b/auth/src/main/res/values-ur/strings.xml index b36cd1fa3..35f8e7c61 100755 --- a/auth/src/main/res/values-ur/strings.xml +++ b/auth/src/main/res/values-ur/strings.xml @@ -111,4 +111,11 @@ اپنا تصدیقی کوڈ درج کریں ان کوڈز کو محفوظ جگہ پر اسٹور کریں۔ اگر آپ اپنے تصدیقی طریقے تک رسائی کھو دیتے ہیں تو آپ سائن ان کرنے کے لیے ان کا استعمال کر سکتے ہیں۔ + پاس ورڈ کی تصدیق کریں + پاس ورڈز مماثل نہیں ہیں + پاس ورڈ کم از کم %1$d حروف پر مشتمل ہونا چاہیے + پاس ورڈ میں کم از کم ایک بڑا حرف ہونا چاہیے + پاس ورڈ میں کم از کم ایک چھوٹا حرف ہونا چاہیے + پاس ورڈ میں کم از کم ایک ہندسہ ہونا چاہیے + پاس ورڈ میں کم از کم ایک خاص حرف ہونا چاہیے diff --git a/auth/src/main/res/values-vi/strings.xml b/auth/src/main/res/values-vi/strings.xml index 500d33678..24df395c6 100755 --- a/auth/src/main/res/values-vi/strings.xml +++ b/auth/src/main/res/values-vi/strings.xml @@ -111,4 +111,11 @@ Nhập mã xác minh của bạn Lưu trữ các mã này ở nơi an toàn. Bạn có thể sử dụng chúng để đăng nhập nếu mất quyền truy cập vào phương thức xác thực của mình. + Xác nhận mật khẩu + Mật khẩu không khớp + Mật khẩu phải có ít nhất %1$d ký tự + Mật khẩu phải chứa ít nhất một chữ cái viết hoa + Mật khẩu phải chứa ít nhất một chữ cái viết thường + Mật khẩu phải chứa ít nhất một chữ số + Mật khẩu phải chứa ít nhất một ký tự đặc biệt diff --git a/auth/src/main/res/values-zh-rCN/strings.xml b/auth/src/main/res/values-zh-rCN/strings.xml index fdc2dbb44..bb727ea18 100755 --- a/auth/src/main/res/values-zh-rCN/strings.xml +++ b/auth/src/main/res/values-zh-rCN/strings.xml @@ -111,4 +111,11 @@ 输入您的验证码 将这些代码保存在安全的地方。如果您无法访问身份验证方法,可以使用这些代码登录。 + 确认密码 + 密码不匹配 + 密码长度至少为 %1$d 个字符 + 密码必须包含至少一个大写字母 + 密码必须包含至少一个小写字母 + 密码必须包含至少一个数字 + 密码必须包含至少一个特殊字符 diff --git a/auth/src/main/res/values-zh-rHK/strings.xml b/auth/src/main/res/values-zh-rHK/strings.xml index 92d8266b0..6f9a2474a 100755 --- a/auth/src/main/res/values-zh-rHK/strings.xml +++ b/auth/src/main/res/values-zh-rHK/strings.xml @@ -111,4 +111,11 @@ 輸入您的驗證碼 將這些代碼儲存在安全的地方。如果您無法存取驗證方法,可以使用這些代碼登入。 + 確認密碼 + 密碼不符 + 密碼長度必須至少為 %1$d 個字元 + 密碼必須包含至少一個大寫字母 + 密碼必須包含至少一個小寫字母 + 密碼必須包含至少一個數字 + 密碼必須包含至少一個特殊字元 diff --git a/auth/src/main/res/values-zh-rTW/strings.xml b/auth/src/main/res/values-zh-rTW/strings.xml index 9b4b19f2d..d9425dbde 100755 --- a/auth/src/main/res/values-zh-rTW/strings.xml +++ b/auth/src/main/res/values-zh-rTW/strings.xml @@ -111,4 +111,11 @@ 輸入您的驗證碼 將這些代碼儲存在安全的地方。如果您無法存取驗證方法,可以使用這些代碼登入。 + 確認密碼 + 密碼不符 + 密碼長度必須至少為 %1$d 個字元 + 密碼必須包含至少一個大寫字母 + 密碼必須包含至少一個小寫字母 + 密碼必須包含至少一個數字 + 密碼必須包含至少一個特殊字元 diff --git a/auth/src/main/res/values-zh/strings.xml b/auth/src/main/res/values-zh/strings.xml index 9334dfbf8..69b678aa7 100755 --- a/auth/src/main/res/values-zh/strings.xml +++ b/auth/src/main/res/values-zh/strings.xml @@ -110,4 +110,11 @@ 输入您的验证码 将这些代码保存在安全的地方。如果您无法访问身份验证方法,可以使用这些代码登录。 + 确认密码 + 密码不匹配 + 密码长度至少为 %1$d 个字符 + 密码必须包含至少一个大写字母 + 密码必须包含至少一个小写字母 + 密码必须包含至少一个数字 + 密码必须包含至少一个特殊字符