React trying to attachEvents under JSDom - reactjs

I am trying to test my React (for the browser) code in what I think is an entirely conventional way: Mocha as the test-runner, JSDom to simulate the browser, Enzyme to check the results.
My problem is this: whenever I manually set the focus on a component, React throws an exception. The problem is deep inside React:
activeElement.attachEvent('onpropertychange', handlePropertyChange);
The active element is set, but as a JSDom HTMLInputElement, it does not have an attachEvent. I have found if I hacked the file node_modules/jsdom/lib/jsdom/living/generated/HTMLInputElement.js to give that class empty methods named attachEvent and detachEvent, the exception goes away.
But clearly, that is not the right solution.
The comments on the function, and some fragmentary information I have found elsewhere, suggests this is a shim meant for antique versions of IE, not JSDom at all. The function involved, startWatchingForValueChange is only invoked if the flag isInputEventSupported is not set, and setting it requires another canUseDOM to be set. Forcibly setting either of those flags causes other problems.

Typing up this question, I figured it out.
As the testing system was set up, when I initialized JSDom, I also set a global afterEach() to reset the contents of the DOM after each test. This did not directly create a problem, but it did mean the order of initialization was necessarily like this:
React
Mocha
JSDom
So when React was initialized, it looked around and saw no working DOM, and figured, "Damn, I must be running on IE or something." After that, it was all down-hill.
So I confidently restructured it like this:
JSDom
React
Mocha
And that... did nothing.
Then I realized the problem was, I was doing this:
import { JSDOM } from "jsdom";
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
global.jsdom = initDom();
...
React was actually initializing itself when it was imported — by the import of Enzyme!
So I confidently rewrote it like this:
import { JSDOM } from "jsdom";
global.jsdom = initDom();
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
And that... did nothing.
Because the import statement effectively gets hoisted to the top of the file.
So I confidently rewrote it like this:
import { JSDOM } from "jsdom";
let jsdom = new JSDOM("");
...
const Enzyme = require("enzyme");
const Adapter = require("enzyme-adapter-react-16");
And that... fixed it.

Related

Mocking React-Redux Store With Jest

Our React-Redux App uses create-react-app. We’re trying to back-apply Jest/Enzyme/redux-mock-store to a large code base. Our src/store.js file is the standard react-redux script that runs the reducers and constructs an initial state. This src/store.js is central to the problem.
Problem is that when we start the jest test-run, jest loads src/store.js 7 times, and then (roughly) once for every test. I must suppress this and can’t figure out how. The reason I must suppress it is that it always leaves the store in its initial state and there is one crucial AppConfig property in that store which must be populated to prevent javascript errors that stop jest. I already have a function which returns a mock store for use with the Provider. That works well. But our components call out from constructor(), componentDidMount(), etc to api functions. Those api functions don’t see the state dropped from Provider. Instead, the api functions do stuff like this:
// AppConfig is an immutable Map
const { AppConfig } = store.getState();
const stuff = AppConfig.get(‘stuff’).toJS();
That’s a JS error because AppConfig is empty and toJS() is called on undefined. (That can never be a problem in run-time because we construct no component without AppConfg being ready.) There are 2 solutions involving refactoring the App that I can do. But I’d rather figure out how to properly use jest. How can I supply the mock redux state without it getting clobbered by jest's repeated loads of src/store.js???
This is killing me. What in Jest, create-react-app, or ??? initiates these unwanted loads of src/store.js? How to override? Is there a config that causes jest to call my function instead of src/store.js?
I figured out my own problem. Will post here in case someone else that is new with jest may benefit from my folly.
Jest does not load src/store.js. The culprit is my shallow component tests because the components have statements like this:
import StuffService from '../../api/stuff';
The calls to the api from the component look like this:
// e.g. from componentDidMount
StuffService.get.myRequest();
But in the es6 module that defines StuffService, at the top we have something like:
import store from '../../store';
// lower in the js file, we have code as shown in my previous post:
const { AppConfig } = store.getState();
That's what is causing src/store to keep running. Will study Jest's Manual Mocks for a solution.

