Skip to content

Commit 099702b

Browse files
committed
(Finally) upgrade multi-tap impl to use DetoxSingleTap
1 parent d8cf10e commit 099702b

File tree

8 files changed

+184
-102
lines changed

8 files changed

+184
-102
lines changed

detox/android/detox/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ dependencies {
101101
testImplementation 'org.json:json:20140107'
102102
testImplementation 'junit:junit:4.12'
103103
testImplementation 'org.assertj:assertj-core:3.8.0'
104+
testImplementation "org.jetbrains.kotlin:kotlin-test:$_kotlinVersion"
104105
testImplementation 'org.apache.commons:commons-io:1.3.2'
105106
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
106107
}

detox/android/detox/src/main/java/com/wix/detox/espresso/DetoxAction.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.wix.detox.espresso.DetoxErrors.DetoxRuntimeException;
66
import com.wix.detox.espresso.DetoxErrors.StaleActionException;
7+
import com.wix.detox.espresso.action.DetoxMultiTap;
78
import com.wix.detox.espresso.action.RNClickAction;
89
import com.wix.detox.espresso.common.annot.MotionDir;
910
import com.wix.detox.espresso.scroll.ScrollEdgeException;
@@ -46,7 +47,7 @@ private DetoxAction() {
4647
}
4748

4849
public static ViewAction multiClick(int times) {
49-
return actionWithAssertions(new GeneralClickAction(new MultiTap(times), GeneralLocation.CENTER, Press.FINGER, 0, 0));
50+
return actionWithAssertions(new GeneralClickAction(new DetoxMultiTap(times), GeneralLocation.CENTER, Press.FINGER, 0, 0));
5051
}
5152

