How to traverse a shallow component nested in ThemeProvider HOC? - javascript

Following up the issue on Github, I have a component Comp that when exported, is wrapped with injectSheet from reactjss. Please see the setup on codesandbox.
In a unit test, I'd like to assert that that component contains <a>, which it does (see codesandbox), but the test fails regardless:
describe("<Comp /> component", () => {
const wrapper = shallow(<Comp />);
it("should render a <a>", () => {
expect(wrapper.find('a')).to.have.length(1);
});
});
I get Error: [undefined] Please use ThemeProvider to be able to use WithTheme. So my natural (perhaps not the correct?) reaction was to wrap the component with ThemeProvider:
const wrapper = shallow(
<ThemeProvider theme={{}}>
<Comp />
</ThemeProvider>
)
Then I get AssertionError: expected { length: 0 } to have a length of 1 but got 0.
I tried a whole slew of approaches, including calling dive, find or first with an extra shallow call, but I would always end up with Please use ThemeProvider to be able to use WithTheme:
// 1. dive(), as suggested in
// https://github.com/cssinjs/react-jss/issues/30#issuecomment-268373765
expect(wrapper.dive('Comp')).to.have.length(1);
expect(wrapper.dive('Comp').find('a')).to.have.length(1);
expect(wrapper.dive().find('a')).to.have.length(1);
// 2. find() / first(), as suggested in https://github.com/airbnb/enzyme/issues/539
expect(wrapper.find(Comp).shallow().find('a')).to.have.length(1);
expect(wrapper.first().shallow().find('a')).to.have.length(1);
Any ideas here? I am a bit new to unit testing with React, so I would appreciate if someone could enlighten me on this ;)

For anyone still struggling with this, one viable approach was suggested on GitHub. Instead of testing the styled component wrapped with injectSheet HOC, you export your stand-alone component and test it in isolation
// Component.js
import React from 'react'
import injectSheet from 'react-jss'
const styles = {
color: 'burlywood'
}
// named export for unit tests
export const Component = props => <h1>Component</h1>
// default export to be used in other components
export default injectSheet(styles)(Component)
which would work for most use cases, since more often than not, you need to unit test the plain component and its logic, and not any of its associated styling. So in your unit test just do
import { Component } from './Component'
instead of (which you would do in the rest of your codebase)
import Component from './Component'

Related

Component Palette Custom Hook

I am fairly new to React and still wrapping my head around custom-hooks. I cam across a code where a custom hook was created to handle the component imports.
useComponentPalette.js
import {TodoEditor} from './components/TodoEditor'
import {TodoItem} from './components/TodoItem'
import {TodoList} from './components/TodoList'
import {CheckBox} from './components/CheckBox'
const defaultComponents = {
TodoEditor,
TodoItem,
TodoList,
CheckBox
}
export function useComponentPalette(){
return defaultComponents
}
And then in order to use the hook,
const {TodoItem, TodoList, Checkbox } = useComponentPalette()
My Question :- Does this approach provides any advantage over the regular imports in the component ? or this is an anti-pattern ?
How I usually import the components is as follows
import {TodoEditor} from './components/TodoEditor'
import {TodoItem} from './components/TodoItem'
import {TodoList} from './components/TodoList'
import {CheckBox} from './components/CheckBox'
function App(){
return(
<>
<TodoList/>
</>
)
}
It's not a good idea to use react hooks like this you can get the same result without react hook
// first file name.js
import {TodoEditor} from './components/TodoEditor'
import {TodoItem} from './components/TodoItem'
import {TodoList} from './components/TodoList'
import {CheckBox} from './components/CheckBox'
export default {
TodoEditor,
TodoItem,
TodoList,
CheckBox
}
//component file
import * as Component form 'first file name';
//<Component.TodoEditor/>
//or
import {TodoEditor} form 'first file name';
The way that I use react-hooks is for making my code more dry and increase it's readability, so react-hooks is not good fit for this kind of usage.
Hi #Sachin,
In my option, React JS use hook to manage reuse stateful logic between components. In other word, Hooks do well to encapsulating state and share logic. If you want to do some stateful logic or condition base logic with these components, then it's fine with that. But if you are using just without condition in the given components. Then, This Is useless for making the custom hook. You can do that without a custom hook in a simpler way.
Here is a simple way to do that:-
In components folder. I create index file, this is the entry point of all my exporting components
In that file. I export all my components, as you can see.
I use that components like this. It much better way. In my option.
import { Header, Footer, Sider } from "./components"
before using react custom hooks, we should be aware of the rationale behind it.
Customs hooks functionality was provided to reuse stateful logic. If logic doesn't require any state, we will use simple functions and if it is about components only there there are different patterns for making code general and scaleable.
So, there is no usage of custom hook in above case at all. For me, I would go with the following code for above scenario:
// components/index.tsx
import {Todo} from './todo'
import {CheckBox} from './components/CheckBox'
export {
Todo,
CheckBox
}
// componentns/todo/index.tsx
import {Editor} from './Editor'
import {Item} from './Item'
import {List} from './List'
const Todo = {
Editor,
Item,
List
}
export default Todo;
and usage will be like
import { Checkbox, Todo } from "components"
...
<Checkbox ... />
<Todo.List ...>
<Todo.Item ... >
</Todo.Editor ... />
</Todo.Item ... >
</Todo.List>
...
P.S Usage can be different based upon the logic of components, just giving an hint how we can patterns to serve our purpose.
Hope it helps.

