Skip to content

Commit

Permalink
Enhancement - Add support for text color checking
Browse files Browse the repository at this point in the history
  • Loading branch information
Evgeny Meltsaykin committed Dec 17, 2023
1 parent 76e304f commit bdd1913
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package io.github.kakaocup.compose.node.assertion

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsProperties
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 androidx.compose.ui.text.TextLayoutResult
import io.github.kakaocup.compose.utilities.ColorAssertionsUtils

interface TextColorAssertions : NodeAssertions {
val textColorSemanticsPropertyKey: SemanticsPropertyKey<Color>

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

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

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

private fun hasTextColor(expectedColor: Color): SemanticsMatcher = SemanticsMatcher(
"${SemanticsProperties.Text.name} is of color '$expectedColor'"
) { node ->
return@SemanticsMatcher node.hasTextColor(expectedColor = expectedColor)
}

private fun hasTextColor(expectedColor: Long): SemanticsMatcher = SemanticsMatcher(
"${SemanticsProperties.Text.name} is of color '$expectedColor'"
) { node ->
return@SemanticsMatcher node.hasTextColor(expectedColor = Color(expectedColor))
}

private fun hasTextColor(expectedColor: String): SemanticsMatcher = SemanticsMatcher(
"${SemanticsProperties.Text.name} is of color '$expectedColor'"
) { node ->
return@SemanticsMatcher node.hasTextColor(expectedColor = ColorAssertionsUtils.getComposeColor(expectedColor))
}

private fun SemanticsNode.hasTextColor(expectedColor: Color): Boolean {
val textLayoutResults = mutableListOf<TextLayoutResult>()
config.getOrNull(SemanticsActions.GetTextLayoutResult)?.action?.invoke(textLayoutResults)

val actualColorOfStyle = textLayoutResults.firstOrNull()?.layoutInput?.style?.color ?: Color.Unspecified
val actualColorOfSemantic = config.getOrNull(textColorSemanticsPropertyKey) ?: Color.Unspecified

/**
* look at how the overrideColor parameter is used
* @see [androidx.compose.foundation.text.modifiers.TextStringSimpleNode.draw]
* @see [androidx.compose.foundation.text.modifiers.TextAnnotatedStringNode.draw]
*/
val actualColor = when {
actualColorOfSemantic.isSpecified -> actualColorOfSemantic
actualColorOfStyle.isSpecified -> actualColorOfStyle
else -> Color.Black
}

return actualColor == expectedColor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ 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
import io.github.kakaocup.compose.utilities.ColorAssertionsUtils

interface TintColorAssertions : NodeAssertions {
val tintColorSemanticsPropertyKey: SemanticsPropertyKey<Color>
Expand All @@ -17,7 +15,9 @@ interface TintColorAssertions : NodeAssertions {
* 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)) }
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) {
assert(ColorAssertionsUtils.hasColor(color, tintColorSemanticsPropertyKey))
}
}

/**
Expand All @@ -28,7 +28,9 @@ interface TintColorAssertions : NodeAssertions {
* Throws [IllegalArgumentException] if the color value is incorrect.
*/
fun assertTintColorEquals(color: String) {
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) { assert(hasColor(color)) }
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) {
assert(ColorAssertionsUtils.hasColor(color, tintColorSemanticsPropertyKey))
}
}

/**
Expand All @@ -38,33 +40,8 @@ interface TintColorAssertions : NodeAssertions {
* 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)
delegate.check(NodeAssertions.ComposeBaseAssertionType.ASSERT_VALUE_EQUALS) {
assert(ColorAssertionsUtils.hasColor(color, tintColorSemanticsPropertyKey))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.github.kakaocup.compose.node.element

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

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

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher

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

return@SemanticsMatcher actualColor == expectedColor
}

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

return@SemanticsMatcher actualColor == getComposeColor(expectedColor)
}

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

return@SemanticsMatcher actualColor == Color(expectedColor)
}

fun getComposeColor(color: String): Color {
val colorString = if (color.contains("#")) color else "#$color"
return Color(android.graphics.Color.parseColor(colorString))
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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.KTextNode
import io.github.kakaocup.compose.sample.semantics.TextColorSemanticKey

class KAppTextNode(
semanticsProvider: SemanticsNodeInteractionsProvider,
nodeMatcher: NodeMatcher,
parentNode: BaseNode<*>? = null,
) : KTextNode(
semanticsProvider = semanticsProvider,
nodeMatcher = nodeMatcher,
parentNode = parentNode,
useUnmergedTree = true
) {
override val textColorSemanticsPropertyKey: SemanticsPropertyKey<Color> = TextColorSemanticKey
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,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.KAppTextNode
import io.github.kakaocup.compose.node.element.ComposeScreen
import io.github.kakaocup.compose.node.element.KNode

Expand Down Expand Up @@ -33,6 +34,22 @@ class MainActivityScreen(semanticsProvider: SemanticsNodeInteractionsProvider) :
hasTestTag("iconImageVector")
}

val changeTextColorButton: KNode = child {
hasTestTag("changeTextColorButton")
}

val textWithoutStyle: KAppTextNode = child {
hasTestTag("textWithoutStyle")
}

val textWithStyle: KAppTextNode = child {
hasTestTag("textWithStyle")
}

val textWithSemantic: KAppTextNode = child {
hasTestTag("textWithSemantic")
}

val myButton: KNode = child {
hasTestTag("myTestButton")
hasText("Button 1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,63 @@ class SimpleTest {
}
}
}

@Test
fun textColorTest() {
onComposeScreen<MainActivityScreen>(composeTestRule) {
changeTextColorButton {
assertIsDisplayed()
}

textWithoutStyle {
assertIsDisplayed()
assertTextColorEquals(Color.Black)
assertTextColorEquals("000000")
assertTextColorEquals("#000000")
assertTextColorEquals(0xFF000000)
}

textWithStyle {
assertIsDisplayed()
assertTextColorEquals(Color.Black)
assertTextColorEquals("000000")
assertTextColorEquals("#000000")
assertTextColorEquals(0xFF000000)
}

textWithSemantic {
assertIsDisplayed()
assertTextColorEquals(Color.Black)
assertTextColorEquals("000000")
assertTextColorEquals("#000000")
assertTextColorEquals(0xFF000000)
}

changeTextColorButton.performClick()

textWithoutStyle {
assertIsDisplayed()
assertTextColorEquals(Color.Black)
assertTextColorEquals("000000")
assertTextColorEquals("#000000")
assertTextColorEquals(0xFF000000)
}

textWithStyle {
assertIsDisplayed()
assertTextColorEquals(Color.Blue)
assertTextColorEquals("0000FF")
assertTextColorEquals("#0000FF")
assertTextColorEquals(0xFF0000FF)
}

textWithSemantic {
assertIsDisplayed()
assertTextColorEquals(Color.Blue)
assertTextColorEquals("0000FF")
assertTextColorEquals("#0000FF")
assertTextColorEquals(0xFF0000FF)
}
}
}
}
Loading

0 comments on commit bdd1913

Please sign in to comment.