detox 20.12.1 → 20.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1-javadoc.jar → 20.12.2/detox-20.12.2-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1-sources.jar → 20.12.2/detox-20.12.2-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1.pom → 20.12.2/detox-20.12.2.pom} +1 -7
  17. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.12.2/detox-20.12.2.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/android/detox/build.gradle +4 -3
  29. package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +2 -2
  30. package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +30 -31
  31. package/android/detox/src/full/java/com/wix/detox/espresso/common/MaterialSliderHelper.kt +21 -0
  32. package/android/detox/src/full/java/com/wix/detox/espresso/common/{SliderHelper.kt → ReactSliderHelper.kt} +6 -5
  33. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +2 -2
  34. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/MaterialSliderHelperTest.kt +33 -0
  35. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/{SliderHelperTest.kt → ReactSliderHelperTest.kt} +3 -3
  36. package/internals.d.ts +10 -1
  37. package/package.json +2 -2
  38. package/runners/jest/reporters/DetoxReporter.js +127 -9
  39. package/runners/jest/testEnvironment/index.js +5 -0
  40. package/src/ipc/IPCClient.js +5 -0
  41. package/src/ipc/IPCServer.js +13 -0
  42. package/src/ipc/SessionState.js +1 -0
  43. package/src/realms/DetoxContext.js +2 -0
  44. package/src/realms/DetoxInternalsFacade.js +1 -0
  45. package/src/realms/DetoxPrimaryContext.js +6 -0
  46. package/src/realms/DetoxSecondaryContext.js +8 -0
  47. package/src/realms/symbols.js +2 -0
  48. package/src/utils/errorUtils.js +1 -1
  49. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.md5 +0 -1
  50. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha1 +0 -1
  51. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha256 +0 -1
  52. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha512 +0 -1
  53. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.md5 +0 -1
  54. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha1 +0 -1
  55. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha256 +0 -1
  56. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha512 +0 -1
  57. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar +0 -0
  58. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.md5 +0 -1
  59. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha1 +0 -1
  60. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha256 +0 -1
  61. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha512 +0 -1
  62. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.md5 +0 -1
  63. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha1 +0 -1
  64. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha256 +0 -1
  65. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha512 +0 -1
@@ -0,0 +1 @@
1
+ 9f36d2885a93f2a409ee4e590f8678fe
@@ -0,0 +1 @@
1
+ 7627d2bccbd1f922472cb85f9e8480210d672d39
@@ -0,0 +1 @@
1
+ 43075c10900e4d81f273d56e642d1230ec01a4748996955a452ee0374aec0200
@@ -0,0 +1 @@
1
+ 2ba3ccabd9c41ad2ffb785c5c2ca9b5baedfe51fadcdcdc7b2c7c215335b061e693f7985800d562dd2e7449503e7e5d44a4332daf00a85f4e5b05da848a1f37f
@@ -0,0 +1 @@
1
+ bc5126d7b11cf8ad581aab27d743b430
@@ -0,0 +1 @@
1
+ ad9dc488b327afa3154138a147f3873dd4f6b644
@@ -0,0 +1 @@
1
+ d80d2d1e03c7f3b8b75ed70946e31725694cb29d4900eacc115276295b773347
@@ -0,0 +1 @@
1
+ be1d9cc4b36042e3f0416159a7b9509ce9e35de4eaabad53c385690a893bc51a0f453e0fe46173e91436b9f1e3dcbe9ab209434e1173558d5a87e5a94c8b278b
@@ -0,0 +1 @@
1
+ 0ff20c1510b97ba80b15a438ce65bddc
@@ -0,0 +1 @@
1
+ 449b07e6bb8435baef57e3d0839e68b5f7f85af5
@@ -0,0 +1 @@
1
+ c3347ddd7b4e4a94c60c55c079451ea219e050f7c69b8dbc7b59a453d2398e67
@@ -0,0 +1 @@
1
+ ebcf7f52b7e34a391174fc8850879e39a3e3f8f8ccfa0a417937680187461d5b41f7811933ca5ca088c554ae7a4e8d8cb2198375fdbb55680888aac561c71f64
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.wix</groupId>
5
5
  <artifactId>detox</artifactId>
6
- <version>20.12.1</version>
6
+ <version>20.12.2</version>
7
7
  <packaging>aar</packaging>
8
8
  <name>Detox</name>
9
9
  <description>Gray box end-to-end testing and automation library for mobile apps</description>
@@ -84,12 +84,6 @@
84
84
  <version>1.2.0</version>
85
85
  <scope>runtime</scope>
86
86
  </dependency>
87
- <dependency>
88
- <groupId>com.google.android.material</groupId>
89
- <artifactId>material</artifactId>
90
- <version>1.2.1</version>
91
- <scope>runtime</scope>
92
- </dependency>
93
87
  <dependency>
94
88
  <groupId>org.apache.commons</groupId>
95
89
  <artifactId>commons-lang3</artifactId>
@@ -0,0 +1 @@
1
+ 06ea49e97933d82debed143a3e362af8
@@ -0,0 +1 @@
1
+ 866a73f92aa581f2d23afd5df152cce65a882de6
@@ -0,0 +1 @@
1
+ b7e47d08915a497db5f42985f3435420bf7580178c6975d1f257123c7bc53b4f
@@ -0,0 +1 @@
1
+ f7fbbb355ce037d0519ad63caeebab6447f1e38f25ce44d495267510f7e4576978fff4b676f1a86966e7f638b4678b7bc769f50e4ffa1d106c786db72b9f7d6b
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.12.1</latest>
7
- <release>20.12.1</release>
6
+ <latest>20.12.2</latest>
7
+ <release>20.12.2</release>
8
8
  <versions>
9
- <version>20.12.1</version>
9
+ <version>20.12.2</version>
10
10
  </versions>
11
- <lastUpdated>20230919081610</lastUpdated>
11
+ <lastUpdated>20230927202100</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 1e3fc98a2197d7ff387e7c2bba133329
1
+ c4e29695a8333254dfdb58e83b723a94
@@ -1 +1 @@
1
- 0bd2298f41bb8578b4eb3bf4b744dbbec757a1ac
1
+ 8c90e83ee27dd4d9370742524f141054555c926b
@@ -1 +1 @@
1
- 01991e2c36e2b46d6ee16a7de9a7bb11fdb5bb1e2f134bcd7e06a6b5f2f74fa8
1
+ 5fb1606470374345397232c3529c053c5aa26fd8650b3f7d9706f251a7f2e3b8
@@ -1 +1 @@
1
- 9c4f073180d321ea3a79f98c16155c2e55e0e2a53ea3052bda82a18a893d35af8e5a257b882f3dcbd723e0886ee3b874f92bdd2a102d06b80a8fb9090c480d2f
1
+ 541d2924f1ccdd6c901ba584a978d8d26ba3027c6abca381dcb6b2553b4b730ca428d6743bc9d1fe0836e2662a4e81f5d53049fa164376173e76960fcdc33ccc
package/Detox-ios-src.tbz CHANGED
Binary file
package/Detox-ios.tbz CHANGED
Binary file
@@ -126,9 +126,6 @@ dependencies {
126
126
 
127
127
  // Third-party/extension deps.
128
128
  dependencies {
129
- implementation("com.google.android.material:material:$_materialMinVersion") {
130
- because 'Material components are mentioned explicitly (e.g. Slider in get-attributes handler)'
131
- }
132
129
  implementation('org.apache.commons:commons-lang3:3.7') {
133
130
  because 'Needed by invoke. Warning: Upgrading to newer versions is not seamless.'
134
131
  }
@@ -150,6 +147,10 @@ dependencies {
150
147
  testImplementation 'org.apache.commons:commons-io:1.3.2'
151
148
  testImplementation 'org.mockito.kotlin:mockito-kotlin:4.0.0'
152
149
  testImplementation 'org.robolectric:robolectric:4.4'
150
+
151
+ testImplementation("com.google.android.material:material:$_materialMinVersion") {
152
+ because 'Material components are mentioned explicitly (e.g. Slider in get-attributes handler)'
153
+ }
153
154
  }
154
155
 
155
156
  // Spek (https://spekframework.org/setup-android)
@@ -6,7 +6,7 @@ import androidx.test.espresso.UiController
6
6
  import androidx.test.espresso.ViewAction
7
7
  import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
8
8
  import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
9
- import com.wix.detox.espresso.common.SliderHelper
9
+ import com.wix.detox.espresso.common.ReactSliderHelper
10
10
  import org.hamcrest.Matcher
11
11
  import org.hamcrest.Matchers
12
12
 
@@ -16,7 +16,7 @@ class AdjustSliderToPositionAction(private val targetPositionPct: Double) : View
16
16
  Matchers.allOf( isDisplayed(), isAssignableFrom(AppCompatSeekBar::class.java) )
17
17
 
18
18
  override fun perform(uiController: UiController?, view: View) {
19
- val sliderHelper = SliderHelper.create(view)
19
+ val sliderHelper = ReactSliderHelper.create(view)
20
20
  sliderHelper.setProgressPct(targetPositionPct)
21
21
  }
22
22
  }
@@ -7,10 +7,10 @@ import android.widget.CheckBox
7
7
  import android.widget.ProgressBar
8
8
  import android.widget.TextView
9
9
  import androidx.test.espresso.UiController
10
- import com.google.android.material.slider.Slider
11
10
  import com.wix.detox.espresso.ViewActionWithResult
12
11
  import com.wix.detox.espresso.MultipleViewsAction
13
- import com.wix.detox.espresso.common.SliderHelper
12
+ import com.wix.detox.espresso.common.ReactSliderHelper
13
+ import com.wix.detox.espresso.common.MaterialSliderHelper
14
14
  import com.wix.detox.reactnative.ui.getAccessibilityLabel
15
15
  import org.hamcrest.Matcher
16
16
  import org.hamcrest.Matchers
@@ -18,23 +18,25 @@ import org.hamcrest.Matchers.allOf
18
18
  import org.hamcrest.Matchers.notNullValue
19
19
  import org.json.JSONObject
20
20
 
21
+ private interface AttributeExtractor {
22
+ fun extractAttributes(json: JSONObject, view: View)
23
+ }
24
+
21
25
  class GetAttributesAction() : ViewActionWithResult<JSONObject?>, MultipleViewsAction {
22
- private val commonAttributes = CommonAttributes()
23
- private val textViewAttributes = TextViewAttributes()
24
- private val checkBoxAttributes = CheckBoxAttributes()
25
- private val progressBarAttributes = ProgressBarAttributes()
26
- private val sliderAttributes = SliderAttributes()
26
+ private val attributeExtractors = listOf(
27
+ CommonAttributes(),
28
+ TextViewAttributes(),
29
+ CheckBoxAttributes(),
30
+ ProgressBarAttributes(),
31
+ MaterialSliderAttributes()
32
+ )
27
33
  private var result: JSONObject? = null
28
34
 
29
35
  override fun perform(uiController: UiController?, view: View?) {
30
36
  view!!
31
37
 
32
38
  val json = JSONObject()
33
- commonAttributes.get(json, view)
34
- textViewAttributes.get(json, view)
35
- checkBoxAttributes.get(json, view)
36
- progressBarAttributes.get(json, view)
37
- sliderAttributes.get(json, view)
39
+ attributeExtractors.forEach { it.extractAttributes(json, view) }
38
40
 
39
41
  result = json
40
42
  }
@@ -44,8 +46,8 @@ class GetAttributesAction() : ViewActionWithResult<JSONObject?>, MultipleViewsAc
44
46
  override fun getConstraints(): Matcher<View> = allOf(notNullValue(), Matchers.isA(View::class.java))
45
47
  }
46
48
 
47
- private class CommonAttributes {
48
- fun get(json: JSONObject, view: View) {
49
+ private class CommonAttributes : AttributeExtractor {
50
+ override fun extractAttributes(json: JSONObject, view: View) {
49
51
  getId(json, view)
50
52
  getVisibility(json, view)
51
53
  getAccessibilityLabel(json, view)
@@ -89,8 +91,8 @@ private class CommonAttributes {
89
91
  }
90
92
  }
91
93
 
92
- private class TextViewAttributes {
93
- fun get(json: JSONObject, view: View) {
94
+ private class TextViewAttributes : AttributeExtractor {
95
+ override fun extractAttributes(json: JSONObject, view: View) {
94
96
  if (view is TextView) {
95
97
  getText(json, view)
96
98
  getLength(json, view)
@@ -118,8 +120,8 @@ private class TextViewAttributes {
118
120
  }
119
121
  }
120
122
 
121
- private class CheckBoxAttributes {
122
- fun get(json: JSONObject, view: View) {
123
+ private class CheckBoxAttributes : AttributeExtractor {
124
+ override fun extractAttributes(json: JSONObject, view: View) {
123
125
  if (view is CheckBox) {
124
126
  getCheckboxValue(json, view)
125
127
  }
@@ -133,31 +135,28 @@ private class CheckBoxAttributes {
133
135
  * Note: this applies also to [androidx.appcompat.widget.AppCompatSeekBar], which
134
136
  * is anything RN-slider-ish.
135
137
  */
136
- private class ProgressBarAttributes {
137
- fun get(json: JSONObject, view: View) {
138
+ private class ProgressBarAttributes : AttributeExtractor {
139
+ override fun extractAttributes(json: JSONObject, view: View) {
138
140
  if (view is ProgressBar) {
139
- SliderHelper.maybeCreate(view)?.let {
140
- getRNSliderValue(json, it)
141
+ ReactSliderHelper.maybeCreate(view)?.let {
142
+ getReactSliderValue(json, it)
141
143
  } ?:
142
144
  getProgressBarValue(json, view)
143
145
  }
144
146
  }
145
147
 
146
- private fun getRNSliderValue(rootObject: JSONObject, sliderHelper: SliderHelper) {
147
- rootObject.put("value", sliderHelper.getCurrentProgressPct())
148
+ private fun getReactSliderValue(rootObject: JSONObject, reactSliderHelper: ReactSliderHelper) {
149
+ rootObject.put("value", reactSliderHelper.getCurrentProgressPct())
148
150
  }
149
151
 
150
152
  private fun getProgressBarValue(rootObject: JSONObject, view: ProgressBar) =
151
153
  rootObject.put("value", view.progress)
152
154
  }
153
155
 
154
- private class SliderAttributes {
155
- fun get(json: JSONObject, view: View) {
156
- if (view is Slider) {
157
- getSliderValue(json, view)
156
+ private class MaterialSliderAttributes : AttributeExtractor {
157
+ override fun extractAttributes(json: JSONObject, view: View) {
158
+ MaterialSliderHelper(view).getValueIfSlider()?.let {
159
+ json.put("value", it)
158
160
  }
159
161
  }
160
-
161
- private fun getSliderValue(rootObject: JSONObject, view: Slider) =
162
- rootObject.put("value", view.value)
163
162
  }
@@ -0,0 +1,21 @@
1
+ package com.wix.detox.espresso.common
2
+
3
+ import android.view.View
4
+ import com.wix.detox.espresso.action.common.ReflectUtils
5
+ import org.joor.Reflect
6
+
7
+ private const val CLASS_MATERIAL_SLIDER = "com.google.android.material.slider.Slider"
8
+
9
+ open class MaterialSliderHelper(protected val view: View) {
10
+ fun getValueIfSlider(): Float? {
11
+ if (!isSlider()) {
12
+ return null
13
+ }
14
+
15
+ return getValue()
16
+ }
17
+
18
+ private fun isSlider() = ReflectUtils.isAssignableFrom(view, CLASS_MATERIAL_SLIDER)
19
+
20
+ private fun getValue() = Reflect.on(view).call("getValue").get() as Float
21
+ }
@@ -11,8 +11,9 @@ import org.joor.Reflect
11
11
 
12
12
  private const val CLASS_REACT_SLIDER_LEGACY = "com.facebook.react.views.slider.ReactSlider"
13
13
  private const val CLASS_REACT_SLIDER_COMMUNITY = "com.reactnativecommunity.slider.ReactSlider"
14
+ private const val CLASS_REACT_SLIDER_COMMUNITY_MANAGER = "com.reactnativecommunity.slider.ReactSliderManager"
14
15
 
15
- abstract class SliderHelper(protected val slider: AppCompatSeekBar) {
16
+ abstract class ReactSliderHelper(protected val slider: AppCompatSeekBar) {
16
17
  fun getCurrentProgressPct(): Double {
17
18
  val nativeProgress = slider.progress.toDouble()
18
19
  val nativeMax = slider.max
@@ -46,7 +47,7 @@ abstract class SliderHelper(protected val slider: AppCompatSeekBar) {
46
47
  ?: throw DetoxIllegalStateException("Cannot handle this type of a seek-bar view (Class ${view.javaClass.canonicalName}). " +
47
48
  "Only React Native sliders are currently supported.")
48
49
 
49
- fun maybeCreate(view: View): SliderHelper? =
50
+ fun maybeCreate(view: View): ReactSliderHelper? =
50
51
  when {
51
52
  ReflectUtils.isAssignableFrom(view, CLASS_REACT_SLIDER_LEGACY)
52
53
  -> LegacySliderHelper(view as ReactSlider)
@@ -58,7 +59,7 @@ abstract class SliderHelper(protected val slider: AppCompatSeekBar) {
58
59
  }
59
60
  }
60
61
 
61
- private class LegacySliderHelper(slider: ReactSlider): SliderHelper(slider) {
62
+ private class LegacySliderHelper(slider: ReactSlider): ReactSliderHelper(slider) {
62
63
  override fun setProgressJS(valueJS: Double) {
63
64
  val reactSliderManager = com.facebook.react.views.slider.ReactSliderManager()
64
65
  reactSliderManager.updateProperties(slider as ReactSlider, buildStyles("value", valueJS))
@@ -67,9 +68,9 @@ private class LegacySliderHelper(slider: ReactSlider): SliderHelper(slider) {
67
68
  private fun buildStyles(vararg keysAndValues: Any) = ReactStylesDiffMap(JavaOnlyMap.of(*keysAndValues))
68
69
  }
69
70
 
70
- private class CommunitySliderHelper(slider: AppCompatSeekBar): SliderHelper(slider) {
71
+ private class CommunitySliderHelper(slider: AppCompatSeekBar): ReactSliderHelper(slider) {
71
72
  override fun setProgressJS(valueJS: Double) {
72
- val reactSliderManager = Class.forName("com.reactnativecommunity.slider.ReactSliderManager").newInstance()
73
+ val reactSliderManager = Class.forName(CLASS_REACT_SLIDER_COMMUNITY_MANAGER).newInstance()
73
74
  Reflect.on(reactSliderManager).call("setValue", slider, valueJS)
74
75
  }
75
76
  }
@@ -7,7 +7,7 @@ import androidx.appcompat.widget.AppCompatSeekBar
7
7
  import androidx.test.espresso.matcher.BoundedMatcher
8
8
  import androidx.test.espresso.matcher.ViewMatchers
9
9
  import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
10
- import com.wix.detox.espresso.common.SliderHelper
10
+ import com.wix.detox.espresso.common.ReactSliderHelper
11
11
  import org.hamcrest.*
12
12
  import org.hamcrest.Matchers.*
13
13
  import kotlin.math.abs
@@ -60,7 +60,7 @@ fun toHaveSliderPosition(expectedValuePct: Double, tolerance: Double): Matcher<V
60
60
  }
61
61
 
62
62
  override fun matchesSafely(view: AppCompatSeekBar): Boolean {
63
- val sliderHelper = SliderHelper.create(view)
63
+ val sliderHelper = ReactSliderHelper.create(view)
64
64
  val progressPct = sliderHelper.getCurrentProgressPct()
65
65
  return (abs(progressPct - expectedValuePct) <= tolerance)
66
66
  }
@@ -0,0 +1,33 @@
1
+ package com.wix.detox.espresso.common
2
+
3
+ import android.view.View
4
+ import com.google.android.material.slider.Slider
5
+ import org.assertj.core.api.Assertions.assertThat
6
+ import org.junit.Test
7
+ import org.junit.runner.RunWith
8
+ import org.mockito.kotlin.doReturn
9
+ import org.mockito.kotlin.mock
10
+ import org.robolectric.RobolectricTestRunner
11
+
12
+ @RunWith(RobolectricTestRunner::class)
13
+ class MaterialSliderHelperTest {
14
+ @Test
15
+ fun `should return value if view is a slider`() {
16
+ val slider: Slider = mock {
17
+ on { value } doReturn 0.2f
18
+ }
19
+
20
+ val uut = MaterialSliderHelper(slider)
21
+
22
+ assertThat(uut.getValueIfSlider()).isEqualTo(0.2f)
23
+ }
24
+
25
+ @Test
26
+ fun `should return null if view is not a slider`() {
27
+ val view: View = mock()
28
+
29
+ val uut = MaterialSliderHelper(view)
30
+
31
+ assertThat(uut.getValueIfSlider()).isNull()
32
+ }
33
+ }
@@ -15,14 +15,14 @@ import org.robolectric.RobolectricTestRunner
15
15
  * to avoid having to install the community slider under node_modules just for this.
16
16
  */
17
17
  @RunWith(RobolectricTestRunner::class)
18
- class SliderHelperTest {
18
+ class ReactSliderHelperTest {
19
19
  lateinit var slider: ReactSlider
20
- lateinit var uut: SliderHelper
20
+ lateinit var uut: ReactSliderHelper
21
21
 
22
22
  @Before
23
23
  fun setup() {
24
24
  slider = mock()
25
- uut = SliderHelper.create(slider)
25
+ uut = ReactSliderHelper.create(slider)
26
26
  }
27
27
 
28
28
  private fun givenNativeProgressTraits(current: Int, max: Int) {
package/internals.d.ts CHANGED
@@ -113,7 +113,11 @@ declare global {
113
113
  /** Test suite name */
114
114
  name: string;
115
115
  }): Promise<void>;
116
-
116
+ /**
117
+ * Workaround for Jest exiting abruptly in --bail mode.
118
+ * Makes sure that all workers and their test environments are properly torn down.
119
+ */
120
+ unsafe_conductEarlyTeardown(): Promise<void>;
117
121
  /**
118
122
  * Reports to Detox CLI about passed and failed test files.
119
123
  * The failed test files might be re-run again if
@@ -215,6 +219,11 @@ declare global {
215
219
  * Randomly generated ID for the entire Detox test session, including retries.
216
220
  */
217
221
  id: string;
222
+ /**
223
+ * Signalizes that the test session is being torn down.
224
+ * Experimental feature for Jest --bail mode.
225
+ */
226
+ unsafe_earlyTeardown?: boolean;
218
227
  /**
219
228
  * Results of test file executions. Primarily used for Detox CLI retry mechanism.
220
229
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "detox",
3
3
  "description": "E2E tests and automation for mobile",
4
- "version": "20.12.1",
4
+ "version": "20.12.2",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -206,5 +206,5 @@
206
206
  "browserslist": [
207
207
  "node 14"
208
208
  ],
209
- "gitHead": "b489519a3166723194c83c5a43bd585bee1d9395"
209
+ "gitHead": "3ed86bf3628cda4673412a4e2c38e32077473851"
210
210
  }
@@ -1,24 +1,142 @@
1
1
  const resolveFrom = require('resolve-from');
2
- /** @type {typeof import('@jest/reporters').VerboseReporter} */
2
+ /** @type {new (globalConfig: any) => import('@jest/reporters').VerboseReporter} */
3
3
  const JestVerboseReporter = require(resolveFrom(process.cwd(), '@jest/reporters')).VerboseReporter;
4
+ /** @type {new (globalConfig: any) => import('@jest/reporters').SummaryReporter} */
5
+ const SummaryReporter = require(resolveFrom(process.cwd(), '@jest/reporters')).SummaryReporter;
4
6
 
5
- const { config, reportTestResults } = require('../../../internals');
7
+ const { config, reportTestResults, unsafe_conductEarlyTeardown, cleanup } = require('../../../internals');
8
+ const Deferred = require('../../../src/utils/Deferred');
6
9
 
7
10
  class DetoxReporter extends JestVerboseReporter {
11
+ constructor(globalConfig) {
12
+ super(globalConfig);
13
+
14
+ /** @type {Deferred | null} */
15
+ this._lastRunComplete = null;
16
+ /** @type {Set<string>} */
17
+ this._pendingTestFiles = new Set();
18
+ /** @type {Promise<any> | null} */
19
+ this._runCompletePromise = null;
20
+ /** @type {import('@jest/reporters').SummaryReporter | null} */
21
+ this._summaryReporter = this._initSummaryReporter();
22
+ }
23
+
24
+ onRunStart(aggregatedResults, options) {
25
+ super.onRunStart(aggregatedResults, options);
26
+
27
+ if (this._summaryReporter) {
28
+ this._summaryReporter.onRunStart(aggregatedResults, options);
29
+ }
30
+ }
31
+
32
+ onTestFileStart(test) {
33
+ this._pendingTestFiles.add(test.path);
34
+
35
+ // @ts-ignore Precaution in case Jest migrates to the new signature
36
+ if (typeof super.onTestFileStart === 'function') {
37
+ // @ts-ignore
38
+ super.onTestFileStart(test);
39
+ } else {
40
+ super.onTestStart(test);
41
+ }
42
+ }
43
+
44
+ onTestFileResult(test, testResult, aggregatedResult) {
45
+ this._pendingTestFiles.delete(test.path);
46
+
47
+ // @ts-ignore Precaution in case Jest migrates to the new signature
48
+ if (typeof super.onTestFileResult === 'function') {
49
+ // @ts-ignore
50
+ super.onTestFileResult(test, testResult, aggregatedResult);
51
+ } else {
52
+ super.onTestResult(test, testResult, aggregatedResult);
53
+ }
54
+
55
+ if (this._lastRunComplete && this._pendingTestFiles.size === 0) {
56
+ this._lastRunComplete.resolve(aggregatedResult);
57
+ }
58
+ }
59
+
8
60
  /**
9
- * @param {import('@jest/test-result').AggregatedResult} results
61
+ * @param {Set<import('@jest/reporters').TestContext>} testContexts
62
+ * @param {import('@jest/reporters').AggregatedResult} aggregatedResult
63
+ * @returns {Promise<any> | null}
10
64
  */
11
- // @ts-ignore
12
- async onRunComplete(_contexts, results) {
13
- // @ts-ignore
14
- await super.onRunComplete(_contexts, results);
65
+ // @ts-ignore We need to use the complete signature, not the one from the base class
66
+ onRunComplete(testContexts, aggregatedResult) {
67
+ if (!this._runCompletePromise) {
68
+ // Both `_lastRunComplete` and `_runCompletePromise` are used to prevent
69
+ // a bug in Jest, where `onRunComplete` is called multiple times when
70
+ // Jest runs with `--bail` and multiple workers, and `onRunComplete`
71
+ // is implemented as an asynchronous long-running operation.
72
+ this._lastRunComplete = this._pendingTestFiles.size === 0
73
+ ? Deferred.resolved(aggregatedResult)
74
+ : new Deferred();
15
75
 
16
- await reportTestResults(results.testResults.map(r => ({
76
+ this._runCompletePromise = this._onRunComplete(testContexts, aggregatedResult).catch(err => {
77
+ console.error(err);
78
+ throw err;
79
+ });
80
+ }
81
+
82
+ return this._runCompletePromise;
83
+ }
84
+
85
+ /**
86
+ * @param {Set<import('@jest/reporters').TestContext>} testContexts
87
+ * @param {import('@jest/reporters').AggregatedResult} aggregatedResult
88
+ * @returns {Promise<any> | null}
89
+ */
90
+ async _onRunComplete(testContexts, { numFailedTests }) {
91
+ const bail = this._globalConfig.bail;
92
+ const earlyTeardown = bail > 0 && numFailedTests >= bail;
93
+ if (earlyTeardown) {
94
+ await unsafe_conductEarlyTeardown();
95
+ }
96
+
97
+ const aggregatedResult = await this._lastRunComplete.promise;
98
+ const lostTests = aggregatedResult.numTotalTestSuites - aggregatedResult.testResults.length;
99
+
100
+ // @ts-expect-error TS2554
101
+ super.onRunComplete(testContexts, aggregatedResult);
102
+
103
+ if (earlyTeardown && lostTests > 0 && config.testRunner.retries > 0) {
104
+ console.warn(
105
+ 'Jest aborted the test execution before all scheduled test files have been reported.\n' +
106
+ 'This brings us to a dilemma: retry the whole test run, or retry only known failed test files.\n' +
107
+ 'Both options are bad in their own way, this is why we have decided to not support this edge case.\n' +
108
+ 'If you want to retry the whole test run, please disable Jest\'s --bail option.'
109
+ );
110
+ }
111
+
112
+ await reportTestResults(aggregatedResult.testResults.map(r => ({
17
113
  success: !r.failureMessage,
18
114
  testFilePath: r.testFilePath,
19
115
  testExecError: r.testExecError,
20
- isPermanentFailure: this._isPermanentFailure(r),
116
+ isPermanentFailure: lostTests > 0 || this._isPermanentFailure(r),
21
117
  })));
118
+
119
+ if (this._summaryReporter) {
120
+ this._summaryReporter.onRunComplete(testContexts, aggregatedResult);
121
+ }
122
+
123
+ if (earlyTeardown) {
124
+ await cleanup();
125
+ }
126
+ }
127
+
128
+ /**
129
+ * @returns {import('@jest/reporters').SummaryReporter | null}
130
+ * @private
131
+ */
132
+ _initSummaryReporter() {
133
+ /** @type {(config: import('@jest/types').Config.ReporterConfig) => boolean} */
134
+ const isSummaryReporter = (config) => config[0] === 'summary';
135
+ if (this._globalConfig.reporters.some(isSummaryReporter)) {
136
+ return null;
137
+ }
138
+
139
+ return new SummaryReporter(this._globalConfig);
22
140
  }
23
141
 
24
142
  /**
@@ -70,7 +70,12 @@ class DetoxCircusEnvironment extends NodeEnvironment {
70
70
  await this.initDetox();
71
71
  }
72
72
 
73
+ // @ts-ignore
73
74
  async handleTestEvent(event, state) {
75
+ if (detox.session.unsafe_earlyTeardown) {
76
+ throw new Error('Detox halted test execution due to an early teardown request');
77
+ }
78
+
74
79
  this._timer.schedule(state.testTimeout != null ? state.testTimeout : this.setupTimeout);
75
80
 
76
81
  if (SYNC_CIRCUS_EVENTS.has(event.name)) {
@@ -70,6 +70,11 @@ class IPCClient {
70
70
  this._sessionState.patch(sessionState);
71
71
  }
72
72
 
73
+ async conductEarlyTeardown() {
74
+ const sessionState = await this._emit('conductEarlyTeardown', {});
75
+ this._sessionState.patch(sessionState);
76
+ }
77
+
73
78
  async _connectToServer() {
74
79
  const serverId = this.serverId;
75
80
 
@@ -38,6 +38,7 @@ class IPCServer {
38
38
  await new Promise((resolve) => {
39
39
  // TODO: handle reject
40
40
  this._ipc.serve(() => resolve());
41
+ this._ipc.server.on('conductEarlyTeardown', this.onConductEarlyTeardown.bind(this));
41
42
  this._ipc.server.on('registerContext', this.onRegisterContext.bind(this));
42
43
  this._ipc.server.on('registerWorker', this.onRegisterWorker.bind(this));
43
44
  this._ipc.server.on('reportTestResults', this.onReportTestResults.bind(this));
@@ -83,6 +84,18 @@ class IPCServer {
83
84
  }
84
85
  }
85
86
 
87
+ onConductEarlyTeardown(_data = null, socket = null) {
88
+ // Note that we don't save `unsafe_earlyTeardown` in the primary session state
89
+ // because it's transient and needed only to make the workers quit early.
90
+ const newState = { unsafe_earlyTeardown: true };
91
+
92
+ if (socket) {
93
+ this._ipc.server.emit(socket, 'conductEarlyTeardownDone', newState);
94
+ }
95
+
96
+ this._ipc.server.broadcast('sessionStateUpdate', newState);
97
+ }
98
+
86
99
  onReportTestResults({ testResults }, socket = null) {
87
100
  const merged = uniqBy([
88
101
  ...testResults.map(r => serializeObjectWithError(r, 'testExecError')),
@@ -20,6 +20,7 @@ class SessionState {
20
20
  this.detoxIPCServer = detoxIPCServer;
21
21
  this.testResults = testResults;
22
22
  this.testSessionIndex = testSessionIndex;
23
+ this.unsafe_earlyTeardown = undefined;
23
24
  this.workersCount = workersCount;
24
25
  }
25
26
 
@@ -101,6 +101,8 @@ class DetoxContext {
101
101
  });
102
102
  /** @abstract */
103
103
  [symbols.reportTestResults](_testResults) {}
104
+ /** @abstract */
105
+ [symbols.conductEarlyTeardown]() {}
104
106
  /**
105
107
  * @abstract
106
108
  * @param {Partial<DetoxInternals.DetoxInitOptions>} _opts
@@ -24,6 +24,7 @@ class DetoxInternalsFacade {
24
24
  this.resolveConfig = context[symbols.resolveConfig];
25
25
  this.session = context[symbols.session];
26
26
  this.tracing = context[symbols.tracing];
27
+ this.unsafe_conductEarlyTeardown = context[symbols.conductEarlyTeardown];
27
28
  this.worker = funpermaproxy(() => context[symbols.worker]);
28
29
  }
29
30
  }
@@ -51,6 +51,12 @@ class DetoxPrimaryContext extends DetoxContext {
51
51
  }
52
52
  }
53
53
 
54
+ [symbols.conductEarlyTeardown] = async () => {
55
+ if (this[_ipcServer]) {
56
+ await this[_ipcServer].onConductEarlyTeardown();
57
+ }
58
+ };
59
+
54
60
  async [symbols.resolveConfig](opts = {}) {
55
61
  const session = this[$sessionState];
56
62
  if (!session.detoxConfig) {
@@ -33,6 +33,14 @@ class DetoxSecondaryContext extends DetoxContext {
33
33
  }
34
34
  }
35
35
 
36
+ [symbols.conductEarlyTeardown] = async () => {
37
+ if (this[_ipcClient]) {
38
+ await this[_ipcClient].conductEarlyTeardown();
39
+ } else {
40
+ throw new DetoxInternalError('Detected an attempt to report early teardown using a non-initialized context.');
41
+ }
42
+ };
43
+
36
44
  async [symbols.resolveConfig]() {
37
45
  return this[symbols.config];
38
46
  }
@@ -12,6 +12,7 @@
12
12
  * readonly onTestDone: unique symbol;
13
13
  * readonly onTestFnFailure: unique symbol;
14
14
  * readonly onTestStart: unique symbol;
15
+ * readonly conductEarlyTeardown: unique symbol;
15
16
  * readonly reportTestResults: unique symbol;
16
17
  * readonly resolveConfig: unique symbol;
17
18
  * readonly session: unique symbol;
@@ -32,6 +33,7 @@ module.exports = {
32
33
 
33
34
  //#region IPC
34
35
  reportTestResults: Symbol('reportTestResults'),
36
+ conductEarlyTeardown: Symbol('conductEarlyTeardown'),
35
37
  //#endregion
36
38
 
37
39
  //#region Main
@@ -41,7 +41,7 @@ function asError(error) {
41
41
  }
42
42
 
43
43
  function serializeObjectWithError(obj, errorKey) {
44
- if (obj[errorKey]) {
44
+ if (obj[errorKey] instanceof Error) {
45
45
  return { ...obj, [errorKey]: serializeError(obj[errorKey]) };
46
46
  }
47
47
 
@@ -1 +0,0 @@
1
- 0780bd77755cae9d89bc326c195d8eb1
@@ -1 +0,0 @@
1
- 9168cee63c5b35da49de50f34c9c41ad5fc0c449
@@ -1 +0,0 @@
1
- d3b9a104400c779bb7a1e70546c33abd374cf8eeb55ac5b4098a6c54737d0411
@@ -1 +0,0 @@
1
- ba86f8d9c3163eefc5d13776f4288708d96e00c83bea5527bbbd1a1c2027c79e6d44d19a8c85959bc48f3b7311c2969c96ea4d26569adb18f8f90fb56f7b558e
@@ -1 +0,0 @@
1
- 70745a3a7e09a2682c6a8902a413bff1
@@ -1 +0,0 @@
1
- 1da2776757d2a28dc4c3e73a7b469b4d92dee9c8
@@ -1 +0,0 @@
1
- 931d28ebdf7b9bceb5ad5ec36cd7b3696f91e6955579f505c311c1b5334fb57a
@@ -1 +0,0 @@
1
- 4750dd9cab79b2cab696c552101c755c609e6a4bfa5ab75d742004cbf4fe5bfdbc87b36096e1145559c5a5f65b6216181b6690efaa73016df660a4865d774782
@@ -1 +0,0 @@
1
- c90cc85230997f3663da1741d1eabc0e
@@ -1 +0,0 @@
1
- 0e17ccf18dbb2e0295e72b77cae67275591d956a
@@ -1 +0,0 @@
1
- 00528b5d136df63bc93f55c2f890a1ad69179f341663310b520227f6b7aad63b
@@ -1 +0,0 @@
1
- 37984e2491685ede0325d74f460ad2a13dc55bd906124e0932fa09e29e2394a91cbbf691aa854848503113f04dfe148a483e616c736918f6e0077af6aa311a31
@@ -1 +0,0 @@
1
- 1ebd5db42295ac98e2151fc5c24b8903
@@ -1 +0,0 @@
1
- e5614e7546e21d6757c4b338efc93e6b6a0a4bf6
@@ -1 +0,0 @@
1
- b38cc093a239ee307a854533ad801c3ee7548c01519f7964da7d0285486a09a8
@@ -1 +0,0 @@
1
- 5f94777c0cbddf9a24e2f661fc598fec79e71d555107020bcbb366a728307e990ae7205b210346562faa08fc00774ff3a9336e8fd185c80bd1fa9ebb4d2fa8c5