GlanceNodeAssertion.kt
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.glance.testing
import androidx.annotation.RestrictTo
import androidx.annotation.RestrictTo.Scope
/**
* Represents a Glance node from the tree that can be asserted on.
*
* An instance of [GlanceNodeAssertion] can be obtained from `onNode` and equivalent methods
* on a [GlanceNodeAssertionsProvider]
*/
class GlanceNodeAssertion<R, T : GlanceNode<R>> @RestrictTo(Scope.LIBRARY_GROUP) constructor(
private val testContext: TestContext<R, T>,
private val selector: GlanceNodeSelector<R>,
) {
/**
* Asserts that the node was found.
* @throws [AssertionError] if the assert fails.
*/
fun assertExists(): GlanceNodeAssertion<R, T> {
findSingleMatchingNode(errorMessageOnFail = "Failed assertExists")
return this
}
/**
* Asserts that no matching node was found.
* @throws [AssertionError] if the assert fails.
*/
fun assertDoesNotExist(): GlanceNodeAssertion<R, T> {
val errorMessageOnFail = "Failed assertDoesNotExist"
val matchedNodesCount = testContext.findMatchingNodes(selector, errorMessageOnFail).size
if (matchedNodesCount != 0) {
throw AssertionError(
buildErrorMessageWithReason(
errorMessageOnFail = errorMessageOnFail,
reason = buildErrorReasonForCountMismatch(
matcherDescription = selector.description,
expectedCount = 0,
actualCount = matchedNodesCount
)
)
)
}
return this
}
/**
* Asserts that the provided [matcher] is satisfied for this node.
*
* <p> This function also can be used to create convenience "assert{somethingConcrete}"
* methods as extension functions on the GlanceNodeAssertion.
*
* @param matcher Matcher to verify.
* @param messagePrefixOnError Prefix to be put in front of an error that gets thrown in case
* this assert fails. This can be helpful in situations where this assert fails as part of a
* bigger operation that used this assert as a precondition check.
*
* @throws AssertionError if the matcher does not match or the node can no longer be found.
*/
fun assert(
matcher: GlanceNodeMatcher<R>,
messagePrefixOnError: (() -> String)? = null
): GlanceNodeAssertion<R, T> {
var errorMessageOnFail = "Failed to assert condition: (${matcher.description})"
if (messagePrefixOnError != null) {
errorMessageOnFail = messagePrefixOnError() + "\n" + errorMessageOnFail
}
val glanceNode = findSingleMatchingNode(errorMessageOnFail)
if (!matcher.matches(glanceNode)) {
throw AssertionError(
buildGeneralErrorMessage(
errorMessageOnFail,
glanceNode
)
)
}
return this
}
/**
* Returns [GlanceNodeAssertionCollection] that allows performing assertions on the children of
* the node selected by this [GlanceNodeAssertion].
*/
fun onChildren(): GlanceNodeAssertionCollection<R, T> {
return GlanceNodeAssertionCollection(testContext, selector.addChildrenSelector())
}
private fun findSingleMatchingNode(errorMessageOnFail: String): GlanceNode<R> {
val matchingNodes = testContext.findMatchingNodes(selector, errorMessageOnFail)
if (matchingNodes.size != 1) {
throw AssertionError(
buildErrorMessageWithReason(
errorMessageOnFail = errorMessageOnFail,
reason = buildErrorReasonForCountMismatch(
matcherDescription = selector.description,
expectedCount = 1,
actualCount = matchingNodes.size
)
)
)
}
return matchingNodes.single()
}
}