Browse Source

Add composable-test-cases project to check JB compose compiler for all targets (#2751)

* Add composable-test-cases project to check JB compose compiler for all targets

* Add README.MD

* Update README.MD

* Update README.MD

* Update CollectionOfComposablesTests

* Add a fun interface test case

* Add class constructors test cases

* Add anonymous objects test cases

* Add lambdas test cases

* Disable mingwX64 target for CI runs

* Update README.MD

* uncomment the getter in `val Project.isInIdea`

* Simplify GlobalSnapshotManager

Apparently some tests get stuck (timeout 60s) in jvm when running on CI. Every time it's a different test.

* add a partially failing test cases

`rememberAnonymousObj` fails with k/js, but works with other targets.

* try workaround tests timeout on CI

close the Channel in the end of a test

* Introduce fun RecompositionObserver.waitUntilChangesApplied

* Try runTest with UnconfinedTestDispatcher()

* remove Dispatchers.Default usage for recomposer

* revert "remove Dispatchers.Default usage for recomposer"

* pass test coroutine context to Recomposer

* add value class test cases

* add "same-module all-private" value class test case

* add a test case for value class with non-primitive property type

* value class: add a composable with a default value test case

* Update Readme
pull/2837/head
Oleksandr Karpovich 2 years ago committed by GitHub
parent
commit
294479d737
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 43
      compose/integrations/composable-test-cases/.gitignore
  2. 53
      compose/integrations/composable-test-cases/README.MD
  3. 46
      compose/integrations/composable-test-cases/build.gradle.kts
  4. 18
      compose/integrations/composable-test-cases/buildSrc/build.gradle.kts
  5. 1
      compose/integrations/composable-test-cases/buildSrc/gradle.properties
  6. 15
      compose/integrations/composable-test-cases/buildSrc/settings.gradle.kts
  7. 57
      compose/integrations/composable-test-cases/buildSrc/src/main/kotlin/TargetsConfiguration.kt
  8. 24
      compose/integrations/composable-test-cases/common/build.gradle.kts
  9. 132
      compose/integrations/composable-test-cases/common/src/commonMain/kotlin/com/example/common/Applier.kt
  10. 67
      compose/integrations/composable-test-cases/common/src/commonTest/kotlin/com.example.common/Test.kt
  11. 16
      compose/integrations/composable-test-cases/gradle.properties
  12. BIN
      compose/integrations/composable-test-cases/gradle/wrapper/gradle-wrapper.jar
  13. 5
      compose/integrations/composable-test-cases/gradle/wrapper/gradle-wrapper.properties
  14. 234
      compose/integrations/composable-test-cases/gradlew
  15. 89
      compose/integrations/composable-test-cases/gradlew.bat
  16. 130
      compose/integrations/composable-test-cases/settings.gradle.kts
  17. 20
      compose/integrations/composable-test-cases/testcases/anonymousObjects/lib/build.gradle.kts
  18. 6
      compose/integrations/composable-test-cases/testcases/anonymousObjects/lib/src/commonMain/kotlin/Dependencies.kt
  19. 21
      compose/integrations/composable-test-cases/testcases/anonymousObjects/main/build.gradle.kts
  20. 22
      compose/integrations/composable-test-cases/testcases/anonymousObjects/main/src/commonMain/kotlin/Implementations.kt
  21. 73
      compose/integrations/composable-test-cases/testcases/anonymousObjects/main/src/commonTest/kotlin/Tests.kt
  22. 20
      compose/integrations/composable-test-cases/testcases/constructors/lib/build.gradle.kts
  23. 90
      compose/integrations/composable-test-cases/testcases/constructors/lib/src/commonMain/kotlin/Dependencies.kt
  24. 21
      compose/integrations/composable-test-cases/testcases/constructors/main/build.gradle.kts
  25. 9
      compose/integrations/composable-test-cases/testcases/constructors/main/src/commonMain/kotlin/Implementations.kt
  26. 330
      compose/integrations/composable-test-cases/testcases/constructors/main/src/commonTest/kotlin/Tests.kt
  27. 20
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/build.gradle.kts
  28. 16
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt
  29. 21
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/build.gradle.kts
  30. 43
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonMain/kotlin/ComposableContentImpl.kt
  31. 40
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt
  32. 99
      compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CompsableInterfaceTests.kt
  33. 20
      compose/integrations/composable-test-cases/testcases/inheritance/funInterface/lib/build.gradle.kts
  34. 11
      compose/integrations/composable-test-cases/testcases/inheritance/funInterface/lib/src/commonMain/kotlin/Dependencies.kt
  35. 21
      compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/build.gradle.kts
  36. 18
      compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/src/commonMain/kotlin/Implementations.kt
  37. 36
      compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/src/commonTest/kotlin/FunInterfaceTests.kt
  38. 20
      compose/integrations/composable-test-cases/testcases/lambdas/lib/build.gradle.kts
  39. 14
      compose/integrations/composable-test-cases/testcases/lambdas/lib/src/commonMain/kotlin/Dependencies.kt
  40. 21
      compose/integrations/composable-test-cases/testcases/lambdas/main/build.gradle.kts
  41. 120
      compose/integrations/composable-test-cases/testcases/lambdas/main/src/commonTest/kotlin/Tests.kt
  42. 38
      compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/lib/build.gradle.kts
  43. 87
      compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/lib/src/commonMain/kotlin/Dependencies.kt
  44. 39
      compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/main/build.gradle.kts
  45. 45
      compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/main/src/commonTest/kotlin/Tests.kt
  46. 20
      compose/integrations/composable-test-cases/testcases/template/lib/build.gradle.kts
  47. 7
      compose/integrations/composable-test-cases/testcases/template/lib/src/commonMain/kotlin/Dependencies.kt
  48. 21
      compose/integrations/composable-test-cases/testcases/template/main/build.gradle.kts
  49. 34
      compose/integrations/composable-test-cases/testcases/template/main/src/commonTest/kotlin/Tests.kt
  50. 20
      compose/integrations/composable-test-cases/testcases/valueClass/lib/build.gradle.kts
  51. 64
      compose/integrations/composable-test-cases/testcases/valueClass/lib/src/commonMain/kotlin/Dependencies.kt
  52. 21
      compose/integrations/composable-test-cases/testcases/valueClass/main/build.gradle.kts
  53. 75
      compose/integrations/composable-test-cases/testcases/valueClass/main/src/commonMain/kotlin/ComposableTakingValue.kt
  54. 193
      compose/integrations/composable-test-cases/testcases/valueClass/main/src/commonTest/kotlin/Tests.kt

43
compose/integrations/composable-test-cases/.gitignore vendored

@ -0,0 +1,43 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/.idea/

53
compose/integrations/composable-test-cases/README.MD

@ -0,0 +1,53 @@
## Run:
`./gradlew build -Pcompose.kotlinCompilerPluginVersion=1.4.2-rc03 -Pkotlin.version=1.8.10`
to build and run the tests,
or
`./gradlew allTests -Pcompose.kotlinCompilerPluginVersion=1.4.2-rc03 -Pkotlin.version=1.8.10`
to only run the tests.
It will build and run the tests for all available targets (depends on a host machine).
See [TargetsConfiguration](./buildSrc/src/main/kotlin/TargetsConfiguration.kt) to update the targets.
## Test cases
#### Directory: `./testcases`
Every test case contains a simple project with 2 or more modules with applied Compose plugin.
The goal of such a project is to check a specific case of Composable usage.
A test case ensures that the code compiles and behaves as expected when running.
A test case contains 2 or more modules to check crossmodule compilation (the test dependencies should be placed in a `...-lib` module).
See a test case [template](./testcases/template) for an example.
Splitting the test cases into separate small projects helps in distinguishing the potential compilation bugs.
The behaviour is checked by the tests in a `...-main` module of a test case.
The tests create a simple text Composition and then compares the text dump with an expected value.
Example:
```kotlin
@Test
fun testExample2() = runTest {
val root = composeText {
TextLeafNode("Leaf")
TextContainerNode("node") {
TextLeafNode("child1")
TextLeafNode("child2")
TextLeafNode("child3")
}
}
assertEquals("root:{Leaf, node:{child1, child2, child3}}", root.dump())
}
```
The API for the text Composition is implemented in the [common](./common) module.
More test examples (including recomposition cases) can be found [here](./common/src/commonTest).
### To add a new test case:
- Use `/testcases/template` as a starting point (copy it and rename). Place a new testcase in `/testcases` folder.
- Add new modules in `settings.gradle.kts`
- Please follow the naming convention for modules. Their names should end with `-lib` or `-main`.
We need 2 modules (`...-lib` and `...-main`) to test crossmodule compilation.
Place the test dependencies in a `...-lib` module.

46
compose/integrations/composable-test-cases/build.gradle.kts

@ -0,0 +1,46 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
group "com.example"
version "1.0-SNAPSHOT"
allprojects {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
// mavenLocal()
}
afterEvaluate {
val project = this
val compilerPluginVersion = project.properties["compose.kotlinCompilerPluginVersion"] as? String
val kotlinVersion = project.properties["kotlin.version"] as? String
project.extensions.findByType<org.jetbrains.compose.ComposeExtension>()?.also {
if (!compilerPluginVersion.isNullOrEmpty()) {
println("${project.name} is using compilerPluginVersion = $compilerPluginVersion")
it.kotlinCompilerPlugin.set(compilerPluginVersion)
it.kotlinCompilerPluginArgs.add("suppressKotlinVersionCompatibilityCheck=$kotlinVersion")
}
}
tasks.withType<KotlinJsCompile>().configureEach {
kotlinOptions.freeCompilerArgs += listOf(
"-Xklib-enable-signature-clash-checks=false",
)
}
}
disableYarnLockMismatchReport()
}
plugins {
kotlin("multiplatform") apply false
id("org.jetbrains.compose") apply false
}
fun Project.disableYarnLockMismatchReport() {
plugins.withType<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin> {
the<org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension>().apply {
yarnLockMismatchReport = org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.NONE
}
}
}

