as mentioned in the title I'm trying to set up some test for <Search /> component, in particular I want to test the useState hooks.
After mocking the Redux store and creating a shallow wrapper I tried to simulate an input from the child component DisplaySearchBar but apparently I cannot even mamage to select it.
That's the error I get:
Method “props” is meant to be run on 1 node. 0 found instead.
Here's Search.js
import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { handleScriptLoad } from '../../../helpers/Autocomplete';
import { getRestaurants, setAlert } from '../../../actions/restaurantAction';
import DisplaySearchBar from '../../layout/DisplaySearchBar/DisplaySearchBar';
import styles from './Search.module.scss';
const Search = ({ getRestaurants, setAlert }) => {
const [where, setWhere] = useState('');
const [what, setWhat] = useState('');
const [sortBy, setSortBy] = useState('rating');
const sortByOptions = {
'Highest Rated': 'rating',
'Best Match': 'best_match',
'Most Reviewed': 'review_count',
};
// give active class to option selected
const getSortByClass = (sortByOption) => {
if (sortBy === sortByOption) {
return styles.active;
} else {
return '';
}
};
// set the state of a sorting option
const handleSortByChange = (sortByOption) => {
setSortBy(sortByOption);
};
//handle input changes
const handleChange = (e) => {
if (e.target.name === 'what') {
setWhat(e.target.value);
} else if (e.target.name === 'where') {
setWhere(e.target.value);
}
};
const onSubmit = (e) => {
e.preventDefault();
if (where && what) {
getRestaurants({ where, what, sortBy });
setWhere('');
setWhat('');
setSortBy('best_match');
} else {
setAlert('Please fill all the inputs');
}
};
// displays sort options
const renderSortByOptions = () => {
return Object.keys(sortByOptions).map((sortByOption) => {
let sortByOptionValue = sortByOptions[sortByOption];
return (
<li
className={getSortByClass(sortByOptionValue)}
key={sortByOptionValue}
onClick={() => handleSortByChange(sortByOptionValue)}
>
{sortByOption}
</li>
);
});
};
return (
<DisplaySearchBar
onSubmit={onSubmit}
handleChange={handleChange}
renderSortByOptions={renderSortByOptions}
where={where}
what={what}
handleScriptLoad={handleScriptLoad}
/>
);
};
Search.propTypes = {
getRestaurants: PropTypes.func.isRequired,
setAlert: PropTypes.func.isRequired,
};
export default connect(null, { getRestaurants, setAlert })(Search);
DisplaySearchBar.js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { clearSearch } from '../../../actions/restaurantAction';
//Import React Script Libraray to load Google object
import Script from 'react-load-script';
import Fade from 'react-reveal/Fade';
import Alert from '../Alert/Alert';
import styles from './DisplaySearchBar.module.scss';
const DisplaySearchBar = ({
renderSortByOptions,
onSubmit,
where,
handleChange,
what,
handleScriptLoad,
restaurants,
clearSearch,
}) => {
const googleUrl = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_API_KEY}&libraries=places`;
// {googleUrl && <Script url={googleUrl} onLoad={handleScriptLoad} />}
return (
<section className={styles.searchBar}>
<form onSubmit={onSubmit} className={styles.searchBarForm}>
<legend className="title">
<Fade left>
<h1>Where are you going to eat tonight?</h1>
</Fade>
</legend>
<Fade>
<fieldset className={styles.searchBarInput}>
<input
type="text"
name="where"
placeholder="Where do you want to eat?"
value={where}
onChange={handleChange}
id="autocomplete"
/>
<input
type="text"
name="what"
placeholder="What do you want to eat?"
onChange={handleChange}
value={what}
/>
<div className={styles.alertHolder}>
<Alert />
</div>
</fieldset>
<fieldset className={styles.searchBarSubmit}>
<input
id="mainSubmit"
className={`${styles.myButton} button`}
type="submit"
name="submit"
value="Search"
></input>
{restaurants.length > 0 && (
<button
className={`${styles.clearButton} button`}
onClick={clearSearch}
>
Clear
</button>
)}
</fieldset>
</Fade>
</form>
<article className={styles.searchBarSortOptions}>
<Fade>
<ul>{renderSortByOptions()}</ul>
</Fade>
</article>
</section>
);
};
DisplaySearchBar.propTypes = {
renderSortByOptions: PropTypes.func.isRequired,
where: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
what: PropTypes.string.isRequired,
handleScriptLoad: PropTypes.func.isRequired,
restaurants: PropTypes.array.isRequired,
clearSearch: PropTypes.func.isRequired,
};
const mapStatetoProps = (state) => ({
restaurants: state.restaurants.restaurants,
});
export default connect(mapStatetoProps, { clearSearch })(DisplaySearchBar);
And Search.test.js
import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import Search from '../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';
const mockStore = configureStore();
const initialState = {
restaurants: { restaurants: ['foo'], alert: null },
};
describe('Search', () => {
test('renders withut errors', () => {
const store = mockStore(initialState);
const wrapper = mount(
<Provider store={store}>
<Search setAlert={jest.fn()} getRestaurants={jest.fn()} />
</Provider>
);
wrapper.find(DisplaySearchBar).props();
});
});
Thanks for your help!
shallow doesn't work for react-redux new versions (>= 6).
Use mount instead:
const wrapper = mount( // <-- changed shallow to mount.
<Provider store={store}>
<Search {...props} />
</Provider>
);
Run It On Sandbox (Use tests tab to run tests.)
Try to mount it like this:
const wrapper = shallow(
<Provider store={store} />
<Search setAlert=jest.fn() getRestaurants=jest.fn() />
</Provider>
);
Related
I want to share Child Component (Product.js) data into Parent Component (App.js) without button click. I will use useState with context API. I don't know how to passed this data without click event.
Product.js data display into console.log() into App.js component.
App.js
import React from 'react';
import Product from './Product';
function App() {
return (
<div className="App">
<Product />
</div>
);
}
export default App;
Product.js
import React from 'react';
const Product = () => {
return (
<div>
Product Name: <input type="text" />
<br />
Description:- <input type="text" />
</div>
)
}
export default Product
Please help I have no advance knowledge of react.
There are three ways to share information without click event with useContext and useState.
useContext
useState in childComponent
useState in parentComponent
1. useContext
import { createContext, useContext, useState } from 'react';
import React from 'react';
const Context = createContext();
const Product = () => {
const [info, setInfo] = useContext(Context);
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => {
setInfo({ ...info, name: e.target.value });
}} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => {
setInfo({ ...info, desc: e.target.value });
}} />
</div>
);
}
function App() {
const [info, setInfo] = useState({
name: '',
desc: ''
});
return (
<div className="App">
<Context.Provider value={[info, setInfo]}>
<Product />
</Context.Provider>
</div>
);
}
export default App;
If you have each component in a file. You have to create the context in a third file and import it from the parent and child component.
2. useState in childComponent
import { useEffect, useState } from 'react';
import React from 'react';
const Product = ({ setParentInfo }) => {
const [info, setInfo] = useState({ name: '', desc: '' });
useEffect(() => {
setParentInfo(info);
}, [info])
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => setInfo({ ...info, name: e.target.value })} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => setInfo({ ...info, desc: e.target.value })} />
</div>
)
}
let info = { name: '', desc: '' }
function App() {
return (
<div className="App">
<Product setParentInfo={(newInfo) => {
info = { ...newInfo };
}} />
</div>
);
}
export default App;
3. useState in parentComponent
import { useState } from 'react';
import React from 'react';
const Product = ({ info, setParentInfo }) => {
return (
<div>
Product Name: <input type="text" value={info.name} onChange={(e) => setParentInfo({ ...info, name: e.target.value })} />
<br />
Description:- <input type="text" value={info.desc} onChange={(e) => setParentInfo({ ...info, desc: e.target.value })} />
</div>
)
}
function App() {
const [info, setInfo] = useState({ name: '', desc: '' });
console.log("parent: ", info);
return (
<div className="App">
<Product info={info} setParentInfo={(newInfo) => {
setInfo({ ...newInfo });
}} />
</div>
);
}
export default App;
I hope I've helped you. Have a nice day!
If its a direct child to parent communication, it's better to done this using props. Less code, works fine!
App.js:-
import React from 'react';
import Product from './Product';
function App() {
return (
<div className="App">
<Product
onProductNameChange={productName => console.log(productName)}
onProductDescChange={productDesc => console.log(productDesc)}
/>
</div>
);
}
export default App;
Product.js:-
import React from 'react';
const Product = ({onProductNameChange, onProductDescChange}) => {
return (
<div>
Product Name: <input type="text" onChange={e => onProductNameChange(e.target.value)} />
<br />
Description:- <input type="text" onChange={e => onProductDescChange(e.target.value)} />
</div>
)
}
export default Product
Share information without click event with useContext and useState.
App.js
import React from 'react';
import Store from './Store';
import ProductList from './ProductList';
function App() {
return (
<div className="App">
<Store>
<ProductList />
</Store>
</div>
);
}
export default App;
ProductList.js
import React, { useContext } from 'react';
import { Context } from './Store';
import Product from './Product';
import AddProduct from './AddProduct';
const ProductList = () => {
const [state, setState] = useContext(Context);
console.log(state);
return (
<>
<AddProduct />
<Product />
</>
)
}
export default ProductList
AddProduct.js
import React, { useContext} from 'react';
import { Context} from './Store';
const AddProduct = () => {
const [products, setProducts] = useContext(Context);
return (
<div>
Description:- <input type="text"
onChange={(e) => setProducts({ ...products, Description: e.target.value })}/>
</div>
)
}
export default AddProduct
Product.js
import React, { useContext} from 'react';
import { Context} from './Store';
const Product = () => {
const [products, setProducts] = useContext(Context);
return (
<>
<h2>Product</h2>
<p>Description: {products.Description}</p>
</>
)
}
export default Product
Store.js
import React, { useState } from 'react';
const product = {
Description: '',
};
export const Context = React.createContext();
const Store = ({ children}) => {
const [products, setProducts] = useState(product);
return (
<Context.Provider value={[products, setProducts]}>{children}</Context.Provider>
);
};
export default Store;
When I try to simulate a change event with Enzyme in my Material-UI component, value is not changing.
My component looks like this:
export class EditableList extends Component {
...
onChangeNewEntry = event => {
this.setState({newEntry: event.target.value});
};
render() {
const {classes} = this.props;
return (
<div className={classes.div_main}>
<List>
<Paper className={classes.paper_title} elevation={2} key="NewEntry">
<ListItem>
<InputBase
data-testid='input-base'
className={classes.inputBase}
placeholder="New Entry"
value={this.state.newEntry}
onKeyPress={(event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.onCreateItem();
}
}}
onChange={this.onChangeNewEntry}
multiline
/>
</ListItem>
</Paper>
</List>
</div>
);
}
}
My test looks like this:
import React from 'react';
import {Provider} from "react-redux";
import EditableList from "./EditableList";
import store from "../../../../store";
import {createMount, createShallow} from "#material-ui/core/test-utils";
import {InputBase} from "#material-ui/core";
let mount;
let wrapper;
beforeEach(() => {
mount = createMount();
wrapper = mount(
<Provider store={store}>
<EditableList/>
</Provider>
);
});
afterEach(() => {
mount.cleanUp();
});
const findComponent = ( value) => {
return wrapper.find(`[data-testid='${value}']`).first();
};
it('allow user to write input for new item', () => {
const inputBase = findComponent('input-base');
const value = "New item";
inputBase.simulate('change', {target: {value: value}});
wrapper.update();
console.log(inputBase.prop('value'));
});
In the console, the valueis always an empty string.
I tried also the following without success:
wrapper.find(InputBase).at(0).props().onChange({target: {value: value}});
wrapper.find(InputBase).at(0).props().onChange({target: {name: 'InputBase', value: value}});
This is my functional Component.
const InputText = (props: Props) => {
return (
<div>
<StyledInput type="text" placeholder={props.placeholder} />
<br></br>
</div>
);
};
export default InputText;
and this is my test cases.
enter code here
const wrap = (props: Props) => shallow(<InputText {...props} />);
it("input text change", () => {
// const wrap = (props: Props) => mount(<StyledInput {...props} />);
const wrapper = wrap({ placeholder: "UserName" });
const usernameInput = wrapper.find("input");
usernameInput.simulate("change", { target: { value: "umesh#hcl.com" } });
expect(usernameInput.text()).toEqual("umesh#hcl.com");
});
So this test case failed with error:
Expected "Umesh#hcl.com" and recevide:"";
How can I resolve this?
Check this one
App.test.js
import React from "react";
import { shallow, configure } from "enzyme";
import Input from "./App";
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});
describe("Input Component", () => {
it("should trigger the onchange", () => {
const wrapper = shallow(<Input />);
wrapper
.find("input")
.simulate("change", { target: { value: "on typing" } });
expect(wrapper.find('input').props().value).toBe('on typing')
});
});
App.js
import React, { useState } from "react";
const InputText = props => {
const [inputText, setInput] = useState("");
const onHandleChange = e => {
setInput(e.target.value);
};
return (
<div>
<input
type="text"
value={inputText}
onChange={onHandleChange}
/>
<br />
</div>
);
};
export default InputText;
I have an issue using jest and enzymes for my unit testing on react. I have a component connected to redux and i want to test a method in my component that update the state. heres's my component :
import React, {Component} from 'react';
import Form from 'react-jsonschema-form';
import pick from 'lodash/pick';
import {bindActionCreators} from 'redux';
import fields from './fields';
import widgets from './widgets';
import {initContainerActions} from '../../utils/actionHelper';
import {actions as modelActions} from './actions';
import {connect} from '../../utils/connect';
import reducerRegistry from '../ReducerRegistry';
import reducer from './reducer';
type StateProperties = {
roleModelSchema: Object,
roleUiSchema: Object
};
export type FormContainerProps = {
model: Object,
uiSchema: Object,
userModelLoad: Function,
isLoading: boolean,
idContainer: string
};
export class FormContainer extends Component<FormContainerProps, StateProperties> {
fields = {...fields};
widgets = {...widgets};
constructor(props) {
super(props);
const {idContainer} = props;
reducerRegistry.register(idContainer, reducer(idContainer));
this.state = {
roleModelSchema: {},
roleUiSchema: {}
};
}
componentDidMount = async () => {
const {userModelLoad} = this.props;
await userModelLoad();
this.renderRoleForm();
};
renderRoleForm = () => {
const {model, uiSchema} = this.props;
const modelProperties = Object.keys(model.properties);
const roleUiSchema = pick(uiSchema, modelProperties);
this.setState({
roleUiSchema,
roleModelSchema: model
});
};
render = () => {
const {roleModelSchema, roleUiSchema} = this.state;
const {isLoading} = this.props;
if (isLoading) {
return <div>...Loading</div>;
}
return (
<div className="container">
<div className="columns">
<div className="column col-12">
<Form schema={roleModelSchema} uiSchema={roleUiSchema} liveValidate fields={this.fields} widgets={this.widgets}>
<div>
<button type="submit" className="btn btn-primary">
Submit
</button>
<button type="button">Cancel</button>
</div>
</Form>
</div>
</div>
</div>
);
};
}
const mapDispatchToProps = (dispatch, props) => {
const initializedActions = initContainerActions(modelActions, props.idContainer);
return bindActionCreators(initializedActions, dispatch);
};
export default connect(
(state, props) => state[props.idContainer] || {},
mapDispatchToProps
)(FormContainer);
And the test :
const mockStore = configureMockStore([]);
const context = React.createContext();
describe('Check if renderRoleForm update state', () => {
it('renders without crashing', () => {
// Initialize mockstore with empty state
const initialState = {roleUiSchema: {}};
const store = mockStore(initialState);
const wrapper = shallow(
<Provider store={store} context={context}>
<FormContainer model={model} uiSchema={uiSchema} context={context}
/>
</Provider>
);
const instance = wrapper.instance();
instance.renderRoleForm();
expect(wrapper.state().roleUiSchema).not.toBeEmpty();
});
});
The error i have : TypeError: instance.renderRoleForm is not a function
I tried with mount, dive with shallow and same problem.
If you have any idea :)
Thanks
const wrapper = mount(
<Provider store={store} context={context}>
<FormContainer model={model} uiSchema={uiSchema} context={context} />
</Provider> );
//
wrapper.instance().renderRoleForm();
Ive created a Rexux store. In my entry point I can add an item to my store and see that it works:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import allReducers from './reducers';
import { ADD_TODO_ITEM } from './actionCreators';
import App from './components/containers/App';
let store = createStore(allReducers);
store.subscribe(() => console.log(store.getState()));
store.dispatch(ADD_TODO_ITEM('test 1'));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'),
);
Im trying to use this action dispatcher in my component. When I submit the form below I get the error:
TypeError: dispatch is not a function
I think Im not passing dispatch to AddTodo, but how do you pass dispatch to component?
import React from 'react';
import { ADD_TODO_ITEM } from '../../actionCreators';
const AddTodo = ({ dispatch }) => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
const text = input.value;
console.log(text);
dispatch(ADD_TODO_ITEM(text));
}}
>
<input
type="text"
ref={node => {
input = node;
}}
/>
<button type="submit">Add Item</button>
</form>
);
};
export default AddTodo;
In your case, this.props is empty because you haven't passed any props or connected your component to your redux state. In order to have dispatch in your component, you'll need to use connect from react-redux which takes 2 arguments, one being mapStateToProps and other is mapDispatchToProps. The code goes something like this:
import React from 'react';
import {connect} from 'react-redux';
import { ADD_TODO_ITEM } from '../../actionCreators';
const AddTodo = ({ addItem }) => {
let input;
return (
<form
onSubmit={e => {
e.preventDefault();
const text = input.value;
console.log(text);
dispatch(addItem(text));
}}
>
<input
type="text"
ref={node => {
input = node;
}}
/>
<button type="submit">Add Item</button>
</form>
);
};
const mapDispatchToProps = (dispatch) => {
return {
addItem: (item) => {
dispatch(ADD_TODO_ITEM(item));
}
}
};
export default connect(undefined, mapDispatchToProps)(AddTodo);