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