Skip to content

Commit 0a5c122

Browse files
authored
Update to Compose 1.7 + implement missing APIs for ComposeContext (#354)
* Update to Compose BOM * Add a unit test that verifies complete coverage of ComposeTestRule API for the JUnit 5 version * Add missing API methods from JUnit 4's ComposeTestRule to JUnit 5's ComposeContext * Update instrumentation/compose/src/main/java/de/mannodermaus/junit5/compose/ComposeContext.kt * Update instrumentation/compose/src/main/java/de/mannodermaus/junit5/compose/ComposeContext.kt * Update instrumentation/compose/src/test/java/de/mannodermaus/junit5/compose/ComposeContextTests.kt
1 parent 6a874bc commit 0a5c122

File tree

9 files changed

+124
-46
lines changed

9 files changed

+124
-46
lines changed

build-logic/src/main/kotlin/Dependencies.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object libs {
77
const val junitVintage = "5.11.0"
88
const val junitPlatform = "1.11.0"
99

10-
const val composeBom = "2024.08.00"
10+
const val composeBom = "2024.09.00"
1111
const val androidXTestAnnotation = "1.0.1"
1212
const val androidXTestCore = "1.6.1"
1313
const val androidXTestMonitor = "1.7.2"

build-logic/src/main/kotlin/Environment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ object Artifacts {
9797
*/
9898
object Instrumentation {
9999
const val groupId = "de.mannodermaus.junit5"
100-
private const val currentVersion = "1.5.1-SNAPSHOT"
100+
private const val currentVersion = "1.6.0-SNAPSHOT"
101101
private const val latestStableVersion = "1.5.0"
102102

103103
val Core = Deployed(

instrumentation/.idea/runConfigurations/Runner__Run_Unit_Tests__Gradle_.xml

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

instrumentation/.idea/runConfigurations/Sample__Run_Unit_Tests__Gradle_.xml

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

instrumentation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Change Log
44
## Unreleased
55

66
- Use square brackets for parameterized tests to ensure that their logs show correctly in the IDE (#350)
7+
- Add missing API methods from JUnit 4's ComposeTestRule to JUnit 5's ComposeContext (#353)
78

89
## 1.5.0 (2024-07-25)
910

instrumentation/compose/api/compose.api

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ public abstract interface class de/mannodermaus/junit5/compose/ComposeContext :
2929
public abstract fun unregisterIdlingResource (Landroidx/compose/ui/test/IdlingResource;)V
3030
public abstract fun waitForIdle ()V
3131
public abstract fun waitUntil (JLkotlin/jvm/functions/Function0;)V
32+
public abstract fun waitUntil (Ljava/lang/String;JLkotlin/jvm/functions/Function0;)V
33+
public abstract fun waitUntilAtLeastOneExists (Landroidx/compose/ui/test/SemanticsMatcher;J)V
34+
public abstract fun waitUntilDoesNotExist (Landroidx/compose/ui/test/SemanticsMatcher;J)V
35+
public abstract fun waitUntilExactlyOneExists (Landroidx/compose/ui/test/SemanticsMatcher;J)V
36+
public abstract fun waitUntilNodeCount (Landroidx/compose/ui/test/SemanticsMatcher;IJ)V
37+
}
38+
39+
public final class de/mannodermaus/junit5/compose/ComposeContext$DefaultImpls {
40+
public static synthetic fun waitUntil$default (Lde/mannodermaus/junit5/compose/ComposeContext;JLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
41+
public static synthetic fun waitUntil$default (Lde/mannodermaus/junit5/compose/ComposeContext;Ljava/lang/String;JLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
42+
public static synthetic fun waitUntilAtLeastOneExists$default (Lde/mannodermaus/junit5/compose/ComposeContext;Landroidx/compose/ui/test/SemanticsMatcher;JILjava/lang/Object;)V
43+
public static synthetic fun waitUntilDoesNotExist$default (Lde/mannodermaus/junit5/compose/ComposeContext;Landroidx/compose/ui/test/SemanticsMatcher;JILjava/lang/Object;)V
44+
public static synthetic fun waitUntilExactlyOneExists$default (Lde/mannodermaus/junit5/compose/ComposeContext;Landroidx/compose/ui/test/SemanticsMatcher;JILjava/lang/Object;)V
45+
public static synthetic fun waitUntilNodeCount$default (Lde/mannodermaus/junit5/compose/ComposeContext;Landroidx/compose/ui/test/SemanticsMatcher;IJILjava/lang/Object;)V
3246
}
3347

3448
public abstract interface class de/mannodermaus/junit5/compose/ComposeExtension : org/junit/jupiter/api/extension/Extension {

instrumentation/compose/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ dependencies {
8787
api(libs.composeUiTestJUnit4)
8888
implementation(libs.composeUiTestManifest)
8989

90+
testImplementation(libs.junitJupiterApi)
91+
testImplementation(libs.junitJupiterParams)
92+
testRuntimeOnly(libs.junitJupiterEngine)
93+
9094
androidTestImplementation(libs.junitJupiterApi)
9195
androidTestImplementation(libs.junitJupiterParams)
9296
androidTestImplementation(libs.espressoCore)
Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package de.mannodermaus.junit5.compose
22

3-
import androidx.activity.ComponentActivity
43
import androidx.compose.runtime.Composable
54
import androidx.compose.ui.test.ComposeUiTest
65
import androidx.compose.ui.test.ExperimentalTestApi
@@ -10,23 +9,41 @@ import androidx.compose.ui.test.SemanticsMatcher
109
import androidx.compose.ui.test.SemanticsNodeInteraction
1110
import androidx.compose.ui.test.SemanticsNodeInteractionCollection
1211
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
12+
import androidx.compose.ui.test.waitUntilAtLeastOneExists
13+
import androidx.compose.ui.test.waitUntilDoesNotExist
14+
import androidx.compose.ui.test.waitUntilExactlyOneExists
15+
import androidx.compose.ui.test.waitUntilNodeCount
1316
import androidx.compose.ui.unit.Density
14-
import androidx.test.core.app.ActivityScenario
1517

1618
/**
1719
* A context through which composable blocks can be orchestrated within a [ComposeExtension].
1820
*/
1921
public sealed interface ComposeContext : SemanticsNodeInteractionsProvider {
20-
// Internal note: The below method list is a copy of `ComposeUiTest`,
22+
// Internal note: The below method list is a copy of `ComposeContentTestRule`,
2123
// preventing the viral spread of its ExperimentalTestApi annotation
22-
// into the consumer's codebase
24+
// into the consumer's codebase and separating it from JUnit 4's TestRule
2325
public val density: Density
2426
public val mainClock: MainTestClock
2527
public fun <T> runOnUiThread(action: () -> T): T
2628
public fun <T> runOnIdle(action: () -> T): T
2729
public fun waitForIdle()
2830
public suspend fun awaitIdle()
29-
public fun waitUntil(timeoutMillis: Long, condition: () -> Boolean)
31+
public fun waitUntil(timeoutMillis: Long = 1_000L, condition: () -> Boolean)
32+
public fun waitUntil(
33+
conditionDescription: String,
34+
timeoutMillis: Long = 1_000L,
35+
condition: () -> Boolean
36+
)
37+
38+
public fun waitUntilNodeCount(
39+
matcher: SemanticsMatcher,
40+
count: Int,
41+
timeoutMillis: Long = 1_000L
42+
)
43+
44+
public fun waitUntilAtLeastOneExists(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
45+
public fun waitUntilExactlyOneExists(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
46+
public fun waitUntilDoesNotExist(matcher: SemanticsMatcher, timeoutMillis: Long = 1_000L)
3047
public fun registerIdlingResource(idlingResource: IdlingResource)
3148
public fun unregisterIdlingResource(idlingResource: IdlingResource)
3249
public fun setContent(composable: @Composable () -> Unit)
@@ -37,52 +54,56 @@ internal class ComposeContextImpl(
3754
private val delegate: ComposeUiTest
3855
) : ComposeContext, SemanticsNodeInteractionsProvider by delegate {
3956

40-
override fun onAllNodes(
41-
matcher: SemanticsMatcher,
42-
useUnmergedTree: Boolean
43-
): SemanticsNodeInteractionCollection {
44-
return delegate.onAllNodes(matcher, useUnmergedTree)
45-
}
46-
47-
override fun onNode(
48-
matcher: SemanticsMatcher,
49-
useUnmergedTree: Boolean
50-
): SemanticsNodeInteraction {
51-
return delegate.onNode(matcher, useUnmergedTree)
52-
}
53-
5457
override val density: Density get() = delegate.density
58+
5559
override val mainClock: MainTestClock get() = delegate.mainClock
5660

57-
override fun <T> runOnUiThread(action: () -> T): T {
58-
return delegate.runOnUiThread(action)
59-
}
61+
override fun <T> runOnUiThread(action: () -> T): T = delegate.runOnUiThread(action)
6062

61-
override fun <T> runOnIdle(action: () -> T): T {
62-
return delegate.runOnIdle(action)
63-
}
63+
override fun <T> runOnIdle(action: () -> T): T = delegate.runOnIdle(action)
6464

65-
override fun waitForIdle() {
66-
delegate.waitForIdle()
67-
}
65+
override fun waitForIdle() = delegate.waitForIdle()
6866

69-
override suspend fun awaitIdle() {
70-
delegate.awaitIdle()
71-
}
67+
override suspend fun awaitIdle() = delegate.awaitIdle()
68+
69+
override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) =
70+
delegate.waitUntil(conditionDescription = null, timeoutMillis, condition)
7271

73-
override fun waitUntil(timeoutMillis: Long, condition: () -> Boolean) {
74-
delegate.waitUntil(timeoutMillis, condition)
72+
override fun waitUntil(
73+
conditionDescription: String,
74+
timeoutMillis: Long,
75+
condition: () -> Boolean
76+
) {
77+
delegate.waitUntil(conditionDescription, timeoutMillis, condition)
7578
}
7679

77-
override fun registerIdlingResource(idlingResource: IdlingResource) {
80+
override fun waitUntilNodeCount(matcher: SemanticsMatcher, count: Int, timeoutMillis: Long) =
81+
delegate.waitUntilNodeCount(matcher, count, timeoutMillis)
82+
83+
override fun waitUntilAtLeastOneExists(matcher: SemanticsMatcher, timeoutMillis: Long) =
84+
delegate.waitUntilAtLeastOneExists(matcher, timeoutMillis)
85+
86+
override fun waitUntilExactlyOneExists(matcher: SemanticsMatcher, timeoutMillis: Long) =
87+
delegate.waitUntilExactlyOneExists(matcher, timeoutMillis)
88+
89+
override fun waitUntilDoesNotExist(matcher: SemanticsMatcher, timeoutMillis: Long) =
90+
delegate.waitUntilDoesNotExist(matcher, timeoutMillis)
91+
92+
override fun registerIdlingResource(idlingResource: IdlingResource) =
7893
delegate.registerIdlingResource(idlingResource)
79-
}
8094

81-
override fun unregisterIdlingResource(idlingResource: IdlingResource) {
95+
override fun unregisterIdlingResource(idlingResource: IdlingResource) =
8296
delegate.unregisterIdlingResource(idlingResource)
83-
}
8497

85-
override fun setContent(composable: @Composable () -> Unit) {
86-
delegate.setContent(composable)
87-
}
98+
override fun onNode(
99+
matcher: SemanticsMatcher,
100+
useUnmergedTree: Boolean
101+
): SemanticsNodeInteraction = delegate.onNode(matcher, useUnmergedTree)
102+
103+
override fun onAllNodes(
104+
matcher: SemanticsMatcher,
105+
useUnmergedTree: Boolean
106+
): SemanticsNodeInteractionCollection = delegate.onAllNodes(matcher, useUnmergedTree)
107+
108+
override fun setContent(composable: @Composable () -> Unit) = delegate.setContent(composable)
88109
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package de.mannodermaus.junit5.compose
2+
3+
import androidx.compose.ui.test.junit4.ComposeContentTestRule
4+
import androidx.compose.ui.test.junit4.ComposeTestRule
5+
import org.junit.jupiter.api.Assertions.fail
6+
import org.junit.jupiter.params.ParameterizedTest
7+
import org.junit.jupiter.params.provider.MethodSource
8+
import java.lang.reflect.Method
9+
10+
class ComposeContextTests {
11+
companion object {
12+
@JvmStatic
13+
fun relevantMethods() = buildList {
14+
addAll(ComposeTestRule::class.java.relevantMethods)
15+
addAll(ComposeContentTestRule::class.java.relevantMethods)
16+
}
17+
18+
private val <T> Class<T>.relevantMethods
19+
get() = declaredMethods.filter { '$' !in it.name }
20+
}
21+
22+
@MethodSource("relevantMethods")
23+
@ParameterizedTest(name = "ComposeContext defines {0} correctly")
24+
fun test(method: Method) {
25+
try {
26+
ComposeContext::class.java.getDeclaredMethod(method.name, *method.parameterTypes)
27+
} catch (ignored: NoSuchMethodException) {
28+
fail("ComposeContext does not define method $method")
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)