react-test-renderer How to Create a Component With Context? - reactjs

The Api for react-test-render says:
TestRenderer.create(element, options);
What are valid options for create?
Can I use it to set a component's context?
https://reactjs.org/docs/test-renderer.html#reference

The TestRenderer API doesn't support setting context directly - check out the create implementation here.
You can create a simple wrapper that just passes context:
import React from 'react'
import TestRenderer from 'react-test-renderer'
import PropTypes from 'prop-types'
// The example component under test
export default class WithContext extends React.Component {
static contextTypes = {
someProperty: PropTypes.any,
}
render () {
return (
<div>{ this.context.someProperty }</div>
)
}
}
describe('<WithContext>', () => {
it('renders the supplied context', () => {
const tree = TestRenderer.create(
<PassContext value={{ someProperty: 'context' }}>
<WithContext />
</PassContext>
)
tree.root.find(findInChildren(node =>
typeof node === 'string' &&
node.toLowerCase() === "context"
))
});
});
class PassContext extends React.Component {
static childContextTypes = {
someProperty: PropTypes.any,
}
getChildContext () {
return this.props.value
}
render () {
return this.props.children
}
}
// Returns a TestInstance#find() predicate that passes
// all test instance children (including text nodes) through
// the supplied predicate, and returns true if one of the
// children passes the predicate.
function findInChildren (predicate) {
return testInstance => {
const children = testInstance.children
return Array.isArray(children)
? children.some(predicate)
: predicate(children)
}
}

Related

Test failed of a Component in react using typescript