Do you really need to import 'React' when creating hooks? (React-hooks)

I saw the examples where https://reactjs.org/docs/hooks-custom.html they always do:
import React, { useState, useEffect } from 'react';
But React is not really used in the file, do we really need it and why?
I asked this question because I am encountering an issue with eslint saying:
'React' is defined but never used no-unused-vars - And I'm on create-react-app 3.0.1 which eslint is already included - (and I'm not sure how to fix this - already tried this and also tried adding it on package.json eslintConfig but still nothing)
You will need React if you are rendering JSX.
To avoid that eslint warning, you should use react-in-jsx-scope rule from eslint-plugin-react.
In that rule, it also explains why you need React in the file, even if you don't use it (you think you don't use it, but if you render JSX, you do).
When using JSX, <a /> expands to React.createElement("a"). Therefore the React variable must be in scope.
If you are using the #jsx pragma this rule will check the designated variable and not the React one.
React 17 has a new JSX transform which no longer requires the import (also backported to new versions 16.14.0, 15.7.0, and 0.14.10). You can read more about it here:
https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
// no import needed
const App = () => <div>hello!</div>
However, you still need to import to use hooks:
import { useState } from 'react'
const App = () => {
const [stuff, setStuff] = useState('stuff')
return <div>{stuff}</div>
}
The docs also link to a script to automatically update all of the files in a project to fix all of the imports. Personally, I was in the habit of just using the React.useWhatever form so I never needed to mess with the import statement, but using named imports can potentially reduce the final bundle size.
The docs say the named import is now the recommended way, so this is NOT recommended, but if you really want to keep the React import, you can set the below eslint rule to stop it from complaining. Note that this will continue to require it in all files.
"react/jsx-uses-react": "error"
From the React official docs:
Fundamentally, JSX just provides syntactic sugar for the
React.createElement(component, props, ...children) function. The JSX
code:
<MyButton color="blue" shadowSize={2}>Click Me</MyButton>
compiles
into:
React.createElement(MyButton, {color: 'blue', shadowSize: 2},'Click Me' )
You can also use the self-closing form of the tag if
there are no children. So:
<div className="sidebar" />
compiles into:
React.createElement('div', {className: 'sidebar'}, null )
https://reactjs.org/docs/jsx-in-depth.html
EDIT Hooks are also under the React namespace, React.useState ...etc

why it is no need to import something when use Jest?

I'm new to React, still struggling to understand some basics, sorry if my question seems to be weird. We know we need to import modules as
import sth from 'sth';
so when we use Jest, don't we need to do as:
import { test, expect...} from 'Jest';
As #nahanil points out, Jest puts the methods you need in the global scope of your NodeJS runtime. If you put a console.log(global) in your file when running jest, you will see the methods are hooked onto the global scope. Other libraries such as assert does not follow the same convention, and you will need to import the assertions you need.
That happens here:
https://github.com/facebook/jest/blob/160d27ae9b6728dccf268f8a98351bcf82a7d9e1/packages/jest-environment-node/src/index.ts#L21
As explained in the first section in official documentation api
📚 :
In your test files, Jest puts each of these methods and objects into the global environment. You don't have to require or import anything to use them. However, if you prefer explicit imports, you can do import {describe, expect, test} from '#jest/globals'.
import {describe, expect, test} from '#jest/globals'

How to correctly mock i18n for Jest

