From ccb2a67ec480bdbabeecbd70cffd8de13149b50e Mon Sep 17 00:00:00 2001 From: Aleksei Tiurin Date: Thu, 4 Jul 2024 18:07:38 +0300 Subject: [PATCH] Feature/66 unmerged tree cfg (#75) * Support useUnmergedTree config param * 2.5.0-alpha07 --- .github/workflows/android-pipeline.yml | 2 +- .../commonTest/kotlin/BaseInteractionTest.kt | 61 +++++++++++++++++++ .../kotlin/{AppTest.kt => ListTest.kt} | 18 +----- composeApp/src/jsMain/kotlin/Platform.js.kt | 3 + gradle.properties | 2 +- .../tests/compose/ComposeListTest.kt | 1 - .../core/compose/list/UltronComposeList.kt | 26 ++++---- .../UltronComposeSemanticsNodeInteraction.kt | 4 +- .../compose/list/UltronComposeListItem.js.kt | 4 +- .../list/UltronComposeListItem.native.kt | 4 +- .../list/UltronComposeListItem.wasmJs.kt | 4 +- 11 files changed, 88 insertions(+), 41 deletions(-) create mode 100644 composeApp/src/commonTest/kotlin/BaseInteractionTest.kt rename composeApp/src/commonTest/kotlin/{AppTest.kt => ListTest.kt} (73%) create mode 100644 composeApp/src/jsMain/kotlin/Platform.js.kt diff --git a/.github/workflows/android-pipeline.yml b/.github/workflows/android-pipeline.yml index 7302d7fa..1278f607 100644 --- a/.github/workflows/android-pipeline.yml +++ b/.github/workflows/android-pipeline.yml @@ -17,4 +17,4 @@ jobs: java-version: '17' - name: Compile framework - run: ./gradlew compileDebugKotlin + run: ./gradlew compileDebugKotlin compileDebugKotlinAndroid compileKotlinDesktop compileKotlinJvm compileKotlinIosArm64 compileKotlinIosSimulatorArm64 compileKotlinJs compileKotlinWasmJs diff --git a/composeApp/src/commonTest/kotlin/BaseInteractionTest.kt b/composeApp/src/commonTest/kotlin/BaseInteractionTest.kt new file mode 100644 index 00000000..9d58c4d8 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/BaseInteractionTest.kt @@ -0,0 +1,61 @@ +import androidx.compose.foundation.layout.Column +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasTestTag +import androidx.compose.ui.test.hasText +import com.atiurin.ultron.core.common.options.TextContainsOption +import com.atiurin.ultron.core.compose.config.UltronComposeConfig +import com.atiurin.ultron.core.compose.nodeinteraction.click +import com.atiurin.ultron.core.compose.runUltronUiTest +import com.atiurin.ultron.extensions.assertIsDisplayed +import com.atiurin.ultron.extensions.assertTextContains +import com.atiurin.ultron.extensions.isSuccess +import com.atiurin.ultron.extensions.withAssertion +import com.atiurin.ultron.extensions.withUseUnmergedTree +import kotlin.test.AfterTest +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@OptIn(ExperimentalTestApi::class) +class BaseInteractionTest { + @Test + fun test() = runUltronUiTest { + setContent { + App() + } + hasText("Click me!").withAssertion() { + hasTestTag("greeting") + .assertIsDisplayed() + .assertTextContains("Compose: Hello,", option = TextContainsOption(substring = true)) + }.click() + } + + @Test + fun useUnmergedTreeConfigTest() = runUltronUiTest { + val testTag = "element" + setContent { + Column { + Button(onClick = {}, modifier = Modifier.testTag(testTag)) { + Text("Text1") + Text("Text2") + } + } + } + UltronComposeConfig.params.useUnmergedTree = true + assertFalse("Ultron operation success should be false") { + hasTestTag(testTag).isSuccess { assertTextContains("Text1") } + } + assertTrue ("Ultron operation success should be true") { + hasTestTag(testTag).withUseUnmergedTree(false).isSuccess { assertTextContains("Text1") } + } + } + + @AfterTest + fun disableUseUnmergedTree(){ + UltronComposeConfig.params.useUnmergedTree = false + } +} \ No newline at end of file diff --git a/composeApp/src/commonTest/kotlin/AppTest.kt b/composeApp/src/commonTest/kotlin/ListTest.kt similarity index 73% rename from composeApp/src/commonTest/kotlin/AppTest.kt rename to composeApp/src/commonTest/kotlin/ListTest.kt index 80c0989a..da2157ec 100644 --- a/composeApp/src/commonTest/kotlin/AppTest.kt +++ b/composeApp/src/commonTest/kotlin/ListTest.kt @@ -1,30 +1,14 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasTestTag -import androidx.compose.ui.test.hasText -import com.atiurin.ultron.core.common.options.TextContainsOption import com.atiurin.ultron.core.compose.list.UltronComposeListItem import com.atiurin.ultron.core.compose.list.composeList -import com.atiurin.ultron.core.compose.nodeinteraction.click import com.atiurin.ultron.core.compose.runUltronUiTest -import com.atiurin.ultron.extensions.assertIsDisplayed -import com.atiurin.ultron.extensions.withAssertion import com.atiurin.ultron.page.Screen import repositories.ContactRepository import kotlin.test.Test @OptIn(ExperimentalTestApi::class) -class AppTest { - @Test - fun test() = runUltronUiTest { - setContent { - App() - } - hasText("Click me!").withAssertion() { - hasTestTag("greeting") - .assertIsDisplayed() - .assertTextContains("Compose: Hello,", option = TextContainsOption(substring = true)) - }.click() - } +class ListTest { @Test fun testList() = runUltronUiTest { diff --git a/composeApp/src/jsMain/kotlin/Platform.js.kt b/composeApp/src/jsMain/kotlin/Platform.js.kt new file mode 100644 index 00000000..1d993300 --- /dev/null +++ b/composeApp/src/jsMain/kotlin/Platform.js.kt @@ -0,0 +1,3 @@ +actual fun getPlatform(): Platform { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index e2448728..032eed79 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,4 +14,4 @@ kotlin.mpp.enableCInteropCommonization=true GROUP=com.atiurin POM_ARTIFACT_ID=ultron -VERSION_NAME=2.5.0-alpha06 +VERSION_NAME=2.5.0-alpha07 diff --git a/sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListTest.kt b/sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListTest.kt index 11a030e6..264a905d 100644 --- a/sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListTest.kt +++ b/sample-app/src/androidTest/java/com/atiurin/sampleapp/tests/compose/ComposeListTest.kt @@ -59,7 +59,6 @@ class ComposeListTest : BaseTest() { val contact = CONTACTS[index] listWithMergedTree.visibleItem(index).printToLog("ULTRON") .assertMatches(hasAnyDescendant(hasText(contact.name))) - } @Test diff --git a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeList.kt b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeList.kt index 0547b9cd..41534693 100644 --- a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeList.kt +++ b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeList.kt @@ -22,17 +22,17 @@ class UltronComposeList( val listMatcher: SemanticsMatcher, var useUnmergedTree: Boolean = true, var positionPropertyKey: SemanticsPropertyKey? = null, - val itemsRegistrator: UltronComposeList.() -> Unit = {}, + val initBlock: UltronComposeList.() -> Unit = {}, private val itemSearchLimit: Int = UltronComposeConfig.params.lazyColumnItemSearchLimit, private var operationTimeoutMs: Long = UltronComposeConfig.params.lazyColumnOperationTimeoutMs ) { private val itemChildInteractionProvider = getItemChildInteractionProvider() - val instancesMap = mutableMapOf, () -> UltronComposeListItem>() + val itemInstancesMap = mutableMapOf, () -> UltronComposeListItem>() inline fun registerItem(noinline creator: () -> T){ - instancesMap[T::class] = creator + itemInstancesMap[T::class] = creator } init { - itemsRegistrator() + initBlock() } open fun withTimeout(timeoutMs: Long) = @@ -224,15 +224,15 @@ class UltronComposeList( fun scrollToNode(itemMatcher: SemanticsMatcher) = apply { getInteraction().scrollToNode(itemMatcher) } fun scrollToIndex(index: Int) = apply { getInteraction().scrollToIndex(index) } fun scrollToKey(key: Any) = apply { getInteraction().scrollToKey(key) } - fun assertIsDisplayed() = apply { getInteraction().withTimeout(getOperationTimeout()).assertIsDisplayed() } - fun assertIsNotDisplayed() = apply { getInteraction().withTimeout(getOperationTimeout()).assertIsNotDisplayed() } - fun assertExists() = apply { getInteraction().withTimeout(getOperationTimeout()).assertExists() } - fun assertDoesNotExist() = apply { getInteraction().withTimeout(getOperationTimeout()).assertDoesNotExist() } - fun assertContentDescriptionEquals(vararg expected: String) = apply { getInteraction().withTimeout(getOperationTimeout()).assertContentDescriptionEquals(*expected) } + fun assertIsDisplayed() = apply { getInteraction().assertIsDisplayed() } + fun assertIsNotDisplayed() = apply { getInteraction().assertIsNotDisplayed() } + fun assertExists() = apply { getInteraction().assertExists() } + fun assertDoesNotExist() = apply { getInteraction().assertDoesNotExist() } + fun assertContentDescriptionEquals(vararg expected: String) = apply { getInteraction().assertContentDescriptionEquals(*expected) } fun assertContentDescriptionContains(expected: String, option: ContentDescriptionContainsOption? = null) = - apply { getInteraction().withTimeout(getOperationTimeout()).assertContentDescriptionContains(expected, option) } + apply { getInteraction().assertContentDescriptionContains(expected, option) } - fun assertMatches(matcher: SemanticsMatcher) = apply { getInteraction().withTimeout(getOperationTimeout()).assertMatches(matcher) } + fun assertMatches(matcher: SemanticsMatcher) = apply { getInteraction().assertMatches(matcher) } fun assertNotEmpty() = apply { AssertUtils.assertTrue( { getVisibleItemsCount() > 0 }, getOperationTimeout(), @@ -261,7 +261,7 @@ class UltronComposeList( * Otherwise, an exception will be thrown. */ fun assertItemDoesNotExist(itemMatcher: SemanticsMatcher) { - getInteraction().withTimeout(getOperationTimeout()).perform( + getInteraction().perform( params = UltronComposeOperationParams( operationName = "Assert item ${itemMatcher.description} doesn't exist in list ${getInteraction().elementInfo.name}", operationDescription = "Assert item ${itemMatcher.description} doesn't exist in list ${getInteraction().elementInfo.name} during ${getOperationTimeout()}", @@ -281,7 +281,7 @@ class UltronComposeList( } fun getVisibleItemsCount(): Int = getInteraction().execute { it.fetchSemanticsNode().children.size } - fun getInteraction() = UltronComposeSemanticsNodeInteraction(listMatcher, useUnmergedTree) + fun getInteraction() = UltronComposeSemanticsNodeInteraction(listMatcher, useUnmergedTree, operationTimeoutMs) @Deprecated("Use getInteraction() instead", ReplaceWith("getInteraction()")) fun getMatcher() = getInteraction() diff --git a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/nodeinteraction/UltronComposeSemanticsNodeInteraction.kt b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/nodeinteraction/UltronComposeSemanticsNodeInteraction.kt index 93e48da2..bdb3e07c 100644 --- a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/nodeinteraction/UltronComposeSemanticsNodeInteraction.kt +++ b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/nodeinteraction/UltronComposeSemanticsNodeInteraction.kt @@ -40,7 +40,7 @@ open class UltronComposeSemanticsNodeInteraction constructor( ) { constructor( matcher: SemanticsMatcher, - useUnmergedTree: Boolean = false, + useUnmergedTree: Boolean = UltronComposeConfig.params.useUnmergedTree, timeoutMs: Long = UltronComposeConfig.params.operationTimeoutMs, resultHandler: ((ComposeOperationResult) -> Unit) = UltronComposeConfig.resultHandler, assertion: OperationAssertion = EmptyOperationAssertion(), @@ -48,7 +48,7 @@ open class UltronComposeSemanticsNodeInteraction constructor( ) : this(SemanticsNodeInteractionProviderContainer.getProvider().onNode(matcher, useUnmergedTree), timeoutMs, resultHandler, assertion, elementInfo) init { - if (elementInfo.name.isEmpty()) elementInfo.name = semanticsNodeInteraction.getSelectorDescription().toString() + if (elementInfo.name.isEmpty()) elementInfo.name = semanticsNodeInteraction.getSelectorDescription() } fun isSuccess(action: UltronComposeSemanticsNodeInteraction.() -> T): Boolean = runCatching { action() }.isSuccess diff --git a/ultron-compose/src/jsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.js.kt b/ultron-compose/src/jsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.js.kt index 4886666f..68d91beb 100644 --- a/ultron-compose/src/jsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.js.kt +++ b/ultron-compose/src/jsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.js.kt @@ -7,7 +7,7 @@ actual inline fun getComposeListItemInstance ultronComposeList: UltronComposeList, itemMatcher: SemanticsMatcher ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} @@ -28,7 +28,7 @@ actual inline fun getComposeListItemInstance position: Int, isPositionPropertyConfigured: Boolean ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} diff --git a/ultron-compose/src/nativeMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.native.kt b/ultron-compose/src/nativeMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.native.kt index 4886666f..68d91beb 100644 --- a/ultron-compose/src/nativeMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.native.kt +++ b/ultron-compose/src/nativeMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.native.kt @@ -7,7 +7,7 @@ actual inline fun getComposeListItemInstance ultronComposeList: UltronComposeList, itemMatcher: SemanticsMatcher ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} @@ -28,7 +28,7 @@ actual inline fun getComposeListItemInstance position: Int, isPositionPropertyConfigured: Boolean ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} diff --git a/ultron-compose/src/wasmJsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.wasmJs.kt b/ultron-compose/src/wasmJsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.wasmJs.kt index 4886666f..68d91beb 100644 --- a/ultron-compose/src/wasmJsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.wasmJs.kt +++ b/ultron-compose/src/wasmJsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.wasmJs.kt @@ -7,7 +7,7 @@ actual inline fun getComposeListItemInstance ultronComposeList: UltronComposeList, itemMatcher: SemanticsMatcher ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} @@ -28,7 +28,7 @@ actual inline fun getComposeListItemInstance position: Int, isPositionPropertyConfigured: Boolean ): T { - val item = ultronComposeList.instancesMap[T::class]?.invoke() + val item = ultronComposeList.itemInstancesMap[T::class]?.invoke() ?: throw UltronException( """ |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description}