detox 20.0.0-breaking.new-global-lifecycle.0 → 20.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (204) hide show
  1. package/Detox-android/com/wix/detox/{20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-javadoc.jar → 20.0.0/detox-20.0.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-sources.jar → 20.0.0/detox-20.0.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.pom → 20.0.0/detox-20.0.0.pom} +2 -8
  17. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.0.0/detox-20.0.0.pom.sha512 +1 -0
  21. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  22. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  26. package/Detox-ios-src.tbz +0 -0
  27. package/Detox-ios.tbz +0 -0
  28. package/README.md +1 -1
  29. package/android/build.gradle +13 -8
  30. package/android/detox/build.gradle +14 -10
  31. package/android/detox/proguard-rules-app.pro +6 -0
  32. package/android/detox/proguard-rules.pro +3 -0
  33. package/android/detox/publish-pom.gradle +5 -1
  34. package/android/detox/publishing.gradle +35 -33
  35. package/android/detox/src/full/java/com/wix/detox/DetoxCrashHandler.kt +1 -1
  36. package/android/detox/src/full/java/com/wix/detox/LaunchArgs.java +9 -0
  37. package/android/detox/src/full/java/com/wix/detox/TestEngineFacade.kt +1 -1
  38. package/android/detox/src/full/java/com/wix/detox/adapters/server/WebSocketClient.java +3 -1
  39. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAction.java +1 -3
  40. package/android/detox/src/full/java/com/wix/detox/espresso/UiAutomatorHelper.java +1 -1
  41. package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +22 -0
  42. package/android/detox/src/{main → full}/java/com/wix/detox/espresso/action/GetAttributesAction.kt +13 -1
  43. package/android/detox/src/full/java/com/wix/detox/espresso/common/SliderHelper.kt +75 -0
  44. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +16 -23
  45. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +15 -2
  46. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt +43 -38
  47. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeLoadingMonitor.kt +54 -8
  48. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/IdlingResourceDescription.kt +19 -13
  49. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResource.java +33 -30
  50. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.kt +7 -27
  51. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.kt +1 -11
  52. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/uimodule/RN66Workaround.kt +13 -4
  53. package/android/detox/src/main/java/com/wix/detox/common/DetoxErrors.java +4 -1
  54. package/android/detox/src/main/java/com/wix/detox/common/TextFileReader.kt +1 -1
  55. package/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +2 -1
  56. package/android/detox/src/main/java/com/wix/detox/espresso/action/common/ReflectUtils.kt +10 -0
  57. package/android/detox/src/main/java/com/wix/detox/espresso/action/common/utils/UiControllerUtils.kt +1 -1
  58. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/DetoxMultiTapSpec.kt +4 -3
  59. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +15 -3
  60. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/SliderHelperTest.kt +39 -0
  61. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResourcesTest.kt +61 -0
  62. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategySpec.kt +3 -11
  63. package/android/gradle/wrapper/gradle-wrapper.properties +2 -1
  64. package/android/gradlew +181 -107
  65. package/index.d.ts +198 -57
  66. package/internals.d.ts +220 -51
  67. package/local-cli/build.js +2 -2
  68. package/local-cli/build.test.js +14 -14
  69. package/local-cli/cli.js +8 -6
  70. package/local-cli/init.js +61 -21
  71. package/local-cli/rebuild-framework-cache.js +1 -1
  72. package/local-cli/reset-lock-file.js +16 -0
  73. package/local-cli/templates/jest.js +13 -10
  74. package/local-cli/test.js +14 -8
  75. package/local-cli/test.test.js +148 -61
  76. package/local-cli/testCommand/TestRunnerCommand.js +78 -71
  77. package/local-cli/testCommand/builder.js +0 -1
  78. package/local-cli/testCommand/middlewares.js +4 -13
  79. package/local-cli/testCommand/warnings.js +0 -3
  80. package/local-cli/utils/jestInternals.js +4 -1
  81. package/package.json +22 -15
  82. package/runners/deprecation.js +42 -44
  83. package/runners/jest/globalSetup.js +1 -1
  84. package/runners/jest/globalTeardown.js +1 -1
  85. package/runners/jest/index.d.ts +60 -0
  86. package/runners/jest/index.js +3 -8
  87. package/runners/jest/index.test.js +13 -0
  88. package/runners/jest/reporters/DetoxReporter.js +33 -2
  89. package/runners/jest/testEnvironment/index.js +119 -69
  90. package/runners/jest/testEnvironment/listeners/DetoxCoreListener.js +94 -51
  91. package/runners/jest/testEnvironment/listeners/DetoxPlatformFilterListener.js +1 -1
  92. package/runners/jest/testEnvironment/listeners/SpecReporter.js +14 -16
  93. package/runners/jest/testEnvironment/listeners/WorkerAssignReporter.js +2 -6
  94. package/runners/jest/testEnvironment/utils/assertJestCircus27.js +17 -3
  95. package/runners/jest/testEnvironment/utils/assertJestCircus27.test.js +0 -1
  96. package/src/DetoxWorker.js +107 -59
  97. package/src/android/core/NativeElement.js +56 -20
  98. package/src/android/core/NativeExpect.js +28 -9
  99. package/src/android/interactions/native.js +25 -18
  100. package/src/artifacts/ArtifactsManager.js +14 -47
  101. package/src/artifacts/instruments/ios/SimulatorInstrumentsRecording.js +3 -3
  102. package/src/artifacts/log/android/ADBLogcatRecording.js +11 -28
  103. package/src/artifacts/log/ios/SimulatorLogRecording.js +1 -1
  104. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +1 -1
  105. package/src/artifacts/templates/artifact/Artifact.js +1 -1
  106. package/src/artifacts/templates/plugin/ArtifactPlugin.js +1 -1
  107. package/src/artifacts/timeline/TimelineContextTypes.js +7 -0
  108. package/src/artifacts/utils/temporaryPath.js +18 -7
  109. package/src/artifacts/video/SimulatorRecordVideoPlugin.js +1 -1
  110. package/src/client/AsyncWebSocket.js +8 -17
  111. package/src/client/Client.js +19 -2
  112. package/src/client/actions/formatters/sync-resources/NetworkFormatter.js +1 -1
  113. package/src/configuration/collectCliConfig.js +1 -12
  114. package/src/configuration/composeAppsConfig.js +5 -1
  115. package/src/configuration/composeDeviceConfig.js +1 -1
  116. package/src/configuration/composeLoggerConfig.js +19 -10
  117. package/src/configuration/composeRunnerConfig.js +62 -9
  118. package/src/configuration/index.js +14 -9
  119. package/src/configuration/loadExternalConfig.js +1 -1
  120. package/src/devices/allocation/DeviceAllocator.js +3 -2
  121. package/src/devices/allocation/drivers/android/emulator/AVDValidator.js +5 -5
  122. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +4 -3
  123. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +1 -1
  124. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +3 -2
  125. package/src/devices/allocation/drivers/android/emulator/EmulatorVersionResolver.js +4 -6
  126. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +1 -1
  127. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +1 -0
  128. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +1 -1
  129. package/src/devices/common/drivers/android/exec/ADB.js +5 -0
  130. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +3 -3
  131. package/src/devices/common/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  132. package/src/devices/common/drivers/android/tools/EmulatorTelnet.js +1 -1
  133. package/src/devices/common/drivers/android/tools/FreeDeviceFinder.js +1 -1
  134. package/src/devices/common/drivers/android/tools/MonitoredInstrumentation.js +1 -1
  135. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +29 -3
  136. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +13 -15
  137. package/src/devices/runtime/RuntimeDevice.js +19 -12
  138. package/src/devices/runtime/drivers/DeviceDriverBase.js +1 -1
  139. package/src/devices/runtime/drivers/android/AndroidDriver.js +10 -2
  140. package/src/devices/runtime/drivers/ios/SimulatorDriver.js +1 -1
  141. package/src/errors/DetoxConfigErrorComposer.js +18 -3
  142. package/src/ios/expectTwo.js +153 -67
  143. package/src/ipc/IPCClient.js +12 -12
  144. package/src/ipc/IPCServer.js +34 -17
  145. package/src/ipc/{state.js → SessionState.js} +26 -31
  146. package/src/logger/DetoxLogger.js +261 -157
  147. package/src/logger/index.js +5 -0
  148. package/src/logger/utils/BunyanLogger.js +76 -0
  149. package/src/logger/utils/CategoryThreadDispatcher.js +36 -0
  150. package/src/logger/utils/DetoxLogFinalizer.js +140 -0
  151. package/src/logger/utils/MessageStack.js +24 -0
  152. package/src/logger/utils/ThreadDispatcher.js +61 -0
  153. package/src/logger/{customConsoleLogger.js → utils/customConsoleLogger.js} +5 -4
  154. package/src/logger/utils/sanitizeBunyanContext.js +30 -0
  155. package/src/{utils → logger/utils}/streamUtils.js +51 -17
  156. package/src/logger/utils/tracerLegacy.js +37 -0
  157. package/src/realms/DetoxContext.js +78 -65
  158. package/src/realms/DetoxInternalsFacade.js +8 -12
  159. package/src/realms/DetoxPrimaryContext.js +106 -73
  160. package/src/realms/DetoxSecondaryContext.js +29 -31
  161. package/src/server/DetoxConnection.js +18 -23
  162. package/src/server/DetoxServer.js +7 -10
  163. package/src/server/DetoxSession.js +6 -6
  164. package/src/server/DetoxSessionManager.js +1 -1
  165. package/src/server/handlers/RegisteredConnectionHandler.js +1 -2
  166. package/src/symbols.js +16 -22
  167. package/src/utils/Timer.js +55 -38
  168. package/src/utils/argparse.js +11 -0
  169. package/src/utils/childProcess/exec.js +1 -1
  170. package/src/utils/childProcess/spawn.js +1 -1
  171. package/src/utils/environment.js +30 -15
  172. package/src/utils/errorUtils.js +24 -3
  173. package/src/utils/invocationTraceDescriptions.js +43 -0
  174. package/src/utils/logger.js +1 -1
  175. package/src/utils/traceInvocationCall.js +21 -0
  176. package/src/utils/traceMethods.js +15 -0
  177. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-javadoc.jar.md5 +0 -1
  178. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-javadoc.jar.sha1 +0 -1
  179. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-javadoc.jar.sha256 +0 -1
  180. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-javadoc.jar.sha512 +0 -1
  181. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-sources.jar.md5 +0 -1
  182. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-sources.jar.sha1 +0 -1
  183. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-sources.jar.sha256 +0 -1
  184. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0-sources.jar.sha512 +0 -1
  185. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.aar +0 -0
  186. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.aar.md5 +0 -1
  187. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.aar.sha1 +0 -1
  188. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.aar.sha256 +0 -1
  189. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.aar.sha512 +0 -1
  190. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.pom.md5 +0 -1
  191. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.pom.sha1 +0 -1
  192. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.pom.sha256 +0 -1
  193. package/Detox-android/com/wix/detox/20.0.0-breaking.new-global-lifecycle.0/detox-20.0.0-breaking.new-global-lifecycle.0.pom.sha512 +0 -1
  194. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategy.kt +0 -84
  195. package/android/detox/src/main/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +0 -36
  196. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/AdjustSliderToPositionActionTest.kt +0 -59
  197. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategySpec.kt +0 -115
  198. package/runners/jest/deprecation.js +0 -25
  199. package/src/configuration/utils/warnings.js +0 -12
  200. package/src/logger/DetoxTraceEventBuilder.js +0 -21
  201. package/src/logger/DetoxTracer.js +0 -133
  202. package/src/logger/TraceThreadDispatcher.js +0 -52
  203. package/src/utils/ChromeTracingExporter.js +0 -53
  204. package/src/utils/trace.js +0 -19