When ever I run the test it fails. I don't know what mistake I am making.
How can I test those if statements and the child component. I am using jest and enzyme for react tests.
This is my test file:
import React from "react";
import { shallow } from "enzyme";
import LaunchContainer from "./index";
import Launch from "./Launch";
describe("render LaunchContainer component", () => {
let container: any;
beforeEach(() => { container = shallow(<LaunchContainer setid={()=>{}} setsuccess={()=>{}} />) });
it("should render LaunchContainer component", () => {
expect(container.containsMatchingElement(<Launch setsuccess={()=>{}} setid={()=>{}} data={{}}/>)).toEqual(true);
});
})
The parent component for which the test is used:
import React from "react";
import { useLaunchesQuery } from "../../generated/graphql";
import Launch from './Launch';
interface Props {
setid: any;
setsuccess: any;
}
const LaunchContainer: React.FC<Props> = ({setid, setsuccess}) => {
const {data, loading, error} = useLaunchesQuery();
if (loading) {
return <div>loading...</div>
}
if (error || !data) {
return <div>error</div>
}
return <Launch setid={setid} data={data} setsuccess={setsuccess} />
}
export default LaunchContainer;
The child component to be added in test:
import React from "react";
import { LaunchesQuery } from "../../generated/graphql";
import './style.css';
interface Props {
data: LaunchesQuery;
setid: any;
setsuccess: any;
}
const Launch: React.FC<Props> = ({setid, data, setsuccess}) => {
return (
<div className="launches">
<h3>All Space X Launches</h3>
<ol className="LaunchesOL">
{!!data.launches && data.launches.map((launch, i) => !!launch &&
<li key={i} className="LaunchesItem" onClick={() => {
setid(launch.flight_number?.toString())
setsuccess(JSON.stringify(launch.launch_success))
}}>
{launch.mission_name} - {launch.launch_year} (<span className={launch.launch_success? "LaunchDetailsSuccess": launch.launch_success===null? "": "LaunchDetailsFailed"}>{JSON.stringify(launch.launch_success)}</span>)
</li>
)}
</ol>
</div>
);
}
export default Launch;
To test those if statements you should mock your useLaunchesQuery to return the loading, error and data values that hit your if statements. You can use mockImplementationOnce or mockReturnValueOnce. For example you could write
import { useLaunchesQuery } from "../../generated/graphql";
/**
* You mock your entire file /generated/graphql so it returns
* an object containing the mock of useLaunchesQuery.
* Note that every members in this file and not specified in this mock
* will not be usable.
*/
jest.mock('../../generated/graphql', () => ({
useLaunchesQuery: jest.fn(() => ({
loading: false,
error: false,
data: [/**Whatever your mocked data is like */]
}))
}))
const setid = jest.fn();
const setsuccess = jest.fn();
/**
* A good practice is to make a setup function that returns
* your tested component so so you can call it in every test
*/
function setup(props?: any) { // type of your component's props
// You pass mock functions declared in the upper scope so you can
// access it later to assert that they have been called the way they should
return <LaunchContainer setid={setid} setsuccess={setsuccess} {...props} />
// Spread the props passed in parameters so you overide with whatever you want
}
describe("render LaunchContainer component", () => {
it('should show loading indicator', () => {
/**
* Here I use mockReturnValueOnce so useLaunchesQuery is mocked to return
* this value only for the next call of it
*/
(useLaunchesQuery as jest.Mock).mockReturnValueOnce({ loading: true, error: false, data: [] })
/** Note that shallow won't render any child of LaunchContainer other than basic JSX tags (div, span, etc)
* So you better use mount instead
*/
const container = mount(setup()); // here i could pass a value for setid or setsuccess
// Put an id on your loading indicator first
expect(container.find('#loading-indicator').exists()).toBeTruthy()
/** The logic for the other if statement remains the same */
})
}

React-Router: How do I add a new component and route to the onboarding steps on a wizard?

This project I am working with has an onboarding Wizard, basically some code to deal with the step by step onboarding process almost similar to what you see here:
https://medium.com/#l_e/writing-a-wizard-in-react-8dafbce6db07
except this one supposedly has a function to convert a component or step into a route:
convertStepToRoute = step => {
const Component = StepComponents[step.component || ''];
return Component
? <Route
key={step.key}
path={`${WizardLayout.pathname}/${step.url}`}
render={this.renderRouteComponent(Component)}
/>
: null;
};
StepComponents comes from import StepComponents from '../Steps'; which is a directory with all the components, they were six now seven of them that are supposed to walk the user through the onboarding process.
And its my understanding that they are pulled from the index.js file inside of Steps/ directory similar to how there would be a root reducer file in a reducers folder to export all of them, the steps component in this case like so:
import glamorous from "glamorous";
import ThemedCard from "../../ThemedCard";
import BusinessAddress from "./BusinessAddress";
import CreatePassword from "./CreatePassword";
import GetInvolved from "./GetInvolved";
import Representatives from "./Representatives";
import Topics from "./Topics";
import MemberBenefits from "./MemberBenefits";
export const StepHeader = glamorous.div({
marginBottom: 20,
marginTop: 20,
fontSize: "2rem",
color: "#757575"
});
const OnboardingCompleted = glamorous(ThemedCard)({
textAlign: "center",
boxShadow: "none !important"
});
export default {
CreatePassword,
BusinessAddress,
Completed: OnboardingCompleted,
GetInvolved,
MemberBenefits,
Topics,
Representatives
};
Well, I added mine MemberBenefits and it does not seem to work, its not rendering with its corresponding route. Where could it not be registering this new step or component?
Okay so the magic is not happening inside of Onboarding/OnBoardingWizard/index.js, its happening inside of Wizard/WizardEngine.js:
import React from "react";
import PropTypes from "prop-types";
import objectToArray from "../../../../common/utils/object-to-array";
// TODO: figure out how to use this without making children of wizard engine tied to wizardStep
// eslint-disable-next-line no-unused-vars
class WizardStep {
constructor({ component, color, order, render }, stepComponents) {
if (!component || !render) {
throw new Error("Component or render must be provided.");
}
let componentValue;
if (component) {
componentValue = this.resolveComponent(component, stepComponents);
if (!!componentValue && !React.isValidElement(componentValue)) {
throw new Error(
"wizard step expected component to be a valid react element"
);
}
} else if (render && typeof render === "function") {
throw new Error("wizard step expected render to be a function");
}
this.Component = componentValue;
this.color = color;
this.order = order;
this.render = render;
}
resolveComponent = (component, stepComponents) => {
const componentValue = component;
if (typeof component === "string") {
const componentValue = stepComponents[component];
if (!componentValue) {
throw new Error("component doesnt exist");
}
}
return componentValue;
};
}
export default class WizardEngine extends React.Component {
static propTypes = {
steps: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
initActiveIndex: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
stepComponents: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
activeIndex: this.resolveInitActiveIndex(props),
steps: this.buildStepsFromConfig(props)
};
}
componentWillReceiveProps(nextProps) {
this.setState({ steps: this.buildStepsFromConfig(nextProps) });
}
resolveInitActiveIndex = props => {
const { initActiveIndex } = props;
let activeIndex = 0;
if (typeof initActiveIndex === "function") {
activeIndex = initActiveIndex(props);
}
if (typeof initActiveIndex === "number") {
activeIndex = initActiveIndex;
}
return activeIndex;
};
buildStepsFromConfig = props => {
const { steps } = props;
let stepArr = steps;
// validate stepList
if (typeof steps === "object" && !Array.isArray(steps)) {
stepArr = objectToArray(steps);
}
if (!Array.isArray(stepArr)) {
throw new Error(
`Unsupported Parameter: Wizard Engine(steps) expected either (object, array); got ${typeof stepArr}`
);
}
return stepArr;
// return stepArr.map(step => new WizardStep(step));
};
setActiveIndex = activeIndex => {
this.setState({ activeIndex });
};
goForward = () => {
this.setState(prevState => ({
activeIndex: prevState.activeIndex + 1
}));
};
goBack = () => {
this.setState(prevState => ({
activeIndex: prevState.activeIndex - 1
}));
};
render() {
const { children } = this.props;
const childProps = {
...this.state,
setActiveIndex: this.setActiveIndex,
goForward: this.goForward,
goBack: this.goBack,
currentStep: this.state.steps[this.state.activeIndex]
};
if (Array.isArray(children)) {
return (
<div>
{children.map((child, i) => {
if (typeof child === "function") {
return child(childProps);
}
childProps.key = `${child.type.name}_${i}`;
return React.cloneElement(child, childProps);
})}
</div>
);
}
if (typeof children === "function") {
return children(childProps);
}
return children;
}
}
I think the first method load the element only when it needed.
The second method load all methods everytime. Why to load Home when you are in /Products?
The path URL is being mapped on the backend utilizing the Entity Framework similar to the setup you can view here in this documentation:
https://dzone.com/articles/aspnet-core-crud-with-reactjs-and-entity-framework
except it is being done in Express.
So it's not using React-Router in the traditional sense where Express allows it to control the whole mapping route paths to components, but instead the path to the onboarding component is being mapped here inside the Express src/app-server/apiConfig.js like so:
"get-involved-onboarding": {
title: "Get Involved",
url: "/account/onboarding/get-involved",
icon: "explore",
component: "GetInvolved",
progress: {
stepType: "GetInvolved",
hasCompleted: true
}
},