18
compose/integrations/composable-test-cases/buildSrc/build.gradle.kts

@ -0,0 +1,18 @@
repositories {
mavenCentral()
gradlePluginPortal()
}
plugins {
kotlin("jvm")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin")
}

1
compose/integrations/composable-test-cases/buildSrc/gradle.properties

@ -0,0 +1 @@
../gradle.properties

15
compose/integrations/composable-test-cases/buildSrc/settings.gradle.kts

@ -0,0 +1,15 @@
pluginManagement {
repositories {
mavenCentral()
google()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id.startsWith("org.jetbrains.kotlin")) {
useVersion(gradle.rootProject.extra["kotlin.version"] as String)
}
}
}
}

57
compose/integrations/composable-test-cases/buildSrc/src/main/kotlin/TargetsConfiguration.kt

@ -0,0 +1,57 @@
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.project
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetContainer
val Project.isInIdea: Boolean
get() {
return System.getProperty("idea.active")?.toBoolean() == true
}
val Project.isFailingJsCase: Boolean
get() = this.name.contains("-failingJs-")
@OptIn(ExternalVariantApi::class)
fun KotlinMultiplatformExtension.configureTargets() {
jvm("desktop")
configureJsTargets()
ios()
iosArm64()
iosSimulatorArm64()
iosX64()
macosX64()
macosArm64()
// We use linux agents on CI. So it doesn't run the tests, but it builds the klib anyway which is time consuming.
if (project.isInIdea) mingwX64()
linuxX64()
}
fun KotlinMultiplatformExtension.configureJsTargets() {
js(IR) {
browser()
// nodejs() // Commented to save a bit of CI time. Testing in a browser should be enough.
}
}
@OptIn(ExternalVariantApi::class)
fun KotlinDependencyHandler.getLibDependencyForMain(): ProjectDependency {
if (!project.name.endsWith("-main")) error("Unexpected main module name: ${project.name}")
return project(":" + project.name.replace("-main", "-lib"))
}
@OptIn(ExternalVariantApi::class)
fun KotlinDependencyHandler.getCommonLib(): ProjectDependency {
return project(":common")
}
fun KotlinSourceSet.configureCommonTestDependencies() {
dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
}

24
compose/integrations/composable-test-cases/common/build.gradle.kts

@ -0,0 +1,24 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
group = "com.example"
version = "1.0-SNAPSHOT"
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

132
compose/integrations/composable-test-cases/common/src/commonMain/kotlin/com/example/common/Applier.kt

@ -0,0 +1,132 @@
/*
* Copyright 2020-2023 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package com.example.common
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.ObserverHandle
import androidx.compose.runtime.snapshots.Snapshot
import kotlinx.coroutines.*
import kotlinx.datetime.Clock
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
open class StringsNodeWrapper(
var text: String? = null,
val list: MutableList<StringsNodeWrapper> = mutableListOf()
) {
override fun toString(): String {
return list.joinToString(prefix = "${text ?: "_"}:{", postfix = "}")
}
fun dump() = toString()
}
private class PlainTextNode(text: String? = null) : StringsNodeWrapper(text) {
override fun toString(): String {
return text ?: ""
}
}
private class StringsListApplier(root: StringsNodeWrapper) : AbstractApplier<StringsNodeWrapper>(root) {
override fun insertBottomUp(index: Int, instance: StringsNodeWrapper) {
// ignored. Building tree bottom-up
}
override fun insertTopDown(index: Int, instance: StringsNodeWrapper) {
current.list.add(index, instance)
}
override fun move(from: Int, to: Int, count: Int) {
current.list.move(from, to, count)
}
override fun remove(index: Int, count: Int) {
current.list.remove(index, count)
}
override fun onClear() {
current.list.clear()
}
}
private object GlobalSnapshotManager {
private var removeWriteObserver: (ObserverHandle)? = null
fun ensureStarted() {
if (removeWriteObserver != null) {
removeWriteObserver!!.dispose()
}
removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver)
}
private val globalWriteObserver: (Any) -> Unit = {
// Race, but we don't care too much if we end up with multiple calls scheduled.
Snapshot.sendApplyNotifications()
}
}
/**
* A clock that runs onFrame immediately when withFrameNanos is requested.
* Since it doesn't sync with any actual/real frame dispatching,
* this Clock impl can be used only for tests.
*/
private class MonotonicClockImpl : MonotonicFrameClock {
private val NANOS_PER_MILLI = 1_000_000
override suspend fun <R> withFrameNanos(
onFrame: (Long) -> R
): R = suspendCoroutine { continuation ->
val now = Clock.System.now()
val currentNanos = now.toEpochMilliseconds() * NANOS_PER_MILLI + now.nanosecondsOfSecond
val result = onFrame(currentNanos)
continuation.resume(result)
}
}
fun composeText(
recomposerCoroutineContext: CoroutineContext? = null,
content: @Composable () -> Unit
): StringsNodeWrapper {
GlobalSnapshotManager.ensureStarted()
val clock = MonotonicClockImpl()
val context = if (recomposerCoroutineContext != null) recomposerCoroutineContext + clock else clock
val recomposer = Recomposer(context)
CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) {
recomposer.runRecomposeAndApplyChanges()
}
val root = StringsNodeWrapper("root")
val composition = Composition(
applier = StringsListApplier(root),
parent = recomposer
)
composition.setContent(content)
return root
}
@Composable
fun TextLeafNode(text: String) {
ComposeNode<StringsNodeWrapper, StringsListApplier>(
factory = { PlainTextNode(text) },
update = {
set(text) { value -> this.text = value }
},
)
}
@Composable
fun TextContainerNode(name: String, content: @Composable () -> Unit = {}) {
ComposeNode<StringsNodeWrapper, StringsListApplier>(
factory = { StringsNodeWrapper(name) },
update = {
set(name) { value -> this.text = value }
},
content = content
)
}

