Testing react components that have JSS injected styles with enzyme - reactjs

I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!

here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example

It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.

Related

React Hook error when loading Solid webId profile

I'm trying to use Solid's react-components to load a user's profile from their webId. I'm running into a problem with useLDflex(). There problem seems to be something to do with React Hooks, but I can't figure it out. My goal is to load the user's profile when the page loads; open to making whatever changes necessary. I'm using MobX for state.
Below is the code and below below is the error in the compiler/web browser. Thank you.
Code (React/JSX/TypeScript):
import React from 'react'; // 16.14.0
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import { useLDflex } from '#solid/react'; // 1.10.0
#observer
export class Profile extends React.Component<{profileId: string}, {}> {
#observable webId = `https://${this.props.profileId}.solidcommunity.net/profile/card#me`;
#observable name = useLDflex(`[${this.webId}`)[0];
render() {
return (
<main role="Profile">
<div className="container">
webId: https://{this.props.profileId}.solidcommunity.net/profile/card#me
Name: {this.name}
</div>
</main>
)
}
}
Error:
src/components/profile/index.tsx
Line 9:24: React Hook "useLDflex" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
You cannot use React Hooks inside class component, ref here: https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both
So you need to rewrite it to functional component with Mobx, or make a higher order component and pass the props into your class component (when your class is too complex to rewrite)
With FC:
import {observer} from "mobx-react";
const Profile = observer(({ profileId }) => {
// ...
const name = useLDflex(`...`);
// ...
})
HOC
const withName = (Component) => ({ profileId }) => {
const name = useLDflex('...');
return <Component name={name} profileId={profileId} />
}
export default withName(Profile);

How can I get the native PIXI object from a react-pixi-fiber React component?

