All Downloads are FREE. Search and download functionalities are using the official Maven repository.

androidAndroidTest.androidx.constraintlayout.compose.ConstraintLayoutTest.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 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 androidx.constraintlayout.compose

import android.content.Context
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.*
import androidx.compose.ui.node.Ref
import androidx.compose.ui.platform.InspectableValue
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.ValueElement
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.*
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.junit.*
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.runner.RunWith
import kotlin.math.roundToInt

@MediumTest
@RunWith(AndroidJUnit4::class)
class ConstraintLayoutTest {
    @get:Rule
    val rule = createComposeRule()

    var displaySize: IntSize = IntSize.Zero

    // region sizing tests

    @Before
    fun before() {
        isDebugInspectorInfoEnabled = true
        displaySize = ApplicationProvider
            .getApplicationContext().resources.displayMetrics.let {
                IntSize(it.widthPixels, it.heightPixels)
            }
    }

    @After
    fun after() {
        isDebugInspectorInfoEnabled = false
    }

    @Test
    fun dividerMatchTextHeight_spread() = with(rule.density) {
        val aspectRatioBoxSize = Ref()
        val dividerSize = Ref()

        rule.setContent {
            ConstraintLayout(
                // Make CL fixed width and wrap content height.
                modifier = Modifier.fillMaxWidth()
            ) {
                val (aspectRatioBox, divider) = createRefs()
                val guideline = createGuidelineFromAbsoluteLeft(0.5f)

                Box(
                    Modifier
                        .constrainAs(aspectRatioBox) {
                            centerTo(parent)
                            start.linkTo(guideline)
                            width = Dimension.preferredWrapContent
                            height = Dimension.wrapContent
                        }
                        // Try to be large to make wrap content impossible.
                        .width((displaySize.width).toDp())
                        // This could be any (width in height out child) e.g. text
                        .aspectRatio(2f)
                        .onGloballyPositioned { coordinates ->
                            aspectRatioBoxSize.value = coordinates.size
                        }
                )
                Box(
                    Modifier
                        .constrainAs(divider) {
                            centerTo(parent)
                            width = Dimension.value(1.dp)
                            height = Dimension.fillToConstraints
                        }
                        .onGloballyPositioned { coordinates ->
                            dividerSize.value = coordinates.size
                        }
                )
            }
        }

        rule.runOnIdle {
            // The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
            assertEquals(
                (displaySize.width / 2),
                aspectRatioBoxSize.value!!.width
            )
            // Aspect ratio is preserved.
            assertEquals(
                (displaySize.width / 2 / 2),
                aspectRatioBoxSize.value!!.height
            )
            // Divider has fixed width 1.dp in constraint set.
            assertEquals(1.dp.roundToPx(), dividerSize.value!!.width)
            // Divider has spread height so it should spread to fill the height of the CL,
            // which in turns is given by the size of the aspect ratio box.
            assertEquals(aspectRatioBoxSize.value!!.height, dividerSize.value!!.height)
        }
    }

    @Test
    fun dividerMatchTextHeight_spread_withPreferredWrapHeightText() = with(rule.density) {
        val aspectRatioBoxSize = Ref()
        val dividerSize = Ref()
        rule.setContent {
            ConstraintLayout(
                // Make CL fixed width and wrap content height.
                modifier = Modifier.fillMaxWidth()
            ) {
                val (aspectRatioBox, divider) = createRefs()
                val guideline = createGuidelineFromAbsoluteLeft(0.5f)

                Box(
                    Modifier
                        .constrainAs(aspectRatioBox) {
                            centerTo(parent)
                            start.linkTo(guideline)
                            width = Dimension.preferredWrapContent
                            height = Dimension.preferredWrapContent
                        }
                        // Try to be large to make wrap content impossible.
                        .width((displaySize.width).toDp())
                        // This could be any (width in height out child) e.g. text
                        .aspectRatio(2f)
                        .onGloballyPositioned { coordinates ->
                            aspectRatioBoxSize.value = coordinates.size
                        }
                )
                Box(
                    Modifier
                        .constrainAs(divider) {
                            centerTo(parent)
                            width = Dimension.value(1.dp)
                            height = Dimension.fillToConstraints
                        }
                        .onGloballyPositioned { coordinates ->
                            dividerSize.value = coordinates.size
                        }
                )
            }
        }

        rule.runOnIdle {
            // The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
            assertEquals(
                (displaySize.width / 2),
                aspectRatioBoxSize.value!!.width
            )
            // Aspect ratio is preserved.
            assertEquals(
                (displaySize.width / 2 / 2),
                aspectRatioBoxSize.value!!.height
            )
            // Divider has fixed width 1.dp in constraint set.
            assertEquals(1.dp.roundToPx(), dividerSize.value!!.width)
            // Divider has spread height so it should spread to fill the height of the CL,
            // which in turns is given by the size of the aspect ratio box.
            assertEquals(aspectRatioBoxSize.value!!.height, dividerSize.value!!.height)
        }
    }

    @Test
    fun dividerMatchTextHeight_percent() = with(rule.density) {
        val aspectRatioBoxSize = Ref()
        val dividerSize = Ref()
        rule.setContent {
            ConstraintLayout(
                // Make CL fixed width and wrap content height.
                modifier = Modifier.fillMaxWidth()
            ) {
                val (aspectRatioBox, divider) = createRefs()
                val guideline = createGuidelineFromAbsoluteLeft(0.5f)

                Box(
                    Modifier
                        .constrainAs(aspectRatioBox) {
                            centerTo(parent)
                            start.linkTo(guideline)
                            width = Dimension.preferredWrapContent
                            height = Dimension.wrapContent
                        }
                        // Try to be large to make wrap content impossible.
                        .width((displaySize.width).toDp())
                        // This could be any (width in height out child) e.g. text
                        .aspectRatio(2f)
                        .onGloballyPositioned { coordinates ->
                            aspectRatioBoxSize.value = coordinates.size
                        }
                )
                Box(
                    Modifier
                        .constrainAs(divider) {
                            centerTo(parent)
                            width = Dimension.value(1.dp)
                            height = Dimension.percent(0.8f)
                        }
                        .onGloballyPositioned { coordinates ->
                            dividerSize.value = coordinates.size
                        }
                )
            }
        }

        rule.runOnIdle {
            // The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
            assertEquals(
                (displaySize.width / 2),
                aspectRatioBoxSize.value!!.width
            )
            // Aspect ratio is preserved.
            assertEquals(
                (displaySize.width / 2 / 2),
                aspectRatioBoxSize.value!!.height
            )
            // Divider has fixed width 1.dp in constraint set.
            assertEquals(1.dp.roundToPx(), dividerSize.value!!.width)
            // Divider has percent height so it should spread to fill 0.8 of the height of the CL,
            // which in turns is given by the size of the aspect ratio box.
            assertEquals(
                (aspectRatioBoxSize.value!!.height * 0.8f).roundToInt(),
                dividerSize.value!!.height
            )
        }
    }

    @Test
    @Ignore
    fun dividerMatchTextHeight_inWrapConstraintLayout_longText() = with(rule.density) {
        val aspectRatioBoxSize = Ref()
        val dividerSize = Ref()
        rule.setContent {
            // CL is wrap content.
            ConstraintLayout {
                val (aspectRatioBox, divider) = createRefs()
                val guideline = createGuidelineFromAbsoluteLeft(0.5f)

                Box(
                    Modifier
                        .constrainAs(aspectRatioBox) {
                            centerTo(parent)
                            start.linkTo(guideline)
                            width = Dimension.preferredWrapContent
                            height = Dimension.wrapContent
                        }
                        // Try to be large to make wrap content impossible.
                        .width((displaySize.width).toDp())
                        // This could be any (width in height out child) e.g. text
                        .aspectRatio(2f)
                        .onGloballyPositioned { coordinates ->
                            aspectRatioBoxSize.value = coordinates.size
                        }
                )
                Box(
                    Modifier
                        .constrainAs(divider) {
                            centerTo(parent)
                            width = Dimension.value(1.dp)
                            height = Dimension.percent(0.8f)
                        }
                        .onGloballyPositioned { coordinates ->
                            dividerSize.value = coordinates.size
                        }
                )
            }
        }

        rule.runOnIdle {
            // The aspect ratio could not wrap and it is wrap suggested, so it respects constraints.
            assertEquals(
                (displaySize.width / 2),
                aspectRatioBoxSize.value!!.width
            )
            // Aspect ratio is preserved.
            assertEquals(
                (displaySize.width / 2 / 2),
                aspectRatioBoxSize.value!!.height
            )
            // Divider has fixed width 1.dp in constraint set.
            assertEquals(1.dp.roundToPx(), dividerSize.value!!.width)
            // Divider has percent height so it should spread to fill 0.8 of the height of the CL,
            // which in turns is given by the size of the aspect ratio box.
            // TODO(popam; b/150277566): uncomment
            assertEquals(
                "broken, display size ${displaySize.width}x${displaySize.height} aspect height ${aspectRatioBoxSize.value!!.width}x${aspectRatioBoxSize.value!!.height}, divider: ${dividerSize.value!!.height}",
                (aspectRatioBoxSize.value!!.height * 0.8f).roundToInt(),
                dividerSize.value!!.height
            )
            assertEquals(
                "broken, aspect height ${aspectRatioBoxSize.value!!.width}x${aspectRatioBoxSize.value!!.height}, divider: ${dividerSize.value!!.height}",
                aspectRatioBoxSize.value!!.width,
                540
            )
        }
    }

    @Test
    fun dividerMatchTextHeight_inWrapConstraintLayout_shortText() = with(rule.density) {
        val constraintLayoutSize = Ref()
        val aspectRatioBoxSize = Ref()
        val dividerSize = Ref()
        val size = 40.toDp()
        rule.setContent {
            ConstraintLayout(
                // CL is wrapping width and height.
                modifier = Modifier.onGloballyPositioned {
                    constraintLayoutSize.value = it.size
                }
            ) {
                val (aspectRatioBox, divider) = createRefs()
                val guideline = createGuidelineFromAbsoluteLeft(0.5f)

                Box(
                    Modifier
                        .constrainAs(aspectRatioBox) {
                            centerTo(parent)
                            start.linkTo(guideline)
                            width = Dimension.preferredWrapContent
                            height = Dimension.wrapContent
                        }
                        // Small width for the CL to wrap it.
                        .width(size)
                        // This could be any (width in height out child) e.g. text
                        .aspectRatio(2f)
                        .onGloballyPositioned { coordinates ->
                            aspectRatioBoxSize.value = coordinates.size
                        }
                )
                Box(
                    Modifier
                        .constrainAs(divider) {
                            centerTo(parent)
                            width = Dimension.value(1.dp)
                            height = Dimension.fillToConstraints
                        }
                        .onGloballyPositioned { coordinates ->
                            dividerSize.value = coordinates.size
                        }
                )
            }
        }

        rule.runOnIdle {
            // The width of the ConstraintLayout should be twice the width of the aspect ratio box.
            assertEquals(size.roundToPx() * 2, constraintLayoutSize.value!!.width)
            // The height of the ConstraintLayout should be the height of the aspect ratio box.
            assertEquals(size.roundToPx() / 2, constraintLayoutSize.value!!.height)
            // The aspect ratio gets the requested size.
            assertEquals(size.roundToPx(), aspectRatioBoxSize.value!!.width)
            // Aspect ratio is preserved.
            assertEquals(size.roundToPx() / 2, aspectRatioBoxSize.value!!.height)
            // Divider has fixed width 1.dp in constraint set.
            assertEquals(1.dp.roundToPx(), dividerSize.value!!.width)
            // Divider should have the height of the aspect ratio box.
            assertEquals(aspectRatioBoxSize.value!!.height, dividerSize.value!!.height)
        }
    }

    // endregion

    // region positioning tests

    @Test
    @Ignore
    fun testConstraintLayout_withInlineDSL() = with(rule.density) {
        val boxSize = 100
        val offset = 150

        val position = Array(3) { Ref() }

        rule.setContent {
            ConstraintLayout(Modifier.fillMaxSize()) {
                val (box0, box1, box2) = createRefs()
                Box(
                    Modifier
                        .constrainAs(box0) {
                            centerTo(parent)
                        }
                        .size(boxSize.toDp(), boxSize.toDp())
                        .onGloballyPositioned {
                            position[0].value = it.positionInRoot()
                        }
                )
                val half = createGuidelineFromAbsoluteLeft(fraction = 0.5f)
                Box(
                    Modifier
                        .constrainAs(box1) {
                            start.linkTo(half, margin = offset.toDp())
                            bottom.linkTo(box0.top)
                        }
                        .size(boxSize.toDp(), boxSize.toDp())
                        .onGloballyPositioned {
                            position[1].value = it.positionInRoot()
                        }
                )
                Box(
                    Modifier
                        .constrainAs(box2) {
                            start.linkTo(parent.start, margin = offset.toDp())
                            bottom.linkTo(parent.bottom, margin = offset.toDp())
                        }
                        .size(boxSize.toDp(), boxSize.toDp())
                        .onGloballyPositioned {
                            position[2].value = it.positionInRoot()
                        }
                )
            }
        }

        val displayWidth = displaySize.width
        val displayHeight = displaySize.height

        rule.runOnIdle {
            assertEquals(
                Offset(
                    ((displayWidth - boxSize) / 2).toFloat(),
                    ((displayHeight - boxSize) / 2).toFloat()
                ),
                position[0].value
            )
            assertEquals(
                Offset(
                    (displayWidth / 2 + offset).toFloat(),
                    ((displayHeight - boxSize) / 2 - boxSize).toFloat()
                ),
                position[1].value
            )
            assertEquals(
                Offset(
                    offset.toFloat(),
                    (displayHeight - boxSize - offset).toFloat()
                ),
                position[2].value
            )
        }
    }

    @Test
    @Ignore
    fun testConstraintLayout_withConstraintSet() = with(rule.density) {
        val boxSize = 100
        val offset = 150

        val position = Array(3) { Ref() }

        rule.setContent {
            ConstraintLayout(
                ConstraintSet {
                    val box0 = createRefFor("box0")
                    val box1 = createRefFor("box1")
                    val box2 = createRefFor("box2")

                    constrain(box0) {
                        centerTo(parent)
                    }

                    val half = createGuidelineFromAbsoluteLeft(fraction = 0.5f)
                    constrain(box1) {
                        start.linkTo(half, margin = offset.toDp())
                        bottom.linkTo(box0.top)
                    }

                    constrain(box2) {
                        start.linkTo(parent.start, margin = offset.toDp())
                        bottom.linkTo(parent.bottom, margin = offset.toDp())
                    }
                },
                Modifier.fillMaxSize()
            ) {
                for (i in 0..2) {
                    Box(
                        Modifier
                            .layoutId("box$i")
                            .size(boxSize.toDp(), boxSize.toDp())
                            .onGloballyPositioned {
                                position[i].value = it.positionInRoot()
                            }
                    )
                }
            }
        }

        val displayWidth = displaySize.width
        val displayHeight = displaySize.height

        rule.runOnIdle {
            assertEquals(
                Offset(
                    (displayWidth - boxSize) / 2f,
                    (displayHeight - boxSize) / 2f
                ),
                position[0].value
            )
            assertEquals(
                Offset(
                    (displayWidth / 2f + offset).toFloat(),
                    ((displayHeight - boxSize) / 2 - boxSize).toFloat()
                ),
                position[1].value
            )
            assertEquals(
                Offset(
                    offset.toFloat(),
                    (displayHeight - boxSize - offset).toFloat()
                ),
                position[2].value
            )
        }
    }

    @Test
    @Ignore
    fun testConstraintLayout_rtl() = with(rule.density) {
        val boxSize = 100
        val offset = 150

        val position = Array(3) { Ref() }

        rule.setContent {
            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                ConstraintLayout(Modifier.fillMaxSize()) {
                    val (box0, box1, box2) = createRefs()
                    Box(
                        Modifier
                            .constrainAs(box0) {
                                centerTo(parent)
                            }
                            .size(boxSize.toDp(), boxSize.toDp())
                            .onGloballyPositioned {
                                position[0].value = it.positionInRoot()
                            }
                    )
                    val half = createGuidelineFromAbsoluteLeft(fraction = 0.5f)
                    Box(
                        Modifier
                            .constrainAs(box1) {
                                start.linkTo(half, margin = offset.toDp())
                                bottom.linkTo(box0.top)
                            }
                            .size(boxSize.toDp(), boxSize.toDp())
                            .onGloballyPositioned {
                                position[1].value = it.positionInRoot()
                            }
                    )
                    Box(
                        Modifier
                            .constrainAs(box2) {
                                start.linkTo(parent.start, margin = offset.toDp())
                                bottom.linkTo(parent.bottom, margin = offset.toDp())
                            }
                            .size(boxSize.toDp(), boxSize.toDp())
                            .onGloballyPositioned {
                                position[2].value = it.positionInRoot()
                            }
                    )
                }
            }
        }

        val displayWidth = displaySize.width
        val displayHeight = displaySize.height

        rule.runOnIdle {
            assertEquals(
                Offset(
                    (displayWidth - boxSize) / 2f,
                    (displayHeight - boxSize) / 2f
                ),
                position[0].value
            )
            assertEquals(
                Offset(
                    (displayWidth / 2 - offset - boxSize).toFloat(),
                    ((displayHeight - boxSize) / 2 - boxSize).toFloat()
                ),
                position[1].value
            )
            assertEquals(
                Offset(
                    (displayWidth - offset - boxSize).toFloat(),
                    (displayHeight - boxSize - offset).toFloat()
                ),
                position[2].value
            )
        }
    }

    @Test
    fun testConstraintLayout_helpers_ltr() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(8) { 0f }
        rule.setContent {
            ConstraintLayout(Modifier.size(size)) {
                val guidelines = arrayOf(
                    createGuidelineFromStart(offset),
                    createGuidelineFromAbsoluteLeft(offset),
                    createGuidelineFromEnd(offset),
                    createGuidelineFromAbsoluteRight(offset),
                    createGuidelineFromStart(0.25f),
                    createGuidelineFromAbsoluteLeft(0.25f),
                    createGuidelineFromEnd(0.25f),
                    createGuidelineFromAbsoluteRight(0.25f)
                )

                guidelines.forEachIndexed { index, guideline ->
                    val ref = createRef()
                    Box(
                        Modifier
                            .size(1.dp)
                            .constrainAs(ref) {
                                absoluteLeft.linkTo(guideline)
                            }
                            .onGloballyPositioned {
                                position[index] = it.positionInParent().x
                            }
                    )
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(50f, position[0])
            Assert.assertEquals(50f, position[1])
            Assert.assertEquals(150f, position[2])
            Assert.assertEquals(150f, position[3])
            Assert.assertEquals(50f, position[4])
            Assert.assertEquals(50f, position[5])
            Assert.assertEquals(150f, position[6])
            Assert.assertEquals(150f, position[7])
        }
    }

    @Test
    fun testConstraintLayout_helpers_rtl() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(8) { 0f }
        rule.setContent {
            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                ConstraintLayout(Modifier.size(size)) {
                    val guidelines = arrayOf(
                        createGuidelineFromStart(offset),
                        createGuidelineFromAbsoluteLeft(offset),
                        createGuidelineFromEnd(offset),
                        createGuidelineFromAbsoluteRight(offset),
                        createGuidelineFromStart(0.25f),
                        createGuidelineFromAbsoluteLeft(0.25f),
                        createGuidelineFromEnd(0.25f),
                        createGuidelineFromAbsoluteRight(0.25f)
                    )

                    guidelines.forEachIndexed { index, guideline ->
                        val ref = createRef()
                        Box(
                            Modifier
                                .size(1.dp)
                                .constrainAs(ref) {
                                    absoluteLeft.linkTo(guideline)
                                }
                                .onGloballyPositioned {
                                    position[index] = it.positionInParent().x
                                }
                        )
                    }
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(150f, position[0])
            Assert.assertEquals(50f, position[1])
            Assert.assertEquals(50f, position[2])
            Assert.assertEquals(150f, position[3])
            Assert.assertEquals(150f, position[4])
            Assert.assertEquals(50f, position[5])
            Assert.assertEquals(50f, position[6])
            Assert.assertEquals(150f, position[7])
        }
    }

    @Test
    fun testConstraintLayout_barriers_ltr() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(4) { 0f }
        rule.setContent {
            ConstraintLayout(Modifier.size(size)) {
                val (box1, box2) = createRefs()
                val guideline1 = createGuidelineFromAbsoluteLeft(offset)
                val guideline2 = createGuidelineFromAbsoluteRight(offset)
                Box(
                    Modifier
                        .size(1.toDp())
                        .constrainAs(box1) {
                            absoluteLeft.linkTo(guideline1)
                        }
                )
                Box(
                    Modifier
                        .size(1.toDp())
                        .constrainAs(box2) {
                            absoluteLeft.linkTo(guideline2)
                        }
                )

                val barriers = arrayOf(
                    createStartBarrier(box1, box2),
                    createAbsoluteLeftBarrier(box1, box2),
                    createEndBarrier(box1, box2),
                    createAbsoluteRightBarrier(box1, box2)
                )

                barriers.forEachIndexed { index, barrier ->
                    val ref = createRef()
                    Box(
                        Modifier
                            .size(1.dp)
                            .constrainAs(ref) {
                                absoluteLeft.linkTo(barrier)
                            }
                            .onGloballyPositioned {
                                position[index] = it.positionInParent().x
                            }
                    )
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(50f, position[0])
            Assert.assertEquals(50f, position[1])
            Assert.assertEquals(151f, position[2])
            Assert.assertEquals(151f, position[3])
        }
    }

    @Test
    fun testConstraintLayout_barriers_rtl() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(4) { 0f }
        rule.setContent {
            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                ConstraintLayout(Modifier.size(size)) {
                    val (box1, box2) = createRefs()
                    val guideline1 = createGuidelineFromAbsoluteLeft(offset)
                    val guideline2 = createGuidelineFromAbsoluteRight(offset)
                    Box(
                        Modifier
                            .size(1.toDp())
                            .constrainAs(box1) {
                                absoluteLeft.linkTo(guideline1)
                            }
                    )
                    Box(
                        Modifier
                            .size(1.toDp())
                            .constrainAs(box2) {
                                absoluteLeft.linkTo(guideline2)
                            }
                    )

                    val barriers = arrayOf(
                        createStartBarrier(box1, box2),
                        createAbsoluteLeftBarrier(box1, box2),
                        createEndBarrier(box1, box2),
                        createAbsoluteRightBarrier(box1, box2)
                    )

                    barriers.forEachIndexed { index, barrier ->
                        val ref = createRef()
                        Box(
                            Modifier
                                .size(1.dp)
                                .constrainAs(ref) {
                                    absoluteLeft.linkTo(barrier)
                                }
                                .onGloballyPositioned {
                                    position[index] = it.positionInParent().x
                                }
                        )
                    }
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(151f, position[0])
            Assert.assertEquals(50f, position[1])
            Assert.assertEquals(50f, position[2])
            Assert.assertEquals(151f, position[3])
        }
    }

    fun listAnchors(box: ConstrainedLayoutReference): List Unit> {
        // TODO(172055763) directly construct an immutable list when Lint supports it
        val anchors = mutableListOf Unit>()
        anchors.add({ start.linkTo(box.start) })
        anchors.add({ absoluteLeft.linkTo(box.start) })
        anchors.add({ start.linkTo(box.absoluteLeft) })
        anchors.add({ absoluteLeft.linkTo(box.absoluteLeft) })
        anchors.add({ end.linkTo(box.start) })
        anchors.add({ absoluteRight.linkTo(box.start) })
        anchors.add({ end.linkTo(box.absoluteLeft) })
        anchors.add({ absoluteRight.linkTo(box.absoluteLeft) })
        anchors.add({ start.linkTo(box.end) })
        anchors.add({ absoluteLeft.linkTo(box.end) })
        anchors.add({ start.linkTo(box.absoluteRight) })
        anchors.add({ absoluteLeft.linkTo(box.absoluteRight) })
        anchors.add({ end.linkTo(box.end) })
        anchors.add({ absoluteRight.linkTo(box.end) })
        anchors.add({ end.linkTo(box.absoluteRight) })
        anchors.add({ absoluteRight.linkTo(box.absoluteRight) })
        return anchors
    }

    @Test
    fun testConstraintLayout_anchors_ltr() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(16) { 0f }
        rule.setContent {
            ConstraintLayout(Modifier.size(size)) {
                val box = createRef()
                val guideline = createGuidelineFromAbsoluteLeft(offset)
                Box(
                    Modifier
                        .size(1.toDp())
                        .constrainAs(box) {
                            absoluteLeft.linkTo(guideline)
                        }
                )

                val anchors = listAnchors(box)

                anchors.forEachIndexed { index, anchor ->
                    val ref = createRef()
                    Box(
                        Modifier
                            .size(1.toDp())
                            .constrainAs(ref) {
                                anchor()
                            }
                            .onGloballyPositioned {
                                position[index] = it.positionInParent().x
                            }
                    )
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(50f, position[0])
            Assert.assertEquals(50f, position[1])
            Assert.assertEquals(50f, position[2])
            Assert.assertEquals(50f, position[3])
            Assert.assertEquals(49f, position[4])
            Assert.assertEquals(49f, position[5])
            Assert.assertEquals(49f, position[6])
            Assert.assertEquals(49f, position[7])
            Assert.assertEquals(51f, position[8])
            Assert.assertEquals(51f, position[9])
            Assert.assertEquals(51f, position[10])
            Assert.assertEquals(51f, position[11])
            Assert.assertEquals(50f, position[12])
            Assert.assertEquals(50f, position[13])
            Assert.assertEquals(50f, position[14])
            Assert.assertEquals(50f, position[15])
        }
    }

    @Test
    fun testConstraintLayout_anchors_rtl() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(16) { 0f }
        rule.setContent {
            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
                ConstraintLayout(Modifier.size(size)) {
                    val box = createRef()
                    val guideline = createGuidelineFromAbsoluteLeft(offset)
                    Box(
                        Modifier
                            .size(1.toDp())
                            .constrainAs(box) {
                                absoluteLeft.linkTo(guideline)
                            }
                    )

                    val anchors = listAnchors(box)

                    anchors.forEachIndexed { index, anchor ->
                        val ref = createRef()
                        Box(
                            Modifier
                                .size(1.toDp())
                                .constrainAs(ref) {
                                    anchor()
                                }
                                .onGloballyPositioned {
                                    position[index] = it.positionInParent().x
                                }
                        )
                    }
                }
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(50f, position[0])
            Assert.assertEquals(51f, position[1])
            Assert.assertEquals(49f, position[2])
            Assert.assertEquals(50f, position[3])
            Assert.assertEquals(51f, position[4])
            Assert.assertEquals(50f, position[5])
            Assert.assertEquals(50f, position[6])
            Assert.assertEquals(49f, position[7])
            Assert.assertEquals(49f, position[8])
            Assert.assertEquals(50f, position[9])
            Assert.assertEquals(50f, position[10])
            Assert.assertEquals(51f, position[11])
            Assert.assertEquals(50f, position[12])
            Assert.assertEquals(49f, position[13])
            Assert.assertEquals(51f, position[14])
            Assert.assertEquals(50f, position[15])
        }
    }

    @Test
    fun testConstraintLayout_barriers_margins() = with(rule.density) {
        val size = 200.toDp()
        val offset = 50.toDp()

        val position = Array(2) { Offset(0f, 0f) }
        rule.setContent {
            ConstraintLayout(Modifier.size(size)) {
                val box = createRef()
                val guideline1 = createGuidelineFromAbsoluteLeft(offset)
                val guideline2 = createGuidelineFromTop(offset)
                Box(
                    Modifier
                        .size(1.toDp())
                        .constrainAs(box) {
                            absoluteLeft.linkTo(guideline1)
                            top.linkTo(guideline2)
                        }
                )

                val leftBarrier = createAbsoluteLeftBarrier(box, margin = 10.toDp())
                val topBarrier = createTopBarrier(box, margin = 10.toDp())
                val rightBarrier = createAbsoluteRightBarrier(box, margin = 10.toDp())
                val bottomBarrier = createBottomBarrier(box, margin = 10.toDp())

                Box(
                    Modifier
                        .size(1.dp)
                        .constrainAs(createRef()) {
                            absoluteLeft.linkTo(leftBarrier)
                            top.linkTo(topBarrier)
                        }
                        .onGloballyPositioned {
                            position[0] = it.positionInParent()
                        }
                )

                Box(
                    Modifier
                        .size(1.dp)
                        .constrainAs(createRef()) {
                            absoluteLeft.linkTo(rightBarrier)
                            top.linkTo(bottomBarrier)
                        }
                        .onGloballyPositioned {
                            position[1] = it.positionInParent()
                        }
                )
            }
        }

        rule.runOnIdle {
            Assert.assertEquals(Offset(60f, 60f), position[0])
            Assert.assertEquals(Offset(61f, 61f), position[1])
        }
    }

    @Test
    fun links_canBeOverridden() = with(rule.density) {
        rule.setContent {
            ConstraintLayout(Modifier.width(10.dp)) {
                val box = createRef()
                Box(
                    Modifier
                        .constrainAs(box) {
                            start.linkTo(parent.end)
                            start.linkTo(parent.start)
                        }
                        .onGloballyPositioned {
                            Assert.assertEquals(0f, it.positionInParent().x)
                        }
                )
            }
        }
        rule.waitForIdle()
    }

    @Test
    @Ignore
    fun chains_defaultOutsideConstraintsCanBeOverridden() = with(rule.density) {
        val size = 100.toDp()
        val boxSize = 10.toDp()
        val guidelinesOffset = 20.toDp()
        rule.setContent {
            ConstraintLayout(Modifier.size(size)) {
                val (box1, box2) = createRefs()
                val startGuideline = createGuidelineFromStart(guidelinesOffset)
                val topGuideline = createGuidelineFromTop(guidelinesOffset)
                val endGuideline = createGuidelineFromEnd(guidelinesOffset)
                val bottomGuideline = createGuidelineFromBottom(guidelinesOffset)
                createHorizontalChain(box1, box2, chainStyle = ChainStyle.SpreadInside)
                createVerticalChain(box1, box2, chainStyle = ChainStyle.SpreadInside)
                Box(
                    Modifier
                        .size(boxSize)
                        .constrainAs(box1) {
                            start.linkTo(startGuideline)
                            top.linkTo(topGuideline)
                        }
                        .onGloballyPositioned {
                            Assert.assertEquals(20f, it.boundsInParent().left)
                            Assert.assertEquals(20f, it.boundsInParent().top)
                        }
                )
                Box(
                    Modifier
                        .size(boxSize)
                        .constrainAs(box2) {
                            end.linkTo(endGuideline)
                            bottom.linkTo(bottomGuideline)
                        }
                        .onGloballyPositioned {
                            Assert.assertEquals(80f, it.boundsInParent().right)
                            Assert.assertEquals(80f, it.boundsInParent().bottom)
                        }
                )
            }
        }
        rule.waitForIdle()
    }

    @Test(expected = Test.None::class)
    fun testConstraintLayout_inlineDSL_recompositionDoesNotCrash() = with(rule.density) {
        val first = mutableStateOf(true)
        rule.setContent {
            ConstraintLayout {
                val box = createRef()
                if (first.value) {
                    Box(Modifier.constrainAs(box) { })
                } else {
                    Box(Modifier.constrainAs(box) { })
                }
            }
        }
        rule.runOnIdle {
            first.value = false
        }
        rule.waitForIdle()
    }

    @Test(expected = Test.None::class)
    fun testConstraintLayout_ConstraintSetDSL_recompositionDoesNotCrash() = with(rule.density) {
        val first = mutableStateOf(true)
        rule.setContent {
            ConstraintLayout(
                constraintSet = ConstraintSet {
                    val box = createRefFor("box")
                    constrain(box) { }
                },
            ) {
                if (first.value) {
                    Box(Modifier.layoutId("box"))
                } else {
                    Box(Modifier.layoutId("box"))
                }
            }
        }
        rule.runOnIdle {
            first.value = false
        }
        rule.waitForIdle()
    }

    @Test(expected = Test.None::class)
    fun testConstraintLayout_inlineDSL_remeasureDoesNotCrash() = with(rule.density) {
        val first = mutableStateOf(true)
        rule.setContent {
            ConstraintLayout(if (first.value) Modifier else Modifier.padding(10.dp)) {
                Box(if (first.value) Modifier else Modifier.size(20.dp))
            }
        }
        rule.runOnIdle {
            first.value = false
        }
        rule.waitForIdle()
    }

    @Test(expected = Test.None::class)
    fun testConstraintLayout_ConstraintSetDSL_remeasureDoesNotCrash() = with(rule.density) {
        val first = mutableStateOf(true)
        rule.setContent {
            ConstraintLayout(
                modifier = if (first.value) Modifier else Modifier.padding(10.dp),
                constraintSet = ConstraintSet { }
            ) {
                Box(if (first.value) Modifier else Modifier.size(20.dp))
            }
        }
        rule.runOnIdle {
            first.value = false
        }
        rule.waitForIdle()
    }

    @Test
    fun testConstraintLayout_doesNotCrashWhenOnlyContentIsRecomposed() {
        var smallSize by mutableStateOf(true)
        rule.setContent {
            Box {
                ConstraintLayout {
                    val (box1, box2) = createRefs()
                    val barrier = createBottomBarrier(box1)
                    Box(
                        Modifier
                            .height(if (smallSize) 30.dp else 40.dp)
                            .constrainAs(box1) {})
                    Box(Modifier)
                }
            }
        }
        rule.runOnIdle {
            smallSize = false
        }
        rule.waitForIdle()
    }

    @Test
    fun testInspectorValue() {
        rule.setContent {
            ConstraintLayout(Modifier.width(10.dp)) {
                val ref = createRef()
                val block: ConstrainScope.() -> Unit = {}
                val modifier = Modifier.constrainAs(ref, block) as InspectableValue

                assertEquals("constrainAs", modifier.nameFallback)
                assertNull(modifier.valueOverride)
                val inspectableElements = modifier.inspectableElements.toList()
                assertEquals(2, inspectableElements.size)
                assertEquals(ValueElement("ref", ref), inspectableElements[0])
                assertEquals(ValueElement("constrainBlock", block), inspectableElements[1])
            }
        }
    }

    @Test
    fun testConstraintLayout_doesNotRemeasureUnnecessarily() {
        var first by mutableStateOf(true)
        var dslExecutions = 0
        rule.setContent {
            val dslExecuted = remember { { ++dslExecutions } }
            ConstraintLayout {
                val (box1) = createRefs()
                val box2 = createRef()
                val guideline = createGuidelineFromStart(0.5f)
                val barrier = createAbsoluteLeftBarrier(box1)

                // Make sure the content is reexecuted when first changes.
                first

                // If the reference changed, we would remeasure and reexecute the DSL.
                Box(Modifier.constrainAs(box1) {})
                // If the guideline, barrier or anchor changed or were inferred as un@Stable, we
                // would remeasure and reexecute the DSL.
                Box(
                    Modifier.constrainAs(box2) {
                        start.linkTo(box1.end)
                        end.linkTo(guideline)
                        start.linkTo(barrier)
                        dslExecuted()
                    }
                )
            }
        }
        rule.runOnIdle {
            assertEquals(1, dslExecutions)
            first = false
        }
        rule.runOnIdle { assertEquals(1, dslExecutions) }
    }

    @Test
    fun testConstraintLayout_doesRemeasure_whenHelpersChange_butConstraintsDont() {
        val size = 100
        val sizeDp = with(rule.density) { size.toDp() }
        var first by mutableStateOf(true)
        var box1Position = Offset(-1f, -1f)
        var box2Position = Offset(-1f, -1f)
        val box1PositionUpdater =
            Modifier.onGloballyPositioned { box1Position = it.positionInRoot() }
        val box2PositionUpdater =
            Modifier.onGloballyPositioned { box2Position = it.positionInRoot() }
        rule.setContent {
            ConstraintLayout {
                val (box1, box2) = createRefs()

                if (!first) {
                    createVerticalChain(box1, box2)
                }

                Box(
                    Modifier
                        .size(sizeDp)
                        .then(box1PositionUpdater)
                        .constrainAs(box1) {})
                Box(
                    Modifier
                        .size(sizeDp)
                        .then(box2PositionUpdater)
                        .constrainAs(box2) {})
            }
        }
        rule.runOnIdle {
            assertEquals(Offset.Zero, box1Position)
            assertEquals(Offset.Zero, box2Position)
            first = false
        }
        rule.runOnIdle {
            assertEquals(Offset.Zero, box1Position)
            assertEquals(Offset(0f, size.toFloat()), box2Position)
        }
    }

    @Test
    fun testConstraintLayout_doesRemeasure_whenHelpersDontChange_butConstraintsDo() {
        val size = 100
        val sizeDp = with(rule.density) { size.toDp() }
        var first by mutableStateOf(true)
        var box1Position = Offset(-1f, -1f)
        var box2Position = Offset(-1f, -1f)
        val box1PositionUpdater =
                Modifier.onGloballyPositioned { box1Position = it.positionInRoot() }
        val box2PositionUpdater =
                Modifier.onGloballyPositioned { box2Position = it.positionInRoot() }
        rule.setContent {
            ConstraintLayout {
                val (box1, box2) = createRefs()

                val topBarrier = createTopBarrier(box1)
                val bottomBarrier = createBottomBarrier(box1)

                Box(
                    Modifier
                        .size(sizeDp)
                        .then(box1PositionUpdater)
                        .constrainAs(box1) {})
                Box(
                    Modifier
                        .size(sizeDp)
                        .then(box2PositionUpdater)
                        .constrainAs(box2) {
                            if (first) {
                                top.linkTo(topBarrier)
                            } else {
                                top.linkTo(bottomBarrier)
                            }
                        }
                )
            }
        }
        rule.runOnIdle {
            assertEquals(Offset.Zero, box1Position)
            assertEquals(Offset.Zero, box2Position)
            first = false
        }
        rule.runOnIdle {
            assertEquals(Offset.Zero, box1Position)
            assertEquals(Offset(0f, size.toFloat()), box2Position)
        }
    }

    @Test
    fun testConstraintLayout_updates_whenConstraintSetChanges() = with(rule.density) {
        val box1Size = 20
        var first by mutableStateOf(true)
        val constraintSet1 = ConstraintSet {
            val box1 = createRefFor("box1")
            val box2 = createRefFor("box2")
            constrain(box2) {
                start.linkTo(box1.end)
            }
        }
        val constraintSet2 = ConstraintSet {
            val box1 = createRefFor("box1")
            val box2 = createRefFor("box2")
            constrain(box2) {
                top.linkTo(box1.bottom)
            }
        }

        var box2Position = IntOffset.Zero
        rule.setContent {
            ConstraintLayout(constraintSet = if (first) constraintSet1 else constraintSet2) {
                Box(
                    Modifier
                        .size(box1Size.toDp())
                        .layoutId("box1"))
                Box(
                    Modifier
                        .layoutId("box2")
                        .onGloballyPositioned {
                            box2Position = it
                                .positionInRoot()
                                .round()
                        }
                )
            }
        }

        rule.runOnIdle {
            assertEquals(IntOffset(box1Size, 0), box2Position)
            first = false
        }

        rule.runOnIdle {
            assertEquals(IntOffset(0, box1Size), box2Position)
        }
    }

    @Test
    fun testConstraintLayout_doesNotRebuildFromDsl_whenResizedOnly() = with(rule.density) {
        var size by mutableStateOf(100.dp)
        var builds = 0
        rule.setContent {
            val onBuild = remember { { ++builds } }
            ConstraintLayout(Modifier.size(size)) {
                val box = createRef()
                Box(Modifier.constrainAs(box) { onBuild() })
            }
        }

        rule.runOnIdle {
            assertEquals(1, builds)
            size = 200.dp
        }

        rule.runOnIdle {
            assertEquals(1, builds)
        }
    }

    @Test
    fun testConstraintLayout_rebuildsConstraintSet_whenHelpersChange() = with(rule.density) {
        var offset by mutableStateOf(10.dp)
        var builds = 0
        var obtainedX = 0f
        rule.setContent {
            ConstraintLayout {
                val box = createRef()
                val g = createGuidelineFromStart(offset)
                Box(
                    Modifier
                        .constrainAs(box) {
                            start.linkTo(g)
                            ++builds
                        }
                        .onGloballyPositioned { obtainedX = it.positionInRoot().x })
            }
        }

        rule.runOnIdle {
            assertEquals(offset.roundToPx().toFloat(), obtainedX)
            offset = 20.dp
            assertEquals(1, builds)
        }

        rule.runOnIdle {
            assertEquals(offset.roundToPx().toFloat(), obtainedX)
            assertEquals(2, builds)
        }
    }

    @Test
    fun testConstraintLayout_rebuilds_whenLambdaChanges() = with(rule.density) {
        var first by mutableStateOf(true)
        var obtainedX = 0f
        rule.setContent {
            ConstraintLayout {
                val l1 = remember Unit> {
                    { start.linkTo(parent.start, 10.dp) }
                }
                val l2 = remember Unit> {
                    { start.linkTo(parent.start, 20.dp) }
                }
                val box = createRef()
                Box(
                    Modifier
                        .constrainAs(box, if (first) l1 else l2)
                        .onGloballyPositioned {
                            obtainedX = it.positionInRoot().x
                        })
            }
        }

        rule.runOnIdle {
            assertEquals(10.dp.roundToPx().toFloat(), obtainedX)
            first = false
        }

        rule.runOnIdle {
            assertEquals(20.dp.roundToPx().toFloat(), obtainedX)
        }
    }

    @Test
    fun testConstraintLayout_updates_whenConstraintSetChangesConstraints() = with(rule.density) {
        val box1Size = 20
        var first by mutableStateOf(true)
        val constraintSet = ConstraintSet {
            val box1 = createRefFor("box1")
            val box2 = createRefFor("box2")
            constrain(box2) {
                if (first) start.linkTo(box1.end) else top.linkTo(box1.bottom)
            }
        }

        var box2Position = IntOffset.Zero
        rule.setContent {
            ConstraintLayout(constraintSet = constraintSet) {
                Box(
                    Modifier
                        .size(box1Size.toDp())
                        .layoutId("box1"))
                Box(
                    Modifier
                        .layoutId("box2")
                        .onGloballyPositioned {
                            box2Position = it
                                .positionInRoot()
                                .round()
                        }
                )
            }
        }

        rule.runOnIdle {
            assertEquals(IntOffset(box1Size, 0), box2Position)
            first = false
        }

        rule.runOnIdle {
            assertEquals(IntOffset(0, box1Size), box2Position)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy