...
 
Commits (2)
# ComposeSample1
This project is a simplified mockup of two pEp's screens.
## Motivation
I created this project to learn and experiment with Jetpack Compose.
And as a learning project, if you follow the commits you can see some experiments.
There are also some logs as example that can help to understand how Jetpack Compose works.
## Some Conclusions
Jetpack Compose makes UI development and testing faster and easier.
One of the big points is decoupling UI from Activities and Fragments.
Another one of course, is using just one language for everything (Kotlin).
What would take several xml files when using traditional UI, can be done in a few lines of Kotlin code.
It is really useful to be able to test each UI component by itself, with mock data.
With this isolation, we can easily identify unwanted behaviors in that component,
without having our component's function depending on anything else in the app.
Of course the concepts change a little and we need to learn new patterns with Compose, but I think it's totally worth it :)
## Components
This project uses the following components and concepts:
* [MVVM architecture][MVVM]
* [ViewModel][viewmodel] (presentation layer)
* [Kotlin Coroutines][coroutines] (background operations)
* [Kotlin StateFlow][stateflow] (communicate presentation and UI layer)
* [Room Database][room] (storage)
* [Jetpack Compose][compose] (UI layer)
* [Dagger Hilt][hilt] (dependency injection)
[MVVM]: https://developer.android.com/jetpack/guide
[viewmodel]: https://developer.android.com/topic/libraries/architecture/viewmodel
[coroutines]: https://kotlinlang.org/docs/reference/coroutines-overview.html
[stateflow]: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
[room]: https://developer.android.com/training/data-storage/room
[compose]: https://developer.android.com/jetpack/compose
[hilt]: https://developer.android.com/training/dependency-injection/hilt-android
...@@ -106,28 +106,29 @@ class MainScreenTest { ...@@ -106,28 +106,29 @@ class MainScreenTest {
deleteMessage() deleteMessage()
assertWeAreInMessageListScreen() assertWeAreInMessageListScreen()
composeTestRule composeTestRule
.onNodeWithTag("messageViewHolderTitleAndAbstract1") .onNodeWithTag("${TestTags.MessageList.messageViewHolderTitleAndAbstract}1")
.assertDoesNotExist() .assertDoesNotExist()
} }
private fun deleteMessage() { private fun deleteMessage() {
helper.assertNodeWithTag("messageViewDeleteMenuItem").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewDeleteMenuItem).performClick()
} }
private fun closeMessage() { private fun closeMessage() {
helper.assertNodeWithTag("messageViewNavigationIcon").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewNavigationIcon).performClick()
assertWeAreInMessageListScreen() assertWeAreInMessageListScreen()
} }
private fun assertMessageFlaggedInMessageView(flagged: Boolean): SemanticsNodeInteraction { private fun assertMessageFlaggedInMessageView(flagged: Boolean): SemanticsNodeInteraction {
return helper.assertInternalNodeWithTag("messageViewFlagMenuItem").assert( return helper.assertInternalNodeWithTag(TestTags.MessageView.messageViewFlagMenuItem)
hasContentDescription( .assert(
helper.context.getString( hasContentDescription(
if (flagged) R.string.message_remove_star_action helper.context.getString(
else R.string.message_add_star_action if (flagged) R.string.message_remove_star_action
else R.string.message_add_star_action
)
) )
) )
)
} }
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
...@@ -135,14 +136,15 @@ class MainScreenTest { ...@@ -135,14 +136,15 @@ class MainScreenTest {
id: Int, id: Int,
flagged: Boolean flagged: Boolean
): SemanticsNodeInteraction { ): SemanticsNodeInteraction {
return helper.assertInternalNodeWithTag("messageViewHolderFlag$id").assert( return helper.assertInternalNodeWithTag("${TestTags.MessageList.messageViewHolderFlag}$id")
hasContentDescription( .assert(
helper.context.getString( hasContentDescription(
if (flagged) R.string.message_remove_star_action helper.context.getString(
else R.string.message_add_star_action if (flagged) R.string.message_remove_star_action
else R.string.message_add_star_action
)
) )
) )
)
} }
private fun assertWeAreInMessageListScreen() { private fun assertWeAreInMessageListScreen() {
...@@ -151,18 +153,18 @@ class MainScreenTest { ...@@ -151,18 +153,18 @@ class MainScreenTest {
} }
private fun assertWeAreInMessageViewScreen() { private fun assertWeAreInMessageViewScreen() {
helper.assertNodeWithTag("messageViewTopBarBadge") helper.assertNodeWithTag(TestTags.MessageView.messageViewTopBarBadge)
helper.assertNodeWithText(R.string.secure_and_trusted) helper.assertNodeWithText(R.string.secure_and_trusted)
} }
private fun openMessage(id: Int) { private fun openMessage(id: Int) {
helper.assertInternalNodeWithTag("messageViewHolderTitleAndAbstract$id") helper.assertInternalNodeWithTag("${TestTags.MessageList.messageViewHolderTitleAndAbstract}$id")
.performClick() .performClick()
assertWeAreInMessageViewScreen() assertWeAreInMessageViewScreen()
} }
private fun openOptionsMenu() { private fun openOptionsMenu() {
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
} }
@Suppress("SameParameterValue") @Suppress("SameParameterValue")
...@@ -170,7 +172,7 @@ class MainScreenTest { ...@@ -170,7 +172,7 @@ class MainScreenTest {
id: Int, id: Int,
unread: Boolean unread: Boolean
): SemanticsNodeInteraction { ): SemanticsNodeInteraction {
return helper.assertNodeWithTag("messageViewHolderTitleAndAbstract$id") return helper.assertNodeWithTag("${TestTags.MessageList.messageViewHolderTitleAndAbstract}$id")
.assert( .assert(
hasContentDescription( hasContentDescription(
helper.context.getString( helper.context.getString(
......
...@@ -5,8 +5,10 @@ import androidx.compose.ui.test.junit4.createComposeRule ...@@ -5,8 +5,10 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import com.ignacio.composesample1.R import com.ignacio.composesample1.R
import com.ignacio.composesample1.ui.DrawerUser import com.ignacio.composesample1.ui.DrawerUser
import com.ignacio.composesample1.ui.TestTags
import com.ignacio.composesample1.ui.UITestHelper import com.ignacio.composesample1.ui.UITestHelper
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
...@@ -18,6 +20,7 @@ class DrawerTest { ...@@ -18,6 +20,7 @@ class DrawerTest {
private val drawerUser = DrawerUser() private val drawerUser = DrawerUser()
@ExperimentalCoroutinesApi
@Before @Before
fun setUp() { fun setUp() {
helper.stubViewModel(null, false) helper.stubViewModel(null, false)
...@@ -39,14 +42,14 @@ class DrawerTest { ...@@ -39,14 +42,14 @@ class DrawerTest {
@Test @Test
fun drawer_test() { fun drawer_test() {
openDrawer() openDrawer()
helper.assertInternalNodeWithTag("currentAccountAvatar") helper.assertInternalNodeWithTag(TestTags.MessageList.currentAccountAvatar)
helper.assertInternalNodeWithText(helper.username) helper.assertInternalNodeWithText(helper.username)
helper.assertInternalNodeWithText(helper.email) helper.assertInternalNodeWithText(helper.email)
helper.assertInternalNodeWithTag("otherAccountClicker1").performClick() helper.assertInternalNodeWithTag(TestTags.MessageList.otherAccountClicker1).performClick()
verify(helper.drawerActionClickListener).onDrawerAccountCircleClicked(helper.allAccounts[1]) verify(helper.drawerActionClickListener).onDrawerAccountCircleClicked(helper.allAccounts[1])
helper.assertInternalNodeWithTag("otherAccountClicker2").performClick() helper.assertInternalNodeWithTag(TestTags.MessageList.otherAccountClicker2).performClick()
verify(helper.drawerActionClickListener).onDrawerAccountCircleClicked(helper.allAccounts[2]) verify(helper.drawerActionClickListener).onDrawerAccountCircleClicked(helper.allAccounts[2])
assertSpecialFolderList() assertSpecialFolderList()
...@@ -64,7 +67,8 @@ class DrawerTest { ...@@ -64,7 +67,8 @@ class DrawerTest {
} }
private fun assertDrawerClosed() { private fun assertDrawerClosed() {
helper.findInternalNodeWithTag("currentAccountAvatar").assertIsNotDisplayed() helper.findInternalNodeWithTag(TestTags.MessageList.currentAccountAvatar)
.assertIsNotDisplayed()
helper.findInternalNodeWithText(helper.username).assertIsNotDisplayed() helper.findInternalNodeWithText(helper.username).assertIsNotDisplayed()
helper.findInternalNodeWithText(helper.email).assertIsNotDisplayed() helper.findInternalNodeWithText(helper.email).assertIsNotDisplayed()
} }
...@@ -78,12 +82,12 @@ class DrawerTest { ...@@ -78,12 +82,12 @@ class DrawerTest {
} }
private fun openDrawer() { private fun openDrawer() {
helper.assertNodeWithTag("drawerIcon").performClick() helper.assertNodeWithTag(TestTags.MessageList.drawerIcon).performClick()
} }
private fun assertSpecialFolderList() { private fun assertSpecialFolderList() {
helper.assertScrollableList( helper.assertScrollableList(
"drawerSpecialFolderList", TestTags.MessageList.drawerSpecialFolderList,
helper.specialFolders, helper.specialFolders,
true true
) { index -> ) { index ->
...@@ -95,7 +99,7 @@ class DrawerTest { ...@@ -95,7 +99,7 @@ class DrawerTest {
private fun assertNormalFolderList() { private fun assertNormalFolderList() {
helper.assertScrollableList( helper.assertScrollableList(
"drawerFolderList", TestTags.MessageList.drawerFolderList,
helper.folders, helper.folders,
true true
) { index -> ) { index ->
...@@ -109,7 +113,7 @@ class DrawerTest { ...@@ -109,7 +113,7 @@ class DrawerTest {
val otherAccounts = helper.allAccounts val otherAccounts = helper.allAccounts
.takeLast(helper.allAccounts.size - 1) .takeLast(helper.allAccounts.size - 1)
helper.assertScrollableList( helper.assertScrollableList(
"drawerAccountList", TestTags.MessageList.drawerAccountList,
otherAccounts, otherAccounts,
true true
) { index -> ) { index ->
......
...@@ -6,6 +6,7 @@ import androidx.compose.ui.test.junit4.createComposeRule ...@@ -6,6 +6,7 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithSubstring import androidx.compose.ui.test.onNodeWithSubstring
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import com.ignacio.composesample1.R import com.ignacio.composesample1.R
import com.ignacio.composesample1.ui.TestTags
import com.ignacio.composesample1.ui.UITestHelper import com.ignacio.composesample1.ui.UITestHelper
import com.nhaarman.mockitokotlin2.reset import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
...@@ -30,15 +31,15 @@ class MessageListTest { ...@@ -30,15 +31,15 @@ class MessageListTest {
helper.assertNodeWithText(R.string.app_name) helper.assertNodeWithText(R.string.app_name)
helper.assertNodeWithText(R.string.subtitle_dummy) helper.assertNodeWithText(R.string.subtitle_dummy)
helper.assertNodeWithTag("searchAction").performClick() helper.assertNodeWithTag(TestTags.MessageList.searchAction).performClick()
verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.search_action) verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.search_action)
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
helper.assertNodeWithText(R.string.option_1_action).performClick() helper.assertNodeWithText(R.string.option_1_action).performClick()
verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_1_action) verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_1_action)
helper.findNodeWithText(R.string.option_1_action).assertDoesNotExist() helper.findNodeWithText(R.string.option_1_action).assertDoesNotExist()
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
helper.assertNodeWithText(R.string.option_2_action).performClick() helper.assertNodeWithText(R.string.option_2_action).performClick()
verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_2_action) verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_2_action)
helper.findNodeWithText(R.string.option_2_action).assertDoesNotExist() helper.findNodeWithText(R.string.option_2_action).assertDoesNotExist()
...@@ -53,7 +54,7 @@ class MessageListTest { ...@@ -53,7 +54,7 @@ class MessageListTest {
) )
} }
helper.assertScrollableList( helper.assertScrollableList(
"messageList", TestTags.MessageList.messageList,
helper.allMessages helper.allMessages
) { position -> ) { position ->
val message = helper.allMessages[position] val message = helper.allMessages[position]
...@@ -68,17 +69,19 @@ class MessageListTest { ...@@ -68,17 +69,19 @@ class MessageListTest {
helper.assertInternalNodeWithText("Subject${message.id}") helper.assertInternalNodeWithText("Subject${message.id}")
helper.assertInternalNodeWithText("Body${message.id}") helper.assertInternalNodeWithText("Body${message.id}")
} }
helper.assertNodeWithTag("messageViewHolderContactBadge${message.id}").performClick() helper.assertNodeWithTag("${TestTags.MessageList.messageViewHolderContactBadge}${message.id}")
.performClick()
verify(helper.messageViewHolderActionListener).onContactClicked(message.from.first()) verify(helper.messageViewHolderActionListener).onContactClicked(message.from.first())
helper.assertNodeWithTag("messageViewHolderTitleAndAbstract${message.id}") helper.assertNodeWithTag("${TestTags.MessageList.messageViewHolderTitleAndAbstract}${message.id}")
.performClick() .performClick()
verify(helper.messageViewHolderActionListener).onMessageClicked(message) verify(helper.messageViewHolderActionListener).onMessageClicked(message)
val sdf = SimpleDateFormat("MMM d", Locale.getDefault()) val sdf = SimpleDateFormat("MMM d", Locale.getDefault())
helper.assertNodeWithTag("messageViewHolderDate${message.id}") helper.assertNodeWithTag("${TestTags.MessageList.messageViewHolderDate}${message.id}")
.assertTextEquals(sdf.format(Date())) .assertTextEquals(sdf.format(Date()))
helper.assertNodeWithTag("messageViewHolderFlag${message.id}").performClick() helper.assertNodeWithTag("${TestTags.MessageList.messageViewHolderFlag}${message.id}")
.performClick()
verify(helper.messageViewHolderActionListener).onFlagClicked(message) verify(helper.messageViewHolderActionListener).onFlagClicked(message)
reset(helper.messageViewHolderActionListener) reset(helper.messageViewHolderActionListener)
......
...@@ -3,6 +3,7 @@ package com.ignacio.composesample1.ui.messageview ...@@ -3,6 +3,7 @@ package com.ignacio.composesample1.ui.messageview
import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import com.ignacio.composesample1.R import com.ignacio.composesample1.R
import com.ignacio.composesample1.ui.TestTags
import com.ignacio.composesample1.ui.UITestHelper import com.ignacio.composesample1.ui.UITestHelper
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
...@@ -24,23 +25,23 @@ class MessageViewTest { ...@@ -24,23 +25,23 @@ class MessageViewTest {
messageInfo = MessageViewMenuMessageInfo(helper.allMessages.first()) messageInfo = MessageViewMenuMessageInfo(helper.allMessages.first())
) )
} }
helper.assertNodeWithTag("messageViewNavigationIcon").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewNavigationIcon).performClick()
verify(helper.messageViewMenuActionListener).onNavigationClicked() verify(helper.messageViewMenuActionListener).onNavigationClicked()
helper.assertNodeWithTag("messageViewTopBarBadge") helper.assertNodeWithTag(TestTags.MessageView.messageViewTopBarBadge)
helper.assertNodeWithText(R.string.secure_and_trusted) helper.assertNodeWithText(R.string.secure_and_trusted)
helper.assertNodeWithTag("messageViewFlagMenuItem").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewFlagMenuItem).performClick()
verify(helper.messageViewMenuActionListener).onFlagIconToggled() verify(helper.messageViewMenuActionListener).onFlagIconToggled()
helper.assertNodeWithTag("messageViewDeleteMenuItem").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewDeleteMenuItem).performClick()
verify(helper.messageViewMenuActionListener).onDeleteIconClicked() verify(helper.messageViewMenuActionListener).onDeleteIconClicked()
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
helper.assertNodeWithText(R.string.mark_as_read_action).performClick() helper.assertNodeWithText(R.string.mark_as_read_action).performClick()
helper.findNodeWithText(R.string.mark_as_read_action).assertDoesNotExist() helper.findNodeWithText(R.string.mark_as_read_action).assertDoesNotExist()
verify(helper.messageViewMenuActionListener).onUnreadToggleClicked() verify(helper.messageViewMenuActionListener).onUnreadToggleClicked()
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
helper.assertNodeWithText(R.string.option_3_action).performClick() helper.assertNodeWithText(R.string.option_3_action).performClick()
helper.findNodeWithText(R.string.option_3_action).assertDoesNotExist() helper.findNodeWithText(R.string.option_3_action).assertDoesNotExist()
verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_3_action) verify(helper.optionsMenuItemClickListener).onOptionsMenuItemClicked(R.string.option_3_action)
...@@ -57,24 +58,24 @@ class MessageViewTest { ...@@ -57,24 +58,24 @@ class MessageViewTest {
homeviewModel = helper.viewModel homeviewModel = helper.viewModel
) )
} }
helper.assertNodeWithTag("messageViewNavigationIcon").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewNavigationIcon).performClick()
verify(helper.viewModel).closeMessage() verify(helper.viewModel).closeMessage()
helper.assertNodeWithTag("messageViewTopBarBadge") helper.assertNodeWithTag(TestTags.MessageView.messageViewTopBarBadge)
helper.assertNodeWithText(R.string.secure_and_trusted) helper.assertNodeWithText(R.string.secure_and_trusted)
helper.assertNodeWithTag("messageViewFlagMenuItem").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewFlagMenuItem).performClick()
verify(helper.viewModel).toggleMessageFlag(message) verify(helper.viewModel).toggleMessageFlag(message)
helper.assertNodeWithTag("messageViewDeleteMenuItem").performClick() helper.assertNodeWithTag(TestTags.MessageView.messageViewDeleteMenuItem).performClick()
verify(helper.viewModel).deleteMessage(message) verify(helper.viewModel).deleteMessage(message)
helper.assertNodeWithTag("overflowMenu").performClick() helper.assertNodeWithTag(TestTags.overflowMenu).performClick()
helper.assertNodeWithText(R.string.mark_as_read_action).performClick() helper.assertNodeWithText(R.string.mark_as_read_action).performClick()
helper.findNodeWithText(R.string.mark_as_read_action).assertDoesNotExist() helper.findNodeWithText(R.string.mark_as_read_action).assertDoesNotExist()
verify(helper.viewModel).toggleMessageUnread(message) verify(helper.viewModel).toggleMessageUnread(message)
helper.assertNodeWithTag("messageViewAvatar") helper.assertNodeWithTag(TestTags.MessageView.messageViewAvatar)
helper.assertNodeWithText(message.subject) helper.assertNodeWithText(message.subject)
helper.assertNodeWithText(message.body) helper.assertNodeWithText(message.body)
helper.assertNodeWithText( helper.assertNodeWithText(
...@@ -87,6 +88,6 @@ class MessageViewTest { ...@@ -87,6 +88,6 @@ class MessageViewTest {
helper.context.getString(R.string.message_to_field, helper.context.getString(R.string.message_to_field,
message.recipients.joinToString(", ") { it.name }) message.recipients.joinToString(", ") { it.name })
) )
helper.assertNodeWithTag("messageViewDate") helper.assertNodeWithTag(TestTags.MessageView.messageViewDate)
} }
} }
\ No newline at end of file
...@@ -26,4 +26,35 @@ fun LogCallbacksWithText(text: String) { ...@@ -26,4 +26,35 @@ fun LogCallbacksWithText(text: String) {
fun getViewModel(): HomeViewModel = viewModel<HomeViewModelImpl>() fun getViewModel(): HomeViewModel = viewModel<HomeViewModelImpl>()
val AmbientProvidableMessage: ProvidableAmbient<MyMessage> = val AmbientProvidableMessage: ProvidableAmbient<MyMessage> =
ambientOf { MyMessage.emptyMessage() } ambientOf { MyMessage.emptyMessage() }
\ No newline at end of file
object TestTags {
const val overflowMenu = "overflowMenu"
object MessageList {
const val drawerAccountList = "drawerAccountList"
const val drawerSpecialFolderList = "drawerSpecialFolderList"
const val drawerFolderList = "drawerFolderList"
const val messageList = "messageList"
const val searchAction = "searchAction"
const val messageViewHolder = "messageViewHolder"
const val messageViewHolderContactBadge = "messageViewHolderContactBadge"
const val messageViewHolderDate = "messageViewHolderDate"
const val messageViewHolderFlag = "messageViewHolderFlag"
const val messageViewHolderTitleAndAbstract = "messageViewHolderTitleAndAbstract"
const val drawerIcon = "drawerIcon"
const val currentAccountAvatar = "currentAccountAvatar"
const val otherAccountClicker1 = "otherAccountClicker1"
const val otherAccountClicker2 = "otherAccountClicker2"
}
object MessageView {
const val messageViewAvatar = "messageViewAvatar"
const val messageViewDate = "messageViewDate"
const val messageViewFlagMenuItem = "messageViewFlagMenuItem"
const val messageViewDeleteMenuItem = "messageViewDeleteMenuItem"
const val messageViewNavigationIcon = "messageViewNavigationIcon"
const val messageViewTopBarBadge = "messageViewTopBarBadge"
}
}
\ No newline at end of file
...@@ -91,7 +91,7 @@ fun MyDropDownMenu( ...@@ -91,7 +91,7 @@ fun MyDropDownMenu(
expanded = showMenu.value, expanded = showMenu.value,
onDismissRequest = { showMenu.value = false }, onDismissRequest = { showMenu.value = false },
dropdownOffset = Position((screenWidth).dp, 0.dp), dropdownOffset = Position((screenWidth).dp, 0.dp),
toggleModifier = Modifier.testTag("overflowMenu") toggleModifier = Modifier.testTag(TestTags.overflowMenu)
) { ) {
myMenuScope.content() myMenuScope.content()
} }
......
...@@ -145,7 +145,7 @@ fun MyTopBar( ...@@ -145,7 +145,7 @@ fun MyTopBar(
navigationIcon = { navigationIcon = {
IconButton( IconButton(
onClick = { onNavigationClicked() }, onClick = { onNavigationClicked() },
modifier = Modifier.testTag("drawerIcon") modifier = Modifier.testTag(TestTags.MessageList.drawerIcon)
) { ) {
Image(vectorResource(id = R.drawable.ic_baseline_menu_24)) Image(vectorResource(id = R.drawable.ic_baseline_menu_24))
} }
...@@ -168,7 +168,7 @@ fun MessageListOptionsMenu( ...@@ -168,7 +168,7 @@ fun MessageListOptionsMenu(
onClick = { onClick = {
optionsMenuItemClickListener.onOptionsMenuItemClicked(R.string.search_action) optionsMenuItemClickListener.onOptionsMenuItemClicked(R.string.search_action)
}, },
modifier = Modifier.testTag("searchAction") modifier = Modifier.testTag(TestTags.MessageList.searchAction)
) { ) {
Image(vectorResource(id = R.drawable.ic_baseline_search_24)) Image(vectorResource(id = R.drawable.ic_baseline_search_24))
} }
...@@ -229,7 +229,7 @@ fun DrawerBodyFolderMode( ...@@ -229,7 +229,7 @@ fun DrawerBodyFolderMode(
DrawerFolderList( DrawerFolderList(
folders = specialFolders, folders = specialFolders,
drawerActionClickListener = drawerActionClickListener, drawerActionClickListener = drawerActionClickListener,
modifier = Modifier.testTag("drawerSpecialFolderList") modifier = Modifier.testTag(TestTags.MessageList.drawerSpecialFolderList)
) )
Text( Text(
stringResource(id = R.string.folders_title), stringResource(id = R.string.folders_title),
...@@ -238,7 +238,7 @@ fun DrawerBodyFolderMode( ...@@ -238,7 +238,7 @@ fun DrawerBodyFolderMode(
DrawerFolderList( DrawerFolderList(
folders = folders, folders = folders,
drawerActionClickListener = drawerActionClickListener, drawerActionClickListener = drawerActionClickListener,
modifier = Modifier.testTag("drawerFolderList") modifier = Modifier.testTag(TestTags.MessageList.drawerFolderList)
) )
} }
} }
...@@ -263,7 +263,7 @@ private fun NavigationHeader( ...@@ -263,7 +263,7 @@ private fun NavigationHeader(
.preferredSize(50.dp) .preferredSize(50.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(colorResource(id = R.color.pep_green)) .background(colorResource(id = R.color.pep_green))
.testTag("currentAccountAvatar"), .testTag(TestTags.MessageList.currentAccountAvatar),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
...@@ -272,7 +272,7 @@ private fun NavigationHeader( ...@@ -272,7 +272,7 @@ private fun NavigationHeader(
image = R.drawable.ic_launcher_foreground, image = R.drawable.ic_launcher_foreground,
account = accounts[1], account = accounts[1],
drawerActionClickListener = drawerActionClickListener, drawerActionClickListener = drawerActionClickListener,
modifier = Modifier.testTag("otherAccountClicker1") modifier = Modifier.testTag(TestTags.MessageList.otherAccountClicker1)
) )
} }
if (accounts.size > 2) { if (accounts.size > 2) {
...@@ -281,7 +281,7 @@ private fun NavigationHeader( ...@@ -281,7 +281,7 @@ private fun NavigationHeader(
image = R.drawable.ic_launcher_foreground, image = R.drawable.ic_launcher_foreground,
account = accounts[2], account = accounts[2],
drawerActionClickListener = drawerActionClickListener, drawerActionClickListener = drawerActionClickListener,
modifier = Modifier.testTag("otherAccountClicker2") modifier = Modifier.testTag(TestTags.MessageList.otherAccountClicker2)
) )
} }
} }
...@@ -344,7 +344,7 @@ fun MessageList( ...@@ -344,7 +344,7 @@ fun MessageList(
messageViewHolderActionListener: MessageViewHolderActionListener, messageViewHolderActionListener: MessageViewHolderActionListener,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
LazyColumn(modifier = modifier.testTag("messageList")) { LazyColumn(modifier = modifier.testTag(TestTags.MessageList.messageList)) {
items(items = messages, items(items = messages,
itemContent = { message -> itemContent = { message ->
MessageViewHolder( MessageViewHolder(
...@@ -371,7 +371,7 @@ fun MessageViewHolder( ...@@ -371,7 +371,7 @@ fun MessageViewHolder(
Row( Row(
modifier = modifier modifier = modifier
.clip(RoundedCornerShape(4.dp)) .clip(RoundedCornerShape(4.dp))
.testTag("messageViewHolder${message.id}") .testTag("${TestTags.MessageList.messageViewHolder}${message.id}")
) { ) {
MessageViewHolderContactBadge(message, messageViewHolderActionListener) MessageViewHolderContactBadge(message, messageViewHolderActionListener)
Spacer( Spacer(
...@@ -405,7 +405,7 @@ fun MessageViewHolderContactBadge( ...@@ -405,7 +405,7 @@ fun MessageViewHolderContactBadge(
.clickable(onClick = { .clickable(onClick = {
messageViewHolderActionListener.onContactClicked(message.from.first()) messageViewHolderActionListener.onContactClicked(message.from.first())
}) })
.testTag("messageViewHolderContactBadge${message.id}"), .testTag("${TestTags.MessageList.messageViewHolderContactBadge}${message.id}"),
shape = CircleShape, shape = CircleShape,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.2f)
) { ) {
...@@ -430,7 +430,7 @@ fun RowScope.MessageViewHolderTitleAndAbstract( ...@@ -430,7 +430,7 @@ fun RowScope.MessageViewHolderTitleAndAbstract(
if (message.read) context.getString(R.string.read_message_conent_description) if (message.read) context.getString(R.string.read_message_conent_description)
else context.getString(R.string.unread_message_conent_description) else context.getString(R.string.unread_message_conent_description)
} }
.testTag("messageViewHolderTitleAndAbstract${message.id}") .testTag("${TestTags.MessageList.messageViewHolderTitleAndAbstract}${message.id}")
) { ) {
Text( Text(
text = message.subject, text = message.subject,
...@@ -466,7 +466,7 @@ fun MessageViewHolderDateAndFlag( ...@@ -466,7 +466,7 @@ fun MessageViewHolderDateAndFlag(
val sdf = SimpleDateFormat("MMM d", locale) val sdf = SimpleDateFormat("MMM d", locale)
Text( Text(
sdf.format(Date(message.date)), sdf.format(Date(message.date)),
modifier = Modifier.testTag("messageViewHolderDate${message.id}") modifier = Modifier.testTag("${TestTags.MessageList.messageViewHolderDate}${message.id}")
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Image( Image(
...@@ -485,7 +485,7 @@ fun MessageViewHolderDateAndFlag( ...@@ -485,7 +485,7 @@ fun MessageViewHolderDateAndFlag(
if (message.flagged) context.getString(R.string.message_remove_star_action) if (message.flagged) context.getString(R.string.message_remove_star_action)
else context.getString(R.string.message_add_star_action) else context.getString(R.string.message_add_star_action)
} }
.testTag("messageViewHolderFlag${message.id}") .testTag("${TestTags.MessageList.messageViewHolderFlag}${message.id}")
) )
} }
} }
...@@ -545,7 +545,7 @@ fun DrawerBodyAccountMode( ...@@ -545,7 +545,7 @@ fun DrawerBodyAccountMode(
DrawerAccountList( DrawerAccountList(
accounts = accounts, accounts = accounts,
drawerActionClickListener = drawerActionClickListener, drawerActionClickListener = drawerActionClickListener,
modifier = Modifier.testTag("drawerAccountList") modifier = Modifier.testTag(TestTags.MessageList.drawerAccountList)
) )
DrawerActionRow( DrawerActionRow(
image = R.drawable.ic_baseline_add_24, image = R.drawable.ic_baseline_add_24,
......
...@@ -107,7 +107,7 @@ fun MessageViewTopBar( ...@@ -107,7 +107,7 @@ fun MessageViewTopBar(
vectorResource(id = R.drawable.pep_status_green), vectorResource(id = R.drawable.pep_status_green),
modifier = Modifier modifier = Modifier
.preferredSize(32.dp) .preferredSize(32.dp)
.testTag("messageViewTopBarBadge") .testTag(TestTags.MessageView.messageViewTopBarBadge)
) )
Spacer(modifier = Modifier.preferredWidth(8.dp)) Spacer(modifier = Modifier.preferredWidth(8.dp))
Text( Text(
...@@ -122,7 +122,7 @@ fun MessageViewTopBar( ...@@ -122,7 +122,7 @@ fun MessageViewTopBar(
navigationIcon = { navigationIcon = {
IconButton( IconButton(
onClick = { messageViewMenuActionListener.onNavigationClicked() }, onClick = { messageViewMenuActionListener.onNavigationClicked() },
modifier = Modifier.testTag("messageViewNavigationIcon") modifier = Modifier.testTag(TestTags.MessageView.messageViewNavigationIcon)
) { ) {
Image(vectorResource(id = R.drawable.ic_clear_daynight)) Image(vectorResource(id = R.drawable.ic_clear_daynight))
} }
...@@ -156,7 +156,7 @@ fun MessageViewOptionsMenu( ...@@ -156,7 +156,7 @@ fun MessageViewOptionsMenu(
if (menuMessageInfo.flagged) context.getString(R.string.message_remove_star_action) if (menuMessageInfo.flagged) context.getString(R.string.message_remove_star_action)
else context.getString(R.string.message_add_star_action) else context.getString(R.string.message_add_star_action)
} }
.testTag("messageViewFlagMenuItem") .testTag(TestTags.MessageView.messageViewFlagMenuItem)
) { ) {
Image( Image(
vectorResource( vectorResource(
...@@ -170,7 +170,7 @@ fun MessageViewOptionsMenu( ...@@ -170,7 +170,7 @@ fun MessageViewOptionsMenu(
onClick = { onClick = {
messageViewMenuActionListener.onDeleteIconClicked() messageViewMenuActionListener.onDeleteIconClicked()
}, },
modifier = Modifier.testTag("messageViewDeleteMenuItem") modifier = Modifier.testTag(TestTags.MessageView.messageViewDeleteMenuItem)
) { ) {
Image(vectorResource(id = R.drawable.ic_trash_can_daynight)) Image(vectorResource(id = R.drawable.ic_trash_can_daynight))
} }
...@@ -241,7 +241,7 @@ fun ColumnScope.MessageViewBodyHeader() { ...@@ -241,7 +241,7 @@ fun ColumnScope.MessageViewBodyHeader() {
.preferredSize(48.dp) .preferredSize(48.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(colorResource(id = R.color.grey_scale_color)) .background(colorResource(id = R.color.grey_scale_color))
.testTag("messageViewAvatar"), .testTag(TestTags.MessageView.messageViewAvatar),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
Spacer(Modifier.preferredWidth(16.dp)) Spacer(Modifier.preferredWidth(16.dp))
...@@ -254,7 +254,7 @@ fun ColumnScope.MessageViewBodyHeader() { ...@@ -254,7 +254,7 @@ fun ColumnScope.MessageViewBodyHeader() {
Date(message.date).toString(), Date(message.date).toString(),
Modifier Modifier
.align(Alignment.End) .align(Alignment.End)
.testTag("messageViewDate") .testTag(TestTags.MessageView.messageViewDate)
) )
} }
......