Pass state value to component

I am really new in React.js. I wanna pass a state (that i set from api data before) to a component so value of selectable list can dynamically fill from my api data. Here is my code for fetching data :
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
From that code, i set a state called item. And i want to pass this state to a component. Here is my code :
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
But i get an error that say
TypeError: Cannot read property 'item' of undefined
I am sorry for my bad explanation. But if you get my point, i am really looking forward for your solution.
Here is my full code for additional info :
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {List, ListItem, makeSelectable} from 'material-ui/List';
import Subheader from 'material-ui/Subheader';
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.getListSiswa();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
export default ListSiswa;
One way to do it is by having the state defined in the parent component instead and pass it down to the child via props:
let SelectableList = makeSelectable(List);
function wrapState(ComposedComponent) {
return class SelectableList extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
componentWillMount() {
this.setState({
selectedIndex: this.props.defaultValue,
});
this.props.fetchItem();
}
handleRequestChange = (event, index) => {
this.setState({
selectedIndex: index,
});
};
render() {
console.log(this.state.item);
return (
<ComposedComponent
value={this.state.selectedIndex}
onChange={this.handleRequestChange}
>
{this.props.children}
{this.props.item}
</ComposedComponent>
);
}
};
}
SelectableList = wrapState(SelectableList);
class ListSiswa extends Component {
state = {
item: {}
}
getListSiswa(){
fetch('http://localhost/assessment-app/adminpg/api/v1/Siswa/')
.then(posts => {
return posts.json();
}).then(data => {
let item = data.posts.map((itm) => {
return(
<div key={itm.siswa_id}>
<ListItem
value={itm.siswa_id}
primaryText={itm.nama}
/>
</div>
)
});
this.setState({item: item});
});
}
render() {
return (
<SelectableList item={this.state.item} fetchItem={this.getListSiswa}>
<Subheader>Daftar Siswa</Subheader>
</SelectableList>
);
}
}
export default ListSiswa;
Notice that in wrapState now I'm accessing the state using this.props.item and this.props.fetchItem. This practice is also known as prop drilling in React and it will be an issue once your app scales and multiple nested components. For scaling up you might want to consider using Redux or the Context API. Hope that helps!
The error is in this component.
const ListSiswa = () => (
<SelectableList>
<Subheader>Daftar Siswa</Subheader>
{this.state.item}
</SelectableList>
);
This component is referred as Stateless Functional Components (Read)
It is simply a pure function which receives some data and returns the jsx.
you do not have the access this here.

Enzyme find node created with createContainer (React.PureComponent)