How to test how many times a component was rendered in React?

I have a scenario with 2 components:
App
People
I want to test if People gets rendered 10 times inside App. So, I'm trying to test that using Jest. So far, I did this on my src/App.test.js:
import React, { Component } from "react";
import People from "./components/People";
import App from './App';
test('Total people = 10', () => {
expect(App).find(People).toHaveLength(10);
});
But I get a message saying:
TypeError: expect(...).find is not a function.
How can I test how many times a component gets rendered inside another component using React and Jest? Can anyone help me?
For testing react components first you need to render them, there are some tools for doing that, but since your reasoning in this test is to check how many times a component has been rendered inside another component, enzyme does a good job with its shallow method.
import React from "react";
import App from "./App";
import People from "./components/People";
import { shallow } from "enzyme";
it("Total people = 10", () => {
const wrapper = shallow(<App />);
expect(wrapper.find(People)).toHaveLength(10);
});
You'll need to set up enzyme in your project first, read the docs for more details.
The current trend in testing is to check for the things the user actually sees in the page, so most people is using react-testing-library, It'll be good for you to check it out
If you ever switch to react-testing-library, you might write the test something like this:
import React, { Component } from "react";
import App from './App';
import {render} from "#testing-library/react";
test('Total people = 10', async () => {
const { getAllByText } = await render(<App />);
expect(getAllByText('string_in_People')).toHaveLength(10);
});
Basically you'd use one of the library's built-in getAllBy... query methods to query for all instances of an element that appears exactly once in each instance of your <People /> component. The resulting set's length will equal the number of <People /> instances on the page.

What is the right way to use new React hook useContext?

I have some difficulties to understand the new way to use react Context API.
I have an app with a custom class Firebase. Now I want to make a hook to pass it. Before I used HOC (higher-order Component) and context.
My questions
Do I need to use HOC or it's a new way to do this?
Do I need the Context.Provider or it's new Hook?
Do I need to declare default value as a null or I can pass my Object
right from context.js
How can I use a new Hook instead of HOC in mine code?
Here is my code with some comments related to questions
// context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
export const withFirebase = Component => (props) => {
// I don't need to wrap it to the FirebaseContext.Consumer
// 1 But do I need this HOC or it's a new way?
const firebase = useContext(FirebaseContext)
return <Component {...props} firebase={firebase} />
}
ReactDOM.render(
// 2 Here I'm lost. Do I need the FirebaseContext.Provider or not?
// 3 Do I need to declare value her or I should do it in context.js as a default?
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
// App.jsx
// 4 Can I use a new Hook instead of HOC here and how?
import { withFirebase } from './components/Firebase/context'
const App = () => {
const firebase = this.props.firebase // But should be useContext(FirebaseContext) or something like this?
return(...)
}
export default withFirebase(App) // I don't need this with the Hook
Any help appreciated.
You should understand it first that, useContext is just to make use of Context and acts like a consumer and not Provider.
To answer your questions
Do I need to use HOC or it's a new way to do this?
You don't need an HOC with hooks. Hooks are meant to replace HOCs and render props pattern.
Do I need the Context.Provider or it's new Hook?
There is no hooks equivalent of Context.Provider. You have to use it as is.
Do I need to declare default value as a null or I can pass my Object
right from context.js
The default value to createContext is only used if you don't pass a value props to the Context.Provider. If you pass it the default value is ignored.
How can I use a new Hook instead of HOC in mine code?
Instead of using useContext in the component returned by HOC use it directly within the component
Sample code
/ context.js this is my hoc
// index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
App.jsx
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;
Do I need to use HOC or it's a new way to do this?
No, you don't need to use HOC as best technique.
Why?
Starting from React v7.0, you can use functional-based components.
From this version efficient is to use the the latest
technique named HOOKS, which were designed to replace class and
provide another great alternative to compose behavior into your
components.
Do I need the Context.Provider or it's new Hook?
Hook like useContext() has a relation with Context.Provider.
Context is designed to share data that can be considered “global”.
The Provider component accepts a
value prop to be passed. Every Context come with a Provider.
Context.Provider component available on the context instance is used to provide the context to its child components, no matter how deep they are.
Do I need to declare default value as a null or I can pass my Object right from context.js?
No, you don't need necessarily to declare a default value.
Example of defining the context in one corner of the codebase without defaultValue.
const CountStateContext = React.createContext() // <-- define the context without defaultValue
How can I use a new Hook instead of HOC in mine code?
index.jsx
import App from './App'
import Firebase, { FirebaseContext } from './components/Firebase'
const FirebaseContext = React.createContext(null)
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root'),
)
Root Component: App.js, where will be used data comes form context:
const App = () => {
const firebase = useContext(FirebaseContext)
return(...)
}
export default App;