I have a container in react-native that is importing the typical kind of function that we usually store at utils directory such as capitaliseWord() or whatever.
One of the functions of that utils module uses t, so we import i18n inside that utils folder in order to be able to use t
We use languageDetector inside our i18n to detect the user's mobile language. Because languageDetector needs deviceLocale (e.g UK, US, NL, etc), and Jest runs on a different context, if I try to do test the container, I will get Cannot read property 'deviceLocale' of undefined.
So I created a manual __mocks__ directory (as https://jestjs.io/docs/en/manual-mocks#mocking-user-modules states) and I created my own i18n that just injects the deviceLocale string manually, in order to be able to run the tests.
Turns out the test ignores the __mocks__/i18n and points directly to the original one... Any ideas of what it could go wrong?
And my jest config inside package.json
https://gist.github.com/e00dd4ee41b06265632d3bcfe76e7cb0
original i18n.js
https://gist.github.com/pavilion/3c48c6017a82356914f0ad69d7251496
mocked i18n.js
https://gist.github.com/pavilion/17e322340581fb948ed7e319ae4a5ac9 (notice the detect key inside languageDetector has been manually mocked)
Component to be tested
https://gist.github.com/pavilion/20dc0c5b1a6d2ee709b7a71ec7b819c1
utils.js
https://gist.github.com/pavilion/1c5df0f71d50462d75234365ae1e4aaf
Comp.test.js
https://gist.github.com/pavilion/2a84880bee3a99fa51fc3e28cfa8f913
Error:
https://gist.github.com/pavilion/012ee0889ebbe2b93b2108d93543e19c
I think the problem is not in the mock, but the import! Try this time requiring the component in the test, after the mock has been injected:
import React from 'react';
import { shallow } from 'enzyme';
jest.mock('../../config/i18n');
describe('<Comp />', () => {
it('must match the snapshot', () => {
// Require here instead of importing on top
const Comp = require("./Comp").default;
// state and props properly set up
const wrapper = shallow(<Comp />);
expect(wrapper).toMatchSnapshot();
});
});
I tried this locally and it's working fine: the module gets mocked. I simplified a lot my example so perhaps you run into some new error but technically this should solve your problem.
EDIT: pushed my working example to github here in case it's for any help.

Redux + karma-webpack - cannot read displayName of undefined upon import of React.Component

I'm using karma-webpack and I am refactoring a React Component to use in multiple places. I moved the component
to it's own file, so I can import it and connect it differently by applying mapStateToProps / mapDispatchToProps in the HOC I later include on my page.
Here's the scenario:
EventTable - imports EventTableComponent, exports connected / wrapped component
MyEventTable - imports EventTableComponent, exports connected / wrapped component
EventTableComponent - defines the props / behaviors shared to render the data rows
When I git mv EventTable to EventTableComponent and refactored the code so the connected stuff is in the HOCs, the tests start failing to import EventTableComponent only in karma-webpack. Chrome works just fine and the view render perfectly. QA is happy, or would be, but my build fails.
The build is failing because WrappedComponent is undefined for the EventTable and MyEventTable components, which causes connect to throw the message in the synopsis (cannot read displayName of undefined), but I don't even import either of these files into my test! Instead, it fails while karma webpack is building, but before running any tests.
I fixed the build locally by destroying the view and making a "fake" / "replacement" like this:
function EventTableComponent () {
return (<div>garbage here</div>);
}
The build passes.
The most confusing part in all of this? I don't even test the HOC at all. I import just the EventTableComponent into the test, but karma-webpack, as suggested in the Redux Documentation.
I've written a minimal example gist to illustrate:
https://gist.github.com/zedd45/cbc981e385dc33d6920d7fcb55e7a3e0
I was able to solve this by tricking the module system.
// EventTableComponentWrapper.jsx
import EventTableComponent from './EventTableComponent';
if (process.env.NODE_ENV === 'test') {
module.exports = require('./EventTableComponent.test.jsx');
} else {
module.exports = EventTableComponent;
}
I import this file in both HOC Components, and based on my environment variable, I get the right one, and Karma Webpack is tricked into not exploding.
I arrived at this conclusion based on a few leads, but credit goes to this SO Post:
Conditional build based on environment using Webpack
and also to Wout Mertens from the Webpack Gitter channel for tipping me off to the issue being ES6 Class Import / Export related.

Resources