Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8e19cf50 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Fix SearchScaffold flaky when close search box" into udc-qpr-dev am:...

Merge "Fix SearchScaffold flaky when close search box" into udc-qpr-dev am: b1b715c8 am: 742db487

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23807527



Change-Id: Ided311d16cfbd1f2f8095f8003e7af1e9cc64f85
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents a68f0500 742db487
Loading
Loading
Loading
Loading
+20 −15
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
@@ -286,21 +286,22 @@ private fun TwoRowsTopAppBar(
        )
    }
    val pinnedHeightPx: Float
    val density = LocalDensity.current
    val maxHeightPx = density.run {
        remember { mutableStateOf((MaxHeightWithoutTitle + DefaultTitleHeight).toPx()) }
    }
    val titleBottomPaddingPx: Int
    val defaultMaxHeightPx: Float
    val density = LocalDensity.current
    density.run {
        pinnedHeightPx = pinnedHeight.toPx()
        titleBottomPaddingPx = titleBottomPadding.roundToPx()
        defaultMaxHeightPx = (MaxHeightWithoutTitle + DefaultTitleHeight).toPx()
    }

    val maxHeightPx = remember(density) { mutableFloatStateOf(defaultMaxHeightPx) }

    // Sets the app bar's height offset limit to hide just the bottom title area and keep top title
    // visible when collapsed.
    SideEffect {
        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.value) {
            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.value
        if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx.floatValue) {
            scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx.floatValue
        }
    }

