Skip to content

Commit

Permalink
Enhancement - Add support for content checking and tint color for com…
Browse files Browse the repository at this point in the history
…pose icon
  • Loading branch information
Evgeny Meltsaykin committed Dec 6, 2023
1 parent 59331ea commit 7eb162b
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.kakaocup.compose.node.assertion

import androidx.annotation.DrawableRes
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert

interface ImageContentAssertions : NodeAssertions {
val imageContentSemanticsPropertyKey: SemanticsPropertyKey<Any>

/**
* Asserts that the image or icon content contains the given [imageVector].
*
* Throws [AssertionError] if the image or icon content value is not equal to `imageVector`.
* Throws [IllegalStateException] if the image or icon does not contain the [imageContentSemanticsPropertyKey] modifier.
*/
fun assertContentEquals(imageVector: ImageVector) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasImageContent(imageVector)) }
}

/**
* Asserts that the image or icon content contains the given [drawableRes].
*
* Throws [AssertionError] if the image or icon content value is not equal to `drawableRes`.
* Throws [IllegalStateException] if the image or icon does not contain the [imageContentSemanticsPropertyKey] modifier.
*/
fun assertContentEquals(@DrawableRes drawableRes: Int) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasImageContent(drawableRes)) }
}

private fun hasImageContent(expectedContent: Any): SemanticsMatcher = SemanticsMatcher(
"The content is expected to be $expectedContent, but the actual content is different"
) { node ->
val actual = node.config.getOrNull(imageContentSemanticsPropertyKey)
?: error("Compose view does not contain $imageContentSemanticsPropertyKey modifier")

return@SemanticsMatcher actual == expectedContent
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package io.github.kakaocup.compose.node.assertion

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import io.github.kakaocup.compose.utilities.getComposeColor

interface TintColorAssertions : NodeAssertions {
val tintColorSemanticsPropertyKey: SemanticsPropertyKey<Color>

/**
* Asserts that the compose view tint color contains the given [color].
*
* Throws [AssertionError] if the compose view tint color value is not equal to `color`.
* Throws [IllegalStateException] if the compose view does not contain the [tintColorSemanticsPropertyKey] modifier.
*/
fun assertTintColorEquals(color: Color) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasColor(color)) }
}

/**
* Asserts that the compose view tint color contains the given [color].
*
* Throws [AssertionError] if the compose view tint color value is not equal to `color`.
* Throws [IllegalStateException] if the compose view does not contain the [tintColorSemanticsPropertyKey] modifier.
* Throws [IllegalArgumentException] if the color value is incorrect.
*/
fun assertTintColorEquals(color: String) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasColor(color)) }
}

/**
* Asserts that the compose view tint color contains the given [color].
*
* Throws [AssertionError] if the compose view tint color value is not equal to `color`.
* Throws [IllegalStateException] if the compose view does not contain the [tintColorSemanticsPropertyKey] modifier.
*/
fun assertTintColorEquals(color: Long) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasColor(color)) }
}

private fun hasColor(expectedColor: Color): SemanticsMatcher = SemanticsMatcher(
"The color is expected to be $expectedColor, but the actual color is different"
) { node ->
val actualColor = node.config.getOrNull(tintColorSemanticsPropertyKey)
?: error("Compose view does not contain $tintColorSemanticsPropertyKey modifier")

return@SemanticsMatcher actualColor == expectedColor
}

private fun hasColor(expectedColor: String): SemanticsMatcher = SemanticsMatcher(
"The color is expected to be $expectedColor, but the actual color is different"
) { node ->
val actualColor = node.config.getOrNull(tintColorSemanticsPropertyKey)
?: error("Compose view does not contain $tintColorSemanticsPropertyKey modifier")

return@SemanticsMatcher actualColor == getComposeColor(expectedColor)
}

