1 /*
2  * Copyright 2023 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.glance.testing
18 
19 import androidx.annotation.RestrictTo
20 import androidx.annotation.RestrictTo.Scope
21 
22 /**
23  * Represents a Glance node from the tree that can be asserted on.
24  *
25  * An instance of [GlanceNodeAssertion] can be obtained from `onNode` and equivalent methods on a
26  * [GlanceNodeAssertionsProvider]
27  */
28 class GlanceNodeAssertion<R, T : GlanceNode<R>>
29 @RestrictTo(Scope.LIBRARY_GROUP)
30 constructor(
31     private val testContext: TestContext<R, T>,
32     private val selector: GlanceNodeSelector<R>,
33 ) {
34     /**
35      * Asserts that the node was found.
36      *
37      * @throws [AssertionError] if the assert fails.
38      */
assertExistsnull39     fun assertExists(): GlanceNodeAssertion<R, T> {
40         findSingleMatchingNode(errorMessageOnFail = "Failed assertExists")
41         return this
42     }
43 
44     /**
45      * Asserts that no matching node was found.
46      *
47      * @throws [AssertionError] if the assert fails.
48      */
assertDoesNotExistnull49     fun assertDoesNotExist(): GlanceNodeAssertion<R, T> {
50         val errorMessageOnFail = "Failed assertDoesNotExist"
51         val matchedNodesCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
52         if (matchedNodesCount != 0) {
53             throw AssertionError(
54                 buildErrorMessageWithReason(
55                     errorMessageOnFail = errorMessageOnFail,
56                     reason =
57                         buildErrorReasonForCountMismatch(
58                             matcherDescription = selector.description,
59                             expectedCount = 0,
60                             actualCount = matchedNodesCount
61                         )
62                 )
63             )
64         }
65         return this
66     }
67 
68     /**
69      * Asserts that the provided [matcher] is satisfied for this node.
70      *
71      * <p> This function also can be used to create convenience "assert{somethingConcrete}" methods
72      * as extension functions on the GlanceNodeAssertion.
73      *
74      * @param matcher Matcher to verify.
75      * @param messagePrefixOnError Prefix to be put in front of an error that gets thrown in case
76      *   this assert fails. This can be helpful in situations where this assert fails as part of a
77      *   bigger operation that used this assert as a precondition check.
78      * @throws AssertionError if the matcher does not match or the node can no longer be found.
79      */
assertnull80     fun assert(
81         matcher: GlanceNodeMatcher<R>,
82         messagePrefixOnError: (() -> String)? = null
83     ): GlanceNodeAssertion<R, T> {
84         var errorMessageOnFail = "Failed to assert condition: (${matcher.description})"
85         if (messagePrefixOnError != null) {
86             errorMessageOnFail = messagePrefixOnError() + "\n" + errorMessageOnFail
87         }
88         val glanceNode = findSingleMatchingNode(errorMessageOnFail)
89 
90         if (!matcher.matches(glanceNode)) {
91             throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, glanceNode))
92         }
93         return this
94     }
95 
96     /**
97      * Returns [GlanceNodeAssertionCollection] that allows performing assertions on the children of
98      * the node selected by this [GlanceNodeAssertion].
99      */
onChildrennull100     fun onChildren(): GlanceNodeAssertionCollection<R, T> {
101         return GlanceNodeAssertionCollection(testContext, selector.addChildrenSelector())
102     }
103 
findSingleMatchingNodenull104     private fun findSingleMatchingNode(errorMessageOnFail: String): GlanceNode<R> {
105         val matchingNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
106         if (matchingNodes.size != 1) {
107             throw AssertionError(
108                 buildErrorMessageWithReason(
109                     errorMessageOnFail = errorMessageOnFail,
110                     reason =
111                         buildErrorReasonForCountMismatch(
112                             matcherDescription = selector.description,
113                             expectedCount = 1,
114                             actualCount = matchingNodes.size
115                         )
116                 )
117             )
118         }
119         return matchingNodes.single()
120     }
121 }
122