Skip to content

Commit 46a87e3

Browse files
committed
Categories screens ui tests
1 parent bc690e6 commit 46a87e3

5 files changed

Lines changed: 371 additions & 12 deletions

File tree

app/src/main/java/com/grishko188/pointofinterest/features/categories/ui/CategoriesScreen.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.compose.material.SnackbarResult
1212
import androidx.compose.material3.MaterialTheme
1313
import androidx.compose.runtime.*
1414
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.platform.testTag
1516
import androidx.compose.ui.res.stringResource
1617
import androidx.compose.ui.unit.dp
1718
import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
@@ -24,7 +25,6 @@ import com.grishko188.pointofinterest.features.categories.ui.models.CategoryUiMo
2425
import com.grishko188.pointofinterest.features.categories.vm.CategoriesViewModel
2526
import com.grishko188.pointofinterest.navigation.Screen
2627
import kotlinx.coroutines.CoroutineScope
27-
2828
import kotlinx.coroutines.launch
2929

3030
@OptIn(ExperimentalLifecycleComposeApi::class)
@@ -67,7 +67,11 @@ fun CategoriesContent(
6767
) {
6868

6969
Column {
70-
LazyColumn(Modifier.weight(1f)) {
70+
LazyColumn(
71+
Modifier
72+
.weight(1f)
73+
.testTag("categories_screen_full_list")
74+
) {
7175
categories.entries.forEach { group ->
7276
stickyHeader(key = group.key) {
7377
CategoryTypeHeader(type = stringResource(id = group.key))

app/src/main/java/com/grishko188/pointofinterest/features/categories/ui/CategoryDetailedScreen.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.Color
1818
import androidx.compose.ui.graphics.vector.ImageVector
1919
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
2020
import androidx.compose.ui.platform.SoftwareKeyboardController
21+
import androidx.compose.ui.platform.testTag
2122
import androidx.compose.ui.res.stringResource
2223
import androidx.compose.ui.res.vectorResource
2324
import androidx.compose.ui.text.TextRange
@@ -132,7 +133,11 @@ fun CategoriesDetailedSuccessContent(
132133
focusRequester.requestFocus()
133134
}
134135

135-
Column(modifier = Modifier.background(MaterialTheme.colorScheme.background)) {
136+
Column(
137+
modifier = Modifier
138+
.background(MaterialTheme.colorScheme.background)
139+
.testTag("detailed_category_content")
140+
) {
136141

137142
Column(
138143
modifier = Modifier
@@ -165,14 +170,15 @@ fun CategoriesDetailedSuccessContent(
165170
trailingIcon = {
166171
if (textFieldValue.text.isNotEmpty()) {
167172
IconButton(
168-
onClick = { onTextChanged(TextFieldValue("")) }
173+
onClick = { onTextChanged(TextFieldValue("")) },
174+
169175
) {
170176
Icon(
171177
modifier = Modifier
172178
.size(20.dp)
173179
.background(MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f), shape = CircleShape),
174180
imageVector = Icons.Default.Clear,
175-
contentDescription = "",
181+
contentDescription = "Clear text icon",
176182
tint = MaterialTheme.colorScheme.background
177183
)
178184
}
@@ -193,7 +199,9 @@ fun CategoriesDetailedSuccessContent(
193199
)
194200

195201
GridColorPalette(
196-
modifier = Modifier.padding(16.dp),
202+
modifier = Modifier
203+
.padding(16.dp)
204+
.testTag("grid_color_pallete"),
197205
onColorSelected = onColorSelected,
198206
originalColor = selectedCategory?.color,
199207
selectedColor = selectedColor

app/src/main/java/com/grishko188/pointofinterest/features/categories/ui/composable/CategoriesScreenComposables.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@ import androidx.compose.foundation.background
77
import androidx.compose.foundation.border
88
import androidx.compose.foundation.clickable
99
import androidx.compose.foundation.layout.*
10-
import androidx.compose.foundation.lazy.LazyColumn
11-
import androidx.compose.foundation.lazy.LazyRow
1210
import androidx.compose.foundation.lazy.grid.GridCells
1311
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
14-
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
1512
import androidx.compose.foundation.lazy.grid.items
1613
import androidx.compose.foundation.shape.CircleShape
1714
import androidx.compose.material.*
@@ -21,15 +18,16 @@ import androidx.compose.material3.Text
2118
import androidx.compose.runtime.*
2219
import androidx.compose.ui.Alignment
2320
import androidx.compose.ui.Alignment.Companion.Center
24-
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
2521
import androidx.compose.ui.Alignment.Companion.CenterVertically
2622
import androidx.compose.ui.Modifier
2723
import androidx.compose.ui.draw.clip
2824
import androidx.compose.ui.graphics.Color
2925
import androidx.compose.ui.graphics.toArgb
3026
import androidx.compose.ui.graphics.vector.ImageVector
27+
import androidx.compose.ui.platform.testTag
3128
import androidx.compose.ui.res.stringResource
3229
import androidx.compose.ui.res.vectorResource
30+
import androidx.compose.ui.semantics.*
3331
import androidx.compose.ui.tooling.preview.Preview
3432
import androidx.compose.ui.unit.dp
3533
import androidx.compose.ui.unit.sp
@@ -192,7 +190,9 @@ fun GridColorPalette(
192190
if (originalColor != null) {
193191

194192
ColorPaletteItem(
195-
modifier = Modifier.size(48.dp),
193+
modifier = Modifier
194+
.size(48.dp)
195+
.testTag("original_selected_color_item"),
196196
color = originalColor,
197197
isSelected = true,
198198
onColorClicked = onColorSelected
@@ -236,10 +236,11 @@ fun GridColorPalette(
236236
fun ColorPaletteItem(modifier: Modifier, color: Color, isSelected: Boolean, onColorClicked: (Color) -> Unit) {
237237
Box(
238238
modifier = modifier
239+
.semantics { selected = isSelected }
239240
.background(color, shape = CircleShape)
240241
.clip(CircleShape)
241242
.border(width = 1.dp, color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.5f), shape = CircleShape)
242-
.clickable { onColorClicked(color) }
243+
.clickable(role = Role.Button) { onColorClicked(color) }
243244
.aspectRatio(1f)
244245

245246

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package com.grishko188.pointofinterest.features.categories.ui
2+
3+
import androidx.compose.runtime.remember
4+
import androidx.compose.ui.ExperimentalComposeUiApi
5+
import androidx.compose.ui.focus.FocusRequester
6+
import androidx.compose.ui.graphics.Color
7+
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
8+
import androidx.compose.ui.semantics.Role
9+
import androidx.compose.ui.semantics.SemanticsProperties
10+
import androidx.compose.ui.test.*
11+
import androidx.compose.ui.test.junit4.createComposeRule
12+
import androidx.compose.ui.text.input.TextFieldValue
13+
import com.grishko188.pointofinterest.MockitoHelper.anyNonNull
14+
import com.grishko188.pointofinterest.MockitoHelper.whenever
15+
import com.grishko188.pointofinterest.features.categories.ui.models.CategoryUiModel
16+
import com.grishko188.pointofinterest.features.categories.ui.models.DetailedCategoriesUiState
17+
import org.junit.After
18+
import org.junit.Before
19+
import org.junit.Rule
20+
import org.junit.Test
21+
import org.junit.runner.RunWith
22+
import org.mockito.Mock
23+
import org.mockito.Mockito
24+
import org.mockito.Mockito.times
25+
import org.mockito.Mockito.verify
26+
import org.mockito.MockitoAnnotations
27+
import org.mockito.junit.MockitoJUnit
28+
import org.robolectric.RobolectricTestRunner
29+
import org.robolectric.annotation.Config
30+
import org.robolectric.shadows.ShadowLog
31+
32+
@RunWith(RobolectricTestRunner::class)
33+
@Config(instrumentedPackages = ["androidx.loader.content"])
34+
class CategoriesDetailedScreenUiTest {
35+
36+
@get:Rule(order = 0)
37+
val composeTestRule = createComposeRule()
38+
39+
@get:Rule(order = 1)
40+
var mockRule = MockitoJUnit.rule()
41+
42+
@Mock
43+
lateinit var onTextChanged: (TextFieldValue) -> Unit
44+
45+
@Mock
46+
lateinit var onColorSelected: (Color) -> Unit
47+
48+
@Mock
49+
lateinit var onSave: (String, Color) -> Unit
50+
51+
@Mock
52+
lateinit var onUpdate: (CategoryUiModel, String, Color) -> Unit
53+
54+
@Before
55+
@Throws(Exception::class)
56+
fun setUp() {
57+
MockitoAnnotations.openMocks(this)
58+
ShadowLog.stream = System.out
59+
}
60+
61+
@After
62+
fun teardown() {
63+
Mockito.validateMockitoUsage()
64+
}
65+
66+
@OptIn(ExperimentalComposeUiApi::class)
67+
@Test
68+
fun `test CategoriesDetailedContent displays progress when uiState is DetailedCategoriesUiState_Loading`() {
69+
composeTestRule.setContent {
70+
val focusRequester = remember { FocusRequester() }
71+
72+
CategoriesDetailedContent(
73+
uiState = DetailedCategoriesUiState.Loading,
74+
selectedColor = Color.Red,
75+
focusRequester = focusRequester,
76+
keyboardController = LocalSoftwareKeyboardController.current,
77+
textFieldValue = TextFieldValue(""),
78+
onTextChanged = onTextChanged,
79+
onColorSelected = onColorSelected,
80+
onSave = onSave,
81+
onUpdate = onUpdate
82+
)
83+
}
84+
85+
composeTestRule.onNodeWithTag("progress_view")
86+
.assertExists()
87+
.assertIsDisplayed()
88+
}
89+
90+
@OptIn(ExperimentalComposeUiApi::class)
91+
@Test
92+
fun `test CategoriesDetailedContent displays CategoriesDetailedSuccessContent when uiState is DetailedCategoriesUiState_Success`() {
93+
composeTestRule.setContent {
94+
val focusRequester = remember { FocusRequester() }
95+
96+
CategoriesDetailedContent(
97+
uiState = DetailedCategoriesUiState.Success(null),
98+
selectedColor = Color.Red,
99+
focusRequester = focusRequester,
100+
keyboardController = LocalSoftwareKeyboardController.current,
101+
textFieldValue = TextFieldValue(""),
102+
onTextChanged = onTextChanged,
103+
onColorSelected = onColorSelected,
104+
onSave = onSave,
105+
onUpdate = onUpdate
106+
)
107+
}
108+
109+
composeTestRule.onNodeWithTag("progress_view")
110+
.assertDoesNotExist()
111+
112+
composeTestRule.onNodeWithTag("detailed_category_content")
113+
.assertExists()
114+
.assertIsDisplayed()
115+
}
116+
117+
@OptIn(ExperimentalComposeUiApi::class)
118+
@Test
119+
fun `test CategoriesDetailedSuccessContent empty screen behaviour when uiState is DetailedCategoriesUiState_Success and selectedCategory is null`() {
120+
whenever(onTextChanged(anyNonNull())).thenReturn(Unit)
121+
whenever(onColorSelected(anyNonNull())).thenReturn(Unit)
122+
123+
composeTestRule.setContent {
124+
val focusRequester = remember { FocusRequester() }
125+
126+
CategoriesDetailedContent(
127+
uiState = DetailedCategoriesUiState.Success(null),
128+
selectedColor = Color.Transparent,
129+
focusRequester = focusRequester,
130+
keyboardController = LocalSoftwareKeyboardController.current,
131+
textFieldValue = TextFieldValue(""),
132+
onTextChanged = onTextChanged,
133+
onColorSelected = onColorSelected,
134+
onSave = onSave,
135+
onUpdate = onUpdate
136+
)
137+
}
138+
139+
composeTestRule.onNodeWithContentDescription("Clear text icon")
140+
.assertDoesNotExist()
141+
142+
composeTestRule.onNodeWithText("Save", ignoreCase = true)
143+
.assertExists()
144+
.assertIsDisplayed()
145+
.assertIsNotEnabled()
146+
147+
composeTestRule.onNodeWithTag("grid_color_pallete")
148+
.assertExists()
149+
.assertIsDisplayed()
150+
.onChildren()
151+
.filter(SemanticsMatcher.keyIsDefined(SemanticsProperties.HorizontalScrollAxisRange))
152+
.onFirst()
153+
.onChildren()
154+
.filter(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
155+
.assertAll(SemanticsMatcher.expectValue(SemanticsProperties.Selected, false))
156+
.onFirst()
157+
.assertHasClickAction()
158+
.performClick()
159+
160+
composeTestRule.onNodeWithTag("original_selected_color_item")
161+
.assertDoesNotExist()
162+
163+
composeTestRule.onNode(SemanticsMatcher.keyIsDefined(SemanticsProperties.EditableText))
164+
.assertExists()
165+
.assertIsDisplayed()
166+
.performTextInput("Test")
167+
168+
verify(onColorSelected, times(1)).invoke(anyNonNull())
169+
verify(onTextChanged, times(1)).invoke(anyNonNull())
170+
}
171+
}

0 commit comments

Comments
 (0)