How can I trigger a state change in a unit test with React DOM?

I'm using the React Test Utilities to unit test some of my code. I call renderIntoDocument to render a custom component and then use findDOMNode to test out what got rendered. The trouble I'm running into is that I'm not sure how to update the state and effectively trigger a re-render within the context of a unit test.
Here's some sample code -- pay attention to the code comment:
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-dom/test-utils';
import MyCustomComponent from '../../app/components/MyCustomComponent';
describe('My Test Suite', () => {
let component, node;
test('verify state change', () => {
const items = [{'value': '1'}];
component = TestUtils.renderIntoDocument(
<MyCustomComponent items={items} />
);
node = ReactDOM.findDOMNode(component);
expect(node.querySelector('input[type=text]').value).toEqual('1');
component.state.items = [{'value': '2'}];
// do something here to trigger a re-render?
expect(node.querySelector('input[type=text]').value).toEqual('2');
});
});
Unfortunately it seems simply changing the state variable doesn't do anything. And I can't call component.componentWillReceiveProps() because that doesn't seem to be defined.
Please note that I do want the same component to call its render function rather than replacing it with, effectively, a brand new component. The reason is because I found a bug where the component was rendering things based on this.props instead of this.state, and I want a test to show that it's always using data from the state and not from the initial values.
Enzyme from AirBnb has some great utilities for this. You'll need to install the dependencies but it's simple enough to get it configured. Then, you can simply call Enzyme's setState method on your component instance. An important note – your "component instance" in this case is a shallow rendered component. Your code would look something like this:
import React from 'react';
import MyCustomComponent from '../../app/components/MyCustomComponent';
import { shallow, configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// configure your adapter
configure({ adapter: new Adapter() });
describe('My Test Suite', () => {
test('verify state change', () => {
const items = [{'value': '1'}];
const wrapper = shallow(<MyCustomComponent items={items} />);
// find your shallow rendered component and check its value
expect(wrapper.find('input[type="text"]').value).toEqual('1');
// set the state of the component
wrapper.setState({ items: [{'value': '2'}] });
// state should be updated, make sure its value was properly set
expect(wrapper.find('input[type="text"]').value).toEqual('2');
});
});
All of this assumes that you are using state in your component properly. In your case, items appears to be passed in as a prop. If you are setting state by just copying props, you may want to rethink your strategy. In any case, this approach should be identical to how state updates in React work – you're operating on the same component instance without unmounting and remounting the component. Hope this helps.

Testing functional components with renderIntoDocument

I am learning to test React stateless components using the ReactTestUtils library. This is my simple component:
import React from 'react';
const Greeter = ({name,place}) => (
<h1>Hello,{name}. Welcome to the {place}.</h1>
);
export default Greeter;
This is my test spec, to get the renderIntoDocument working, I wrapped my Greeter component in a div as suggested here:
import {expect} from 'chai';
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import Greeter from '../Greeter';
describe('Greeter Components',() => {
it('renders correctly',() => {
var component = ReactTestUtils.renderIntoDocument(<div>
<Greeter name="Vamsi" place="Hotel California"/>
</div>);
var hasH1 = ReactTestUtils.findRenderedDOMComponentWithTag(component,'h1');
expect(hasH1).to.be.ok;
});
});
I get the error
findAllInRenderedTree(...): instance must be a composite component.
I am providing my code as jsbin here.
Since function components don't have an instance associated with them, you can't use them directly with render or renderIntoDocument. Attempting to wrap the function component is a good idea, unfortunately using a div doesn't work for a similar reason. DOM components also don't return a component instance, instead they return the underlying DOM node.
Which is all to say that you can't use the test utils function or native components as the "root" component you are rendering. Instead you will want to wrap your function components in a wrapper component that uses createClass or extends React.Component.
class Wrapper extends React.Component {
render() {
return this.props.children
}
}
let component = renderIntoDocument(<Wrapper><Greeter /></wrapper>
Gotcha's like this may be reason enough to make use of a third-party testing library like the popular enzyme, or my own take: teaspoon. Both abstract over issues like this by seamlessly wrapping and unwrapping function components for you, so you don't need to worry about what type of component you are trying to render.
Wrapping functional components in a <div> works for me. You just have to search for the component you want to test a little differently, i.e.
const props = { p1: "1" }
test('Foo renders correctly classed div', () => {
const cpt = TestUtils.renderIntoDocument(<div><Foo {...props} /></div>);
const myNode = ReactDOM.findDOMNode(cpt.childNodes[0]);
expect(myNode.className).toBe('my-class');
});
notice that you can target myNode for testing using cpt.childNodes[0]
In order to improve #monastic-panic's answer, my two cents:
You don't have to create a class for that. Do it dynamically:
import createReactClass from 'create-react-class';
// need to be a class component
const Clazz = createReactClass({
render: () => {
return <YourFunctionalComponentName {...props} />;
},
});
ReactTestUtils.renderIntoDocument(<Clazz />);

Categories