React Testing: Cannot read property 'toLowerCase' of undefined - javascript

I am testing a react component using Mocha, Chai and Enzyme. The component is
TodoList.js
export class TodoList extends Component {
render() {
var {todos, searchText, showCompleted, isFetching} = this.props;
var renderTodos = () => {
if(isFetching){
return (
<div className='container__message'>
<PulseLoader color="#bbb" size="6px" margin="1.5px" />
</div>
);
}
if(todos.length === 0){
return <p className='container__message'>Nothing to show</p>
}
return TodoAPI.filterTodos(todos, showCompleted, searchText).map((todo) => {
return (
<Todo key={todo.id} {...todo} />
)
});
}
return (
<div>
{renderTodos()}
</div>
);
}
}
export default connect(
(state) => {
return state;
}
)(TodoList);
This component uses another function which is
TodoAPI.js
import $ from 'jquery';
module.exports = {
filterTodos: function(todos, showCompleted, searchText){
var filteredTodos = todos;
filteredTodos = filteredTodos.filter((todo) => {
return !todo.completed || showCompleted; // todo is not completed or showCompleted is toggled
});
console.log(filteredTodos);
filteredTodos = filteredTodos.filter((todo) => {
console.log(todo.text);
return todo.text.toLowerCase().indexOf(searchText.toLowerCase()) !== -1;
});
filteredTodos.sort((a, b) => {
if(!a.completed && b.completed){
return -1;
} else if(a.completed && !b.completed){
return 1;
} else {
return 0;
}
});
return filteredTodos;
}
};
The test which I have written tests that TodoList.js renders 2 Todo components as I have provided an array of two objects.
TodoList.spec.js
import React from 'react';
import ConnectedTodoList, {TodoList} from '../../src/components/TodoList';
describe('TodoList', function(){
let todos = [
{
id: 1,
text: 'some dummy text',
},
{
id: 2,
text: 'some more dummy text',
}
];
beforeEach(function(){
this.wrapper = shallow(<TodoList todos={todos} />);
});
it('should exist', function(){
expect(this.wrapper).to.exist;
});
it('should display 2 Todos', function(){
expect(this.wrapper.find('Todo')).to.have.lengthOf(2);
});
})
But when I execute this test I get an error which says
1) TodoList "before each" hook for "should exist":
TypeError: Cannot read property 'toLowerCase' of undefined
at F:/Study Material/Web/React Projects/ReactTodoApp/src/api/TodoAPI.js:16:43

Your issues stems from this line in TodoList.js:
var {todos, searchText, showCompleted, isFetching} = this.props;
This is expecting all of these values to be passed as props to the TodoList component. As searchText is not provided in the tests, it has the value undefined when it gets passed to filterTodos where searchText.toLowerCase() is eventually called, causing the error.
Changing the beforeEach section of your tests to:
beforeEach(function(){
this.wrapper = shallow(<TodoList todos={todos} searchText='dummy' />);
});
should solve the issue. You should probably also provide showCompleted and isFetching so that you aren't relying on defaults.

Best guess without running the code myself is that searchText is undefined and so when you call toLowerCase on it in the TodoAPI the function cannot be called.
The only other place you have used toLowerCase is on the todo text itself which you provide through a prop.

Related

In React Context, how can I use state variables in state functions?

