Skip to content

Commit d1f688f

Browse files
authored
Merge branch 'main' into fix/plural-word-not-recognised
2 parents e4193c1 + 6562b93 commit d1f688f

File tree

17 files changed

+1275
-46
lines changed

17 files changed

+1275
-46
lines changed
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
package be.scri.ui.screens.settings
4+
5+
import android.content.Context
6+
import androidx.compose.foundation.clickable
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.lazy.LazyColumn
12+
import androidx.compose.foundation.lazy.items
13+
import androidx.compose.material3.Switch
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.platform.testTag
19+
import androidx.compose.ui.semantics.Role
20+
import androidx.compose.ui.semantics.semantics
21+
import androidx.compose.ui.semantics.stateDescription
22+
import androidx.compose.ui.semantics.toggleableState
23+
import androidx.compose.ui.state.ToggleableState
24+
import androidx.compose.ui.test.assertCountEquals
25+
import androidx.compose.ui.test.assertIsDisplayed
26+
import androidx.compose.ui.test.assertIsNotDisplayed
27+
import androidx.compose.ui.test.junit4.createComposeRule
28+
import androidx.compose.ui.test.onAllNodesWithTag
29+
import androidx.compose.ui.test.onNodeWithTag
30+
import androidx.compose.ui.test.onNodeWithText
31+
import androidx.compose.ui.test.performClick
32+
import androidx.compose.ui.test.performScrollToIndex
33+
import androidx.compose.ui.unit.dp
34+
import androidx.test.core.app.ApplicationProvider
35+
import androidx.test.ext.junit.runners.AndroidJUnit4
36+
import be.scri.R
37+
import be.scri.ui.models.ScribeItem
38+
import be.scri.ui.models.ScribeItemList
39+
import org.junit.Rule
40+
import org.junit.Test
41+
import org.junit.runner.RunWith
42+
43+
@RunWith(AndroidJUnit4::class)
44+
class ScribeItemListTest {
45+
@get:Rule
46+
val composeRule = createComposeRule()
47+
48+
private val context = ApplicationProvider.getApplicationContext<Context>()
49+
50+
@Test
51+
fun displaysClickableItems() {
52+
val items =
53+
listOf(
54+
ScribeItem.ClickableItem(
55+
title = R.string.app_settings_menu_app_language,
56+
desc = R.string.app_settings_menu_app_language_description,
57+
action = { },
58+
),
59+
ScribeItem.ClickableItem(
60+
title = R.string.app_settings_keyboard_title,
61+
desc = null,
62+
action = { },
63+
),
64+
)
65+
66+
composeRule.setContent {
67+
ScribeItemList(itemList = ScribeItemList(items))
68+
}
69+
70+
composeRule.onNodeWithTag("scribe_item_list").assertIsDisplayed()
71+
composeRule.onAllNodesWithTag("scribe_item").assertCountEquals(2)
72+
}
73+
74+
@Test
75+
fun displaysSwitchItems() {
76+
val items =
77+
listOf(
78+
ScribeItem.SwitchItem(
79+
title = R.string.app_settings_menu_app_color_mode,
80+
desc = R.string.app_settings_menu_app_color_mode_description,
81+
state = false,
82+
onToggle = { },
83+
),
84+
ScribeItem.SwitchItem(
85+
title = R.string.app_settings_keyboard_title,
86+
desc = 0,
87+
state = true,
88+
onToggle = { },
89+
),
90+
)
91+
92+
composeRule.setContent {
93+
ScribeItemList(itemList = ScribeItemList(items))
94+
}
95+
96+
composeRule.onNodeWithTag("scribe_item_list").assertIsDisplayed()
97+
composeRule.onNodeWithTag("switch_item_0").assertIsDisplayed()
98+
composeRule.onNodeWithTag("switch_item_1").assertIsDisplayed()
99+
}
100+
101+
@Test
102+
fun showsEmptyState() {
103+
composeRule.setContent {
104+
ScribeItemList(itemList = ScribeItemList(emptyList()))
105+
}
106+
107+
composeRule.onNodeWithTag("empty_state").assertIsDisplayed()
108+
composeRule.onNodeWithText("No items available").assertIsDisplayed()
109+
}
110+
111+
@Test
112+
fun handlesItemClicks() {
113+
var clicked = false
114+
val items =
115+
listOf(
116+
ScribeItem.ClickableItem(
117+
title = R.string.app_settings_menu_app_language,
118+
desc = R.string.app_settings_menu_app_language_description,
119+
action = { clicked = true },
120+
),
121+
)
122+
123+
composeRule.setContent {
124+
ScribeItemList(itemList = ScribeItemList(items))
125+
}
126+
127+
composeRule.onNodeWithTag("clickable_item_0").performClick()
128+
assert(clicked)
129+
}
130+
131+
@Test
132+
fun togglesSwitchState() {
133+
var state = false
134+
val items =
135+
listOf(
136+
ScribeItem.SwitchItem(
137+
title = R.string.app_settings_menu_app_color_mode,
138+
desc = R.string.app_settings_menu_app_color_mode_description,
139+
state = state,
140+
onToggle = { state = it },
141+
),
142+
)
143+
144+
composeRule.setContent {
145+
ScribeItemList(itemList = ScribeItemList(items))
146+
}
147+
148+
composeRule.onNodeWithTag("switch_item_0").performClick()
149+
assert(state)
150+
}
151+
152+
@Test
153+
fun scrollsLargeListInLazyColumn() {
154+
val items =
155+
(0..49).map { index ->
156+
ScribeItem.ClickableItem(
157+
title = R.string.app_settings_menu_app_language,
158+
desc = R.string.app_settings_menu_app_language_description,
159+
action = { },
160+
)
161+
}
162+
163+
composeRule.setContent {
164+
ScribeItemList(itemList = ScribeItemList(items))
165+
}
166+
167+
composeRule.onNodeWithTag("scribe_item_list").assertIsDisplayed()
168+
169+
composeRule.onNodeWithTag("clickable_item_0").assertIsDisplayed()
170+
171+
composeRule.onNodeWithTag("clickable_item_49").assertIsNotDisplayed()
172+
173+
composeRule.onNodeWithTag("scribe_item_list").performScrollToIndex(49)
174+
175+
composeRule.onNodeWithTag("clickable_item_49").assertIsDisplayed()
176+
}
177+
178+
@Test
179+
fun displaysEmptyStateWhenNoItems() {
180+
composeRule.setContent {
181+
ScribeItemList(itemList = ScribeItemList(emptyList()))
182+
}
183+
184+
composeRule.onNodeWithTag("empty_state").assertIsDisplayed()
185+
composeRule.onNodeWithTag("scribe_item_list").assertIsNotDisplayed()
186+
}
187+
188+
@Test
189+
fun displaysLazyColumnWhenItemsExist() {
190+
val items =
191+
listOf(
192+
ScribeItem.ClickableItem(
193+
title = R.string.app_settings_menu_app_language,
194+
desc = R.string.app_settings_menu_app_language_description,
195+
action = { },
196+
),
197+
)
198+
199+
composeRule.setContent {
200+
ScribeItemList(itemList = ScribeItemList(items))
201+
}
202+
203+
composeRule.onNodeWithTag("scribe_item_list").assertIsDisplayed()
204+
composeRule.onNodeWithTag("empty_state").assertIsNotDisplayed()
205+
}
206+
207+
@Composable
208+
private fun ClickableItem(
209+
item: ScribeItem.ClickableItem,
210+
index: Int,
211+
) {
212+
Column(
213+
modifier =
214+
Modifier
215+
.testTag("clickable_item_$index")
216+
.clickable { item.action() },
217+
) {
218+
Text(
219+
text = context.getString(item.title),
220+
)
221+
item.desc?.let { desc ->
222+
Text(
223+
text = context.getString(desc),
224+
modifier = Modifier.testTag("description_$index"),
225+
)
226+
}
227+
}
228+
}
229+
230+
@Composable
231+
private fun SwitchItem(
232+
item: ScribeItem.SwitchItem,
233+
index: Int,
234+
) {
235+
Row(
236+
modifier =
237+
Modifier
238+
.testTag("switch_item_$index")
239+
.clickable(role = Role.Switch) { item.onToggle(!item.state) }
240+
.semantics {
241+
toggleableState = if (item.state) ToggleableState.On else ToggleableState.Off
242+
stateDescription = if (item.state) "On" else "Off"
243+
}.padding(16.dp),
244+
verticalAlignment = Alignment.CenterVertically,
245+
) {
246+
Text(
247+
text = context.getString(item.title),
248+
modifier = Modifier.weight(1f),
249+
)
250+
Switch(
251+
checked = item.state,
252+
onCheckedChange = item.onToggle,
253+
)
254+
}
255+
}
256+
257+
@Composable
258+
private fun ScribeItemList(itemList: ScribeItemList) {
259+
if (itemList.items.isEmpty()) {
260+
Text(
261+
text = "No items available",
262+
modifier = Modifier.testTag("empty_state"),
263+
)
264+
} else {
265+
LazyColumn(
266+
modifier =
267+
Modifier
268+
.fillMaxSize()
269+
.testTag("scribe_item_list"),
270+
) {
271+
items(itemList.items) { item ->
272+
when (item) {
273+
is ScribeItem.ClickableItem -> {
274+
Text(
275+
text = "",
276+
modifier = Modifier.testTag("scribe_item"),
277+
)
278+
ClickableItem(
279+
item = item,
280+
index = itemList.items.indexOf(item),
281+
)
282+
}
283+
is ScribeItem.SwitchItem -> {
284+
Text(
285+
text = "",
286+
modifier = Modifier.testTag("scribe_item"),
287+
)
288+
SwitchItem(
289+
item = item,
290+
index = itemList.items.indexOf(item),
291+
)
292+
}
293+
else -> {
294+
Text(
295+
text = "Unknown item type",
296+
modifier = Modifier.testTag("scribe_item"),
297+
)
298+
}
299+
}
300+
}
301+
}
302+
}
303+
}
304+
}

0 commit comments

Comments
 (0)