[go: nahoru, domu]

blob: a1a4ea5b6b5c27a9c4fa7b552f03b984473531d3 [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.
*/
package androidx.ui.core.gesture
import androidx.ui.core.CustomEventDispatcher
import androidx.ui.core.PointerId
import androidx.ui.core.consumeDownChange
import androidx.ui.core.gesture.customevents.DelayUpEvent
import androidx.ui.core.gesture.customevents.DelayUpMessage
import androidx.ui.geometry.Offset
import androidx.ui.testutils.consume
import androidx.ui.testutils.down
import androidx.ui.testutils.invokeOverAllPasses
import androidx.ui.testutils.moveTo
import androidx.ui.testutils.up
import androidx.ui.unit.IntSize
import androidx.ui.unit.milliseconds
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.times
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.util.concurrent.TimeUnit
// TODO(shepshapard): Add more tests for:
// 1. More complex multi-pointer scenarios testing how consumption affects firing events
// 2. More complex multi-pointer scenarios testing how pointers effect consumption
@kotlinx.coroutines.ObsoleteCoroutinesApi
@RunWith(JUnit4::class)
class DoubleTapGestureFilterTest {
private val DoubleTapTimeoutMillis = 100.milliseconds
@Suppress("DEPRECATION")
private val testContext = kotlinx.coroutines.test.TestCoroutineContext()
private val onDoubleTap: (Offset) -> Unit = mock()
private val customEventDispatcher: CustomEventDispatcher = mock()
private lateinit var filter: DoubleTapGestureFilter
@Before
fun setup() {
filter = DoubleTapGestureFilter(testContext)
filter.onDoubleTap = onDoubleTap
filter.doubleTapTimeout = DoubleTapTimeoutMillis
filter.onInit(customEventDispatcher)
}
// Tests that verify conditions under which onDoubleTap will not be called.
@Test
fun onPointerInput_down_onDoubleTapNotCalled() {
filter::onPointerInput.invokeOverAllPasses(down(0, 0.milliseconds))
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUp_onDoubleTapNotCalled() {
val down = down(0, 0.milliseconds)
val up = down.up(duration = 1.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
filter::onPointerInput.invokeOverAllPasses(up)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownWithinTimeout_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val down2 = down(2, 100.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownOutsideTimeout_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val down2 = down(2, 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownOutsideTimeoutUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downMoveConsumedUpDownInsideTimeoutUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val moveConsumed = down1.moveTo(1.milliseconds, x = 1f).consume(dx = 1f)
val up1 = moveConsumed.up(duration = 2.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(moveConsumed)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownInsideTimeoutMoveConsumedUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 100.milliseconds)
val moveConsumed = down2.moveTo(101.milliseconds, x = 1f).consume(dx = 1f)
val up2 = moveConsumed.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(moveConsumed)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_2Down1MoveConsumedUpDownInsideTimeoutUp_onDoubleTapNotCalled() {
val down1A = down(0, 0.milliseconds)
val down1B = down(1, 0.milliseconds)
val moveConsumed1A = down1A.moveTo(1.milliseconds, x = 1f).consume(dx = 1f)
val move1B = down1B.moveTo(1.milliseconds)
val up1A = moveConsumed1A.up(duration = 2.milliseconds)
val up1B = move1B.up(duration = 2.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1A, down1B)
filter::onPointerInput.invokeOverAllPasses(moveConsumed1A, move1B)
filter::onPointerInput.invokeOverAllPasses(up1A, up1B)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUp2DownInsideTimeout1MoveConsumedUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up2 = down1.up(duration = 1.milliseconds)
val down2A = down(0, 100.milliseconds)
val down2B = down(1, 100.milliseconds)
val moveConsumed2A = down2A.moveTo(101.milliseconds, x = 1f).consume(dx = 1f)
val move2B = down2B.moveTo(101.milliseconds)
val up2A = moveConsumed2A.up(duration = 102.milliseconds)
val up2B = move2B.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up2)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2A, down2B)
filter::onPointerInput.invokeOverAllPasses(moveConsumed2A, move2B)
filter::onPointerInput.invokeOverAllPasses(up2A, up2B)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downConsumedUpDownWithinTimeoutUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds).consumeDownChange()
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(0, 100.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpConsumedDownWithinTimeoutUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds).consumeDownChange()
val down2 = down(0, 100.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownConsumedWithinTimeoutUp_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(0, 100.milliseconds).consumeDownChange()
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownWithinTimeoutUpConsumed_onDoubleTapNotCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(0, 100.milliseconds)
val up2 = down2.up(duration = 102.milliseconds).consumeDownChange()
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_2down1Up1DownWithinTimeout1Up_onDoubleTapNotCalled() {
val down1A = down(0, 0.milliseconds)
val down1B = down(1, 0.milliseconds)
val move1A1 = down1A.moveTo(2.milliseconds)
val up2B = down1B.up(duration = 2.milliseconds)
val move1A2 = move1A1.moveTo(101.milliseconds)
val down2 = down(1, 101.milliseconds)
val move1A3 = move1A2.moveTo(102.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1A, down1B)
filter::onPointerInput.invokeOverAllPasses(move1A1, up2B)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(move1A2, down2)
filter::onPointerInput.invokeOverAllPasses(move1A3, up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_1down1Up2DownWithinTimeout1Up_onDoubleTapNotCalled() {
val down1 = down(0, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2A = down(0, 100.milliseconds)
val down2B = down(1, 100.milliseconds)
val move2A = down2A.moveTo(101.milliseconds)
val up2B = down2B.up(duration = 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2A, down2B)
filter::onPointerInput.invokeOverAllPasses(move2A, up2B)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downMoveOutOfBoundsUpDownUp_onDoubleTapNotCalled() {
val down = down(0, 0.milliseconds, 0f, 0f)
val move = down.moveTo(1.milliseconds, 1f, 1f)
val up = move.up(duration = 12.milliseconds)
val down2 = down(0, 13.milliseconds, 0f, 0f)
val up2 = down2.up(duration = 14.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(move, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up2, IntSize(1, 1))
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onPointerInput_downUpDownMoveOutOfBoundsUp_onDoubleTapNotCalled() {
val down = down(0, 0.milliseconds, 0f, 0f)
val up = down.up(duration = 1.milliseconds)
val down2 = down(0, 2.milliseconds, 0f, 0f)
val move2 = down2.moveTo(3.milliseconds, 1f, 1f)
val up2 = down2.up(duration = 4.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(move2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up2, IntSize(1, 1))
verify(onDoubleTap, never()).invoke(any())
}
// Tests that verify conditions under which onDoubleTap will be called.
@Test
fun onPointerInput_downUpDownInsideTimeoutUp_onDoubleTapCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 100.milliseconds)
val up2 = down2.up(duration = 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_downMoveUpDownInsideTimeoutUp_onDoubleTapCalled() {
val down1 = down(1, 0.milliseconds)
val move = down1.moveTo(1.milliseconds, x = 1f)
val up1 = move.up(duration = 2.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(move)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_downUpDownInsideTimeoutMoveUp_onDoubleTapCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 10.milliseconds)
val move = down2.moveTo(101.milliseconds, x = 1f)
val up2 = move.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(move)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_2Down1MoveUpDownInsideTimeoutUp_onDoubleTapCalled() {
val down1A = down(0, 0.milliseconds)
val down1B = down(1, 0.milliseconds)
val move1A = down1A.moveTo(1.milliseconds, x = 1f)
val move1B = down1B.moveTo(1.milliseconds)
val up1A = move1A.up(duration = 2.milliseconds)
val up1B = move1B.up(duration = 2.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1A, down1B)
filter::onPointerInput.invokeOverAllPasses(move1A, move1B)
filter::onPointerInput.invokeOverAllPasses(up1A, up1B)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_downUp2DownInsideTimeout1MoveUp_onDoubleTapCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2A = down(0, 100.milliseconds)
val down2B = down(1, 100.milliseconds)
val move2A = down2A.moveTo(101.milliseconds, x = 1f)
val move2B = down2B.moveTo(101.milliseconds)
val up2A = move2A.up(duration = 102.milliseconds)
val up2B = move2B.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2A, down2B)
filter::onPointerInput.invokeOverAllPasses(move2A, move2B)
filter::onPointerInput.invokeOverAllPasses(up2A, up2B)
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_downMoveOutOfBoundsUpDownUpDownUp_onDoubleTapCalledOnce() {
val down = down(0, 0.milliseconds, 0f, 0f)
val move = down.moveTo(1.milliseconds, 1f, 1f)
val up = move.up(duration = 2.milliseconds)
val down2 = down(0, 3.milliseconds, 0f, 0f)
val up2 = down2.up(duration = 4.milliseconds)
val down3 = down(0, 5.milliseconds, 0f, 0f)
val up3 = down3.up(6.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(move, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down3, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up3, IntSize(1, 1))
verify(onDoubleTap).invoke(any())
}
@Test
fun onPointerInput_downUpDownMoveOutOfBoundsUpDownUpDownUp_onDoubleTapCalledOnce() {
val down = down(0, 0.milliseconds, 0f, 0f)
val up = down.up(duration = 2.milliseconds)
val down2 = down(0, 3.milliseconds, 0f, 0f)
val move2 = down2.moveTo(1.milliseconds, 1f, 1f)
val up2 = move2.up(duration = 4.milliseconds)
val down3 = down(0, 5.milliseconds, 0f, 0f)
val up3 = down3.up(6.milliseconds)
val down4 = down(0, 7.milliseconds, 0f, 0f)
val up4 = down4.up(8.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(move2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up2, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down3, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up3, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(down4, IntSize(1, 1))
filter::onPointerInput.invokeOverAllPasses(up4, IntSize(1, 1))
verify(onDoubleTap).invoke(any())
}
// This test verifies that the 2nd down causes the double tap time out timer to stop such that
// the second wait doesn't cause the gesture detector to reset to an idle state.
@Test
fun onPointerInput_downUpWaitHalfDownWaitHalfUp_onDoubleTapCalled() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val wait1 = 50L
val down2 = down(2, 51.milliseconds)
val wait2 = 50L
val up2 = down2.up(duration = 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(wait1, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
testContext.advanceTimeBy(wait2, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
// Tests that verify correctness of PxPosition value passed to onDoubleTap
@Test
fun onPointerInput_downUpDownUpAllAtOrigin_onDoubleTapCalledWithOrigin() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 100.milliseconds)
val up2 = down2.up(duration = 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(Offset.Zero)
}
@Test
fun onPointerInput_downUpDownMoveUp_onDoubleTapCalledWithFinalMovePosition() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2 = down(2, 100.milliseconds)
val move2 = down2.moveTo(101.milliseconds, 3f, 5f)
val up2 = move2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(move2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(Offset(3f, 5f))
}
@Test
fun onPointerInput_downUp2Down2Move1UpThen1Up_onDoubleTapCalledWithFinalFingerPosition() {
val down1 = down(1, 0.milliseconds)
val up1 = down1.up(duration = 1.milliseconds)
val down2A = down(0, 100.milliseconds)
val down2B = down(1, 100.milliseconds)
val move2A = down2A.moveTo(101.milliseconds, 3f, 5f)
val move2B1 = down2B.moveTo(101.milliseconds, 7f, 11f)
val up2A = move2A.up(duration = 102.milliseconds)
val move2B2 = move2B1.moveTo(102.milliseconds, x = 7f, y = 11f)
val up2B = move2B2.up(duration = 103.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2A, down2B)
filter::onPointerInput.invokeOverAllPasses(move2A, move2B1)
filter::onPointerInput.invokeOverAllPasses(up2A, move2B2)
filter::onPointerInput.invokeOverAllPasses(up2B)
verify(onDoubleTap).invoke(Offset(7f, 11f))
}
// Tests that verify correct consumption behavior
@Test
fun onPointerInput_down_downNotConsumed() {
val down = down(0, 0.milliseconds)
val result = filter::onPointerInput.invokeOverAllPasses(down)
assertThat(result.consumed.downChange).isFalse()
}
@Test
fun onPointerInput_downUp_upNotConsumed() {
val down = down(0, 0.milliseconds)
val up = down.up(1.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
val result = filter::onPointerInput.invokeOverAllPasses(up)
assertThat(result.consumed.downChange).isFalse()
}
@Test
fun onPointerInput_downUpDownInsideTimeout_lastDownNotConsumed() {
val down = down(0, 0.milliseconds)
val up = down.up(1.milliseconds)
val down2 = down(2, 100.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
val result = filter::onPointerInput.invokeOverAllPasses(down2)
assertThat(result.consumed.downChange).isFalse()
}
@Test
fun onPointerInput_downUpDownOutsideTimeoutUp_lastUpNotConsumed() {
val down = down(0, 0.milliseconds)
val up = down.up(1.milliseconds)
val down2 = down(2, 101.milliseconds)
val up2 = down2.up(duration = 102.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(100, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
val result = filter::onPointerInput.invokeOverAllPasses(up2)
assertThat(result.consumed.downChange).isFalse()
}
@Test
fun onPointerInput_downUpDownInsideTimeoutUp_lastUpConsumed() {
val down = down(0, 0.milliseconds)
val up = down.up(1.milliseconds)
val down2 = down(2, 100.milliseconds)
val up2 = down2.up(duration = 101.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
val result = filter::onPointerInput.invokeOverAllPasses(up2)
assertThat(result.consumed.downChange).isTrue()
}
// Tests that verify correct cancellation behavior
@Test
fun onCancel_downUpCancelWaitDownUp_onDoubleTapNotCalled() {
val down1 = down(0, duration = 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(1, duration = 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
filter.onCancel()
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onCancel_downUpWaitCancelDownUp_onDoubleTapNotCalled() {
val down1 = down(1, 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(2, 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap, never()).invoke(any())
}
@Test
fun onCancel_cancelDownUpDownUp_onDoubleTapCalledOnce() {
val down1 = down(0, duration = 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(1, duration = 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onCancel_downCancelDownUpDownUp_onDoubleTapCalledOnce() {
val down0 = down(0, duration = 99.milliseconds)
val down1 = down(1, duration = 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(2, duration = 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down0)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onCancel_downUpCancelDownUpDownUp_onDoubleTapCalledOnce() {
val down0 = down(0, duration = 98.milliseconds)
val up0 = down0.up(duration = 99.milliseconds)
val down1 = down(1, duration = 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(2, duration = 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down0)
filter::onPointerInput.invokeOverAllPasses(up0)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
@Test
fun onCancel_downUpDownCancelDownUpDownUp_onDoubleTapCalledOnce() {
val down0 = down(0, duration = 97.milliseconds)
val up0 = down0.up(duration = 98.milliseconds)
val down1 = down(1, 99.milliseconds)
val down2 = down(2, 100.milliseconds)
val up2 = down2.up(duration = 101.milliseconds)
val down3 = down(3, 200.milliseconds)
val up3 = down3.up(duration = 201.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down0)
filter::onPointerInput.invokeOverAllPasses(up0)
filter::onPointerInput.invokeOverAllPasses(down1)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down3)
filter::onPointerInput.invokeOverAllPasses(up3)
verify(onDoubleTap).invoke(any())
}
@Test
fun onCancel_downUpDownUpCancelDownUpDownUp_onDoubleTapCalledTwice() {
val down0 = down(0, 0.milliseconds)
val up0 = down0.up(duration = 1.milliseconds)
val down1 = down(1, 100.milliseconds)
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(2, 200.milliseconds)
val up2 = down2.up(duration = 201.milliseconds)
val down3 = down(3, 300.milliseconds)
val up3 = down3.up(duration = 301.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down0)
filter::onPointerInput.invokeOverAllPasses(up0)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up1)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
testContext.advanceTimeBy(99, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down3)
filter::onPointerInput.invokeOverAllPasses(up3)
verify(onDoubleTap, times(2)).invoke(any())
}
// This test verifies that the cancel event causes the double tap timer to be reset. If it does
// not cause it to be reset, then when delay1 is dispatched, the DoubleTapGestureDetector will
// be forced back into the IDLE state, preventing the double tap that follows cancel from
// firing.
@Test
fun onCancel_downUpWaitCancelDownWaitUpDownUp_onDoubleTapCalledOnce() {
val down0 = down(0, 0.milliseconds)
val up0 = down0.up(duration = 1.milliseconds)
val delay0 = 50L
// Cancel happens here
val down1 = down(1, 51.milliseconds)
val delay1 = 50L
val up1 = down1.up(duration = 101.milliseconds)
val down2 = down(2, 102.milliseconds)
val up2 = down2.up(duration = 103.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down0)
filter::onPointerInput.invokeOverAllPasses(up0)
testContext.advanceTimeBy(delay0, TimeUnit.MILLISECONDS)
filter.onCancel()
filter::onPointerInput.invokeOverAllPasses(down1)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(up1)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(onDoubleTap).invoke(any())
}
// Verifies correct behavior around dispatching custom messages with simple cases
@Test
fun onPointerInput_downUp_delayUpToCorrectPointersAndRetained() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
verify(customEventDispatcher)
.dispatchCustomEvent(DelayUpEvent(DelayUpMessage.DelayUp, setOf(PointerId(123))))
verify(customEventDispatcher)
.retainHitPaths(setOf(PointerId(123)))
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpDownBeforeTimeOut_delayUpConsumedToCorrectPointersAndReleased() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val delay1 = 1L
val down2 = down(456, 2.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
reset(customEventDispatcher)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
verify(customEventDispatcher).dispatchCustomEvent(
DelayUpEvent(DelayUpMessage.DelayedUpConsumed, setOf(PointerId(123)))
)
verify(customEventDispatcher).releaseHitPaths(
setOf(PointerId(123))
)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpTimeOut_delayUpNotConsumedToCorrectPointersAndReleased() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val delay1 = 1000L
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
reset(customEventDispatcher)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
verify(customEventDispatcher).dispatchCustomEvent(
DelayUpEvent(DelayUpMessage.DelayedUpNotConsumed, setOf(PointerId(123)))
)
verify(customEventDispatcher).releaseHitPaths(
setOf(PointerId(123))
)
verifyNoMoreInteractions(customEventDispatcher)
}
// Verifies correct behavior around dispatching custom messages in relation to other factors
@Test
fun onPointerInput_downUpConsumed_noCustomMessageDispatched() {
val down1 = down(123, 0.milliseconds)
val upConsumed = down1.up(duration = 1.milliseconds).consumeDownChange()
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(upConsumed)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpConsumedDownBeforeTimeout_noCustomMessageDispatched() {
val down1 = down(123, 0.milliseconds)
val upConsumed = down1.up(duration = 1.milliseconds).consumeDownChange()
val delay1 = 1L
val down2 = down(456, 2.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(upConsumed)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
filter::onPointerInput.invokeOverAllPasses(down2)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpConsumedTimeout_noCustomMessageDispatched() {
val down1 = down(123, 0.milliseconds)
val upConsumed = down1.up(duration = 1.milliseconds).consumeDownChange()
val delay1 = 1000L
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(upConsumed)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onCancel_downUpCancelTimeOut_delayUpConsumedToCorrectPointersAndReleased() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val delay1 = 1000L
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
reset(customEventDispatcher)
filter.onCancel()
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
verify(customEventDispatcher).dispatchCustomEvent(
DelayUpEvent(DelayUpMessage.DelayedUpConsumed, setOf(PointerId(123)))
)
verify(customEventDispatcher).releaseHitPaths(
setOf(PointerId(123))
)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpCancelDownBeforeTimeOut_noMessageDispatched() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val delay1 = 1L
val down2 = down(456, 2.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
filter.onCancel()
reset(customEventDispatcher)
filter::onPointerInput.invokeOverAllPasses(down2)
verifyNoMoreInteractions(customEventDispatcher)
}
@Test
fun onPointerInput_downUpCancelDownUp_delayUpToCorrectPointersAndRetained() {
val down1 = down(123, 0.milliseconds)
val up = down1.up(duration = 1.milliseconds)
val delay1 = 1L
val down2 = down(456, 2.milliseconds)
val up2 = down2.up(3.milliseconds)
filter::onPointerInput.invokeOverAllPasses(down1)
filter::onPointerInput.invokeOverAllPasses(up)
testContext.advanceTimeBy(delay1, TimeUnit.MILLISECONDS)
filter.onCancel()
reset(customEventDispatcher)
filter::onPointerInput.invokeOverAllPasses(down2)
filter::onPointerInput.invokeOverAllPasses(up2)
verify(customEventDispatcher)
.dispatchCustomEvent(DelayUpEvent(DelayUpMessage.DelayUp, setOf(PointerId(456))))
verify(customEventDispatcher)
.retainHitPaths(setOf(PointerId(456)))
verifyNoMoreInteractions(customEventDispatcher)
}
}