I have a React Context which looks like this:
import React, { Component } from 'react'
const AlertsContext = React.createContext({
categoryList: [],
setCategoryList: () => {}
})
export class AlertsProvider extends Component {
state = {
categoryList: [],
setCategoryList: categoryString => (
this.categoryList.includes(categoryString)
? this.setState({ categoryList: this.categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: this.categoryList.concat([categoryString]) })
)
}
render() {
const { children } = this.props
const {categoryList, setCategoryList } = this.state
return (
<AlertsContext.Provider value={{categoryList, setCategoryList}}>
{children}
</AlertsContext.Provider>
)
}
}
export const AlertsConsumer = AlertsContext.Consumer
So, categoryList is an array of strings, each representing a category. setCategoryList should take a string; if that string is already in the array, it removes it, and if it's not in the array it adds it.
In one of my components the user can select categories from a list of checkboxes. When a checkbox is clicked, the AlertsContext setCategoryList should be called with the value of the clicked box:
import React, { Component } from 'react'
import { AlertsConsumer } from '../../../context/alerts-context'
class AlertFilters extends Component {
constructor(props) {
super(props)
this.state = {
categories: props.categories
}
}
render() {
const { categories } = this.state
return (
<AlertsConsumer>
{({ categoryList, setCategoryList }) => (
<>
{
categories.map(category => (
return (
<div key={category.id}>
<Checkbox id={category.id} value={category.value} onChange={e => setCategoryList(e.target.value)} checked={categoryList.includes(category.value)} />
<label htmlFor={category.id}>{category.value}</label>
</div>
)
))
}
</>
)}
</AlertsConsumer>
)
}
}
export default AlertFilters
This compiles ok, but when I run it and click a checkbox I get the following error:
alerts-context.jsx:77 Uncaught TypeError: Cannot read property 'includes' of undefined
This is in the line:
this.categoryList.includes(categoryString)
in the Context Provider, suggesting that "this.categoryList" is undefined at this point.
I tried changing it to
this.state.categoryList.includes(categoryString)
but it said I had to use state destructuring, so I changed to:
setCategoryList: (categoryString) => {
const { categoryList } = this.state
categoryList.includes(categoryString)
? this.setState({ categoryList: categoryList.filter(value => value !== categoryString) })
: this.setState({ categoryList: categoryList.concat([categoryString]) })
}
which highlighted the ternary operator and gave the following lint error:
Expected an assignment or function call and instead saw an expression.
What am I doing wrong?
Use if/else syntax to update the state.
setCategoryList: categoryString => {
const { categoryList } = this.state;
if (categoryList.includes(categoryString)) {
this.setState({
categoryList: categoryList.filter(value => value !== categoryString)
});
} else {
this.setState({ categoryList: categoryList.concat([categoryString]) });
}
};

React Context API - get updated state value

I am experimenting with React context api,
Please check someComponent function where I am passing click event (updateName function) then state.name value update from GlobalProvider function
after updated state.name it will reflect on browser but not getting updated value in console ( I have called console below the line of click function to get updated value below )
Why not getting updated value in that console, but it is getting inside render (on browser) ?
Example code
App function
<GlobalProvider>
<Router>
<ReactRouter />
</Router>
</GlobalProvider>
=== 2
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: () => { this.setState({ name: "Batman 2 " }) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
=== 3
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme();
console.log(globalValue.name ) //*** Here is my concern - not getting updated value here but , getting updated value in browser
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>) }
React state isn't an observer like Vue or Angular states which means you can't get updated values exactly right after changing them.
If you want to get the updated value after changing them you can follow this solution:
class A extends Component {
state = {
name: "Test"
}
updateName = () => {
this.setState({name: "Test 2"}, () => {
console.log(this.state.name) // here, name has been updated and will return Test 2
})
}
}
So, you need to write a callback function for the clickme and call it as below:
class GlobalProvider extends React.Component {
state = {
name: "Batman"
};
render() {
return (
<globalContext.Provider
value={{
name: this.state.name,
clickme: (callback) => { this.setState({ name: "Batman 2 " }, () => callback(this.state.name)) }
}}
>
{this.props.children}
</globalContext.Provider>
);
}
}
export default GlobalProvider;
And for using:
const SomeComponent = () => {
const globalValue = useContext(globalContext);
const updateName = ()=> {
globalValue.clickme((name) => {
console.log(name) // Batman 2
});
}
return (
<div onClick={(e)=> updateName(e) }>
{globalValue.name}//*** In initial load display - Batman, after click it display Batman 2
</div>)
}

TypeError: this.props.myMaterials.fetch is not a function

