Skip to content

Commit

Permalink
Merge pull request #159 from PeriodPals/feat/profile/integrate-vm-cre…
Browse files Browse the repository at this point in the history
…ateprofile

Feat/profile/integrate-vm-createprofile : Integrate profile VM into CreateProfile screen
  • Loading branch information
charliemangano authored Nov 9, 2024
2 parents d2f61e9 + 377bc8e commit af38548
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.android.periodpals.ui.profile

import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertTextEquals
Expand All @@ -8,6 +9,8 @@ import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.periodpals.model.user.User
import com.android.periodpals.model.user.UserViewModel
import com.android.periodpals.resources.C.Tag.BottomNavigationMenu
import com.android.periodpals.resources.C.Tag.CreateProfileScreen
import com.android.periodpals.resources.C.Tag.TopAppBar
Expand All @@ -29,17 +32,32 @@ import org.mockito.kotlin.verify
@RunWith(AndroidJUnit4::class)
class CreateProfileTest {
private lateinit var navigationActions: NavigationActions
private lateinit var userViewModel: UserViewModel

@get:Rule val composeTestRule = createComposeRule()

companion object {
private val email = "test@email.com"
private val name = "John Doe"
private val imageUrl = "https://example.com"
private val description = "A short description"
private val dob = "01/01/2000"
private val userState =
mutableStateOf(User(name = name, imageUrl = imageUrl, description = description, dob = dob))
}

@Before
fun setUp() {
navigationActions = mock(NavigationActions::class.java)
userViewModel = mock(UserViewModel::class.java)

`when`(navigationActions.currentRoute()).thenReturn(Screen.CREATE_PROFILE)
composeTestRule.setContent { CreateProfileScreen(navigationActions) }
}

@Test
fun allComponentsAreDisplayed() {
composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.SCREEN).assertIsDisplayed()
composeTestRule.onNodeWithTag(CreateProfileScreen.PROFILE_PICTURE).assertIsDisplayed()
composeTestRule.onNodeWithTag(CreateProfileScreen.MANDATORY_TEXT).assertIsDisplayed()
Expand All @@ -63,13 +81,17 @@ class CreateProfileTest {

@Test
fun testValidDateRecognition() {
composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("01/01/2000")
assertTrue(validateDate("01/01/2000"))
assertTrue(validateDate("31/12/1999"))
}

@Test
fun testInvalidDateRecognition() {
composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("invalid_date")
assertFalse(validateDate("32/01/2000")) // Invalid day
assertFalse(validateDate("01/13/2000")) // Invalid month
Expand All @@ -80,72 +102,113 @@ class CreateProfileTest {

@Test
fun createInvalidProfileNoEmail() {
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("01/01/2000")
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput("John Doe")
`when`(userViewModel.user).thenReturn(userState)

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput(dob)
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput(name)
composeTestRule
.onNodeWithTag(CreateProfileScreen.DESCRIPTION_FIELD)
.performTextInput("A short bio")
.performTextInput(description)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel, never()).saveUser(any())

verify(navigationActions, never()).navigateTo(any<TopLevelDestination>())
verify(navigationActions, never()).navigateTo(any<String>())
}

@Test
fun createInvalidProfileNoDob() {
composeTestRule
.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD)
.performTextInput("john.doe@example.com")
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput("John Doe")
`when`(userViewModel.user).thenReturn(userState)

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD).performTextInput(email)
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput(name)
composeTestRule
.onNodeWithTag(CreateProfileScreen.DESCRIPTION_FIELD)
.performTextInput("A short bio")
.performTextInput(description)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel, never()).saveUser(any())

verify(navigationActions, never()).navigateTo(any<TopLevelDestination>())
verify(navigationActions, never()).navigateTo(any<String>())
}

@Test
fun createInvalidProfileNoName() {
composeTestRule
.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD)
.performTextInput("john.doe@example.com")
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("01/01/2000")
`when`(userViewModel.user).thenReturn(userState)

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD).performTextInput(email)
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput(dob)
composeTestRule
.onNodeWithTag(CreateProfileScreen.DESCRIPTION_FIELD)
.performTextInput("A short bio")
.performTextInput(description)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel, never()).saveUser(any())

verify(navigationActions, never()).navigateTo(any<TopLevelDestination>())
verify(navigationActions, never()).navigateTo(any<String>())
}

@Test
fun createInvalidProfileNoDescription() {
composeTestRule
.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD)
.performTextInput("john.doe@example.com")
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("01/01/2000")
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput("John Doe")
`when`(userViewModel.user).thenReturn(userState)

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD).performTextInput(email)
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput(dob)
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput(name)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel, never()).saveUser(any())

verify(navigationActions, never()).navigateTo(any<TopLevelDestination>())
verify(navigationActions, never()).navigateTo(any<String>())
}

