tangem-app-android-audited/app/src/androidTest/kotlin/com/tangem/common/extensions/CustomAssertsExt.kt
2026-05-29 16:28:48 +03:00

127 lines
No EOL
4.4 KiB
Kotlin

package com.tangem.common.extensions
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.onAllNodesWithText
import com.tangem.common.BaseTestCase
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
import com.tangem.common.utils.LazyListItemNode
import com.tangem.core.ui.components.buttons.actions.HasBadgeKey
import com.tangem.core.ui.components.buttons.actions.IsDimmedKey
import io.github.kakaocup.compose.node.element.KNode
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
fun BaseTestCase.assertSnackbarWithText(text: String, timeoutMs: Long = WAIT_UNTIL_TIMEOUT) {
composeTestRule.waitUntil(timeoutMillis = timeoutMs) {
composeTestRule
.onAllNodesWithText(text, substring = true)
.fetchSemanticsNodes()
.isNotEmpty()
}
}
fun assertElementDoesNotExist(
elementProvider: () -> KNode,
elementDescription: String,
) {
try {
elementProvider().assertExists()
throw AssertionError("$elementDescription should not exist but was found")
} catch (e: AssertionError) {
val isNotFoundError = e.message?.let { message ->
message.contains("No node found") ||
message.contains("scrollable container") ||
message.contains("There are no existing nodes") ||
message.contains("There are no existing nodes for that selector")
} ?: false
if (isNotFoundError) {
return
} else {
throw e
}
}
}
fun Any.assertIsDimmed(expectedValue: Boolean = true) {
val matcher = SemanticsMatcher.expectValue(IsDimmedKey, expectedValue)
when (this) {
is KNode, is LazyListItemNode -> this.assert(matcher)
else -> throw IllegalArgumentException("Unsupported type: ${this::class}")
}
}
fun Any.assertHasBadge(expectedValue: Boolean = true) {
val matcher = SemanticsMatcher.expectValue(HasBadgeKey, expectedValue)
when (this) {
is KNode, is LazyListItemNode -> this.assert(matcher)
else -> throw IllegalArgumentException("Unsupported type: ${this::class}")
}
}
fun List<SemanticsNode>.assertSortedByVolumeDescending() {
val volumes = this.mapNotNull { parseVolume(it) }
assertFalse("Trading volumes list should not be empty", volumes.isEmpty())
assertTrue(
"Exchanges list should be sorted by volume in descending order",
volumes == volumes.sortedDescending(),
)
}
fun List<SemanticsNode>.assertExchangeTypesAreCexOrDex() {
assertFalse("Exchange types list should not be empty", isEmpty())
forEach { node ->
val text = extractText(node)
assertTrue(
"Exchange type should be 'CEX' or 'DEX', but found: $text",
text == "CEX" || text == "DEX",
)
}
}
fun List<SemanticsNode>.assertTrustScoresValid() {
val validScores = setOf("Risky", "Caution", "Trusted")
assertFalse("Trust scores list should not be empty", isEmpty())
forEach { node ->
val text = extractText(node)
assertTrue(
"Trust score should be one of $validScores, but found: $text",
text in validScores,
)
}
}
/**
* Extracts the text content from a [KNode]'s semantics.
* Useful when the displayed value is dynamic and you need to capture it for later assertions.
*/
fun KNode.extractText(): String {
val node = delegate.interaction.semanticsNodeInteraction.fetchSemanticsNode(
"Failed to extract text from KNode",
)
return extractText(node) ?: error("Node does not contain SemanticsProperties.Text")
}
/**
* Extracts the text value from a semantic node's config.
*/
private fun extractText(node: SemanticsNode): String? {
if (SemanticsProperties.Text in node.config) {
return node.config[SemanticsProperties.Text].firstOrNull()?.text
}
return null
}
private fun parseVolume(node: SemanticsNode): Double? {
val text = extractText(node) ?: return null
val multiplier = when {
text.contains('T', ignoreCase = true) -> 1_000_000_000_000.0
text.contains('B', ignoreCase = true) -> 1_000_000_000.0
text.contains('M', ignoreCase = true) -> 1_000_000.0
text.contains('K', ignoreCase = true) -> 1_000.0
else -> 1.0
}
val number = text.replace("[^0-9.]".toRegex(), "").toDoubleOrNull() ?: return null
return number * multiplier
}