I'm working on jest unit testing using react-test-renderer.The test cases fails and showing this error
"TypeError: this.props.myMaterials.fetch is not a function"
where this.props.notes.fetch is inside the componentWillMount.Is there any solution to fix this without using enzyme?
myComponent.jsx :
class myComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
column: this.getColumns(),
pageNotFound: false
};
}
componentWillMount() {
this.props.notes.fetch(this.props.courseId);
window.addEventListener('resize', this.handleResizeEvent);
this.handleError = EventBus.on(constants.NOTES_NOT_FOUND, () => {
this.setState({ pageNotFound: true });
});
}
componentDidMount() {
window.addEventListener('resize', this.handleResizeEvent);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResizeEvent);
this.handleError();
}
handleResizeEvent = () => {
this.setState({ column: this.getColumns() });
};
getColumns = () => (window.innerWidth > (constants.NOTES_MAX_COLUMNS * constants.NOTES_WIDTH) ?
constants.NOTES_MAX_COLUMNS :
Math.floor(window.innerWidth / constants.NOTES_WIDTH))
callback = (msg, data) => {
}
render() {
const { notes, language } = this.props;
if (this.state.pageNotFound) {
return (<div className="emptyMessage"><span>Empty</span></div>);
}
if (notes.loading) {
return (<Progress/>);
}
// To Refresh Child component will receive props
const lists = [...notes.cards];
return (
<div className="notesContainer" >
<NoteBook notesList={lists} callback={this.callback} coloums={this.state.column} />
</div>
);
}
}
myComponent.propTypes = {
notes: PropTypes.object,
courseId: PropTypes.string,
language: PropTypes.shape(shapes.language)
};
export default withRouter(myComponent);
myComponent.test.jsx:
const tree = renderer.create(
<myComponent.WrappedComponent/>).toJSON();
expect(tree).toMatchSnapshot();
});
Its pretty evident from the error that while testing you are not supplying the prop notes which is being used in your componentWillMount function. Pass it when you are creating an instance for testing and it should work.
All you need to do is this
const notes = {
fetch: jest.fn()
}
const tree = renderer.create(
<myComponent.WrappedComponent notes={notes}/>).toJSON();
expect(tree).toMatchSnapshot();
});
One more thing that you should take care is that your component names must begin with Uppercase characters.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
column: this.getColumns(),
pageNotFound: false
};
}
componentWillMount() {
this.props.notes.fetch(this.props.courseId);
window.addEventListener('resize', this.handleResizeEvent);
this.handleError = EventBus.on(constants.NOTES_NOT_FOUND, () => {
this.setState({ pageNotFound: true });
});
}
componentDidMount() {
window.addEventListener('resize', this.handleResizeEvent);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResizeEvent);
this.handleError();
}
handleResizeEvent = () => {
this.setState({ column: this.getColumns() });
};
getColumns = () => (window.innerWidth > (constants.NOTES_MAX_COLUMNS * constants.NOTES_WIDTH) ?
constants.NOTES_MAX_COLUMNS :
Math.floor(window.innerWidth / constants.NOTES_WIDTH))
callback = (msg, data) => {
}
render() {
const { notes, language } = this.props;
if (this.state.pageNotFound) {
return (<div className="emptyMessage"><span>Empty</span></div>);
}
if (notes.loading) {
return (<Progress/>);
}
// To Refresh Child component will receive props
const lists = [...notes.cards];
return (
<div className="notesContainer" >
<NoteBook notesList={lists} callback={this.callback} coloums={this.state.column} />
</div>
);
}
}
MyComponent.propTypes = {
notes: PropTypes.object,
courseId: PropTypes.string,
language: PropTypes.shape(shapes.language)
};
export default withRouter(MyComponent);
Have you tried giving your component a stub notes.fetch function?
Let isFetched = false;
const fakeNotes = {
fetch: () => isFetched = true
}
That way you can test that fetch is called without making a request. I'm not sure, but the test runner is running in node, and I think you may need to require fetch in node, and so the real notes may be trying to use the browser's fetch that does not exist.
I'm not an expert, but I believe it is good practice to use a fakes for side effects/dependencies anyway, unless the test specifically is testing the side effect/dependency.
Pass notes as props to your component like <myComponent.WrappedComponent notes={<here>} /> and also put a check like this.props.notes && this.props.notes.fetch so that even if your props aren't passed you don't get an error.

