[go: nahoru, domu]

blob: 4ab61cd275879b520dcf3aa2198d3950f9e81260 [file] [log] [blame]
Filip Pavlis6ee6aa62019-01-31 16:19:30 +00001/*
2 * Copyright 2019 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
17package androidx.ui.test
18
Jelle Fresen891799c2020-05-12 10:30:34 +010019import androidx.test.espresso.matcher.ViewMatchers
Jelle Fresen75563952020-05-06 19:06:18 +010020import androidx.ui.core.AndroidOwner
George Mount5469e252020-06-17 10:01:42 -070021import androidx.ui.core.ExperimentalLayoutNodeApi
Cătălin Tudor37adfc72019-09-04 17:58:58 +010022import androidx.ui.core.LayoutNode
Ryan Mentley7865a632019-08-20 20:10:29 -070023import androidx.ui.core.findClosestParentNode
Filip Pavlisde9c6fe2020-05-04 14:19:02 +010024import androidx.ui.core.semantics.SemanticsNode
Louis Pullen-Freilichf434a132020-07-22 14:19:24 +010025import androidx.compose.ui.geometry.Offset
26import androidx.compose.ui.geometry.Rect
yingleiw4bfcd922020-04-09 15:34:27 -070027import androidx.ui.semantics.AccessibilityRangeInfo
Ryan Mentley6cd57192019-05-29 21:03:18 -070028import androidx.ui.semantics.SemanticsProperties
George Mount46405542020-03-12 23:15:24 -070029import androidx.ui.unit.height
Filip Pavlised382ac2020-06-04 17:10:14 +010030import androidx.ui.unit.toRect
George Mount46405542020-03-12 23:15:24 -070031import androidx.ui.unit.width
Filip Pavlis6ee6aa62019-01-31 16:19:30 +000032
33/**
Filip Pavlis659ea722020-07-13 14:14:32 +010034 * Asserts that the current semantics node has hidden property set to true.
Filip Pavlis8b8a8622020-02-13 16:32:04 +000035 *
Filip Pavlis659ea722020-07-13 14:14:32 +010036 * Note that this does not verify parents of the node. For stronger guarantees of visibility
37 * see [assertIsNotDisplayed]. If you want to assert that the node is not even in the hierarchy
Filip Pavlis8b8a8622020-02-13 16:32:04 +000038 * use [SemanticsNodeInteraction.assertDoesNotExist].
39 *
Filip Pavlis659ea722020-07-13 14:14:32 +010040 * Throws [AssertionError] if the node is not hidden.
Filip Pavlis6ee6aa62019-01-31 16:19:30 +000041 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +000042fun SemanticsNodeInteraction.assertIsHidden(): SemanticsNodeInteraction = assert(isHidden())
Filip Pavlis6ee6aa62019-01-31 16:19:30 +000043
44/**
Filip Pavlis659ea722020-07-13 14:14:32 +010045 * Asserts that the current semantics node has hidden property set to false.
Filip Pavlis8b8a8622020-02-13 16:32:04 +000046 *
Filip Pavlis659ea722020-07-13 14:14:32 +010047 * Note that this does not verify parents of the node. For stronger guarantees of visibility
48 * see [assertIsDisplayed]. If you only want to assert that the node is in the hierarchy use
Filip Pavlis8b8a8622020-02-13 16:32:04 +000049 * [SemanticsNodeInteraction.assertExists]
50 *
Filip Pavlis659ea722020-07-13 14:14:32 +010051 * Throws [AssertionError] if the node is hidden.
Filip Pavlis6ee6aa62019-01-31 16:19:30 +000052 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +000053fun SemanticsNodeInteraction.assertIsNotHidden(): SemanticsNodeInteraction = assert(isNotHidden())
Filip Pavlis6ee6aa62019-01-31 16:19:30 +000054
55/**
Filip Pavlis659ea722020-07-13 14:14:32 +010056 * Asserts that the current semantics node is displayed on screen.
Filip Pavliscd255fb2020-01-22 16:06:33 +000057 *
Filip Pavlis659ea722020-07-13 14:14:32 +010058 * Throws [AssertionError] if the node is not displayed.
Cătălin Tudor37adfc72019-09-04 17:58:58 +010059 */
60fun SemanticsNodeInteraction.assertIsDisplayed(): SemanticsNodeInteraction {
61 // TODO(b/143607231): check semantics hidden property
62 // TODO(b/143608742): check the correct AndroidCraneView is visible
63
Filip Pavliscd255fb2020-01-22 16:06:33 +000064 if (!checkIsDisplayed()) {
65 // TODO(b/133217292)
66 throw AssertionError("Assert failed: The component is not displayed!")
Cătălin Tudor37adfc72019-09-04 17:58:58 +010067 }
68 return this
69}
70
71/**
Filip Pavlis659ea722020-07-13 14:14:32 +010072 * Asserts that the current semantics node is not displayed on screen.
Filip Pavliscd255fb2020-01-22 16:06:33 +000073 *
Filip Pavlis659ea722020-07-13 14:14:32 +010074 * Throws [AssertionError] if the node is displayed.
Cătălin Tudor37adfc72019-09-04 17:58:58 +010075 */
76fun SemanticsNodeInteraction.assertIsNotDisplayed(): SemanticsNodeInteraction {
77 // TODO(b/143607231): check semantics hidden property
78 // TODO(b/143608742): check no AndroidCraneView contains the given component
79
Filip Pavliscd255fb2020-01-22 16:06:33 +000080 if (checkIsDisplayed()) {
81 // TODO(b/133217292)
82 throw AssertionError("Assert failed: The component is displayed!")
Cătălin Tudor37adfc72019-09-04 17:58:58 +010083 }
Cătălin Tudor37adfc72019-09-04 17:58:58 +010084 return this
85}
86
87/**
Filip Pavlis659ea722020-07-13 14:14:32 +010088 * Asserts that the current semantics node is enabled.
Filip Pavlis5a3690b2020-04-24 15:00:43 +010089 *
Filip Pavlis659ea722020-07-13 14:14:32 +010090 * Throws [AssertionError] if the node is not enabled or does not define the property at all.
Filip Pavlis5a3690b2020-04-24 15:00:43 +010091 */
92fun SemanticsNodeInteraction.assertIsEnabled(): SemanticsNodeInteraction = assert(isEnabled())
93
94/**
Filip Pavlis659ea722020-07-13 14:14:32 +010095 * Asserts that the current semantics node is not enabled.
Filip Pavlis5a3690b2020-04-24 15:00:43 +010096 *
Filip Pavlis659ea722020-07-13 14:14:32 +010097 * Throws [AssertionError] if the node is enabled or does not defined the property at all.
Filip Pavlis5a3690b2020-04-24 15:00:43 +010098 */
99fun SemanticsNodeInteraction.assertIsNotEnabled(): SemanticsNodeInteraction = assert(isNotEnabled())
100
101/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100102 * Asserts that the current semantics node is checked.
Ryan Mentley6cd57192019-05-29 21:03:18 -0700103 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100104 * Throws [AssertionError] if the node is not unchecked, indeterminate, or not toggleable.
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100105 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000106fun SemanticsNodeInteraction.assertIsOn(): SemanticsNodeInteraction = assert(isOn())
Filip Pavlis6ee6aa62019-01-31 16:19:30 +0000107
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100108/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100109 * Asserts that the current semantics node is unchecked.
Ryan Mentley6cd57192019-05-29 21:03:18 -0700110 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100111 * Throws [AssertionError] if the node is checked, indeterminate, or not toggleable.
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100112 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000113fun SemanticsNodeInteraction.assertIsOff(): SemanticsNodeInteraction = assert(isOff())
Cătălin Tudor316c2b02019-02-12 18:24:18 +0000114
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100115/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100116 * Asserts that the current semantics node is selected.
Ryan Mentley6cd57192019-05-29 21:03:18 -0700117 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100118 * Throws [AssertionError] if the node is unselected or not selectable.
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100119 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000120fun SemanticsNodeInteraction.assertIsSelected(): SemanticsNodeInteraction = assert(isSelected())
Matvei Malkovbf7f55a2019-03-18 16:19:33 +0000121
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100122/**
yingleiw56f5ea82020-07-17 12:05:11 -0700123 * Asserts that the current semantics node is not selected.
Ryan Mentley6cd57192019-05-29 21:03:18 -0700124 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100125 * Throws [AssertionError] if the node is selected or not selectable.
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100126 */
yingleiw56f5ea82020-07-17 12:05:11 -0700127fun SemanticsNodeInteraction.assertIsNotSelected(): SemanticsNodeInteraction =
128 assert(isNotSelected())
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100129
Filip Pavliscd255fb2020-01-22 16:06:33 +0000130/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100131 * Asserts that the current semantics node is toggleable.
Filip Pavliscd255fb2020-01-22 16:06:33 +0000132 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100133 * Throws [AssertionError] if the node is not toggleable.
Filip Pavliscd255fb2020-01-22 16:06:33 +0000134 */
135fun SemanticsNodeInteraction.assertIsToggleable(): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000136 assert(isToggleable())
Filip Pavliscd255fb2020-01-22 16:06:33 +0000137
138/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100139 * Asserts that the current semantics node is selectable.
Filip Pavliscd255fb2020-01-22 16:06:33 +0000140 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100141 * Throws [AssertionError] if the node is not selectable.
Filip Pavliscd255fb2020-01-22 16:06:33 +0000142 */
143fun SemanticsNodeInteraction.assertIsSelectable(): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000144 assert(isSelectable())
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100145
146/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100147 * Asserts the semantics node is in a mutually exclusive group. This is used by radio groups to
148 * assert only one is selected at a given time.
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100149 */
Filip Pavliscd255fb2020-01-22 16:06:33 +0000150fun SemanticsNodeInteraction.assertIsInMutuallyExclusiveGroup(): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000151 assert(isInMutuallyExclusiveGroup())
Louis Pullen-Freilich7ecf5122019-02-25 18:52:05 +0000152
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100153/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100154 * Asserts the node's label equals the given String.
Filip Pavliscd255fb2020-01-22 16:06:33 +0000155 * For further details please check [SemanticsProperties.AccessibilityLabel].
Ryan Mentley7865a632019-08-20 20:10:29 -0700156 * Throws [AssertionError] if the node's value is not equal to `value`, or if the node has no value
157 */
Filip Pavliscd255fb2020-01-22 16:06:33 +0000158fun SemanticsNodeInteraction.assertLabelEquals(value: String): SemanticsNodeInteraction =
yingleiw8103f442020-05-21 11:46:39 -0700159 assert(hasLabel(value))
160
161/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100162 * Asserts the node's text equals the given String.
yingleiw8103f442020-05-21 11:46:39 -0700163 * For further details please check [SemanticsProperties.Text].
164 * Throws [AssertionError] if the node's value is not equal to `value`, or if the node has no value
165 */
166fun SemanticsNodeInteraction.assertTextEquals(value: String): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000167 assert(hasText(value))
Ryan Mentley7865a632019-08-20 20:10:29 -0700168
169/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100170 * Asserts the node's value equals the given value.
Filip Pavlis08b19932019-11-27 17:09:55 +0000171 *
Filip Pavliscd255fb2020-01-22 16:06:33 +0000172 * For further details please check [SemanticsProperties.AccessibilityValue].
Ryan Mentley6cd57192019-05-29 21:03:18 -0700173 * Throws [AssertionError] if the node's value is not equal to `value`, or if the node has no value
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100174 */
Filip Pavliscd255fb2020-01-22 16:06:33 +0000175fun SemanticsNodeInteraction.assertValueEquals(value: String): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000176 assert(hasValue(value))
Filip Pavlis6ee6aa62019-01-31 16:19:30 +0000177
Cătălin Tudor83cb5502019-05-22 11:29:23 +0100178/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100179 * Asserts the node's range info equals the given value.
yingleiw4bfcd922020-04-09 15:34:27 -0700180 *
181 * For further details please check [SemanticsProperties.AccessibilityRangeInfo].
182 * Throws [AssertionError] if the node's value is not equal to `value`, or if the node has no value
183 */
184fun SemanticsNodeInteraction.assertRangeInfoEquals(value: AccessibilityRangeInfo):
185 SemanticsNodeInteraction =
186 assert(hasRangeInfo(value))
187
188/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100189 * Asserts that the current semantics node has a click action.
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100190 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100191 * Throws [AssertionError] if the node is doesn't have a click action.
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100192 */
Filip Pavliscd255fb2020-01-22 16:06:33 +0000193fun SemanticsNodeInteraction.assertHasClickAction(): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000194 assert(hasClickAction())
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100195
196/**
Filip Pavlis659ea722020-07-13 14:14:32 +0100197 * Asserts that the current semantics node has doesn't have a click action.
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100198 *
Filip Pavlis659ea722020-07-13 14:14:32 +0100199 * Throws [AssertionError] if the node has a click action.
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100200 */
Filip Pavliscd255fb2020-01-22 16:06:33 +0000201fun SemanticsNodeInteraction.assertHasNoClickAction(): SemanticsNodeInteraction =
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000202 assert(hasNoClickAction())
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100203
204/**
Filip Pavlisfc3b32f2020-04-15 15:36:02 +0100205 * Asserts that the provided [matcher] is satisfied for this node.
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000206 *
Filip Pavlisfc3b32f2020-04-15 15:36:02 +0100207 * @param matcher Matcher to verify.
Filip Pavlis66f9c892020-05-14 12:00:08 +0100208 * @param messagePrefixOnError Prefix to be put in front of an error that gets thrown in case this
209 * assert fails. This can be helpful in situations where this assert fails as part of a bigger
210 * operation that used this assert as a precondition check.
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000211 *
Filip Pavlisfc3b32f2020-04-15 15:36:02 +0100212 * @throws AssertionError if the matcher does not match or the node can no longer be found.
Ryan Mentley6cd57192019-05-29 21:03:18 -0700213 */
Filip Pavlisf46a1d812020-02-18 18:23:05 +0000214fun SemanticsNodeInteraction.assert(
Filip Pavlis66f9c892020-05-14 12:00:08 +0100215 matcher: SemanticsMatcher,
216 messagePrefixOnError: (() -> String)? = null
Filip Pavliscd255fb2020-01-22 16:06:33 +0000217): SemanticsNodeInteraction {
Filip Pavlis66f9c892020-05-14 12:00:08 +0100218 var errorMessageOnFail = "Failed to assert the following: (${matcher.description})"
219 if (messagePrefixOnError != null) {
220 errorMessageOnFail = messagePrefixOnError() + "\n" + errorMessageOnFail
221 }
Filip Pavlis8b8a8622020-02-13 16:32:04 +0000222 val node = fetchSemanticsNode(errorMessageOnFail)
Filip Pavlisfc3b32f2020-04-15 15:36:02 +0100223 if (!matcher.matches(node)) {
Filip Pavlis66f9c892020-05-14 12:00:08 +0100224 throw AssertionError(buildGeneralErrorMessage(errorMessageOnFail, selector, node))
Filip Pavlis6ee6aa62019-01-31 16:19:30 +0000225 }
Filip Pavliscd255fb2020-01-22 16:06:33 +0000226 return this
Ryan Mentley6cd57192019-05-29 21:03:18 -0700227}
Cătălin Tudor8d91edd2019-08-05 15:14:09 +0100228
Filip Pavlis21b8c202020-04-23 14:36:29 +0100229/**
230 * Asserts that this collection of nodes is equal to the given [expectedSize].
231 *
232 * Provides a detailed error message on failure.
233 *
234 * @throws AssertionError if the size is not equal to [expectedSize]
235 */
236fun SemanticsNodeInteractionCollection.assertCountEquals(
237 expectedSize: Int
238): SemanticsNodeInteractionCollection {
239 val errorOnFail = "Failed to assert count of nodes."
240 val matchedNodes = fetchSemanticsNodes(errorOnFail)
241 if (matchedNodes.size != expectedSize) {
242 throw AssertionError(buildErrorMessageForCountMismatch(
243 errorMessage = errorOnFail,
244 selector = selector,
245 foundNodes = matchedNodes,
246 expectedCount = expectedSize))
247 }
248 return this
249}
250
Filip Pavlisde9c6fe2020-05-04 14:19:02 +0100251/**
252 * Asserts that this collection contains at least one element that satisfies the given [matcher].
253 *
254 * @param matcher Matcher that has to be satisfied by at least one of the nodes in the collection.
255 *
256 * @throws AssertionError if not at least one matching node was node.
257 */
258fun SemanticsNodeInteractionCollection.assertAny(
259 matcher: SemanticsMatcher
260): SemanticsNodeInteractionCollection {
261 val errorOnFail = "Failed to assertAny(${matcher.description})"
262 val nodes = fetchSemanticsNodes(errorOnFail)
263 if (nodes.isEmpty()) {
264 throw AssertionError(buildErrorMessageForAtLeastOneNodeExpected(errorOnFail, selector))
265 }
266 if (!matcher.matchesAny(nodes)) {
267 throw AssertionError(buildErrorMessageForAssertAnyFail(selector, nodes, matcher))
268 }
269 return this
270}
271
272/**
273 * Asserts that all the nodes in this collection satisfy the given [matcher].
274 *
275 * This passes also for empty collections.
276 *
277 * @param matcher Matcher that has to be satisfied by all the nodes in the collection.
278 *
279 * @throws AssertionError if the collection contains at least one element that does not satisfy
280 * the given matcher.
281 */
282fun SemanticsNodeInteractionCollection.assertAll(
283 matcher: SemanticsMatcher
284): SemanticsNodeInteractionCollection {
285 val errorOnFail = "Failed to assertAll(${matcher.description})"
286 val nodes = fetchSemanticsNodes(errorOnFail)
287
288 val violations = mutableListOf<SemanticsNode>()
289 nodes.forEach {
290 if (!matcher.matches(it)) {
291 violations.add(it)
292 }
293 }
294 if (violations.isNotEmpty()) {
295 throw AssertionError(buildErrorMessageForAssertAllFail(selector, violations, matcher))
296 }
297 return this
298}
299
George Mount5469e252020-06-17 10:01:42 -0700300@OptIn(ExperimentalLayoutNodeApi::class)
Cătălin Tudor37adfc72019-09-04 17:58:58 +0100301private fun SemanticsNodeInteraction.checkIsDisplayed(): Boolean {
302 // hierarchy check - check layout nodes are visible
Filip Pavlis8b8a8622020-02-13 16:32:04 +0000303 val errorMessageOnFail = "Failed to perform isDisplayed check."
304 val node = fetchSemanticsNode(errorMessageOnFail)
Jelle Fresend4bca952020-05-11 15:51:02 +0100305
George Mount17d6fe52020-05-15 08:08:23 -0700306 fun isNotPlaced(node: LayoutNode): Boolean {
307 return !node.isPlaced
Jelle Fresend4bca952020-05-11 15:51:02 +0100308 }
309
George Mount17d6fe52020-05-15 08:08:23 -0700310 val layoutNode = node.componentNode
311 if (isNotPlaced(layoutNode) || layoutNode.findClosestParentNode(::isNotPlaced) != null) {
Cătălin Tudor37adfc72019-09-04 17:58:58 +0100312 return false
313 }
314
George Mount17d6fe52020-05-15 08:08:23 -0700315 (layoutNode.owner as? AndroidOwner)?.let {
Jelle Fresen891799c2020-05-12 10:30:34 +0100316 if (!ViewMatchers.isDisplayed().matches(it.view)) {
317 return false
318 }
319 }
320
Cătălin Tudor37adfc72019-09-04 17:58:58 +0100321 // check node doesn't clip unintentionally (e.g. row too small for content)
Filip Pavlis8b8a8622020-02-13 16:32:04 +0000322 val globalRect = node.globalBounds
Jelle Fresen75563952020-05-06 19:06:18 +0100323 if (!node.isInScreenBounds()) {
Cătălin Tudor37adfc72019-09-04 17:58:58 +0100324 return false
325 }
326
Nader Jawad63153c12020-05-21 14:17:08 -0700327 return (globalRect.width > 0f && globalRect.height > 0f)
Cătălin Tudor37adfc72019-09-04 17:58:58 +0100328}
329
George Mount5469e252020-06-17 10:01:42 -0700330@OptIn(ExperimentalLayoutNodeApi::class)
Jelle Fresenf1efe672020-07-02 15:24:59 +0100331private fun SemanticsNode.clippedNodeBoundsInWindow(): Rect {
Filip Pavlised382ac2020-06-04 17:10:14 +0100332 val composeView = (componentNode.owner as AndroidOwner).view
333 val rootLocationInWindow = intArrayOf(0, 0).let {
334 composeView.getLocationInWindow(it)
335 Offset(it[0].toFloat(), it[1].toFloat())
336 }
337 return boundsInRoot.toRect().shift(rootLocationInWindow)
338}
339
George Mount5469e252020-06-17 10:01:42 -0700340@OptIn(ExperimentalLayoutNodeApi::class)
Jelle Fresen75563952020-05-06 19:06:18 +0100341private fun SemanticsNode.isInScreenBounds(): Boolean {
Filip Pavlised382ac2020-06-04 17:10:14 +0100342 val composeView = (componentNode.owner as AndroidOwner).view
343
344 // Window relative bounds of our node
Jelle Fresenf1efe672020-07-02 15:24:59 +0100345 val nodeBoundsInWindow = clippedNodeBoundsInWindow()
Filip Pavlised382ac2020-06-04 17:10:14 +0100346 if (nodeBoundsInWindow.width == 0f || nodeBoundsInWindow.height == 0f) {
Filip Pavlis5aa07642020-04-09 17:41:18 +0100347 return false
348 }
Filip Pavlis5aa07642020-04-09 17:41:18 +0100349
Filip Pavlised382ac2020-06-04 17:10:14 +0100350 // Window relative bounds of our compose root view that are visible on the screen
351 val globalRootRect = android.graphics.Rect()
352 if (!composeView.getGlobalVisibleRect(globalRootRect)) {
353 return false
354 }
Jelle Fresen75563952020-05-06 19:06:18 +0100355
Filip Pavlised382ac2020-06-04 17:10:14 +0100356 return nodeBoundsInWindow.top >= globalRootRect.top &&
357 nodeBoundsInWindow.left >= globalRootRect.left &&
358 nodeBoundsInWindow.right <= globalRootRect.right &&
359 nodeBoundsInWindow.bottom <= globalRootRect.bottom
Filip Pavlis5aa07642020-04-09 17:41:18 +0100360}