5253
public static ViewAction tapAtLocation(final int x, final int y) {

detox/android/detox/src/main/java/com/wix/detox/espresso/MultiTap.java

Lines changed: 0 additions & 98 deletions
This file was deleted.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.wix.detox.espresso.action
2+
3+
import androidx.test.espresso.UiController
4+
import androidx.test.espresso.action.Tapper
5+
import com.wix.detox.espresso.common.DetoxViewConfigurations.getDoubleTapMinTime
6+
7+
class DetoxMultiTap(private val times: Int, private val interTapsDelayMs: Long?, delegatedTapperGenFn: () -> Tapper): Tapper {
8+
private var delegateTapper: Tapper = delegatedTapperGenFn()
9+
10+
constructor(times: Int)
11+
: this(times, getDoubleTapMinTime(), { DetoxSingleTap() })
12+
13+
override fun sendTap(uiController: UiController?, coordinates: FloatArray?, precision: FloatArray?)
14+
= sendTap(uiController, coordinates, precision, 0, 0)
15+
16+
override fun sendTap(uiController: UiController?, coordinates: FloatArray?, precision: FloatArray?, inputDevice: Int, buttonState: Int): Tapper.Status {
17+
uiController!!
18+
coordinates!!
19+
precision!!
20+
21+
for (i in 1..times) {
22+
if (delegateTapper.sendTap(uiController, coordinates, precision, inputDevice, buttonState) == Tapper.Status.FAILURE) {
23+
return Tapper.Status.FAILURE
24+
}
25+
26+
if (i < times) {
27+
interTapsDelayMs?.let {
28+
uiController.loopMainThreadForAtLeast(interTapsDelayMs)
29+
}
30+
}
31+
}
32+
return Tapper.Status.SUCCESS
33+
}
34+
}

detox/android/detox/src/main/java/com/wix/detox/espresso/action/DetoxSingleTap.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.wix.detox.espresso.action
22

3-
import android.view.ViewConfiguration
43
import androidx.test.espresso.UiController
54
import androidx.test.espresso.action.Tapper
5+
import com.wix.detox.espresso.common.DetoxViewConfigurations.getPracticalTapTimeout
66
import com.wix.detox.espresso.common.MotionEvents
77

88
/**
@@ -19,13 +19,14 @@ import com.wix.detox.espresso.common.MotionEvents
1919
*/
2020
class DetoxSingleTap(
2121
private val motionEvents: MotionEvents = MotionEvents(),
22-
private val tapTimeout: Long = (ViewConfiguration.getTapTimeout() * 1.5).toLong())
22+
private val tapTimeout: Long = getPracticalTapTimeout())
2323
: Tapper {
2424

2525
override fun sendTap(uiController: UiController?, coordinates: FloatArray?, precision: FloatArray?): Tapper.Status
2626
= sendTap(uiController, coordinates, precision, 0, 0)
2727

2828
override fun sendTap(uiController: UiController?, coordinates: FloatArray?, precision: FloatArray?, inputDevice: Int, buttonState: Int): Tapper.Status {
29+
uiController!!
2930
coordinates!!
3031
precision!!
3132

@@ -34,7 +35,7 @@ class DetoxSingleTap(
3435
val downEvent = motionEvents.obtainDownEvent(x, y, precision)
3536
val upEvent = motionEvents.obtainUpEvent(downEvent, downEvent.eventTime + EVENTS_TIME_GAP_MS, x, y)
3637
try {
37-
val result = uiController!!.injectMotionEventSequence(arrayListOf(downEvent, upEvent))
38+
val result = uiController.injectMotionEventSequence(arrayListOf(downEvent, upEvent))
3839
if (result) {
3940
uiController.loopMainThreadForAtLeast(tapTimeout)
4041
return Tapper.Status.SUCCESS
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.wix.detox.espresso.common
2+
3+
import android.os.Build
4+
import android.util.Log
5+
import android.view.ViewConfiguration
6+
import java.lang.reflect.InvocationTargetException
7+
8+
private const val LOG_TAG = "Detox-ViewConfig"
9+
10+
object DetoxViewConfigurations {
11+
12+
fun getPracticalTapTimeout() = (ViewConfiguration.getTapTimeout() * 1.5).toLong()
13+
14+
/**
15+
* Taken from [androidx.test.espresso.action.Tap]
16+
*/
17+
fun getDoubleTapMinTime(): Long? {
18+
if (Build.VERSION.SDK_INT > 18) {
19+
try {
20+
val getDoubleTapMinTimeMethod = ViewConfiguration::class.java.getDeclaredMethod("getDoubleTapMinTime")
21+
return (getDoubleTapMinTimeMethod.invoke(null) as Int).toLong()
22+
} catch (nsme: NoSuchMethodException) {
23+
Log.w(LOG_TAG, "Expected to find getDoubleTapMinTime", nsme)
24+
} catch (ite: InvocationTargetException) {
25+
Log.w(LOG_TAG, "Unable to query multi-tap min time!", ite)
26+
} catch (iae: IllegalAccessException) {
27+
Log.w(LOG_TAG, "Unable to query multi-tap min time!", iae)
28+
}
29+
}
30+
return null
31+
}
32+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.wix.detox.espresso.action
2+
3+
import androidx.test.espresso.UiController
4+
import androidx.test.espresso.action.Tapper
5+
import com.nhaarman.mockitokotlin2.*
6+
import org.spekframework.spek2.Spek
7+
import org.spekframework.spek2.style.specification.describe
8+
import org.assertj.core.api.Assertions.assertThat
9+
import kotlin.test.*
10+
11+
private fun dontCareCoordinates() = FloatArray(2) { 0f }
12+
private fun dontCarePrecision() = FloatArray(2) { 0f }
13+
14+
object DetoxMultiTapSpec: Spek({
15+
describe("Detox multi-tapper replacement for Espresso") {
16+
17+
val coordinates = dontCareCoordinates()
18+
val precision = dontCarePrecision()
19+
val interTapDelayMs = 667L
20+
21+
lateinit var delegatedTapper: Tapper
22+
lateinit var uiController: UiController
23+
24+
beforeEachTest {
25+
uiController = mock()
26+
delegatedTapper = mock()
27+
}
28+
29+
fun uut(times: Int) = DetoxMultiTap(times, interTapDelayMs) { delegatedTapper }
30+
fun uutNoTapWait(times: Int) = DetoxMultiTap(times, null) { delegatedTapper }
31+
32+
it("should trigger a delegated tapper (typically a single-tap tapper)") {
33+
uut(1).sendTap(uiController, coordinates, precision, 0, 0)
34+
verify(delegatedTapper, times(1)).sendTap(uiController, coordinates, precision, 0, 0)
35+
}
36+
37+
it("should trigger the delegated tapper multiple times") {
38+
uut(2).sendTap(uiController, coordinates, precision, 0, 0)
39+
verify(delegatedTapper, times(2)).sendTap(uiController, coordinates, precision, 0, 0)
40+
}
41+
42+
it("should return a successful result") {
43+
val result = uut(2).sendTap(uiController, coordinates, precision, 0, 0)
44+
assertThat(result).isEqualTo(Tapper.Status.SUCCESS)
45+
}
46+
47+
it("should break early if delegated tapper fails") {
48+
whenever(delegatedTapper.sendTap(eq(uiController), any(), any(), any(), any())).thenReturn(Tapper.Status.FAILURE)
49+
50+
val result = uut(2).sendTap(uiController, coordinates, precision, 0, 0)
51+
52+
verify(delegatedTapper, times(1)).sendTap(uiController, coordinates, precision, 0, 0)
53+
assertThat(result).isEqualTo(Tapper.Status.FAILURE)
54+
}
55+
56+
it("should wait in-between taps") {
57+
uut(2).sendTap(uiController, coordinates, precision, 0, 0)
58+
59+
verify(uiController, times(1)).loopMainThreadForAtLeast(interTapDelayMs)
60+
inOrder(delegatedTapper, uiController) {
61+
verify(delegatedTapper).sendTap(any(), any(), any(), any(), any())
62+
verify(uiController).loopMainThreadForAtLeast(any())
63+
verify(delegatedTapper).sendTap(any(), any(), any(), any(), any())
64+
}
65+
}
66+
67+
it("should not wait in-between taps if time-out was set 'null'") {
68+
uutNoTapWait(2).sendTap(uiController, coordinates, precision, 0, 0)
69+
verify(uiController, never()).loopMainThreadForAtLeast(any())
70+
}
71+
72+
it("should throw if no UI-controller provided") {
73+
assertFailsWith(KotlinNullPointerException::class) {
74+
uut(2).sendTap(null, coordinates, precision, 0, 0)
75+
}
76+
}
77+
78+
it("should throw if no coordinates / precision are provided") {
79+
assertFailsWith(KotlinNullPointerException::class) {
80+
uut(2).sendTap(uiController, null, precision, 0, 0)
81+
}
82+
83+
assertFailsWith(KotlinNullPointerException::class) {
84+
uut(2).sendTap(uiController, coordinates, null, 0, 0)
85+
}
86+
}
87+
88+
it("should support the tapper's deprecated sendTap() call") {
89+
val result = uut(1).sendTap(uiController, coordinates, precision)
90+
verify(delegatedTapper, times(1)).sendTap(uiController, coordinates, precision, 0, 0)
91+
assertThat(result).isEqualTo(Tapper.Status.SUCCESS)
92+
}
93+
}
94+
})

detox/android/detox/src/test/java/com/wix/detox/espresso/action/DetoxSingleTapSpec.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.wix.detox.espresso.common.MotionEvents
88
import org.assertj.core.api.Assertions.assertThat
99
import org.spekframework.spek2.Spek
1010
import org.spekframework.spek2.style.specification.describe
11+
import kotlin.test.assertFailsWith
1112

1213
private fun dontCareCoordinates() = FloatArray(2) { 0f }
1314
private fun dontCarePrecision() = FloatArray(2) { 0f }
@@ -157,5 +158,21 @@ object DetoxSingleTapSpec: Spek({
157158

158159
verify(uiController, never()).loopMainThreadForAtLeast(any())
159160
}
161+
162+
it("should throw if no UI-controller provided") {
163+
assertFailsWith(KotlinNullPointerException::class) {
164+
uut().sendTap(null, dontCareCoordinates(), dontCarePrecision(), 0, 0)
165+
}
166+
}
167+
168+
it("should throw if no coordinates / precision are provided") {
169+
assertFailsWith(KotlinNullPointerException::class) {
170+
uut().sendTap(uiController, null, dontCarePrecision(), 0, 0)
171+
}
172+
173+
assertFailsWith(KotlinNullPointerException::class) {
174+
uut().sendTap(uiController, dontCareCoordinates(), null, 0, 0)
175+
}
176+
}
160177
}
161178
})

0 commit comments

Comments
 (0)