/**

 * Copyright (c) 2024 Huawei Technologies Co., Ltd.

 *

 * This source code is licensed under the MIT license found in the

 * LICENSE-MIT file in the root directory of this source tree.

 */



import { TestCase, TestSuite } from '@rnoh/testerino';

import React, { useEffect } from 'react';

import { AppState, AppStateStatus, Text } from 'react-native';

import { Button } from '../components';



async function wait(ms: number) {

  return new Promise(resolve => {

    setTimeout(resolve, ms);

  });

}



const PRECISION_IN_MS = 100;



export function TimerTest() {

  return (

    <TestSuite name="Timer">

      <TestCase

        itShould="take three seconds to finish this test (setTimeout)"

        initialState={0}

        arrange={({ setState }) => {

          return (

            <Button

              label="Start Timeout"

              onPress={() => {

                setState(prev => prev + 1);

              }}

            />

          );

        }}

        assert={async ({ expect }) => {

          const waitTimeInMs = 3000;

          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

        itShould="take three seconds to finish this test (setInterval)"

        initialState={0}

        arrange={({ setState }) => {

          return (

            <Button

              label="Start Interval"

              onPress={() => {

                setState(prev => prev + 1);

              }}

            />

          );

        }}

        assert={async ({ expect }) => {

          await wait(3000);

          let i = 0;

          const time1 = new Date().getTime();



          await new Promise(resolve => {

            setInterval(() => {

              if (i === 1) {

                resolve(null);

              }

              i++;

            }, 1000);

          });



          const time2 = new Date().getTime();

          expect(Math.abs(time2 - time1 - 2000)).to.be.lessThan(

            PRECISION_IN_MS,

          );

        }}

      />

      <TestCase<{ 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);

        }}

      />

    </TestSuite>

  );

}



function Effect({

  onEffect,

  children,

}: {

  onEffect: () => void | (() => void);

  children: any;

}) {

  useEffect(onEffect, []);

  return children;

}