[go: nahoru, domu]

blob: 43622801a7ec12949d193c0ba6391c42eaa3ef11 [file] [log] [blame]
/*
* Copyright 2019 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.ui.test
import androidx.ui.core.semantics.SemanticsNode
import androidx.ui.core.semantics.getOrNull
import androidx.compose.foundation.selection.ToggleableState
import androidx.compose.foundation.semantics.FoundationSemanticsProperties
import androidx.compose.ui.text.input.ImeAction
import androidx.ui.semantics.AccessibilityRangeInfo
import androidx.ui.semantics.SemanticsActions
import androidx.ui.semantics.SemanticsProperties
import androidx.compose.foundation.text.TextSemanticsProperties
import androidx.ui.util.fastAny
/**
* Returns whether the node is enabled.
*
* @see SemanticsProperties.Disabled
*/
fun isEnabled(): SemanticsMatcher =
!SemanticsMatcher.keyIsDefined(SemanticsProperties.Disabled)
/**
* Returns whether the node is not enabled.
*
* @see SemanticsProperties.Disabled
*/
fun isNotEnabled(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(SemanticsProperties.Disabled)
/**
* Return whether the node is checkable.
*
* @see FoundationSemanticsProperties.ToggleableState
*/
fun isToggleable(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(FoundationSemanticsProperties.ToggleableState)
/**
* Returns whether the node is toggled.
*
* @see FoundationSemanticsProperties.ToggleableState
*/
fun isOn(): SemanticsMatcher = SemanticsMatcher.expectValue(
FoundationSemanticsProperties.ToggleableState, ToggleableState.On)
/**
* Returns whether the node is not toggled.
*
* @see FoundationSemanticsProperties.ToggleableState
*/
fun isOff(): SemanticsMatcher = SemanticsMatcher.expectValue(
FoundationSemanticsProperties.ToggleableState, ToggleableState.Off)
/**
* Return whether the node is selectable.
*
* @see FoundationSemanticsProperties.Selected
*/
fun isSelectable(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(FoundationSemanticsProperties.Selected)
/**
* Returns whether the node is selected.
*
* @see FoundationSemanticsProperties.Selected
*/
fun isSelected(): SemanticsMatcher =
SemanticsMatcher.expectValue(FoundationSemanticsProperties.Selected, true)
/**
* Returns whether the node is not selected.
*
* @see FoundationSemanticsProperties.Selected
*/
fun isNotSelected(): SemanticsMatcher =
SemanticsMatcher.expectValue(FoundationSemanticsProperties.Selected, false)
/**
* Returns whether the node is focused.
*
* @see SemanticsProperties.Focused
*/
fun isFocused(): SemanticsMatcher =
SemanticsMatcher.expectValue(SemanticsProperties.Focused, true)
/**
* Returns whether the node is not focused.
*
* @see SemanticsProperties.Focused
*/
fun isNotFocused(): SemanticsMatcher =
SemanticsMatcher.expectValue(SemanticsProperties.Focused, false)
/**
* Return whether the node has a semantics click action defined.
*
* @see SemanticsActions.OnClick
*/
fun hasClickAction(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(SemanticsActions.OnClick)
/**
* Return whether the node has no semantics click action defined.
*
* @see SemanticsActions.OnClick
*/
fun hasNoClickAction(): SemanticsMatcher =
SemanticsMatcher.keyNotDefined(SemanticsActions.OnClick)
/**
* Return whether the node has a semantics scrollable action defined.
*
* @see SemanticsActions.ScrollBy
*/
fun hasScrollAction(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(SemanticsActions.ScrollBy)
/**
* Return whether the node has no semantics scrollable action defined.
*
* @see SemanticsActions.ScrollBy
*/
fun hasNoScrollAction(): SemanticsMatcher =
SemanticsMatcher.keyNotDefined(SemanticsActions.ScrollBy)
/**
* Returns whether the node's label matches exactly to the given text.
*
* @param label Text to match.
* @param ignoreCase Whether case should be ignored.
*
* @see SemanticsProperties.AccessibilityLabel
*/
fun hasLabel(label: String, ignoreCase: Boolean = false): SemanticsMatcher {
return SemanticsMatcher(
"${SemanticsProperties.AccessibilityLabel.name} = '$label' (ignoreCase: $ignoreCase)"
) {
it.config.getOrNull(SemanticsProperties.AccessibilityLabel).equals(label, ignoreCase)
}
}
/**
* Returns whether the node's text matches exactly to the given text.
*
* @param text Text to match.
* @param ignoreCase Whether case should be ignored.
*
* @see hasSubstring
* @see SemanticsProperties.Text
*/
fun hasText(text: String, ignoreCase: Boolean = false): SemanticsMatcher {
return SemanticsMatcher(
"${SemanticsProperties.Text.name} = '$text' (ignoreCase: $ignoreCase)"
) {
it.config.getOrNull(SemanticsProperties.Text)?.text.equals(text, ignoreCase)
}
}
/**
* Returns whether the node's text contains the given substring.
*
* @param substring Substring to check.
* @param ignoreCase Whether case should be ignored.
*
* @see hasText
* @see SemanticsProperties.Text
*/
fun hasSubstring(substring: String, ignoreCase: Boolean = false):
SemanticsMatcher {
return SemanticsMatcher(
"${SemanticsProperties.Text.name}.contains($substring, $ignoreCase)"
) {
it.config.getOrNull(SemanticsProperties.Text)?.text?.contains(substring, ignoreCase)
?: false
}
}
/**
* Returns whether the node's value matches exactly to the given accessibility value.
*
* @param value Value to match.
*
* @see SemanticsProperties.AccessibilityValue
*/
fun hasValue(value: String): SemanticsMatcher = SemanticsMatcher.expectValue(
SemanticsProperties.AccessibilityValue, value)
/**
* Returns whether the node's range info matches exactly to the given accessibility range info.
*
* @param rangeInfo range info to match.
*
* @see SemanticsProperties.AccessibilityRangeInfo
*/
fun hasRangeInfo(rangeInfo: AccessibilityRangeInfo): SemanticsMatcher = SemanticsMatcher
.expectValue(SemanticsProperties.AccessibilityRangeInfo, rangeInfo)
/**
* Returns whether the node is annotated by the given test tag.
*
* @param testTag Value to match.
*
* @see SemanticsProperties.TestTag
*/
fun hasTestTag(testTag: String): SemanticsMatcher =
SemanticsMatcher.expectValue(SemanticsProperties.TestTag, testTag)
/**
* Verifies that the node is in a mutually exclusive group - that is,
* that [FoundationSemanticsProperties.InMutuallyExclusiveGroup] is set to true
*
*/
fun isInMutuallyExclusiveGroup(): SemanticsMatcher =
SemanticsMatcher.expectValue(FoundationSemanticsProperties.InMutuallyExclusiveGroup, true)
/**
* Returns whether the node is hidden.
*
* This checks only the property of the node itself. Ignoring parents visibility.
*
* @see SemanticsProperties.Hidden
*/
fun isHidden(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(SemanticsProperties.Hidden)
/**
* Returns whether the node is not hidden.
*
* This checks only the property of the node itself. Ignoring parents visibility.
*
* @see SemanticsProperties.Hidden
*/
fun isNotHidden(): SemanticsMatcher =
!SemanticsMatcher.keyIsDefined(SemanticsProperties.Hidden)
/**
* Returns whether the node is a dialog.
*
* This only checks if the node itself is a dialog, not if it is _part of_ a dialog. Use
* `hasAnyAncestorThat(isDialog())` for that.
*
* @see FoundationSemanticsProperties.IsDialog
*/
fun isDialog(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(FoundationSemanticsProperties.IsDialog)
/** Returns whether the node is a popup.
*
* This only checks if the node itself is a popup, not if it is _part of_ a popup. Use
* `hasAnyAncestorThat(isPopup())` for that.
*
* @see SemanticsProperties.IsPopup
*/
fun isPopup(): SemanticsMatcher =
SemanticsMatcher.keyIsDefined(SemanticsProperties.IsPopup)
/**
* Returns whether the node defines the given IME action.
*
* @param actionType the action to match.
*/
fun hasImeAction(actionType: ImeAction) =
SemanticsMatcher.expectValue(TextSemanticsProperties.ImeAction, actionType)
/**
* Return whether the node supports input methods.
*
* Supporting input methods means that the node provides a connection to IME (keyboard) and is
* able to accept input from it. This is however not enforced and relies on the nodes to
* properly add this to semantics when they provide input. Note that this is not related to
* gestures input but only to IME. This can be used to for instance filter out all text fields.
*/
fun hasInputMethodsSupport() =
SemanticsMatcher.keyIsDefined(TextSemanticsProperties.SupportsInputMethods)
/**
* Return whether the node is the root semantics node.
*
* There is always one root in every node tree, added implicitly by Compose.
*/
fun isRoot() =
SemanticsMatcher("isRoot") { it.isRoot }
/**
* Returns whether the node's parent satisfies the given matcher.
*
* Returns false if no parent exists.
*/
fun hasParent(matcher: SemanticsMatcher): SemanticsMatcher {
// TODO(b/150292800): If this is used in assert we should print the parent's node semantics
// in the error message or say that no parent was found.
return SemanticsMatcher("hasParentThat(${matcher.description})") {
it.parent?.run { matcher.matches(this) } ?: false
}
}
/**
* Returns whether the node has at least one child that satisfies the given matcher.
*/
fun hasAnyChild(matcher: SemanticsMatcher): SemanticsMatcher {
// TODO(b/150292800): If this is used in assert we should print the children nodes semantics
// in the error message or say that no children were found.
return SemanticsMatcher("hasAnyChildThat(${matcher.description})") {
matcher.matchesAny(it.children)
}
}
/**
* Returns whether the node has at least one sibling that satisfies the given matcher.
*
* Sibling is defined as a any other node that shares the same parent.
*/
fun hasAnySibling(matcher: SemanticsMatcher): SemanticsMatcher {
// TODO(b/150292800): If this is used in assert we should print the sibling nodes semantics
// in the error message or say that no siblings were found.
return SemanticsMatcher("hasAnySiblingThat(${matcher.description})"
) {
val node = it
it.parent?.run { matcher.matchesAny(this.children.filter { child -> child.id != node.id }) }
?: false
}
}
/**
* Returns whether the node has at least one ancestor that satisfies the given matcher.
*
* Example: For the following tree
* |-X
* |-A
* |-B
* |-C1
* |-C2
* In case of C1, we would check the matcher against A and B
*/
fun hasAnyAncestor(matcher: SemanticsMatcher): SemanticsMatcher {
// TODO(b/150292800): If this is used in assert we should print the ancestor nodes semantics
// in the error message or say that no ancestors were found.
return SemanticsMatcher("hasAnyAncestorThat(${matcher.description})") {
matcher.matchesAny(it.ancestors)
}
}
/**
* Returns whether the node has at least one descendant that satisfies the given matcher.
*
* Example: For the following tree
* |-X
* |-A
* |-B
* |-C1
* |-C2
* In case of A, we would check the matcher against B,C1 and C2
*/
fun hasAnyDescendant(matcher: SemanticsMatcher): SemanticsMatcher {
// TODO(b/150292800): If this is used in assert we could consider printing the whole subtree but
// it might be too much to show. But we could at least warn if there were no ancestors found.
fun checkIfSubtreeMatches(matcher: SemanticsMatcher, node: SemanticsNode): Boolean {
if (matcher.matchesAny(node.children)) {
return true
}
return node.children.fastAny { checkIfSubtreeMatches(matcher, it) }
}
return SemanticsMatcher("hasAnyDescendantThat(${matcher.description})") {
checkIfSubtreeMatches(matcher, it)
}
}
internal val SemanticsNode.ancestors: Iterable<SemanticsNode>
get() = object : Iterable<SemanticsNode> {
override fun iterator(): Iterator<SemanticsNode> {
return object : Iterator<SemanticsNode> {
var next = parent
override fun hasNext(): Boolean {
return next != null
}
override fun next(): SemanticsNode {
return next!!.also { next = it.parent }
}
}
}
}