From 77062beabc97ecce0a8ff4587d147071684925b6 Mon Sep 17 00:00:00 2001 From: Death Date: Thu, 8 Sep 2022 12:45:58 -0500 Subject: [PATCH] adding UsersScreenCompose.kt --- .../userscompose/ui/TestCompose.kt | 154 ------------- .../userscompose/ui/UsersScreenCompose.kt | 202 ++++++++++++++++++ .../nullpointer/userscompose/models/User.kt | 8 +- .../ui/screens/users/components/UserItem.kt | 6 +- .../userscompose/ui/share/ToolbarBack.kt | 2 +- app/src/main/res/values-en/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 7 files changed, 213 insertions(+), 163 deletions(-) delete mode 100644 app/src/androidTest/java/com/nullpointer/userscompose/ui/TestCompose.kt create mode 100644 app/src/androidTest/java/com/nullpointer/userscompose/ui/UsersScreenCompose.kt diff --git a/app/src/androidTest/java/com/nullpointer/userscompose/ui/TestCompose.kt b/app/src/androidTest/java/com/nullpointer/userscompose/ui/TestCompose.kt deleted file mode 100644 index 4516b34..0000000 --- a/app/src/androidTest/java/com/nullpointer/userscompose/ui/TestCompose.kt +++ /dev/null @@ -1,154 +0,0 @@ -package com.nullpointer.userscompose.ui - -import android.content.Context -import android.util.Log -import androidx.compose.ui.semantics.SemanticsActions -import androidx.compose.ui.semantics.SemanticsNode -import androidx.compose.ui.test.* -import androidx.compose.ui.test.junit4.createAndroidComposeRule -import androidx.lifecycle.SavedStateHandle -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import coil.annotation.ExperimentalCoilApi -import com.nullpointer.userscompose.R -import com.nullpointer.userscompose.core.constants.Constants.TAG_BUTTON_ADD -import com.nullpointer.userscompose.core.constants.Constants.TAG_BUTTON_CANCEL -import com.nullpointer.userscompose.domain.users.UsersRepository -import com.nullpointer.userscompose.presentation.SelectViewModel -import com.nullpointer.userscompose.presentation.UsersViewModel -import com.nullpointer.userscompose.ui.activitys.MainActivity -import com.nullpointer.userscompose.ui.screens.users.UsersScreen -import dagger.hilt.android.testing.HiltAndroidRule -import dagger.hilt.android.testing.HiltAndroidTest -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import timber.log.Timber -import java.lang.Thread.sleep -import javax.inject.Inject -import kotlin.random.Random - -@ExperimentalCoilApi -@HiltAndroidTest -@RunWith(AndroidJUnit4::class) -class TestCompose { - - @get:Rule(order = 1) - var hiltTestRule = HiltAndroidRule(this) - - @get:Rule(order = 2) - var composeTestRule = createAndroidComposeRule() - - @Inject - lateinit var userRepo: UsersRepository - - private val context: Context = ApplicationProvider.getApplicationContext() - - @Before - fun init() { - hiltTestRule.inject() - } - - @Test - fun visibilityButtonsActions() { - val userViewModel = UsersViewModel(userRepo) - composeTestRule.setContent { - UsersScreen(userViewModel) - } - - // * test only exist button add - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() - // * test hidden button add when processing data and show cancel operation - composeTestRule.waitUntil { userViewModel.isProcessing } - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertDoesNotExist() - composeTestRule.onNodeWithTag(TAG_BUTTON_CANCEL).assertExists() - composeTestRule.waitUntil { !userViewModel.isProcessing } - // * test show button add when processing finish - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() - } - - @Test - fun showMessageWhenCancelOperationAddNewUserAndRestoreInterface() { - val userViewModel = UsersViewModel(userRepo) - composeTestRule.setContent { - UsersScreen(userViewModel) - } - // * click init add for request new user - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() - // * await to processing - composeTestRule.waitUntil { userViewModel.isProcessing } - // * cancel operation - composeTestRule.onNodeWithTag(TAG_BUTTON_CANCEL).performClick() - // * show message cancel operation - composeTestRule.onNodeWithText(context.getString(R.string.action_stop_add_user)) - .assertExists() - composeTestRule.waitUntil { !userViewModel.isProcessing } - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() - } - - @Test - fun showUsersCorrectlyWhenAddAndChangeTitle() { - val userViewModel = UsersViewModel(userRepo) - val randomTest = (5..15).random() - composeTestRule.setContent { - UsersScreen(userViewModel) - } - repeat(randomTest) { - composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() - composeTestRule.onNodeWithTag(TAG_BUTTON_CANCEL).assertExists() - composeTestRule.waitUntil { !userViewModel.isProcessing } - composeTestRule.onNodeWithText("Name $it").assertExists() - composeTestRule.onNodeWithText(context.getString(R.string.title_numbers_user_saved, - it + 1)).assertExists() - } - } - - // ! i need smooth scroll for see this -// @OptIn(ExperimentalTestApi::class) -// @Test -// fun hiddenButtonAddWhenScrollListUsers() { -// val userViewModel = UsersViewModel(userRepo) -// val randomTest = (10..22).random() -// composeTestRule.setContent { -// UsersScreen(userViewModel) -// } -// repeat(randomTest) { -// composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() -// composeTestRule.waitUntil(1500) { !userViewModel.isProcessing } -// } -// val node=composeTestRule.onAllNodes(hasScrollAction()).onFirst() -// node.performScrollToIndex(randomTest) -// composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertDoesNotExist() -// } - - -// @Test -// fun selectUsers() { -// val userViewModel = UsersViewModel(userRepo) -// val selectViewModel = SelectViewModel(SavedStateHandle()) -// val listPreSelectUsers = mutableListOf() -// val randomTest = (5..10).random() -// composeTestRule.setContent { -// UsersScreen(userViewModel, selectViewModel) -// } -// repeat(randomTest) { -// composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() -// composeTestRule.waitUntil(1500) { !userViewModel.isProcessing } -// composeTestRule.onNodeWithText("Name $it").assertExists() -// if (Random.nextBoolean()) listPreSelectUsers.add(composeTestRule.onNodeWithText("Name $it")) -// } -// composeTestRule.mainClock.autoAdvance=false -// listPreSelectUsers.forEach{ -// it.performSemanticsAction(SemanticsActions.OnLongClick) -// composeTestRule.mainClock.advanceTimeBy(500) -// } -// -// Log.d("TAG","Select list ${listPreSelectUsers.size}") -// Log.d("TAG","Select view model ${selectViewModel.numberSelection}") -// assert(selectViewModel.numberSelection == listPreSelectUsers.size) -// } -} \ No newline at end of file diff --git a/app/src/androidTest/java/com/nullpointer/userscompose/ui/UsersScreenCompose.kt b/app/src/androidTest/java/com/nullpointer/userscompose/ui/UsersScreenCompose.kt new file mode 100644 index 0000000..8e662db --- /dev/null +++ b/app/src/androidTest/java/com/nullpointer/userscompose/ui/UsersScreenCompose.kt @@ -0,0 +1,202 @@ +package com.nullpointer.userscompose.ui + +import android.content.Context +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nullpointer.userscompose.R +import com.nullpointer.userscompose.core.constants.Constants.TAG_BUTTON_ADD +import com.nullpointer.userscompose.core.constants.Constants.TAG_BUTTON_CANCEL +import com.nullpointer.userscompose.core.constants.Constants.TAG_LIST_USERS +import com.nullpointer.userscompose.domain.users.UsersRepository +import com.nullpointer.userscompose.models.User +import com.nullpointer.userscompose.presentation.UsersViewModel +import com.nullpointer.userscompose.ui.screens.users.UsersScreen +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class UsersScreenCompose { + + private class FakeUsersRepo( + listUsersFake: List = emptyList(), + val errorRequestNewUser: Exception? = null + ) : UsersRepository { + + private val _listUsers = MutableStateFlow(listUsersFake) + override val listUsers: Flow> = _listUsers.asStateFlow() + + override suspend fun addNewUser(): User { + delay(800) + errorRequestNewUser?.let { throw it } + var randomUser = User.getUserRandom() + while (_listUsers.value.contains(randomUser)) { + randomUser = User.getUserRandom() + } + _listUsers.value = _listUsers.value + randomUser + return randomUser + } + + override suspend fun deleterUser(user: User) { + val currentList = _listUsers.value + if (currentList.contains(user)) { + _listUsers.value = currentList - user + } else { + throw RuntimeException("User not found") + } + } + + override suspend fun deleterAllUsers() { + _listUsers.value = emptyList() + } + + override suspend fun deleterUserByIds(list: List) { + val listDeleterUsers = _listUsers.value.filter { + list.contains(it.id) + } + _listUsers.value = _listUsers.value - listDeleterUsers.toSet() + } + + override suspend fun getUserById(id: Long): User? { + return _listUsers.value.find { it.id == id } + } + + } + + @get:Rule + var composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun visibilityButtonsActions() { + val userViewModel = UsersViewModel(FakeUsersRepo()) + composeTestRule.setContent { + UsersScreen(userViewModel) + } + + // * test only exist button add + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() + // * test hidden button add when processing data and show cancel operation + composeTestRule.waitUntil { userViewModel.isProcessing } + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertDoesNotExist() + composeTestRule.onNodeWithTag(TAG_BUTTON_CANCEL).assertExists() + composeTestRule.waitUntil { !userViewModel.isProcessing } + // * test show button add when processing finish + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() + } + + @Test + fun showMessageWhenCancelOperationAddNewUserAndRestoreInterface() { + val userViewModel = UsersViewModel(FakeUsersRepo()) + composeTestRule.setContent { + UsersScreen(userViewModel) + } + // * click init add for request new user + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() + // * await to processing + composeTestRule.waitUntil { userViewModel.isProcessing } + // * cancel operation + composeTestRule.onNodeWithTag(TAG_BUTTON_CANCEL).performClick() + // * show message cancel operation + composeTestRule.onNodeWithText(context.getString(R.string.action_stop_add_user)) + .assertExists() + composeTestRule.waitUntil { !userViewModel.isProcessing } + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).assertExists() + } + + @Test + fun showMessageNoNetwork() { + val userViewModel = + UsersViewModel(FakeUsersRepo(errorRequestNewUser = NullPointerException())) + composeTestRule.setContent { + UsersScreen(userViewModel) + } + // * click init add for request new user + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() + composeTestRule.waitUntil { userViewModel.isProcessing } + composeTestRule.waitUntil { !userViewModel.isProcessing } + composeTestRule.onNodeWithText(context.getString(R.string.server_time_now)).assertExists() + } + + @Test + fun showCorrectNumberUsers() { + val userViewModel = UsersViewModel(FakeUsersRepo()) + val randomTest = (5..15).random() + composeTestRule.setContent { + UsersScreen(userViewModel) + } + repeat(randomTest) { + composeTestRule.onNodeWithTag(TAG_BUTTON_ADD).performClick() + composeTestRule.waitUntil { !userViewModel.isProcessing } + composeTestRule.onNodeWithText( + context.getString( + R.string.title_numbers_user_saved, + it + 1 + ) + ).assertExists() + } + } + + @Test + fun showCorrectAllUsers() { + val listFakeUsers = User.generateListUsers(100) + val userViewModel = UsersViewModel(FakeUsersRepo(listFakeUsers)) + val randomTest = (5..listFakeUsers.size).random() + composeTestRule.setContent { + UsersScreen(userViewModel) + } + with(composeTestRule) { + repeat(randomTest) { + val randomUser = listFakeUsers.random() + onNodeWithTag(TAG_LIST_USERS).performScrollToKey(randomUser.id) + onNodeWithText(randomUser.name).assertExists() + onNodeWithContentDescription( + context.getString( + R.string.description_img_user, + randomUser.name + ) + ).assertExists() + } + } + + } + + +// @Test +// fun selectUsers() = runBlocking { +// val userList = User.generateListUsers(100) +// val userFake = FakeUsersRepo(userList) +// val userViewModel = UsersViewModel(userFake) +// val selectViewModel = SelectViewModel(SavedStateHandle()) +// val expectedList = mutableListOf() +// composeTestRule.setContent { +// UsersScreen(userViewModel, selectViewModel) +// } +// +// with(composeTestRule) { +// repeat(2) { +// val user = userList.random() +// +// if (!expectedList.contains(user)) { +// onNodeWithTag(TAG_LIST_USERS).performScrollToKey(user.id) +// +// onNodeWithText(user.name).printToLog("user ${user.id}") +// onNodeWithText(user.name).performSemanticsAction(SemanticsActions.OnLongClick) +// +// expectedList.add(user) +// delay(1000) +// } +// } +// } +// assert(expectedList.size == selectViewModel.numberSelection) +// } +} \ No newline at end of file diff --git a/app/src/main/java/com/nullpointer/userscompose/models/User.kt b/app/src/main/java/com/nullpointer/userscompose/models/User.kt index 6c158a1..f42a15b 100644 --- a/app/src/main/java/com/nullpointer/userscompose/models/User.kt +++ b/app/src/main/java/com/nullpointer/userscompose/models/User.kt @@ -10,10 +10,10 @@ import kotlinx.parcelize.Parcelize @Entity(tableName = "users_table") @Parcelize data class User( - val name: String, - val lastName: String, - val city: String, - val imgUser: String, + val name: String = "", + val lastName: String = "", + val city: String = "", + val imgUser: String = "", @PrimaryKey(autoGenerate = true) val id: Long = 0, val timestamp: Long = System.currentTimeMillis(), diff --git a/app/src/main/java/com/nullpointer/userscompose/ui/screens/users/components/UserItem.kt b/app/src/main/java/com/nullpointer/userscompose/ui/screens/users/components/UserItem.kt index 800ea84..b341540 100644 --- a/app/src/main/java/com/nullpointer/userscompose/ui/screens/users/components/UserItem.kt +++ b/app/src/main/java/com/nullpointer/userscompose/ui/screens/users/components/UserItem.kt @@ -59,7 +59,8 @@ fun UserItem( ) { ImageUser( urlImg = user.imgUser, - modifier = Modifier.size(80.dp) + modifier = Modifier.size(80.dp), + contentDescription = stringResource(R.string.description_img_user, user.name) ) Spacer(modifier = Modifier.height(10.dp)) Text( @@ -76,6 +77,7 @@ fun UserItem( private fun ImageUser( urlImg: String, modifier: Modifier = Modifier, + contentDescription: String ) { AsyncImage( @@ -87,7 +89,7 @@ private fun ImageUser( .data(urlImg) .crossfade(true) .build(), - contentDescription = stringResource(R.string.description_img_user), + contentDescription = contentDescription, modifier = modifier ) diff --git a/app/src/main/java/com/nullpointer/userscompose/ui/share/ToolbarBack.kt b/app/src/main/java/com/nullpointer/userscompose/ui/share/ToolbarBack.kt index bd7b7d4..86a1241 100644 --- a/app/src/main/java/com/nullpointer/userscompose/ui/share/ToolbarBack.kt +++ b/app/src/main/java/com/nullpointer/userscompose/ui/share/ToolbarBack.kt @@ -44,7 +44,7 @@ fun SelectToolbar( ) { val (showMenu, changeVisibleMenu) = rememberSaveable { mutableStateOf(false) } - val title by remember { + val title by remember(numberSelection) { derivedStateOf { if (numberSelection == 0) context.getString(titleDefault) else diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 57fa864..0a91c32 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -29,7 +29,7 @@ Add a new user Delete selected users Cancel the current operation - User Image + Image User from %s User details diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e65e658..2b5feb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,7 +29,7 @@ Agrega un nuevo usuario Elimina los usuarios seleccionados Cancela la operacion actual - Imagen del usuario + Imagen de perfil de usuario %s Detalles del usuario