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