From 98a1af457106a4fff56f4344cf13889edfe64d8c Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Tue, 29 Oct 2024 14:20:48 +0100 Subject: [PATCH] feat: rename package `model.auth` to `model.authentication` for consistency Updated references project-wide as well. --- .../com/android/periodpals/MainActivity.kt | 45 +-- .../periodpals/model/auth/AuthModel.kt | 22 -- .../periodpals/model/auth/AuthViewModel.kt | 89 ------ .../model/authentication/AuthModel.kt | 22 ++ .../AuthModelSupabase.kt | 30 +- .../model/authentication/AuthViewModel.kt | 89 ++++++ .../PluginManagerWrapper.kt | 2 +- .../periodpals/ui/authentication/SignIn.kt | 291 +++++++++--------- .../periodpals/ui/authentication/SignUp.kt | 214 ++++++------- .../model/auth/AuthViewModelTest.kt | 169 ---------- .../AuthModelSupabaseTest.kt | 46 +-- .../model/authentication/AuthViewModelTest.kt | 181 +++++++++++ 12 files changed, 610 insertions(+), 590 deletions(-) delete mode 100644 app/src/main/java/com/android/periodpals/model/auth/AuthModel.kt delete mode 100644 app/src/main/java/com/android/periodpals/model/auth/AuthViewModel.kt create mode 100644 app/src/main/java/com/android/periodpals/model/authentication/AuthModel.kt rename app/src/main/java/com/android/periodpals/model/{auth => authentication}/AuthModelSupabase.kt (76%) create mode 100644 app/src/main/java/com/android/periodpals/model/authentication/AuthViewModel.kt rename app/src/main/java/com/android/periodpals/model/{auth => authentication}/PluginManagerWrapper.kt (86%) delete mode 100644 app/src/test/java/com/android/periodpals/model/auth/AuthViewModelTest.kt rename app/src/test/java/com/android/periodpals/model/{auth => authentication}/AuthModelSupabaseTest.kt (82%) create mode 100644 app/src/test/java/com/android/periodpals/model/authentication/AuthViewModelTest.kt diff --git a/app/src/main/java/com/android/periodpals/MainActivity.kt b/app/src/main/java/com/android/periodpals/MainActivity.kt index 592561408..995de2646 100644 --- a/app/src/main/java/com/android/periodpals/MainActivity.kt +++ b/app/src/main/java/com/android/periodpals/MainActivity.kt @@ -26,8 +26,8 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navigation -import com.android.periodpals.model.auth.AuthModelSupabase -import com.android.periodpals.model.auth.AuthViewModel +import com.android.periodpals.model.authentication.AuthModelSupabase +import com.android.periodpals.model.authentication.AuthViewModel import com.android.periodpals.ui.alert.AlertListScreen import com.android.periodpals.ui.alert.AlertScreen import com.android.periodpals.ui.authentication.SignInScreen @@ -55,12 +55,12 @@ class MainActivity : ComponentActivity() { } private val supabaseClient = - createSupabaseClient( - supabaseUrl = BuildConfig.SUPABASE_URL, - supabaseKey = BuildConfig.SUPABASE_KEY, - ) { - install(Auth) - } + createSupabaseClient( + supabaseUrl = BuildConfig.SUPABASE_URL, + supabaseKey = BuildConfig.SUPABASE_KEY, + ) { + install(Auth) + } private val authModel = AuthModelSupabase(supabaseClient) private val authViewModel = AuthViewModel(authModel) @@ -86,16 +86,18 @@ class MainActivity : ComponentActivity() { // Check if location permission is granted or request it if not private fun checkLocationPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == - PackageManager.PERMISSION_GRANTED) { + if ( + ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == + PackageManager.PERMISSION_GRANTED + ) { // **Permission is granted, update state** locationPermissionGranted = true } else { // **Request permission** ActivityCompat.requestPermissions( - this, - arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), - LOCATION_PERMISSION_REQUEST_CODE, + this, + arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), + LOCATION_PERMISSION_REQUEST_CODE, ) } } @@ -103,14 +105,16 @@ class MainActivity : ComponentActivity() { // Handle permission result and check if permission was granted @Deprecated("Deprecated in Java") override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, + requestCode: Int, + permissions: Array, + grantResults: IntArray, ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == LOCATION_PERMISSION_REQUEST_CODE && + if ( + requestCode == LOCATION_PERMISSION_REQUEST_CODE && grantResults.isNotEmpty() && - grantResults[0] == PackageManager.PERMISSION_GRANTED) { + grantResults[0] == PackageManager.PERMISSION_GRANTED + ) { // **Permission granted, update state** locationPermissionGranted = true } else { @@ -127,10 +131,7 @@ fun PeriodPalsApp(locationPermissionGranted: Boolean, authViewModel: AuthViewMod NavHost(navController = navController, startDestination = Route.AUTH) { // Authentication - navigation( - startDestination = Screen.SIGN_IN, - route = Route.AUTH, - ) { + navigation(startDestination = Screen.SIGN_IN, route = Route.AUTH) { composable(Screen.SIGN_IN) { SignInScreen(authViewModel, navigationActions) } composable(Screen.SIGN_UP) { SignUpScreen(authViewModel, navigationActions) } composable(Screen.CREATE_PROFILE) { CreateProfileScreen(navigationActions) } diff --git a/app/src/main/java/com/android/periodpals/model/auth/AuthModel.kt b/app/src/main/java/com/android/periodpals/model/auth/AuthModel.kt deleted file mode 100644 index c95b33e9e..000000000 --- a/app/src/main/java/com/android/periodpals/model/auth/AuthModel.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.android.periodpals.model.auth - -interface AuthModel { - - suspend fun login( - userEmail: String, - userPassword: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, - ) - - suspend fun register( - userEmail: String, - userPassword: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, - ) - - suspend fun logout(onSuccess: () -> Unit, onFailure: (Exception) -> Unit) - - suspend fun isUserLoggedIn(token: String, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) -} diff --git a/app/src/main/java/com/android/periodpals/model/auth/AuthViewModel.kt b/app/src/main/java/com/android/periodpals/model/auth/AuthViewModel.kt deleted file mode 100644 index d732d0ca3..000000000 --- a/app/src/main/java/com/android/periodpals/model/auth/AuthViewModel.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.android.periodpals.model.auth - -import android.content.Context -import android.util.Log -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.android.periodpals.model.user.UserAuthState -import kotlinx.coroutines.launch - -private const val TAG = "AuthViewModel" - -class AuthViewModel(private val authModel: AuthModel) : ViewModel() { - - private val _userAuthState = mutableStateOf(UserAuthState.Loading) - val userAuthState: State = _userAuthState - - fun signUpWithEmail(context: Context, userEmail: String, userPassword: String) { - _userAuthState.value = UserAuthState.Loading - viewModelScope.launch { - authModel.register( - userEmail = userEmail, - userPassword = userPassword, - onSuccess = { - Log.d(TAG, "signUpWithEmail: registered user successfully") - _userAuthState.value = UserAuthState.Success("Registered user successfully") - }, - onFailure = { e: Exception -> - Log.d(TAG, "signUpWithEmail: failed to register user: $e") - _userAuthState.value = UserAuthState.Error("Error: $e") - }, - ) - } - } - - fun logInWithEmail(context: Context, userEmail: String, userPassword: String) { - _userAuthState.value = UserAuthState.Loading - viewModelScope.launch { - authModel.login( - userEmail = userEmail, - userPassword = userPassword, - onSuccess = { - Log.d(TAG, "logInWithEmail: logged in successfully") - _userAuthState.value = UserAuthState.Success("Logged in successfully") - }, - onFailure = { e: Exception -> - Log.d(TAG, "logInWithEmail: failed to log in: $e") - _userAuthState.value = UserAuthState.Error("Error: $e") - }, - ) - } - } - - fun logOut(context: Context) { - // val sharedPreferenceHelper = SharedPreferenceHelper(context) - _userAuthState.value = UserAuthState.Loading - viewModelScope.launch { - authModel.logout( - onSuccess = { - Log.d(TAG, "logOut: logged out successfully") - // sharedPreferenceHelper.clearPreferences() - _userAuthState.value = UserAuthState.Success("Logged out successfully") - }, - onFailure = { e: Exception -> - Log.d(TAG, "logOut: failed to log out: $e") - _userAuthState.value = UserAuthState.Error("Error: $e") - }, - ) - } - } - - fun isUserLoggedIn(context: Context) { - viewModelScope.launch { - // call model for this ofc - authModel.isUserLoggedIn( - token = "", - onSuccess = { - Log.d(TAG, "isUserLoggedIn: user is confirmed logged in") - _userAuthState.value = UserAuthState.Success("User is logged in") - }, - onFailure = { - Log.d(TAG, "isUserLoggedIn: user is not logged in") - _userAuthState.value = UserAuthState.Error("User is not logged in") - }, - ) - } - } -} diff --git a/app/src/main/java/com/android/periodpals/model/authentication/AuthModel.kt b/app/src/main/java/com/android/periodpals/model/authentication/AuthModel.kt new file mode 100644 index 000000000..99c8d53da --- /dev/null +++ b/app/src/main/java/com/android/periodpals/model/authentication/AuthModel.kt @@ -0,0 +1,22 @@ +package com.android.periodpals.model.authentication + +interface AuthModel { + + suspend fun login( + userEmail: String, + userPassword: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, + ) + + suspend fun register( + userEmail: String, + userPassword: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, + ) + + suspend fun logout(onSuccess: () -> Unit, onFailure: (Exception) -> Unit) + + suspend fun isUserLoggedIn(token: String, onSuccess: () -> Unit, onFailure: (Exception) -> Unit) +} diff --git a/app/src/main/java/com/android/periodpals/model/auth/AuthModelSupabase.kt b/app/src/main/java/com/android/periodpals/model/authentication/AuthModelSupabase.kt similarity index 76% rename from app/src/main/java/com/android/periodpals/model/auth/AuthModelSupabase.kt rename to app/src/main/java/com/android/periodpals/model/authentication/AuthModelSupabase.kt index c9d4765b8..a4c5e0fec 100644 --- a/app/src/main/java/com/android/periodpals/model/auth/AuthModelSupabase.kt +++ b/app/src/main/java/com/android/periodpals/model/authentication/AuthModelSupabase.kt @@ -1,4 +1,4 @@ -package com.android.periodpals.model.auth +package com.android.periodpals.model.authentication import android.util.Log import io.github.jan.supabase.SupabaseClient @@ -9,18 +9,18 @@ import io.github.jan.supabase.auth.providers.builtin.Email private const val TAG = "AuthModelSupabase" class AuthModelSupabase( - private val supabase: SupabaseClient, - private val pluginManagerWrapper: PluginManagerWrapper = - PluginManagerWrapperImpl(supabase.pluginManager), + private val supabase: SupabaseClient, + private val pluginManagerWrapper: PluginManagerWrapper = + PluginManagerWrapperImpl(supabase.pluginManager), ) : AuthModel { private val supabaseAuth: Auth = pluginManagerWrapper.getAuthPlugin() override suspend fun register( - userEmail: String, - userPassword: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + userEmail: String, + userPassword: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { supabaseAuth.signUpWith(Email) { @@ -36,10 +36,10 @@ class AuthModelSupabase( } override suspend fun login( - userEmail: String, - userPassword: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + userEmail: String, + userPassword: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { supabaseAuth.signInWith(Email) { @@ -66,9 +66,9 @@ class AuthModelSupabase( } override suspend fun isUserLoggedIn( - token: String, - onSuccess: () -> Unit, - onFailure: (Exception) -> Unit, + token: String, + onSuccess: () -> Unit, + onFailure: (Exception) -> Unit, ) { try { if (null != supabaseAuth.currentUserOrNull()) { diff --git a/app/src/main/java/com/android/periodpals/model/authentication/AuthViewModel.kt b/app/src/main/java/com/android/periodpals/model/authentication/AuthViewModel.kt new file mode 100644 index 000000000..ed59b5f6a --- /dev/null +++ b/app/src/main/java/com/android/periodpals/model/authentication/AuthViewModel.kt @@ -0,0 +1,89 @@ +package com.android.periodpals.model.authentication + +import android.content.Context +import android.util.Log +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.android.periodpals.model.user.UserAuthState +import kotlinx.coroutines.launch + +private const val TAG = "AuthViewModel" + +class AuthViewModel(private val authModel: AuthModel) : ViewModel() { + + private val _userAuthState = mutableStateOf(UserAuthState.Loading) + val userAuthState: State = _userAuthState + + fun signUpWithEmail(context: Context, userEmail: String, userPassword: String) { + _userAuthState.value = UserAuthState.Loading + viewModelScope.launch { + authModel.register( + userEmail = userEmail, + userPassword = userPassword, + onSuccess = { + Log.d(TAG, "signUpWithEmail: registered user successfully") + _userAuthState.value = UserAuthState.Success("Registered user successfully") + }, + onFailure = { e: Exception -> + Log.d(TAG, "signUpWithEmail: failed to register user: $e") + _userAuthState.value = UserAuthState.Error("Error: $e") + }, + ) + } + } + + fun logInWithEmail(context: Context, userEmail: String, userPassword: String) { + _userAuthState.value = UserAuthState.Loading + viewModelScope.launch { + authModel.login( + userEmail = userEmail, + userPassword = userPassword, + onSuccess = { + Log.d(TAG, "logInWithEmail: logged in successfully") + _userAuthState.value = UserAuthState.Success("Logged in successfully") + }, + onFailure = { e: Exception -> + Log.d(TAG, "logInWithEmail: failed to log in: $e") + _userAuthState.value = UserAuthState.Error("Error: $e") + }, + ) + } + } + + fun logOut(context: Context) { + // val sharedPreferenceHelper = SharedPreferenceHelper(context) + _userAuthState.value = UserAuthState.Loading + viewModelScope.launch { + authModel.logout( + onSuccess = { + Log.d(TAG, "logOut: logged out successfully") + // sharedPreferenceHelper.clearPreferences() + _userAuthState.value = UserAuthState.Success("Logged out successfully") + }, + onFailure = { e: Exception -> + Log.d(TAG, "logOut: failed to log out: $e") + _userAuthState.value = UserAuthState.Error("Error: $e") + }, + ) + } + } + + fun isUserLoggedIn(context: Context) { + viewModelScope.launch { + // call model for this ofc + authModel.isUserLoggedIn( + token = "", + onSuccess = { + Log.d(TAG, "isUserLoggedIn: user is confirmed logged in") + _userAuthState.value = UserAuthState.Success("User is logged in") + }, + onFailure = { + Log.d(TAG, "isUserLoggedIn: user is not logged in") + _userAuthState.value = UserAuthState.Error("User is not logged in") + }, + ) + } + } +} diff --git a/app/src/main/java/com/android/periodpals/model/auth/PluginManagerWrapper.kt b/app/src/main/java/com/android/periodpals/model/authentication/PluginManagerWrapper.kt similarity index 86% rename from app/src/main/java/com/android/periodpals/model/auth/PluginManagerWrapper.kt rename to app/src/main/java/com/android/periodpals/model/authentication/PluginManagerWrapper.kt index 844b1c8a4..3a2c0595a 100644 --- a/app/src/main/java/com/android/periodpals/model/auth/PluginManagerWrapper.kt +++ b/app/src/main/java/com/android/periodpals/model/authentication/PluginManagerWrapper.kt @@ -1,4 +1,4 @@ -package com.android.periodpals.model.auth +package com.android.periodpals.model.authentication import io.github.jan.supabase.auth.Auth import io.github.jan.supabase.plugins.PluginManager diff --git a/app/src/main/java/com/android/periodpals/ui/authentication/SignIn.kt b/app/src/main/java/com/android/periodpals/ui/authentication/SignIn.kt index 440ca14e7..516691b22 100644 --- a/app/src/main/java/com/android/periodpals/ui/authentication/SignIn.kt +++ b/app/src/main/java/com/android/periodpals/ui/authentication/SignIn.kt @@ -41,7 +41,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.android.periodpals.R -import com.android.periodpals.model.auth.AuthViewModel +import com.android.periodpals.model.authentication.AuthViewModel import com.android.periodpals.model.user.UserAuthState import com.android.periodpals.ui.components.AuthButton import com.android.periodpals.ui.components.AuthEmailInput @@ -74,131 +74,134 @@ fun SignInScreen(authViewModel: AuthViewModel, navigationActions: NavigationActi // Screen Scaffold( - modifier = Modifier.fillMaxSize().testTag("signInScreen"), - content = { padding -> - // Purple-ish background - GradedBackground(Purple80, Pink40, PurpleGrey80, "signInBackground") + modifier = Modifier.fillMaxSize().testTag("signInScreen"), + content = { padding -> + // Purple-ish background + GradedBackground(Purple80, Pink40, PurpleGrey80, "signInBackground") - Column( - modifier = Modifier.fillMaxSize().padding(padding).padding(60.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(48.dp, Alignment.CenterVertically), + Column( + modifier = Modifier.fillMaxSize().padding(padding).padding(60.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(48.dp, Alignment.CenterVertically), + ) { + // Welcome text + AuthWelcomeText( + text = "Welcome to PeriodPals", + color = Color.Black, + testTag = "signInTitle", + ) + + // Rectangle with login fields and button + Box( + modifier = + Modifier.fillMaxWidth() + .border(1.dp, Color.Gray, RectangleShape) + .background(Color.White) + .padding(24.dp) ) { - // Welcome text - AuthWelcomeText( - text = "Welcome to PeriodPals", color = Color.Black, testTag = "signInTitle") - - // Rectangle with login fields and button - Box( - modifier = - Modifier.fillMaxWidth() - .border(1.dp, Color.Gray, RectangleShape) - .background(Color.White) - .padding(24.dp)) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically)) { - // Sign in instruction - AuthInstruction( - text = "Sign in to your account", testTag = "signInInstruction") - - // Email input and error message - AuthEmailInput( - email = email, onEmailChange = { email = it }, testTag = "signInEmail") - if (emailErrorMessage.isNotEmpty()) { - ErrorText(emailErrorMessage, "signInEmailError") - } - - // Password input and error message - AuthPasswordInput( - password = password, - onPasswordChange = { password = it }, - passwordVisible = passwordVisible, - onPasswordVisibilityChange = { passwordVisible = !passwordVisible }, - testTag = "signInPassword", - visibilityTestTag = "signInPasswordVisibility") - if (passwordErrorMessage.isNotEmpty()) { - ErrorText(passwordErrorMessage, "signInPasswordError") - } - - // Sign in button - AuthButton( - text = "Sign in", - onClick = { - emailErrorMessage = validateEmail(email) - passwordErrorMessage = validatePassword(password) - - if (emailErrorMessage.isEmpty() && passwordErrorMessage.isEmpty()) { - authViewModel.logInWithEmail(context, email, password) - authViewModel.isUserLoggedIn(context) - val loginSuccess = userState is UserAuthState.Success - if (loginSuccess) { - // with supabase - Toast.makeText(context, "Login Successful", Toast.LENGTH_SHORT) - .show() - navigationActions.navigateTo(Screen.PROFILE) - } else { - Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText( - context, "Invalid email or password.", Toast.LENGTH_SHORT) - .show() - } - emailErrorMessage = validateEmail(email) - passwordErrorMessage = validatePassword(password) - - if (emailErrorMessage.isEmpty() && passwordErrorMessage.isEmpty()) { - authViewModel.logInWithEmail(context, email, password) - authViewModel.isUserLoggedIn(context) - val loginSuccess = userState is UserAuthState.Success - if (loginSuccess) { - Toast.makeText(context, "Login Successful", Toast.LENGTH_SHORT) - .show() - } else { - Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show() - } - } else { - Toast.makeText( - context, "Invalid email or password.", Toast.LENGTH_SHORT) - .show() - } - }, - testTag = "signInButton") - - // Or continue with text - AuthSecondInstruction(text = "Or continue with", testTag = "signInOrText") - - // Google sign in button - GoogleButton( - onClick = { - Toast.makeText( - context, - "Use other login method for now, thanks!", - Toast.LENGTH_SHORT) - .show() - }, - testTag = "signInGoogleButton") - } - } - // Not registered yet? Sign up here! - val annotatedText = buildAnnotatedString { - append("Not registered yet? ") - pushStringAnnotation(tag = "SignUp", annotation = "SignUp") - withStyle(style = SpanStyle(color = Color.Blue)) { append("Sign up here!") } - pop() + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically), + ) { + // Sign in instruction + AuthInstruction(text = "Sign in to your account", testTag = "signInInstruction") + + // Email input and error message + AuthEmailInput(email = email, onEmailChange = { email = it }, testTag = "signInEmail") + if (emailErrorMessage.isNotEmpty()) { + ErrorText(emailErrorMessage, "signInEmailError") + } + + // Password input and error message + AuthPasswordInput( + password = password, + onPasswordChange = { password = it }, + passwordVisible = passwordVisible, + onPasswordVisibilityChange = { passwordVisible = !passwordVisible }, + testTag = "signInPassword", + visibilityTestTag = "signInPasswordVisibility", + ) + if (passwordErrorMessage.isNotEmpty()) { + ErrorText(passwordErrorMessage, "signInPasswordError") + } + + // Sign in button + AuthButton( + text = "Sign in", + onClick = { + emailErrorMessage = validateEmail(email) + passwordErrorMessage = validatePassword(password) + + if (emailErrorMessage.isEmpty() && passwordErrorMessage.isEmpty()) { + authViewModel.logInWithEmail(context, email, password) + authViewModel.isUserLoggedIn(context) + val loginSuccess = userState is UserAuthState.Success + if (loginSuccess) { + // with supabase + Toast.makeText(context, "Login Successful", Toast.LENGTH_SHORT).show() + navigationActions.navigateTo(Screen.PROFILE) + } else { + Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(context, "Invalid email or password.", Toast.LENGTH_SHORT).show() + } + emailErrorMessage = validateEmail(email) + passwordErrorMessage = validatePassword(password) + + if (emailErrorMessage.isEmpty() && passwordErrorMessage.isEmpty()) { + authViewModel.logInWithEmail(context, email, password) + authViewModel.isUserLoggedIn(context) + val loginSuccess = userState is UserAuthState.Success + if (loginSuccess) { + Toast.makeText(context, "Login Successful", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(context, "Login Failed", Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(context, "Invalid email or password.", Toast.LENGTH_SHORT).show() + } + }, + testTag = "signInButton", + ) + + // Or continue with text + AuthSecondInstruction(text = "Or continue with", testTag = "signInOrText") + + // Google sign in button + GoogleButton( + onClick = { + Toast.makeText( + context, + "Use other login method for now, thanks!", + Toast.LENGTH_SHORT, + ) + .show() + }, + testTag = "signInGoogleButton", + ) } - ClickableText( - modifier = Modifier.testTag("signInNotRegistered"), - text = annotatedText, - onClick = { offset -> - annotatedText - .getStringAnnotations(tag = "SignUp", start = offset, end = offset) - .firstOrNull() - ?.let { navigationActions.navigateTo(Screen.SIGN_UP) } - }) } - }) + // Not registered yet? Sign up here! + val annotatedText = buildAnnotatedString { + append("Not registered yet? ") + pushStringAnnotation(tag = "SignUp", annotation = "SignUp") + withStyle(style = SpanStyle(color = Color.Blue)) { append("Sign up here!") } + pop() + } + ClickableText( + modifier = Modifier.testTag("signInNotRegistered"), + text = annotatedText, + onClick = { offset -> + annotatedText + .getStringAnnotations(tag = "SignUp", start = offset, end = offset) + .firstOrNull() + ?.let { navigationActions.navigateTo(Screen.SIGN_UP) } + }, + ) + } + }, + ) } /** Validates the email and returns an error message if the email is invalid. */ @@ -228,24 +231,28 @@ private fun validatePassword(password: String): String { @Composable fun GoogleButton(onClick: () -> Unit, modifier: Modifier = Modifier, testTag: String) { Button( - modifier = modifier.wrapContentSize().testTag(testTag), - onClick = onClick, - colors = ButtonDefaults.buttonColors(containerColor = Color.White), - shape = RoundedCornerShape(50), - border = BorderStroke(1.dp, Color.LightGray)) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center) { - Image( - painter = painterResource(id = R.drawable.google_logo), - contentDescription = "Google Logo", - modifier = Modifier.size(24.dp)) - Spacer(modifier = Modifier.size(8.dp)) - Text( - text = "Sign in with Google", - color = Color.Black, - fontWeight = FontWeight.Medium, - style = MaterialTheme.typography.bodyMedium) - } - } + modifier = modifier.wrapContentSize().testTag(testTag), + onClick = onClick, + colors = ButtonDefaults.buttonColors(containerColor = Color.White), + shape = RoundedCornerShape(50), + border = BorderStroke(1.dp, Color.LightGray), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + ) { + Image( + painter = painterResource(id = R.drawable.google_logo), + contentDescription = "Google Logo", + modifier = Modifier.size(24.dp), + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "Sign in with Google", + color = Color.Black, + fontWeight = FontWeight.Medium, + style = MaterialTheme.typography.bodyMedium, + ) + } + } } diff --git a/app/src/main/java/com/android/periodpals/ui/authentication/SignUp.kt b/app/src/main/java/com/android/periodpals/ui/authentication/SignUp.kt index 061e09b2c..8cebbd5c2 100644 --- a/app/src/main/java/com/android/periodpals/ui/authentication/SignUp.kt +++ b/app/src/main/java/com/android/periodpals/ui/authentication/SignUp.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp -import com.android.periodpals.model.auth.AuthViewModel +import com.android.periodpals.model.authentication.AuthViewModel import com.android.periodpals.model.user.UserAuthState import com.android.periodpals.ui.components.AuthButton import com.android.periodpals.ui.components.AuthEmailInput @@ -59,114 +59,114 @@ fun SignUpScreen(authViewModel: AuthViewModel, navigationActions: NavigationActi // Screen Scaffold( - modifier = Modifier.fillMaxSize().testTag("signUpScreen"), - content = { padding -> - // Purple-ish background - GradedBackground(Pink40, Purple40, PurpleGrey80, "signUpBackground") - - Column( - modifier = Modifier.fillMaxSize().padding(padding).padding(60.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(48.dp, Alignment.CenterVertically), + modifier = Modifier.fillMaxSize().testTag("signUpScreen"), + content = { padding -> + // Purple-ish background + GradedBackground(Pink40, Purple40, PurpleGrey80, "signUpBackground") + + Column( + modifier = Modifier.fillMaxSize().padding(padding).padding(60.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(48.dp, Alignment.CenterVertically), + ) { + // Welcome text + AuthWelcomeText( + text = "Welcome to PeriodPals", + color = Color.White, + testTag = "signUpTitle", + ) + + // Rectangle with login fields and button + Box( + modifier = + Modifier.fillMaxWidth() + .border(1.dp, Color.Gray, RectangleShape) + .background(Color.White) + .padding(24.dp) ) { - // Welcome text - AuthWelcomeText( - text = "Welcome to PeriodPals", color = Color.White, testTag = "signUpTitle") - - // Rectangle with login fields and button - Box( - modifier = - Modifier.fillMaxWidth() - .border(1.dp, Color.Gray, RectangleShape) - .background(Color.White) - .padding(24.dp)) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically)) { - // Sign up instruction - AuthInstruction(text = "Create your account", testTag = "signUpInstruction") - - // Email input and error message - AuthEmailInput( - email = email, onEmailChange = { email = it }, testTag = "signUpEmail") - if (emailErrorMessage.isNotEmpty()) { - ErrorText(message = emailErrorMessage, testTag = "signUpEmailError") - } - - // Password input and error message - AuthPasswordInput( - password = password, - onPasswordChange = { - password = it - passwordErrorMessage = validatePassword(password) - }, - passwordVisible = passwordVisible, - onPasswordVisibilityChange = { passwordVisible = !passwordVisible }, - testTag = "signUpPassword", - visibilityTestTag = "signUpPasswordVisibility") - if (passwordErrorMessage.isNotEmpty()) { - ErrorText(message = passwordErrorMessage, testTag = "signUpPasswordError") - } - - // Confirm password text - AuthSecondInstruction( - text = "Confirm your password", testTag = "signUpConfirmText") - - // Confirm password input and error message - AuthPasswordInput( - password = confirm, - onPasswordChange = { confirm = it }, - passwordVisible = confirmVisible, - onPasswordVisibilityChange = { confirmVisible = !confirmVisible }, - testTag = "signUpConfirmPassword", - visibilityTestTag = "signUpConfirmVisibility") - if (confirmErrorMessage.isNotEmpty()) { - ErrorText(message = confirmErrorMessage, testTag = "signUpConfirmError") - } - - // Sign up button - AuthButton( - text = "Sign up", - onClick = { - emailErrorMessage = validateEmail(email) - passwordErrorMessage = validatePassword(password) - confirmErrorMessage = validateConfirmPassword(password, confirm) - - if (emailErrorMessage.isEmpty() && - passwordErrorMessage.isEmpty() && - confirmErrorMessage.isEmpty()) { - if (email.isNotEmpty() && password.isNotEmpty()) { - authViewModel.signUpWithEmail(context, email, password) - authViewModel.isUserLoggedIn(context) - val loginSuccess = userState is UserAuthState.Success - if (loginSuccess) { - Toast.makeText( - context, - "Account Creation Successful", - Toast.LENGTH_SHORT) - .show() - navigationActions.navigateTo(Screen.CREATE_PROFILE) - } else { - Toast.makeText( - context, "Account Creation Failed", Toast.LENGTH_SHORT) - .show() - } - } else { - Toast.makeText(context, "Email cannot be empty", Toast.LENGTH_SHORT) - .show() - } - } else { - Toast.makeText( - context, "Invalid email or password", Toast.LENGTH_SHORT) - .show() - } - }, - testTag = "signUpButton", - ) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically), + ) { + // Sign up instruction + AuthInstruction(text = "Create your account", testTag = "signUpInstruction") + + // Email input and error message + AuthEmailInput(email = email, onEmailChange = { email = it }, testTag = "signUpEmail") + if (emailErrorMessage.isNotEmpty()) { + ErrorText(message = emailErrorMessage, testTag = "signUpEmailError") + } + + // Password input and error message + AuthPasswordInput( + password = password, + onPasswordChange = { + password = it + passwordErrorMessage = validatePassword(password) + }, + passwordVisible = passwordVisible, + onPasswordVisibilityChange = { passwordVisible = !passwordVisible }, + testTag = "signUpPassword", + visibilityTestTag = "signUpPasswordVisibility", + ) + if (passwordErrorMessage.isNotEmpty()) { + ErrorText(message = passwordErrorMessage, testTag = "signUpPasswordError") + } + + // Confirm password text + AuthSecondInstruction(text = "Confirm your password", testTag = "signUpConfirmText") + + // Confirm password input and error message + AuthPasswordInput( + password = confirm, + onPasswordChange = { confirm = it }, + passwordVisible = confirmVisible, + onPasswordVisibilityChange = { confirmVisible = !confirmVisible }, + testTag = "signUpConfirmPassword", + visibilityTestTag = "signUpConfirmVisibility", + ) + if (confirmErrorMessage.isNotEmpty()) { + ErrorText(message = confirmErrorMessage, testTag = "signUpConfirmError") + } + + // Sign up button + AuthButton( + text = "Sign up", + onClick = { + emailErrorMessage = validateEmail(email) + passwordErrorMessage = validatePassword(password) + confirmErrorMessage = validateConfirmPassword(password, confirm) + + if ( + emailErrorMessage.isEmpty() && + passwordErrorMessage.isEmpty() && + confirmErrorMessage.isEmpty() + ) { + if (email.isNotEmpty() && password.isNotEmpty()) { + authViewModel.signUpWithEmail(context, email, password) + authViewModel.isUserLoggedIn(context) + val loginSuccess = userState is UserAuthState.Success + if (loginSuccess) { + Toast.makeText(context, "Account Creation Successful", Toast.LENGTH_SHORT) + .show() + navigationActions.navigateTo(Screen.CREATE_PROFILE) + } else { + Toast.makeText(context, "Account Creation Failed", Toast.LENGTH_SHORT).show() } - } + } else { + Toast.makeText(context, "Email cannot be empty", Toast.LENGTH_SHORT).show() + } + } else { + Toast.makeText(context, "Invalid email or password", Toast.LENGTH_SHORT).show() + } + }, + testTag = "signUpButton", + ) + } } - }) + } + }, + ) } /** Validates the email field is not empty, contains an '@' character and is not already used. */ @@ -200,7 +200,7 @@ private fun validatePassword(password: String): String { password.length < 8 -> "Password must be at least 8 characters long" !capitalLetter.containsMatchIn(password) -> "Password must contain at least one capital letter" !minusculeLetter.containsMatchIn(password) -> - "Password must contain at least one lower case letter" + "Password must contain at least one lower case letter" !number.containsMatchIn(password) -> "Password must contain at least one number" !specialChar.containsMatchIn(password) -> "Password must contain at least one special character" else -> "" diff --git a/app/src/test/java/com/android/periodpals/model/auth/AuthViewModelTest.kt b/app/src/test/java/com/android/periodpals/model/auth/AuthViewModelTest.kt deleted file mode 100644 index 274b4258d..000000000 --- a/app/src/test/java/com/android/periodpals/model/auth/AuthViewModelTest.kt +++ /dev/null @@ -1,169 +0,0 @@ -package com.android.periodpals.model.auth - -import android.content.Context -import com.android.periodpals.MainCoroutineRule -import com.android.periodpals.model.user.UserAuthState -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any -import org.mockito.kotlin.doAnswer - -@OptIn(ExperimentalCoroutinesApi::class) -class AuthViewModelTest { - - @Mock private lateinit var mockContext: Context - - @Mock private lateinit var authModel: AuthModelSupabase - - private lateinit var authViewModel: AuthViewModel - - @ExperimentalCoroutinesApi @get:Rule var mainCoroutineRule = MainCoroutineRule() - - @Before - fun setup() { - MockitoAnnotations.openMocks(this) - authViewModel = AuthViewModel(authModel) - } - - @Test - fun `signUpWithEmail success`() = runBlocking { - doAnswer { inv -> (inv.getArgument<() -> Unit>(2))() } - .`when`(authModel) - .register(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.signUpWithEmail( - context = mockContext, userEmail = "example@email.com", userPassword = "password") - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Success -> true - else -> false - } - assert(result) - } - - @Test - fun `signUpWithEmail failure`() = runBlocking { - doAnswer { inv -> (inv.getArgument<(Exception) -> Unit>(3))(Exception("Heyhey")) } - .`when`(authModel) - .register(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.signUpWithEmail( - context = mockContext, userEmail = "example@email.com", userPassword = "password") - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Error -> true - else -> false - } - assert(result) - } - - @Test - fun `signInWithEmail success`() = runBlocking { - doAnswer { inv -> inv.getArgument<() -> Unit>(2)() } - .`when`(authModel) - .login(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.logInWithEmail( - context = mockContext, userEmail = "example@email.com", userPassword = "password") - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Success -> true - else -> false - } - assert(result) - } - - @Test - fun `signInWithEmail failure`() = runBlocking { - doAnswer { inv -> - val onFailure = inv.getArgument<(Exception) -> Unit>(3) - onFailure(Exception("heyhey")) - } - .`when`(authModel) - .login(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.logInWithEmail( - context = mockContext, userEmail = "example@email.com", userPassword = "password") - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Success -> false - is UserAuthState.Error -> true - is UserAuthState.Loading -> false - else -> false - } - assert(result) - } - - @Test - fun `logOut success`() = runBlocking { - doAnswer { inv -> inv.getArgument<() -> Unit>(0)() } - .`when`(authModel) - .logout(any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.logOut(context = mockContext) - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Success -> true - else -> false - } - assert(result) - } - - @Test - fun `logOut failure`() = runBlocking { - doAnswer { inv -> inv.getArgument<(Exception) -> Unit>(1)(Exception("eyyo pogger")) } - .`when`(authModel) - .logout(any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.logOut(context = mockContext) - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Error -> true - else -> false - } - assert(result) - } - - @Test - fun `isUserLoggedIn success`() = runBlocking { - doAnswer { inv -> inv.getArgument<() -> Unit>(1)() } - .`when`(authModel) - .isUserLoggedIn(any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.isUserLoggedIn(mockContext) - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Success -> true - else -> false - } - assert(result) - } - - @Test - fun `isUserLoggedIn failure`() = runBlocking { - doAnswer { inv -> inv.getArgument<(Exception) -> Unit>(2)(Exception("eyyo pogger")) } - .`when`(authModel) - .isUserLoggedIn(any(), any<() -> Unit>(), any<(Exception) -> Unit>()) - - authViewModel.isUserLoggedIn(mockContext) - - val result = - when (authViewModel.userAuthState.value) { - is UserAuthState.Error -> true - else -> false - } - assert(result) - } -} diff --git a/app/src/test/java/com/android/periodpals/model/auth/AuthModelSupabaseTest.kt b/app/src/test/java/com/android/periodpals/model/authentication/AuthModelSupabaseTest.kt similarity index 82% rename from app/src/test/java/com/android/periodpals/model/auth/AuthModelSupabaseTest.kt rename to app/src/test/java/com/android/periodpals/model/authentication/AuthModelSupabaseTest.kt index 069fc9309..da6046973 100644 --- a/app/src/test/java/com/android/periodpals/model/auth/AuthModelSupabaseTest.kt +++ b/app/src/test/java/com/android/periodpals/model/authentication/AuthModelSupabaseTest.kt @@ -1,4 +1,4 @@ -package com.android.periodpals.model.auth +package com.android.periodpals.model.authentication import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.auth.Auth @@ -50,10 +50,10 @@ class AuthModelSupabaseTest { var successCalled = false authModel.register( - "test@example.com", - "password", - { successCalled = true }, - { fail("Should not call onFailure") }, + "test@example.com", + "password", + { successCalled = true }, + { fail("Should not call onFailure") }, ) assert(successCalled) @@ -66,10 +66,10 @@ class AuthModelSupabaseTest { var failureCalled = false authModel.register( - "test@example.com", - "password", - { fail("Should not call onSuccess") }, - { failureCalled = true }, + "test@example.com", + "password", + { fail("Should not call onSuccess") }, + { failureCalled = true }, ) assert(failureCalled) @@ -81,10 +81,10 @@ class AuthModelSupabaseTest { var successCalled = false authModel.login( - "test@example.com", - "password", - { successCalled = true }, - { fail("Should not call onFailure") }, + "test@example.com", + "password", + { successCalled = true }, + { fail("Should not call onFailure") }, ) assert(successCalled) @@ -97,10 +97,10 @@ class AuthModelSupabaseTest { var failureCalled = false authModel.login( - "test@example.com", - "password", - { fail("Should not call onSuccess") }, - { failureCalled = true }, + "test@example.com", + "password", + { fail("Should not call onSuccess") }, + { failureCalled = true }, ) assert(failureCalled) @@ -138,9 +138,9 @@ class AuthModelSupabaseTest { var successCalled = false authModel.isUserLoggedIn( - "token", - { successCalled = true }, - { fail("Should not call onFailure") }, + "token", + { successCalled = true }, + { fail("Should not call onFailure") }, ) assert(successCalled) @@ -154,9 +154,9 @@ class AuthModelSupabaseTest { var failureCalled = false authModel.isUserLoggedIn( - "token", - { fail("Should not call onSuccess") }, - { failureCalled = true }, + "token", + { fail("Should not call onSuccess") }, + { failureCalled = true }, ) assert(failureCalled) diff --git a/app/src/test/java/com/android/periodpals/model/authentication/AuthViewModelTest.kt b/app/src/test/java/com/android/periodpals/model/authentication/AuthViewModelTest.kt new file mode 100644 index 000000000..e30a24bf8 --- /dev/null +++ b/app/src/test/java/com/android/periodpals/model/authentication/AuthViewModelTest.kt @@ -0,0 +1,181 @@ +package com.android.periodpals.model.authentication + +import android.content.Context +import com.android.periodpals.MainCoroutineRule +import com.android.periodpals.model.user.UserAuthState +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer + +@OptIn(ExperimentalCoroutinesApi::class) +class AuthViewModelTest { + + @Mock private lateinit var mockContext: Context + + @Mock private lateinit var authModel: AuthModelSupabase + + private lateinit var authViewModel: AuthViewModel + + @ExperimentalCoroutinesApi @get:Rule var mainCoroutineRule = MainCoroutineRule() + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + authViewModel = AuthViewModel(authModel) + } + + @Test + fun `signUpWithEmail success`() = runBlocking { + doAnswer { inv -> (inv.getArgument<() -> Unit>(2))() } + .`when`(authModel) + .register(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.signUpWithEmail( + context = mockContext, + userEmail = "example@email.com", + userPassword = "password", + ) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Success -> true + else -> false + } + assert(result) + } + + @Test + fun `signUpWithEmail failure`() = runBlocking { + doAnswer { inv -> (inv.getArgument<(Exception) -> Unit>(3))(Exception("Heyhey")) } + .`when`(authModel) + .register(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.signUpWithEmail( + context = mockContext, + userEmail = "example@email.com", + userPassword = "password", + ) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Error -> true + else -> false + } + assert(result) + } + + @Test + fun `signInWithEmail success`() = runBlocking { + doAnswer { inv -> inv.getArgument<() -> Unit>(2)() } + .`when`(authModel) + .login(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.logInWithEmail( + context = mockContext, + userEmail = "example@email.com", + userPassword = "password", + ) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Success -> true + else -> false + } + assert(result) + } + + @Test + fun `signInWithEmail failure`() = runBlocking { + doAnswer { inv -> + val onFailure = inv.getArgument<(Exception) -> Unit>(3) + onFailure(Exception("heyhey")) + } + .`when`(authModel) + .login(any(), any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.logInWithEmail( + context = mockContext, + userEmail = "example@email.com", + userPassword = "password", + ) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Success -> false + is UserAuthState.Error -> true + is UserAuthState.Loading -> false + else -> false + } + assert(result) + } + + @Test + fun `logOut success`() = runBlocking { + doAnswer { inv -> inv.getArgument<() -> Unit>(0)() } + .`when`(authModel) + .logout(any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.logOut(context = mockContext) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Success -> true + else -> false + } + assert(result) + } + + @Test + fun `logOut failure`() = runBlocking { + doAnswer { inv -> inv.getArgument<(Exception) -> Unit>(1)(Exception("eyyo pogger")) } + .`when`(authModel) + .logout(any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.logOut(context = mockContext) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Error -> true + else -> false + } + assert(result) + } + + @Test + fun `isUserLoggedIn success`() = runBlocking { + doAnswer { inv -> inv.getArgument<() -> Unit>(1)() } + .`when`(authModel) + .isUserLoggedIn(any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.isUserLoggedIn(mockContext) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Success -> true + else -> false + } + assert(result) + } + + @Test + fun `isUserLoggedIn failure`() = runBlocking { + doAnswer { inv -> inv.getArgument<(Exception) -> Unit>(2)(Exception("eyyo pogger")) } + .`when`(authModel) + .isUserLoggedIn(any(), any<() -> Unit>(), any<(Exception) -> Unit>()) + + authViewModel.isUserLoggedIn(mockContext) + + val result = + when (authViewModel.userAuthState.value) { + is UserAuthState.Error -> true + else -> false + } + assert(result) + } +}