1 /*
2  * Copyright 2020 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.compose.ui.test
18 
19 import androidx.compose.ui.semantics.SemanticsNode
20 import androidx.compose.ui.semantics.SemanticsPropertyKey
21 
22 /**
23  * Wrapper for semantics matcher lambdas that allows to build string explaining to the developer
24  * what conditions were being tested.
25  */
26 class SemanticsMatcher(val description: String, private val matcher: (SemanticsNode) -> Boolean) {
27 
28     companion object {
29         /**
30          * Builds a predicate that tests whether the value of the given [key] is equal to
31          * [expectedValue].
32          */
expectValuenull33         fun <T> expectValue(key: SemanticsPropertyKey<T>, expectedValue: T): SemanticsMatcher {
34             return SemanticsMatcher("${key.name} = '$expectedValue'") {
35                 it.config.getOrElseNullable(key) { null } == expectedValue
36             }
37         }
38 
39         /** Builds a predicate that tests whether the given [key] is defined in semantics. */
keyIsDefinednull40         fun <T> keyIsDefined(key: SemanticsPropertyKey<T>): SemanticsMatcher {
41             return SemanticsMatcher("${key.name} is defined") { key in it.config }
42         }
43 
44         /** Builds a predicate that tests whether the given [key] is NOT defined in semantics. */
keyNotDefinednull45         fun <T> keyNotDefined(key: SemanticsPropertyKey<T>): SemanticsMatcher {
46             return SemanticsMatcher("${key.name} is NOT defined") { key !in it.config }
47         }
48     }
49 
50     /** Returns whether the given node is matched by this matcher. */
matchesnull51     fun matches(node: SemanticsNode): Boolean {
52         return matcher(node)
53     }
54 
55     /** Returns whether at least one of the given nodes is matched by this matcher. */
matchesAnynull56     fun matchesAny(nodes: Iterable<SemanticsNode>): Boolean {
57         return nodes.any(matcher)
58     }
59 
andnull60     infix fun and(other: SemanticsMatcher): SemanticsMatcher {
61         return SemanticsMatcher("($description) && (${other.description})") {
62             matcher(it) && other.matches(it)
63         }
64     }
65 
ornull66     infix fun or(other: SemanticsMatcher): SemanticsMatcher {
67         return SemanticsMatcher("($description) || (${other.description})") {
68             matcher(it) || other.matches(it)
69         }
70     }
71 
notnull72     operator fun not(): SemanticsMatcher {
73         return SemanticsMatcher("NOT ($description)") { !matcher(it) }
74     }
75 }
76