blob: 183c479da600402393a11df3baa900326edfa75c [file] [log] [blame]
/*
* Copyright 2020 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 androidx.compose.ui.test
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.util.BoundaryNode
import androidx.compose.ui.test.util.expectErrorMessageStartsWith
import androidx.compose.ui.test.util.obfuscateNodesInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@MediumTest
@RunWith(AndroidJUnit4::class)
class PrintToStringTest {
@get:Rule
val rule = createComposeRule()
@Test
fun printToString_nothingFound() {
rule.setContent {
ComposeSimpleCase()
}
expectErrorMessageStartsWith(
"Failed: assertExists.\n" +
"Reason: Expected exactly '1' node but could not find any node that satisfies:"
) {
rule.onNodeWithText("Oops").printToString()
}
}
@Test
fun printToString_one() {
rule.setContent {
ComposeSimpleCase()
}
val result = rule.onNodeWithText("Hello")
.printToString(maxDepth = 0)
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
Node #X at (l=X, t=X, r=X, b=X)px
Text = '[Hello]'
Actions = [GetTextLayoutResult]
Has 1 sibling
""".trimIndent()
)
}
@Test
fun printToString_many() {
rule.setContent {
ComposeSimpleCase()
}
val result = rule.onRoot()
.onChildren()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
1) Node #X at (l=X, t=X, r=X, b=X)px
Text = '[Hello]'
Actions = [GetTextLayoutResult]
Has 1 sibling
2) Node #X at (l=X, t=X, r=X, b=X)px
Text = '[World]'
Actions = [GetTextLayoutResult]
Has 1 sibling
""".trimIndent()
)
}
@Test
fun printHierarchy() {
rule.setContentWithoutMinimumTouchTarget {
Column(Modifier.semantics { this.disabled(); this.testTag = "column" }) {
Box(Modifier.semantics { this.disabled(); this.testTag = "box" }) {
Button(onClick = {}) {
Text("Button")
}
}
Text("Hello")
}
}
val result = rule.onRoot()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
Node #X at (l=X, t=X, r=X, b=X)px
|-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'column'
[Disabled]
|-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'box'
| [Disabled]
| |-Node #X at (l=X, t=X, r=X, b=X)px
| Role = 'Button'
| Focused = 'false'
| Text = '[Button]'
| Actions = [OnClick, GetTextLayoutResult]
| MergeDescendants = 'true'
|-Node #X at (l=X, t=X, r=X, b=X)px
Text = '[Hello]'
Actions = [GetTextLayoutResult]
""".trimIndent()
)
}
@Test
fun printMultiple_withDepth() {
rule.setContent {
BoundaryNode("tag1") {
BoundaryNode("tag11") {
BoundaryNode("tag111")
}
}
BoundaryNode("tag2") {
BoundaryNode("tag22") {
BoundaryNode("tag222")
}
}
}
val result = rule.onRoot()
.onChildren()
.printToString(maxDepth = 1)
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
1) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag1'
|-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag11'
Has 1 child
2) Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag2'
|-Node #X at (l=X, t=X, r=X, b=X)px, Tag: 'tag22'
Has 1 child
""".trimIndent()
)
}
@Test
fun printMergedContentDescriptions() {
rule.setContent {
Box(Modifier.semantics(mergeDescendants = true) { }) {
Box(Modifier.semantics { contentDescription = "first" })
Box(Modifier.semantics { contentDescription = "second" })
}
}
val result = rule.onRoot()
.onChild()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
Node #X at (l=X, t=X, r=X, b=X)px
ContentDescription = '[first, second]'
MergeDescendants = 'true'
""".trimIndent()
)
}
@Test
fun printUnmergedContentDescriptions() {
rule.setContent {
Box(Modifier.semantics(mergeDescendants = true) { }) {
Box(Modifier.semantics { contentDescription = "first" })
Box(Modifier.semantics { contentDescription = "second" })
}
}
val result = rule.onRoot(useUnmergedTree = true)
.onChild()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'true'
Node #X at (l=X, t=X, r=X, b=X)px
MergeDescendants = 'true'
|-Node #X at (l=X, t=X, r=X, b=X)px
| ContentDescription = '[first]'
|-Node #X at (l=X, t=X, r=X, b=X)px
ContentDescription = '[second]'
""".trimIndent()
)
}
@Test
fun printMergedText() {
rule.setContent {
Box(Modifier.semantics(mergeDescendants = true) { }) {
Text("first")
Text("second")
}
}
val result = rule.onRoot()
.onChild()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'false'
Node #X at (l=X, t=X, r=X, b=X)px
Text = '[first, second]'
Actions = [GetTextLayoutResult]
MergeDescendants = 'true'
""".trimIndent()
)
}
@Test
fun printUnmergedText() {
rule.setContent {
Box(Modifier.semantics(mergeDescendants = true) { }) {
Text("first")
Text("second")
}
}
val result = rule.onRoot(useUnmergedTree = true)
.onChild()
.printToString()
assertThat(obfuscateNodesInfo(result)).isEqualTo(
"""
Printing with useUnmergedTree = 'true'
Node #X at (l=X, t=X, r=X, b=X)px
MergeDescendants = 'true'
|-Node #X at (l=X, t=X, r=X, b=X)px
| Text = '[first]'
| Actions = [GetTextLayoutResult]
|-Node #X at (l=X, t=X, r=X, b=X)px
Text = '[second]'
Actions = [GetTextLayoutResult]
""".trimIndent()
)
}
@Composable
fun ComposeSimpleCase() {
MaterialTheme {
Column {
Text("Hello")
Text("World")
}
}
}
}