1 /**
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * ```
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  * ```
10  *
11  * Unless required by applicable law or agreed to in writing, software distributed under the License
12  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13  * or implied. See the License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 package com.android.healthconnect.controller.tests.utils
17 
18 import android.view.View
19 import android.view.ViewGroup
20 import androidx.recyclerview.widget.RecyclerView
21 import androidx.test.espresso.matcher.BoundedMatcher
22 import org.hamcrest.Description
23 import org.hamcrest.Matcher
24 import org.hamcrest.TypeSafeMatcher
25 
26 /**
27  * A custom matcher used when there are more than one view with the same resourceId/text/contentDesc
28  * etc.
29  *
30  * @param matcher a view matcher for UI element which may have potentially more than one matched.
31  *   Typical example is the element that is repeated in ListView or RecyclerView
32  * @param index the index to select matcher if there are more than one matcher. it's started at
33  *   zero.
34  * @return the view matcher selected from given matcher and index.
35  */
withIndexnull36 fun withIndex(matcher: Matcher<View?>, index: Int): Matcher<View?> {
37     return object : TypeSafeMatcher<View?>() {
38         var currentIndex = 0
39 
40         override fun describeTo(description: Description) {
41             description.appendText("with index: ")
42             description.appendValue(index)
43             matcher.describeTo(description)
44         }
45 
46         override fun matchesSafely(view: View?): Boolean {
47             return matcher.matches(view) && currentIndex++ == index
48         }
49     }
50 }
51 
atPositionnull52 fun atPosition(position: Int, itemMatcher: Matcher<View?>): Matcher<View?> {
53     return object : BoundedMatcher<View?, RecyclerView>(RecyclerView::class.java) {
54         override fun describeTo(description: Description) {
55             description.appendText("has item at position $position: ")
56             itemMatcher.describeTo(description)
57         }
58 
59         override fun matchesSafely(view: RecyclerView): Boolean {
60             val viewHolder: RecyclerView.ViewHolder =
61                 view.findViewHolderForAdapterPosition(position)
62                     ?: // has no item on such position
63                     return false
64             return itemMatcher.matches(viewHolder.itemView)
65         }
66     }
67 }
68 
isAbovenull69 fun isAbove(otherViewMatcher: Matcher<View>): TypeSafeMatcher<View> {
70     return object : TypeSafeMatcher<View>() {
71         private var otherView: View? = null
72 
73         override fun describeTo(description: Description) {
74             description.appendText("is above view: ")
75             otherViewMatcher.describeTo(description)
76         }
77 
78         override fun matchesSafely(view: View): Boolean {
79             if (otherView == null) {
80                 otherView = view.rootView.findViewByMatcher(otherViewMatcher)
81                 if (otherView == null) return false // Other view not found
82             }
83 
84             val location1 = IntArray(2)
85             val location2 = IntArray(2)
86             view.getLocationOnScreen(location1)
87             otherView!!.getLocationOnScreen(location2) // Safe call as otherView might be null
88 
89             return location1[1] < location2[1] // Compare y-coordinates
90         }
91     }
92 }
93 
94 // Extension function to find a view by matcher
findViewByMatchernull95 private fun View.findViewByMatcher(matcher: Matcher<View>): View? {
96     if (matcher.matches(this)) return this // Check if this view itself matches
97     if (this is ViewGroup) {
98         for (i in 0 until childCount) {
99             val child = getChildAt(i)
100             val foundView = child.findViewByMatcher(matcher)
101             if (foundView != null) return foundView
102         }
103     }
104     return null // No match found
105 }
106