@Test
fun createValidProfile() {
fun createValidProfileVMFailure() {
`when`(userViewModel.user).thenReturn(mutableStateOf(null))

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD).performTextInput(email)
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput(dob)
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput(name)
composeTestRule
.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD)
.performTextInput("john.doe@example.com")
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput("01/01/2000")
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput("John Doe")
.onNodeWithTag(CreateProfileScreen.DESCRIPTION_FIELD)
.performTextInput(description)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel).saveUser(any())

verify(navigationActions, never()).navigateTo(Screen.PROFILE)
}

@Test
fun createValidProfileVMSuccess() {
`when`(userViewModel.user).thenReturn(userState)

composeTestRule.setContent { CreateProfileScreen(userViewModel, navigationActions) }

composeTestRule.onNodeWithTag(CreateProfileScreen.EMAIL_FIELD).performTextInput(email)
composeTestRule.onNodeWithTag(CreateProfileScreen.DOB_FIELD).performTextInput(dob)
composeTestRule.onNodeWithTag(CreateProfileScreen.NAME_FIELD).performTextInput(name)
composeTestRule
.onNodeWithTag(CreateProfileScreen.DESCRIPTION_FIELD)
.performTextInput("A short bio")
.performTextInput(description)
composeTestRule.onNodeWithTag(CreateProfileScreen.SAVE_BUTTON).performClick()

verify(userViewModel).saveUser(any())

verify(navigationActions).navigateTo(Screen.PROFILE)
}
}
12 changes: 10 additions & 2 deletions app/src/main/java/com/android/periodpals/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import androidx.navigation.compose.rememberNavController
import androidx.navigation.navigation
import com.android.periodpals.model.authentication.AuthenticationModelSupabase
import com.android.periodpals.model.authentication.AuthenticationViewModel
import com.android.periodpals.model.user.UserRepositorySupabase
import com.android.periodpals.model.user.UserViewModel
import com.android.periodpals.services.GPSServiceImpl
import com.android.periodpals.ui.alert.AlertListsScreen
import com.android.periodpals.ui.alert.CreateAlertScreen
Expand All @@ -31,6 +33,7 @@ import com.android.periodpals.ui.theme.PeriodPalsAppTheme
import com.android.periodpals.ui.timer.TimerScreen
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.postgrest.Postgrest
import org.osmdroid.config.Configuration

class MainActivity : ComponentActivity() {
Expand All @@ -43,11 +46,15 @@ class MainActivity : ComponentActivity() {
supabaseKey = BuildConfig.SUPABASE_KEY,
) {
install(Auth)
install(Postgrest)
}

private val authModel = AuthenticationModelSupabase(supabaseClient)
private val authenticationViewModel = AuthenticationViewModel(authModel)

private val userModel = UserRepositorySupabase(supabaseClient)
private val userViewModel = UserViewModel(userModel)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -58,7 +65,7 @@ class MainActivity : ComponentActivity() {
PeriodPalsAppTheme {
// A surface container using the 'background' color from the theme
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
PeriodPalsApp(locationService, authenticationViewModel)
PeriodPalsApp(locationService, authenticationViewModel, userViewModel)
}
}
}
Expand All @@ -69,6 +76,7 @@ class MainActivity : ComponentActivity() {
fun PeriodPalsApp(
locationService: GPSServiceImpl,
authenticationViewModel: AuthenticationViewModel,
userViewModel: UserViewModel,
) {
val navController = rememberNavController()
val navigationActions = NavigationActions(navController)
Expand All @@ -78,7 +86,7 @@ fun PeriodPalsApp(
navigation(startDestination = Screen.SIGN_IN, route = Route.AUTH) {
composable(Screen.SIGN_IN) { SignInScreen(authenticationViewModel, navigationActions) }
composable(Screen.SIGN_UP) { SignUpScreen(authenticationViewModel, navigationActions) }
composable(Screen.CREATE_PROFILE) { CreateProfileScreen(navigationActions) }
composable(Screen.CREATE_PROFILE) { CreateProfileScreen(userViewModel, navigationActions) }
}

// Alert push notifications
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.android.periodpals.model.user

import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

/**
Expand All @@ -16,8 +16,8 @@ private const val TAG = "UserViewModel"

class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewModel() {

private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user
private val _user = mutableStateOf<User?>(null)
val user: State<User?> = _user

/** Loads the user profile and updates the user state. */
fun loadUser() {
Expand All @@ -30,7 +30,8 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
onFailure = {
Log.d(TAG, "loadUserProfile: fail to load user profile: ${it.message}")
_user.value = null
})
},
)
}
}

Expand All @@ -50,7 +51,8 @@ class UserViewModel(private val userRepository: UserRepositorySupabase) : ViewMo
onFailure = {
Log.d(TAG, "saveUser: fail to save user: ${it.message}")
_user.value = null
})
},
)
}
}
}
Loading

0 comments on commit af38548

Please sign in to comment.