From 02a19110f72798459b368aaf297a6beaa10dbae9 Mon Sep 17 00:00:00 2001 From: Eli Hart Date: Tue, 16 Jul 2024 17:43:27 -0700 Subject: [PATCH 1/3] fix semantics modifiers --- buildSrc/src/main/java/Dependencies.kt | 4 +++- compose-tests/build.gradle.kts | 2 +- .../java/radiography/test/compose/ComposeTestRules.kt | 2 ++ .../src/main/java/radiography/ViewStateRenderers.kt | 5 ++--- .../src/main/java/radiography/internal/Semantics.kt | 7 +++---- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 3914266..07af9f3 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -6,7 +6,9 @@ object Versions { val KotlinCompiler = System.getProperty("square.kotlinVersion") ?: "1.9.10" const val AndroidXTest = "1.5.0" - const val Compose = "1.5.3" +// const val Compose = "1.5.3" +// const val Compose = "1.6.8" + const val Compose = "1.7.0-beta05" } object Dependencies { diff --git a/compose-tests/build.gradle.kts b/compose-tests/build.gradle.kts index b96d2d1..72a9a4f 100644 --- a/compose-tests/build.gradle.kts +++ b/compose-tests/build.gradle.kts @@ -25,7 +25,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = Versions.Compose + kotlinCompilerExtensionVersion = "1.5.3" } packaging { diff --git a/compose-tests/src/androidTest/java/radiography/test/compose/ComposeTestRules.kt b/compose-tests/src/androidTest/java/radiography/test/compose/ComposeTestRules.kt index fbcb737..7ea3c08 100644 --- a/compose-tests/src/androidTest/java/radiography/test/compose/ComposeTestRules.kt +++ b/compose-tests/src/androidTest/java/radiography/test/compose/ComposeTestRules.kt @@ -2,6 +2,7 @@ package radiography.test.compose import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable +import androidx.compose.runtime.currentComposer import androidx.compose.ui.test.junit4.ComposeContentTestRule /** @@ -14,6 +15,7 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule */ fun ComposeContentTestRule.setContentWithExplicitRoot(content: @Composable () -> Unit) { setContent { + currentComposer.collectParameterInformation() Box { content() } diff --git a/radiography/src/main/java/radiography/ViewStateRenderers.kt b/radiography/src/main/java/radiography/ViewStateRenderers.kt index 55d14fa..7aa6cc7 100644 --- a/radiography/src/main/java/radiography/ViewStateRenderers.kt +++ b/radiography/src/main/java/radiography/ViewStateRenderers.kt @@ -72,11 +72,10 @@ public object ViewStateRenderers { // Semantics composeView - .modifiers - .filterIsInstance() + .semanticsConfigurations // Technically there can be multiple semantic modifiers on a single node, so read them // all. - .flatMap { semantics -> semantics.semanticsConfiguration } + .flatten() .forEach { (key, value) -> when (key) { SemanticsProperties.TestTag -> appendLabeledValue("test-tag", value) diff --git a/radiography/src/main/java/radiography/internal/Semantics.kt b/radiography/src/main/java/radiography/internal/Semantics.kt index 149e963..8c7a614 100644 --- a/radiography/src/main/java/radiography/internal/Semantics.kt +++ b/radiography/src/main/java/radiography/internal/Semantics.kt @@ -8,12 +8,11 @@ import radiography.ExperimentalRadiographyComposeApi /** Returns all tag strings set on the composable via `Modifier.testTag`. */ @OptIn(ExperimentalRadiographyComposeApi::class) internal fun ComposeView.findTestTags(): Sequence { - return modifiers + return semanticsConfigurations .asSequence() - .filterIsInstance() +// .filterIsInstance() .flatMap { semantics -> - semantics.semanticsConfiguration.asSequence() - .filter { it.key == SemanticsProperties.TestTag } + semantics.filter { it.key == SemanticsProperties.TestTag } } .mapNotNull { it.value as? String } } From 7594ff04e23ef020471900aae0b764e9b14fb385 Mon Sep 17 00:00:00 2001 From: Eli Hart Date: Wed, 17 Jul 2024 11:59:46 -0700 Subject: [PATCH 2/3] fix rest of tests --- buildSrc/src/main/java/Dependencies.kt | 1 + compose-tests/build.gradle.kts | 2 +- .../radiography/test/compose/ComposeUiTest.kt | 57 ++++++++++++------- compose-unsupported-tests/build.gradle.kts | 2 +- .../ExperimentalRadiographyComposeApi.kt | 1 + .../main/java/radiography/ScannableView.kt | 18 ++++++ .../java/radiography/ViewStateRenderers.kt | 1 - .../java/radiography/internal/Semantics.kt | 2 - sample-compose/build.gradle.kts | 5 +- .../sample/compose/MainActivity.kt | 2 + 10 files changed, 65 insertions(+), 26 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 07af9f3..c5b5d37 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -9,6 +9,7 @@ object Versions { // const val Compose = "1.5.3" // const val Compose = "1.6.8" const val Compose = "1.7.0-beta05" + const val ComposeCompiler = "1.5.3" } object Dependencies { diff --git a/compose-tests/build.gradle.kts b/compose-tests/build.gradle.kts index 72a9a4f..ea73fb7 100644 --- a/compose-tests/build.gradle.kts +++ b/compose-tests/build.gradle.kts @@ -25,7 +25,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = "1.5.3" + kotlinCompilerExtensionVersion = Versions.ComposeCompiler } packaging { diff --git a/compose-tests/src/androidTest/java/radiography/test/compose/ComposeUiTest.kt b/compose-tests/src/androidTest/java/radiography/test/compose/ComposeUiTest.kt index b2a60a0..50d21c4 100644 --- a/compose-tests/src/androidTest/java/radiography/test/compose/ComposeUiTest.kt +++ b/compose-tests/src/androidTest/java/radiography/test/compose/ComposeUiTest.kt @@ -14,6 +14,7 @@ import androidx.compose.material.Button import androidx.compose.material.Checkbox import androidx.compose.material.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.currentComposer import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.layout.SubcomposeLayout @@ -380,6 +381,8 @@ class ComposeUiTest { @Test fun scanningHandlesDialog() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { Dialog(onDismissRequest = {}) { Box(Modifier.testTag("child")) @@ -393,8 +396,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─Dialog |${BLANK} ╰─CompositionLocalProvider { DIALOG } |${BLANK} ╰─Box { test-tag:"child" } @@ -409,6 +412,8 @@ class ComposeUiTest { } composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { CustomTestDialog { Box(Modifier.testTag("child")) @@ -422,8 +427,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─CustomTestDialog |${BLANK} ╰─CompositionLocalProvider { DIALOG } |${BLANK} ╰─Box { test-tag:"child" } @@ -434,6 +439,8 @@ class ComposeUiTest { @Test fun scanningHandlesSingleSubcomposeLayout_withSingleChild() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { SingleSubcompositionLayout(Modifier.testTag("subcompose-layout")) { Box(Modifier.testTag("child")) @@ -447,8 +454,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─SingleSubcompositionLayout { test-tag:"subcompose-layout" } |${BLANK} ╰─ |${BLANK} ╰─Box { test-tag:"child" } @@ -459,6 +466,8 @@ class ComposeUiTest { @Test fun scanningHandlesSingleSubcomposeLayout_withMultipleChildren() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { SingleSubcompositionLayout(Modifier.testTag("subcompose-layout")) { Box(Modifier.testTag("child1")) @@ -473,8 +482,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─SingleSubcompositionLayout { test-tag:"subcompose-layout" } |${BLANK} ╰─ |${BLANK} ├─Box { test-tag:"child1" } @@ -486,6 +495,8 @@ class ComposeUiTest { @Test fun scanningHandlesSingleSubcomposeLayout_withMultipleSubcompositionsAndChildren() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { MultipleSubcompositionLayout(Modifier.testTag("subcompose-layout"), firstChildren = { @@ -505,8 +516,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─MultipleSubcompositionLayout { test-tag:"subcompose-layout" } |${BLANK} ├─ |${BLANK} │ ├─Box { test-tag:"child1.1" } @@ -521,6 +532,8 @@ class ComposeUiTest { @Test fun scanningHandlesSiblingSubcomposeLayouts() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { SingleSubcompositionLayout(Modifier.testTag("subcompose-layout1")) { Box(Modifier.testTag("child1")) @@ -537,8 +550,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}├─SingleSubcompositionLayout { test-tag:"subcompose-layout1" } |${BLANK}│ ╰─ |${BLANK}│ ╰─Box { test-tag:"child1" } @@ -552,6 +565,8 @@ class ComposeUiTest { @Test fun scanningHandlesWithConstraints() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { BoxWithConstraints(Modifier.testTag("with-constraints")) { Box(Modifier.testTag("child")) @@ -565,8 +580,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } + |Box: + |${BLANK}Box { test-tag:"parent" } |${BLANK}╰─BoxWithConstraints { test-tag:"with-constraints" } |${BLANK} ╰─ |${BLANK} ╰─Box { test-tag:"child" } @@ -577,6 +592,8 @@ class ComposeUiTest { @Test fun scanningHandlesLazyLists() { composeRule.setContent { + currentComposer.collectParameterInformation() + Box(Modifier.testTag("parent")) { LazyColumn(Modifier.testTag("list")) { items(listOf(1, 2, 3)) { @@ -595,9 +612,9 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { test-tag:"parent" } - |${BLANK}╰─LazyColumn { test-tag:"list", vertical-scroll-axis-range:"ScrollAxisRange(value=0.0, maxValue=0.0)" } + |Box: + |${BLANK}Box { test-tag:"parent" } + |${BLANK}╰─LazyColumn { vertical-scroll-axis-range:"ScrollAxisRange(value=0.0, maxValue=0.0)", test-tag:"list" } |${BLANK} ├─ |${BLANK} │ ╰─SkippableItem { test-tag:"child:1" } |${BLANK} ├─ @@ -612,6 +629,8 @@ class ComposeUiTest { @Test fun scanningSubcomposition_includesSize() { composeRule.setContent { + currentComposer.collectParameterInformation() + // Convert 10 px to DP, since output is always in px. val sizeDp = with(LocalDensity.current) { 10.toDp() } @@ -646,8 +665,8 @@ class ComposeUiTest { assertThat(hierarchy).isEqualTo( """ - |CompositionLocalProvider: - |${BLANK}CompositionLocalProvider { 10×30px, test-tag:"parent" } + |Box: + |${BLANK}Box { 10×30px, test-tag:"parent" } |${BLANK}╰─MultipleSubcompositionLayout { 10×30px, test-tag:"subcompose-layout" } |${BLANK} ├─ |${BLANK} │ ├─Box { 10×10px, test-tag:"child1" } diff --git a/compose-unsupported-tests/build.gradle.kts b/compose-unsupported-tests/build.gradle.kts index b1fa86b..797061e 100644 --- a/compose-unsupported-tests/build.gradle.kts +++ b/compose-unsupported-tests/build.gradle.kts @@ -31,7 +31,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = Versions.Compose + kotlinCompilerExtensionVersion = Versions.ComposeCompiler } packaging { diff --git a/radiography/src/main/java/radiography/ExperimentalRadiographyComposeApi.kt b/radiography/src/main/java/radiography/ExperimentalRadiographyComposeApi.kt index 5c19c53..e45152d 100644 --- a/radiography/src/main/java/radiography/ExperimentalRadiographyComposeApi.kt +++ b/radiography/src/main/java/radiography/ExperimentalRadiographyComposeApi.kt @@ -4,4 +4,5 @@ package radiography message = "This API is experimental, may only work with a specific version of Compose, " + "and may change or break at any time. Use with caution." ) +@Retention(AnnotationRetention.BINARY) public annotation class ExperimentalRadiographyComposeApi diff --git a/radiography/src/main/java/radiography/ScannableView.kt b/radiography/src/main/java/radiography/ScannableView.kt index 44593c6..06be7ef 100644 --- a/radiography/src/main/java/radiography/ScannableView.kt +++ b/radiography/src/main/java/radiography/ScannableView.kt @@ -3,6 +3,8 @@ package radiography import android.view.View import android.view.ViewGroup import androidx.compose.ui.Modifier +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.SemanticsModifierNode import androidx.compose.ui.semantics.SemanticsConfiguration import androidx.compose.ui.semantics.SemanticsModifier import androidx.compose.ui.semantics.SemanticsNode @@ -68,9 +70,25 @@ public sealed class ScannableView { public val semanticsConfigurations: List get() { return semanticsNodes.map { it.config }.ifEmpty { null } + // Before Compose 1.6, semantics could be held in a SemanticsModifier ?: modifiers .filterIsInstance() .map { it.semanticsConfiguration } + .ifEmpty { null } + // After Compose 1.6, semantics are held in nodes. While this usually gets represented + // by semanticsNodes, sometimes they instead are only found in SemanticsModifierNode, + // which we must then extract. + ?: modifiers + .filterIsInstance>() + .map { it.create() } + .filterIsInstance() + .map { semanticsModifierNode -> + semanticsModifierNode.run { + val semanticsConfiguration = SemanticsConfiguration() + semanticsConfiguration.applySemantics() + semanticsConfiguration + } + } } } diff --git a/radiography/src/main/java/radiography/ViewStateRenderers.kt b/radiography/src/main/java/radiography/ViewStateRenderers.kt index 7aa6cc7..ecfb5e6 100644 --- a/radiography/src/main/java/radiography/ViewStateRenderers.kt +++ b/radiography/src/main/java/radiography/ViewStateRenderers.kt @@ -8,7 +8,6 @@ import android.widget.TextView import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.layout.LayoutIdParentData import androidx.compose.ui.semantics.ScrollAxisRange -import androidx.compose.ui.semantics.SemanticsModifier import androidx.compose.ui.semantics.SemanticsProperties import androidx.compose.ui.semantics.SemanticsProperties.EditableText import androidx.compose.ui.semantics.SemanticsProperties.Text diff --git a/radiography/src/main/java/radiography/internal/Semantics.kt b/radiography/src/main/java/radiography/internal/Semantics.kt index 8c7a614..59032f7 100644 --- a/radiography/src/main/java/radiography/internal/Semantics.kt +++ b/radiography/src/main/java/radiography/internal/Semantics.kt @@ -1,6 +1,5 @@ package radiography.internal -import androidx.compose.ui.semantics.SemanticsModifier import androidx.compose.ui.semantics.SemanticsProperties import radiography.ScannableView.ComposeView import radiography.ExperimentalRadiographyComposeApi @@ -10,7 +9,6 @@ import radiography.ExperimentalRadiographyComposeApi internal fun ComposeView.findTestTags(): Sequence { return semanticsConfigurations .asSequence() -// .filterIsInstance() .flatMap { semantics -> semantics.filter { it.key == SemanticsProperties.TestTag } } diff --git a/sample-compose/build.gradle.kts b/sample-compose/build.gradle.kts index 6e554ff..97eb181 100644 --- a/sample-compose/build.gradle.kts +++ b/sample-compose/build.gradle.kts @@ -6,7 +6,8 @@ plugins { } /** Use a separate property for the sample so we can test with different versions easily. */ -val sampleComposeVersion = "1.5.3" +val sampleComposeVersion = "1.6.8" +val sampleComposeCompilerVersion = "1.5.3" android { compileSdk = 34 @@ -28,7 +29,7 @@ android { } composeOptions { - kotlinCompilerExtensionVersion = sampleComposeVersion + kotlinCompilerExtensionVersion = sampleComposeCompilerVersion } packaging { diff --git a/sample-compose/src/main/java/com/squareup/radiography/sample/compose/MainActivity.kt b/sample-compose/src/main/java/com/squareup/radiography/sample/compose/MainActivity.kt index 07ed27e..64845f2 100644 --- a/sample-compose/src/main/java/com/squareup/radiography/sample/compose/MainActivity.kt +++ b/sample-compose/src/main/java/com/squareup/radiography/sample/compose/MainActivity.kt @@ -3,11 +3,13 @@ package com.squareup.radiography.sample.compose import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.currentComposer class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { + currentComposer.collectParameterInformation() ComposeSampleApp() } } From 7cd79be6be2e953de07a07685ccf55648d347983 Mon Sep 17 00:00:00 2001 From: Eli Hart Date: Wed, 17 Jul 2024 12:07:29 -0700 Subject: [PATCH 3/3] clean up --- buildSrc/src/main/java/Dependencies.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index c5b5d37..377d447 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -6,8 +6,6 @@ object Versions { val KotlinCompiler = System.getProperty("square.kotlinVersion") ?: "1.9.10" const val AndroidXTest = "1.5.0" -// const val Compose = "1.5.3" -// const val Compose = "1.6.8" const val Compose = "1.7.0-beta05" const val ComposeCompiler = "1.5.3" }