diff options
| author | 2024-10-17 09:07:02 +0000 | |
|---|---|---|
| committer | 2024-10-17 09:07:02 +0000 | |
| commit | 0144760b3c8ce65ac36fca2fdcc878c04d6134a7 (patch) | |
| tree | 722efb05d656b861addc266461e88320f9155024 | |
| parent | 3b08060bda0d3641230d851c0089d7e7fd0a46d6 (diff) | |
| parent | 4dfd56f81043c6eec75a821f104a240244e9e4f9 (diff) | |
Merge "Introduce more complex ways to match elements" into main
6 files changed, 107 insertions, 16 deletions
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt index ca68c256fd73..772872719ebe 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt @@ -22,19 +22,52 @@ interface ElementMatcher { fun matches(key: ElementKey, content: ContentKey): Boolean } +/** Returns an [ElementMatcher] that matches any element in [content]. */ +fun inContent(content: ContentKey): ElementMatcher { + val matcherContent = content + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return content == matcherContent + } + } +} + +/** Returns an [ElementMatcher] that matches all elements not matching [this] matcher. */ +operator fun ElementMatcher.not(): ElementMatcher { + val delegate = this + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return !delegate.matches(key, content) + } + } +} + +/** + * Returns an [ElementMatcher] that matches all elements matching both [this] matcher and [other]. + */ +infix fun ElementMatcher.and(other: ElementMatcher): ElementMatcher { + val delegate = this + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return delegate.matches(key, content) && other.matches(key, content) + } + } +} + /** - * Returns an [ElementMatcher] that matches elements in [content] also matching [this] - * [ElementMatcher]. + * Returns an [ElementMatcher] that matches all elements either [this] matcher, or [other], or both. */ -fun ElementMatcher.inContent(content: ContentKey): ElementMatcher { +infix fun ElementMatcher.or(other: ElementMatcher): ElementMatcher { val delegate = this - val matcherScene = content return object : ElementMatcher { override fun matches(key: ElementKey, content: ContentKey): Boolean { - return content == matcherScene && delegate.matches(key, content) + return delegate.matches(key, content) || other.matches(key, content) } } } -@Deprecated("Use inContent() instead", replaceWith = ReplaceWith("inContent(scene)")) -fun ElementMatcher.inScene(scene: SceneKey) = inContent(scene) +@Deprecated( + "Use `this and inContent()` instead", + replaceWith = ReplaceWith("this and inContent(scene)"), +) +fun ElementMatcher.inScene(scene: SceneKey) = this and inContent(scene) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt new file mode 100644 index 000000000000..af0962361fb2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +package com.android.compose.animation.scene + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestElements.Bar +import com.android.compose.animation.scene.TestElements.Foo +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ElementMatcherTest { + @Test + fun and() { + val matcher = Foo and inContent(SceneA) + assertThat(matcher.matches(Foo, SceneA)).isTrue() + assertThat(matcher.matches(Foo, SceneB)).isFalse() + assertThat(matcher.matches(Bar, SceneA)).isFalse() + assertThat(matcher.matches(Bar, SceneB)).isFalse() + } + + @Test + fun or() { + val matcher = Foo or inContent(SceneA) + assertThat(matcher.matches(Foo, SceneA)).isTrue() + assertThat(matcher.matches(Foo, SceneB)).isTrue() + assertThat(matcher.matches(Bar, SceneA)).isTrue() + assertThat(matcher.matches(Bar, SceneB)).isFalse() + } + + @Test + fun not() { + val matcher = !Foo + assertThat(matcher.matches(Foo, SceneA)).isFalse() + assertThat(matcher.matches(Foo, SceneB)).isFalse() + assertThat(matcher.matches(Bar, SceneA)).isTrue() + assertThat(matcher.matches(Bar, SceneB)).isTrue() + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 39d46990dc4b..ee807e6a7ede 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -2221,8 +2221,8 @@ class ElementTest { // In A => B, Foo is not shared and first fades out from A then fades in // B. sharedElement(TestElements.Foo, enabled = false) - fractionRange(end = 0.5f) { fade(TestElements.Foo.inContent(SceneA)) } - fractionRange(start = 0.5f) { fade(TestElements.Foo.inContent(SceneB)) } + fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) } + fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) } } from(SceneB, to = SceneA) { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt index cae6617cb11b..7ea414d6b8cd 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertPositionInRootIsEqualTo @@ -205,7 +206,8 @@ class OverlayTest { val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB)) val elementChildTag = "elementChildTag" - fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content) + fun elementChild(content: ContentKey) = + hasTestTag(elementChildTag) and SemanticsMatcher.inContent(content) @Composable fun ContentScope.MovableBar() { @@ -773,7 +775,7 @@ class OverlayTest { // Overscroll on Overlay A. scope.launch { state.startTransition(transition(SceneA, OverlayA, progress = { 1.5f })) } rule - .onNode(hasTestTag(movableElementChildTag) and inContent(SceneA)) + .onNode(hasTestTag(movableElementChildTag) and SemanticsMatcher.inContent(SceneA)) .assertPositionInRootIsEqualTo(0.dp, 0.dp) .assertSizeIsEqualTo(100.dp) .assertIsDisplayed() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 4877cd610875..2e3a934c2701 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -31,7 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TestElements import com.android.compose.animation.scene.TestScenes -import com.android.compose.animation.scene.inContent +import com.android.compose.animation.scene.inScene import com.android.compose.animation.scene.testTransition import com.android.compose.test.assertSizeIsEqualTo import org.junit.Rule @@ -125,10 +125,10 @@ class SharedElementTest { sharedElement(TestElements.Foo, enabled = false) // In SceneA, Foo leaves to the left edge. - translate(TestElements.Foo.inContent(TestScenes.SceneA), Edge.Left) + translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left) // In SceneB, Foo comes from the bottom edge. - translate(TestElements.Foo.inContent(TestScenes.SceneB), Edge.Bottom) + translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom) }, ) { before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) } diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt index 22450d32ea62..b3201d0daffe 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt @@ -25,11 +25,11 @@ fun isElement(element: ElementKey, content: ContentKey? = null): SemanticsMatche return if (content == null) { hasTestTag(element.testTag) } else { - hasTestTag(element.testTag) and inContent(content) + hasTestTag(element.testTag) and SemanticsMatcher.inContent(content) } } /** A [SemanticsMatcher] that matches anything inside [content]. */ -fun inContent(content: ContentKey): SemanticsMatcher { +fun SemanticsMatcher.Companion.inContent(content: ContentKey): SemanticsMatcher { return hasAnyAncestor(hasTestTag(content.testTag)) } |