Skip to content

Instantly share code, notes, and snippets.

@gnumilanix
Created March 26, 2023 12:52
Show Gist options
  • Save gnumilanix/2cee2e31ba319e644a94454051278f68 to your computer and use it in GitHub Desktop.
Save gnumilanix/2cee2e31ba319e644a94454051278f68 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