[go: nahoru, domu]

blob: 1de398223aebdff8e5931f27703641b6678f6cac [file] [log] [blame]
/*
* 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.
*/
@file:OptIn(ExperimentalPointerInput::class)
@file:Suppress("PrivatePropertyName")
package androidx.ui.core.gesture
import androidx.ui.core.Direction
import androidx.ui.core.PointerEventPass
import androidx.ui.core.consumeDownChange
import androidx.ui.core.gesture.scrollorientationlocking.InternalScrollOrientationLocker
import androidx.ui.core.gesture.scrollorientationlocking.Orientation
import androidx.ui.core.gesture.scrollorientationlocking.ScrollOrientationLocker
import androidx.ui.core.gesture.scrollorientationlocking.ShareScrollOrientationLockerEvent
import androidx.ui.testutils.consume
import androidx.ui.testutils.down
import androidx.ui.testutils.invokeOverAllPasses
import androidx.ui.testutils.invokeOverPasses
import androidx.ui.testutils.moveBy
import androidx.ui.testutils.moveTo
import androidx.ui.testutils.up
import androidx.ui.unit.Duration
import androidx.ui.unit.milliseconds
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
// TODO(shepshapard): Write the following tests.
// Verify correct shape of slop area (should it be a square or circle)?
// Test for cases with more than one pointer
// Test for cases where things are reset when last pointer goes up
// Verify all methods called during onPostUp
// Verify default behavior when no callback provided for filter or canDrag
// Changing this value will break tests that expect the value to be 10.
private const val TestTouchSlop = 10
@RunWith(JUnit4::class)
class DragSlopExceededGestureFilterTest {
private val onDragSlopExceeded: () -> Unit = { onDragSlopExceededCallCount++ }
private val canDrag: (Direction) -> Boolean = { direction ->
canDragDirections.add(direction)
canDragReturn
}
private var onDragSlopExceededCallCount: Int = 0
private var canDragReturn = false
private var canDragDirections: MutableList<Direction> = mutableListOf()
private lateinit var filter: DragSlopExceededGestureFilter
private val TinyNum = .01f
@Before
fun setup() {
onDragSlopExceededCallCount = 0
canDragReturn = true
canDragDirections.clear()
filter =
DragSlopExceededGestureFilter(TestTouchSlop.toFloat())
filter.setDraggableData(null, canDrag)
filter.onDragSlopExceeded = onDragSlopExceeded
filter.scrollOrientationLocker = ScrollOrientationLocker(mock())
}
// Verify the circumstances under which canDrag should not be called.
@Test
fun onPointerInputChanges_down_canDragNotCalled() {
filter::onPointerInput.invokeOverAllPasses(down(0))
assertThat(canDragDirections).isEmpty()
}
@Test
fun onPointerInputChanges_downUp_canDragNotCalled() {
val down = down(0, duration = 0.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
val up = down.up(10.milliseconds)
filter::onPointerInput.invokeOverAllPasses(up)
assertThat(canDragDirections).isEmpty()
}
@Test
fun onPointerInputChanges_downMoveFullyConsumed_canDragNotCalled() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), 3f, 5f).consume(3f, 5f)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(canDragDirections).isEmpty()
}
// Verify the circumstances under which canDrag should be called.
@Test
fun onPointerInputChanges_downMove1Dimension_canDragCalledOnce() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), 3f, 0f)
filter::onPointerInput.invokeOverAllPasses(move)
// Twice because while under touch slop, TouchSlopExceededGestureDetector checks during PostUp and PostDown
assertThat(canDragDirections).hasSize(2)
}
@Test
fun onPointerInputChanges_downMove2Dimensions_canDragCalledTwice() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), 3f, 5f)
filter::onPointerInput.invokeOverAllPasses(move)
// 4 times because while under touch slop, TouchSlopExceededGestureDetector checks during PostUp and
// PostDown
assertThat(canDragDirections).hasSize(4)
}
@Test
fun onPointerInputChanges_downMoveOneDimensionPartiallyConsumed_canDragCalledOnce() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), 0f, 5f).consume(0f, 4f)
filter::onPointerInput.invokeOverAllPasses(move)
// Twice because while under touch slop, DragGestureDetector checks during PostUp and
// PostDown
assertThat(canDragDirections).hasSize(2)
}
@Test
fun onPointerInputChanges_downMoveTwoDimensionPartiallyConsumed_canDragCalledTwice() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), 3f, 5f).consume(2f, 4f)
filter::onPointerInput.invokeOverAllPasses(move)
// 4 times because while under touch slop, DragGestureDetector checks during PostUp and
// PostDown
assertThat(canDragDirections).hasSize(4)
}
@Test
fun onPointerInputChanges_dragPastTouchSlopOneDimensionAndDrag3MoreTimes_canDragCalledOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
var move = down.moveTo(10.milliseconds, 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(move)
repeat(3) {
move = move.moveBy(Duration(milliseconds = 10), 0f, 1f)
filter::onPointerInput.invokeOverAllPasses(move)
}
// Once because although DragGestureDetector checks during PostUp and PostDown, slop is
// surpassed during PostUp, and thus isn't checked again.
assertThat(canDragDirections).hasSize(1)
}
@Test
fun onPointerInputChanges_downMoveUnderSlop3Times_canDragCalled3Times() {
val thirdSlop = TestTouchSlop / 3
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
var move = down
repeat(3) {
move = move.moveBy(Duration(milliseconds = 10), 0f, thirdSlop.toFloat())
filter::onPointerInput.invokeOverAllPasses(move)
}
// 6 times because while under touch slop, DragGestureDetector checks during PostUp and
// PostDown
assertThat(canDragDirections).hasSize(6)
}
@Test
fun onPointerInputChanges_moveBeyondSlopThenIntoTouchSlopAreaAndOutAgain_canDragCalledOnce() {
val beyondSlop = TestTouchSlop + TinyNum
var event = down(0)
filter::onPointerInput.invokeOverAllPasses(event)
// Out of touch slop region
event = event.moveBy(Duration(milliseconds = 10), 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
// Back into touch slop region
event = event.moveBy(Duration(milliseconds = 10), 0f, -beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
// Out of touch slop region again
event = event.moveBy(Duration(milliseconds = 10), 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
// Once because although DragGestureDetector checks during PostUp and PostDown, slop is
// surpassed during PostUp, and thus isn't checked again.
assertThat(canDragDirections).hasSize(1)
}
// Verification of correctness of values passed to canDrag.
@Test
fun onPointerInputChanges_canDragCalledWithCorrectDirection() {
onPointerInputChanges_canDragCalledWithCorrectDirection(
-1f, 0f, arrayOf(Direction.LEFT)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
0f, -1f, arrayOf(Direction.UP)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
1f, 0f, arrayOf(Direction.RIGHT)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
0f, 1f, arrayOf(Direction.DOWN)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
-1f, -1f, arrayOf(Direction.LEFT, Direction.UP)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
-1f, 1f, arrayOf(Direction.LEFT, Direction.DOWN)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
1f, -1f, arrayOf(Direction.RIGHT, Direction.UP)
)
onPointerInputChanges_canDragCalledWithCorrectDirection(
1f, 1f, arrayOf(Direction.RIGHT, Direction.DOWN)
)
}
private fun onPointerInputChanges_canDragCalledWithCorrectDirection(
dx: Float,
dy: Float,
expectedDirections: Array<Direction>
) {
canDragDirections.clear()
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 10), dx, dy)
filter::onPointerInput.invokeOverAllPasses(move)
// Everything here is twice because DragGestureDetector checks during PostUp and PostDown.
assertThat(canDragDirections).hasSize(expectedDirections.size * 2)
expectedDirections.forEach { direction ->
assertThat(canDragDirections.count { it == direction })
.isEqualTo(2)
}
}
// Verify the circumstances under which onTouchSlopExceeded should not be called.
// TODO(b/129701831): This test assumes that if a pointer moves by slop in both x and y, we are
// still under slop even though sqrt(slop^2 + slop^2) > slop. This may be inaccurate and this
// test may therefore need to be updated.
@Test
fun onPointerInputChanges_downMoveWithinSlop_onTouchSlopExceededNotCalled() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 10),
TestTouchSlop.toFloat(),
TestTouchSlop.toFloat()
)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_moveBeyondSlopInUnsupportedDirection_onTouchSlopExceededNotCalled() {
val beyondSlop = TestTouchSlop + TinyNum
canDragReturn = false
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 10),
beyondSlop,
beyondSlop
)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_moveBeyondSlopButConsumeUnder_onTouchSlopExceededNotCalled() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, TestTouchSlop + TinyNum, 0f).consume(dx = 1f)
filter::onPointerInput.invokeOverAllPasses(move)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_moveUnderToPostUpThenModOverInOppDir_onTouchSlopExceededNotCalled() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, TestTouchSlop.toFloat(), 0f)
filter::onPointerInput.invokeOverPasses(
listOf(move),
listOf(
PointerEventPass.InitialDown,
PointerEventPass.PreUp,
PointerEventPass.PreDown,
PointerEventPass.PostUp
)
)
val move2 = move.consume(dx = (TestTouchSlop * 2f + TinyNum))
filter::onPointerInput.invokeOverPasses(
move2,
PointerEventPass.PostDown
)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
// TODO(b/129701831): This test assumes that if a pointer moves by slop in both x and y, we are
// still under slop even though sqrt(slop^2 + slop^2) > slop. This may be inaccurate and this
// test may therefore need to be updated.
@Test
fun onPointerInputChanges_moveAroundWithinSlop_onTouchSlopExceededNotCalled() {
val slop = TestTouchSlop.toFloat()
var change = down(0)
filter::onPointerInput.invokeOverAllPasses(change)
// Go around the border of the touch slop area
// To top left
change = change.moveTo(10.milliseconds, -slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
// To bottom left
change = change.moveTo(20.milliseconds, -slop, slop)
filter::onPointerInput.invokeOverAllPasses(change)
// To bottom right
change = change.moveTo(30.milliseconds, slop, slop)
filter::onPointerInput.invokeOverAllPasses(change)
// To top right
change = change.moveTo(40.milliseconds, slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
// Jump from corner to opposite corner and back
// To bottom left
change = change.moveTo(50.milliseconds, -slop, slop)
filter::onPointerInput.invokeOverAllPasses(change)
// To top right
change = change.moveTo(60.milliseconds, slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
// Move the other diagonal
// To top left
change = change.moveTo(70.milliseconds, -slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
// Jump from corner to opposite corner and back
// To bottom right
change = change.moveTo(80.milliseconds, slop, slop)
filter::onPointerInput.invokeOverAllPasses(change)
// To top left
change = change.moveTo(90.milliseconds, -slop, -slop)
filter::onPointerInput.invokeOverAllPasses(change)
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
// Verify the circumstances under which onTouchSlopExceeded should be called.
@Test
fun onPointerInputChanges_movePassedSlop_onTouchSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 100),
beyondSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_movePassedSlopIn2Events_onTouchSlopExceededCallOnce() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 100),
TestTouchSlop.toFloat(),
0f
)
filter::onPointerInput.invokeOverAllPasses(move)
val move2 = down.moveBy(
Duration(milliseconds = 100),
1f,
0f
)
filter::onPointerInput.invokeOverAllPasses(move2)
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_passSlopThenInSlopAreaThenOut_onTouchSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
var event = down(0)
filter::onPointerInput.invokeOverAllPasses(event)
// Out of touch slop region
event = event.moveBy(Duration(milliseconds = 10), 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
// Back into touch slop region
event = event.moveBy(Duration(milliseconds = 10), 0f, -beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
// Out of touch slop region again
event = event.moveBy(Duration(milliseconds = 10), 0f, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(event)
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_downConsumedMovePassedSlop_onTouchSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0).consumeDownChange()
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(Duration(milliseconds = 100), beyondSlop, 0f)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_beyondInUnsupportThenBeyondInSupport_onTouchSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
var change = down(0)
filter::onPointerInput.invokeOverAllPasses(change)
canDragReturn = false
change = change.moveBy(
Duration(milliseconds = 10),
0f,
beyondSlop
)
// Sanity check that onTouchSlopExceeded has not been called.
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
canDragReturn = true
filter::onPointerInput.invokeOverAllPasses(change)
change = change.moveBy(
Duration(milliseconds = 10),
0f,
-beyondSlop
)
filter::onPointerInput.invokeOverAllPasses(change)
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_2PointsMoveInOpposite_onTouchSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
var pointer1 = down(1)
var pointer2 = down(2)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Act
pointer1 = pointer1.moveBy(
Duration(milliseconds = 100),
beyondSlop,
0f
)
pointer2 = pointer2.moveBy(
Duration(milliseconds = 100),
-beyondSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_3PointsMoveAverage0_onDragSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
val pointers = arrayOf(down(0), down(1), down(2))
filter::onPointerInput.invokeOverAllPasses(*pointers)
// Act
// These movements average to no movement.
pointers[0] =
pointers[0].moveBy(
Duration(milliseconds = 100),
beyondSlop * -1,
beyondSlop * -1
)
pointers[1] =
pointers[1].moveBy(
Duration(milliseconds = 100),
beyondSlop * 1,
beyondSlop * -1
)
pointers[2] =
pointers[2].moveBy(
Duration(milliseconds = 100),
0f,
beyondSlop * 2
)
filter::onPointerInput.invokeOverAllPasses(*pointers)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_2Points1MoveJustBeyondSlop_onDragSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
var pointer1 = down(0)
var pointer2 = down(1)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Act
// These movements average to no movement.
pointer1 =
pointer1.moveBy(
Duration(milliseconds = 100),
0f,
0f
)
pointer2 =
pointer2.moveBy(
Duration(milliseconds = 100),
beyondSlop * -1,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_2Points1MoveJustUnderTwiceSlop_onDragSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
var pointer1 = down(0)
var pointer2 = down(1)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Act
// These movements average to no movement.
pointer1 =
pointer1.moveBy(
Duration(milliseconds = 100),
0f,
0f
)
pointer2 =
pointer2.moveBy(
Duration(milliseconds = 100),
beyondSlop * 2 - 1,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onPointerInputChanges_2Points1MoveToTwiceSlop_onDragSlopExceededNotCalled() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
var pointer1 = down(0)
var pointer2 = down(1)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Act
// These movements average to no movement.
pointer1 =
pointer1.moveBy(
Duration(milliseconds = 100),
0f,
0f
)
pointer2 =
pointer2.moveBy(
Duration(milliseconds = 100),
beyondSlop * 2,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_1PointMovesBeyondSlopAndThenManyTimes_onDragSlopExceededCallOnce() {
// Arrange
val beyondSlop = TestTouchSlop + TinyNum
var pointer = down(0)
filter::onPointerInput.invokeOverAllPasses(pointer)
// Act
repeat(5) {
pointer = pointer.moveBy(100.milliseconds, beyondSlop, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(pointer)
}
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_1ModifiedToMoveBeyondSlopBeforePostUp_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, 0f, 0f).consume(dx = beyondSlop)
filter::onPointerInput.invokeOverPasses(
listOf(move),
listOf(
PointerEventPass.InitialDown,
PointerEventPass.PreUp,
PointerEventPass.PreDown,
PointerEventPass.PostUp
)
)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_1ModedToMoveBeyondSlopBeforePostDown_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, 0f, 0f)
filter::onPointerInput.invokeOverPasses(
listOf(move),
listOf(
PointerEventPass.InitialDown,
PointerEventPass.PreUp,
PointerEventPass.PreDown,
PointerEventPass.PostUp
)
)
val moveConsumed = move.consume(dx = beyondSlop)
filter::onPointerInput.invokeOverPasses(
moveConsumed,
PointerEventPass.PostDown
)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_moveUnderToPostUpThenModOverToPostDown_onDragSlopExceededCallOnce() {
val halfSlop = TestTouchSlop / 2
val restOfSlopAndBeyond = TestTouchSlop - halfSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, halfSlop.toFloat(), 0f)
filter::onPointerInput.invokeOverPasses(
listOf(move),
listOf(
PointerEventPass.InitialDown,
PointerEventPass.PreUp,
PointerEventPass.PreDown,
PointerEventPass.PostUp
)
)
val moveConsumed = move.consume(dx = -restOfSlopAndBeyond)
filter::onPointerInput.invokeOverPasses(
moveConsumed,
PointerEventPass.PostDown
)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
@Test
fun onPointerInputChanges_moveBeyondSlopAllPassesUpToPostUp_onDragSlopExceededCallOnce() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, beyondSlop, 0f)
filter::onPointerInput.invokeOverPasses(
listOf(move),
listOf(
PointerEventPass.InitialDown,
PointerEventPass.PreUp,
PointerEventPass.PreDown,
PointerEventPass.PostUp
)
)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(1)
}
// Verification that TouchSlopExceededGestureDetector does not consume any changes.
@Test
fun onPointerInputChanges_1Down_nothingConsumed() {
val result = filter::onPointerInput.invokeOverAllPasses(down(0))
// Assert
assertThat(result.consumed.downChange).isFalse()
assertThat(result.consumed.positionChange.x).isEqualTo(0f)
assertThat(result.consumed.positionChange.y).isEqualTo(0f)
}
@Test
fun onPointerInputChanges_1MoveUnderSlop_nothingConsumed() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, TestTouchSlop.toFloat(), TestTouchSlop.toFloat())
val result = filter::onPointerInput.invokeOverAllPasses(move)
// Assert
assertThat(result.consumed.downChange).isFalse()
assertThat(result.consumed.positionChange.x).isEqualTo(0f)
assertThat(result.consumed.positionChange.y).isEqualTo(0f)
}
@Test
fun onPointerInputChanges_1MoveUnderSlopThenUp_nothingConsumed() {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, TestTouchSlop.toFloat(), TestTouchSlop.toFloat())
filter::onPointerInput.invokeOverAllPasses(move)
val up = move.up(20.milliseconds)
val result = filter::onPointerInput.invokeOverAllPasses(up)
// Assert
assertThat(result.consumed.downChange).isFalse()
assertThat(result.consumed.positionChange.x).isEqualTo(0f)
assertThat(result.consumed.positionChange.y).isEqualTo(0f)
}
@Test
fun onPointerInputChanges_1MoveOverSlop_nothingConsumed() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, beyondSlop, beyondSlop)
val result = filter::onPointerInput.invokeOverAllPasses(move)
// Assert
assertThat(result.consumed.downChange).isFalse()
assertThat(result.consumed.positionChange.x).isEqualTo(0f)
assertThat(result.consumed.positionChange.y).isEqualTo(0f)
}
@Test
fun onPointerInputChanges_1MoveOverSlopThenUp_nothingConsumed() {
val beyondSlop = TestTouchSlop + TinyNum
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, beyondSlop, beyondSlop)
filter::onPointerInput.invokeOverAllPasses(move)
val up = move.up(20.milliseconds)
val result = filter::onPointerInput.invokeOverAllPasses(up)
// Assert
assertThat(result.consumed.downChange).isFalse()
assertThat(result.consumed.positionChange.x).isEqualTo(0f)
assertThat(result.consumed.positionChange.y).isEqualTo(0f)
}
// Verification that TouchSlopExceededGestureDetector resets after up correctly.
@Test
fun onPointerInputChanges_MoveBeyondUpDownMoveBeyond_onDragSlopExceededCalledTwice() {
val beyondSlop = TestTouchSlop + TinyNum
repeat(2) {
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(10.milliseconds, beyondSlop, 0f)
filter::onPointerInput.invokeOverAllPasses(move)
val up = move.up(20.milliseconds)
filter::onPointerInput.invokeOverAllPasses(up)
}
assertThat(onDragSlopExceededCallCount).isEqualTo(2)
}
// Orientation tests
// Tests that verify correct behavior when orientation is set.
@Test
fun onPointerInput_filterIsHorizontalMovementVertical_canDragNotCalled() {
filter.setDraggableData(Orientation.Horizontal, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move1 = down.moveBy(Duration(milliseconds = 10), 0f, 1f)
filter::onPointerInput.invokeOverAllPasses(move1)
val move2 = down.moveBy(Duration(milliseconds = 10), 0f, -1f)
filter::onPointerInput.invokeOverAllPasses(move2)
assertThat(canDragDirections).isEmpty()
}
@Test
fun onPointerInput_filterIsVerticalMovementIsHorizontal_canDragNotCalled() {
filter.setDraggableData(Orientation.Vertical, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move1 = down.moveBy(Duration(milliseconds = 10), 1f, 0f)
filter::onPointerInput.invokeOverAllPasses(move1)
val move2 = down.moveBy(Duration(milliseconds = 10), -1f, 0f)
filter::onPointerInput.invokeOverAllPasses(move2)
assertThat(canDragDirections).isEmpty()
}
@Test
fun onPointerInput_filterIsHorizontalMovementHorizontal_canDragCalled() {
filter.setDraggableData(Orientation.Horizontal, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move1 = down.moveBy(Duration(milliseconds = 10), 1f, 0f)
filter::onPointerInput.invokeOverAllPasses(move1)
val move2 = down.moveBy(Duration(milliseconds = 10), -1f, 0f)
filter::onPointerInput.invokeOverAllPasses(move2)
// 2 for each because canDrag is currently checked on both postUp and postDown
assertThat(canDragDirections.filter { it == Direction.LEFT }).hasSize(2)
assertThat(canDragDirections.filter { it == Direction.RIGHT }).hasSize(2)
}
@Test
fun onPointerInput_filterIsVerticalMovementIsVertical_canDragCalled() {
filter.setDraggableData(Orientation.Vertical, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move1 = down.moveBy(Duration(milliseconds = 10), 0f, 1f)
filter::onPointerInput.invokeOverAllPasses(move1)
val move2 = down.moveBy(Duration(milliseconds = 10), 0f, -1f)
filter::onPointerInput.invokeOverAllPasses(move2)
// 2 for each because canDrag is currently checked on both postUp and postDown
assertThat(canDragDirections.filter { it == Direction.UP }).hasSize(2)
assertThat(canDragDirections.filter { it == Direction.DOWN }).hasSize(2)
}
@Test
fun onPointerInput_filterIsHorizontalMoveLeftPassedSlop_onTouchSlopExceededCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Horizontal, -1, 0, 1)
}
@Test
fun onPointerInput_filterIsHorizontalMoveUpPassedSlop_onTouchSlopExceededNotCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Horizontal, 0, -1, 0)
}
@Test
fun onPointerInput_filterIsHorizontalMoveRightPassedSlop_onTouchSlopExceededCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Horizontal, 1, 0, 1)
}
@Test
fun onPointerInput_filterIsHorizontalMoveDownPassedSlop_onTouchSlopExceededNotCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Horizontal, 0, 1, 0)
}
@Test
fun onPointerInput_filterIsVerticalMoveLeftPassedSlop_onTouchSlopExceededNotCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Vertical, -1, 0, 0)
}
@Test
fun onPointerInput_filterIsVerticalMoveUpPassedSlop_onTouchSlopExceededCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Vertical, 0, -1, 1)
}
@Test
fun onPointerInput_filterIsVerticalMoveRightPassedSlop_onTouchSlopExceededNotCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Vertical, 1, 0, 0)
}
@Test
fun onPointerInput_filterIsVerticalMoveDownPassedSlop_onTouchSlopExceededCalled() {
onPointerInput_filterHasOrientationMovePassedSlop(Orientation.Vertical, 0, 1, 1)
}
private fun onPointerInput_filterHasOrientationMovePassedSlop(
filterOrientation: Orientation,
horizontalDirection: Int,
verticalDirection: Int,
expectecdOnDragSlopExceededCallCount: Int
) {
val beyondSlop = TestTouchSlop + TinyNum
filter.setDraggableData(filterOrientation, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 100),
horizontalDirection * beyondSlop,
verticalDirection * beyondSlop
)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(expectecdOnDragSlopExceededCallCount)
}
@Test
fun onPointerInput_filterHorizontalPointerVerticalMovesLeftPastSlop_callBackNotCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Horizontal, Orientation.Vertical, -1, 0, 0
)
}
@Test
fun onPointerInput_filterHorizontalPointerVerticalMovesRightPastSlop_callBackNotCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Horizontal, Orientation.Vertical, 1, 0, 0
)
}
@Test
fun onPointerInput_filterHorizontalPointerHorizontalMovesLeftPastSlop_callBackCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Horizontal, Orientation.Horizontal, -1, 0, 1
)
}
@Test
fun onPointerInput_filterHorizontalPointerHorizontallMovesRightPastSlop_callBackCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Horizontal, Orientation.Horizontal, 1, 0, 1
)
}
@Test
fun onPointerInput_filterVerticalPointerHorizontalMovesUpPastSlop_callBackNotCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Vertical, Orientation.Horizontal, 0, -1, 0
)
}
@Test
fun onPointerInput_filterVerticalPointerHorizontalMovesDownPastSlop_callBackNotCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Vertical, Orientation.Horizontal, 0, 1, 0
)
}
@Test
fun onPointerInput_filterVerticalPointerVerticalMovesUpPastSlop_callBackCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Vertical, Orientation.Vertical, 0, -1, 1
)
}
@Test
fun onPointerInput_filterVerticalPointerVerticalMovesDownPastSlop_callBackCalled() {
onPointerInput_pointerIsLockedMovesPassedSlop(
Orientation.Vertical, Orientation.Vertical, 0, 1, 1
)
}
private fun onPointerInput_pointerIsLockedMovesPassedSlop(
filterOrientation: Orientation,
lockedOrientation: Orientation,
horizontalDirection: Int,
verticalDirection: Int,
expectedOnDragSlopExceededCallCount: Int
) {
val beyondSlop = TestTouchSlop + TinyNum
filter.onInit(mock())
val scrollOrientationLocker = InternalScrollOrientationLocker()
filter::onCustomEvent.invokeOverAllPasses(
ShareScrollOrientationLockerEvent(scrollOrientationLocker)
)
filter.setDraggableData(filterOrientation, canDrag)
val down = down(0)
filter::onPointerInput.invokeOverAllPasses(down)
val move = down.moveBy(
Duration(milliseconds = 100),
horizontalDirection * beyondSlop,
verticalDirection * beyondSlop
)
scrollOrientationLocker.attemptToLockPointers(listOf(move), lockedOrientation)
filter::onPointerInput.invokeOverAllPasses(move)
assertThat(onDragSlopExceededCallCount).isEqualTo(expectedOnDragSlopExceededCallCount)
}
// Verification that cancellation behavior is correct.
@Test
fun onCancel_underSlopCancelUnderSlop_onDragSlopExceededNotCalled() {
val underSlop = TestTouchSlop - TinyNum
// Arrange
var pointer = down(0, 0.milliseconds, 0f, 0f)
filter::onPointerInput.invokeOverAllPasses(pointer)
pointer = pointer.moveTo(
10.milliseconds,
underSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer)
// Act
filter.onCancel()
pointer = down(0, 0.milliseconds, 0f, 0f)
filter::onPointerInput.invokeOverAllPasses(pointer)
pointer = pointer.moveTo(
10.milliseconds,
underSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(0)
}
@Test
fun onCancel_pastSlopCancelPastSlop_onScaleSlopExceededCalledTwice() {
val overSlop = TestTouchSlop + TinyNum
// Arrange
var pointer = down(0, 0.milliseconds, 0f, 0f)
filter::onPointerInput.invokeOverAllPasses(pointer)
pointer = pointer.moveTo(
10.milliseconds,
overSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer)
// Act
filter.onCancel()
pointer = down(0, 0.milliseconds, 0f, 0f)
filter::onPointerInput.invokeOverAllPasses(pointer)
pointer = pointer.moveTo(
10.milliseconds,
overSlop,
0f
)
filter::onPointerInput.invokeOverAllPasses(pointer)
// Assert
assertThat(onDragSlopExceededCallCount).isEqualTo(2)
}
}