diff --git a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt index 8aabec3b0..5216b87ef 100644 --- a/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt +++ b/app/src/main/java/com/firebaseui/android/demo/HighLevelApiDemoActivity.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.TextButton import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -70,10 +71,6 @@ class HighLevelApiDemoActivity : ComponentActivity() { val authUI = FirebaseAuthUI.getInstance() val emailLink = intent.getStringExtra(EmailLinkConstants.EXTRA_EMAIL_LINK) - val customTheme = AuthUITheme.Default.copy( - providerButtonShape = ShapeDefaults.ExtraLarge - ) - class CustomAuthUIStringProvider( private val defaultProvider: AuthUIStringProvider ) : AuthUIStringProvider by defaultProvider { @@ -85,124 +82,132 @@ class HighLevelApiDemoActivity : ComponentActivity() { val customStringProvider = CustomAuthUIStringProvider(DefaultAuthUIStringProvider(applicationContext)) - val configuration = authUIConfiguration { - context = applicationContext - theme = customTheme - logo = AuthUIAsset.Resource(R.drawable.firebase_auth) - tosUrl = "https://policies.google.com/terms" - privacyPolicyUrl = "https://policies.google.com/privacy" - isAnonymousUpgradeEnabled = false - isMfaEnabled = false - stringProvider = customStringProvider - transitions = AuthUITransitions( - enterTransition = { slideInHorizontally { it } }, - exitTransition = { slideOutHorizontally { -it } }, - popEnterTransition = { slideInHorizontally { -it } }, - popExitTransition = { slideOutHorizontally { it } } + setContent { + val customTheme = AuthUITheme.Adaptive.copy( + providerButtonShape = ShapeDefaults.ExtraLarge, + topAppBarColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFFFFA000), + scrolledContainerColor = Color(0xFFFFA000), + ) ) - providers { - provider(AuthProvider.Anonymous) - provider( - AuthProvider.Google( - scopes = listOf("email"), - serverClientId = "406099696497-a12gakvts4epfk5pkio7dphc1anjiggc.apps.googleusercontent.com", - ) + + val configuration = authUIConfiguration { + context = applicationContext + theme = customTheme + logo = AuthUIAsset.Resource(R.drawable.firebase_auth) + tosUrl = "https://policies.google.com/terms" + privacyPolicyUrl = "https://policies.google.com/privacy" + isAnonymousUpgradeEnabled = false + isMfaEnabled = false + stringProvider = customStringProvider + transitions = AuthUITransitions( + enterTransition = { slideInHorizontally { it } }, + exitTransition = { slideOutHorizontally { -it } }, + popEnterTransition = { slideInHorizontally { -it } }, + popExitTransition = { slideOutHorizontally { it } } ) - provider( - AuthProvider.Email( - isDisplayNameRequired = true, - isEmailLinkForceSameDeviceEnabled = false, - isEmailLinkSignInEnabled = true, - emailLinkActionCodeSettings = actionCodeSettings { - url = "https://flutterfire-e2e-tests.firebaseapp.com" - handleCodeInApp = true - setAndroidPackageName( - "com.firebaseui.android.demo", - true, - null - ) - }, - isNewAccountsAllowed = true, - minimumPasswordLength = 8, - passwordValidationRules = listOf( - PasswordRule.MinimumLength(8), - PasswordRule.RequireLowercase, - PasswordRule.RequireUppercase, - ), + providers { + provider(AuthProvider.Anonymous) + provider( + AuthProvider.Google( + scopes = listOf("email"), + serverClientId = "406099696497-a12gakvts4epfk5pkio7dphc1anjiggc.apps.googleusercontent.com", + ) ) - ) - provider( - AuthProvider.Phone( - defaultNumber = null, - defaultCountryCode = null, - allowedCountries = emptyList(), - smsCodeLength = 6, - timeout = 120L, - isInstantVerificationEnabled = true + provider( + AuthProvider.Email( + isDisplayNameRequired = true, + isEmailLinkForceSameDeviceEnabled = false, + isEmailLinkSignInEnabled = true, + emailLinkActionCodeSettings = actionCodeSettings { + url = "https://flutterfire-e2e-tests.firebaseapp.com" + handleCodeInApp = true + setAndroidPackageName( + "com.firebaseui.android.demo", + true, + null + ) + }, + isNewAccountsAllowed = true, + minimumPasswordLength = 8, + passwordValidationRules = listOf( + PasswordRule.MinimumLength(8), + PasswordRule.RequireLowercase, + PasswordRule.RequireUppercase, + ), + ) ) - ) - provider( - AuthProvider.Facebook() - ) - provider( - AuthProvider.Twitter( - customParameters = emptyMap() + provider( + AuthProvider.Phone( + defaultNumber = null, + defaultCountryCode = null, + allowedCountries = emptyList(), + smsCodeLength = 6, + timeout = 120L, + isInstantVerificationEnabled = true + ) ) - ) - provider( - AuthProvider.Apple( - customParameters = emptyMap(), - locale = null + provider( + AuthProvider.Facebook() ) - ) - provider( - AuthProvider.Microsoft( - scopes = emptyList(), - tenant = "", - customParameters = emptyMap(), + provider( + AuthProvider.Twitter( + customParameters = emptyMap() + ) ) - ) - provider( - AuthProvider.Github( - scopes = emptyList(), - customParameters = emptyMap(), + provider( + AuthProvider.Apple( + customParameters = emptyMap(), + locale = null + ) ) - ) - provider( - AuthProvider.Yahoo( - scopes = emptyList(), - customParameters = emptyMap(), + provider( + AuthProvider.Microsoft( + scopes = emptyList(), + tenant = "", + customParameters = emptyMap(), + ) ) - ) - provider( - AuthProvider.GenericOAuth( - providerName = "LINE", - providerId = "oidc.line", - scopes = emptyList(), - customParameters = emptyMap(), - buttonLabel = "Sign in with LINE", - buttonIcon = AuthUIAsset.Resource(R.drawable.ic_line_logo_24dp), - buttonColor = Color(0xFF06C755), - contentColor = Color.White + provider( + AuthProvider.Github( + scopes = emptyList(), + customParameters = emptyMap(), + ) ) - ) - provider( - AuthProvider.GenericOAuth( - providerName = "Discord", - providerId = "oidc.discord", - scopes = emptyList(), - customParameters = emptyMap(), - buttonLabel = "Sign in with Discord", - buttonIcon = AuthUIAsset.Resource(R.drawable.ic_discord_24dp), - buttonColor = Color(0xFF5865F2), - contentColor = Color.White + provider( + AuthProvider.Yahoo( + scopes = emptyList(), + customParameters = emptyMap(), + ) ) - ) + provider( + AuthProvider.GenericOAuth( + providerName = "LINE", + providerId = "oidc.line", + scopes = emptyList(), + customParameters = emptyMap(), + buttonLabel = "Sign in with LINE", + buttonIcon = AuthUIAsset.Resource(R.drawable.ic_line_logo_24dp), + buttonColor = Color(0xFF06C755), + contentColor = Color.White + ) + ) + provider( + AuthProvider.GenericOAuth( + providerName = "Discord", + providerId = "oidc.discord", + scopes = emptyList(), + customParameters = emptyMap(), + buttonLabel = "Sign in with Discord", + buttonIcon = AuthUIAsset.Resource(R.drawable.ic_discord_24dp), + buttonColor = Color(0xFF5865F2), + contentColor = Color.White + ) + ) + } } - } - setContent { - AuthUITheme { + AuthUITheme(theme = customTheme) { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background diff --git a/auth/README.md b/auth/README.md index e4311d8f3..1cf4c8e00 100644 --- a/auth/README.md +++ b/auth/README.md @@ -1546,6 +1546,26 @@ val configuration = authUIConfiguration { } ``` +### Customizing the Top App Bar + +Override the colors used by the top app bar shown on auth screens: + +```kotlin +val customTheme = AuthUITheme.Default.copy( + topAppBarColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color(0xFF2E7D32), + scrolledContainerColor = Color(0xFF2E7D32), + ) +) + +val configuration = authUIConfiguration { + providers { provider(AuthProvider.Email()) } + theme = customTheme +} +``` + +If left unset (`null`), the top app bar falls back to colors derived from `colorScheme`'s `primary`/`onPrimary`. + ### Screen Transitions Customize the animations when navigating between screens using the `AuthUITransitions` object: diff --git a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt index 79e78f80f..79a7db901 100644 --- a/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt +++ b/auth/src/main/java/com/firebase/ui/auth/configuration/theme/AuthUITheme.kt @@ -16,9 +16,9 @@ package com.firebase.ui.auth.configuration.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ColorScheme -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Shapes +import androidx.compose.material3.TopAppBarColors import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.Typography import androidx.compose.material3.darkColorScheme @@ -81,6 +81,12 @@ class AuthUITheme( * ``` */ val providerButtonShape: Shape? = null, + + /** + * Custom colors for the top app bar shown on auth screens. If null, falls back to + * colors derived from [colorScheme] (see [AuthUITheme.topAppBarColors]). + */ + val topAppBarColors: TopAppBarColors? = null, ) { /** @@ -91,6 +97,7 @@ class AuthUITheme( * @param shapes The shapes to use. Defaults to this theme's shapes. * @param providerStyles Custom styling for individual providers. Defaults to this theme's provider styles. * @param providerButtonShape Default shape for provider buttons. Defaults to this theme's provider button shape. + * @param topAppBarColors Custom top app bar colors. Defaults to this theme's top app bar colors. * @return A new AuthUITheme instance with the specified properties. */ fun copy( @@ -99,13 +106,15 @@ class AuthUITheme( shapes: Shapes = this.shapes, providerStyles: Map = this.providerStyles, providerButtonShape: Shape? = this.providerButtonShape, + topAppBarColors: TopAppBarColors? = this.topAppBarColors, ): AuthUITheme { return AuthUITheme( colorScheme = colorScheme, typography = typography, shapes = shapes, providerStyles = providerStyles, - providerButtonShape = providerButtonShape + providerButtonShape = providerButtonShape, + topAppBarColors = topAppBarColors ) } @@ -118,6 +127,7 @@ class AuthUITheme( if (shapes != other.shapes) return false if (providerStyles != other.providerStyles) return false if (providerButtonShape != other.providerButtonShape) return false + if (topAppBarColors != other.topAppBarColors) return false return true } @@ -128,12 +138,14 @@ class AuthUITheme( result = 31 * result + shapes.hashCode() result = 31 * result + providerStyles.hashCode() result = 31 * result + (providerButtonShape?.hashCode() ?: 0) + result = 31 * result + (topAppBarColors?.hashCode() ?: 0) return result } override fun toString(): String { return "AuthUITheme(colorScheme=$colorScheme, typography=$typography, shapes=$shapes, " + - "providerStyles=$providerStyles, providerButtonShape=$providerButtonShape)" + "providerStyles=$providerStyles, providerButtonShape=$providerButtonShape, " + + "topAppBarColors=$topAppBarColors)" } /** @@ -228,7 +240,6 @@ class AuthUITheme( ) } - @OptIn(ExperimentalMaterial3Api::class) @get:Composable val topAppBarColors get() = TopAppBarDefaults.topAppBarColors( @@ -236,6 +247,14 @@ class AuthUITheme( titleContentColor = MaterialTheme.colorScheme.onPrimary, navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, ) + + /** + * Resolves the top app bar colors to use for the current [LocalAuthUITheme], falling back + * to [topAppBarColors] when the current theme doesn't specify its own. + */ + @get:Composable + val resolvedTopAppBarColors: TopAppBarColors + get() = LocalAuthUITheme.current.topAppBarColors ?: topAppBarColors } } diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/MfaEnrollmentDefaults.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/MfaEnrollmentDefaults.kt index 61f1151a6..1cbb45949 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/MfaEnrollmentDefaults.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/MfaEnrollmentDefaults.kt @@ -53,6 +53,7 @@ import com.firebase.ui.auth.configuration.AuthUIConfiguration import com.firebase.ui.auth.configuration.MfaFactor import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider +import com.firebase.ui.auth.configuration.theme.AuthUITheme import com.firebase.ui.auth.mfa.MfaEnrollmentContentState import com.firebase.ui.auth.mfa.MfaEnrollmentStep import com.firebase.ui.auth.mfa.toMfaErrorMessage @@ -253,7 +254,8 @@ private fun SelectFactorUI( Scaffold( topBar = { TopAppBar( - title = { Text(stringProvider.mfaManageFactorsTitle) } + title = { Text(stringProvider.mfaManageFactorsTitle) }, + colors = AuthUITheme.resolvedTopAppBarColors ) } ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt index 8b73f2c2d..9d94b6e03 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/ResetPasswordUI.kt @@ -130,7 +130,7 @@ fun ResetPasswordUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInEmailLinkUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInEmailLinkUI.kt index 14e045827..0a27749dc 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInEmailLinkUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInEmailLinkUI.kt @@ -141,7 +141,7 @@ fun SignInEmailLinkUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt index eabde87a1..3093f23f1 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignInUI.kt @@ -170,7 +170,7 @@ fun SignInUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt index dfc413bc6..c05a1c03c 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/email/SignUpUI.kt @@ -95,7 +95,7 @@ fun SignUpUI( val isFormValid = remember(displayName, email, password, confirmPassword) { derivedStateOf { listOf( - displayNameValidator.validate(displayName), + !provider.isDisplayNameRequired || displayNameValidator.validate(displayName), emailValidator.validate(email), passwordValidator.validate(password), confirmPasswordValidator.validate(confirmPassword) @@ -120,7 +120,7 @@ fun SignUpUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterPhoneNumberUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterPhoneNumberUI.kt index ef8bfe545..21e0d5ae5 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterPhoneNumberUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterPhoneNumberUI.kt @@ -99,7 +99,7 @@ fun EnterPhoneNumberUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt index 122e73a87..b678ab4fe 100644 --- a/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt +++ b/auth/src/main/java/com/firebase/ui/auth/ui/screens/phone/EnterVerificationCodeUI.kt @@ -103,7 +103,7 @@ fun EnterVerificationCodeUI( } } }, - colors = AuthUITheme.topAppBarColors + colors = AuthUITheme.resolvedTopAppBarColors ) }, ) { innerPadding -> diff --git a/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt index 46e6a1dc8..caa6f9022 100644 --- a/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt +++ b/auth/src/test/java/com/firebase/ui/auth/configuration/theme/AuthUIThemeTest.kt @@ -7,6 +7,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Shapes import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarColors +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.Typography import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme @@ -313,6 +315,80 @@ class AuthUIThemeTest { assertThat(observedProviderStyles?.get("google.com")?.backgroundColor).isEqualTo(Color.Red) } + // ======================================================================== + // topAppBarColors Tests + // ======================================================================== + + @Test + fun `Default theme has null topAppBarColors`() { + assertThat(AuthUITheme.Default.topAppBarColors).isNull() + } + + @Test + fun `Copy with custom topAppBarColors applies correctly`() { + lateinit var customColors: TopAppBarColors + var observedColors: TopAppBarColors? = null + + composeTestRule.setContent { + customColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Red, + titleContentColor = Color.White, + navigationIconContentColor = Color.White, + ) + val customTheme = AuthUITheme.Default.copy(topAppBarColors = customColors) + + CompositionLocalProvider( + LocalAuthUITheme provides customTheme + ) { + observedColors = LocalAuthUITheme.current.topAppBarColors + } + } + + composeTestRule.waitForIdle() + + assertThat(observedColors).isEqualTo(customColors) + } + + @Test + fun `resolvedTopAppBarColors falls back to default when theme value is null`() { + var resolvedColors: TopAppBarColors? = null + var defaultColors: TopAppBarColors? = null + + composeTestRule.setContent { + AuthUITheme(theme = AuthUITheme.Default) { + defaultColors = AuthUITheme.topAppBarColors + resolvedColors = AuthUITheme.resolvedTopAppBarColors + } + } + + composeTestRule.waitForIdle() + + assertThat(resolvedColors).isEqualTo(defaultColors) + } + + @Test + fun `resolvedTopAppBarColors uses the theme's custom value when set`() { + lateinit var customColors: TopAppBarColors + var resolvedColors: TopAppBarColors? = null + + composeTestRule.setContent { + customColors = TopAppBarDefaults.topAppBarColors( + containerColor = Color.Red, + titleContentColor = Color.White, + navigationIconContentColor = Color.White, + ) + val customTheme = AuthUITheme.Default.copy(topAppBarColors = customColors) + + AuthUITheme(theme = customTheme) { + resolvedColors = AuthUITheme.resolvedTopAppBarColors + } + } + + composeTestRule.waitForIdle() + + assertThat(resolvedColors).isEqualTo(customColors) + } + // ======================================================================== // fromMaterialTheme Tests // ======================================================================== diff --git a/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignUpUITest.kt b/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignUpUITest.kt new file mode 100644 index 000000000..ac642adb7 --- /dev/null +++ b/auth/src/test/java/com/firebase/ui/auth/ui/screens/email/SignUpUITest.kt @@ -0,0 +1,115 @@ +/* + * 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.ui.screens.email + +import android.content.Context +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.hasClickAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performTextInput +import androidx.test.core.app.ApplicationProvider +import com.firebase.ui.auth.configuration.authUIConfiguration +import com.firebase.ui.auth.configuration.auth_provider.AuthProvider +import com.firebase.ui.auth.configuration.string_provider.AuthUIStringProvider +import com.firebase.ui.auth.configuration.string_provider.DefaultAuthUIStringProvider +import com.firebase.ui.auth.configuration.string_provider.LocalAuthUIStringProvider +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * Unit tests for [SignUpUI], covering form validity logic. + * + * @suppress Internal test class + */ +@Config(sdk = [34]) +@RunWith(RobolectricTestRunner::class) +class SignUpUITest { + + @get:Rule + val composeTestRule = createComposeRule() + + private lateinit var applicationContext: Context + private lateinit var stringProvider: AuthUIStringProvider + + @Before + fun setUp() { + applicationContext = ApplicationProvider.getApplicationContext() + stringProvider = DefaultAuthUIStringProvider(applicationContext) + } + + @Test + fun `sign up button becomes enabled when display name is not required and hidden`() { + val provider = AuthProvider.Email( + isDisplayNameRequired = false, + emailLinkActionCodeSettings = null, + passwordValidationRules = emptyList() + ) + val configuration = authUIConfiguration { + context = applicationContext + providers { provider(provider) } + } + + composeTestRule.setContent { + CompositionLocalProvider(LocalAuthUIStringProvider provides stringProvider) { + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var confirmPassword by remember { mutableStateOf("") } + + SignUpUI( + configuration = configuration, + isLoading = false, + displayName = "", + email = email, + password = password, + confirmPassword = confirmPassword, + onDisplayNameChange = { }, + onEmailChange = { email = it }, + onPasswordChange = { password = it }, + onConfirmPasswordChange = { confirmPassword = it }, + onGoToSignIn = { }, + onSignUpClick = { } + ) + } + } + + // Name field should not be rendered since it isn't required. + composeTestRule.onNodeWithText(stringProvider.nameHint).assertDoesNotExist() + + composeTestRule.onNodeWithText(stringProvider.emailHint) + .performTextInput("test@example.com") + composeTestRule.onNodeWithText(stringProvider.passwordHint) + .performTextInput("Password123") + composeTestRule.onNodeWithText(stringProvider.confirmPasswordHint) + .performTextInput("Password123") + + composeTestRule.waitForIdle() + + // With email/password/confirmPassword all valid and no display name required, + // the sign up button should be enabled even though displayName is still "". + composeTestRule.onNode(hasText(stringProvider.signupPageTitle.uppercase()) and hasClickAction()) + .assertIsEnabled() + } +}