private fun hasColor(expectedColor: Long): SemanticsMatcher = SemanticsMatcher(
"The color is expected to be $expectedColor, but the actual color is different"
) { node ->
val actualColor = node.config.getOrNull(tintColorSemanticsPropertyKey)
?: error("Compose view does not contain $tintColorSemanticsPropertyKey modifier")

return@SemanticsMatcher actualColor == Color(expectedColor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.github.kakaocup.compose.node.element

import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import io.github.kakaocup.compose.node.builder.NodeMatcher
import io.github.kakaocup.compose.node.core.BaseNode
import io.github.kakaocup.compose.node.assertion.ImageContentAssertions
import io.github.kakaocup.compose.node.assertion.TintColorAssertions

abstract class KIconNode(
semanticsProvider: SemanticsNodeInteractionsProvider,
nodeMatcher: NodeMatcher,
parentNode: BaseNode<*>?,
useUnmergedTree: Boolean = false
) : BaseNode<KIconNode>(
semanticsProvider = semanticsProvider,
nodeMatcher = nodeMatcher.copy(useUnmergedTree = useUnmergedTree),
parentNode = parentNode
), ImageContentAssertions, TintColorAssertions
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.kakaocup.compose.utilities

import androidx.compose.ui.graphics.Color

internal fun getComposeColor(color: String): Color {
val normalizeColor = if (color.contains("#")) color else "#$color"
return Color(android.graphics.Color.parseColor(normalizeColor))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.kakaocup.compose.node

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import io.github.kakaocup.compose.node.builder.NodeMatcher
import io.github.kakaocup.compose.node.core.BaseNode
import io.github.kakaocup.compose.node.element.KIconNode
import io.github.kakaocup.compose.sample.semantics.ImageContentSemanticKey
import io.github.kakaocup.compose.sample.semantics.TintColorSemanticKey

class KAppIconNode(
semanticsProvider: SemanticsNodeInteractionsProvider,
nodeMatcher: NodeMatcher,
parentNode: BaseNode<*>? = null,
) : KIconNode(
semanticsProvider = semanticsProvider,
nodeMatcher = nodeMatcher,
parentNode = parentNode,
useUnmergedTree = true
) {
override val imageContentSemanticsPropertyKey: SemanticsPropertyKey<Any> = ImageContentSemanticKey
override val tintColorSemanticsPropertyKey: SemanticsPropertyKey<Color> = TintColorSemanticKey
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.kakaocup.compose.screen

import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import io.github.kakaocup.compose.node.KAppIconNode
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.KNode

Expand All @@ -20,6 +21,18 @@ class MainActivityScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasPosition(1)
}

val changeIconsButton: KNode = child {
hasTestTag("changeIconButton")
}

val iconDrawableRes: KAppIconNode = child {
hasTestTag("iconDrawableRes")
}

val iconImageVector: KAppIconNode = child {
hasTestTag("iconImageVector")
}

val myButton: KNode = child {
hasTestTag("myTestButton")
hasText("Button 1")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package io.github.kakaocup.compose.test

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Call
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import io.github.kakaocup.compose.sample.MainActivity
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
import io.github.kakaocup.compose.sample.R
import io.github.kakaocup.compose.screen.MainActivityScreen
import org.junit.Rule
import org.junit.Test
Expand All @@ -18,7 +23,7 @@ class SimpleTest {
myButton {
assertIsDisplayed()
assertTextContains("Button 1")
assertTextContains(io.github.kakaocup.compose.sample.R.string.button_1)
assertTextContains(R.string.button_1)
}

myText1 {
Expand All @@ -38,4 +43,50 @@ class SimpleTest {
}
}
}

@Test
fun iconTest() {
onComposeScreen<MainActivityScreen>(composeTestRule) {
changeIconsButton{
assertIsDisplayed()
}
iconDrawableRes{
assertIsDisplayed()
assertContentEquals(R.drawable.ic_android)
assertTintColorEquals(Color.Black)
assertTintColorEquals("000000")
assertTintColorEquals("#000000")
assertTintColorEquals(0xFF000000)
}

iconImageVector{
assertIsDisplayed()
assertContentEquals(Icons.Filled.AccountCircle)
assertTintColorEquals(Color.Black)
assertTintColorEquals("000000")
assertTintColorEquals("#000000")
assertTintColorEquals(0xFF000000)
}

changeIconsButton.performClick()

iconDrawableRes{
assertIsDisplayed()
assertContentEquals(R.drawable.ic_adb)
assertTintColorEquals(Color.Blue)
assertTintColorEquals("0000FF")
assertTintColorEquals("#0000FF")
assertTintColorEquals(0xFF0000FF)
}

iconImageVector{
assertIsDisplayed()
assertContentEquals(Icons.Filled.Call)
assertTintColorEquals(Color.Blue)
assertTintColorEquals("0000FF")
assertTintColorEquals("#0000FF")
assertTintColorEquals(0xFF0000FF)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
package io.github.kakaocup.compose.sample

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Call
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import io.github.kakaocup.compose.sample.semantics.imageContentSemantic
import io.github.kakaocup.compose.sample.semantics.tintColorSemantic

@Composable
fun MainScreen() {
var tintColor by remember { mutableStateOf(Color.Black) }
var iconRes by remember { mutableIntStateOf(R.drawable.ic_android) }
var iconImageVector by remember { mutableStateOf(Icons.Filled.AccountCircle) }

Column(
modifier = Modifier
.fillMaxSize()
Expand All @@ -39,6 +54,41 @@ fun MainScreen() {
.semantics { testTag = "mySimpleText" }
)

Row {
Icon(
modifier = Modifier
.imageContentSemantic(iconRes)
.tintColorSemantic(tintColor)
.testTag("iconDrawableRes"),
painter = painterResource(id = iconRes),
tint = tintColor,
contentDescription = null
)
Icon(
modifier = Modifier
.imageContentSemantic(iconImageVector)
.tintColorSemantic(tintColor)
.testTag("iconImageVector"),
imageVector = iconImageVector,
tint = tintColor,
contentDescription = null
)
}

Button(
content = {
Text(text = stringResource(R.string.button_change_icon))
},
onClick = {
tintColor = Color.Blue
iconRes = R.drawable.ic_adb
iconImageVector = Icons.Filled.Call
},
modifier = Modifier
.padding(8.dp)
.semantics { testTag = "changeIconButton" }
)

Button(
content = {
Text(text = stringResource(R.string.button_1))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.kakaocup.compose.sample.semantics

import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics

val TintColorSemanticKey = SemanticsPropertyKey<Color>("TintColor")
var SemanticsPropertyReceiver.tintColor by TintColorSemanticKey
fun Modifier.tintColorSemantic(color: Color): Modifier {
return semantics { tintColor = color }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.kakaocup.compose.sample.semantics

import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics

val ImageContentSemanticKey = SemanticsPropertyKey<Any>("ImageContent")
var SemanticsPropertyReceiver.imageContent by ImageContentSemanticKey
fun Modifier.imageContentSemantic(imageContent: Any): Modifier {
return semantics { this.imageContent = imageContent }
}
5 changes: 5 additions & 0 deletions sample/src/main/res/drawable/ic_adb.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4L5,12v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM15,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
</vector>
5 changes: 5 additions & 0 deletions sample/src/main/res/drawable/ic_android.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"/>
</vector>
Loading

0 comments on commit 7eb162b

Please sign in to comment.