67
compose/integrations/composable-test-cases/common/src/commonTest/kotlin/com.example.common/Test.kt

@ -0,0 +1,67 @@
package com.example.common
import androidx.compose.runtime.*
import kotlinx.coroutines.*
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Test {
@Test
fun testEmptyPlainTextNode() {
val root = composeText {}
assertEquals("root:{}", root.dump())
}
@Test
fun testPlainTextNode() {
val root = composeText {
TextLeafNode("Hello World!")
}
assertEquals("root:{Hello World!}", root.dump())
}
@Test
fun testTextContainerNodeEmpty() {
val root = composeText {
TextContainerNode("abc") {}
}
assertEquals("root:{abc:{}}", root.dump())
}
@Test
fun testTextContainerNode() {
val root = composeText {
TextContainerNode("abc") {
TextLeafNode("Hello World!")
}
}
assertEquals("root:{abc:{Hello World!}}", root.dump())
}
@Test
fun testRecomposition() = runTest {
val index = mutableStateOf(1)
val job = Job()
val root = composeText(coroutineContext + job) {
TextContainerNode("abc${index.value}") {
TextLeafNode("Hello World!")
}
}
assertEquals("root:{abc1:{Hello World!}}", root.dump())
index.value = 2
testScheduler.advanceUntilIdle()
assertEquals("root:{abc2:{Hello World!}}", root.dump())
index.value = 3
testScheduler.advanceUntilIdle()
assertEquals("root:{abc3:{Hello World!}}", root.dump())
job.cancel()
}
}

16
compose/integrations/composable-test-cases/gradle.properties

@ -0,0 +1,16 @@
org.gradle.jvmargs=-Xmx2048M -XX:MaxMetaspaceSize=512m
kotlin.code.style=official
kotlin.native.enableDependencyPropagation=false
android.useAndroidX=true
kotlin.version=1.8.10
agp.version=7.3.0
compose.version=1.3.0
kotlin.native.cacheKind=none
#empty by default - a default version will be used
compose.kotlinCompilerPluginVersion=1.4.2-rc03
# default|failingJs - see enum class CasesToRun
tests.casesToRun=default
# comma separated names of test cases known to fail (either compilation or while running) with k/js.
tests.failing.kjs=rememberAnonymousObj

BIN
compose/integrations/composable-test-cases/gradle/wrapper/gradle-wrapper.jar vendored

Binary file not shown.

5
compose/integrations/composable-test-cases/gradle/wrapper/gradle-wrapper.properties vendored

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
compose/integrations/composable-test-cases/gradlew vendored

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
compose/integrations/composable-test-cases/gradlew.bat vendored

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

130
compose/integrations/composable-test-cases/settings.gradle.kts

@ -0,0 +1,130 @@
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
mavenLocal()
}
plugins {
id("org.jetbrains.compose").version(extra["compose.version"] as String)
}
}
rootProject.name = "composable-test-cases"
fun module(name: String, path: String) {
include(name)
val projectDir = rootDir.resolve(path).normalize().absoluteFile
if (!projectDir.exists()) {
throw AssertionError("file $projectDir does not exist")
}
project(name).projectDir = projectDir
}
gradle.startParameter.setContinueOnFailure(true)
enum class CasesToRun(val value: String) {
Default("default"),
FailingJs("failingJs"),
;
fun isDefault() = this == Default
}
val casesToRun = CasesToRun.values().firstOrNull {
it.value == extra["tests.casesToRun"]
} ?: CasesToRun.Default
val listOfFailingJsCases = (extra["tests.failing.kjs"] as String).split(",")
val failingJsSuffix = "failingJs"
fun getFailingSuffix(testCaseName: String): String? {
if (casesToRun == CasesToRun.FailingJs &&
listOfFailingJsCases.contains(testCaseName)
) {
return failingJsSuffix
}
return null
}
fun addRememberAnonymousObjTestCase(testFailingJs: Boolean = false) {
val libName = ":testcase-rememberAnonymousObj-lib".let {
if (testFailingJs) {
it.replace("-lib", "-$failingJsSuffix-lib")
} else {
it
}
}
val mainName = ":testcase-rememberAnonymousObj-main".let {
if (testFailingJs) {
it.replace("-main", "-$failingJsSuffix-main")
} else {
it
}
}
module(libName, "testcases/rememberAnonymousObj/lib")
module(mainName, "testcases/rememberAnonymousObj/main")
}
/**
* @param name - the name of a test case
* @param failingTestCaseNameSuffix - the suffix to add for a failing test case.
* Each platform should have its own unique suffix.
*/
fun addATestCase(name: String, failingTestCaseNameSuffix: String? = null) {
val libName = ":testcase-$name-lib".let {
if (failingTestCaseNameSuffix != null) {
it.replace("-lib", "-$failingTestCaseNameSuffix-lib")
} else {
it
}
}
val mainName = ":testcase-$name-main".let {
if (failingTestCaseNameSuffix != null) {
it.replace("-main", "-$failingTestCaseNameSuffix-main")
} else {
it
}
}
println("adding $libName, $mainName")
module(libName, "testcases/$name/lib")
module(mainName, "testcases/$name/main")
}
include(":common")
if (casesToRun.isDefault()) {
module(":testcase-template-lib", "testcases/template/lib")
module(":testcase-template-main", "testcases/template/main")
module(":testcase-inheritance-composableInterface-lib", "testcases/inheritance/composableInterface/lib")
module(":testcase-inheritance-composableInterface-main", "testcases/inheritance/composableInterface/main")
module(":testcase-inheritance-funInterface-lib", "testcases/inheritance/funInterface/lib")
module(":testcase-inheritance-funInterface-main", "testcases/inheritance/funInterface/main")
module(":testcase-constructors-lib", "testcases/constructors/lib")
module(":testcase-constructors-main", "testcases/constructors/main")
module(":testcase-anonymousObjects-lib", "testcases/anonymousObjects/lib")
module(":testcase-anonymousObjects-main", "testcases/anonymousObjects/main")
module(":testcase-valueClass-lib", "testcases/valueClass/lib")
module(":testcase-valueClass-main", "testcases/valueClass/main")
module(":testcase-lambdas-lib", "testcases/lambdas/lib")
module(":testcase-lambdas-main", "testcases/lambdas/main")
}
/**
* Below we add modules for cases which are known to be failing at least on 1 platform.
* These cases should be fixed!
*/
"rememberAnonymousObj".also {
addATestCase(name = it, failingTestCaseNameSuffix = getFailingSuffix(it))
}

