* Copyright (c) 2025 Huawei Technologies Co., Ltd.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {TestSuite} from '@rnoh/testerino';
import React, {useEffect} from 'react';
import {AppState, AppStateStatus, Text, View} from 'react-native';
import {TestCase} from '../components';
async function wait(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
function burnCPU(ms: number) {
const start = Date.now();
while (Date.now() < start + ms) {}
}
const PRECISION_IN_MS = 100;
export function TimerTest() {
return (
<TestSuite name="Timer">
<TestSuite name="requestIdleCallback">
<TestCase.Logical
tags={['sequential']}
itShould="not crash"
fn={async () => {
// @ts-ignore
cancelIdleCallback(requestIdleCallback(() => {}));
}}
/>
<TestCase.Logical
tags={['sequential']}
itShould="eventually execute idle callback"
fn={async ({expect}) => {
await new Promise<void>(resolve => {
// @ts-ignore
requestIdleCallback(deadline => {
expect(deadline.didTimeout).to.be.false;
expect(deadline.timeRemaining()).greaterThanOrEqual(0);
return resolve();
});
});
}}
/>
<TestCase.Logical
tags={['sequential']}
itShould="did timeout"
fn={async ({expect}) => {
// @ts-ignore
requestIdleCallback(
// @ts-ignore
deadline => {
expect(deadline.didTimeout).to.be.true;
},
{timeout: 50},
);
burnCPU(100);
}}
/>
</TestSuite>
<TestCase.Logical
itShould="take one second to finish this test (setTimeout, 1s)"
tags={['sequential']}
fn={async ({expect}) => {
const waitTimeInMs = 1000;
const time1 = new Date().getTime();
await wait(waitTimeInMs);
const time2 = new Date().getTime();
expect(time1).to.be.greaterThan(0);
expect(time2).to.be.greaterThan(0);
expect(Math.abs(time2 - time1 - waitTimeInMs)).to.be.lessThan(
PRECISION_IN_MS,
);
}}
/>
<TestCase.Logical
itShould="take a short time to finish this test (setTimeout, 0ms)"
tags={['sequential']}
fn={async ({expect}) => {
const waitTimeInMs = 0;
const time1 = new Date().getTime();
await wait(waitTimeInMs);
const time2 = new Date().getTime();
expect(time1).to.be.greaterThan(0);
expect(time2).to.be.greaterThan(0);
expect(Math.abs(time2 - time1 - waitTimeInMs)).to.be.lessThan(
PRECISION_IN_MS,
);
}}
/>
<TestCase.Logical
itShould="take one second to finish this test (setInterval)"
tags={['sequential']}
fn={async ({expect}) => {
let i = 0;
const time1 = new Date().getTime();
await new Promise(resolve => {
setInterval(() => {
if (i === 1) {
resolve(null);
}
i++;
}, 500);
});
const time2 = new Date().getTime();
expect(Math.abs(time2 - time1 - 1000)).to.be.lessThan(
PRECISION_IN_MS,
);
}}
/>
<TestCase.Logical
itShould="cancel the timer before it's triggered"
tags={['sequential']}
fn={async ({expect}) => {
let finished = 0;
const timerId = setTimeout(() => {
finished = 2;
}, 500);
setTimeout(() => {
clearTimeout(timerId);
finished = 1;
}, 250);
await wait(1000);
expect(finished).to.be.eq(1);
}}
/>
<TestCase.Manual<{date: Date; appStateStatus: AppStateStatus}[]>
modal
itShould="not trigger updates when the application is in background"
initialState={[]}
arrange={({state, setState}) => {
return (
<Effect
onEffect={() => {
const interval = setInterval(() => {
setState(prev => [
...prev,
{
date: new Date(),
appStateStatus: AppState.currentState,
},
]);
}, 1000);
return () => clearInterval(interval);
}}>
<Text>
{JSON.stringify(
state.reduce(
(acc, tick) => {
return {
...acc,
[tick.appStateStatus]: acc[tick.appStateStatus] + 1,
};
},
{
active: 0,
background: 0,
extension: 0,
inactive: 0,
unknown: 0,
} as Record<AppStateStatus, number>,
),
)}
</Text>
</Effect>
);
}}
assert={({expect, state}) => {
expect(
state.filter(tick => tick.appStateStatus !== 'active').length,
).to.be.eq(0);
}}
/>
<TestCase.Example modal itShould="display the average delay of timer">
<DelayExample />
</TestCase.Example>
</TestSuite>
);
}
function DelayExample() {
const DURATION = 350;
const [iterations, setIterations] = React.useState(0);
const [delay, setDelay] = React.useState(0);
const [lastDelay, setLastDelay] = React.useState(0);
useEffect(() => {
const now = new Date().getTime();
const id = setTimeout(() => {
const elapsed = new Date().getTime() - now;
const currentDelay = elapsed - DURATION;
setLastDelay(currentDelay);
setDelay(prev => prev + currentDelay);
setIterations(prev => prev + 1);
}, DURATION);
return () => clearTimeout(id);
}, [iterations]);
return (
<View>
<Text>Iterations: {iterations}</Text>
<Text>Total delay: {delay}</Text>
<Text>Last delay: {lastDelay}</Text>
<Text>Avg. delay: {delay / iterations}</Text>
</View>
);
}
function Effect({
onEffect,
children,
}: {
onEffect: () => void | (() => void);
children: any;
}) {
useEffect(onEffect, []);
return children;
}