@@ -3,15 +3,17 @@
3
3
  package com.wix.detox.espresso.matcher
4
4
 
5
5
  import android.view.View
6
+ import androidx.appcompat.widget.AppCompatSeekBar
6
7
  import androidx.test.espresso.matcher.BoundedMatcher
7
8
  import androidx.test.espresso.matcher.ViewMatchers
8
9
  import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
9
- import com.facebook.react.views.slider.ReactSlider
10
+ import com.wix.detox.espresso.common.SliderHelper
10
11
  import org.hamcrest.BaseMatcher
11
12
  import org.hamcrest.Description
12
13
  import org.hamcrest.Matcher
13
14
  import org.hamcrest.Matchers.allOf
14
15
  import org.hamcrest.TypeSafeMatcher
16
+ import kotlin.math.abs
15
17
 
16
18
  /*
17
19
  * An extension of [androidx.test.espresso.matcher.ViewMatchers].
@@ -36,6 +38,19 @@ fun isOfClassName(className: String): Matcher<View> {
36
38
  fun isMatchingAtIndex(index: Int, innerMatcher: Matcher<View>): Matcher<View> =
37
39
  ViewAtIndexMatcher(index, innerMatcher)
38
40
 
41
+ fun toHaveSliderPosition(expectedValuePct: Double, tolerance: Double): Matcher<View?> =
42
+ object: BoundedMatcher<View?, AppCompatSeekBar>(AppCompatSeekBar::class.java) {
43
+ override fun describeTo(description: Description) {
44
+ description.appendText("sliderPositionPercent($expectedValuePct)")
45
+ }
46
+
47
+ override fun matchesSafely(view: AppCompatSeekBar): Boolean {
48
+ val sliderHelper = SliderHelper.create(view)
49
+ val progressPct = sliderHelper.getCurrentProgressPct()
50
+ return (abs(progressPct - expectedValuePct) <= tolerance)
51
+ }
52
+ }
53
+
39
54
  /**
40
55
  * Same as [androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom], but accepts any class. Needed
41
56
  * in order to avoid warning when passing 'any' class.
@@ -47,25 +62,3 @@ private class IsAssignableFromMatcher(private val clazz: Class<*>) : TypeSafeMat
47
62
  description.appendText("is assignable from class: $clazz")
48
63
  }
49
64
  }
50
-
51
- fun toHaveSliderPosition(expectedValue: Double, tolerance: Double): Matcher<View?> {
52
- return object : BoundedMatcher<View?, ReactSlider>(ReactSlider::class.java) {
53
- override fun describeTo(description: Description) {
54
- description.appendText("expected: $expectedValue")
55
- }
56
-
57
- override fun matchesSafely(slider: ReactSlider?): Boolean {
58
- val currentProgress = slider?.progress
59
-
60
- if (currentProgress != null) {
61
- val realProgress = slider.toRealProgress(currentProgress)
62
- val currentPctFactor = slider.max / currentProgress.toDouble()
63
- val realTotal = realProgress * currentPctFactor
64
- val actualValue = realProgress / realTotal
65
- return Math.abs(actualValue - expectedValue) <= tolerance
66
- }
67
-
68
- return false
69
- }
70
- }
71
- }
@@ -39,7 +39,7 @@ object ReactNativeExtension {
39
39
  reloadReactNativeInBackground(it)
40
40
  val reactContext = awaitNewReactNativeContext(it, previousReactContext)
41
41
 
42
- setupIdlingResources(reactContext, networkSyncEnabled)
42
+ enableOrDisableSynchronization(reactContext, networkSyncEnabled)
43
43
  hackRN50OrHigherWaitForReady()
44
44
  }
45
45
  }
@@ -58,7 +58,7 @@ object ReactNativeExtension {
58
58
  (applicationContext as ReactApplication).let {
59
59
  val reactContext = awaitNewReactNativeContext(it, null)
60
60
 
61
- setupIdlingResources(reactContext)
61
+ enableOrDisableSynchronization(reactContext)
62
62
  hackRN50OrHigherWaitForReady()
63
63
  }
64
64
  }
@@ -124,6 +124,19 @@ object ReactNativeExtension {
124
124
  return rnLoadingMonitor.getNewContext()!!
125
125
  }
126
126
 
127
+ private fun enableOrDisableSynchronization(reactContext: ReactContext, networkSyncEnabled: Boolean = true) {
128
+ if (shouldDisableSynchronization()) {
129
+ clearAllSynchronization()
130
+ } else {
131
+ setupIdlingResources(reactContext, networkSyncEnabled)
132
+ }
133
+ }
134
+
135
+ private fun shouldDisableSynchronization(): Boolean {
136
+ val launchArgs = LaunchArgs()
137
+ return launchArgs.hasEnableSynchronization() && launchArgs.enableSynchronization.equals("0")
138
+ }
139
+
127
140
  private fun setupIdlingResources(reactContext: ReactContext, networkSyncEnabled: Boolean = true) {
128
141
  val launchArgs = LaunchArgs()
129
142
 
@@ -2,6 +2,7 @@ package com.wix.detox.reactnative
2
2
 
3
3
  import android.os.Looper
4
4
  import android.util.Log
5
+ import androidx.test.espresso.Espresso
5
6
  import androidx.test.espresso.IdlingRegistry
6
7
  import androidx.test.espresso.base.IdlingResourceRegistry
7
8
  import com.facebook.react.bridge.ReactContext
@@ -12,7 +13,6 @@ import com.wix.detox.reactnative.idlingresources.timers.getInterrogationStrategy
12
13
  import com.wix.detox.reactnative.idlingresources.uimodule.UIModuleIdlingResource
13
14
  import org.joor.Reflect
14
15
  import org.joor.ReflectException
15
- import java.util.Set
16
16
 
17
17
  private const val LOG_TAG = "DetoxRNIdleRes"
18
18
 
@@ -46,15 +46,11 @@ private class MQThreadReflected(private val queue: Any?, private val queueName:
46
46
  }
47
47
 
48
48
  class ReactNativeIdlingResources constructor(
49
- private val reactContext: ReactContext,
50
- private var launchArgs: LaunchArgs,
51
- internal var networkSyncEnabled: Boolean = true
52
- )
53
-
54
- {
55
-
49
+ private val reactContext: ReactContext,
50
+ private var launchArgs: LaunchArgs,
51
+ internal var networkSyncEnabled: Boolean = true
52
+ ) {
56
53
  companion object {
57
- private const val FIELD_UI_BG_MSG_QUEUE = "mUiBackgroundMessageQueueThread"
58
54
  private const val FIELD_NATIVE_MODULES_MSG_QUEUE = "mNativeModulesMessageQueueThread"
59
55
  private const val FIELD_JS_MSG_QUEUE = "mJSMessageQueueThread"
60
56
  }
@@ -69,17 +65,11 @@ class ReactNativeIdlingResources constructor(
69
65
 
70
66
  fun registerAll() {
71
67
  Log.i(LOG_TAG, "Setting up Espresso Idling Resources for React Native")
72
-
73
68
  unregisterAll()
74
69
 
75
- if (launchArgs.hasURLBlacklist()) {
76
- val blacklistUrls = launchArgs.getURLBlacklist()
77
- setBlacklistUrls(blacklistUrls)
78
- }
79
-
70
+ setupUrlBlacklist()
80
71
  setupMQThreadsInterrogators()
81
72
  syncIdlingResources()
82
-
83
73
  setupCustomRNIdlingResources()
84
74
  syncIdlingResources()
85
75
  }
@@ -108,28 +98,42 @@ class ReactNativeIdlingResources constructor(
108
98
  networkIdlingResource?.resume()
109
99
  }
110
100
  }
101
+
111
102
  fun pauseRNTimersIdlingResource() = timersIdlingResource?.pause()
112
103
  fun resumeRNTimersIdlingResource() = timersIdlingResource?.resume()
113
104
  fun pauseUIIdlingResource() = uiModuleIdlingResource?.pause()
114
105
  fun resumeUIIdlingResource() = uiModuleIdlingResource?.resume()
115
- fun pauseJSBridgeIdlingResource() = rnBridgeIdlingResource?.pause()
116
- fun resumeJSBridgeIdlingResource() = rnBridgeIdlingResource?.resume()
106
+
107
+ fun setBlacklistUrls(urlList: String) {
108
+ setIldingResourceBlacklist(urlList)
109
+ }
110
+
111
+ private fun setIldingResourceBlacklist(urlList: String) {
112
+ val urlArray = toFormattedUrlArray(urlList)
113
+ NetworkIdlingResource.setURLBlacklist(urlArray)
114
+ }
117
115
 
118
116
  private fun setupMQThreadsInterrogators() {
119
117
  if (IdlingRegistry.getInstance().loopers.isEmpty()) {
120
118
  val mqThreadsReflector = MQThreadsReflector(reactContext)
121
- // val mqUIBackground = mqThreadsReflector.getQueue(FIELD_UI_BG_MSG_QUEUE)?.getLooper() TODO
122
119
  val mqJS = mqThreadsReflector.getQueue(FIELD_JS_MSG_QUEUE)?.getLooper()
123
- val mqNativeModules = mqThreadsReflector.getQueue(FIELD_NATIVE_MODULES_MSG_QUEUE)?.getLooper()
120
+ val mqNativeModules =
121
+ mqThreadsReflector.getQueue(FIELD_NATIVE_MODULES_MSG_QUEUE)?.getLooper()
124
122
 
125
123
  IdlingRegistry.getInstance().apply {
126
- // registerLooperAsIdlingResource(mqUIBackground)
127
124
  registerLooperAsIdlingResource(mqJS)
128
125
  registerLooperAsIdlingResource(mqNativeModules)
129
126
  }
130
127
  }
131
128
  }
132
129
 
130
+ private fun setupUrlBlacklist() {
131
+ if (launchArgs.hasURLBlacklist()) {
132
+ val blacklistUrls = launchArgs.urlBlacklist
133
+ setIldingResourceBlacklist(blacklistUrls)
134
+ }
135
+ }
136
+
133
137
  private fun setupCustomRNIdlingResources() {
134
138
  rnBridgeIdlingResource = BridgeIdlingResource(reactContext)
135
139
  timersIdlingResource = TimersIdlingResource(getInterrogationStrategy(reactContext)!!)
@@ -151,13 +155,16 @@ class ReactNativeIdlingResources constructor(
151
155
 
152
156
  private fun syncIdlingResources() {
153
157
  IdlingRegistry.getInstance().apply {
154
- val irr: IdlingResourceRegistry = Reflect.on(androidx.test.espresso.Espresso::class.java).field("baseRegistry").get()
158
+ val irr: IdlingResourceRegistry =
159
+ Reflect.on(Espresso::class.java).field("baseRegistry").get()
155
160
  irr.sync(this.resources, this.loopers)
156
161
  }
157
162
  }
158
163
 
159
164
  private fun unregisterMQThreadsInterrogators() {
160
- Reflect.on(IdlingRegistry.getInstance()).field("loopers").get<Set<Any>>().clear()
165
+ val idlingResourceInstance = IdlingRegistry.getInstance()
166
+ val loopersField = Reflect.on(idlingResourceInstance).field("loopers")
167
+ loopersField.get<MutableSet<Any>>().clear()
161
168
  }
162
169
 
163
170
  private fun unregisterCustomRNIdlingResources() {
@@ -166,7 +173,8 @@ class ReactNativeIdlingResources constructor(
166
173
  timersIdlingResource,
167
174
  rnBridgeIdlingResource,
168
175
  uiModuleIdlingResource,
169
- animIdlingResource)
176
+ animIdlingResource
177
+ )
170
178
  rnBridgeIdlingResource?.onDetach()
171
179
 
172
180
  removeNetworkIdlingResource()
@@ -174,13 +182,15 @@ class ReactNativeIdlingResources constructor(
174
182
  }
175
183
 
176
184
  private fun setupAsyncStorageIdlingResource() {
177
- asyncStorageIdlingResource = AsyncStorageIdlingResource.createIfNeeded(reactContext, false)?.also {
178
- IdlingRegistry.getInstance().register(it)
179
- }
185
+ asyncStorageIdlingResource =
186
+ AsyncStorageIdlingResource.createIfNeeded(reactContext, false)?.also {
187
+ IdlingRegistry.getInstance().register(it)
188
+ }
180
189
 
181
- legacyAsyncStorageIdlingResource = AsyncStorageIdlingResource.createIfNeeded(reactContext, true)?.also {
182
- IdlingRegistry.getInstance().register(it)
183
- }
190
+ legacyAsyncStorageIdlingResource =
191
+ AsyncStorageIdlingResource.createIfNeeded(reactContext, true)?.also {
192
+ IdlingRegistry.getInstance().register(it)
193
+ }
184
194
  }
185
195
 
186
196
  private fun removeAsyncStorageIdlingResource() {
@@ -212,13 +222,8 @@ class ReactNativeIdlingResources constructor(
212
222
 
213
223
  private fun toFormattedUrlArray(urlList: String): List<String> {
214
224
  var formattedUrls = urlList
215
- formattedUrls = formattedUrls.replace(Regex("""[()"]"""), "");
216
- formattedUrls = formattedUrls.trim();
217
- return formattedUrls.split(',');
218
- }
219
-
220
- fun setBlacklistUrls(urlList: String) {
221
- val urlArray = toFormattedUrlArray(urlList)
222
- NetworkIdlingResource.setURLBlacklist(urlArray);
225
+ formattedUrls = formattedUrls.replace(Regex("""[()"]"""), "")
226
+ formattedUrls = formattedUrls.trim()
227
+ return formattedUrls.split(',')
223
228
  }
224
229
  }
@@ -7,17 +7,22 @@ import com.facebook.react.ReactInstanceManager
7
7
  import com.facebook.react.bridge.ReactContext
8
8
  import com.wix.detox.common.DetoxErrors
9
9
  import com.wix.detox.config.DetoxConfig
10
+ import org.joor.Reflect
11
+ import java.lang.reflect.Proxy
10
12
  import java.util.concurrent.CountDownLatch
11
13
  import java.util.concurrent.TimeUnit
12
14
 
13
15
  private const val LOG_TAG = "DetoxRNLoading"
14
16
 
17
+ private const val REACT_INSTANCE_EVENT_LISTENER_CLASS = "com.facebook.react.ReactInstanceEventListener"
18
+ private const val REACT_INSTANCE_EVENT_LISTENER_CLASS_COMPAT = "com.facebook.react.ReactInstanceManager\$ReactInstanceEventListener"
19
+
15
20
  open class ReactNativeLoadingMonitor(
16
21
  private val instrumentation: Instrumentation,
17
22
  private val rnApplication: ReactApplication,
18
23
  private val previousReactContext: ReactContext?,
19
24
  private val config: DetoxConfig = DetoxConfig.CONFIG) {
20
- val countDownLatch = CountDownLatch(1)
25
+ private val countDownLatch = CountDownLatch(1)
21
26
 
22
27
  fun getNewContext(): ReactContext? {
23
28
  subscribeToNewRNContextUpdates()
@@ -35,13 +40,9 @@ open class ReactNativeLoadingMonitor(
35
40
  return@Runnable
36
41
  }
37
42
 
38
- rnInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener {
39
- override fun onReactContextInitialized(context: ReactContext) {
40
- Log.i(LOG_TAG, "Got new RN-context async'ly through listener")
41
- rnInstanceManager.removeReactInstanceEventListener(this)
42
- countDownLatch.countDown()
43
- }
44
- })
43
+ subscribeAsyncRNContextHandler(rnInstanceManager) {
44
+ countDownLatch.countDown()
45
+ }
45
46
  })
46
47
  }
47
48
 
@@ -81,3 +82,48 @@ open class ReactNativeLoadingMonitor(
81
82
  return rnInstanceManager.currentReactContext
82
83
  }
83
84
  }
85
+
86
+ private interface DummyListenerIdentifier
87
+
88
+ /**
89
+ * This baby bridges over RN's breaking change introduced in version 0.68:
90
+ * `ReactInstanceManager$ReactInstanceEventListener` was extracted onto a separate interface, having
91
+ * `ReactInstanceManager.{add|remove}ReactInstanceEventLister()` changing their signature to use it, accordingly.
92
+ *
93
+ * This made us resort to a solution based on dynamic proxies, because - depending on RN's version (at runtime), we
94
+ * need to dynamically decide what interface we "extend" (or actually shadow) via the proxy.
95
+ *
96
+ * @see RNDiff https://github.com/facebook/react-native/compare/v0.67.4..v0.68.0#diff-2f01f0cd7ff8c9ea58f12ef0eff5fa8250cad144dd5490598d80d8e9e743458aR1009
97
+ * @see DynamicProxies https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html
98
+ */
99
+ private fun subscribeAsyncRNContextHandler(rnInstanceManager: ReactInstanceManager, onReactContextInitialized: () -> Any) {
100
+ val listenerClass = resolveListenerClass()
101
+ val proxyInterfaces = arrayOf(
102
+ listenerClass,
103
+ DummyListenerIdentifier::class.java // In order to be able to implement equals()
104
+ )
105
+ val listener = Proxy.newProxyInstance(listenerClass.classLoader, proxyInterfaces) { listener, method, args ->
106
+ Log.d(LOG_TAG, "Listener-proxy method called: ${method.name}")
107
+
108
+ val result = when (method.name) {
109
+ "onReactContextInitialized" -> {
110
+ Log.i(LOG_TAG, "Got new RN-context async'ly through listener")
111
+ Reflect.on(rnInstanceManager).call("removeReactInstanceEventListener", listener)
112
+ onReactContextInitialized()
113
+ }
114
+ "equals" -> {
115
+ val candidate = args[0]
116
+ candidate is DummyListenerIdentifier
117
+ }
118
+ else -> Any()
119
+ }
120
+
121
+ result
122
+ }
123
+ Reflect.on(rnInstanceManager).call("addReactInstanceEventListener", listener)
124
+ }
125
+
126
+ private fun resolveListenerClass(): Class<*> {
127
+ val className = if (ReactNativeInfo.rnVersion().minor >= 68) REACT_INSTANCE_EVENT_LISTENER_CLASS else REACT_INSTANCE_EVENT_LISTENER_CLASS_COMPAT
128
+ return Class.forName(className)
129
+ }
@@ -1,20 +1,26 @@
1
1
  package com.wix.detox.reactnative.idlingresources