20
compose/integrations/composable-test-cases/testcases/anonymousObjects/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

6
compose/integrations/composable-test-cases/testcases/anonymousObjects/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,6 @@
import androidx.compose.runtime.Composable
interface HasComposable2 {
@Composable
fun Abc()
}

21
compose/integrations/composable-test-cases/testcases/anonymousObjects/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

22
compose/integrations/composable-test-cases/testcases/anonymousObjects/main/src/commonMain/kotlin/Implementations.kt

@ -0,0 +1,22 @@
import androidx.compose.runtime.Composable
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
class TestConstructor constructor() {
var otherComposable: (@Composable () -> Unit)? = null
constructor(retInt: () -> Int): this() {
otherComposable = {
val abc3: HasComposable2 = object : HasComposable2 {
@Composable
override fun Abc() {
TextContainerNode("div") {
val i = retInt()
TextLeafNode("Abc-$i")
}
}
}
abc3.Abc()
}
}
}

73
compose/integrations/composable-test-cases/testcases/anonymousObjects/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,73 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
// Issue: content.Abc$composable_z540rc_k$ is not a function
// https://github.com/JetBrains/compose-jb/issues/2549
fun testComposableInAnonymousObject() = runTest {
val root = composeText {
val content: HasComposable2 = createHasComposable()
content.Abc()
}
assertEquals("root:{div:{Abc}}", root.dump())
}
@Test
// Issue:
// java.lang.IllegalArgumentException: Could not find local implementation for Abc$composable
// at androidx.compose.compiler.plugins.kotlin.lower.decoys.DecoyTransformBase$DefaultImpls.getComposableForDecoy(DecoyTransformBase.kt:110)
fun testLocalClassWithComposable() = runTest {
val root = composeText {
HasLocalClassWithComposable()
}
assertEquals("root:{div:{Abc2}}", root.dump())
}
@Test
fun testConstructorWithComposable() = runTest {
val root = composeText {
TestConstructor { return@TestConstructor 111 }.otherComposable!!.invoke()
}
assertEquals("root:{div:{Abc-111}}", root.dump())
}
}
@Composable
internal fun HasLocalClassWithComposable() {
class Abc : HasComposable2 {
@Composable
override fun Abc() {
TextContainerNode("div") {
TextLeafNode("Abc2")
}
}
}
val abc = remember { Abc() }
abc.Abc()
}
@Composable
internal fun createHasComposable(): HasComposable2 {
return object : HasComposable2 {
@Composable
override fun Abc() {
TextContainerNode("div") {
TextLeafNode("Abc")
}
}
}
}

20
compose/integrations/composable-test-cases/testcases/constructors/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

90
compose/integrations/composable-test-cases/testcases/constructors/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,90 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import com.example.common.TextLeafNode
import kotlin.jvm.JvmInline
class ClassTakesComposablePrivateVal(private val c: @Composable () -> Unit) {
@Composable
fun callPrivateComposablePassedIntoConstructor() {
c()
}
}
interface HasComposable {
val composable: @Composable () -> Unit
}
interface HasComposableTyped<T> {
val composable: @Composable (T) -> Unit
}
class ClassSavesComposableIntoVar(c: @Composable () -> Unit) {
var composableVar: @Composable () -> Unit = c
}
@Suppress("UNNECESSARY_LATEINIT")
class ClassSavesComposableIntoLateinitVar(c: @Composable () -> Unit) {
lateinit var composableVar: @Composable () -> Unit
init {
composableVar = c
}
}
class ClassSavesComposableIntoNullableVar(c: @Composable () -> Unit) {
var composableVar: (@Composable () -> Unit)? = null
init {
composableVar = c
}
}
class ClassSavesTypedComposableIntoVar<T>(c: @Composable (T) -> Unit) {
var composableVar: @Composable (T) -> Unit = c
}
@Suppress("UNNECESSARY_LATEINIT")
class ClassSavesTypedComposableIntoLateinitVar<T>(c: @Composable (T) -> Unit) {
lateinit var composableVar: @Composable (T) -> Unit
init {
composableVar = c
}
}
class ClassWithSecondaryConstructorSavesComposable(val c: @Composable () -> Unit) {
constructor(): this({
TextLeafNode("SecondaryConstructor")
})
}
data class DataClassTakesValComposable(val c: @Composable () -> Unit)
data class DataClassTakesValComposableTyped<T>(val c: @Composable (T) -> Unit)
data class DataClassTakesVarComposable(var c: @Composable () -> Unit)
class ClassTakesValComposable(val c: @Composable () -> Unit)
class ClassTakesValComposableTyped<T>(val c: @Composable (T) -> Unit)
class ClassTakesVarComposable(var c: @Composable () -> Unit)
data class DataClassTakesValStringAndComposable(
val s: String, val c: @Composable DataClassTakesValStringAndComposable.() -> Unit
)
class ClassTakesValStringAndComposable(
val s: String, val c: @Composable ClassTakesValStringAndComposable.() -> Unit
)
class ClassSavesStringAndComposableIntoVar(
s: String, c: @Composable ClassSavesStringAndComposableIntoVar.() -> Unit
) {
var composableVar: @Composable ClassSavesStringAndComposableIntoVar.() -> Unit = c
var stringVar: String = s
}
val GlobalComposableLambdaToShowText: @Composable (text: () -> String) -> Unit = {
TextLeafNode(it())
}
@JvmInline
value class ComposableContent(val content: @Composable () -> Unit)

21
compose/integrations/composable-test-cases/testcases/constructors/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

9
compose/integrations/composable-test-cases/testcases/constructors/main/src/commonMain/kotlin/Implementations.kt

@ -0,0 +1,9 @@
import androidx.compose.runtime.Composable
class ImplementsHasComposable(
override val composable: @Composable () -> Unit
): HasComposable
class ImplementsHasComposableTyped<T>(
override val composable: @Composable (T) -> Unit
): HasComposableTyped<T>

