`requestAnimationFrame` polyfill error in Jest tests - reactjs

Got this error after upgrading to React when I ran my Jest unit tests:
React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers.
How do I fix it?
I'm using Jest 18.1.0.

Found a workaround!
Steps:
Create the file __mocks__/react.js
Add the following into __mocks__/react.js
const react = require('react');
// Resolution for requestAnimationFrame not supported in jest error :
// https://github.com/facebook/react/issues/9102#issuecomment-283873039
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
throw new Error('requestAnimationFrame is not supported in Node');
};
module.exports = react;
Run jest !
As marked on comments on the code
This is the solution from
https://github.com/facebook/react/issues/9102#issuecomment-283873039

this worked for me:
Install raf
npm install --saveDev raf or yarn add -D raf
Add the polyfill to your setupFiles in your jest config in package.json like this:
'setupFiles': ['raf/polyfill']
Note: if you have other setup files in this array, you may want to put raf/polyfill first.

If you just need to polyfill it for tests, then you don't actually need the throttling.
Create a new file with this code:
global.requestAnimationFrame = function (cb) {
return setTimeout(cb, 0);
};
Add that file to the jest/setupFiles array in your package.json.

If you are using create-react-app, some of these solutions will not work well (or at all, in the case of setupFiles). What does work well is creating a file at src/setupTests.js and adding your mock in there:
global.requestAnimationFrame = (cb) => { cb(); };
You can also add other global mocks in there (e.g. localStorage and navigator.serviceWorker).

Another working solution!
The idea is to load a simple shim before each spec, by using the setupFiles property in the jest config.
Create a file shim.js file (preferably in your root dir) and have this code in it:
global.requestAnimationFrame = (callback) => {
setTimeout(callback, 0);
};
Next, you may be having redundant code that keep reappearing in all/most of your files - and you want to put them in a single file and have them run before each spec also, to do that:
Create a setup.js file in the root dir too. A good piece of redundant code to D.R.Y is the react enzyme adapter configuration code. Paste it here
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
Now create the jest.config.js file, to specify the paths of the two files
{
module.exports = {
"setupFiles": ["<rootDir>shim.js", "<rootDir>setup.js"]
}
}
N.B: The jest config file take json, so make sure json's in. Also, if your shim.js and setup.js files are not in the same dir as your jest.config.js, adjust the path accordingly.
Hope this helps!
Credit: https://github.com/facebook/jest/issues/4545

Here's a realistic way to mock requestAnimationFrame:
let time;
const maxTimeElapsed = 2000;
beforeEach(() => {
time = 0;
jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => {
time += 100;
if (time < maxTimeElapsed) {
return cb(time) as any;
}
});
});
in your test, it repeatedly calls the RAF callback until it reaches the max elapsed time that you set. It happens instantly, so you don't need to stall it.

Just upgrade your react-scripts to 1.0.15 or above. It has been officially fixed after that version. See more details in https://github.com/facebook/create-react-app/issues/3199
Search the comment of gaearon commented on 31 Oct 2017

If you use TypeScript, the best solution is;
window.requestAnimationFrame = (): number => {
window.clearTimeout();
return 0;
};
Before all your describe and test suits.

According raf package docs at this moment need to run polyfill() function and specify object that need to be polyfilled. In my case works:
require('raf').polyfill(global.window)

Turns out it was because I upgraded enzyme without upgrading react and react-dom.
React 15.5 brings about some deprecations that caused many dependent libraries to have to update too. Make sure you are updating react, react-dom and check the README of those dependent packages for new libraries you have to install. For instance, Enzyme 2.8.2 now requires react-addons-test-utils as a dependency.

Related

React component library can not load its own static asset

