react-spring needs tests badly, asking for help ...

Created on 30 May 2018  ·  23Comments  ·  Source: pmndrs/react-spring

I have some experience with Jest, but not enough to deal with UI/animation states, pixels and async. I've heard about Puppeteer but i don't have the capacity right now to delve into something like that due to my work. It kind of sucks that little changes can cause things like this: https://github.com/drcmda/react-spring/issues/101 and it would be awesome if react-spring could calm down now and focus on stability, after months of exploring api's and functionality.

If anyone knows how it could be done i would gladly receive PR's. @TrySound ?

good first issue help wanted

Most helpful comment

I'm not sure if Puppeteer (or even Cypress) is needed here, as this library is less about user-interactions and more about interpolating values under certain conditions (completely separated from the concept of a Web Browser, Web Page or a UI).

My first thought was that requestAnimationFrame needs to be mocked so we can get the tests to run in a consistent manner and compare each tick with the expected value. Turns out this is exactly what react-motion did and the logic had been extract.

Since react-spring animations are not functions of time but rather physical simulations I believe one way to create the fixtures would be to record/snapshot every frame of an animation and then later use that as the expected values. The nice thing about mocking raf is that the tests don't need to run in real-time. You can have an animation that takes 1s run in a few ms. I'm not sure how react-spring works internally and how much it depends on setState (or how much it of depends on React at all), because async rendering that was introduced in React might conflict with mocking requestAnimationFrame.

However, the flow would be:

  1. Set up a test case (e.g. animating opacity from 0 to 1)
  2. Simulate the test case in recording mode with mocked raf and record the result of every single tick
  3. Store the record as fixture
  4. Later: run the test case in testing mode and compare the values in every single tick.

This would only work for test cases that have manually been verified because these tests would not catch cases which are broken as of now. However, they would catch regressions.

All 23 comments

Hey, I would love to contribute with testing the project. I have some experience testing with puppeteer and enzyme. Do you have any recommendation on where I should start?

A spring from here to there, then snapshotting the end state? Then perhaps colors, interpolations, arrays, etc. That would be awesome.

Cool! I'll get started and give you an update when I can get something going

@drcmda could you write a spec first like a list? Each item will be used like test title then. Your understanding will be helpful.

If anyone can provide boilerplate/example or any other guidance, I'll contribute where possible. I'm considering using this lib in a larger app.

@drcmda Setting { module: false } for preset-env in .babelrc is causing conflict with jest. Is there a particular reason why it is false?

@TrySound good idea, but i would like to see how its done because as of right now i have not the slightest idea how a test could pull off checking visual states. If i could see how something simple like opacity 0 -> 1 works, it would help me to get the bigger picture. I'll make a list then of all the things that would be great to write tests for.

@mkdinh i can't remember right now, but you can adapt it to jests needs - in the end it should still be able to produce three kinds of builds: esm, cjs and umd.

@sbrichardson it will probably develop in this thread.

@mkdinh Extend babelrc with this piece and install this plugin. Everything should work.