330
compose/integrations/composable-test-cases/testcases/constructors/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,330 @@
import androidx.compose.runtime.RecomposeScope
import androidx.compose.runtime.currentRecomposeScope
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
fun testClassTakesComposablePrivateVal() = runTest {
val impl = ClassTakesComposablePrivateVal {
TextLeafNode("ClassTakesComposablePrivateVal")
}
val root = composeText {
impl.callPrivateComposablePassedIntoConstructor()
}
assertEquals("root:{ClassTakesComposablePrivateVal}", root.dump())
}
@Test
fun testImplementsHasComposable() = runTest {
val impl = ImplementsHasComposable {
TextLeafNode("ImplementsHasComposable")
}
val root = composeText {
impl.composable()
}
assertEquals("root:{ImplementsHasComposable}", root.dump())
}
@Test
fun testImplementsHasComposableTyped() = runTest {
val impl = ImplementsHasComposableTyped<String> {
TextLeafNode("ImplementsHasComposableTyped-$it")
}
val root = composeText {
impl.composable("Hello")
}
assertEquals("root:{ImplementsHasComposableTyped-Hello}", root.dump())
}
@Test
fun testClassSavesComposableIntoVar() = runTest {
val impl = ClassSavesComposableIntoVar {
TextLeafNode("ClassSavesComposableIntoVar")
}
val job = Job()
var scope: RecomposeScope? = null
val root = composeText(coroutineContext + job) {
scope = currentRecomposeScope
impl.composableVar()
}
assertEquals("root:{ClassSavesComposableIntoVar}", root.dump())
impl.composableVar = {
TextLeafNode("NewClassSavesComposableIntoVar")
}
scope!!.invalidate()
testScheduler.advanceUntilIdle()
assertEquals("root:{NewClassSavesComposableIntoVar}", root.dump())
job.cancel()
}
@Test
fun testClassSavesComposableIntoLateinitVar() = runTest {
val impl = ClassSavesComposableIntoLateinitVar {
TextLeafNode("ClassSavesComposableIntoLateinitVar")
}
var scope: RecomposeScope? = null
val job = Job()
val root = composeText(coroutineContext + job) {
scope = currentRecomposeScope
impl.composableVar()
}
assertEquals("root:{ClassSavesComposableIntoLateinitVar}", root.dump())
impl.composableVar = {
TextLeafNode("NewClassSavesComposableIntoLateinitVar")
}
scope!!.invalidate()
testScheduler.advanceUntilIdle()
assertEquals("root:{NewClassSavesComposableIntoLateinitVar}", root.dump())
job.cancel()
}
@Test
fun testClassSavesComposableIntoNullableVar() = runTest {
val impl = ClassSavesComposableIntoNullableVar {
TextLeafNode("ClassSavesComposableIntoNullableVar")
}
var scope: RecomposeScope? = null
val job = Job()
val root = composeText(coroutineContext + job) {
scope = currentRecomposeScope
impl.composableVar?.invoke()
}
assertEquals("root:{ClassSavesComposableIntoNullableVar}", root.dump())
impl.composableVar = null
scope!!.invalidate()
testScheduler.advanceUntilIdle()
assertEquals("root:{}", root.dump())
job.cancel()
}
@Test
fun testClassSavesTypedComposableIntoVar() = runTest {
val impl = ClassSavesTypedComposableIntoVar<String> {
TextLeafNode("ClassSavesTypedComposableIntoVar-$it")
}
var scope: RecomposeScope? = null
val job = Job()
val root = composeText(coroutineContext + job) {
scope = currentRecomposeScope
impl.composableVar("abc")
}
assertEquals("root:{ClassSavesTypedComposableIntoVar-abc}", root.dump())
impl.composableVar = {
TextLeafNode("recomposed-$it")
}
scope!!.invalidate()
testScheduler.advanceUntilIdle()
assertEquals("root:{recomposed-abc}", root.dump())
job.cancel()
}
@Test
fun testClassSavesTypedComposableIntoLateinitVar() = runTest {
val impl = ClassSavesTypedComposableIntoLateinitVar<String> {
TextLeafNode("ClassSavesTypedComposableIntoLateinitVar-$it")
}
var scope: RecomposeScope? = null
val job = Job()
val root = composeText(coroutineContext + job) {
scope = currentRecomposeScope
impl.composableVar("abc")
}
assertEquals("root:{ClassSavesTypedComposableIntoLateinitVar-abc}", root.dump())
impl.composableVar = {
TextLeafNode("recomposed-$it")
}
scope!!.invalidate()
testScheduler.advanceUntilIdle()
assertEquals("root:{recomposed-abc}", root.dump())
job.cancel()
}
@Test
fun testClassWithSecondaryConstructorSavesComposable() = runTest {
val impl = ClassWithSecondaryConstructorSavesComposable()
val root = composeText {
impl.c()
}
assertEquals("root:{SecondaryConstructor}", root.dump())
}
@Test
fun testDataClassTakesValComposable() = runTest {
val impl = DataClassTakesValComposable {
TextLeafNode("DataClassTakesValComposable")
}
val root = composeText {
impl.c()
}
assertEquals("root:{DataClassTakesValComposable}", root.dump())
}
@Test
fun testDataClassTakesValComposableTyped() = runTest {
val impl = DataClassTakesValComposableTyped<String> {
TextLeafNode("DataClassTakesValComposableTyped-$it")
}
val root = composeText {
impl.c("abc")
}
assertEquals("root:{DataClassTakesValComposableTyped-abc}", root.dump())
}
@Test
fun testDataClassTakesVarComposable() = runTest {
val impl = DataClassTakesVarComposable {
TextLeafNode("DataClassTakesVarComposable")
}
val root = composeText {
impl.c()
}
assertEquals("root:{DataClassTakesVarComposable}", root.dump())
}
@Test
fun testClassTakesValComposable() = runTest {
val impl = ClassTakesValComposable {
TextLeafNode("ClassTakesValComposable")
}
val root = composeText {
impl.c()
}
assertEquals("root:{ClassTakesValComposable}", root.dump())
}
@Test
fun testClassTakesValComposableTyped() = runTest {
val impl = ClassTakesValComposableTyped<Int> {
TextLeafNode("ClassTakesValComposableTyped-$it")
}
val root = composeText {
impl.c(100)
}
assertEquals("root:{ClassTakesValComposableTyped-100}", root.dump())
}
@Test
fun testClassTakesVarComposable() = runTest {
val impl = ClassTakesVarComposable {
TextLeafNode("ClassTakesVarComposable")
}
val root = composeText {
impl.c()
}
assertEquals("root:{ClassTakesVarComposable}", root.dump())
}
@Test
fun testDataClassTakesValStringAndComposable() = runTest {
val impl = DataClassTakesValStringAndComposable("Abc") {
TextLeafNode("DataClassTakesValStringAndComposable-$s")
}
val root = composeText {
with(impl) {
c()
}
}
assertEquals("root:{DataClassTakesValStringAndComposable-Abc}", root.dump())
}
@Test
fun testClassTakesValStringAndComposable() = runTest {
val impl = ClassTakesValStringAndComposable("Abc2") {
TextLeafNode("ClassTakesValStringAndComposable-$s")
}
val root = composeText {
with(impl) {
c()
}
}
assertEquals("root:{ClassTakesValStringAndComposable-Abc2}", root.dump())
}
@Test
fun testClassSavesStringAndComposableIntoVar() = runTest {
val impl = ClassSavesStringAndComposableIntoVar("Abc3") {
TextLeafNode("ClassSavesStringAndComposableIntoVar-${this.stringVar}")
}
val root = composeText {
with(impl) { impl.composableVar() }
}
assertEquals("root:{ClassSavesStringAndComposableIntoVar-Abc3}", root.dump())
}
@Test
fun testGlobalComposableLambdaToShowText() = runTest {
val root = composeText {
GlobalComposableLambdaToShowText {
"TextReturnedFromALambda"
}
}
assertEquals("root:{TextReturnedFromALambda}", root.dump())
}
@Test
@Ignore // compilation fails on desktop only
fun testValueClass() = runTest {
// val impl = ComposableContent { TextLeafNode("ValueClassComposableContent") }
//
// val root = composeText {
// impl.content()
// }
//
// assertEquals("root:{ValueClassComposableContent}", root.dump())
}
}

20
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

16
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,16 @@
import androidx.compose.runtime.Composable
interface ComposableContent {
@Composable
fun ComposableContent()
@Composable
fun ComposableContentWithChildren(moreContent: @Composable () -> Unit)
}
interface CollectionOfComposable {
fun add(composable: @Composable () -> Unit)
fun iterator(): Iterator<@Composable () -> Unit>
}

21
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

43
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonMain/kotlin/ComposableContentImpl.kt

