Skip to content

Commit 2b5d210

Browse files
committed
Android: Improve RN-timers idling resource such that it would skip over interval-timers
1 parent b0e3b0f commit 2b5d210

File tree

3 files changed

+64
-34
lines changed

3 files changed

+64
-34
lines changed

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

+28-12
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ public boolean isIdleNow() {
9090
return true;
9191
}
9292

93-
Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get();
94-
Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get();
93+
final Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get();
94+
final Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get();
9595
synchronized (timerLock) {
9696
final PriorityQueue<?> timers = Reflect.on(timingModule).field(FIELD_TIMERS).get();
97-
final Object nextTimer = timers.peek();
97+
final Object nextTimer = findNextTimer(timers);
9898
if (nextTimer == null) {
9999
if (callback != null) {
100100
callback.onTransitionToIdle();
@@ -147,22 +147,38 @@ public void resume() {
147147
paused.set(false);
148148
}
149149

150+
private Object findNextTimer(PriorityQueue<?> timers) {
151+
Object nextTimer = timers.peek();
152+
if (nextTimer == null) {
153+
return null;
154+
}
155+
156+
final boolean isRepetitive = Reflect.on(nextTimer).field(TIMER_FIELD_REPETITIVE).get();
157+
if (!isRepetitive) {
158+
return nextTimer;
159+
}
160+
161+
Object timer = null;
162+
long targetTime = Long.MAX_VALUE;
163+
for (Object aTimer : timers) {
164+
final boolean timerIsRepetitive = Reflect.on(aTimer).field(TIMER_FIELD_REPETITIVE).get();
165+
final long timerTargetTime = Reflect.on(aTimer).field(TIMER_FIELD_TARGET_TIME).get();
166+
if (!timerIsRepetitive && timerTargetTime < targetTime) {
167+
targetTime = timerTargetTime;
168+
timer = aTimer;
169+
}
170+
}
171+
return timer;
172+
}
173+
150174
private boolean isTimerOutsideBusyWindow(Object nextTimer) {
151175
final long currentTimeMS = System.nanoTime() / 1000000L;
152176
final Reflect nextTimerReflected = Reflect.on(nextTimer);
153177
final long targetTimeMS = nextTimerReflected.field(TIMER_FIELD_TARGET_TIME).get();
154178
final int intervalMS = nextTimerReflected.field(TIMER_FIELD_INTERVAL).get();
155-
final boolean isRepetitive = nextTimerReflected.field(TIMER_FIELD_REPETITIVE).get();
156179

157180
// Log.i(LOG_TAG, "Next timer has duration of: " + intervalMS
158-
// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS
159-
// + "; is " + (isRepetitive ? "repeating" : "a one-shot"));
160-
161-
// Before making any concrete checks, be sure to ignore repeating timers or we'd loop forever.
162-
// TODO: Should we iterate to the first, non-repeating timer?
163-
if (isRepetitive) {
164-
return true;
165-
}
181+
// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS);
166182

167183
// Core condition is for the timer interval (duration) to be set beyond our window.
168184
// Note: we check the interval in an 'absolute' way rather than comparing to the 'current time'

detox/test/e2e/09.stress-timeouts.test.js

+5
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ describe('StressTimeouts', () => {
3333
await element(by.id('IntervalIgnore')).tap();
3434
await expect(element(by.text('Interval Ignored!!!'))).toBeVisible();
3535
});
36+
37+
it('should skip over setInterval', async () => {
38+
await element(by.id('SkipOverInterval')).tap();
39+
await expect(element(by.text('Interval Skipped-Over!!!'))).toBeVisible();
40+
});
3641
});

detox/test/src/Screens/TimeoutsScreen.js

+31-22
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ export default class TimeoutsScreen extends Component {
4444
<Text testID='IntervalIgnore' style={{color: 'blue', marginBottom: 20}}>Interval Ignore</Text>
4545
</TouchableOpacity>
4646

47+
<TouchableOpacity onPress={this.onIntervalSkipOver.bind(this, 'Interval Skipped-Over')}>
48+
<Text testID='SkipOverInterval' style={{color: 'blue', marginBottom: 20}}>Interval Skip-Over</Text>
49+
</TouchableOpacity>
50+
4751
</View>
4852
);
4953
}
@@ -59,37 +63,42 @@ export default class TimeoutsScreen extends Component {
5963
}
6064

6165
onTimeoutButtonPress(greeting, timeout) {
62-
setTimeout(() => {
63-
this.setState({
64-
greeting: greeting
65-
});
66-
}, timeout);
66+
setTimeout(() => this.setState({greeting}), timeout);
6767
}
6868

6969
onTimeoutIgnoreButtonPress(greeting, timeout) {
70-
setTimeout(() => {
71-
console.log('this will happen soon');
72-
}, timeout);
73-
this.setState({
74-
greeting: greeting
75-
});
70+
setTimeout(() => console.log('this will happen soon'), timeout);
71+
this.setState({greeting});
7672
}
7773

7874
onImmediateButtonPress(greeting) {
79-
setImmediate(() => {
80-
this.setState({
81-
greeting: greeting
82-
});
83-
});
75+
setImmediate(() => this.setState({greeting}));
8476
}
8577

8678
onIntervalIgnoreButtonPress(greeting, interval) {
87-
setInterval(() => {
88-
console.log('this is recurring');
89-
}, interval);
90-
this.setState({
91-
greeting: greeting
92-
});
79+
setInterval(() => console.log('this is recurring'), interval);
80+
this.setState({greeting});
9381
}
9482

83+
onIntervalSkipOver(greeting) {
84+
const busyPeriodTimeoutHandler = () => this.setState({greeting});
85+
const idledTimeoutHandler = (timeMs) => console.log(`Non-busy keeping timer of ${timeMs}ms has expired`);
86+
87+
const interval = 88;
88+
const busyKeepingTime = 600;
89+
const idledTimeBase = 1500 + 1;
90+
const foreverTimer = 2500;
91+
let intervalId;
92+
93+
intervalId = setInterval(() => console.log(`this should show every ${interval}ms`), interval);
94+
setTimeout(() => idledTimeoutHandler(idledTimeBase), idledTimeBase);
95+
setTimeout(() => busyPeriodTimeoutHandler(), busyKeepingTime);
96+
setTimeout(() => idledTimeoutHandler(idledTimeBase + 10), idledTimeBase + 10);
97+
setTimeout(() => idledTimeoutHandler(idledTimeBase + 100), idledTimeBase + 100);
98+
setTimeout(() => {
99+
console.log('"Forever" timer expired - though it shouldn\'t have!');
100+
clearInterval(intervalId);
101+
this.setState({greeting: '???'});
102+
}, foreverTimer);
103+
}
95104
}

0 commit comments

Comments
 (0)