I have a react app (using create react app) and then a component library (using webpack directly). One component in the component library needs to load a png file in one of the components that it exports. In the webpack config for the component library I have a section such as:
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
This successfully results in a file like: 29b6b66cf1a691be2b3f.png in the component libraries output directory. The issue, is that when the application uses that component and the component attempts to load the image, the img element is <img ... src="29b6b66cf1a691be2b3f.png" /> and that fails to load, since that image actually lives in the component library folder (within the react application's node_modules/component-library/ at that point).
I have scoured the internet to the best of my ability, and can not seem to figure out what the best solution would be here. Any help is appreciated. I will quickly offer clarification if needed.
UPDATE: I have discovered CopyWebpackPlugin but it is quite unfortunate that this would require me to eject the "parent" application from create react app. Something I would very much prefer to avoid.
UPDATE2: Current plan is to try following something like what is explained here. The jist of it is to utilize something like rewire to avoid needing to eject and still be able to edit the webpack config via something like:
// in ./build.js
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
// edit webpack config values here
... I will answer my own question here if I find that this approach works.
Alright, well this felt like it was much messier than it needed to be, but I was able to figure out a way to accomplish this. The pieces were as follows.
First, needed to install copy-webpack-plugin. This was not as simple as it might sound, because I needed to find one that would not conflict with the version of webpack required by my react-scripts (create react app). I determined that copy-webpack-plugin#6.4.1 was the last version to support webpack v4, so I installed that and then added the following to my package.json:
"overrides": {
"copy-webpack-plugin": {
"webpack": "4.44.2"
}
},
this ensured that it would use the version of webpack that my react-scripts installation was expecting.
Then, I also needed to install rewire, and add the following to a file called build.js:
const rewire = require('rewire');
const defaults = rewire('react-scripts/scripts/build.js');
const config = defaults.__get__('config');
const CopyPlugin = require("copy-webpack-plugin");
const patterns = [
{
context: "node_modules/component-library/path/",
from: "*.png",
to: "."
},
]
if(config.plugins === undefined){
config.plugins = [new CopyPlugin({patterns})]
}else{
config.plugins.push(new CopyPlugin({patterns}))
}
// below lets it work in dev mode too
if(config.devServer === undefined){
config.devServer = {
writeToDisk: true
}
}else{
config.devServer.writeToDisk = true;
}
Finally, had to update my build script to be:
"scripts": {
...
"build": "node ./build.js",
...
},

ReactJS Jest Puppeteer tests no longer working: ReferenceError: document is not defined

Scenario
npm test used to work without issue. Over the course of a month or so (I neglected tests) something changed and now I receive ReferenceError: document is not defined when trying to run Jest-Puppeteer tests via npm test.
This error shows up even with document removed so it seems like a puppeteer issue but I'm not sure why this is showing up now. I've checked out code from over a month ago and the tests still work but so much has changed that it's difficult to chase down the actually issue.
Attempted Solutions
upgrade node
reinstall npm packages
revert jest-puppeteer.config.js to previous version
add #jest-environment jsdom to tests which fixes the document issue but then causes ReferenceError: page is undefined
Question
How can I troubleshoot this problem short of starting over from scratch? That said, I'm prepared to start over if that's what it's going to take, which sometimes it does.
Code
this is a basic jest file
import "core-js/stable";
import "regenerator-runtime/runtime";
import {Provider} from "react-redux"
import mockState from "./mocks/mockState"
import configureStore from "redux-mock-store"
import ShallowRenderer from 'react-test-renderer/shallow'
import API from '../src/API'
import getNavigationResponse from '../src/nocks/getNavigation'
import expectedNavigationState from "./static/expectedNavigationState"
import pageObjects from "./static/pageObjects"
import utils from './utils'
import constants from '../src/constants'
describe('API tests', () => {
beforeEach(async() => {
await page.goto('http://localhost:3000');
await page.setViewport({ width: 900, height: 600 });
await page.goto('http://localhost:3000/');
await page.evaluate(() => {
document.getElementById('root').classList.add('animated-test');
});
await page.waitForSelector(pageObjects.navFactory);
});
// PASS
test('API data to be in store', async () => {
await page.waitForSelector(pageObjects.primaryNavLink);
// get state from root
const store = await utils.getStore();
expect(store[0].navigation.urlHashMap).toEqual(expectedNavigationState);
});
test.todo('Make sure content==true on vanity urls (home)')
test.todo('Make sure content==false on url items with children (visitor)')
// PASS
test('API cancel should cancel the API request', async () => {
API.dispatch = () => {
};
API.fetch(constants.NAVIGATION_HREF, 'API_FETCH_TYPE_NAVIGATION');
const promiseCanceled = API.cancel('API_FETCH_TYPE_NAVIGATION');
expect(promiseCanceled).hasOwnProperty('promise');
expect(promiseCanceled).hasOwnProperty('cancel');
});
});
** EDIT **
From what I can find out, this "ReferenceError" seems to be a babel error that is caused because babel can't seem to figure out what "document" is. I traced down where the issue is happening and it is within a third party plugin so I left a note on the developer's github page in the mean time. Currently my "solution" is to comment this test out - I'll put more effort into this again when I have time to find a proper solution
** EDIT 2 **
If I add <rootDir>/node_modules/react-json-view/dist/main.js to babel config's transformIgnorePatterns then I get a different error of
ReferenceError: regeneratorRuntime is not defined
Which is odd because I explicitly have import "regenerator-runtime/runtime" at the top. This seems to be a step closer but I'm not sure. I switched back to babel-polyfill (deprecated) just to try it but ended with a different error of TypeError: jest: failed to cache transform results.
Normally you can do something like this answer which is to add:
npm test --env=jsdom
But since I also need Puppeteer's environment there's a clash because node only seems to support ONE environment.
Ultimately I removed the troubled plugin.

Is it possible to disable specific React warnings from Jest (using Create React App)

Recently, React started giving depreciation warnings for componentWillReceiveProps lifecycle method. I am using a library that utilized this function and the maintainers haven't updated their codebase yet.
Currently, any time I run my tests, whether it is in development or in CI, I keep getting ~30 lines of depreciation warnings for each component that the maintainer provides.
Is there a way to suppress these warnings (at least in development)?
EDIT:
I am willing to add certain comments in my files to disable warnings from a specific package if there is a chance:
// some line to disable warnings for this package
import { DateRangePicker } from 'react-dates';
If you want to disable all warnings that meet some condition, keeping all other warnings, for all tests:
const originalWarn = console.warn.bind(console.warn)
beforeAll(() => {
console.warn = (msg) =>
!msg.toString().includes('componentWillReceiveProps') && originalWarn(msg)
})
afterAll(() => {
console.warn = originalWarn
})
React codebase also contains expect(render(...)).toWarnDev(...), but that's not included in Jest documentation, you might need to investigate more if you want to use that feature.
Similar in concept to a previous answer, but a bit easier would be:
jest.spyOn(global.console, 'warn').mockImplementationOnce((message) => {
if (!message.includes('componentWillReceiveProps')) {
global.console.warn(message);
}
});
If you wanted to do it across tests you could do:
let consoleSpy;
beforeAll(() => {
consoleSpy = jest.spyOn(global.console, 'warn').mockImplementation((message) => {
// same implementation as above
});
afterAll(() => consoleSpy.mockRestore());
A variation on #Aprillion's answer...
I wanted to suppress certain error messages in all tests (in a create-react-app application).
I added this to my setupTests.js:
// This error is a bug fixed in React 18: https://github.com/facebook/react/pull/22114.
// Suppress it for all tests.
const BOGUS_UNMOUNTED_ERROR = (
"Can't perform a React state update on an unmounted component."
);
const originalError = console.error.bind(console.error);
console.error = (...args) => !args.toString().includes(BOGUS_UNMOUNTED_ERROR)
&& originalError(...args);
I guess the most important difference is that I replaced (msg) with (...args) in two places, so that all arguments to console.error() were passed through. Without this, I was getting %s's in my console error messages that should have been filled in with other arguments.
So I found a way to fix these warnings for any library using react-codemod.
Since this library does not work inside node_modules so have to do a little hack.
Run:
yarn add -D react-codemod
open ./node_modules/react-codemod/bin/cli.js
Remove/comment line (72) > + // args.push('--ignore-pattern=/node_modules/');
Run:
./node_modules/.bin/react-codemod rename-unsafe-lifecycles
To this question answer with the path of the library, you want to fix...
“On which files or directory should the codemods be applied?”
./node_modules/react-dates/lib/** // or any library with issue
This could be a temporary fix till react-codemod support node_modules libraries.
You can also fork the library and remove the line for yourself and use it like this inside your CI pipeline to not get any warnings like this anymore.
You can just add this line above whatever is showing the warning. I have tried this in VScode so i'm not sure if it works with others. This disables whatever warning that will be displayed in the next line.
// eslint-disable-next-line

How do i mock and customise the values of a function imported from a npm package using Jest?

I use isMobileOnly from "react-device-detect" npm package in my React component say(SampleComponent.js).
I would like to customize the return value of isMobileOnly in my jest unit tests.
I have tried Jest manual mocks as mentioned in the link below:
https://jestjs.io/docs/en/manual-mocks
But it does not seem to work for me.
I have also tried:
jest's mockImplementation
jest's mockImplementationOnce
jest's spyOn
import {isMobileOnly} from 'react-device-detect;
In my jest unit tests, i would like to mock the function isMobileOnly in such a way that i should be able to customize its return value to "true". The default value is "false".
This worked for me.
In your test file, add this: import * as deviceDetect from 'react-device-detect';
then you can change things like: deviceDetect.isMobileOnly = true;
eg.
import * as deviceDetect from 'react-device-detect'; //<--important
it.only('clicking on myAccount redirects to /user/myAccount', () => {
///make sure this is before mount or shallow
deviceDetect.isMobileOnly = true; //<--important
component = mount(<Provider store={store}><Sidebar history={mockedHistory} /></Provider>);
component.find('[test-id="myAccount"]').first().simulate('click');
expect(mockedHistory.push).toHaveBeenCalledWith('/user/myAccount');
});
Finally! I figured it out myself after hours of struggle. Here is what i did:
Created __mocks__ folder in the same level as node_modules directory where the package "react-device-detect" is available. Note: smaller case is important for __mocks__.
Created a file named "react-device-detect.js" within the __mocks__ folder.
Added the following code in it:
const deviceDetect = jest.genMockFromModule('react-device-detect');
deviceDetect.isMobileOnly = true;
module.exports = deviceDetect;
Within the test file, i imported the "isMobileOnly" as i did in the original
component:
import { isMobileOnly } from 'react-device-detect';
Now, i can change value of "deviceDetect.isMobileOnly" to true or false in the
mocked file as per the unit test case's need .
For more details, refer the official documentation here https://jestjs.io/docs/en/manual-mocks
Thanks #Roman for reaching out!
I use the " import * as deviceDetect" answer, it worked but I ran into an issue because of typescript and the readonly property of isMobile.
So this solution worked for me :
Object.defineProperty(reactDeviceDetect, 'isIOS', { get: () => true });
as describe here
I hope that help!
You can possibly override the User Agent for testing purposes so react-device-detect package will identify it like You need, here's how to do that.
This topic should also be helpful.

Import multiple files in folder in React / Nextjs

I'm trying to import multiple files with a certain extension in a folder:
const allEntries = require.context('../static/blog', true, '/\.md/')
but I'm getting:
Unhandled Rejection (TypeError): __webpack_require__(...).context is not a function
I'm using Nextjs and require the files in one of the pages. Something seems off here?
Edit: I don't necessarily need to do it via require I just want to be able to import/require multiple files at once without knowing the filename or how many of the files are in a folder.
You can give the following a try in webpack:
const glob = require('glob');
const allEntries = glob.sync("../static/blog/*.md");
The glob will return an array of files. The array will contain all files with .md extension in the ../static/blog/ folder. Eventough there is a package it shouldn't be required to install the package.
Try using require context npm library.
$ npm i --save require-context
In your file:
// Load globally into all modules.
require('require-context/register')
// Load locally as a function.
var requireContext = require('require-context');
function requireAll(r) { r.keys().forEach(r); }
requireAll(requireContext('../static/blog', true, /\.md$/));
From what I understand you are very close, from the error you are using webpack's require.context
const allEntries = require.context('../static/blog', true, '/\.md/')
console.log(allEntries.keys()) // all the files found in the context
allEntries.keys().forEach(allEntries) // require them all
const imageDirectory = path.join(process.cwd(), '/public/dirname');
const imageFilenames = await fs.readdir(imageDirectory)
// Store the file names in an array and use it.
webpack.require is basically using webpack, make sure what you're using is webpack in fact

Resources