How to correctly mock i18n for Jest - reactjs

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.

Related

Import a file as a string (or source asset) in Gatsby / React

I want to import .ts, .tsx, .js, and .jsx files into a react component and render them within a PrismJS highlighting block. For example, let's say I have a TypeScript file with functionA in it that I want to highlight in my actual website:
functionA.ts:
export function functionA() {
console.log("I am function A!");
}
I want to include this in a different component. The problem is, when I import it, I am obviously importing the webpack module version of it. My weak attempt at trying to get my function render in a react component looks like this:
MyComponent.tsx:
import * as React from "react"
import { functionA } from "./functionA"
export function MyComponent() {
return (
<>
<h1>Here is your code block:</h1>
<pre>
<code>
{functionA.toString()}
</code>
</pre>
</>
)
}
and what will actually render on the page where the code block is will look something like this:
Here is your code block:
WEBPACK__IMPORT.functionA() {
console.log("I am function A!")
}
I can't exactly remember what the .toString() function output looked like, but the point is it is NOT just the contents of the file how it appears in a code edit for example - it has been modulized by WebPack.
So, in a Gatsby project, how can i get these various code snippets to be imported directly as a string, purely as they are written, without WebPack enacting its import stuff on it? Is there a plugin or some way to tell Webpack to use the imported file as its asset/source module type? I know for MD or MDX files there is the gatsby-remark-embed-snippet, but I am building a component based HTML page and can't use MD or MDX files!
It's very late, and perhaps I just can't see the forest from the trees, I know there must be a way to do this...
You need to require the file using webpack's raw-loader, i.e:
const functionA = require("!!raw-loader!./functionA");
This works for create-react-app, as in the solution discussed here, and this works for Gatsby as well!
After using require on such a file, the file contents can be rendered in the component as:
<pre>{functionA.default.toString()}</pre>
It's then up to you to add syntax highlighting using a tool like prism or similar.
Note this solution will only work as long as Gatsby V3 continues to use WebPack v4, as raw-loader is deprecated in WebPack v5 and will be phased out for asset/source type modules.

Using a hook to retrieve a context imported from an imported module returns undefined

Happy Friday.
Let me see if I can lay out the problem clearly here:
I build up a bunch of providers in a 'core' package project-core to be shared across a bunch of related projects, and wrap them all in a super provider ProjAppProvider, which I then wrap around my related project, call it Proj1.
So, super simplified to make the issue clear, let's just use material-ui's ThemeProvider as an example (but, in reality, I have apollo-client, notistack, an auth provider, etc.):
In core...
import * as React from 'react'
import theme from '../theme'
import {ThemeProvider} from '#material-ui/core'
const ProjAppProvider = ({children}) => {
return(
<ThemeProvider theme={theme}>
{children}
<ThemeProvider>
}
}
export default ProjAppProvider
and then export that in the index and publish that package.
Then in project-one's package.json I import project-core and then do something like this:
import...
import ProjAppProvider from 'project-core'
const WrappedApp = () => {
return (
<ProjAppProvider>
<ProjOneComponent />
</ProjAppProvider>
)
}
and then in ProjOneComponent:
...
import {useTheme} from '#material-ui/core'
const ProjOneComponent = () => {
const theme = useTheme()
console.log(theme)
}
I get the default theme. If this were notistack, I'd get undefined. Just happens that MUI has a fallback.
Some provisos to stick on here... if at any point in project-core, I use that hook (inside the provider), it works as expected. Also, it appears that if I actually export the hook itself from project-core and use that (i.e., projUseTheme is just exported useTheme), it works.
Everything else "works". It's definitely seeing the core package, importing the right version, etc. etc. Shared components work as expected. Same versions of #material-ui/notistack in both. The theme is available anywhere in the core package-- meaning in nested providers, I can read it correctly. It's something about importing a provider, wrapping it, and then using the use... hook from the original package to retrieve the context that isn't working (or, more likely, I'm not understanding.)
Also, it appears, though I haven't had the time to bisect it, that this started when we upgraded to React 17 and/or CRA 4. Not sure about that. I'll be working on that debugging when I get time.
Any input on what to look for, and/or, if I'm just missing something about why this would be expected is appreciated.
Update:
Seems that I have to export the useTheme and makeStyles from the core package, and that'll make it work. (I.e., import useTheme as coreUseTheme from '#mui' and then export const coreUseTheme in core... and import {coreUseTheme as useTheme} in project-one.
The issue seems to be that the two referencing and external provider conflict somehow? Even if I get the theme from coreUseTheme and log it, it's right. But if I don't import makeStyles from core, and pass it that theme, it will just ignore it and use the default. Which... kinda blows my mind if I'm being really honest.

React trying to attachEvents under JSDom

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.

how to add external library to reactjs?

what are the various options to add an external JS library to reactjs component? for example, if we have any library file such as example.js and it is used by only one react component then i would like to either add this library at index.html or at the component level. I would like to know how to include at component level.
Hosted Client-Side Libraries
If you want to import a client-side hosted library your only option is to include it in index.html
For instance, if we wanted to include D3 in one of our components, we could add <script src="https://d3js.org/d3.v5.min.js"></script> to /public/index.html
Then to use it from inside your component, you need to call window.d3 instead of just d3
Non-Hosted client-side libraries
If you have a library that is not hosted, you could always save it to it's own file. once you do that, simply import it like you would any other local file import some_library from "./path/to/some/library"
By "external libraries" I'm assuming you're talking about npm/yarn repository libraries, for example:
$ npm install moment (Moment.js)
In those cases, you want to import it as:
import moment from 'moment'
Another example from the same package can be seen here
Easiest way is to find out whether your external library has an npm module, if so you can simply do an npm install.
Else you can add the link to this library in your index.html.
Otherwise you can download the files of your external library and use it inside as if its your own code(better checkout whether its free library to do that). In this case you can simply import the file
You can create a component that inserts the script to the header of your site. For example:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
const ExternalLibrary = () => {
useEffect(()=>{
if (typeof window !== "undefined"){
// Make sure it only gets included once.
if (!document.getElementById("externalLibrary")){
const script = document.createElement('script');
script.id='externalLibrary'
script.type = 'text/javascript';
script.src = `https://externallibrary.com/example.js`;
// Call some function from the library when it loads.
script.onload = function() {
window.someFunction()
};
document.getElementsByTagName("head")[0].appendChild(script);
}
}
}, []);
return null;
}
export default ExternalLibrary;
Then you can just include this in your App.js, or other components.
You can use like that :
import abc from './abc.js'
Check here : How to include external javascript library in reactjs

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