maarten ha
09/16/2025, 5:07 AMmaarten ha
09/16/2025, 5:08 AM@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SuggestedTextField(
suggestions: List<String>,
modifier: Modifier = Modifier
) {
var statement by remember { mutableStateOf("") }
var expanded by remember { mutableStateOf(false) }
val filteredSuggestions = suggestions.filter {
it.contains(statement, ignoreCase = true)
}
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
) {
TextField(
value = statement,
onValueChange = {
statement = it
expanded = true
},
label = { Text("Statement") },
modifier = modifier
.testTag("SuggestedTextField")
.onFocusChanged { expanded = it.isFocused }
.onPreviewKeyEvent { e ->
if (!expanded) return@onPreviewKeyEvent false
when (e.key) {
Key.DirectionDown -> {
if (filteredSuggestions.isEmpty()) return@onPreviewKeyEvent false
val currentIndex =
filteredSuggestions.indexOfFirst { it.equals(statement, ignoreCase = true) }
val nextIndex = if (currentIndex >= 0) (currentIndex + 1) % filteredSuggestions.size else 0
statement = filteredSuggestions[nextIndex]
true
}
Key.DirectionUp -> {
if (filteredSuggestions.isEmpty()) return@onPreviewKeyEvent false
val currentIndex =
filteredSuggestions.indexOfFirst { it.equals(statement, ignoreCase = true) }
val prevIndex =
if (currentIndex >= 0) (currentIndex - 1 + filteredSuggestions.size) % filteredSuggestions.size else filteredSuggestions.lastIndex
statement = filteredSuggestions[prevIndex]
true
}
else -> false
}
}
.menuAnchor(),
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded) },
)
ExposedDropdownMenu(
expanded = expanded && filteredSuggestions.isNotEmpty(),
onDismissRequest = { expanded = false },
modifier = Modifier.testTag("DropdownMenu")
) {
filteredSuggestions.forEachIndexed { index, suggestion ->
DropdownMenuItem(
text = { Text(suggestion) },
onClick = {
statement = suggestion
expanded = false
},
modifier = Modifier.testTag("DropdownMenu$index")
.focusable()
.onPreviewKeyEvent { e ->
when (e.key) {
Key.Enter -> {
statement = suggestion
expanded = false
true
}
else -> false
}
}
)
}
}
}
}maarten ha
09/16/2025, 5:11 AM@Test
fun `enter key selects dropdown item`() = runComposeUiTest {
setContent { SuggestedTextField(items) }
val textField = onNodeWithTag("SuggestedTextField")
textField.performClick()
textField.performKeyInput { keyDown(Key.DirectionDown); keyUp(Key.DirectionDown) }
onNode(hasText(items[0]) and hasAnyAncestor(hasTestTag("DropdownMenu"))).assertIsFocused()
textField.performKeyInput { keyDown(Key.Enter); keyUp(Key.Enter) }
onNodeWithTag("SuggestedTextField").assert(hasText(items[0]))
onNodeWithTag("DropdownMenu").assertDoesNotExist()
}