Trying to test this component, and im getting this
error
TypeError: this.props.onItemAdded is not a function
I've referenced this but this solution doesn't really apply to my problem
Enzyme test: TypeError: expect(...).find is not a function
How would i test the button functionality being that the button is a prop ?
todo-add-item.test.js
import React from "react";
import { shallow } from "enzyme";
import TodoAddItem from './todo-add-item';
describe('Should render add item component', ()=> {
it('should render add item component', () => {
const wrapper = shallow(<TodoAddItem/>)
})
})
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
const wrapper =shallow(<TodoAddItem/>)
wrapper.find('button').simulate('click') // getting the type error here.
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
You don't pass any props to your component.
const wrapper =shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
You can check props with .props()
Eg:
console.log('props',wrapper.find('button').props());
Related
So I have TodoList component which is updated just fine when I add new Item. Inside I have TodoItem width delete and togglecomplete functions and they do invoke methodsof my todoList object, I see in console that object is changing, yet List component won't get rerendered. Any idia what can be done.
package json: "mobx": "^6.7.0", "mobx-react-lite": "^3.4.0",
import { ITodo } from '../type/ITodo';
import TodoItem from '../TodoItem/TodoItem.component'
import './TodoList.styles.css';
import { observer } from 'mobx-react-lite';
type Props = {
todoList: ITodo[];
}
const TodoList: React.FC<Props> = ({ todoList }) => {
return (
<div className="TodoList">
{
todoList && todoList.length > 0 && (
todoList.map(el => (
<TodoItem content={el} key={el.id} />
))
)
}
</div>
)
}
export default observer(TodoList);
here are delete and toggle complete functions
import { useStores } from '../Store/StoreContext';
import { observer } from 'mobx-react-lite';
type Props = {
content: ITodo;
}
const TodoItem: React.FC<Props> = ({ content }) => {
const { todoList } = useStores();
const { deadline, title , description, completed, id } = content
const handleChange = () => {
todoList.toggleComplete(id)
}
const deleteHandler = () => {
todoList.deleteTodo(id);
}
return (
<div className="TodoItem">
<div className="actions">
<FormControlLabel
onChange={handleChange}
/>
<Button
variant="outlined"
onClick={deleteHandler}
>
Delete
</Button>
</div>
</div>
)
}
export default observer(TodoItem);
and here is my store just in case, I keep my store in Context.
import { action, makeObservable, observable } from "mobx";
import { ITodo } from "../type/ITodo";
export class Todo {
todos: ITodo[] = [];
constructor() {
makeObservable(this, {
todos: observable,
getTodos: action,
addTodo: action,
deleteTodo: action,
toggleComplete: action,
});
}
getTodos() {
return this.todos;
}
addTodo(todo: ITodo) {
this.todos.push(todo);
}
deleteTodo(id: string) {
console.log(id);
this.todos = this.todos.filter((el) => el.id !== id);
}
toggleComplete(id: string) {
this.todos = this.todos.map((el) => {
if (el.id !== id) {
return el;
}
return { ...el, completed: !el.completed };
});
}
}
Here is repository on github: https://github.com/pavel-gutsal/mobx-todo-list
node version - 16.xx
**How to check for the dynamic state changes in a parent component and write the test case using the react testing library if the props passed to the child component are based on the state changes which are happening dynamically in the parent component. Can someone help me with this?
App.js
import React, { Component } from 'react';
import './App.css';
import TextArea from './components/TextArea/TextArea';
class App extends Component {
constructor() {
super();
this.state = {
textAreaParams: {},
};
}
componentDidMount() {
this.setDefaultTextAreaMessage();
}
setDefaultTextAreaMessage = () => {
this.setState({
textAreaParams: { message: 'Hello' },
});
};
render() {
const { textAreaParams } = this.state;
return (
<div className="App">
{Object.keys(textAreaParams).length > 0 ? (
<TextArea params={textAreaParams} />
) : null}
</div>
);
}
}
export default App;
TextArea.js
import { Component } from 'react';
class TextArea extends Component {
constructor(props) {
super(props);
this.state = {
message: this.props.params.message,
};
}
render() {
return (
<div>
<textarea
rows="4"
cols="50"
value={this.state.message ? this.state.message : ''}
placeholder="test"
onChange={() => {}}
>
{this.state.message}
</textarea>
</div>
);
}
}
export default TextArea;
App.test.js
import App from './App';
import { cleanup, render } from '#testing-library/react';
describe('Rendering the App component and passing props to text area', () => {
afterEach(cleanup);
it('render the App component and check for the TextArea component', async () => {
const props = { textAreaParams: { message: 'save' } };
const { getByPlaceholderText } = render(<App {...props} />);
const textAreaParams = getByPlaceholderText('test');
expect(textAreaParams).toHaveTextContent('save');
});
});
We need to pass onChange handler prop from the App component to TextArea and then TextArea will component will call that handler when there is a change in the text area.
updateTextAreaMessage = (messageInTextArea) => {
this.setState({
textAreaParams: { message: messageInTextArea}
})
}
In the above code, messageInTextArea is a string value when we change the text in TextArea and when updateTextAreaMessage is called in the TextArea component with the same string value as a parameter, it will update the state in the App component.
Full Implementation:
App.js:
import React, { Component } from "react";
import './App.css';
import TextArea from './components/TextArea/TextArea';
class Main extends Component {
constructor() {
super();
this.state = {
textAreaParams: { message: "hello" } // we can provide default value here
};
}
updateTextAreaMessage = (messageInTextArea) => {
this.setState({
textAreaParams: { message: messageInTextArea }
});
};
render() {
const { textAreaParams } = this.state;
return (
<div className="App">
{Object.keys(textAreaParams).length > 0 ? (
<TextArea
params={textAreaParams}
onUpdate={this.updateTextAreaMessage}
/>
) : null}
<p aria-label="text area message">{textAreaParams.message}</p>
</div>
);
}
}
export default Main;
TextArea.js:
import { Component } from "react";
class TextArea extends Component {
render() {
return (
<div>
<textarea
rows="4"
cols="50"
value={this.props.params.message ? this.props.params.message : ""}
placeholder="test"
onChange={(event) => this.props.onUpdate(event.target.value)}
>
{this.props.params.message}
</textarea>
</div>
);
}
}
export default TextArea;
Now, we'll add the test for App.js. But the question is what to test here? The answer would we'll add the test for whether the state is updated or not when there is a change in the text of the TextArea component.
import { render } from "#testing-library/react";
import App from "./App";
import TextArea from './components/TextArea/TextArea';
describe("Rendering the App component and passing props to text area", () => {
it("should render the App component with default message in TextArea", () => {
const { getByPlaceholderText } = render(<Main />);
const textAreaParams = getByPlaceholderText("test");
expect(textAreaParams).toHaveTextContent(/hello/i);
});
it("should update the text area when we type something", () => {
const { getByPlaceholderText, getByLabelText } = render(<Main />);
userEvent.type(getByPlaceholderText("test"), "Anything");
expect(getByLabelText(/text area message/i)).toHaveTextContent(/anything/i);
});
});
describe("Rendering the Text Area component", () => {
it("should render the TextArea component and calls onChange handlers when we type something", () => {
const mockOnChangeHandler = jest.fn();
const mockParams = { message: "save" };
const { getByPlaceholderText } = render(
<TextArea params={mockParams} onUpdate={mockOnChangeHandler} />
);
const inputTextArea = getByPlaceholderText("test");
expect(inputTextArea).toHaveTextContent(/save/i);
userEvent.type(inputTextArea, "Anything");
expect(mockOnChangeHandler).toHaveBeenCalled();
});
});
I have a basic app for the sack of my training composed of tow Components App and User and a snapshot test file for the User.
The test passes for now but I want to test the methods that update the state of the parent but I don't know how to proceed, Please Help.
App component
import React from 'react'
import './App.css'
import data from './data/users-data.json'
import User from './components/User/User'
export default class App extends React.Component {
constructor() {
super()
this.state = {users: data}
this.clickFollowHandler = this.clickFollowHandler.bind(this)
this.clickStarHandler = this.clickStarHandler.bind(this)
}
clickFollowHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isFollowed === 'active' ? user.isFollowed = 'idle' : user.isFollowed = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
clickStarHandler(id) {
this.setState(prevState => {
const updatedUsers = prevState.users.map(user => {
if (user.id === id) {
user.isStared === 'active' ? user.isStared = 'idle' : user.isStared = 'active'
}
return user
})
return {
users: updatedUsers
}
})
}
render() {
return (
<div>
{this.state.users.map(u => {
return (
<User
key={u.id}
id={u.id}
name={u.name}
date={u.date}
readingTime={u.readingTime}
isStared={u.isStared}
isFollowed={u.isFollowed}
image={u.image}
handleFollowClick={this.clickFollowHandler}
handleStarClick={this.clickStarHandler}
/>
)
})}
</div>
)
}
}
User component
import React from 'react'
import classes from './User.module.css'
import myImage from '../../assets/images/avatar.png'
import PropTypes from 'prop-types'
const User = props => {
return(
<div className={classes.User} key={props.id}>
<div className={classes.name}>name: {props.name}</div>
<button onClick={() => props.handleFollowClick(props.id)}>
{props.isFollowed === 'active' ? 'Unfollow' : 'Follow'}
</button>
<input
className={classes.hvrIconPop}
checked={props.isStared === 'active' ? true : false}
onChange={() => props.handleStarClick(props.id)}
type='checkbox'
/>
<div>date: {props.date}</div>
<div>reading time: {props.readingTime}</div>
<img src={myImage} alt={props.name} />
</div>
)
}
User.propTypes = {
handleFollowClick: PropTypes.func.isRequired,
handleStarClick: PropTypes.func.isRequired,
}
export default User
User.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import User from './User';
const users = [{
"id": "5d552d0058f193f2795fc814",
"isFollowed": "active",
"isStared": "idle",
"image": "./assets/images/avata.png",
"readingTime": 20,
"name": "Walton Morton",
"date": "Aug 9"
}];
it('renders correctly when there are no users', () => {
const tree = renderer.create(<User
users={[]}
handleFollowClick={() => 'test'}
handleStarClick={() => {}} />).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders correctly when there is one user', () => {
const tree = renderer.create(<User users={users}
handleFollowClick={() => 'test'}
handleStarClick={() => {}}
/>).toJSON();
expect(tree).toMatchSnapshot();
});
You pass your User component a mock function (jest.fn()) through its handleFollowClick and handleStarClick props, then simulate whatever is supposed to trigger the parent action (a click event on the <button /> or a change event on the <input />) and test whether the corresponding mock function was called.
I personally always use Enzyme for this sort of thing, but here's how I'd assume it works using react-test-renderer based on this answer:
const mockFollowClick = jest.fn();
const mockStarClick = jest.fn();
const tree = renderer.create(<User
{...users[0]}
handleFollowClick={mockFollowClick}
handleStarClick={mockStarClick}
/>)
const button = tree.root.findByType('button');
const input = tree.root.findByType('input');
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalled();
input.props.onChange();
expect(mockStarClick).toHaveBeenCalled();
You can even check if it was called with the correct user id:
button.props.onClick();
expect(mockFollowClick).toHaveBeenCalledWith("5d552d0058f193f2795fc814");
Getting this error
Matcher error: received value must be a mock or spy function
Received has type: object
Received has value: {}
However, i think i shouldn't be getting this error because im using jest.fn. So im mocking the function.
describe('Should simulate button click', ()=> {
it('should simulate button click', () => {
// add the name of the prop, which in this case ites called onItemAdded prop,
// then use jest.fn()
const wrapper = shallow(<TodoAddItem onItemAdded={() => jest.fn()}/>)
// console.log('props',wrapper.find('button').props());
wrapper.find('button').simulate('click');
expect(wrapper).toHaveBeenCalled(); // error happens when this executes
})
})
todo-add-item.js
import React, { Component } from 'react';
import './todo-add-item.css';
export default class TodoAddItem extends Component {
render() {
return (
<div className="todo-add-item">
<button
className="test-button btn btn-outline-secondary float-left"
onClick={() => this.props.onItemAdded('Hello world')}>
Add Item
</button>
</div>
);
}
}
app.js (using the component in this file)
import React, { Component } from 'react';
import AppHeader from '../app-header';
import SearchPanel from '../search-panel';
import TodoList from '../todo-list';
import ItemStatusFilter from '../item-status-filter';
import TodoAddItem from '../todo-add-item';
import './app.css';
export default class App extends Component {
constructor() {
super();
this.createTodoItem = (label) => {
return {
label,
important: false,
done: false,
id: this.maxId++
}
};
this.maxId = 100;
this.state = {
todoData: [
this.createTodoItem('Drink Coffee'),
this.createTodoItem('Make Awesome App'),
this.createTodoItem('Have a lunch')
]
};
this.deleteItem = (id) => {
this.setState(({ todoData }) => {
const idx = todoData.findIndex((el) => el.id === id);
const newArray = [
...todoData.slice(0, idx),
...todoData.slice(idx + 1)
];
return {
todoData: newArray
};
});
};
this.addItem = (text) => {
const newItem = this.createTodoItem(text);
this.setState(({ todoData }) => {
const newArray = [
...todoData,
newItem
];
return {
todoData: newArray
};
});
};
this.onToggleImportant = (id) => {
console.log('toggle important', id);
};
this.onToggleDone = (id) => {
console.log('toggle done', id);
};
};
render() {
return (
<div className="todo-app">
<AppHeader toDo={ 1 } done={ 3 } />
<div className="top-panel d-flex">
<SearchPanel />
<ItemStatusFilter />
</div>
<TodoList
todos={ this.state.todoData }
onDeleted={ this.deleteItem }
onToggleImportant={ this.onToggleImportant }
onToggleDone={ this.onToggleDone } />
<TodoAddItem onItemAdded={ this.addItem } />
</div>
);
};
};
I'm not 100% sure, but I believe you should do something like this:
describe('should simulate button click', () => {
it('should simulate button click', () => {
const mockedFunction = jest.fn();
const wrapper = shallow(<TodoAddItem onItemAdded={ mockedFunction } />);
wrapper.find('button').simulate('click');
expect(mockedFunction).toHaveBeenCalled();
});
});
You are testing if the onItemAdded function gets called when you click the <TodoAddItem /> component. So you have to mock it first using jest.fn and then check if the mocked function got called after you simulated the click.
For me works replacing the next one:
const setCategories = () => jest.fn();
With this one:
const setCategories = jest.fn();
I suppose that you should to set just jest.fn or jest.fn() in your code.
I need the help with testing this component using expect library with karma and mocha.
import React from 'react';
import {Clock} from 'Clock';
import {CountdownForm} from "CountdownForm";
import {Controls} from "Controls";
export class Countdown extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
countdownStatus: 'stopped'
}
}
componentDidUpdate(prevProps, prevState) {
if(prevState.countdownStatus !== this.state.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.timerID = setInterval(
this.tick,
1000
);
break;
case 'stopped':
console.log('stopped fireeed');
this.setState({count: 0});
case 'paused':
console.log('paused fireeed');
clearInterval(this.timerID)
this.timerID = undefined;
break;
}
}
}
componentWillUnmount() {
clearInterval(this.timerID)
this.timerID = undefined;
}
handleSetCountdown = (seconds) => {
this.setState({
count: seconds,
countdownStatus: 'started'
});
}
tick = () => {
console.log('tick fireeeed');
let newMoment = this.state.count - 1;
let newState = 'started'
if(newMoment === 0) {
newState = 'stopped'
}
this.setState(() => {
return {
count: newMoment >= 0 ? newMoment : 0,
countdownStatus: newState
}
})
}
handleStatusChange = (newStatus) => {
this.setState({
countdownStatus: newStatus,
})
}
render() {
const renderControlArea = () => {
if(this.state.countdownStatus !== 'stopped') {
return <Controls countdownStatus={this.state.countdownStatus} onStatusChange={this.handleStatusChange}/>
} else {
return <CountdownForm onSetCountdown={this.handleSetCountdown}/>
}
}
const count = this.state.count
return (
<div>
<Clock totalSeconds={count}/>
{renderControlArea()}
{/* <CountdownForm onSetCountdown={this.handleSetCountdown}/> */}
</div>
);
}
}
const Controls = (props) => {
const renderStartStopButton = (countdownStatus) => {
if(countdownStatus === 'started') {
return <button className="button secondary" onClick={() => props.onStatusChange('paused')}>Pause</button>
}
else if (countdownStatus === 'paused'){
return <button className="button primary" onClick={() => props.onStatusChange('started')}>Start</button>
}
}
return (
<div className="controls">
{renderStartStopButton(props.countdownStatus)}
<button className="button alert hollow" onClick={() => props.onStatusChange('stopped')}>Clear</button>
</div>
)
}
export {Controls}
I tried something like this but it didn't work, it seems like I should test if the function is called when countdownStatus is passed. Here is the error log:
import React from 'react';
import ReactDOM from 'react-dom';
import expect from 'expect';
import $ from 'jQuery';
import TestUtils from 'react-addons-test-utils';
import { Controls } from "Controls"
describe('Controls', () => {
it('should exist', () => {
expect(Controls).toExist()
});
describe('render', () => {
it('should render pause button when started', () => {
var controls = TestUtils.renderIntoDocument(<Controls countdownStatus={'started'}/>);
var $el = $(ReactDOM.findDOMNode(controls));
var $PauseBtn = $el.find('button:contains(Pause)');
expect($PauseBtn.length).toBe(1);
});
it('should render start button when paused', () => {
var controls = TestUtils.renderIntoDocument(<Controls countdownStatus={'paused'}/>);
var $el = $(ReactDOM.findDOMNode(controls));
var $StartBtn = $el.find('button:contains(Start)');
expect($StartBtn.length).toBe(1);
});
});
})
Well, the issue was that I couldn't node into the document before it got returned from the Control function. I figured it out, just in case someone is dealing with the similar issue keep in mind you are testing function not Class here is the code that fixes problem
import React from 'react';
import ReactDOM from 'react-dom';
import expect from 'expect';
import $ from 'jQuery';
import TestUtils from 'react-addons-test-utils';
import { Controls } from "Controls"
describe('Controls', () => {
it('should exist', () => {
expect(Controls).toExist()
});
describe('render', () => {
it('should render pause button when started', () => {
var controls = TestUtils.renderIntoDocument(Controls({countdownStatus:'started'}));
var $el = $(ReactDOM.findDOMNode(controls));
var $PauseBtn = $el.find('button:contains(Pause)');
expect($PauseBtn.length).toBe(1);
});
it('should render start button when paused', () => {
var controls = TestUtils.renderIntoDocument(Controls({countdownStatus:'paused'}));
var $el = $(ReactDOM.findDOMNode(controls));
var $StartBtn = $el.find('button:contains(Start)');
expect($StartBtn.length).toBe(1);
});
});
})