1 /*
2 * Copyright (C) 2024 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 platform.test.motion.compose
18
19 import androidx.compose.ui.semantics.SemanticsNode
20 import androidx.compose.ui.test.SemanticsMatcher
21 import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
22 import platform.test.motion.compose.values.MotionTestValueKey
23 import platform.test.motion.golden.DataPointType
24 import platform.test.motion.golden.FeatureCapture
25 import platform.test.motion.golden.TimeSeriesCaptureScope
26
27 /** Matches nodes that have the [motionTestValueKey] set. */
hasMotionTestValuenull28 fun <T> hasMotionTestValue(motionTestValueKey: MotionTestValueKey<T>): SemanticsMatcher =
29 SemanticsMatcher.keyIsDefined(motionTestValueKey.semanticsPropertyKey)
30
31 /**
32 * Looks up a node matching [matcher], and returns the associated [motionTestValueKey].
33 *
34 * [AssertionError] is thrown if zero or more than one matching node is found.
35 * [IllegalStateException] is thrown if the node does not have a [motionTestValueKey] exported.
36 */
37 fun <T> SemanticsNodeInteractionsProvider.motionTestValueOfNode(
38 motionTestValueKey: MotionTestValueKey<T>,
39 matcher: SemanticsMatcher = hasMotionTestValue(motionTestValueKey),
40 useUnmergedTree: Boolean = false,
41 ): T = onNode(matcher, useUnmergedTree).fetchSemanticsNode().get(motionTestValueKey)
42
43 /**
44 * Records a feature by sampling the value associated to [motionTestValueKey], on a single node
45 * matching [matcher].
46 *
47 * Records `DataPoint.notFound()` if 0 or 2+ matching node are found. [IllegalStateException] is
48 * thrown if the node does not have a [motionTestValueKey] exported.
49 */
50 fun <T : Any> TimeSeriesCaptureScope<SemanticsNodeInteractionsProvider>.feature(
51 motionTestValueKey: MotionTestValueKey<T>,
52 dataPointType: DataPointType<T>,
53 matcher: SemanticsMatcher = hasMotionTestValue(motionTestValueKey),
54 name: String = motionTestValueKey.semanticsPropertyKey.name,
55 ) {
56 feature(
57 matcher,
58 FeatureCapture(name) { dataPointType.makeDataPoint(it.get(motionTestValueKey)) },
59 )
60 }
61
62 /**
63 * Captures the feature using [capture], where [capture]'s input is the value associated to
64 * [motionTestValueKey], read from a single node matching [matcher].
65 *
66 * Records `DataPoint.notFound()` if 0 or 2+ matching node are found. [IllegalStateException] is
67 * thrown if the node does not have a [motionTestValueKey] exported.
68 */
featurenull69 fun <T> TimeSeriesCaptureScope<SemanticsNodeInteractionsProvider>.feature(
70 motionTestValueKey: MotionTestValueKey<T>,
71 capture: FeatureCapture<T, *>,
72 matcher: SemanticsMatcher = hasMotionTestValue(motionTestValueKey),
73 name: String = "${motionTestValueKey.semanticsPropertyKey.name}_${capture.name}",
74 ) {
75 feature(matcher, FeatureCapture(name) { capture.capture(it.get(motionTestValueKey)) })
76 }
77
getnull78 private fun <T> SemanticsNode.get(motionTestValueKey: MotionTestValueKey<T>) =
79 config[motionTestValueKey.semanticsPropertyKey]
80