2
2
 
3
3
  class IdlingResourceDescription private constructor(
4
- private val name: String,
5
- private val description: Map<String, Any>) {
6
- fun json(): Map<String, Any> = mutableMapOf<String, Any>("name" to name)
7
- .apply { if (description.isNotEmpty()) put("description", description) }
4
+ private val name: String,
5
+ private val description: Map<String, Any>) {
8
6
 
9
- override fun equals(other: Any?): Boolean = other is IdlingResourceDescription &&
10
- other.json() == this.json()
7
+ fun json() =
8
+ mutableMapOf<String, Any>("name" to name)
9
+ .apply {
10
+ if (description.isNotEmpty()) put("description", description)
11
+ }.toMap()
11
12
 
12
- data class Builder(
13
- var name: String = "unknown",
14
- var description: MutableMap<String, Any> = mutableMapOf()) {
13
+ override fun equals(other: Any?) =
14
+ (other is IdlingResourceDescription && other.json() == this.json())
15
+
16
+ override fun hashCode(): Int =
17
+ (31 * name.hashCode() + description.hashCode())
15
18
 
16
- fun name(name: String) = apply { this.name = name }
17
- fun addDescription(key: String, value: Any) = apply { this.description[key] = value }
18
- fun build() = IdlingResourceDescription(name, description)
19
- }
19
+ data class Builder(
20
+ var name: String = "unknown",
21
+ var description: MutableMap<String, Any> = mutableMapOf()) {
22
+ fun name(value: String) = apply { name = value }
23
+ fun addDescription(key: String, value: Any) = apply { description[key] = value }
24
+ fun build() = IdlingResourceDescription(name, description)
25
+ }
20
26
  }
