| /* |
| * 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.PointerEventPass |
| import androidx.ui.core.consumeDownChange |
| import androidx.ui.geometry.Offset |
| 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.IntSize |
| import androidx.ui.unit.milliseconds |
| import com.nhaarman.mockitokotlin2.any |
| import com.nhaarman.mockitokotlin2.mock |
| import com.nhaarman.mockitokotlin2.never |
| import com.nhaarman.mockitokotlin2.times |
| import com.nhaarman.mockitokotlin2.verify |
| import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions |
| import org.hamcrest.CoreMatchers.`is` |
| import org.hamcrest.MatcherAssert.assertThat |
| import org.junit.Before |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.JUnit4 |
| |
| @RunWith(JUnit4::class) |
| class RawPressStartGestureFilterTest { |
| |
| private lateinit var filter: RawPressStartGestureFilter |
| |
| @Before |
| fun setup() { |
| filter = RawPressStartGestureFilter() |
| filter.onPressStart = mock() |
| } |
| |
| // Verification of scenarios where onPressStart should not be called. |
| |
| @Test |
| fun onPointerInput_downConsumed_onPressStartNotCalled() { |
| filter::onPointerInput.invokeOverAllPasses(down(0).consumeDownChange()) |
| verify(filter.onPressStart, never()).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_downConsumedDown_onPressStartNotCalled() { |
| var pointer1 = down(1, duration = 0.milliseconds).consumeDownChange() |
| filter::onPointerInput.invokeOverAllPasses(pointer1) |
| pointer1 = pointer1.moveBy(10.milliseconds) |
| val pointer2 = down(2, duration = 10.milliseconds) |
| filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2) |
| verify(filter.onPressStart, never()).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_disabledDown_onPressStartNotCalled() { |
| filter.setEnabled(false) |
| filter::onPointerInput.invokeOverAllPasses(down(0)) |
| verify(filter.onPressStart, never()).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_disabledDownEnabledDown_onPressStartNotCalled() { |
| |
| filter.setEnabled(false) |
| var pointer1 = down(1, duration = 0.milliseconds).consumeDownChange() |
| filter::onPointerInput.invokeOverAllPasses(pointer1) |
| filter.setEnabled(true) |
| pointer1 = pointer1.moveBy(10.milliseconds) |
| val pointer2 = down(2, duration = 10.milliseconds) |
| filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2) |
| verify(filter.onPressStart, never()).invoke(any()) |
| } |
| |
| // Verification of scenarios where onPressStart should be called once. |
| |
| @Test |
| fun onPointerInput_down_onPressStartCalledOnce() { |
| filter::onPointerInput.invokeOverAllPasses(down(0)) |
| verify(filter.onPressStart).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_downDown_onPressStartCalledOnce() { |
| var pointer0 = down(0) |
| filter::onPointerInput.invokeOverAllPasses(pointer0) |
| pointer0 = pointer0.moveTo(1.milliseconds) |
| val pointer1 = down(1, 1.milliseconds) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, pointer1) |
| |
| verify(filter.onPressStart).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_2Down1Up1Down_onPressStartCalledOnce() { |
| var pointer0 = down(0) |
| var pointer1 = down(1) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, pointer1) |
| pointer0 = pointer0.up(100.milliseconds) |
| pointer1 = pointer1.moveTo(100.milliseconds) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, pointer1) |
| pointer0 = down(0, duration = 200.milliseconds) |
| pointer1 = pointer1.moveTo(200.milliseconds) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, pointer1) |
| |
| verify(filter.onPressStart).invoke(any()) |
| } |
| |
| @Test |
| fun onPointerInput_1DownMoveOutside2ndDown_onPressStartOnlyCalledOnce() { |
| var pointer0 = down(0, x = 0f, y = 0f) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, IntSize(5, 5)) |
| pointer0 = pointer0.moveTo(100.milliseconds, 10f, 0f) |
| filter::onPointerInput.invokeOverAllPasses(pointer0, IntSize(5, 5)) |
| pointer0 = pointer0.moveTo(200.milliseconds) |
| val pointer1 = down(1, x = 0f, y = 0f) |
| |
| filter::onPointerInput.invokeOverAllPasses(pointer0, pointer1) |
| |
| verify(filter.onPressStart).invoke(any()) |
| } |
| |
| // Verification of correct position returned by onPressStart. |
| |
| @Test |
| fun onPointerInput_down_downPositionIsCorrect() { |
| filter::onPointerInput.invokeOverAllPasses(down(0, x = 13f, y = 17f)) |
| verify(filter.onPressStart).invoke(Offset(13f, 17f)) |
| } |
| |
| // Verification of correct consumption behavior. |
| |
| @Test |
| fun onPointerInput_disabledDown_noDownChangeConsumed() { |
| filter.setEnabled(false) |
| var pointer = down(0) |
| pointer = filter::onPointerInput.invokeOverAllPasses(pointer) |
| assertThat(pointer.consumed.downChange, `is`(false)) |
| } |
| |
| // Verification of correct cancellation handling. |
| |
| @Test |
| fun onCancel_justCancel_noCallbacksCalled() { |
| filter.onCancel() |
| |
| verifyNoMoreInteractions(filter.onPressStart) |
| } |
| |
| @Test |
| fun onCancel_downCancelDown_onPressStartCalledTwice() { |
| filter::onPointerInput.invokeOverAllPasses(down(id = 0, duration = 0.milliseconds)) |
| filter.onCancel() |
| filter::onPointerInput.invokeOverAllPasses(down(id = 0, duration = 1.milliseconds)) |
| |
| verify(filter.onPressStart, times(2)).invoke(any()) |
| } |
| |
| // Verification of correct execution pass behavior |
| |
| @Test |
| fun onPointerInput_initialDown_behaviorOccursAtCorrectTime() { |
| filter.setExecutionPass(PointerEventPass.InitialDown) |
| |
| val pointer = filter::onPointerInput.invokeOverPasses( |
| down(0), |
| PointerEventPass.InitialDown |
| ) |
| |
| verify(filter.onPressStart).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(true)) |
| } |
| |
| @Test |
| fun onPointerInput_preUp_behaviorOccursAtCorrectTime() { |
| filter.setExecutionPass(PointerEventPass.PreUp) |
| |
| var pointer = filter::onPointerInput.invokeOverPasses( |
| down(0), |
| PointerEventPass.InitialDown |
| ) |
| |
| verify(filter.onPressStart, never()).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(false)) |
| |
| pointer = filter::onPointerInput.invokeOverPasses( |
| down(1), |
| PointerEventPass.PreUp |
| ) |
| |
| verify(filter.onPressStart).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(true)) |
| } |
| |
| @Test |
| fun onPointerInput_preDown_behaviorOccursAtCorrectTime() { |
| filter.setExecutionPass(PointerEventPass.PreDown) |
| |
| var pointer = filter::onPointerInput.invokeOverPasses( |
| down(0), |
| PointerEventPass.InitialDown, |
| PointerEventPass.PreUp |
| ) |
| |
| verify(filter.onPressStart, never()).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(false)) |
| |
| pointer = filter::onPointerInput.invokeOverPasses( |
| down(1), |
| PointerEventPass.PreDown |
| ) |
| |
| verify(filter.onPressStart).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(true)) |
| } |
| |
| @Test |
| fun onPointerInput_PostUp_behaviorOccursAtCorrectTime() { |
| filter.setExecutionPass(PointerEventPass.PostUp) |
| |
| var pointer = filter::onPointerInput.invokeOverPasses( |
| down(0), |
| PointerEventPass.InitialDown, |
| PointerEventPass.PreUp, |
| PointerEventPass.PreDown |
| ) |
| |
| verify(filter.onPressStart, never()).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(false)) |
| |
| pointer = filter::onPointerInput.invokeOverPasses( |
| down(1), |
| PointerEventPass.PostUp |
| ) |
| |
| verify(filter.onPressStart).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(true)) |
| } |
| |
| @Test |
| fun onPointerInput_postDown_behaviorOccursAtCorrectTime() { |
| filter.setExecutionPass(PointerEventPass.PostDown) |
| |
| var pointer = filter::onPointerInput.invokeOverPasses( |
| down(0), |
| PointerEventPass.InitialDown, |
| PointerEventPass.PreUp, |
| PointerEventPass.PreDown, |
| PointerEventPass.PostUp |
| ) |
| |
| verify(filter.onPressStart, never()).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(false)) |
| |
| pointer = filter::onPointerInput.invokeOverPasses( |
| down(1), |
| PointerEventPass.PostDown |
| ) |
| |
| verify(filter.onPressStart).invoke(any()) |
| assertThat(pointer.consumed.downChange, `is`(true)) |
| } |
| |
| // Verification of correct cancellation behavior. |
| |
| // The purpose of this test is hard to understand, but it proves that the cancel event sets the |
| // state of the gesture detector to inactive such that when a new stream of events starts, |
| // and the 1st down is already consumed, the gesture detector won't consume the 2nd down. |
| @Test |
| fun onCancel_downCancelDownConsumedDown_thirdDownNotConsumed() { |
| filter::onPointerInput |
| .invokeOverAllPasses(down(id = 0, duration = 0.milliseconds)) |
| filter.onCancel() |
| var pointer1 = down(id = 1, duration = 10.milliseconds).consumeDownChange() |
| filter::onPointerInput.invokeOverAllPasses(pointer1) |
| pointer1 = pointer1.moveTo(20.milliseconds, 0f, 0f) |
| val pointer2 = down(id = 2, duration = 20.milliseconds) |
| val results = filter::onPointerInput.invokeOverAllPasses(pointer1, pointer2) |
| |
| assertThat(results[0].consumed.downChange, `is`(false)) |
| assertThat(results[1].consumed.downChange, `is`(false)) |
| } |
| } |