How to use React.cloneElement to pass a function property with a return object?

I'm using react-router which forces me to use React.cloneElement to pass down properties to my Children. I can pass down objects and functions, but my issue is where one of my functions has a return object back up to the parent, which is always undefined. The function triggers in the parent, but it doesn't receive the object I'm passing it from the child.
Here is a jsFiddle of the below example code if anyone wants to edit it https://jsfiddle.net/conor909/gqdfwg6p/
import React from "react";
import ReactDom from "react-dom";
const App = React.createClass({
render() {
return (
<div>
{this.getChildrenWithProps()}
</div>
)
},
getChildrenWithProps() {
return React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
myFunction: this.myFunction
});
});
},
// NOTE:
// the idea is that the variable 'newForm' should be sent back up to App, I can log out 'newForm' in the Child, but here in App, it is undefined.
myFunction(newForm) {
console.log(newForm); // => undefined object
}
});
const Child = React.createClass({
propTypes: {
myFunction: React.PropTypes.func,
myForm: React.PropTypes.object
},
render() {
return (
<form className="col-sm-12">
<MyForm
changeForm={this.onChangeForm}
form={this.props.myForm} />
</form>
)
},
onChangeForm(formChanges) {
let newForm = {
...this.props.myForm,
...formChanges
}
// console.log(newForm); => here my newForm object looks fine
this.props.myFunction(newForm);
}
});
const MyForm = React.createClass({
propTypes: {
changeForm: React.PropTypes.func.isRequired
},
render() {
return (
<div>
<Input onChange={this.onChangeForm}>
</div>
)
},
onChangeForm(value) {
this.props.changeForm({ something: value });
}
});

unit testing a react component with mocha

