From 809ef984cf778247d03256e99aa723fbe0b9370a Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Mon, 28 Oct 2024 09:45:04 +0100 Subject: [PATCH 1/6] fix: add dependencies for androidTesting with mockito Added `com.linkedin.dexmaker:dexmaker-mockito-inline` dependency to enable Mockito inline for androidTests Fix comes from 2776ce7. --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index af39bd6ba..2157bd233 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -221,8 +221,8 @@ dependencies { /// Mockito for android testing androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.mockito.android) androidTestImplementation(libs.mockito.kotlin) + androidTestImplementation(libs.dexmaker.mockito.inline) // Mockito for unit testing testImplementation(libs.mockito.kotlin) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index edbc4f9dd..4f9445c9e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ agp = "8.6.1" androidxNavigationCompose = "2.5.3" bomVersion = "3.0.0" compose = "1.0.0-beta01" +dexmakerMockitoInline = "2.28.4" imagepicker = "2.1" json = "20240303" kotlin = "2.0.0" @@ -101,6 +102,7 @@ androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "ui" } compose = { module = "com.github.bumptech.glide:compose", version.ref = "compose" } core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "coreKtxVersion" } +dexmaker-mockito-inline = { module = "com.linkedin.dexmaker:dexmaker-mockito-inline", version.ref = "dexmakerMockitoInline" } github-postgrest-kt = { module = "io.github.jan-tennert.supabase:postgrest-kt" } imagepicker = { module = "com.github.dhaval2404:imagepicker", version.ref = "imagepicker" } json = { module = "org.json:json", version.ref = "json" } From 5bb30085ba90c1f49aec69d604ba3d056a3460ef Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Mon, 28 Oct 2024 18:59:25 +0100 Subject: [PATCH 2/6] test: add UI tests for the SignIn screen Uncommented the existing (partly wrong) tests, corrected them and created new ones. Used mocking for `NavigationActions` and `AuthViewModel` to be able to check for calls to them. Used a `companion object` to have a reusable valid email and password. The tests now check for: * display of all components * correct errors showing up when invalid attempt (empty email or password) * not trying to log in the user if invalid attempt * valid attempt navigates to next screen * valid attempt logs in the user * click on sign up navigates to SignUp screen --- .../ui/authentication/SignInTest.kt | 269 ++++++++---------- 1 file changed, 126 insertions(+), 143 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt index d68f97149..fc46ede04 100644 --- a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt +++ b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt @@ -1,144 +1,127 @@ package com.android.periodpals.ui.authentication -// -// import androidx.compose.ui.test.assertIsDisplayed -// import androidx.compose.ui.test.assertTextEquals -// import androidx.compose.ui.test.junit4.createComposeRule -// import androidx.compose.ui.test.onNodeWithTag -// import androidx.compose.ui.test.performClick -// import androidx.compose.ui.test.performTextInput -// import androidx.navigation.compose.rememberNavController -// import com.android.periodpals.model.auth.AuthViewModel -// import com.android.periodpals.ui.navigation.NavigationActions -// import org.junit.Before -// import org.junit.Rule -// import org.junit.Test -// import org.mockito.Mockito.mock -// -// class SignInScreenTest { -// -// @get:Rule val composeTestRule = createComposeRule() -// lateinit var authViewModel: AuthViewModel -// -// // lateinit var supabaseClient: SupabaseClient -// -// @Before -// fun setUp() { -// authViewModel = mock(AuthViewModel::class.java) -// // supabaseClient = mock(SupabaseClient::class.java) -// } -// -// @Test -// fun signInScreen_displaysCorrectUI() { -// // Set the content to the SignInScreen -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Check if the welcome text is displayed -// composeTestRule.onNodeWithTag("signInScreen").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInBackground").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInTitle").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInInstruction").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInEmail").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInPassword").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInPasswordVisibility").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInButton").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInOrText").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInGoogleButton").assertIsDisplayed() -// composeTestRule.onNodeWithTag("signInNotRegistered").assertIsDisplayed() -// } -// -// @Test -// fun signInScreen_emailValidation_emptyEmail_showsError() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Click on the sign in button with empty fields -// composeTestRule.onNodeWithTag("signInButton").performClick() -// -// // Verify that the error message for email is displayed -// composeTestRule.onNodeWithTag("signInEmailError").assertTextEquals("Email cannot be empty") -// } -// -// @Test -// fun signInScreen_emailValidation_invalidEmail_showsError() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Enter an invalid email -// composeTestRule.onNodeWithTag("signInEmail").performTextInput("invalidEmail") -// -// // Click on the sign in button -// composeTestRule.onNodeWithTag("signInButton").performClick() -// -// // Verify that the error message for email is displayed -// composeTestRule.onNodeWithTag("signInEmailError").assertTextEquals("Email must contain @") -// } -// -// @Test -// fun signInScreen_passwordValidation_emptyPassword_showsError() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Enter a valid email -// composeTestRule.onNodeWithTag("signInEmail").performTextInput("test@example.com") -// -// // Click on the sign in button with empty password -// composeTestRule.onNodeWithTag("signInButton").performClick() -// -// // Verify that the error message for password is displayed -// composeTestRule -// .onNodeWithTag("signInPasswordError") -// .assertTextEquals("Password cannot be empty") -// } -// -// @Test -// fun signInScreen_signIn_successfulLogin() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Enter valid email and password -// composeTestRule.onNodeWithTag("signInEmail").performTextInput("test@example.com") -// composeTestRule.onNodeWithTag("signInPassword").performTextInput("ValidPassword123") -// -// // Click on the sign in button -// composeTestRule.onNodeWithTag("signInButton").performClick() -// -// // Check for a successful login Toast (mocking would be required here) -// // Currently, you can't test Toast directly; you can use dependency injection or other methods -// } -// -// @Test -// fun signInScreen_signIn_failsInvalidLogin() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Enter valid email and an invalid password -// composeTestRule.onNodeWithTag("signInEmail").performTextInput("test@example.com") -// composeTestRule.onNodeWithTag("signInPassword").performTextInput("InvalidPassword") -// -// // Click on the sign in button -// composeTestRule.onNodeWithTag("signInButton").performClick() -// -// // Check for a failed login Toast (mocking would be required here) -// // You can set up your test to verify that the error message or Toast appears. -// } -// -// @Test -// fun signInScreen_navigatesToSignUp() { -// composeTestRule.setContent { -// SignInScreen(authViewModel, NavigationActions(rememberNavController())) -// } -// -// // Click on the "Not registered yet? Sign up here!" text -// composeTestRule.onNodeWithTag("signInNotRegistered").performClick() -// -// // Check for a navigation action (mocking would be required here) -// // You would verify that the navigation to the sign-up screen is triggered. -// } -// } + +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextEquals +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import com.android.periodpals.model.auth.AuthViewModel +import com.android.periodpals.model.user.UserAuthState +import com.android.periodpals.ui.navigation.NavigationActions +import com.android.periodpals.ui.navigation.Route +import com.android.periodpals.ui.navigation.Screen +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +class SignInScreenTest { + + @get:Rule val composeTestRule = createComposeRule() + private lateinit var navigationActions: NavigationActions + private lateinit var authViewModel: AuthViewModel + + companion object { + private const val email = "test@example.com" + private const val password = "password" + } + + @Before + fun setUp() { + navigationActions = mock(NavigationActions::class.java) + authViewModel = mock(AuthViewModel::class.java) + + `when`(navigationActions.currentRoute()).thenReturn(Route.ALERT_LIST) + `when`(authViewModel.userAuthState) + .thenReturn(mutableStateOf(UserAuthState.Success("User is logged in"))) + composeTestRule.setContent { SignInScreen(authViewModel, navigationActions) } + } + + @Test + fun allComponentsAreDisplayed() { + + composeTestRule.onNodeWithTag("signInScreen").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInBackground").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInTitle").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInInstruction").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInEmail").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInPassword").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInPasswordVisibility").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInButton").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInOrText").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInGoogleButton").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInNotRegistered").assertIsDisplayed() + } + + @Test + fun emptyEmailShowsCorrectError() { + + composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) + composeTestRule.onNodeWithTag("signInButton").performClick() + composeTestRule.onNodeWithTag("signInEmailError").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInEmailError").assertTextEquals("Email cannot be empty") + } + + @Test + fun emptyEmailDoesNotCallVM() { + + composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) + composeTestRule.onNodeWithTag("signInButton").performClick() + composeTestRule.onNodeWithTag("signInEmailError").assertIsDisplayed() + composeTestRule.onNodeWithTag("signInEmailError").assertTextEquals("Email cannot be empty") + verify(authViewModel, never()).logInWithEmail(any(), any(), any()) + } + + @Test + fun emptyPasswordShowsCorrectError() { + + composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) + composeTestRule.onNodeWithTag("signInButton").performClick() + composeTestRule.onNodeWithTag("signInPasswordError").assertIsDisplayed() + composeTestRule + .onNodeWithTag("signInPasswordError") + .assertTextEquals("Password cannot be empty") + } + + @Test + fun emptyPasswordDoesNotCallVM() { + + composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) + composeTestRule.onNodeWithTag("signInButton").performClick() + composeTestRule.onNodeWithTag("signInPasswordError").assertIsDisplayed() + composeTestRule + .onNodeWithTag("signInPasswordError") + .assertTextEquals("Password cannot be empty") + } + + @Test + fun validSignInAttemptNavigatesToProfileScreen() { + + composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) + composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) + composeTestRule.onNodeWithTag("signInButton").performClick() + verify(navigationActions).navigateTo(Screen.PROFILE) + } + + @Test + fun validSignInAttemptCallsVMLogInWithEmail() { + + composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) + composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) + composeTestRule.onNodeWithTag("signInButton").performClick() + verify(authViewModel).logInWithEmail(any(), eq(email), eq(password)) + } + + @Test + fun signUpHereNavigatesToSignUpScreen() { + composeTestRule.onNodeWithTag("signInNotRegistered").performClick() + verify(navigationActions).navigateTo(Screen.SIGN_UP) + } +} From 1af8eb5f419bc4792fced8cd4c5d2e6271338701 Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Mon, 28 Oct 2024 19:04:38 +0100 Subject: [PATCH 3/6] fix: fix no interaction with navigation mock error for SignUp text Changed deprecated `ClickableText` to a `Text` with a `clickable` attribute on its modifier. This makes it so that the `signUpHereNavigatesToSignUpScreen` test can detect calls to `navigationActions` --- .../periodpals/ui/authentication/SignIn.kt | 221 ++++++++---------- 1 file changed, 100 insertions(+), 121 deletions(-) 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..a8ead3133 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 @@ -5,6 +5,7 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -16,7 +17,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme @@ -35,10 +35,7 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString 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 @@ -86,7 +83,10 @@ fun SignInScreen(authViewModel: AuthViewModel, navigationActions: NavigationActi ) { // Welcome text AuthWelcomeText( - text = "Welcome to PeriodPals", color = Color.Black, testTag = "signInTitle") + text = "Welcome to PeriodPals", + color = Color.Black, + testTag = "signInTitle", + ) // Rectangle with login fields and button Box( @@ -97,108 +97,83 @@ fun SignInScreen(authViewModel: AuthViewModel, navigationActions: NavigationActi .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") - } + 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() + } + }, + 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() - } - ClickableText( - modifier = Modifier.testTag("signInNotRegistered"), - text = annotatedText, - onClick = { offset -> - annotatedText - .getStringAnnotations(tag = "SignUp", start = offset, end = offset) - .firstOrNull() - ?.let { navigationActions.navigateTo(Screen.SIGN_UP) } - }) + Text( + text = "Not registered yet? Sign up here!", + modifier = + Modifier.clickable { navigationActions.navigateTo(Screen.SIGN_UP) } + .testTag("signInNotRegistered"), + ) } - }) + }, + ) } /** Validates the email and returns an error message if the email is invalid. */ @@ -232,20 +207,24 @@ fun GoogleButton(onClick: () -> Unit, modifier: Modifier = Modifier, testTag: St 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) - } - } + 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, + ) + } + } } From 32c0eea10310b94f3ac026c55f22117c9049144b Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Mon, 28 Oct 2024 19:07:43 +0100 Subject: [PATCH 4/6] style: format using ktfmt for readability --- .../android/periodpals/ui/authentication/SignInTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt index fc46ede04..21aa2a8c3 100644 --- a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt +++ b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt @@ -40,7 +40,7 @@ class SignInScreenTest { `when`(navigationActions.currentRoute()).thenReturn(Route.ALERT_LIST) `when`(authViewModel.userAuthState) - .thenReturn(mutableStateOf(UserAuthState.Success("User is logged in"))) + .thenReturn(mutableStateOf(UserAuthState.Success("User is logged in"))) composeTestRule.setContent { SignInScreen(authViewModel, navigationActions) } } @@ -86,8 +86,8 @@ class SignInScreenTest { composeTestRule.onNodeWithTag("signInButton").performClick() composeTestRule.onNodeWithTag("signInPasswordError").assertIsDisplayed() composeTestRule - .onNodeWithTag("signInPasswordError") - .assertTextEquals("Password cannot be empty") + .onNodeWithTag("signInPasswordError") + .assertTextEquals("Password cannot be empty") } @Test @@ -97,8 +97,8 @@ class SignInScreenTest { composeTestRule.onNodeWithTag("signInButton").performClick() composeTestRule.onNodeWithTag("signInPasswordError").assertIsDisplayed() composeTestRule - .onNodeWithTag("signInPasswordError") - .assertTextEquals("Password cannot be empty") + .onNodeWithTag("signInPasswordError") + .assertTextEquals("Password cannot be empty") } @Test From 45352b4dabbe153bf7098b7bdf95464276ea5bb5 Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Mon, 28 Oct 2024 19:23:15 +0100 Subject: [PATCH 5/6] feat: modify layout of SignIn screen to comply with figma Added a `Row` with content two different `Text`s, once for non-clickable text ("Not registered yet? "), and one for clickable text ("Sign up here!"). --- .../periodpals/ui/authentication/SignIn.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 a8ead3133..c72f1fd0d 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 @@ -165,12 +165,16 @@ fun SignInScreen(authViewModel: AuthViewModel, navigationActions: NavigationActi ) } } - Text( - text = "Not registered yet? Sign up here!", - modifier = - Modifier.clickable { navigationActions.navigateTo(Screen.SIGN_UP) } - .testTag("signInNotRegistered"), - ) + Row(modifier = Modifier) { + Text("Not registered yet? ") + Text( + text = "Sign up here!", + modifier = + Modifier.clickable { navigationActions.navigateTo(Screen.SIGN_UP) } + .testTag("signInNotRegistered"), + color = Color.Blue, + ) + } } }, ) From d99aa99e89652987bc2fb2b0bbea2b2889695966 Mon Sep 17 00:00:00 2001 From: charlie mangano Date: Thu, 31 Oct 2024 12:02:19 +0100 Subject: [PATCH 6/6] fix: correct problems pointed out by PR review Replaced ``when`(navigationActions.currentRoute()).thenReturn(Route.ALERT_LIST) ` with correct return screen (`Screen.SIGN_IN`). Added check for not calling the VM in `emptyPasswordDoesNotCallVM`. --- .../periodpals/ui/authentication/SignInTest.kt | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt index 21aa2a8c3..ea13fe09c 100644 --- a/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt +++ b/app/src/androidTest/java/com/android/periodpals/ui/authentication/SignInTest.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.test.performTextInput import com.android.periodpals.model.auth.AuthViewModel import com.android.periodpals.model.user.UserAuthState import com.android.periodpals.ui.navigation.NavigationActions -import com.android.periodpals.ui.navigation.Route import com.android.periodpals.ui.navigation.Screen import org.junit.Before import org.junit.Rule @@ -38,7 +37,7 @@ class SignInScreenTest { navigationActions = mock(NavigationActions::class.java) authViewModel = mock(AuthViewModel::class.java) - `when`(navigationActions.currentRoute()).thenReturn(Route.ALERT_LIST) + `when`(navigationActions.currentRoute()).thenReturn(Screen.SIGN_IN) `when`(authViewModel.userAuthState) .thenReturn(mutableStateOf(UserAuthState.Success("User is logged in"))) composeTestRule.setContent { SignInScreen(authViewModel, navigationActions) } @@ -74,8 +73,6 @@ class SignInScreenTest { composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) composeTestRule.onNodeWithTag("signInButton").performClick() - composeTestRule.onNodeWithTag("signInEmailError").assertIsDisplayed() - composeTestRule.onNodeWithTag("signInEmailError").assertTextEquals("Email cannot be empty") verify(authViewModel, never()).logInWithEmail(any(), any(), any()) } @@ -95,10 +92,7 @@ class SignInScreenTest { composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) composeTestRule.onNodeWithTag("signInButton").performClick() - composeTestRule.onNodeWithTag("signInPasswordError").assertIsDisplayed() - composeTestRule - .onNodeWithTag("signInPasswordError") - .assertTextEquals("Password cannot be empty") + verify(authViewModel, never()).logInWithEmail(any(), any(), any()) } @Test @@ -112,7 +106,6 @@ class SignInScreenTest { @Test fun validSignInAttemptCallsVMLogInWithEmail() { - composeTestRule.onNodeWithTag("signInEmail").performTextInput(email) composeTestRule.onNodeWithTag("signInPassword").performTextInput(password) composeTestRule.onNodeWithTag("signInButton").performClick()