Shopify / react-native-skia

High-performance React Native Graphics using Skia
https://shopify.github.io/react-native-skia
MIT License
6.92k stars 448 forks source link

Animations Colors gives when running jest (0 , _interpolate.interpolate) is not a function #2517

Closed arelstone closed 3 months ago

arelstone commented 3 months ago

Description

I am not sure if this should be marked as Documentation issue or a Bug report. I grabbed the code from the documentation so i'll start it off as a documentation issue. If incorrect please change the label

Following the example of animating colors gives me an error when my tests

import React, { FC, useEffect } from 'react';
import { Canvas, Fill, LinearGradient, vec } from '@shopify/react-native-skia';
import { useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
import { View } from 'react-native';
import { interpolateColors } from './interpolateColors';

type Radius = number | 'square' | 'round'

type SkeletonProps = {
  width: number
  height?: number
  duration?: number
  radius?: Radius
}

const startColors = [
  'rgb(250, 250, 250)',
  'rgba(34,193,195,0.4)',
  'rgba(63,94,251,1)',
  'rgba(253,29,29,0.4)',
];
const endColors = [
  'rgb(205, 205, 205)',
  // 'rgba(253,187,45,0.4)',
  'rgba(252,70,107,1)',
  'rgba(252,176,69,0.4)',
];

const calculateBorderRadius = (r: Radius) => {
  switch (r) {
    case 'round': return 1000;
    case 'square': return 0;
    default: return r;
  }
};

export const Skeleton: FC<SkeletonProps> = ({
  width, height = 32, duration = 4000, radius = 8,
}) => {
  const colorsIndex = useSharedValue(0);
  useEffect(() => {
    colorsIndex.value = withRepeat(
      withTiming(startColors.length - 1, { duration }),
      -1,
      true,
    );
  }, []);
  const gradientColors = useDerivedValue(() => {
    return [
      interpolateColors(colorsIndex.value, [0, 1, 2, 3], startColors),
      interpolateColors(colorsIndex.value, [0, 1, 2, 3], endColors),
    ];
  });

  return (
    <View style={{ width, height, borderRadius: calculateBorderRadius(radius), overflow: 'hidden' }}>
      <Canvas style={{ width, height }}>
        <Fill>
          <LinearGradient
            start={vec(0, 0)}
            end={vec(width, height)}
            colors={gradientColors}
          />
        </Fill>
      </Canvas>
    </View>
  );
};

I have followed the steps from Testing with Jest in the documentation and have updated the transformIgnorePatterns and setupFiles

Error:

``` TypeError: (0 , _interpolate.interpolate) is not a function 48 | const gradientColors = useDerivedValue(() => { 49 | return [ > 50 | interpolateColors(colorsIndex.value, [0, 1, 2, 3], startColors), | ^ 51 | interpolateColors(colorsIndex.value, [0, 1, 2, 3], endColors), 52 | ]; 53 | }); at interpolateColorsRGB (node_modules/@shopify/react-native-skia/lib/commonjs/animation/functions/interpolateColors.ts:13:24) at anonymous (node_modules/@shopify/react-native-skia/lib/commonjs/animation/functions/interpolateColors.ts:48:10) at updater (src/@next/screens/ProductDetailsScreen/Skeleton/Skeleton.tsx:50:24) at initialUpdaterRun (node_modules/react-native-reanimated/lib/module/reanimated2/animation/util.ts:75:18) at useDerivedValue (node_modules/react-native-reanimated/lib/module/reanimated2/hook/useDerivedValue.ts:46:52) at Skeleton (src/@next/screens/ProductDetailsScreen/Skeleton/Skeleton.tsx:48:41) at renderWithHooks (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5608:18) at mountIndeterminateComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9884:13) at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11351:16) at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15850:12) at workLoopSync (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15784:5) at renderRootSync (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15756:7) at performSyncWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15461:20) at flushSyncCallbacks (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:2597:22) at flushActQueue (node_modules/react/cjs/react.development.js:2667:24) at actImplementation (node_modules/react/cjs/react.development.js:2521:11) at node_modules/@testing-library/react-native/src/act.ts:30:25 at renderWithAct (node_modules/@testing-library/react-native/src/render-act.ts:12:11) at renderInternal (node_modules/@testing-library/react-native/src/render.tsx:59:33) at render (node_modules/@testing-library/react-native/src/render.tsx:29:10) at Object. (src/@next/screens/ProductDetailsScreen/components/__tests__/SellerInfoSection.spec.tsx:25:28) ```

React native env:

``` info Fetching system and libraries information... System: OS: macOS 14.5 CPU: (8) arm64 Apple M1 Pro Memory: 1.48 GB / 32.00 GB Shell: version: "5.9" path: /bin/zsh Binaries: Node: version: 20.10.0 path: ~/.nvm/versions/node/v20.10.0/bin/node Yarn: version: 3.8.2 path: /opt/homebrew/bin/yarn npm: version: 10.2.3 path: ~/.nvm/versions/node/v20.10.0/bin/npm Watchman: version: 2023.12.04.00 path: /opt/homebrew/bin/watchman Managers: CocoaPods: version: 1.15.2 path: /Users/cdaurehoej/.rbenv/shims/pod SDKs: iOS SDK: Platforms: - DriverKit 23.4 - iOS 17.4 - macOS 14.4 - tvOS 17.4 - visionOS 1.1 - watchOS 10.4 Android SDK: API Levels: - "30" - "31" - "33" - "33" - "34" Build Tools: - 29.0.2 - 30.0.2 - 30.0.3 - 31.0.0 - 33.0.0 - 33.0.1 - 34.0.0 System Images: - android-33 | Google APIs ARM 64 v8a Android NDK: Not Found IDEs: Android Studio: 2022.3 AI-223.8836.35.2231.10671973 Xcode: version: 15.3/15E204a path: /usr/bin/xcodebuild Languages: Java: version: 17.0.9 path: /usr/bin/javac Ruby: version: 3.2.1 path: /Users/cdaurehoej/.rbenv/shims/ruby npmPackages: "@react-native-community/cli": Not Found react: installed: 18.3.1 wanted: 18.3.1 react-native: installed: 0.74.2 wanted: 0.74.2 react-native-macos: Not Found npmGlobalPackages: "*react-native*": Not Found Android: hermesEnabled: true newArchEnabled: false iOS: hermesEnabled: true newArchEnabled: false ```

A solution?

After copying @shopify/react-native-skia/src/animation/functions/interpolateColors.ts and @shopify/react-native-skia/src/animation/functions/interpolate.ts to be "side by side" with my own component jest does not report the error above

Please Note:

Please note that jest does not report any errors when removing @shopify/react-native-skia from my testPathIgnorePatterns

wcandillon commented 3 months ago

Can you show me what's inside import { interpolateColors } from './interpolateColors';? This doesn't look standard.

arelstone commented 3 months ago

Can you show me what's inside import { interpolateColors } from './interpolateColors';? This doesn't look standard.

It's not standard, no. As mentioned I've done a copy of @shopify/react-native-skia/src/animation/functions/interpolateColors.ts and @shopify/react-native-skia/src/animation/functions/interpolate.ts to my own project. No changes made at all

wcandillon commented 3 months ago

May I ask why you did that? I'm trying to get a sense of how I can reproduce the issue.

arelstone commented 3 months ago

I am actually not sure why I came to try this.... You know, sometimes when debugging you try some odd things and it end up working 🙈

Could the problem be cause by my jest or babel config?

jest.config.ts

import type { JestConfigWithTsJest } from 'ts-jest';

const TRANSFORM_IGNORE_PATTERNS_NODE_MODULES = [
  '@react-native',
  'react-native',
  'react-native-vector-icons',
  'react-native-circular-progress',
  'react-native-reanimated',
  'react-native-linear-gradient',
  'react-native-haptic-feedback',
  'mixpanel-react-native',
  'newrelic-react-native-agent',
  'react-native-element-dropdown',
  // @see: https://github.com/Shopify/react-native-skia/issues/2517
  '@shopify/react-native-skia',
];

const jestConfig: JestConfigWithTsJest = {
  preset: 'react-native',
  transform: {
    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/jest.fileTransformer.js',
    '^.+\\.js(x)?$': 'babel-jest',
    '^.+\\.ts(x)?$': ['ts-jest', { tsconfig: 'tsconfig.spec.json' }],
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
  testRegex: '(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$',
  testPathIgnorePatterns: [
    '\\.snap$',
    '<rootDir>/node_modules/',
    '<rootDir>/build',
    '<rootDir>/e2e',
    '<rootDir>/__tests__/__mocks__',
  ],
  transformIgnorePatterns: [`node_modules/(?!(${TRANSFORM_IGNORE_PATTERNS_NODE_MODULES.join('|')})/)`],
  cacheDirectory: '.jest/cache',
  setupFiles: [
    './node_modules/react-native-gesture-handler/jestSetup.js',
    '@shopify/react-native-skia/jestSetup.js',
    './node_modules/newrelic-react-native-agent/jestSetup.js',
    '<rootDir>/jest.mocks.ts',
  ],
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  forceExit: true,
  fakeTimers: { enableGlobally: true },
  globals: {
    __DEV__: true,
  },
};

export default jestConfig;

bable.config.js

/** @type {import('ts-jest').JestConfigWithTsJest} */

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:@react-native/babel-preset'],
    plugins: [
      '@babel/plugin-transform-export-namespace-from',
      '@babel/plugin-transform-optional-chaining',
      '@babel/plugin-proposal-optional-chaining',
      '@babel/plugin-transform-nullish-coalescing-operator',
      'react-native-reanimated/plugin', // Reanimated plugin needs to be listed last!!
    ],
  };
};

tsconfig.json

{
  "extends": "@tsconfig/react-native/tsconfig.json",
  "compilerOptions": {
    "outDir": "build",
    "types": ["node", "jest", "@testing-library/jest-native", "@testing-library/react-native"],
  }
}
wcandillon commented 3 months ago

I think you should try to use interpolateColors from Skia directly no? We also unit test in jest so if there is a small repro it's easy for us to add in the test suite.

arelstone commented 3 months ago

importing interpolateColors from skia is what caused the problem

wcandillon commented 3 months ago

I check if the function is properly mocked and it is and we can even add a test for it on our side. We would need to small reproduction to reopen/fix the issue.