I'm working through a TodoMVC example for the Redux ecosystem. I've completed working code for the example and am now working through the creation of tests for each of the elements of the application.
For actions and reducers, the testing is very straightforward, but for the components, writing tests has proven somewhat more challenging.
My general component architecture looks like this:
Home.js
\-App.js
\-TodoList.js
\-TodoItem.js
\-TodoInput.js
Writing the unit tests for TodoInput.js has been relatively straightforward:
TodoInput.js:
handleChange(e) {
this.setState({ text: e.target.value });
}
...
render() {
return (
<input type="text" autoFocus='true'
className={classnames({
edit: this.props.editing,
'new-todo': this.props.newTodo
})}
value={this.state.text}
placeholder={this.props.placeholder}
onKeyDown={this.handleKeyDown.bind(this)}
onBlur={this.handleBlur.bind(this)}
onChange={this.handleChange.bind(this)}>
</input>
);
}
TodoInput-test.js:
const mockedTodo = {
text: 'abc123',
complete: false
};
it(`should update text from user input`, () => {
const component = TestUtils.renderIntoDocument(
<TodoInput
text = {mockedTodo.text}
editing = {false}
onSave = {_.noop}
/>
);
const inputComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'input');
expect(React.findDOMNode(inputComponent).value).toBe(mockedTodo.text);
TestUtils.Simulate.change(React.findDOMNode(inputComponent), {target: {value: "newValue"}});
expect(React.findDOMNode(inputComponent).value).toBe("newValue");
React.unmountComponentAtNode(React.findDOMNode(component));
});
But for TodoItem.js, testing has been a little trickier.
The render code branches based on whether or not an editing flag has been set on the item:
TodoItem.js:
import React, { Component, PropTypes } from 'react';
import TodoInput from './TodoInput';
import classnames from 'classnames';
export default class TodoItem extends Component {
static propTypes = {
todo: PropTypes.object.isRequired,
editTodo: PropTypes.func.isRequired,
markTodoAsComplete: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
constructor(props, context) {
super(props, context);
this.state = {
editing: false
};
}
handleDoubleClick() {
this.setState({ editing: true });
}
handleSave(id, text) {
if (text.length === 0) {
this.props.deleteTodo(id);
} else {
this.props.editTodo(id, text);
}
this.setState({ editing: false });
}
render() {
const {todo, markTodoAsComplete, deleteTodo} = this.props;
let element;
if (this.state.editing) {
element = (
<TodoInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
);
} else {
element = (
<div className='view'>
<label onDoubleClick={this.handleDoubleClick.bind(this)}>
{todo.text}
</label>
<input className='markComplete'
type='checkbox'
checked={todo.complete}
onChange={() => markTodoAsComplete(todo)} />
<button className='destroy'
onClick={() => deleteTodo(todo)} />
</div>
);
}
return (
<li className={classnames({
completed: todo.complete,
editing: this.state.editing
})}>
{element}
</li>
)
}
}
I'm a little stumped on how to go about writing a test that, for instance, would verify that a double-click on the component had successfully set the state to editing: true.
Typically, I have my tests divided into two parts, "rendering" and "events", i.e. for TodoItem-test.js:
import React, { addons } from 'react/addons';
import _ from 'lodash';
import expect from 'expect';
const { TestUtils } = addons;
import TodoItem from '../TodoItem';
describe('TodoItem', () => {
const mockedTodo = {
text: 'abc123',
complete: false
};
describe('rendering', () => {
let component;
before(() => {
component = TestUtils.renderIntoDocument(
<TodoItem
todo={mockedTodo}
editTodo={_.noop}
markTodoAsComplete={_.noop}
deleteTodo={_.noop}
/>
);
});
afterEach(() => {
React.unmountComponentAtNode(React.findDOMNode(component));
});
it('should render the element', () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
expect(liComponent).toExist();
});
it('should render text in label', () => {
const labelComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'label');
expect(labelComponent).toExist();
expect(React.findDOMNode(labelComponent).textContent).toEqual('abc123');
});
});
describe('events', () => {
...
});
but in this case, I want to see if double-clicking on the component leads to the following:
the component state should now have an editing flag associated with it
the element should have changed, and TodoItem.js should now render a <TodoInput/> component instead.
What is the most efficient way to structure a test against this expected behavior? I am thinking that I should do two things:
First, test to see if a double-click on the component adds the expected "editing: true" flag. I am not sure how to do this. If I set up a test as follows:
describe('events', () => {
let component;
let deleteTodoCallback = sinon.stub();
beforeEach(() => {
component = TestUtils.renderIntoDocument(
<TodoItem
todo={mockedTodo}
editTodo={_.noop}
markTodoAsComplete={_.noop}
deleteTodo={deleteTodoCallback}
/>
);
});
afterEach(() => {
React.unmountComponentAtNode(React.findDOMNode(component));
});
it(`should change the editing state to be true if a user double-clicks
on the todo`, () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
// expect the editing flag to be false
TestUtils.Simulate.doubleClick(React.findDOMNode(liComponent));
// expect the editing flag to be true
});
});
how do I go about testing to ensure that the editing flag has been set? liComponent.props.editing returns undefined.
Second, have a context("if the component is editing mode") that tests to make sure that the following has been rendered correctly:
<li className={classnames({
completed: todo.complete,
editing: this.state.editing
})}>
<TodoInput text={todo.text}
editing={this.state.editing}
onSave={(text) => this.handleSave(todo.id, text)} />
</li>
I'm also not sure how I would go about testing this rigorously as well.
liComponent.props is undenfined because liComponent is a DOM element, not a react component. That's the case because you're fetching it with findRenderedDOMComponentWithTag. You actually already have access to the React component you're trying to test against.
it('should render the element', () => {
const liComponent = TestUtils.findRenderedDOMComponentWithTag(component, 'li');
// `component` is from your `before` function
expect(component.state.editing).toBe(false);
// Make sure you're simulating on a DOM element
TestUtils.Simulate.doubleClick(liComponent);
expect(component.state.editing).toBe(true);
});
You can then use scryRenderedComponentsWithType to check whether a TodoInput is rendered.

Categories

Resources