Skip to content

Instantly share code, notes, and snippets.

@Dadoufi
Forked from gnumilanix/CustomSemantics.kt
Created September 6, 2024 11:29
Show Gist options
  • Save Dadoufi/e43ef6764039471c5d3fdf4cf20362e3 to your computer and use it in GitHub Desktop.
Save Dadoufi/e43ef6764039471c5d3fdf4cf20362e3 to your computer and use it in GitHub Desktop.
Compose custom semantics with multiple matchers

Unlike sample here which produces single semantics which makes it impossible to test nodes with multiple drawable ids. Following allows producing and testing multiple semantics of same type in a tree.

Usage in compose:

Modifier.semantics { drawableId = R.drawable.baseline_call_made_24 }
Modifier.semantics { drawableUrl = avatar ?: "" }

Produces tree:

Node #1
  |-Node #4
    ...
    |-Node #9
    | DrawableUrl = '[https://placekitten.com/200/100, https://placekitten.com/200/200]'
    | DrawableResId = '[2131230723, 2131230722]'
    ...

Test:

composeTestRule
    .onNode(SemanticsMatcher.keyIsDefined(SemanticsProperties.CollectionInfo))
    .onChildAt(1)
    .assert(
        hasDrawable(target1.avatar) and
                hasText(target1.cover) and
                hasDrawable(R.drawable.baseline_call_made_24) and
                hasDrawable(R.drawable.baseline_call_24)
    )
    .assertIsDisplayed()
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
val DrawableId = SemanticsPropertyKey<List<Int>>(
name = "DrawableResId",
mergePolicy = mergeSemantics()
)
var SemanticsPropertyReceiver.drawableId: Int
get() = throwSemanticsGetNotSupported()
set(value) = set(DrawableId, listOf(value))
val DrawableUrl = SemanticsPropertyKey<List<String>>(
name = "DrawableUrl",
mergePolicy = mergeSemantics()
)
var SemanticsPropertyReceiver.drawableUrl: String
get() = throwSemanticsGetNotSupported()
set(value) = set(DrawableUrl, listOf(value))
private fun <T> mergeSemantics(): (List<T>?, List<T>) -> List<T> {
return { parentValue, childValue ->
parentValue?.toMutableList()?.also { it.addAll(childValue) } ?: childValue
}
}
private fun <T> throwSemanticsGetNotSupported(): T {
throw UnsupportedOperationException(
"You cannot retrieve a semantics property directly - " +
"use one of the SemanticsConfiguration.getOr* methods instead"
)
}
import androidx.annotation.DrawableRes
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
import com.ignitetech.compose.utility.DrawableId
import com.ignitetech.compose.utility.DrawableUrl
fun hasDrawable(@DrawableRes id: Int) = matcher(DrawableId, id)
fun hasDrawable(url: String?) = matcher(DrawableUrl, url)
private fun <T> matcher(property: SemanticsPropertyKey<List<T>>, expected: T?): SemanticsMatcher {
return SemanticsMatcher("${property.name} = [$expected]") {
it.config.getOrNull(property)?.any { item -> item == expected } ?: false
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment