Merge "Re-enable fallbackLineSpacing" into androidx-master-dev
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
index 3a50822..333125a8 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
@@ -75,7 +75,8 @@
      * @return [Selection] object which is constructed by combining all Composables that are
      * selected.
      */
-    private fun mergeSelections(
+    // This function is internal for testing purposes.
+    internal fun mergeSelections(
         startPosition: PxPosition,
         endPosition: PxPosition,
         longPress: Boolean = false,
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerDragTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerDragTest.kt
new file mode 100644
index 0000000..5b4ff27
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerDragTest.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2019 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.ui.core.selection
+
+import androidx.test.filters.SmallTest
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.text.style.TextDirection
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.spy
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectionManagerDragTest {
+    private val selectionRegistrar = SelectionRegistrarImpl()
+    private val selectable = mock<Selectable>()
+    private val selectionManager = SelectionManager(selectionRegistrar)
+
+    private val containerLayoutCoordinates = mock<LayoutCoordinates>()
+    private val childToLocal_result = PxPosition(300.px, 400.px)
+
+    private val startLayoutCoordinates = mock<LayoutCoordinates>()
+    private val endLayoutCoordinates = mock<LayoutCoordinates>()
+    private val startCoordinates = PxPosition(3.px, 30.px)
+    private val endCoordinates = PxPosition(3.px, 600.px)
+    private val fakeInitialSelection: Selection = Selection(
+        start = Selection.AnchorInfo(
+            coordinates = startCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 0,
+            layoutCoordinates = startLayoutCoordinates
+        ),
+        end = Selection.AnchorInfo(
+            coordinates = endCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 5,
+            layoutCoordinates = endLayoutCoordinates
+        )
+    )
+    private val fakeResultSelection: Selection = Selection(
+        start = Selection.AnchorInfo(
+            coordinates = endCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 5,
+            layoutCoordinates = endLayoutCoordinates
+        ),
+        end = Selection.AnchorInfo(
+            coordinates = startCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 0,
+            layoutCoordinates = startLayoutCoordinates
+        )
+    )
+    private var selection: Selection? = fakeInitialSelection
+    private val lambda: (Selection?) -> Unit = { selection = it }
+    private val spyLambda = spy(lambda)
+
+    @Before
+    fun setup() {
+        selectionRegistrar.subscribe(selectable)
+
+        whenever(
+            containerLayoutCoordinates.childToLocal(
+                child = any(),
+                childLocal = any()
+            )
+        ).thenReturn(childToLocal_result)
+
+        whenever(
+            selectable.getSelection(
+                startPosition = any(),
+                endPosition = any(),
+                containerLayoutCoordinates = any(),
+                longPress = any()
+            )
+        ).thenReturn(fakeResultSelection)
+
+        selectionManager.containerLayoutCoordinates = containerLayoutCoordinates
+        selectionManager.onSelectionChange = spyLambda
+        selectionManager.selection = selection
+    }
+
+    @Test
+    fun handleDragObserver_onStart_startHandle_enable_draggingHandle_get_startHandle_info() {
+        selectionManager.handleDragObserver(isStartHandle = true).onStart(PxPosition.Origin)
+
+        verify(containerLayoutCoordinates, times(1))
+            .childToLocal(
+                child = startLayoutCoordinates,
+                childLocal = getAdjustedCoordinates(startCoordinates)
+            )
+        verify_draggingHandle(expectedDraggingHandleValue = true)
+        verify(spyLambda, times(0)).invoke(fakeResultSelection)
+    }
+
+    @Test
+    fun handleDragObserver_onStart_endHandle_enable_draggingHandle_get_endHandle_info() {
+        selectionManager.handleDragObserver(isStartHandle = false).onStart(PxPosition.Origin)
+
+        verify(containerLayoutCoordinates, times(1))
+            .childToLocal(
+                child = endLayoutCoordinates,
+                childLocal = getAdjustedCoordinates(endCoordinates)
+            )
+        verify_draggingHandle(expectedDraggingHandleValue = true)
+        verify(spyLambda, times(0)).invoke(fakeResultSelection)
+    }
+
+    @Test
+    fun handleDragObserver_onDrag_startHandle_reuse_endHandle_calls_getSelection_change_selection
+                () {
+        val dragDistance = PxPosition(100.px, 100.px)
+        selectionManager.handleDragObserver(isStartHandle = true).onStart(PxPosition.Origin)
+
+        val result = selectionManager.handleDragObserver(isStartHandle = true).onDrag(dragDistance)
+
+        verify(containerLayoutCoordinates, times(1))
+            .childToLocal(
+                child = endLayoutCoordinates,
+                childLocal = getAdjustedCoordinates(endCoordinates)
+            )
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = childToLocal_result + dragDistance,
+                endPosition = childToLocal_result,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+        assertThat(selection).isEqualTo(fakeResultSelection)
+        verify(spyLambda, times(1)).invoke(fakeResultSelection)
+        assertThat(result).isEqualTo(dragDistance)
+    }
+
+    @Test
+    fun handleDragObserver_onDrag_endHandle_resue_startHandle_calls_getSelection_change_selection
+                () {
+        val dragDistance = PxPosition(100.px, 100.px)
+        selectionManager.handleDragObserver(isStartHandle = false).onStart(PxPosition.Origin)
+
+        val result = selectionManager.handleDragObserver(isStartHandle = false).onDrag(dragDistance)
+
+        verify(containerLayoutCoordinates, times(1))
+            .childToLocal(
+                child = startLayoutCoordinates,
+                childLocal = getAdjustedCoordinates(startCoordinates)
+            )
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = childToLocal_result,
+                endPosition = childToLocal_result + dragDistance,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+        assertThat(selection).isEqualTo(fakeResultSelection)
+        verify(spyLambda, times(1)).invoke(fakeResultSelection)
+        assertThat(result).isEqualTo(dragDistance)
+    }
+
+    @Test
+    fun handleDragObserver_onStop_disable_draggingHandle() {
+        selectionManager.handleDragObserver(false).onStart(PxPosition.Origin)
+        selectionManager.handleDragObserver(false).onDrag(PxPosition.Origin)
+
+        selectionManager.handleDragObserver(false).onStop(PxPosition.Origin)
+
+        verify_draggingHandle(expectedDraggingHandleValue = false)
+    }
+
+    private fun getAdjustedCoordinates(position: PxPosition): PxPosition {
+        return PxPosition(position.x, position.y - 1.px)
+    }
+
+    private fun verify_draggingHandle(expectedDraggingHandleValue: Boolean) {
+        // Verify draggingHandle is true, by verifying LongPress does nothing. Vice Versa.
+        val position = PxPosition(100.px, 100.px)
+        selectionManager.longPressDragObserver.onLongPress(position)
+        verify(selectable, times(if (expectedDraggingHandleValue) 0 else 1))
+            .getSelection(
+                startPosition = position,
+                endPosition = position,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = true
+            )
+    }
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerLongPressDragTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerLongPressDragTest.kt
new file mode 100644
index 0000000..dd309f8
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerLongPressDragTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2019 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.ui.core.selection
+
+import androidx.test.filters.SmallTest
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.text.style.TextDirection
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.spy
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectionManagerLongPressDragTest {
+    private val selectionRegistrar = SelectionRegistrarImpl()
+    private val selectable = mock<Selectable>()
+    private val selectionManager = SelectionManager(selectionRegistrar)
+
+    private val startLayoutCoordinates = mock<LayoutCoordinates>()
+    private val endLayoutCoordinates = mock<LayoutCoordinates>()
+    private val startCoordinates = PxPosition(3.px, 30.px)
+    private val endCoordinates = PxPosition(3.px, 600.px)
+    private val fakeInitialSelection: Selection = Selection(
+        start = Selection.AnchorInfo(
+            coordinates = startCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 0,
+            layoutCoordinates = startLayoutCoordinates
+        ),
+        end = Selection.AnchorInfo(
+            coordinates = endCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 5,
+            layoutCoordinates = endLayoutCoordinates
+        )
+    )
+
+    private val fakeResultSelection: Selection = Selection(
+        start = Selection.AnchorInfo(
+            coordinates = endCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 5,
+            layoutCoordinates = endLayoutCoordinates
+        ),
+        end = Selection.AnchorInfo(
+            coordinates = startCoordinates,
+            direction = TextDirection.Ltr,
+            offset = 0,
+            layoutCoordinates = startLayoutCoordinates
+        )
+    )
+
+    private var selection: Selection? = null
+    private val lambda: (Selection?) -> Unit = { selection = it }
+    private val spyLambda = spy(lambda)
+
+    @Before
+    fun setup() {
+        val containerLayoutCoordinates = mock<LayoutCoordinates>()
+        selectionRegistrar.subscribe(selectable)
+
+        whenever(
+            selectable.getSelection(
+                startPosition = any(),
+                endPosition = any(),
+                containerLayoutCoordinates = any(),
+                longPress = any()
+            )
+        ).thenReturn(fakeResultSelection)
+
+        selectionManager.containerLayoutCoordinates = containerLayoutCoordinates
+        selectionManager.onSelectionChange = spyLambda
+        selectionManager.selection = selection
+    }
+
+    @Test
+    fun longPressDragObserver_onLongPress_calls_getSelection_change_selection() {
+        val position = PxPosition(100.px, 100.px)
+
+        selectionManager.longPressDragObserver.onLongPress(position)
+
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = position,
+                endPosition = position,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = true
+            )
+        assertThat(selection).isEqualTo(fakeResultSelection)
+        verify(spyLambda, times(1)).invoke(fakeResultSelection)
+    }
+
+    @Test
+    fun longPressDragObserver_onDragStart_reset_dragTotalDistance() {
+        // Setup. Make sure selectionManager.dragTotalDistance is not 0.
+        val dragDistance1 = PxPosition(15.px, 10.px)
+        val beginPosition1 = PxPosition(30.px, 20.px)
+        val dragDistance2 = PxPosition(100.px, 300.px)
+        val beginPosition2 = PxPosition(300.px, 200.px)
+        selectionManager.longPressDragObserver.onLongPress(beginPosition1)
+        selectionManager.longPressDragObserver.onDragStart()
+        selectionManager.longPressDragObserver.onDrag(dragDistance1)
+        // Setup. Cancel selection and reselect.
+        selectionManager.onRelease()
+        // Start the new selection
+        selectionManager.longPressDragObserver.onLongPress(beginPosition2)
+        selectionManager.selection = fakeInitialSelection
+        selection = fakeInitialSelection
+
+        // Act. Reset selectionManager.dragTotalDistance to zero.
+        selectionManager.longPressDragObserver.onDragStart()
+        selectionManager.longPressDragObserver.onDrag(dragDistance2)
+
+        // Verify.
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = beginPosition2,
+                endPosition = beginPosition2 + dragDistance2,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = true
+            )
+        assertThat(selection).isEqualTo(fakeResultSelection)
+        verify(spyLambda, times(3)).invoke(fakeResultSelection)
+    }
+
+    @Test
+    fun longPressDragObserver_onDrag_calls_getSelection_change_selection() {
+        val dragDistance = PxPosition(15.px, 10.px)
+        val beginPosition = PxPosition(30.px, 20.px)
+        selectionManager.longPressDragObserver.onLongPress(beginPosition)
+        selectionManager.selection = fakeInitialSelection
+        selection = fakeInitialSelection
+        selectionManager.longPressDragObserver.onDragStart()
+
+        val result = selectionManager.longPressDragObserver.onDrag(dragDistance)
+
+        assertThat(result).isEqualTo(dragDistance)
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = beginPosition,
+                endPosition = beginPosition + dragDistance,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = true
+            )
+        assertThat(selection).isEqualTo(fakeResultSelection)
+        verify(spyLambda, times(2)).invoke(fakeResultSelection)
+    }
+
+    @Test
+    fun longPressDragObserver_onDrag_directly_not_call_getSelection_not_change_selection() {
+        val dragDistance = PxPosition(15.px, 10.px)
+        val beginPosition = PxPosition(30.px, 20.px)
+
+        selection = fakeInitialSelection
+        val result = selectionManager.longPressDragObserver.onDrag(dragDistance)
+
+        assertThat(result).isEqualTo(PxPosition.Origin)
+        verify(selectable, times(0))
+            .getSelection(
+                startPosition = beginPosition,
+                endPosition = beginPosition + dragDistance,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = true
+            )
+        assertThat(selection).isEqualTo(fakeInitialSelection)
+        verify(spyLambda, times(0)).invoke(fakeResultSelection)
+    }
+}
diff --git a/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerTest.kt b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerTest.kt
new file mode 100644
index 0000000..dbd4c94
--- /dev/null
+++ b/ui/ui-framework/src/test/java/androidx/ui/core/selection/SelectionManagerTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2019 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.ui.core.selection
+
+import androidx.test.filters.SmallTest
+import androidx.ui.core.LayoutCoordinates
+import androidx.ui.core.PxPosition
+import androidx.ui.core.px
+import androidx.ui.text.style.TextDirection
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.spy
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class SelectionManagerTest {
+    private val selectionRegistrar = SelectionRegistrarImpl()
+    private val selectable = mock<Selectable>()
+    private val selectionManager = SelectionManager(selectionRegistrar)
+
+    private val startLayoutCoordinates = mock<LayoutCoordinates>()
+    private val endLayoutCoordinates = mock<LayoutCoordinates>()
+    private val startCoordinates = PxPosition(3.px, 30.px)
+    private val endCoordinates = PxPosition(3.px, 600.px)
+
+    @Before
+    fun setup() {
+        val containerLayoutCoordinates = mock<LayoutCoordinates>()
+        selectionRegistrar.subscribe(selectable)
+        selectionManager.containerLayoutCoordinates = containerLayoutCoordinates
+    }
+
+    @Test
+    fun mergeSelections_single_selectable_calls_getSelection_once() {
+        selectionManager.mergeSelections(
+            startPosition = startCoordinates,
+            endPosition = endCoordinates
+        )
+
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = startCoordinates,
+                endPosition = endCoordinates,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+    }
+
+    @Test
+    fun mergeSelections_multiple_selectables_calls_getSelection_multiple_times() {
+        val selectable_another = mock<Selectable>()
+        selectionRegistrar.subscribe(selectable_another)
+
+        selectionManager.mergeSelections(
+            startPosition = startCoordinates,
+            endPosition = endCoordinates
+        )
+
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = startCoordinates,
+                endPosition = endCoordinates,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+        verify(selectable_another, times(1))
+            .getSelection(
+                startPosition = startCoordinates,
+                endPosition = endCoordinates,
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+    }
+
+    @Test
+    fun cancel_selection_calls_getSelection_selection_becomes_null() {
+        val fakeSelection =
+            Selection(
+                start = Selection.AnchorInfo(
+                    coordinates = startCoordinates,
+                    direction = TextDirection.Ltr,
+                    offset = 0,
+                    layoutCoordinates = startLayoutCoordinates
+                ),
+                end = Selection.AnchorInfo(
+                    coordinates = endCoordinates,
+                    direction = TextDirection.Ltr,
+                    offset = 5,
+                    layoutCoordinates = endLayoutCoordinates
+                )
+            )
+        var selection: Selection? = fakeSelection
+        val lambda: (Selection?) -> Unit = { selection = it }
+        val spyLambda = spy(lambda)
+        selectionManager.onSelectionChange = spyLambda
+        selectionManager.selection = fakeSelection
+
+        selectionManager.onRelease()
+
+        verify(selectable, times(1))
+            .getSelection(
+                startPosition = PxPosition((-1).px, (-1).px),
+                endPosition = PxPosition((-1).px, (-1).px),
+                containerLayoutCoordinates = selectionManager.containerLayoutCoordinates,
+                longPress = false
+            )
+        assertThat(selection).isNull()
+        verify(spyLambda, times(1)).invoke(null)
+    }
+}