GeekyAnts / NativeBase

Mobile-first, accessible components for React Native & Web to build consistent UI across Android, iOS and Web.
https://nativebase.io/
MIT License
20.16k stars 2.38k forks source link

Skeleton component animation keeps running in backgroud after finished loading #5306

Open rafamanzo opened 2 years ago

rafamanzo commented 2 years ago

Description

After adding the Skeleton component, my detox tests started to hang because there are animations running.

CodeSandbox/Snack link

It is a issue that only happen with detox running.

Steps to reproduce

  1. Add a Skeleton to the code
  2. Run detox tests

NativeBase Version

3.4.13

Platform

Other Platform

No response

Additional Information

Detox output:

11:00:48.957 detox[545061] INFO:  [APP_STATUS] The app is busy with the following tasks:
• UI elements are busy:
  - Reason: Animations running on screen.
1
Viraj-10 commented 2 years ago

Hi @rafamanzo, Can you provide the test case which you have written?

mizutani256 commented 1 year ago

Here it seems to happen when we use the isLoaded prop, for instance:

<Skeleton isLoaded={someValue}>
  <Text>something</Text>
</Skeleton>

The detox test hangs as soon as it reaches a screen with a component like this.

BrantApps commented 1 year ago

We've also been having problems with this and until now have been peppering our production code base with animation disabling code where our test cases with Detox have encountered a Native Base Skeleton. However, I just refactored some of the code to use this wrapping component;

import {Box, ISkeletonProps, Skeleton, useColorModeValue} from "native-base"
import {ISkeletonTextProps} from "native-base/lib/typescript/components/composites"
import React, {ComponentType} from "react"

import {getIsUiTest} from "../../config"

const DetoxAwareSkeletonText: ComponentType<ISkeletonTextProps> = (props) => {
  const boxColor = useColorModeValue("muted.200", "muted.600")
  if (getIsUiTest() && !props.isLoaded) {
    return (
      <Box
        bgColor={boxColor}
        w={props.width ?? "100%"}
        minH={4 * (props.lines ?? 1)}
      />
    )
  }
  return <Skeleton.Text {...props}>{props.children}</Skeleton.Text>
}

const DetoxAwareSkeletonCircle: ComponentType<ISkeletonProps> = (props) => {
  const boxColor = useColorModeValue("muted.200", "muted.600")
  if (getIsUiTest() && !props.isLoaded) {
    return <Box bgColor={boxColor} rounded={"full"} {...props} />
  }
  return <Skeleton.Circle {...props}>{props.children}</Skeleton.Circle>
}

type DetoxSkeletonProps = ISkeletonProps & {Text: ISkeletonTextProps} & {
  Circle: ISkeletonProps
}

export const DetoxAwareSkeleton: ComponentType<DetoxSkeletonProps> & {
  Text: ComponentType<DetoxSkeletonProps["Text"]>
} & {
  Circle: ComponentType<DetoxSkeletonProps["Circle"]>
} = (props) => {
  const boxColor = useColorModeValue("muted.200", "muted.600")
  if (getIsUiTest() && !props.isLoaded) {
    return <Box bgColor={boxColor} {...props} />
  }
  return <Skeleton {...props}>{props.children}</Skeleton>
}

DetoxAwareSkeleton.Text = DetoxAwareSkeletonText
DetoxAwareSkeleton.Circle = DetoxAwareSkeletonCircle

And then in my production code where a Detox test was encountering a Skeleton component (causing it to sometimes time out) the code has become;

-  <Skeleton.Text lines={1} isLoaded={isLoaded}>
+  <DetoxAwareSkeleton.Text lines={1} isLoaded={isLoaded}>
     <HeadlineCaps>{date}</HeadlineCaps>
-  </Skeleton.Text>
+  </DetoxAwareSkeleton.Text>

I think the ideal API from Native Base to accommodate animation-sensitive tooling like grey/black box tests would be;

<Skeleton isTesting={(true|false)} /> with a default of false. Internally within the root Skeleton component this would then render boxes (as opposed to the animated views) or even bind to another component entirely. This is similar to the approach taken by the Meta team for a similar issue in their snapshotting tests.

What do you think?

jpgorman commented 1 year ago

Looking at the source code, the issue seems to be that an Animation timer is started in the Skeleton components useEffect hook. However this hook doesn't then stop the animation once the component isLoaded, nor does it stop the animation when the component is unmounted.

Essentially it starts a new timer on each mount and never cleans them up. Detox then detects that the timers are running and waits for them to stop, which never happens.

https://github.com/GeekyAnts/NativeBase/blob/master/src/components/composites/Skeleton/Skeleton.tsx#L40

sardyy commented 1 year ago

I've just encountered the same issue.

What's the state of this? Any resolution in mind?