1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.ui.test
18 
19 import androidx.compose.ui.semantics.SemanticsNode
20 
21 /**
22  * Builds error message for case where expected amount of nodes does not match reality.
23  *
24  * Provide [errorMessage] to explain which operation you were about to perform. This makes it easier
25  * for developer to find where the failure happened.
26  *
27  * In case of only one node that went missing (was seen before) use
28  * [buildErrorMessageForNodeMissingInTree] for better clarity.
29  *
30  * To see some examples, check out "ErrorMessagesTest".
31  */
buildErrorMessageForCountMismatchnull32 internal fun buildErrorMessageForCountMismatch(
33     errorMessage: String,
34     selector: SemanticsSelector?,
35     foundNodes: List<SemanticsNode>,
36     expectedCount: Int,
37     foundNodesUnmerged: List<SemanticsNode> = emptyList()
38 ): String {
39     val sb = StringBuilder()
40 
41     sb.append(errorMessage)
42     sb.append("\n")
43 
44     sb.append("Reason: ")
45     if (expectedCount == 0) {
46         sb.append("Did not expect any node")
47     } else if (expectedCount == 1) {
48         sb.append("Expected exactly '1' node")
49     } else {
50         sb.append("Expected '$expectedCount' nodes")
51     }
52 
53     if (foundNodes.isEmpty()) {
54         sb.append(" but could not find any")
55     } else {
56         sb.append(" but found '${foundNodes.size}'")
57     }
58 
59     if (selector != null) {
60         if (foundNodes.size <= 1) {
61             sb.append(" node that satisfies: (${selector.description})")
62         } else {
63             sb.append(" nodes that satisfy: (${selector.description})")
64         }
65     } else {
66         sb.append(".")
67     }
68 
69     // If no nodes were found but they exist in the unmerged tree, display a warning.
70     if (foundNodes.isEmpty() && foundNodesUnmerged.isNotEmpty()) {
71         sb.appendLine()
72         sb.append("However, the unmerged tree contains ")
73         if (foundNodesUnmerged.size == 1) {
74             sb.append("'1' node that matches. ")
75         } else {
76             sb.append("'${foundNodesUnmerged.size}' nodes that match. ")
77         }
78         sb.append("Are you missing `useUnmergedNode = true` in your finder?")
79     }
80 
81     sb.appendLine()
82 
83     if (foundNodes.isNotEmpty()) {
84         if (foundNodes.size == 1) {
85             sb.appendLine("Node found:")
86         } else {
87             sb.appendLine("Nodes found:")
88         }
89         sb.appendLine(foundNodes.printToString())
90     }
91 
92     return sb.toString()
93 }
94 
95 /**
96  * Builds error message for case where node is no longer in the tree but is expected to be.
97  *
98  * Provide [errorMessage] to explain which operation you were about to perform. This makes it easier
99  * for developer to find where the failure happened.
100  *
101  * Note that [lastSeenSemantics] is the last semantics we have seen before we couldn't find the node
102  * anymore. This can provide more info to the developer on what could have happened.
103  *
104  * To see some examples, check out "ErrorMessagesTest".
105  */
buildErrorMessageForNodeMissingInTreenull106 internal fun buildErrorMessageForNodeMissingInTree(
107     errorMessage: String,
108     selector: SemanticsSelector,
109     lastSeenSemantics: String
110 ): String {
111     val sb = StringBuilder()
112 
113     sb.append(errorMessage)
114     sb.append("\n")
115 
116     sb.appendLine("The node is no longer in the tree, last known semantics:")
117     sb.appendLine(lastSeenSemantics)
118     sb.append("Original selector: ")
119     sb.appendLine(selector.description)
120 
121     return sb.toString()
122 }
123 
buildErrorMessageForAssertAnyFailnull124 internal fun buildErrorMessageForAssertAnyFail(
125     selector: SemanticsSelector,
126     nodes: List<SemanticsNode>,
127     assertionMatcher: SemanticsMatcher
128 ): String {
129     val sb = StringBuilder()
130 
131     sb.appendLine("Failed to assertAny(${assertionMatcher.description})")
132 
133     sb.appendLine("None of the following nodes match:")
134     sb.appendLine(nodes.printToString())
135 
136     sb.append("Selector used: '")
137     sb.append(selector.description)
138     sb.appendLine("'")
139 
140     return sb.toString()
141 }
142 
buildErrorMessageForAssertAllFailnull143 internal fun buildErrorMessageForAssertAllFail(
144     selector: SemanticsSelector,
145     nodesNotMatching: List<SemanticsNode>,
146     assertionMatcher: SemanticsMatcher
147 ): String {
148     val sb = StringBuilder()
149 
150     sb.appendLine("Failed to assertAll(${assertionMatcher.description})")
151 
152     sb.append("Found '${nodesNotMatching.size}' ")
153     sb.append(if (nodesNotMatching.size == 1) "node" else "nodes")
154     sb.appendLine(" not matching:")
155     sb.appendLine(nodesNotMatching.printToString())
156 
157     sb.append("Selector used: '")
158     sb.append(selector.description)
159     sb.appendLine("'")
160 
161     return sb.toString()
162 }
163 
buildErrorMessageForAtLeastOneNodeExpectednull164 internal fun buildErrorMessageForAtLeastOneNodeExpected(
165     errorMessage: String,
166     selector: SemanticsSelector
167 ): String {
168     val sb = StringBuilder()
169 
170     sb.appendLine(errorMessage)
171 
172     sb.append("Assert needs to receive at least 1 node but 0 nodes were found for selector: ")
173     sb.append("'")
174     sb.append(selector.description)
175     sb.appendLine("'")
176 
177     return sb.toString()
178 }
179 
buildGeneralErrorMessagenull180 internal fun buildGeneralErrorMessage(
181     errorMessage: String,
182     selector: SemanticsSelector,
183     node: SemanticsNode
184 ): String {
185     val sb = StringBuilder()
186 
187     sb.appendLine(errorMessage)
188 
189     sb.appendLine("Semantics of the node:")
190     sb.appendLine(node.printToString())
191 
192     sb.append("Selector used: (")
193     sb.append(selector.description)
194     sb.appendLine(")")
195 
196     return sb.toString()
197 }
198 
buildIndexErrorMessagenull199 internal fun buildIndexErrorMessage(
200     index: Int,
201     selector: SemanticsSelector,
202     nodes: List<SemanticsNode>
203 ): String {
204     val sb = StringBuilder()
205 
206     sb.append("Can't retrieve node at index '$index' of '")
207     sb.append(selector.description)
208     sb.appendLine("'")
209 
210     if (nodes.isEmpty()) {
211         sb.appendLine("There are no existing nodes for that selector.")
212     } else if (nodes.size == 1) {
213         sb.appendLine("There is 1 node only:")
214         sb.appendLine(nodes.printToString())
215     } else {
216         sb.appendLine("There are '${nodes.size}' nodes only:")
217         sb.appendLine(nodes.printToString())
218     }
219 
220     return sb.toString()
221 }
222