@ -0,0 +1,43 @@
import androidx.compose.runtime.Composable
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
class ComposableContentImpl : ComposableContent {
@Composable
override fun ComposableContent() {
TextLeafNode("ComposableContent")
}
@Composable
override fun ComposableContentWithChildren(moreContent: @Composable () -> Unit) {
TextContainerNode("ComposableContent", moreContent)
}
}
class DelegateComposableContent(delegateTo: ComposableContent): ComposableContent by delegateTo
open class OpenComposableContentImpl : ComposableContent {
@Composable
override fun ComposableContent() {
TextLeafNode("OpenComposableContentImpl")
}
@Composable
override fun ComposableContentWithChildren(moreContent: @Composable () -> Unit) {
TextContainerNode("OpenComposableContentImpl", moreContent)
}
}
class CollectionOfComposablesImpl : CollectionOfComposable {
private val list = mutableListOf<@Composable () -> Unit>()
override fun add(composable: @Composable () -> Unit) {
list.add(composable)
}
override fun iterator(): Iterator<@Composable () -> Unit> {
return list.iterator()
}
}

40
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CollectionOfComposablesTests.kt

@ -0,0 +1,40 @@
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class CollectionOfComposablesTests {
@Test
fun testCanAddItemsAndIterate() = runTest {
val impl = CollectionOfComposablesImpl()
impl.add {
TextLeafNode("leaf1")
TextLeafNode("leaf2")
TextLeafNode("leaf3")
}
impl.add {
TextContainerNode("node") {
TextLeafNode("child1")
TextLeafNode("child2")
TextLeafNode("child3")
}
}
val root = composeText {
impl.iterator().forEach {
it()
}
}
assertEquals(
expected = "root:{leaf1, leaf2, leaf3, node:{child1, child2, child3}}",
actual = root.dump()
)
}
}

99
compose/integrations/composable-test-cases/testcases/inheritance/composableInterface/main/src/commonTest/kotlin/CompsableInterfaceTests.kt

@ -0,0 +1,99 @@
import androidx.compose.runtime.Composable
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class CompsableInterfaceTests {
@Test
fun testComposableContentInterfaceImpl() = runTest {
val impl: ComposableContent = ComposableContentImpl()
val root = composeText {
impl.ComposableContent()
}
assertEquals("root:{ComposableContent}", root.dump())
}
@Test
fun testComposableContentInterfaceImpl2() = runTest {
val impl: ComposableContent = ComposableContentImpl()
val root = composeText {
impl.ComposableContentWithChildren {
TextLeafNode("Leaf")
}
}
assertEquals("root:{ComposableContent:{Leaf}}", root.dump())
}
@Test
fun testComposableContentDelegation() = runTest {
val impl: ComposableContent = DelegateComposableContent(
object : ComposableContent {
@Composable
override fun ComposableContent() {
TextLeafNode("anonymous_ComposableContent")
}
@Composable
override fun ComposableContentWithChildren(moreContent: @Composable () -> Unit) {
TextContainerNode("anonymous_ComposableContent2", moreContent)
}
}
)
val root = composeText {
impl.ComposableContent()
impl.ComposableContentWithChildren {
TextLeafNode("Leaf")
}
}
assertEquals(
"root:{anonymous_ComposableContent, anonymous_ComposableContent2:{Leaf}}",
root.dump()
)
}
@Test
fun testOverrideOpenComposableContentImpl() = runTest {
val impl = FinalComposableContentImpl()
val root = composeText {
impl.ComposableContent()
impl.ComposableContentWithChildren {
TextLeafNode("Leaf")
}
}
assertEquals(
"root:{FinalComposableContentImpl:{OpenComposableContentImpl}, FinalComposableContentImpl:{OpenComposableContentImpl:{Leaf}}}",
root.dump()
)
}
}
private class FinalComposableContentImpl : OpenComposableContentImpl() {
@Composable
override fun ComposableContent() {
TextContainerNode("FinalComposableContentImpl") {
super.ComposableContent()
}
}
@Composable
override fun ComposableContentWithChildren(moreContent: @Composable () -> Unit) {
TextContainerNode("FinalComposableContentImpl") {
super.ComposableContentWithChildren(moreContent)
}
}
}

20
compose/integrations/composable-test-cases/testcases/inheritance/funInterface/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

11
compose/integrations/composable-test-cases/testcases/inheritance/funInterface/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,11 @@
import androidx.compose.runtime.Composable
fun interface FunInterfaceWithComposable {
@Composable
fun content()
}
fun interface FunInterfaceReturnComposable {
fun getContent(): @Composable () -> Unit
}

21
compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

18
compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/src/commonMain/kotlin/Implementations.kt

@ -0,0 +1,18 @@
import androidx.compose.runtime.Composable
import com.example.common.TextLeafNode
val funInterfaceWithComposable = FunInterfaceWithComposable {
TextLeafNode("FunInterfaceWithComposable")
}
val funInterfaceReturnComposable = FunInterfaceReturnComposable {
{ TextLeafNode("FunInterfaceReturnComposable") }
}
class ClassImplementingFunInterface : FunInterfaceWithComposable {
@Composable
override fun content() {
TextLeafNode("ClassImplementingFunInterface")
}
}

36
compose/integrations/composable-test-cases/testcases/inheritance/funInterface/main/src/commonTest/kotlin/FunInterfaceTests.kt

@ -0,0 +1,36 @@
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class FunInterfaceTests {
@Test
fun testFunInterfaceWithComposable() = runTest {
val root = composeText {
funInterfaceWithComposable.content()
}
assertEquals("root:{FunInterfaceWithComposable}", root.dump())
val classImplFunInterface = ClassImplementingFunInterface()
val root2 = composeText {
classImplFunInterface.content()
}
assertEquals("root:{ClassImplementingFunInterface}", root2.dump())
}
@Test
fun testFunInterfaceReturnComposable() = runTest {
val root = composeText {
funInterfaceReturnComposable.getContent().invoke()
}
assertEquals("root:{FunInterfaceReturnComposable}", root.dump())
}
}

20
compose/integrations/composable-test-cases/testcases/lambdas/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

14
compose/integrations/composable-test-cases/testcases/lambdas/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,14 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.cache
import androidx.compose.runtime.currentComposer
import com.example.common.TextLeafNode
@Composable
fun ComposableSomeText(someText : () -> String) {
TextLeafNode(someText())
}
val composableInt: Int
@Composable
get() = currentComposer.cache(false) { 100 }

21
compose/integrations/composable-test-cases/testcases/lambdas/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