In an app wrapped with withApp from this:
import { withApp } from "react-pixi-fiber";
And some code that looks a little like this:
class Foo extends React.Component {
// ...
eventHandler(evt) {
console.log("Event target =", evt.target);
}
render() {
let comp = (<Sprite interactive texture={...} pointerup={eventHandler} {/* ... */} />);
console.log("Component =", comp);
return (comp);
}
}
Doing this, the object that is logged as the "Event target" is a native PIXI Sprite object, which gives me access to methods like getBounds(). I'd like to be able to access this same sort of data from the comp variable (which I would then store somewhere), but when I log that, the object I get is something different. It has a $$typeof: Symbol(react.element), so I presume it's just a React object. I'd like to find a way to get access to the PIXI object associated with it so that I can do use that object later for doing things like bounds checking on an interactive setup with various other elements.
Is there a way to do this? Or: How can I do bounds checking on interactivity into an object that isn't the current target of an event from e.g. pointerup, pointermove, etc.?
It's been a while, but if you're still looking to solve this, you need to use a ref on your Sprite component. This isn't specific to react-pix-fiber, just standard React behavior. Using ReactDOM the ref would give you access to the html element, with PIXI and react-pixi-fiber, it gives you the PIXI display object.
import React, { createRef, Component } from "react";
class Foo extends Component {
constructor() {
super();
this.ref = createRef();
}
eventHandler() {
console.log(this.ref.current);
}
render() {
return (
<Sprite
interactive
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
}
Besides the event handler, this.ref will be available in other lifecycle methods. Before render though, this.ref.current will be undefined.
Alternatively, you could use function components and hooks, either the useCallback hook or a combination for useRef and useEffect.
import React, { useCallback } from "react";
const Foo = () => {
const ref = useCallback(sprite => {
console.log(sprite);
}, []);
return (
<Sprite
interactive
ref={ref}
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
import React, { useEffect, useRef } from "react";
const Foo = () => {
const ref = useRef();
useEffect(() => {
console.log(ref.current);
}, []);
return (
<Sprite
interactive
ref={ref}
texture={...}
pointerup={this.eventHandler.bind(this)}
/>
);
}
I created working examples of each of these methods here (see the BunnyClass, BunnyCallback and BunnyRef files):
https://codesandbox.io/s/hardcore-maxwell-pirh3

React/Mobx: Integration Tests with Stores Injected into Child Components

We are trying to write out unit/integration tests for all of our existing React components. We are currently using React with Mobx 4, with tests written mostly with react-testing-library/jest. We did use Enzyme in some areas as well to make use of shallow rendering.
Our issue is that as we get to some of our 'pages', or container components, we are getting errors such as "MobX injector: Store 'teamStore' is not available! Make sure it is provided by some Provider"
We've done a bit of digging but couldn't find anything in our searches of similar issues for reference. We do know that this is caused by child components that have stores injected into them directly, and that are called into our container/page.
My question is: Is there any way within the testing frameworks to pass down mock stores created in our container components down to child components? Obviously if we passed the store as a prop from the parent to the child, that solves the issue, but we are trying to avoid modifying the components themselves in any way.
If the above is not possible, do we have any other options without refactoring components to pass down stores as needed rather than injecting directly into child components?
import React, { Component } from "react";
import { inject, observer } from "mobx-react";
import { Container, Grid, Segment } from "semantic-ui-react";
import ChildComp from "../../components/ChildComp";
#inject("userStore")
#observer
class ParentComponent extends Component {
render() {
return (
<Container className="parent">
<Segment basic>
<h1>Hello</h1>
<ChildComp />
</Segment>
</Container>
);
}
}
export default ParentComponent;
import React, { Component } from "react";
import { inject, observer } from "mobx-react";
import { Container, Grid, Segment } from "semantic-ui-react";
#inject("teamStore")
#observer
class ChildComp extends Component {
render() {
return (
<Segment basic>
<p>How can I help you?</p>
</Segment>
);
}
}
export default ChildComp;
Using jest you can mock parts of mobx to provide your own mock store so instead of running the real inject function you can provide your own inject function instead.
Using that custom inject function you can return a fake store (which needs to match the same interface as the original store).
If you want to pre populate the store with values by importing the mock you created (jest doesn't allow variables on the module/global scope to be used when using jest.mock)
Here is an example code that achieves this (this is untested code written right here on stackoverflow so might needs some tweaks to get right).
jest.mock('mobx-react', () => {
// get the original reference to mobx-react
const originalMobx = require.requireActual('mobx-react');
// create your fake stores, they should have the same interface as the real store
const mockStores = {
userStore: new UserStore()
};
return {
...originalMobx, // allow to import the original properties in react-mobx
// override the inject decorator to instead return the fake store as a prop
inject: (injectName) => (component) => (props) => {
// render the real component with the additional prop
return react.createElement(component, {...props, [injectName]: mockStores[injectName] })
},
mockStores // Allows access afterwards via import e.g import { mockStores } from 'mobx-react'
}
});
Once you mocked the mobx-react inject function you can reference the store to pre populate the values by:
import { mockStores } from 'mobx-react';
test('my test', () => {
mockStores.userStore.clearUsers();
// render the component here
})
There is also an alternative solution where you can just wrap the tested component with Provider from mobx-react and supply fake stores.
so the test will initialize them beforehand and pass the down the context.
e.g
test('my comp', () => {
const userStore = new UserStore();
const component = shallow(
<Provider userStore={userStore}>
<MyComponent />
</Provider>
)
});

Shallow rendering using enzyme simple test not working

I am very new to the enzyme/shallow render testing. I probably don't fully understand it yet.
Using this simplified component:
export const PlacementOption = (props) => <div/>
const UpdatingSelectField = (props) => <div/>
export class DevicePlatforms extends Component{
render(){
return <div>
<UpdatingSelectField/>
{this.props.value.device_platforms && this.props.children}
</div>
}
}
I am trying to tests DevicePlatforms. If this.props.value.device_platforms is not present children are not rendered and if it is they are rendered.
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import { DevicePlatforms } from './Placement.js';
import { PlacementOption } from './Placement.js'
describe("<DevicePlatforms/>", function() {
it("with all devices selected renders all children", function() {
const value = {
device_platforms: "mobile/desktop",
}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(2)
})
it("with no devices selected renders no children", function() {
const value = {}
const Component = <DevicePlatforms
value={value}
>
<PlacementOption/>
<PlacementOption/>
</DevicePlatforms>
const wrapper = shallow(Component)
expect(wrapper.find('PlacementOption')).toBe(0)
})
})
I have tried 'PlacementOption', PlacementOption in a find call.
All I get is a:
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 3
Expected ShallowWrapper({ many many lines of shallow wrapper content }) to be 0
errors.
I can paste the "many many lines of shallow wrapper content" if needed but i don't think it is related and my problem is somewhere else - probably around somewhere around me not knowing how to use shallow render stuff.
You're asserting that an enzyme ShallowWrapper is equal to 3 or 0. This doesn't make sense.
Instead, the ShallowWrapper that is returned from .find() has a .length property. Try using that instead.
expect(wrapper.find('PlacementOption').length).toBe(2)

How do I test React-Responsive components with Enzyme?

I am using the library React-Reponsive.
https://github.com/contra/react-responsive
I am struggling to figure out how to test components that are nested in React-Responsive Media Query Components:
export default class AppContainer extends React.Component {
render(){
return(
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
);
}
}
-
describe("AppContainer", () => {
let App;
let wrapper;
beforeEach(() => {
wrapper = mount(<Provider store={store}><AppContainer location={location} /></Provider>);
App = wrapper.find(AppContainer);
});
it('to have a <Header /> component', () => {
console.log(App.debug());
expect(App.find(Header)).to.have.length(1);
});
}
The test result:
1) AppContainer to have a <Header /> component:
AssertionError: expected { Object (component, root, ...) } to have a length of 1 but got 0
The relevant part of the output of the console.log is:
<MediaQuery minDeviceWidth={750} values={{...}} />
Indicating that Header is indeed not appearing in the render tree. However if I remove the MediaQuery and make Header a direct child of AppContainer the test passes.
I imagine this is not a bug as I'm very new to Enzyme and testing components in general. Any help or examples would be appreciated.
Please note: The other tests I have on this component are passing fine. I am confident that the imports and setup are all correct.
Issue was that Media Query is looking for window.matchMedia which with jsdom is undefined.
In this case I needed to use the Server Side Rendering implementation. However this would require a static value for width, which breaks the responsiveness.
My solution is to set a global variable on the test virtual DOM.
window.testMediaQueryValues = {width:740};
Then MediaQuery can access them if they are there:
<MediaQuery maxWidth={smallViewMaxWidth} values={window.testMediaQueryValues}>
In the case when the variable is not set, the null values are ignored and the Component renders as usual.
Big thanks to #Contra for his help and super library
What worked for me was adding a mocked react-responsive component using a __mocks__ directory. Basically create the following file in the directory structure:
-your-component
--component-using-media-query.js
--__mocks__
---react-responsive.js
Then you can mock out the MediaQuery component in the react-responsive.js file.
const MediaQuery = ({ children }) => children;
export default MediaQuery;
I was able to make it work using react-responsive v7.0.0.
Given:
<MediaQuery minDeviceWidth={750}>
<Header />
</MediaQuery>
The following works:
import { Context as ResponsiveContext } from 'react-responsive'
import { mount } from 'enzyme'
const wrappingComponent = ResponsiveContext.Provider
const wrappingComponentProps = { value: { width: 750 } }
const mountProps = { wrappingComponent, wrappingComponentProps }
const wrapper = mount(<AppContainer />, mountProps)
Try mocking the react-responsive dependency in your unit test. Working with Webpack, I'm using inject-loader for injecting test dependencies to modules. You can import your component using "inject-loader" and pass it which dependencies you want to overwrite.
Example:
import YourComponentInjector from 'inject-loader!../YourComponent.jsx';
and then
class MediaQueryDesktopMock extends React.Component {
render() {
const {minDeviceWidth, children} = this.props;
if(minDeviceWidth === 765) {
return (<div>{children}</div>)
}
return <span/>;
}
}
let YourComponentMock = YourComponentInjector({
'react-responsive': MediaQueryDesktopMock
});
you can then test for specific media queries

Resources