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 21 /** 22 * Represents a collection of Glance nodes from the tree that can be asserted on. 23 * 24 * An instance of [GlanceNodeAssertionCollection] can be obtained from 25 * [GlanceNodeAssertionsProvider.onAllNodes] and equivalent methods. 26 */ 27 // Equivalent to SemanticsNodeInteractionCollection in compose. 28 class GlanceNodeAssertionCollection<R, T : GlanceNode<R>> 29 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 30 constructor( 31 private val testContext: TestContext<R, T>, 32 private val selector: GlanceNodeSelector<R> 33 ) { 34 /** 35 * Asserts that this collection of nodes is equal to the given [expectedCount]. 36 * 37 * @throws AssertionError if the size is not equal to [expectedCount] 38 */ assertCountEqualsnull39 fun assertCountEquals(expectedCount: Int): GlanceNodeAssertionCollection<R, T> { 40 val errorMessageOnFail = "Failed to assert count of nodes" 41 42 val actualCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size 43 if (actualCount != expectedCount) { 44 throw AssertionError( 45 buildErrorMessageWithReason( 46 errorMessageOnFail = errorMessageOnFail, 47 reason = 48 buildErrorReasonForCountMismatch( 49 matcherDescription = selector.description, 50 expectedCount = expectedCount, 51 actualCount = actualCount 52 ) 53 ) 54 ) 55 } 56 return this 57 } 58 59 /** 60 * Asserts that all the nodes in this collection satisfy the given [matcher]. 61 * 62 * Doesn't throw error if the collection is empty. Use [assertCountEquals] to assert on expected 63 * size of the collection. 64 * 65 * @param matcher Matcher that has to be satisfied by all the nodes in the collection. 66 * @throws AssertionError if the collection contains at least one element that does not satisfy 67 * the given matcher. 68 */ assertAllnull69 fun assertAll( 70 matcher: GlanceNodeMatcher<R>, 71 ): GlanceNodeAssertionCollection<R, T> { 72 val errorMessageOnFail = "Failed to assertAll(${matcher.description})" 73 74 val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail) 75 val violations = filteredNodes.filter { !matcher.matches(it) } 76 if (violations.isNotEmpty()) { 77 throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, violations)) 78 } 79 return this 80 } 81 82 /** 83 * Asserts that this collection contains at least one element that satisfies the given 84 * [matcher]. 85 * 86 * @param matcher Matcher that has to be satisfied by at least one of the nodes in the 87 * collection. 88 * @throws AssertionError if not at least one matching node was found. 89 */ assertAnynull90 fun assertAny( 91 matcher: GlanceNodeMatcher<R>, 92 ): GlanceNodeAssertionCollection<R, T> { 93 val errorMessageOnFail = "Failed to assertAny(${matcher.description})" 94 val filteredNodes = testContext.findMatchingNodes(selector, errorMessageOnFail) 95 96 if (filteredNodes.isEmpty()) { 97 throw AssertionError( 98 buildErrorMessageWithReason( 99 errorMessageOnFail = errorMessageOnFail, 100 reason = buildErrorReasonForAtLeastOneNodeExpected(selector.description) 101 ) 102 ) 103 } 104 105 if (!matcher.matchesAny(filteredNodes)) { 106 throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, filteredNodes)) 107 } 108 return this 109 } 110 111 /** 112 * Returns a [GlanceNodeAssertion] that can assert on the node at the given index of this 113 * collection. 114 * 115 * Any subsequent assertion on its result will throw error if index is out of bounds of the 116 * matching nodes found from previous operations. 117 */ getnull118 operator fun get(index: Int): GlanceNodeAssertion<R, T> { 119 return GlanceNodeAssertion( 120 testContext = testContext, 121 selector = selector.addIndexedSelector(index) 122 ) 123 } 124 125 /** 126 * Returns a new collection of nodes by filtering the given nodes using the provided [matcher]. 127 */ filternull128 fun filter(matcher: GlanceNodeMatcher<R>): GlanceNodeAssertionCollection<R, T> { 129 return GlanceNodeAssertionCollection( 130 testContext, 131 selector.addMatcherSelector(selectorName = "filter", matcher = matcher) 132 ) 133 } 134 } 135