@@ -69,42 +69,13 @@ public class NetworkIdlingResource extends DetoxBaseIdlingResource implements Ch
69
69
 
70
70
  @NotNull
71
71
  @Override
72
- public IdlingResourceDescription getDescription() {
72
+ public synchronized IdlingResourceDescription getDescription() {
73
73
  return new IdlingResourceDescription.Builder()
74
74
  .name("network")
75
75
  .addDescription("urls", new ArrayList<>(busyResources))
76
76
  .build();
77
77
  }
78
78
 
79
- @Override
80
- protected boolean checkIdle() {
81
- boolean idle = true;
82
- busyResources.clear();
83
- List<Call> calls = dispatcher.runningCalls();
84
- for (Call call : calls) {
85
- idle = false;
86
- String url = call.request().url().toString();
87
- for (Pattern pattern : blacklist) {
88
- if (pattern.matcher(url).matches()) {
89
- idle = true;
90
- break;
91
- }
92
- }
93
- if (!idle) {
94
- busyResources.add(call.request().url().toString());
95
- }
96
- }
97
- if (!idle) {
98
- Choreographer.getInstance().postFrameCallback(this);
99
- Log.i(LOG_TAG, "Network is busy");
100
- } else {
101
- if (callback != null) {
102
- callback.onTransitionToIdle();
103
- }
104
- }
105
- return idle;
106
- }
107
-
108
79
  @Override
109
80
  public void registerIdleTransitionCallback(ResourceCallback callback) {
110
81
  this.callback = callback;
@@ -116,10 +87,42 @@ public class NetworkIdlingResource extends DetoxBaseIdlingResource implements Ch
116
87
  isIdleNow();
117
88
  }
118
89
 
90
+ @Override
91
+ protected synchronized boolean checkIdle() {
92
+ busyResources.clear();
93
+
94
+ List<Call> calls = dispatcher.runningCalls();
95
+ for (Call call: calls) {
96
+ final String url = call.request().url().toString();
97
+
98
+ if (!isUrlBlacklisted(url)) {
99
+ busyResources.add(url);
100
+ }
101
+ }
102
+
103
+ if (!busyResources.isEmpty()) {
104
+ Log.i(LOG_TAG, "Network is busy, with " + busyResources.size() + " in-flight calls");
105
+ Choreographer.getInstance().postFrameCallback(this);
106
+ return false;
107
+ }
108
+
109
+ notifyIdle();
110
+ return true;
111
+ }
112
+
119
113
  @Override
120
114
  protected void notifyIdle() {
121
115
  if (callback != null) {
122
116
  callback.onTransitionToIdle();
123
117
  }
124
118
  }
119
+
120
+ private boolean isUrlBlacklisted(String url) {
121
+ for (Pattern pattern: blacklist) {
122
+ if (pattern.matcher(url).matches()) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
125
128
  }
@@ -1,43 +1,23 @@
1
+
1
2
  package com.wix.detox.reactnative.idlingresources.timers
2
3
 
3
- import com.facebook.react.bridge.NativeModule
4
4
  import com.facebook.react.bridge.ReactContext
5
- import com.wix.detox.common.RNDropSupportTodo
6
- import com.wix.detox.reactnative.helpers.RNHelpers
7
- import org.joor.Reflect
5
+ import com.facebook.react.modules.core.TimingModule
8
6
 
9
7
  private const val BUSY_WINDOW_THRESHOLD = 1500L
10
8
 
11
- private class RN62TimingModuleReflected(private val timingModule: NativeModule) {
12
- fun hasActiveTimers(): Boolean = Reflect.on(timingModule).call("hasActiveTimersInRange", BUSY_WINDOW_THRESHOLD).get()
13
- }
14
-
15
9
  /**
16
10
  * Delegates the interrogation to the native module itself, added
17
11
  * [here](https://github.com/facebook/react-native/pull/27539) in the context
18
12
  * of RN v0.62 (followed by a previous refactor and rename of the class).
19
13
  */
20
- @RNDropSupportTodo(62, """
21
- When min RN version supported by Detox is 0.62.x (or higher), can (and
22
- should) remove any usage of reflection here.
23
- That includes the unit test's stub being used for that reason in particular.
24
- """)
25
- class DelegatedIdleInterrogationStrategy(timingModule: NativeModule): IdleInterrogationStrategy {
26
- private val timingModuleReflected = RN62TimingModuleReflected(timingModule)
27
-
28
- override fun isIdleNow(): Boolean = !timingModuleReflected.hasActiveTimers()
14
+ class DelegatedIdleInterrogationStrategy(private val timingModule: TimingModule): IdleInterrogationStrategy {
15
+ override fun isIdleNow(): Boolean = !timingModule.hasActiveTimersInRange(BUSY_WINDOW_THRESHOLD)
29
16
 
30
17
  companion object {
31
- fun createIfSupported(reactContext: ReactContext): DelegatedIdleInterrogationStrategy? {
32
- val module = RNHelpers.getNativeModule(reactContext, "com.facebook.react.modules.core.TimingModule")
33
- ?: return null
34
-
35
- return try {
36
- module.javaClass.getDeclaredMethod("hasActiveTimersInRange", Long::class.java)
37
- DelegatedIdleInterrogationStrategy(module)
38
- } catch (ex: Exception) {
39
- null
40
- }
18
+ fun create(reactContext: ReactContext): DelegatedIdleInterrogationStrategy {
19
+ val timingModule = reactContext.getNativeModule(TimingModule::class.java)!!
20
+ return DelegatedIdleInterrogationStrategy(timingModule)
41
21
  }
42
22
  }
43
23
  }
@@ -1,6 +1,5 @@
1
1
  package com.wix.detox.reactnative.idlingresources.timers
2
2
 
3
- import android.util.Log
4
3
  import com.facebook.react.bridge.ReactContext
5
4
  import com.wix.detox.common.UIThread
6
5
  import java.util.concurrent.Callable
@@ -13,14 +12,5 @@ fun getInterrogationStrategy(reactContext: ReactContext): IdleInterrogationStrat
13
12
  // Getting a native-module (inside) also initializes it if needed. That has to run on a
14
13
  // looper thread, and the easiest to make sure that happens is to use the main thread.
15
14
  UIThread.runSync(Callable {
16
- DelegatedIdleInterrogationStrategy.createIfSupported(reactContext)?.let {
17
- return@Callable it
18
- }
19
-
20
- DefaultIdleInterrogationStrategy.createIfSupported(reactContext)?.let {
21
- return@Callable it
22
- }
23
-
24
- Log.e(TimersIdlingResource.LOG_TAG, "Failed to determine proper implementation-strategy for timers idling resource")
25
- null
15
+ return@Callable DelegatedIdleInterrogationStrategy.create(reactContext)
26
16
  })
@@ -2,6 +2,7 @@ package com.wix.detox.reactnative.idlingresources.uimodule
2
2
 
3
3
  import android.util.Log
4
4
  import android.view.View
5
+ import com.facebook.react.uimanager.IllegalViewOperationException
5
6
  import com.wix.detox.common.DetoxLog.Companion.LOG_TAG
6
7
  import com.wix.detox.reactnative.ReactNativeInfo
7
8
  import java.lang.ref.WeakReference
@@ -19,9 +20,7 @@ class RN66Workaround {
19
20
  fun isScarceUISwitchCommandStuckInQueue(uiManagerModuleReflected: UIManagerModuleReflected): Boolean {
20
21
  var isStuckSwitchOperation = false
21
22
 
22
- val rnVersion = ReactNativeInfo.rnVersion()
23
-
24
- if (rnVersion.minor >= 66 && uiManagerModuleReflected.getUIOpsCount() >= 1) {
23
+ if (isRelevantRNVersion() && uiManagerModuleReflected.getUIOpsCount() >= 1) {
25
24
  val nextUIOperation = uiManagerModuleReflected.getNextUIOpReflected()
26
25
  val view = getUIOpView(uiManagerModuleReflected, nextUIOperation)
27
26
  val isReactSwitch = isReactSwitch(view)
@@ -46,10 +45,20 @@ class RN66Workaround {
46
45
  return isStuckSwitchOperation
47
46
  }
48
47
 
48
+ private fun isRelevantRNVersion(): Boolean {
49
+ val rnVersion = ReactNativeInfo.rnVersion()
50
+ return rnVersion.minor == 66 || (rnVersion.minor == 67 && rnVersion.patch < 4)
51
+ }
52
+
49
53
  private fun getUIOpView(uiManagerModuleReflected: UIManagerModuleReflected, uiOperation: DispatchCommandOperationReflected?): View? {
50
54
  val nativeViewHierarchyManager = uiManagerModuleReflected.nativeViewHierarchyManager() ?: return null
51
55
  val tag = uiOperation?.tag ?: return null
52
- return nativeViewHierarchyManager.getViewClass(tag)
56
+ return try {
57
+ nativeViewHierarchyManager.getViewClass(tag)
58
+ } catch(e: IllegalViewOperationException) {
59
+ Log.e(LOG_TAG, "failed to get view from tag ", e.cause)
60
+ null
61
+ }
53
62
  }
54
63
 
55
64
  private fun isReactSwitch(view: View?) = try {
@@ -5,7 +5,6 @@ public interface DetoxErrors {
5
5
  public DetoxRuntimeException(Throwable cause) {
6
6
  super(cause);
7
7
  }
8
-
9
8
  public DetoxRuntimeException(String message) {
10
9
  super(message);
11
10
  }
@@ -31,5 +30,9 @@ public interface DetoxErrors {
31
30
  public DetoxIllegalArgumentException(String message) {
32
31
  super(message);
33
32
  }
33
+
34
+ public DetoxIllegalArgumentException(Throwable cause) {
35
+ super(cause);
36
+ }
34
37
  }
35
38
  }
@@ -3,5 +3,5 @@ package com.wix.detox.common
3
3
  import java.io.File
4
4
 
5
5
  internal class TextFileReader(private val fileName: String) {
6
- fun read() = File(fileName).inputStream().readBytes().toString(Charsets.UTF_8)
6
+ fun read() = File(fileName).readText(Charsets.UTF_8)
7
7
  }
@@ -1,10 +1,11 @@
1
+
1
2
  package com.wix.detox.espresso
2
3
 
3
4
  import androidx.test.espresso.UiController
4
5
  import com.wix.detox.common.proxy.CallInfo
5
6
  import com.wix.detox.common.proxy.SpyingInvocationHandler
6
7
  import com.wix.detox.common.proxy.MethodsSpy
7
- import com.wix.detox.espresso.common.utils.getUiController
8
+ import com.wix.detox.espresso.action.common.utils.getUiController
8
9
  import org.joor.Reflect
9
10
 
10
11
  class UiControllerSpy: MethodsSpy("uiController") {
@@ -0,0 +1,10 @@
1
+ package com.wix.detox.espresso.action.common
2
+
3
+ object ReflectUtils {
4
+ fun isAssignableFrom(source: Any, className: String) =
5
+ try {
6
+ Class.forName(className).isAssignableFrom(source.javaClass)
7
+ } catch (ex: ClassNotFoundException) {
8
+ false
9
+ }
10
+ }
@@ -1,6 +1,6 @@
1
1
  @file:JvmName("UiControllerUtils")
2
2
 
3
- package com.wix.detox.espresso.common.utils
3
+ package com.wix.detox.espresso.action.common.utils
4
4
 
5
5
  import androidx.test.espresso.Espresso
6
6
  import androidx.test.espresso.UiController