{
  "env": {
    "test": {
      "plugins": [
        "@babel/plugin-transform-modules-commonjs"
      ]
    }
  }

I'm not sure if Puppeteer (or even Cypress) is needed here, as this library is less about user-interactions and more about interpolating values under certain conditions (completely separated from the concept of a Web Browser, Web Page or a UI).

My first thought was that requestAnimationFrame needs to be mocked so we can get the tests to run in a consistent manner and compare each tick with the expected value. Turns out this is exactly what react-motion did and the logic had been extract.

Since react-spring animations are not functions of time but rather physical simulations I believe one way to create the fixtures would be to record/snapshot every frame of an animation and then later use that as the expected values. The nice thing about mocking raf is that the tests don't need to run in real-time. You can have an animation that takes 1s run in a few ms. I'm not sure how react-spring works internally and how much it depends on setState (or how much it of depends on React at all), because async rendering that was introduced in React might conflict with mocking requestAnimationFrame.

However, the flow would be:

  1. Set up a test case (e.g. animating opacity from 0 to 1)
  2. Simulate the test case in recording mode with mocked raf and record the result of every single tick
  3. Store the record as fixture
  4. Later: run the test case in testing mode and compare the values in every single tick.

This would only work for test cases that have manually been verified because these tests would not catch cases which are broken as of now. However, they would catch regressions.

@Prinzhorn @mkdinh @sbrichardson here's a first draft: https://github.com/drcmda/react-spring/blob/master/tests/test.js

it uses plain jest and enzyme with deep mount, renders out a spring and uses onRest to complete the promise by checking the result. I've tested snapshotting as well, it worked, but like you said, probably just checking end-states is probably enough.

probably just checking end-states is probably enough.

I disagree. For example easings (or different tension, friction etc.) would then not be tested at all or imagine a commit breaks the spring physics and it just jumps to the end state without any animation. Also the exact time when the end-state is reached is equally important. I think if we get snapshotting to work reliably on every tick then this allows us to test the "feeling" of the spring and see if it has changed.

Hmmm, agreed, but how would you do it? Springs aren't duration based and requestAnimationFrame is quite arbitrary and can be interrupted by the browser. We can of course define our own tick function that react-spring will use, there's a global inject for it. But wouldn't this also stretch tests into long waiting phases?

instead of testing the component, can we just test the calculations themselves to make sure that they follows the correct physics?

Sure, that’s possible, too, but it’s usually lifecycles and component phases that cause issues. The math is stable and doesn’t normally change.

If that is the case, then would spying on the methods during the lifecycles be suffice? All we have to test is the method is being called with the correct argument at the correct time.

@drcmda Hi, I refactored all the animations in my app from gsap to react-spring and everything went well except when I wanted to test(jest/enzyme) a component with "react-spring/dist/addons", I had a weird error saying unexpected token when reading the addons module, like it wasn't transpiled to es5 or something.

I use Typescript and has to include a bunch of babel packages to make the tests work, but it started breaking other stuff. To find a workaround, I started to look in the dist folder and I realized there are other versions of the module like the .cjs and tried to use
import { TimingAnimation, Easing } from "react-spring/dist/addons.cjs";
instead and everything worked perfectly!

I removed all the babel stuff and it's all good, but I was wondering, shouldn't the addons.js be the .cjs one?

@mbeauchamp7 The difference is that index uses esm and .cjs doesn't. Normally you'd use esm in your bundler because of tree-shaking and possibly some other benefits. If TS can't resolve it, using .cjs for testing is completely fine.

@drcmda What I ment is that the index version actually works when I develop and bundle with webpack, but not when I run tests with jest, it probably has something to do with the ts-jest package, I'm not sure..

Pulled in Enyzme. Writing snapshots/unit tests for all the things. 😀

Tests seem to run through again. There's a distinction now that i think is messing with a few tests, though. Before it would use setTimeout for delay, which was controlled by jest. Now it checks time inside the requestAnimationFrame-loop. Probably easier to handle for us ... ?

I get this import error, but it`s not in the same component either ^^
Using CRA

` src/containers/ComponentRoute/ComponentRoute.test.js
● Test suite failed to run

C:\Git\Wiklem.no\node_modules\react-spring\dist\addons.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import _extends from '@babel/runtime/helpers/esm/extends';
                                                                                                ^^^^^^^^

SyntaxError: Unexpected identifier

  1 | import React from "react";
> 2 | import { Parallax, ParallaxLayer } from "react-spring/dist/addons";
    | ^

  at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
  at Object.<anonymous> (src/pages/public/Home/Home.jsx:2:1)

`

@issuehuntfest has funded $60.00 to this issue. See it on IssueHunt

It's probably much easier to write tests for the basic primitives (AnimatedValue et al) than to the whole DOM thing; should be a good place to start.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cklab picture cklab  ·  4Comments

eDubrovsky picture eDubrovsky  ·  3Comments

VincentCtr picture VincentCtr  ·  3Comments

localjo picture localjo  ·  3Comments

lennerd picture lennerd  ·  3Comments