diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8d406a78..2f1d7f50 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -4,7 +4,6 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetTree import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig plugins { alias(libs.plugins.kotlinMultiplatform) @@ -26,23 +25,26 @@ kotlin { dependencies { implementation(libs.androidx.ui.test.junit4.android) debugImplementation(libs.androidx.ui.test.manifest) + implementation(project(":ultron-compose")) } } } jvm("desktop") - -// listOf( -// iosX64(), -// iosArm64(), -// iosSimulatorArm64() -// ).forEach { iosTarget -> -// iosTarget.binaries.framework { -// baseName = "ComposeApp" -// isStatic = true -// } -// } - + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + js(IR) + @OptIn(ExperimentalWasmDsl::class) + wasmJs() + sourceSets { val desktopMain by getting @@ -70,13 +72,15 @@ kotlin { desktopMain.dependencies { implementation(compose.desktop.currentOs) } - // Adds the desktop test dependency val desktopTest by getting { dependencies { implementation(compose.desktop.uiTestJUnit4) implementation(compose.desktop.currentOs) } } + @OptIn(ExperimentalWasmDsl::class) + wasmJs() + val wasmJsTest by getting } } diff --git a/composeApp/src/commonTest/kotlin/AppTest.kt b/composeApp/src/commonTest/kotlin/AppTest.kt index 00897c5d..80c0989a 100644 --- a/composeApp/src/commonTest/kotlin/AppTest.kt +++ b/composeApp/src/commonTest/kotlin/AppTest.kt @@ -1,16 +1,15 @@ import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText -import repositories.ContactRepository 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.click import com.atiurin.ultron.extensions.withAssertion import com.atiurin.ultron.page.Screen +import repositories.ContactRepository import kotlin.test.Test @OptIn(ExperimentalTestApi::class) @@ -20,7 +19,7 @@ class AppTest { setContent { App() } - hasText("Click me!").withAssertion(){ + hasText("Click me!").withAssertion() { hasTestTag("greeting") .assertIsDisplayed() .assertTextContains("Compose: Hello,", option = TextContainsOption(substring = true)) @@ -32,10 +31,17 @@ class AppTest { setContent { App() } - val contact = ContactRepository.getFirst() composeList(hasTestTag("contactsListTestTag")) .assertIsDisplayed().assertNotEmpty() .firstVisibleItem().assertIsDisplayed() + } + + @Test + fun testListItemChildElements() = runUltronUiTest { + setContent { + App() + } + val contact = ContactRepository.getFirst() ListScreen { list.assertContentDescriptionEquals(contactsListContentDesc) list.getFirstVisibleItem().apply { @@ -49,9 +55,14 @@ class AppTest { object ListScreen : Screen() { const val contactsListTestTag = "contactsListTestTag" const val contactsListContentDesc = "contactsListContentDesc" - val list = composeList(hasTestTag(contactsListTestTag)) + val list = composeList( + listMatcher = hasTestTag(contactsListTestTag), + initBlock = { + registerItem { ListItem() } + } + ) - class ListItem : UltronComposeListItem(){ + class ListItem : UltronComposeListItem() { val name by child { hasTestTag("contactNameTestTag") } val status by child { hasTestTag("contactStatusTestTag") } } diff --git a/gradle.properties b/gradle.properties index 6a777cf7..e2448728 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,10 +7,11 @@ android.useAndroidX=true android.nonTransitiveRClass=true org.jetbrains.compose.experimental.wasm.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.macos.enabled=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.enableCInteropCommonization=true GROUP=com.atiurin POM_ARTIFACT_ID=ultron -VERSION_NAME=2.5.0-alpha05 +VERSION_NAME=2.5.0-alpha06 diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index f5bde619..8db87d07 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -17,7 +17,7 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* sample-kmp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = sample-kmp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* sample-kmp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "sample-kmp.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; @@ -127,7 +127,7 @@ }; }; buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; - compatibilityVersion = "Xcode 14.0"; + compatibilityVersion = "Xcode 15.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -326,7 +326,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.3; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -358,7 +358,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); INFOPLIST_FILE = iosApp/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.3; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -400,4 +400,4 @@ /* End XCConfigurationList section */ }; rootObject = 7555FF73242A565900829871 /* Project object */; -} \ No newline at end of file +} diff --git a/sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/AssertUtils.kt b/sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/AssertUtils.kt index 9633efd2..4eaa1d18 100644 --- a/sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/AssertUtils.kt +++ b/sample-app/src/androidTest/java/com/atiurin/sampleapp/framework/utils/AssertUtils.kt @@ -8,7 +8,7 @@ object AssertUtils { try { block() } catch (ex: Throwable) { - throw ex +// throw ex exceptionOccurs = true } Assert.assertEquals(expected, exceptionOccurs) diff --git a/ultron-common/build.gradle.kts b/ultron-common/build.gradle.kts index e7e3533b..eef7062a 100644 --- a/ultron-common/build.gradle.kts +++ b/ultron-common/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) @@ -10,16 +14,35 @@ group = project.findProperty("GROUP")!! version = project.findProperty("VERSION_NAME")!! kotlin { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + } + // targets jvm() androidTarget { publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "17" - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) } } - + applyDefaultHierarchyTemplate() + macosX64() + macosArm64() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs() + js(IR) sourceSets { commonMain.dependencies { implementation(libs.okio) @@ -42,6 +65,16 @@ kotlin { implementation(kotlin("stdlib-jdk8")) } } + val jsMain by getting { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + val wasmJsMain by getting { + dependencies { + implementation(kotlin("stdlib")) + } + } } } diff --git a/ultron-compose/build.gradle.kts b/ultron-compose/build.gradle.kts index f877604a..48281958 100644 --- a/ultron-compose/build.gradle.kts +++ b/ultron-compose/build.gradle.kts @@ -1,3 +1,7 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) @@ -13,14 +17,36 @@ version = project.findProperty("VERSION_NAME")!! kotlin { jvm() + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0) + } androidTarget { publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "17" - } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) } } + @OptIn(ExperimentalKotlinGradlePluginApi::class) + applyDefaultHierarchyTemplate { + } + macosX64() + macosArm64() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs() + js(IR) sourceSets { commonMain.dependencies { @@ -32,6 +58,7 @@ kotlin { implementation(libs.atomicfu) } val androidMain by getting { +// dependsOn(jvmMain.get()) dependencies { api(project(":ultron-common")) implementation(Libs.androidXRunner) @@ -44,6 +71,16 @@ kotlin { implementation(kotlin("stdlib-jdk8")) } } + val jsMain by getting { + dependencies { + implementation(kotlin("stdlib-js")) + } + } + val wasmJsMain by getting { + dependencies { + implementation(kotlin("stdlib")) + } + } } } diff --git a/ultron-compose/src/androidMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.android.kt b/ultron-compose/src/androidMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.android.kt new file mode 100644 index 00000000..712f4a71 --- /dev/null +++ b/ultron-compose/src/androidMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.android.kt @@ -0,0 +1,48 @@ +package com.atiurin.ultron.core.compose.list + +import androidx.compose.ui.test.SemanticsMatcher +import com.atiurin.ultron.exceptions.UltronException + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T { + val item = createUltronComposeListItemInstance() + item.setExecutor(ultronComposeList, itemMatcher) + return item +} + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean +): T { + val item = createUltronComposeListItemInstance() + item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) + return item +} + +inline fun createUltronComposeListItemInstance(): T { + return try { + T::class.java.newInstance() + } catch (ex: Exception) { + val desc = when { + T::class.isInner -> { + "${T::class.simpleName} is an inner class so you have to delete inner modifier (It is often when kotlin throws 'has no zero argument constructor' but real reason is an inner modifier)" + } + + T::class.constructors.find { it.parameters.isEmpty() } == null -> { + "${T::class.simpleName} doesn't have a constructor without params (create an empty constructor)" + } + + else -> ex.message + } + throw UltronException( + """ + |Couldn't create an instance of ${T::class.simpleName}. + |Possible reason: $desc + |Original exception: ${ex.message}, cause ${ex.cause} + """.trimMargin() + ) + } +} \ No newline at end of file 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 cb836c1d..0547b9cd 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 @@ -16,15 +16,24 @@ import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams import com.atiurin.ultron.exceptions.UltronAssertionException import com.atiurin.ultron.exceptions.UltronException import com.atiurin.ultron.utils.AssertUtils +import kotlin.reflect.KClass class UltronComposeList( val listMatcher: SemanticsMatcher, var useUnmergedTree: Boolean = true, var positionPropertyKey: SemanticsPropertyKey? = null, + val itemsRegistrator: UltronComposeList.() -> Unit = {}, private val itemSearchLimit: Int = UltronComposeConfig.params.lazyColumnItemSearchLimit, private var operationTimeoutMs: Long = UltronComposeConfig.params.lazyColumnOperationTimeoutMs ) { private val itemChildInteractionProvider = getItemChildInteractionProvider() + val instancesMap = mutableMapOf, () -> UltronComposeListItem>() + inline fun registerItem(noinline creator: () -> T){ + instancesMap[T::class] = creator + } + init { + itemsRegistrator() + } open fun withTimeout(timeoutMs: Long) = UltronComposeList( @@ -45,8 +54,11 @@ class UltronComposeList( if (positionPropertyKey == null) { throw UltronException( """ - |[positionPropertyKey] parameter is not specified for Compose List - |Configure it by using [composeList(.., positionPropertyKey = ListItemPositionPropertyKey)] + |[positionPropertyKey] parameter is not specified for UltronComposeList + |Configure it like + |``` + |composeList(.., positionPropertyKey = ListItemPositionPropertyKey) + |``` """.trimMargin() ) } @@ -101,19 +113,22 @@ class UltronComposeList( ) inline fun getItem(matcher: SemanticsMatcher): T { - return UltronComposeListItem.getInstance(this, matcher) + return getComposeListItemInstance(this, matcher) } inline fun getItem(position: Int): T { if (positionPropertyKey == null) { throw UltronException( """ - |[positionPropertyKey] parameter is not specified for Compose List - |Configure it by using [composeList(.., positionPropertyKey = ListItemPositionPropertyKey)] + |[positionPropertyKey] parameter is not specified for UltronComposeList + |Configure it like + |``` + |composeList(.., positionPropertyKey = ListItemPositionPropertyKey) + |``` """.trimMargin() ) } - return UltronComposeListItem.getInstance(this, position, true) + return getComposeListItemInstance(this, position, true) } inline fun getFirstItem(): T = getItem(0) @@ -123,7 +138,7 @@ class UltronComposeList( * After scroll the positions of children inside the list are changed. */ inline fun getVisibleItem(index: Int): T { - return UltronComposeListItem.getInstance(this, index) + return getComposeListItemInstance(this, index) } /** @@ -277,5 +292,6 @@ class UltronComposeList( fun composeList( listMatcher: SemanticsMatcher, useUnmergedTree: Boolean = true, - positionPropertyKey: SemanticsPropertyKey? = null -) = UltronComposeList(listMatcher, useUnmergedTree, positionPropertyKey) + positionPropertyKey: SemanticsPropertyKey? = null, + initBlock: UltronComposeList.() -> Unit = {} +) = UltronComposeList(listMatcher, useUnmergedTree, positionPropertyKey, initBlock) diff --git a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.kt b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.kt index 6e9f6f4c..f2b6a82d 100644 --- a/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.kt +++ b/ultron-compose/src/commonMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.kt @@ -49,7 +49,6 @@ import com.atiurin.ultron.core.compose.operation.ComposeOperationResult import com.atiurin.ultron.core.compose.operation.UltronComposeOperation import com.atiurin.ultron.core.compose.operation.UltronComposeOperationParams import com.atiurin.ultron.core.compose.option.ComposeSwipeOption -import com.atiurin.ultron.exceptions.UltronException open class UltronComposeListItem { lateinit var executor: ComposeItemExecutor @@ -191,51 +190,15 @@ open class UltronComposeListItem { fun assertTextEquals(expected: String) = apply { getItemUltronComposeInteraction().assertTextEquals(expected) } fun assertTextContains(expected: String) = apply { getItemUltronComposeInteraction().assertTextContains(expected) } - - - companion object { - inline fun getInstance( - ultronComposeList: UltronComposeList, - itemMatcher: SemanticsMatcher - ): T { - val item = createUltronComposeListItemInstance() - item.setExecutor(ultronComposeList, itemMatcher) - return item - } - - inline fun getInstance( - ultronComposeList: UltronComposeList, - position: Int, - isPositionPropertyConfigured: Boolean = false - ): T { - val item = createUltronComposeListItemInstance() - item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) - return item - } - - inline fun createUltronComposeListItemInstance(): T { - return try { - T::class.java.newInstance() - } catch (ex: Exception) { - val desc = when { - T::class.isInner -> { - "${T::class.simpleName} is an inner class so you have to delete inner modifier (It is often when kotlin throws 'has no zero argument constructor' but real reason is an inner modifier)" - } - - T::class.constructors.find { it.parameters.isEmpty() } == null -> { - "${T::class.simpleName} doesn't have a constructor without params (create an empty constructor)" - } - - else -> ex.message - } - throw UltronException( - """ - |Couldn't create an instance of ${T::class.simpleName}. - |Possible reason: $desc - |Original exception: ${ex.message}, cause ${ex.cause} - """.trimMargin() - ) - } - } - } -} \ No newline at end of file +} + +expect inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T + +expect inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean = false +): T \ No newline at end of file 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 new file mode 100644 index 00000000..4886666f --- /dev/null +++ b/ultron-compose/src/jsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.js.kt @@ -0,0 +1,45 @@ +package com.atiurin.ultron.core.compose.list + +import androidx.compose.ui.test.SemanticsMatcher +import com.atiurin.ultron.exceptions.UltronException + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, itemMatcher) + return item as T +} + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) + return item as T +} \ No newline at end of file diff --git a/ultron-compose/src/jvmMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.jvm.kt b/ultron-compose/src/jvmMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.jvm.kt new file mode 100644 index 00000000..800f4489 --- /dev/null +++ b/ultron-compose/src/jvmMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.jvm.kt @@ -0,0 +1,49 @@ +package com.atiurin.ultron.core.compose.list + + +import androidx.compose.ui.test.SemanticsMatcher +import com.atiurin.ultron.exceptions.UltronException + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T { + val item = createUltronComposeListItemInstance() + item.setExecutor(ultronComposeList, itemMatcher) + return item +} + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean +): T { + val item = createUltronComposeListItemInstance() + item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) + return item +} + +inline fun createUltronComposeListItemInstance(): T { + return try { + T::class.java.newInstance() + } catch (ex: Exception) { + val desc = when { + T::class.isInner -> { + "${T::class.simpleName} is an inner class so you have to delete inner modifier (It is often when kotlin throws 'has no zero argument constructor' but real reason is an inner modifier)" + } + + T::class.constructors.find { it.parameters.isEmpty() } == null -> { + "${T::class.simpleName} doesn't have a constructor without params (create an empty constructor)" + } + + else -> ex.message + } + throw UltronException( + """ + |Couldn't create an instance of ${T::class.simpleName}. + |Possible reason: $desc + |Original exception: ${ex.message}, cause ${ex.cause} + """.trimMargin() + ) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..4886666f --- /dev/null +++ b/ultron-compose/src/nativeMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.native.kt @@ -0,0 +1,45 @@ +package com.atiurin.ultron.core.compose.list + +import androidx.compose.ui.test.SemanticsMatcher +import com.atiurin.ultron.exceptions.UltronException + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, itemMatcher) + return item as T +} + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) + return item as T +} \ No newline at end of file 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 new file mode 100644 index 00000000..4886666f --- /dev/null +++ b/ultron-compose/src/wasmJsMain/kotlin/com/atiurin/ultron/core/compose/list/UltronComposeListItem.wasmJs.kt @@ -0,0 +1,45 @@ +package com.atiurin.ultron.core.compose.list + +import androidx.compose.ui.test.SemanticsMatcher +import com.atiurin.ultron.exceptions.UltronException + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + itemMatcher: SemanticsMatcher +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, itemMatcher) + return item as T +} + +actual inline fun getComposeListItemInstance( + ultronComposeList: UltronComposeList, + position: Int, + isPositionPropertyConfigured: Boolean +): T { + val item = ultronComposeList.instancesMap[T::class]?.invoke() + ?: throw UltronException( + """ + |Item with ${T::class} not registered in compose list ${ultronComposeList.listMatcher.description} + |Configure it by using [initBlock] parameter + |``` + |composeList(.., initBlock = { + | registerItem { ListItem() } + |}) + |``` + """.trimMargin() + ) + item.setExecutor(ultronComposeList, position, isPositionPropertyConfigured) + return item as T +} \ No newline at end of file