@@ -370,17 +371,21 @@ private fun TwoRowsTopAppBar(
            )
            TopAppBarLayout(
                modifier = Modifier.clipToBounds(),
                heightPx = maxHeightPx.value - pinnedHeightPx +
                heightPx = maxHeightPx.floatValue - pinnedHeightPx +
                    (scrollBehavior?.state?.heightOffset ?: 0f),
                navigationIconContentColor = colors.navigationIconContentColor,
                titleContentColor = colors.titleContentColor,
                actionIconContentColor = colors.actionIconContentColor,
                title = {
                    Box(modifier = Modifier.onGloballyPositioned { coordinates ->
                        density.run {
                            maxHeightPx.value =
                        val measuredMaxHeightPx = density.run {
                            MaxHeightWithoutTitle.toPx() + coordinates.size.height.toFloat()
                        }
                        // Allow larger max height for multi-line title, but do not reduce
                        // max height to prevent flaky.
                        if (measuredMaxHeightPx > defaultMaxHeightPx) {
                            maxHeightPx.floatValue = measuredMaxHeightPx
                        }
                    }) { title() }
                },
                titleTextStyle = titleTextStyle,
@@ -506,7 +511,7 @@ private fun TopAppBarLayout(
                0
            }

        val layoutHeight = heightPx.roundToInt()
        val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()

        layout(constraints.maxWidth, layoutHeight) {
            // Navigation icon
@@ -612,9 +617,9 @@ private suspend fun settleAppBar(
// Medium or Large app bar.
private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)

private val MaxHeightWithoutTitle = 124.dp
private val DefaultTitleHeight = 52.dp
private val ContainerHeight = 56.dp
internal val MaxHeightWithoutTitle = 124.dp
internal val DefaultTitleHeight = 52.dp
internal val ContainerHeight = 56.dp
private val LargeTitleBottomPadding = 28.dp
private val TopAppBarHorizontalPadding = 4.dp

+3 −3
Original line number Diff line number Diff line
@@ -163,7 +163,6 @@ private fun SearchTopAppBar(
    BackHandler { onClose() }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
    val focusRequester = remember { FocusRequester() }
@@ -186,8 +185,9 @@ private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) ->
        keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
        keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
        singleLine = true,
        colors = TextFieldDefaults.textFieldColors(
            containerColor = Color.Transparent,
        colors = TextFieldDefaults.colors(
            focusedContainerColor = Color.Transparent,
            unfocusedContainerColor = Color.Transparent,
            focusedIndicatorColor = Color.Transparent,
            unfocusedIndicatorColor = Color.Transparent,
        ),
+457 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 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 com.android.settingslib.spa.widget.scaffold

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.getUnclippedBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeLeft
import androidx.compose.ui.test.swipeRight
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.testutils.rootWidth
import com.android.settingslib.spa.testutils.setContentForSizeAssertions
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@OptIn(ExperimentalMaterial3Api::class)
@RunWith(AndroidJUnit4::class)
class CustomizedAppBarTest {

    @get:Rule
    val rule = createComposeRule()

    @Test
    fun smallTopAppBar_expandsToScreen() {
        rule
            .setContentForSizeAssertions {
                CustomizedTopAppBar(title = { Text("Title") })
            }
            .assertHeightIsEqualTo(ContainerHeight)
            .assertWidthIsEqualTo(rule.rootWidth())
    }

    @Test
    fun smallTopAppBar_withTitle() {
        val title = "Title"
        rule.setContent {
            Box(Modifier.testTag(TopAppBarTestTag)) {
                CustomizedTopAppBar(title = { Text(title) })
            }
        }
        rule.onNodeWithText(title).assertIsDisplayed()
    }

    @Test
    fun smallTopAppBar_default_positioning() {
        rule.setContent {
            Box(Modifier.testTag(TopAppBarTestTag)) {
                CustomizedTopAppBar(
                    navigationIcon = {
                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
                    },
                    title = {
                        Text("Title", Modifier.testTag(TitleTestTag))
                    },
                    actions = {
                        FakeIcon(Modifier.testTag(ActionsTestTag))
                    }
                )
            }
        }
        assertSmallDefaultPositioning()
    }

    @Test
    fun smallTopAppBar_noNavigationIcon_positioning() {
        rule.setContent {
            Box(Modifier.testTag(TopAppBarTestTag)) {
                CustomizedTopAppBar(
                    title = {
                        Text("Title", Modifier.testTag(TitleTestTag))
                    },
                    actions = {
                        FakeIcon(Modifier.testTag(ActionsTestTag))
                    }
                )
            }
        }
        assertSmallPositioningWithoutNavigation()
    }

    @Test
    fun smallTopAppBar_titleDefaultStyle() {
        var textStyle: TextStyle? = null
        var expectedTextStyle: TextStyle? = null
        rule.setContent {
            CustomizedTopAppBar(
                title = {
                    Text("Title")
                    textStyle = LocalTextStyle.current
                    expectedTextStyle = MaterialTheme.typography.titleMedium
                },
            )
        }
        assertThat(textStyle).isNotNull()
        assertThat(textStyle).isEqualTo(expectedTextStyle)
    }

    @Test
    fun smallTopAppBar_contentColor() {
        var titleColor: Color = Color.Unspecified
        var navigationIconColor: Color = Color.Unspecified
        var actionsColor: Color = Color.Unspecified
        var expectedTitleColor: Color = Color.Unspecified
        var expectedNavigationIconColor: Color = Color.Unspecified
        var expectedActionsColor: Color = Color.Unspecified

        rule.setContent {
            CustomizedTopAppBar(
                navigationIcon = {
                    FakeIcon(Modifier.testTag(NavigationIconTestTag))
                    navigationIconColor = LocalContentColor.current
                    expectedNavigationIconColor =
                        TopAppBarDefaults.topAppBarColors().navigationIconContentColor
                    // fraction = 0f to indicate no scroll.
                },
                title = {
                    Text("Title", Modifier.testTag(TitleTestTag))
                    titleColor = LocalContentColor.current
                    expectedTitleColor = TopAppBarDefaults.topAppBarColors().titleContentColor
                },
                actions = {
                    FakeIcon(Modifier.testTag(ActionsTestTag))
                    actionsColor = LocalContentColor.current
                    expectedActionsColor =
                        TopAppBarDefaults.topAppBarColors().actionIconContentColor
                }
            )
        }
        assertThat(navigationIconColor).isNotNull()
        assertThat(titleColor).isNotNull()
        assertThat(actionsColor).isNotNull()
        assertThat(navigationIconColor).isEqualTo(expectedNavigationIconColor)
        assertThat(titleColor).isEqualTo(expectedTitleColor)
        assertThat(actionsColor).isEqualTo(expectedActionsColor)
    }

    @Test
    fun largeTopAppBar_scrolled_positioning() {
        val content = @Composable { scrollBehavior: TopAppBarScrollBehavior? ->
            Box(Modifier.testTag(TopAppBarTestTag)) {
                CustomizedLargeTopAppBar(
                    navigationIcon = {
                        FakeIcon(Modifier.testTag(NavigationIconTestTag))
                    },
                    title = "Title",
                    actions = {
                        FakeIcon(Modifier.testTag(ActionsTestTag))
                    },
                    scrollBehavior = scrollBehavior,
                )
            }
        }
        assertLargeScrolledHeight(
            MaxHeightWithoutTitle + DefaultTitleHeight,
            MaxHeightWithoutTitle + DefaultTitleHeight,
            content,
        )
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Test
    fun topAppBar_enterAlways_allowHorizontalScroll() {
        lateinit var state: LazyListState
        rule.setContent {
            state = rememberLazyListState()
            MultiPageContent(TopAppBarDefaults.enterAlwaysScrollBehavior(), state)
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Test
    fun topAppBar_exitUntilCollapsed_allowHorizontalScroll() {
        lateinit var state: LazyListState
        rule.setContent {
            state = rememberLazyListState()
            MultiPageContent(TopAppBarDefaults.exitUntilCollapsedScrollBehavior(), state)
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Test
    fun topAppBar_pinned_allowHorizontalScroll() {
        lateinit var state: LazyListState
        rule.setContent {
            state = rememberLazyListState()
            MultiPageContent(
                TopAppBarDefaults.pinnedScrollBehavior(),
                state
            )
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeLeft() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(1)
        }

        rule.onNodeWithTag(LazyListTag).performTouchInput { swipeRight() }
        rule.runOnIdle {
            assertThat(state.firstVisibleItemIndex).isEqualTo(0)
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun MultiPageContent(scrollBehavior: TopAppBarScrollBehavior, state: LazyListState) {
        Scaffold(
            modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
            topBar = {
                CustomizedTopAppBar(
                    title = { Text(text = "Title") },
                )
            }
        ) { contentPadding ->
            LazyRow(
                Modifier
                    .fillMaxSize()
                    .testTag(LazyListTag), state
            ) {
                items(2) { page ->
                    LazyColumn(
                        modifier = Modifier.fillParentMaxSize(),
                        contentPadding = contentPadding
                    ) {
                        items(50) {
                            Text(
                                modifier = Modifier.fillParentMaxWidth(),
                                text = "Item #$page x $it"
                            )
                        }
                    }
                }
            }
        }
    }

    /**
     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
     * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
     * configuration and there is no navigation icon.
     */
    private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()

        val titleNode = rule.onNodeWithTag(TitleTestTag)
        // Title should be vertically centered
        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
        if (isCenteredTitle) {
            // Title should be horizontally centered
            titleNode.assertLeftPositionInRootIsEqualTo(
                (appBarBounds.width - titleBounds.width) / 2
            )
        } else {
            // Title should now be placed 16.dp from the start, as there is no navigation icon
            // 4.dp padding for the whole app bar + 12.dp inset
            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + 12.dp)
        }

        rule.onNodeWithTag(ActionsTestTag)
            // Action should still be placed at the end
            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
    }

    /**
     * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
     * [CenterAlignedTopAppBar].
     */
    private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
        val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
        val titleBounds = rule.onNodeWithTag(TitleTestTag).getUnclippedBoundsInRoot()
        val appBarBottomEdgeY = appBarBounds.top + appBarBounds.height

        rule.onNodeWithTag(NavigationIconTestTag)
            // Navigation icon should be 4.dp from the start
            .assertLeftPositionInRootIsEqualTo(AppBarStartAndEndPadding)
            // Navigation icon should be centered within the height of the app bar.
            .assertTopPositionInRootIsEqualTo(
                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
            )

        val titleNode = rule.onNodeWithTag(TitleTestTag)
        // Title should be vertically centered
        titleNode.assertTopPositionInRootIsEqualTo((appBarBounds.height - titleBounds.height) / 2)
        if (isCenteredTitle) {
            // Title should be horizontally centered
            titleNode.assertLeftPositionInRootIsEqualTo(
                (appBarBounds.width - titleBounds.width) / 2
            )
        } else {
            // Title should be 56.dp from the start
            // 4.dp padding for the whole app bar + 48.dp icon size + 4.dp title padding.
            titleNode.assertLeftPositionInRootIsEqualTo(4.dp + FakeIconSize + 4.dp)
        }

        rule.onNodeWithTag(ActionsTestTag)
            // Action should be placed at the end
            .assertLeftPositionInRootIsEqualTo(expectedActionPosition(appBarBounds.width))
            // Action should be 8.dp from the top
            .assertTopPositionInRootIsEqualTo(
                appBarBottomEdgeY - AppBarTopAndBottomPadding - FakeIconSize
            )
    }

    /**
     * Checks that changing values at a [CustomizedLargeTopAppBar] scroll behavior
     * affects the height of the app bar.
     *
     * This check partially and fully collapses the app bar to test its height.
     *
     * @param appBarMaxHeight the max height of the app bar [content]
     * @param appBarMinHeight the min height of the app bar [content]
     * @param content a Composable that adds a CustomizedLargeTopAppBar
     */
    @OptIn(ExperimentalMaterial3Api::class)
    private fun assertLargeScrolledHeight(
        appBarMaxHeight: Dp,
        appBarMinHeight: Dp,
        content: @Composable (TopAppBarScrollBehavior?) -> Unit
    ) {
        val fullyCollapsedOffsetDp = appBarMaxHeight - appBarMinHeight
        val partiallyCollapsedOffsetDp = fullyCollapsedOffsetDp / 3
        var partiallyCollapsedHeightOffsetPx = 0f
        var fullyCollapsedHeightOffsetPx = 0f
        lateinit var scrollBehavior: TopAppBarScrollBehavior
        rule.setContent {
            scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
            with(LocalDensity.current) {
                partiallyCollapsedHeightOffsetPx = partiallyCollapsedOffsetDp.toPx()
                fullyCollapsedHeightOffsetPx = fullyCollapsedOffsetDp.toPx()
            }

            content(scrollBehavior)
        }

        // Simulate a partially collapsed app bar.
        rule.runOnIdle {
            scrollBehavior.state.heightOffset = -partiallyCollapsedHeightOffsetPx
            scrollBehavior.state.contentOffset = -partiallyCollapsedHeightOffsetPx
        }
        rule.waitForIdle()
        rule.onNodeWithTag(TopAppBarTestTag)
            .assertHeightIsEqualTo(
                appBarMaxHeight - partiallyCollapsedOffsetDp
            )

        // Simulate a fully collapsed app bar.
        rule.runOnIdle {
            scrollBehavior.state.heightOffset = -fullyCollapsedHeightOffsetPx
            // Simulate additional content scroll beyond the max offset scroll.
            scrollBehavior.state.contentOffset =
                -fullyCollapsedHeightOffsetPx - partiallyCollapsedHeightOffsetPx
        }
        rule.waitForIdle()
        // Check that the app bar collapsed to its min height.
        rule.onNodeWithTag(TopAppBarTestTag).assertHeightIsEqualTo(appBarMinHeight)
    }

    /**
     * An [IconButton] with an [Icon] inside for testing positions.
     *
     * An [IconButton] is defaulted to be 48X48dp, while its child [Icon] is defaulted to 24x24dp.
     */
    private val FakeIcon = @Composable { modifier: Modifier ->
        IconButton(
            onClick = { /* doSomething() */ },
            modifier = modifier.semantics(mergeDescendants = true) {}
        ) {
            Icon(ColorPainter(Color.Red), null)
        }
    }

    private fun expectedActionPosition(appBarWidth: Dp): Dp =
        appBarWidth - AppBarStartAndEndPadding - FakeIconSize

    private val FakeIconSize = 48.dp
    private val AppBarStartAndEndPadding = 4.dp
    private val AppBarTopAndBottomPadding = (ContainerHeight - FakeIconSize) / 2

    private val LazyListTag = "lazyList"
    private val TopAppBarTestTag = "bar"
    private val NavigationIconTestTag = "navigationIcon"
    private val TitleTestTag = "title"
    private val ActionsTestTag = "actions"
}
+51 −0
Original line number Diff line number Diff line
@@ -16,13 +16,28 @@

package com.android.settingslib.spa.testutils

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ComposeTimeoutException
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.getUnclippedBoundsInRoot
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.height
import androidx.compose.ui.unit.width
import com.android.settingslib.spa.framework.theme.SettingsTheme

/** Blocks until the found a semantics node that match the given condition. */
fun ComposeContentTestRule.waitUntilExists(matcher: SemanticsMatcher) = waitUntil {
@@ -39,3 +54,39 @@ fun ComposeContentTestRule.delay(timeoutMillis: Long = 1_000) = try {
/** Finds a text node that within dialog. */
fun ComposeContentTestRule.onDialogText(text: String): SemanticsNodeInteraction =
    onNode(hasAnyAncestor(isDialog()) and hasText(text))

fun ComposeTestRule.rootWidth(): Dp = onRoot().getUnclippedBoundsInRoot().width

fun ComposeTestRule.rootHeight(): Dp = onRoot().getUnclippedBoundsInRoot().height

/**
 * Constant to emulate very big but finite constraints
 */
private val sizeAssertionMaxSize = 5000.dp

private const val SIZE_ASSERTION_TAG = "containerForSizeAssertion"

fun ComposeContentTestRule.setContentForSizeAssertions(
    parentMaxWidth: Dp = sizeAssertionMaxSize,
    parentMaxHeight: Dp = sizeAssertionMaxSize,
    // TODO : figure out better way to make it flexible
    content: @Composable () -> Unit
): SemanticsNodeInteraction {
    setContent {
        SettingsTheme {
            Surface {
                Box {
                    Box(
                        Modifier
                            .sizeIn(maxWidth = parentMaxWidth, maxHeight = parentMaxHeight)
                            .testTag(SIZE_ASSERTION_TAG)
                    ) {
                        content()
                    }
                }
            }
        }
    }

    return onNodeWithTag(SIZE_ASSERTION_TAG)
}