I have a wrapper component similar to the following:
/* Some imports here */
class Wrapper extends React.Component {
renderChildren() {
const { children } = this.props;
return children.map(child =>
<Child key={child._id} />
);
}
render() {
return (
<div>
{this.renderChildren()}
</div>
);
}
}
Wrapper.propTypes = {
children: React.PropTypes.array,
}
export default createContainer(() => {
let children = [];
const subscriptions = [
Meteor.subscribe('Collection.all'),
/* ... */
];
if (subscriptions.every(sub => sub.ready()) {
/* Some irrelevant code here */
children = Collection.find({}).fetch();
}
return { children };
}, Wrapper);
And the Child component is similar to:
/* Some imports here */
class Child extends React.Component {
render () {
return (
<div></div>
);
}
}
export default createContainer(() => {
/* Some irrelevant code here that has to stay in createContainer (and not constructor)*/
}, Child);
I am attempting to write a test that, whenever a Document is inserted to the Collection, I compare if the length (amount) of Child components in Wrapper is incremented correctly. Before, when I did not need to use createContainer in my child element, the following:
it('should render with some children and reactively update with them', () => {
let children = [];
_.times(4, () => children.push(Factory.create('doc'));
const wrapper = mount(
<Wrapper />
);
chai.assert.notEqual(wrapper.find('Child').length, children.length);
wrapper.setProps({ children });
chai.assert.equal(wrapper.find('Child').length, children.length);
}
Did work as intended but as I added createContainer for the child element also the previously worked test(s) started to fail. It appears that enzyme's find('Child') function does not return any of these nodes anymore. Any suggestions are appreciated.

function argument in component's constructor in react.js

When I look at the following line in this example:
const SortableItem = SortableElement(({value}) => <li>{value}</li>);
then I don't understand where is the lambda function ({value}) => <li>{value}</li> used in SortableElement ?
Can someone please enlighten me ?
SortableElement's code:
import React, {Component, PropTypes} from 'react';
import {findDOMNode} from 'react-dom';
import invariant from 'invariant';
// Export Higher Order Sortable Element Component
export default function SortableElement (WrappedComponent, config = {withRef: false}) {
return class extends Component {
static displayName = (WrappedComponent.displayName) ? `SortableElement(${WrappedComponent.displayName})` : 'SortableElement';
static WrappedComponent = WrappedComponent;
static contextTypes = {
manager: PropTypes.object.isRequired
};
static propTypes = {
index: PropTypes.number.isRequired,
collection: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
disabled: PropTypes.bool
};
static defaultProps = {
collection: 0
};
componentDidMount() {
let {collection, disabled, index} = this.props;
if (!disabled) {
this.setDraggable(collection, index);
}
}
componentWillReceiveProps(nextProps) {
const {index} = this.props;
if (index !== nextProps.index && this.node) {
this.node.sortableInfo.index = nextProps.index;
}
if (this.props.disabled !== nextProps.disabled)
{
let {collection, disabled, index} = nextProps;
if (disabled) {
this.removeDraggable(collection);
}
else {
this.setDraggable(collection, index);
}
}
}
componentWillUnmount() {
let {collection, disabled} = this.props;
if (!disabled) this.removeDraggable(collection);
}
setDraggable(collection, index){
let node = this.node = findDOMNode(this);
node.sortableInfo = {index, collection};
this.ref = {node};
this.context.manager.add(collection, this.ref);
}
removeDraggable(collection) {
this.context.manager.remove(collection, this.ref);
}
getWrappedInstance() {
invariant(config.withRef, 'To access the wrapped instance, you need to pass in {withRef: true} as the second argument of the SortableElement() call');
return this.refs.wrappedInstance;
}
render() {
const ref = (config.withRef) ? 'wrappedInstance' : null;
return (
<WrappedComponent ref={ref} {...this.props} />
);
}
}
}
export default function SortableElement (WrappedComponent, config = {withRef: false}) {
return class extends Component {
...
render() {
const ref = (config.withRef) ? 'wrappedInstance' : null;
return (
<WrappedComponent ref={ref} {...this.props} />
);
}
}
}
Look at the render() method in the returned React Component by the higher order SortableElement function, the lambda function (which is a stateless component) is passed as the first argument to the higher order function, and this first argument is going to end up as being the parameter WrappedComponent you see in the signature of this higher order function.
So this higher order function is going to spit a React component with a render() method that uses/calls your actual React component that you just passed in (the lambda function).
<WrappedComponent ref={ref} {...this.props} />
Thanks to ({value}) => <li>{value}</li> in
const SortableItem = SortableElement(({value}) => <li>{value}</li>);
we will be actually rendering an li element with a value passed as a prop from the map method below.
const SortableList = SortableContainer(({items}) => {
return (
<ul>
{items.map((value, index) =>
<SortableItem key={`item-${index}`} index={index} value={value} />
)}
</ul>
);
});
In the context of the SortableElement's API code the important thing is that it renders the WrappedComponent (lines 67-69). We can treat SortableElement as any other Higher Order Component - a component that wraps another component to deliver some extra functionality. In this case - a fancy sorting animation of the lambda function.
Put simply
({value}) => <li>{value}</li> is a short hand for
React.crateClass({
render:function(){
return <li>{this.props.value}</li>
}
})
refer to pure functional component in React
and SortableElement is a higher order component wrapping another React component ,such the functional component above

Resources