detox 20.0.5-breaking.new-global-lifecycle.0 → 20.0.8-prerelease.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. package/Detox-android/com/wix/detox/{20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-javadoc.jar → 20.0.8-prerelease.0/detox-20.0.8-prerelease.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-sources.jar → 20.0.8-prerelease.0/detox-20.0.8-prerelease.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.pom → 20.0.8-prerelease.0/detox-20.0.8-prerelease.0.pom} +1 -7
  17. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.0.8-prerelease.0/detox-20.0.8-prerelease.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/android/build.gradle +12 -6
  29. package/android/detox/build.gradle +13 -9
  30. package/android/detox/proguard-rules-app.pro +4 -0
  31. package/android/detox/publishing.gradle +27 -27
  32. package/android/detox/src/full/java/com/wix/detox/DetoxCrashHandler.kt +1 -1
  33. package/android/detox/src/full/java/com/wix/detox/LaunchArgs.java +9 -0
  34. package/android/detox/src/full/java/com/wix/detox/TestEngineFacade.kt +1 -1
  35. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeExtension.kt +15 -2
  36. package/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt +43 -38
  37. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.kt +7 -27
  38. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.kt +1 -11
  39. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/DetoxMultiTapSpec.kt +4 -3
  40. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategySpec.kt +3 -11
  41. package/index.d.ts +21 -1
  42. package/local-cli/test.js +9 -4
  43. package/local-cli/testCommand/TestRunnerCommand.js +8 -7
  44. package/package.json +3 -3
  45. package/runners/jest/testEnvironment/index.js +4 -3
  46. package/src/DetoxWorker.js +0 -6
  47. package/src/android/core/NativeElement.js +56 -20
  48. package/src/android/core/NativeExpect.js +28 -9
  49. package/src/android/interactions/native.js +24 -18
  50. package/src/artifacts/timeline/TimelineContextTypes.js +7 -0
  51. package/src/client/Client.js +18 -1
  52. package/src/configuration/composeRunnerConfig.js +1 -0
  53. package/src/devices/common/drivers/android/tools/MonitoredInstrumentation.js +1 -1
  54. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +3 -1
  55. package/src/devices/runtime/RuntimeDevice.js +2 -4
  56. package/src/devices/runtime/drivers/android/AndroidDriver.js +9 -1
  57. package/src/ios/expectTwo.js +152 -67
  58. package/src/logger/DetoxLogger.js +21 -17
  59. package/src/logger/utils/tracerLegacy.js +24 -1
  60. package/src/realms/DetoxPrimaryContext.js +26 -11
  61. package/src/utils/errorUtils.js +4 -3
  62. package/src/utils/invocationTraceDescriptions.js +43 -0
  63. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-javadoc.jar.md5 +0 -1
  64. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-javadoc.jar.sha1 +0 -1
  65. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-javadoc.jar.sha256 +0 -1
  66. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-javadoc.jar.sha512 +0 -1
  67. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-sources.jar.md5 +0 -1
  68. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-sources.jar.sha1 +0 -1
  69. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-sources.jar.sha256 +0 -1
  70. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0-sources.jar.sha512 +0 -1
  71. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.aar +0 -0
  72. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.aar.md5 +0 -1
  73. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.aar.sha1 +0 -1
  74. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.aar.sha256 +0 -1
  75. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.aar.sha512 +0 -1
  76. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.pom.md5 +0 -1
  77. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.pom.sha1 +0 -1
  78. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.pom.sha256 +0 -1
  79. package/Detox-android/com/wix/detox/20.0.5-breaking.new-global-lifecycle.0/detox-20.0.5-breaking.new-global-lifecycle.0.pom.sha512 +0 -1
  80. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategy.kt +0 -84
  81. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategySpec.kt +0 -115
@@ -34,6 +34,22 @@ class DetoxLogger {
34
34
  */
35
35
  this._sharedConfig = sharedConfig;
36
36
 
37
+ /** @type {object | undefined} */
38
+ this._context = context;
39
+
40
+ /** @public */
41
+ this.fatal = this._setupLogMethod('fatal');
42
+ /** @public */
43
+ this.error = this._setupLogMethod('error');
44
+ /** @public */
45
+ this.warn = this._setupLogMethod('warn');
46
+ /** @public */
47
+ this.info = this._setupLogMethod('info');
48
+ /** @public */
49
+ this.debug = this._setupLogMethod('debug');
50
+ /** @public */
51
+ this.trace = this._setupLogMethod('trace');
52
+
37
53
  if (!context) {
38
54
  // In this branch, `this` refers to the first (root) logger instance.
39
55
 
@@ -76,22 +92,6 @@ class DetoxLogger {
76
92
 
77
93
  this.overrideConsole();
78
94
  }
79
-
80
- /** @type {object | undefined} */
81
- this._context = context;
82
-
83
- /** @public */
84
- this.fatal = this._setupLogMethod('fatal');
85
- /** @public */
86
- this.error = this._setupLogMethod('error');
87
- /** @public */
88
- this.warn = this._setupLogMethod('warn');
89
- /** @public */
90
- this.info = this._setupLogMethod('info');
91
- /** @public */
92
- this.debug = this._setupLogMethod('debug');
93
- /** @public */
94
- this.trace = this._setupLogMethod('trace');
95
95
  }
96
96
 
97
97
  /**
@@ -232,7 +232,11 @@ class DetoxLogger {
232
232
  const action = typeof maybeContext !== 'string' ? maybeAction : maybeMessage;
233
233
  const args = maybeAction === action ? [maybeContext, maybeMessage] : [maybeContext];
234
234
  const { context, msg } = this._parseArgs(null, args);
235
- const end = (ctx) => this[level].end(ctx);
235
+ const end = (ctx) => this[level].end({
236
+ id: context.id,
237
+ cat: context.cat,
238
+ ...ctx,
239
+ });
236
240
 
237
241
  let result;
238
242
  this[level].begin(context, ...msg);
@@ -8,15 +8,38 @@ const methods = {
8
8
  },
9
9
 
10
10
  traceCall(logger) {
11
- return (name, action) => logger.trace.complete(name, action);
11
+ return (name, action, args = {}) => logger.trace.complete(args, name, action);
12
+ },
13
+
14
+ invocationCall(logger) {
15
+ return (sectionName, invocation, action) => {
16
+ return logger.trace.complete({
17
+ cat: 'ws-client,ws-client-invocation',
18
+ data: invocation,
19
+ stack: _getCallStackTrace(),
20
+ }, sectionName, action);
21
+ };
12
22
  },
13
23
  };
14
24
 
25
+ function _getCallStackTrace() {
26
+ return new Error().stack
27
+ .split('\n')
28
+ .slice(1) // Ignore Error message
29
+ .map(line => line
30
+ .replace(/^\s*at\s+/, '')
31
+ .replace(process.cwd(), '')
32
+ )
33
+ .filter(line => !line.includes('/detox/src')) // Ignore detox internal calls
34
+ .join('\n');
35
+ }
36
+
15
37
  function installLegacyTracerInterface(logger, target) {
16
38
  target.traceCall = methods.traceCall(logger);
17
39
  target.trace = Object.freeze({
18
40
  startSection: methods.startSection(logger),
19
41
  endSection: methods.endSection(logger),
42
+ invocationCall: methods.invocationCall(logger),
20
43
  });
21
44
 
22
45
  return this;
@@ -53,7 +53,7 @@ class DetoxPrimaryContext extends DetoxContext {
53
53
  }
54
54
 
55
55
  async [symbols.resolveConfig](opts = {}) {
56
- const session = this[symbols.session];
56
+ const session = this[$sessionState];
57
57
  if (!session.detoxConfig) {
58
58
  const configuration = require('../configuration');
59
59
  session.detoxConfig = await configuration.composeDetoxConfig(opts);
@@ -170,11 +170,13 @@ class DetoxPrimaryContext extends DetoxContext {
170
170
 
171
171
  await fs.remove(this[$sessionState].detoxConfigSnapshotPath);
172
172
 
173
- try {
174
- this[_lifecycleLogger].trace.end();
175
- await this[_finalizeLogs]();
176
- } catch (err) {
177
- this[_lifecycleLogger].error({ err }, 'Encountered an error while merging the process logs:');
173
+ if (this[_dirty]) {
174
+ try {
175
+ this[_lifecycleLogger].trace.end();
176
+ await this[_finalizeLogs]();
177
+ } catch (err) {
178
+ this[_lifecycleLogger].error({ err }, 'Encountered an error while merging the process logs:');
179
+ }
178
180
  }
179
181
  }
180
182
  }
@@ -229,7 +231,7 @@ class DetoxPrimaryContext extends DetoxContext {
229
231
  return;
230
232
  }
231
233
 
232
- if (this[_areLogsEnabled]) {
234
+ if (this[_areLogsEnabled]()) {
233
235
  const streamUtils = require('../utils/streamUtils');
234
236
  const { rootDir } = this[symbols.config].artifacts;
235
237
 
@@ -256,7 +258,10 @@ class DetoxPrimaryContext extends DetoxContext {
256
258
  const logsEnabled = this[_areLogsEnabled]();
257
259
 
258
260
  const { rootDir } = this[symbols.config].artifacts;
259
- fs.mkdirpSync(rootDir);
261
+
262
+ if (logsEnabled) {
263
+ fs.mkdirpSync(rootDir);
264
+ }
260
265
 
261
266
  const sessionId = this[$sessionState].id;
262
267
  const logs = globSync(temporary.for.jsonl(`${sessionId}.*`));
@@ -272,10 +277,20 @@ class DetoxPrimaryContext extends DetoxContext {
272
277
 
273
278
  [_areLogsEnabled]() {
274
279
  const { rootDir, plugins } = this[symbols.config].artifacts || {};
275
- const logConfig = plugins && plugins.log || 'none';
276
- const enabled = rootDir && (typeof logConfig === 'string' ? logConfig !== 'none' : logConfig.enabled);
280
+ if (!rootDir || !plugins) {
281
+ return false;
282
+ }
283
+
284
+ if (!plugins.log.enabled) {
285
+ return false;
286
+ }
287
+
288
+ if (!plugins.log.keepOnlyFailedTestsArtifacts) {
289
+ return true;
290
+ }
277
291
 
278
- return enabled;
292
+ const { failedTestFiles, testFilesToRetry } = this[$sessionState];
293
+ return failedTestFiles.length + testFilesToRetry.length > 0;
279
294
  }
280
295
 
281
296
  async[_resetLockFile]() {
@@ -16,11 +16,12 @@ function filterErrorStack(error, predicate) {
16
16
  }
17
17
 
18
18
  function replaceErrorStack(source, target) {
19
- const sourceStack = source.stack || source.message;
20
- const targetStack = target.stack || target.message;
19
+ const sourceStack = (source.stack || '');
21
20
  const sourceMessage = sourceStack.replace(CLEAN_AT, '');
22
- const targetMessage = targetStack.replace(CLEAN_AT, '');
23
21
  const actualSourceStack = sourceStack.slice(sourceMessage.length);
22
+
23
+ const targetMessage = target.message || target.stack.replace(CLEAN_AT, '');
24
+
24
25
  target.stack = targetMessage + actualSourceStack;
25
26
  return target;
26
27
  }
@@ -0,0 +1,43 @@
1
+ module.exports = {
2
+ actionDescription: {
3
+ adjustSliderToPosition: (newPosition) => `adjust slider to position ${newPosition}`,
4
+ clearText: () => 'clear input text',
5
+ getAttributes: () => 'get element attributes',
6
+ longPress: (duration) => `long press${duration !== undefined ? ` for ${duration}ms` : ''}`,
7
+ longPressAndDrag: (duration, startX, startY, targetElement, endX, endY, speed, holdDuration) =>
8
+ `long press and drag from ${startX}, ${startY} to ${endX}, ${endY} with speed ${speed} and hold duration ${holdDuration}`,
9
+ multiTap: (times) => `tap ${times} times`,
10
+ pinch: (scale, speed, angle) => `pinch with scale ${scale}, speed ${speed}, and angle ${angle}`,
11
+ pinchWithAngle: (direction, speed, angle) => `pinch with direction ${direction}, speed ${speed}, and angle ${angle}`,
12
+ replaceText: (value) => `replace input text: "${value}"`,
13
+ scroll: (amount, direction, startPositionX, startPositionY) =>
14
+ `scroll ${amount} pixels ${direction}${startPositionX !== undefined && startPositionY !== undefined ? ` from normalized position (${startPositionX}, ${startPositionY})` : ''}`,
15
+ scrollTo: (edge) => `scroll to ${edge}`,
16
+ scrollToIndex: (index) => `scroll to index #${index}`,
17
+ setColumnToValue: (column, value) => `set column ${column} to value ${value}`,
18
+ setDatePickerDate: (dateString, dateFormat) => `set date picker date to ${dateString} using format ${dateFormat}`,
19
+ swipe: (direction, speed, normalizedSwipeOffset, normalizedStartingPointX, normalizedStartingPointY) =>
20
+ `swipe ${direction} ${speed} with offset ${normalizedSwipeOffset}
21
+ ${!isNaN(normalizedStartingPointX) && !isNaN(normalizedStartingPointY) ? ` from normalized position (${normalizedStartingPointX}, ${normalizedStartingPointY})` : ''}`,
22
+ takeScreenshot: (screenshotName) => `take screenshot${screenshotName !== undefined ? ` with name "${screenshotName}"` : ''}`,
23
+ tapAtPoint: (value) => `tap${value !== undefined ? ` at ${JSON.stringify(value)}` : ''}`,
24
+ tapBackspaceKey: () => 'tap on backspace key',
25
+ tapReturnKey: () => 'tap on return key',
26
+ typeText: (value) => `type input text: "${value}"`,
27
+ },
28
+ expectDescription: {
29
+ waitFor: (actionDescription) => `wait for expectation while ${actionDescription}`,
30
+ waitForWithTimeout: (expectDescription, timeout) => `${expectDescription} with timeout (${timeout} ms)`,
31
+ withTimeout: (timeout) => `wait until timeout (${timeout} ms)`,
32
+ toBeFocused: () => 'to be focused',
33
+ toBeVisible: (percent) => `to be visible${percent !== undefined ? ` ${percent}%` : ''}`,
34
+ toExist: () => 'to exist',
35
+ toHaveText: (text) => `to have text: "${text}"`,
36
+ toHaveLabel: (label) => `to have label: "${label}"`,
37
+ toHaveId: (id) => `to have id: "${id}"`,
38
+ toHaveValue: (value) => `to have value: "${value}"`,
39
+ toHaveSliderPosition: (position, tolerance) => `to have slider position: ${position}${tolerance > 0 ? ` with tolerance ${tolerance}` : ''}`,
40
+ toHaveToggleValue: (value) => `to have toggle value: ${value}`,
41
+ full: (expectDescription, notCondition) => `expect element ${notCondition ? `not ${expectDescription}` : expectDescription}`
42
+ }
43
+ };
@@ -1 +0,0 @@
1
- 274e47fbbdbe85abdf2ebfd4c76e00ef33ad353309da375460f71f9f6fe04c2a
@@ -1 +0,0 @@
1
- 063f4a5549133b4d661a7677d68a91473877b26ed3e29ef9d7502a83c179810d68f15dd9e35399b5bab23f3c97f96ee875a80abcb07d1dfa57a6177399dcc347
@@ -1 +0,0 @@
1
- 4a344c3b3b5597488fece53c9cc7358705e6d40116b0779122428094bd021db7
@@ -1 +0,0 @@
1
- 93443f0d56c9f84ec24523b954e6e9782652c4bd5d8c94bf558812fade6750beb49f4678dcbaef53e1948edcf5fb5e35c47efb49c7acc79cd2ffa0fea2b287bd
@@ -1 +0,0 @@
1
- 7828ffdff493f8a856efad9b7db540e4e6b13b91260292efe98791013ba2db04
@@ -1 +0,0 @@
1
- a57448b5eec5edfb2b45b99b9c8a6714b3384f71ade0f25da7b453d14c29597e818690b293cefa9be9362be29efb2c420664283cb1fc994883cd143d46506a2f
@@ -1 +0,0 @@
1
- b9ea5a9228c8248ce58fcfc5eb12983b642c2240ae61fd0929cfdafb664cc9a6
@@ -1 +0,0 @@
1
- 7d5986795398d9c6021a9d4085a4e1b2c3e794e51fb873ddfefc596d2f8b3a04a58c553fdb4202f6bd5fa4bbbf36373b318a7497a66e345b6f8f23bfee8b4145
@@ -1,84 +0,0 @@
1
- @file:RNDropSupportTodo(62, "Remove all of this; Use DelegatedIdleInterrogationStrategy, instead.")
2
-
3
- package com.wix.detox.reactnative.idlingresources.timers
4
-
5
- import com.facebook.react.bridge.ReactContext
6
- import com.wix.detox.common.RNDropSupportTodo
7
- import com.wix.detox.reactnative.helpers.RNHelpers
8
- import org.joor.Reflect
9
- import java.util.*
10
-
11
- private const val BUSY_WINDOW_THRESHOLD = 1500
12
-
13
- private class TimerReflected(timer: Any) {
14
- private var reflected = Reflect.on(timer)
15
-
16
- val isRepeating: Boolean
17
- get() = reflected.field("mRepeat").get()
18
- val interval: Int
19
- get() = reflected.field("mInterval").get()
20
- val targetTime: Long
21
- get() = reflected.field("mTargetTime").get()
22
- }
23
-
24
- private class TimingModuleReflected(private val timingModule: Any) {
25
- val timersQueue: PriorityQueue<Any>
26
- get() = Reflect.on(timingModule).field("mTimers").get()
27
- val timersLock: Any
28
- get() = Reflect.on(timingModule).field("mTimerGuard").get()
29
-
30
- operator fun component1() = timersQueue
31
- operator fun component2() = timersLock
32
- }
33
-
34
- class DefaultIdleInterrogationStrategy
35
- internal constructor(private val timersModule: Any)
36
- : IdleInterrogationStrategy {
37
-
38
- override fun isIdleNow(): Boolean {
39
- val (timersQueue, timersLock) = TimingModuleReflected(timersModule)
40
- synchronized(timersLock) {
41
- val nextTimer = timersQueue.peek()
42
- nextTimer?.let {
43
- return !isTimerInBusyWindow(it) && !hasBusyTimers(timersQueue)
44
- }
45
- return true
46
- }
47
- }
48
-
49
- private fun isTimerInBusyWindow(timer: Any): Boolean {
50
- val timerReflected = TimerReflected(timer)
51
- return when {
52
- timerReflected.isRepeating -> false
53
- timerReflected.interval > BUSY_WINDOW_THRESHOLD -> false
54
- else -> true
55
- }
56
- }
57
-
58
- private fun hasBusyTimers(timersQueue: PriorityQueue<Any>): Boolean {
59
- timersQueue.forEach {
60
- if (isTimerInBusyWindow(it)) {
61
- return true
62
- }
63
- }
64
- return false
65
- }
66
-
67
- companion object {
68
- fun createIfSupported(reactContext: ReactContext): DefaultIdleInterrogationStrategy? {
69
- // RN = 0.62.0:
70
- // Should have been handled by DelegatedIdleInterrogationStrategy.createIfSupported() but seems the new TimingModule class
71
- // was released without the awaited-for "hasActiveTimersInRange()" method.
72
- try {
73
- val timingModule = RNHelpers.getNativeModule(reactContext, "com.facebook.react.modules.core.TimingModule")
74
- val timersManager = Reflect.on(timingModule).get<Any>("mJavaTimerManager")
75
- return DefaultIdleInterrogationStrategy(timersManager)
76
- } catch (ex: Exception) {
77
- }
78
-
79
- // RN < 0.62
80
- val timingModule = RNHelpers.getNativeModule(reactContext, "com.facebook.react.modules.core.Timing") ?: return null
81
- return DefaultIdleInterrogationStrategy(timingModule)
82
- }
83
- }
84
- }
@@ -1,115 +0,0 @@
1
- package com.wix.detox.reactnative.idlingresources.timers
2
-
3
- import com.facebook.react.bridge.NativeModule
4
- import com.wix.detox.UTHelpers
5
- import org.assertj.core.api.Assertions
6
- import org.spekframework.spek2.Spek
7
- import org.spekframework.spek2.style.specification.describe
8
- import java.util.*
9
- import java.util.concurrent.Executors
10
-
11
- private const val BUSY_INTERVAL_MS = 1500
12
- private const val MEANINGFUL_TIMER_INTERVAL = BUSY_INTERVAL_MS
13
-
14
- data class TimerStub(
15
- private var mCallbackID: Int,
16
- private var mTargetTime: Long,
17
- private var mInterval: Int,
18
- private var mRepeat: Boolean)
19
-
20
- class TimersNativeModuleStub : NativeModule {
21
- val mTimers: PriorityQueue<Any> = PriorityQueue(2) { _, _ -> 0}
22
- val mTimerGuard = "Lock-Mock"
23
-
24
- override fun onCatalystInstanceDestroy() {}
25
- override fun invalidate() {}
26
-
27
- override fun getName(): String = "TimersNativeModuleStub"
28
- override fun canOverrideExistingModule() = false
29
- override fun initialize() {}
30
- }
31
-
32
- private fun now() = System.nanoTime() / 1000000L
33
- private fun aTimer(targetTime: Long, interval: Int, isRepeating: Boolean): TimerStub {
34
- return TimerStub(-1, targetTime, interval, isRepeating)
35
- }
36
- private fun aTimer(interval: Int, isRepeating: Boolean) = aTimer(now() + interval + 10, interval, isRepeating)
37
- private fun aOneShotTimer(interval: Int) = aTimer(interval, false)
38
- private fun aRepeatingTimer(interval: Int) = aTimer(interval, true)
39
- private fun anOverdueTimer() = aTimer(now() - 100, 123, false)
40
-
41
- object DefaultIdleInterrogationStrategySpec: Spek({
42
- describe("Default timers idle-interrogation strategy") {
43
-
44
- lateinit var timersNativeModule: TimersNativeModuleStub
45
-
46
- beforeEachTest {
47
- timersNativeModule = TimersNativeModuleStub()
48
- }
49
-
50
- fun givenTimer(timer: Any) {
51
- timersNativeModule.mTimers.add(timer)
52
- }
53
-
54
- fun uut() = DefaultIdleInterrogationStrategy(timersNativeModule)
55
-
56
- it("should be idle if there are no timers in queue") {
57
- Assertions.assertThat(uut().isIdleNow()).isTrue()
58
- }
59
-
60
- it("should be busy if there's a meaningful pending timer") {
61
- givenTimer(aOneShotTimer(MEANINGFUL_TIMER_INTERVAL))
62
- Assertions.assertThat(uut().isIdleNow()).isFalse()
63
- }
64
-
65
- it("should be idle if pending timer is too far away (ie not meaningful)") {
66
- givenTimer(aOneShotTimer(BUSY_INTERVAL_MS + 1))
67
- Assertions.assertThat(uut().isIdleNow()).isTrue()
68
- }
69
-
70
- it("should be idle if the only timer is a repeating one") {
71
- givenTimer(aRepeatingTimer(MEANINGFUL_TIMER_INTERVAL))
72
- Assertions.assertThat(uut().isIdleNow()).isTrue()
73
- }
74
-
75
- it("should be busy if a meaningful pending timer lies beyond a repeating one") {
76
- givenTimer(aRepeatingTimer(BUSY_INTERVAL_MS / 10))
77
- givenTimer(aOneShotTimer(BUSY_INTERVAL_MS))
78
- Assertions.assertThat(uut().isIdleNow()).isFalse()
79
- }
80
-
81
- /**
82
- * Note: Reversed logic due to this issue: https://github.com/wix/Detox/issues/1171 !!!
83
- *
84
- * Apparently at times (rare) this caused Espresso to think we're idle too soon, rendering
85
- * it never to query any idling resource again even after the timer effectively expires...
86
- */
87
- it("should be *busy* even if all timers are overdue") {
88
- givenTimer(anOverdueTimer())
89
- givenTimer(anOverdueTimer())
90
- Assertions.assertThat(uut().isIdleNow()).isFalse()
91
- }
92
-
93
- it("should be busy if has a meaningful pending timer set beyond an overdue timer") {
94
- givenTimer(anOverdueTimer())
95
- givenTimer(aOneShotTimer(MEANINGFUL_TIMER_INTERVAL))
96
- Assertions.assertThat(uut().isIdleNow()).isFalse()
97
- }
98
-
99
- it("should yield to other threads using the timers module") {
100
- val executor = Executors.newSingleThreadExecutor()
101
- var isIdle: Boolean? = null
102
-
103
- synchronized(timersNativeModule.mTimerGuard) {
104
- executor.submit {
105
- isIdle = uut().isIdleNow()
106
- }
107
- UTHelpers.yieldToOtherThreads(executor)
108
- Assertions.assertThat(isIdle).isNull()
109
- }
110
- UTHelpers.yieldToOtherThreads(executor)
111
- Assertions.assertThat(isIdle).isNotNull()
112
- executor.shutdownNow()
113
- }
114
- }
115
- })