120
compose/integrations/composable-test-cases/testcases/lambdas/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,120 @@
import androidx.compose.runtime.Composable
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
fun passingFunctionReference() = runTest {
val root = composeText {
ComposableSomeText(::someText)
}
assertEquals("root:{SomeText}", root.dump())
}
@Test
fun passingAnonymousLambda() = runTest {
val root = composeText {
ComposableSomeText { "passingAnonymousLambda" }
}
assertEquals("root:{passingAnonymousLambda}", root.dump())
}
@Test
fun callingComposableLambdaWithoutArguments() {
val l: @Composable () -> Unit = {
TextLeafNode("TextA")
}
val root = composeText {
l()
}
assertEquals("root:{TextA}", root.dump())
}
@Test
fun invokingNullComposableLambdaWithoutArguments() {
val l: (@Composable () -> Unit)? = null
val root = composeText {
l?.invoke()
}
assertEquals("root:{}", root.dump())
}
@Test
fun invokingNullComposableLambdaWithArguments() {
val l: (@Composable (Int) -> Unit)? = null
var someIntInvoked = false
fun someInt(): Int {
someIntInvoked = true
return 10
}
val root = composeText {
l?.invoke(someInt())
}
assertEquals("root:{}", root.dump())
assertFalse(someIntInvoked)
}
@Test
fun invokingComposableLambdaWithArguments() {
val l: (@Composable (Int) -> Unit) = {
TextLeafNode("Text$it")
}
var someIntInvoked = false
fun someInt(): Int {
someIntInvoked = true
return 2023
}
val root = composeText {
l.invoke(someInt())
}
assertEquals("root:{Text2023}", root.dump())
assertTrue(someIntInvoked)
}
@Test
fun invokingComposableLambdaWithFunctionReferenceAsArgument() {
val l: (@Composable (() -> String) -> Unit) = {
TextLeafNode("Text-${it()}")
}
val root = composeText {
l(::someText)
}
assertEquals("root:{Text-SomeText}", root.dump())
}
@Test
fun testComposableGetter() {
val root = composeText {
TextLeafNode("Value = $composableInt")
}
assertEquals("root:{Value = 100}", root.dump())
}
}
private fun someText(): String {
return "SomeText"
}

38
compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/lib/build.gradle.kts

@ -0,0 +1,38 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
@OptIn(org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi::class)
fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.configureDefaultTargets() {
jvm("desktop")
ios()
iosArm64()
iosSimulatorArm64()
iosX64()
macosX64()
macosArm64()
// We use linux agents on CI. So it doesn't run the tests, but it builds the klib anyway which is time consuming.
if (project.isInIdea) mingwX64()
linuxX64()
}
kotlin {
if (project.isFailingJsCase) {
configureJsTargets()
} else {
configureDefaultTargets()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

87
compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,87 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.example.common.TextLeafNode
interface SomeInterface {
fun abc()
}
// Known to fail with k/js and decoys enabled
// https://github.com/JetBrains/compose-jb/issues/2615
@Composable
fun CallsRememberAnonymousObject() {
val obj = remember { object {} }
TextLeafNode("obj1")
}
@Composable
fun CallsRememberAnonymousObjectImplInterface() {
val obj = remember {
object : SomeInterface {
override fun abc() {}
}
}
TextLeafNode("obj2")
}
@Composable
fun CallsRememberAnonymousObjectExplicitType() {
val obj: SomeInterface = remember {
object : SomeInterface {
override fun abc() {}
}
}
TextLeafNode("obj3")
}
@Composable
fun CallsRememberAnonymousObjectExplicitType2() {
val obj = remember<SomeInterface> {
object : SomeInterface {
override fun abc() {}
}
}
TextLeafNode("obj4")
}
@Composable
fun CallsRememberLocalClass() {
val obj = remember {
class Abc {}
Abc()
}
TextLeafNode("AbcLocalClass")
}
@Composable
fun CallsRememberLocalClassImplInterface() {
val obj = remember {
class Abc : SomeInterface {
override fun abc() {}
}
Abc()
}
TextLeafNode("AbcLocalClassImplInterface")
}
@Composable
fun CallsRememberLocalClassImplInterfaceExplicitType() {
val obj: SomeInterface = remember {
class Abc : SomeInterface {
override fun abc() {}
}
Abc()
}
TextLeafNode("AbcLocalClassImplInterfaceExplicitType")
}
@Composable
fun CallsRememberLocalClassImplInterfaceExplicitType2() {
val obj = remember<SomeInterface> {
class Abc : SomeInterface {
override fun abc() {}
}
Abc()
}
TextLeafNode("AbcLocalClassImplInterfaceExplicitType2")
}

39
compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/main/build.gradle.kts

@ -0,0 +1,39 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
@OptIn(org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi::class)
fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.configureDefaultTargets() {
jvm("desktop")
ios()
iosArm64()
iosSimulatorArm64()
iosX64()
macosX64()
macosArm64()
// We use linux agents on CI. So it doesn't run the tests, but it builds the klib anyway which is time consuming.
if (project.isInIdea) mingwX64()
linuxX64()
}
kotlin {
if (project.isFailingJsCase) {
configureJsTargets()
} else {
configureDefaultTargets()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

45
compose/integrations/composable-test-cases/testcases/rememberAnonymousObj/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,45 @@
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
fun testCallsRememberAnonymousObject() = runTest {
val root = composeText {
CallsRememberAnonymousObject()
}
assertEquals("root:{obj1}", root.dump())
}
@Test
fun testCallsRememberAnonymousObjectImplInterface() = runTest {
val root = composeText {
CallsRememberAnonymousObjectImplInterface()
}
assertEquals("root:{obj2}", root.dump())
}
@Test
fun testCallsRememberAnonymousObjectExplicitType() = runTest {
val root = composeText {
CallsRememberAnonymousObjectExplicitType()
}
assertEquals("root:{obj3}", root.dump())
}
@Test
fun testCallsRememberAnonymousObjectExplicitType2() = runTest {
val root = composeText {
CallsRememberAnonymousObjectExplicitType2()
}
assertEquals("root:{obj4}", root.dump())
}
}

20
compose/integrations/composable-test-cases/testcases/template/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

7
compose/integrations/composable-test-cases/testcases/template/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,7 @@
import androidx.compose.runtime.Composable
import com.example.common.TextLeafNode
@Composable
fun SimpleComposable() {
TextLeafNode("SimpleComposable")
}

21
compose/integrations/composable-test-cases/testcases/template/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

34
compose/integrations/composable-test-cases/testcases/template/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,34 @@
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
fun testExample() = runTest {
val root = composeText {
SimpleComposable()
}
assertEquals("root:{SimpleComposable}", root.dump())
}
@Test
fun testExample2() = runTest {
val root = composeText {
TextLeafNode("Leaf")
TextContainerNode("node") {
TextLeafNode("child1")
TextLeafNode("child2")
TextLeafNode("child3")
}
}
assertEquals("root:{Leaf, node:{child1, child2, child3}}", root.dump())
}
}

20
compose/integrations/composable-test-cases/testcases/valueClass/lib/build.gradle.kts

@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

64
compose/integrations/composable-test-cases/testcases/valueClass/lib/src/commonMain/kotlin/Dependencies.kt

@ -0,0 +1,64 @@
import kotlin.jvm.JvmInline
@JvmInline
value class VCAllPublic(val value: Int)
@JvmInline
value class VCNestedVCAllPublic(val value: VCAllPublic)
@JvmInline
value class VCInternalVal(internal val value: Int)
@JvmInline
value class VCPrivateVal(private val value: Int)
@JvmInline
value class VCNestedVCPrivateVal(val value: VCPrivateVal)
@JvmInline
value class VCInternalCtor internal constructor(val value: Int) {
companion object {
val V1 = VCInternalCtor(1)
val V2 = VCInternalCtor(2)
}
}
@JvmInline
value class VCInternalAll internal constructor(internal val value: Int) {
companion object {
val V1 = VCInternalAll(111)
val V2 = VCInternalAll(222)
}
}
@JvmInline
value class VCPrivateCtor private constructor(val value: Int) {
companion object {
val V1 = VCPrivateCtor(1111)
val V2 = VCPrivateCtor(2222)
}
}
@JvmInline
value class VCPrivateCtorInternalVal private constructor(internal val value: Int) {
companion object {
val V1 = VCPrivateCtorInternalVal(101)
val V2 = VCPrivateCtorInternalVal(202)
}
}
@JvmInline
value class VCPrivateAll private constructor(private val value: Int) {
companion object {
val V1 = VCPrivateAll(1001)
val V2 = VCPrivateAll(2002)
}
}
@JvmInline
value class VCPrivateAllNonPrimitive private constructor(private val value: String) {
companion object {
val V1 = VCPrivateAllNonPrimitive("V1")
val V2 = VCPrivateAllNonPrimitive("V2")
}
}

21
compose/integrations/composable-test-cases/testcases/valueClass/main/build.gradle.kts

@ -0,0 +1,21 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
configureTargets()
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(getCommonLib())
implementation(getLibDependencyForMain())
}
}
val commonTest by getting {
configureCommonTestDependencies()
}
}
}

75
compose/integrations/composable-test-cases/testcases/valueClass/main/src/commonMain/kotlin/ComposableTakingValue.kt

@ -0,0 +1,75 @@
import androidx.compose.runtime.Composable
import com.example.common.TextLeafNode
import kotlin.jvm.JvmInline
@Composable
fun TakeVCAllPublic(a: VCAllPublic) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCNestedVCAllPublic(a: VCNestedVCAllPublic) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCInternalVal(a: VCInternalVal) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateVal(a: VCPrivateVal) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCNestedVCPrivateVal(a: VCNestedVCPrivateVal) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCInternalCtor(a: VCInternalCtor) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCInternalAll(a: VCInternalAll) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateCtor(a: VCPrivateCtor) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateCtorInternalVal(a: VCPrivateCtorInternalVal) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateAll(a: VCPrivateAll) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateAllNonPrimitive(a: VCPrivateAllNonPrimitive) {
TextLeafNode("Value = $a")
}
@Composable
fun TakeVCPrivateAllWithDefaultValue(myabcaccc: VCPrivateAll = VCPrivateAll.V1) {
TextLeafNode("Value = $myabcaccc")
}
@JvmInline
value class SameModuleVCAllPrivate private constructor(private val value: Int) {
companion object {
val V1 = SameModuleVCAllPrivate(11011)
val V2 = SameModuleVCAllPrivate(22022)
}
}
@Composable
fun TakeSameModuleVCAllPrivate(a: SameModuleVCAllPrivate) {
TextLeafNode("Value = $a")
}

193
compose/integrations/composable-test-cases/testcases/valueClass/main/src/commonTest/kotlin/Tests.kt

@ -0,0 +1,193 @@
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.example.common.TextContainerNode
import com.example.common.TextLeafNode
import com.example.common.composeText
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class Tests {
@Test
fun testTakeVCAllPublic() = runTest {
val root = composeText {
TakeVCAllPublic(VCAllPublic(100))
}
assertEquals("root:{Value = VCAllPublic(value=100)}", root.dump())
}
@Test
fun testTakeVCNestedVCAllPublic() = runTest {
val root = composeText {
TakeVCNestedVCAllPublic(VCNestedVCAllPublic(VCAllPublic(123)))
}
assertEquals("root:{Value = VCNestedVCAllPublic(value=VCAllPublic(value=123))}", root.dump())
}
@Test
fun testTakeVCInternalVal() = runTest {
val root = composeText {
TakeVCInternalVal(VCInternalVal(200))
}
assertEquals("root:{Value = VCInternalVal(value=200)}", root.dump())
}
@Test
fun testTakeVCPrivateVal() = runTest {
val root = composeText {
TakeVCPrivateVal(VCPrivateVal(300))
}
assertEquals("root:{Value = VCPrivateVal(value=300)}", root.dump())
}
// TODO: fix for k/native
// Caused by: java.lang.IllegalStateException: actual type is VCPrivateVal, expected kotlin.Int
// at org.jetbrains.kotlin.backend.konan.BoxingKt.getTypeConversion(Boxing.kt:30)
// @Test
// fun testTakeVCNestedVCPrivateVal() = runTest {
// val root = composeText {
// TakeVCNestedVCPrivateVal(VCNestedVCPrivateVal(VCPrivateVal(321)))
// }
//
// assertEquals("root:{Value = VCNestedVCPrivateVal(value=VCPrivateVal(value=321))}", root.dump())
// }
@Test
fun testTakeVCInternalCtor() = runTest {
val root = composeText {
TakeVCInternalCtor(VCInternalCtor.V1)
}
assertEquals("root:{Value = VCInternalCtor(value=1)}", root.dump())
}
@Test
fun testTakeVCInternalAll() = runTest {
var v: VCInternalAll by mutableStateOf(VCInternalAll.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeVCInternalAll(v)
}
assertEquals("root:{Value = VCInternalAll(value=111)}", root.dump())
v = VCInternalAll.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = VCInternalAll(value=222)}", root.dump())
job.cancel()
}
@Test
fun testTakeVCPrivateCtor() = runTest {
var v: VCPrivateCtor by mutableStateOf(VCPrivateCtor.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeVCPrivateCtor(v)
}
assertEquals("root:{Value = VCPrivateCtor(value=1111)}", root.dump())
v = VCPrivateCtor.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = VCPrivateCtor(value=2222)}", root.dump())
job.cancel()
}
@Test
fun testTakeVCPrivateCtorInternalVal() = runTest {
var v: VCPrivateCtorInternalVal by mutableStateOf(VCPrivateCtorInternalVal.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeVCPrivateCtorInternalVal(v)
}
assertEquals("root:{Value = VCPrivateCtorInternalVal(value=101)}", root.dump())
v = VCPrivateCtorInternalVal.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = VCPrivateCtorInternalVal(value=202)}", root.dump())
job.cancel()
}
@Test
fun testTakeVCPrivateAll() = runTest {
var v: VCPrivateAll by mutableStateOf(VCPrivateAll.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeVCPrivateAll(v)
}
assertEquals("root:{Value = VCPrivateAll(value=1001)}", root.dump())
v = VCPrivateAll.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = VCPrivateAll(value=2002)}", root.dump())
job.cancel()
}
@Test
fun testTakeVCPrivateAllWithDefaultValue() = runTest {
val root = composeText {
TakeVCPrivateAllWithDefaultValue()
TakeVCPrivateAllWithDefaultValue(VCPrivateAll.V2)
}
assertEquals("root:{Value = VCPrivateAll(value=1001), Value = VCPrivateAll(value=2002)}", root.dump())
}
@Test
fun testTakeSameModuleVCAllPrivate() = runTest {
var v: SameModuleVCAllPrivate by mutableStateOf(SameModuleVCAllPrivate.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeSameModuleVCAllPrivate(v)
}
assertEquals("root:{Value = SameModuleVCAllPrivate(value=11011)}", root.dump())
v = SameModuleVCAllPrivate.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = SameModuleVCAllPrivate(value=22022)}", root.dump())
job.cancel()
}
@Test
fun testVCPrivateAllNonPrimitive() = runTest {
var v: VCPrivateAllNonPrimitive by mutableStateOf(VCPrivateAllNonPrimitive.V1)
val job = Job()
val root = composeText(coroutineContext + job) {
TakeVCPrivateAllNonPrimitive(v)
}
assertEquals("root:{Value = VCPrivateAllNonPrimitive(value=V1)}", root.dump())
v = VCPrivateAllNonPrimitive.V2
testScheduler.advanceUntilIdle()
assertEquals("root:{Value = VCPrivateAllNonPrimitive(value=V2)}", root.dump())
job.cancel()
}
}
Loading…
Cancel
Save