I try to test my React app with Jest.
I have a button that copies text to the clipboard. (this text is generated in my App, the user can't see it on the screen)
I want to test this copied text with reference text. How can I get copied text from the clipboard in my test?
React component:
import React from 'react';
import { useSelector } from 'react-redux';
import useClipboard from 'react-use-clipboard';
function FileLoader() {
const state = useSelector((state) => state);
const dataForHtml = `<img src="${state.png}" alt="banner" />`;
const [isCopiedHtml, setCopiedHtml] = useClipboard(dataForHtml);
return (
<button className="btn" onClick={setCopiedHtml}>
Save
</button>
);
}
export default FileLoader;
The beginning of my test:
import { render, screen } from '#testing-library/react';
import React from 'react';
import userEvent from '#testing-library/user-event';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
import FileLoader from '../src/components/FileLoader/FileLoader';
describe('save btns', () => {
const initialState = {
png: 'some_img_url'
},
const mockStore = configureStore()
let store
it('should save html', () => {
store = mockStore(initialState)
const { getByText } = render(<Provider store={store}><FileLoader /></Provider>)
const saveHtmlBtn = screen.findByText('html');
saveHtmlBtn.then((save) => {
userEvent.click(save)
});
})
})
Related
I've been studying react and developing an app, but i got a problem using context. In one component I create the context and provide its value, but when I try to use the current value of context in another component, I have userName:undefined in console.log(). Code:
import React, { useContext, useState } from 'react'
import EnterRoom from '../../Pages/EnterRoom'
import NameChoose from '../../Pages/NameChoose'
import Room from '../../Pages/Room'
export const AllInformation = React.createContext({userName:'default'}) as any
const InformationContextProvider:React.FC = () => {
const [userInformation,setUserInformation] = useState({userName:'newValue'})
return (
<AllInformation.Provider value={{userInformation, setUserInformation}}>
<Room/>
<NameChoose/>
<EnterRoom/>
</AllInformation.Provider>
)
}
export default InformationContextProvider
export function useInformationContext(){
const {userInformation} = useContext(AllInformation)
return { userInformation }
}
And i try to use here:
import React, { useState, FormEvent, useEffect, useContext} from 'react';
import './styles.css'
import { Link, useHistory } from 'react-router-dom';
import { useInformationContext } from '../../components/Context/index'
import io from 'socket.io-client';
function EnterRoom() {
const [roomId, setRoomId] = useState('');
const [name, setName] = useState('');
const history = useHistory();
const socket = io('http://localhost:3333');
const { userInformation } = useInformationContext()
useEffect(() => {
if(sessionStorage.getItem('userName') || sessionStorage.getItem('roomId')) {
history.push('/')
}
console.log({ userInformation })
})
return <h1>hello word</h1>
}
useInformationContext is destructing the context value, which means it's doing value.userInformation, but that key does not exist. Remove the destructuring since the value is not nested:
export function useInformationContext() {
// no destructuring needed here
const userInformation = useContext(AllInformation)
return { userInformation }
}
return directly the useContext hook without destructing.
export function useInformationContext() {
return useContext(AllInformation)
}
I have a React app that I need to test. It's using the useContext() hook to create Provider that are using in most of my components. I have a dedicated component to handle a Context (lets say UserContext for the example) that look like that:
UserContext.jsx:
import React from 'react'
export const UserContext = React.createContext(undefined)
export const UserProvider = (props) => {
const [currentUser, setCurrentUser] = React.useState(undefined)
const context = {
currentUser,
setCurrentUser,
}
return (
<UserContext.Provider value={context}>
{props.children}
</UserContext.Provider>
)
}
So you can use the Provider like that:
import { UserProvider } from './context/UserContext'
<UserProvider>
{ ... }
</UserProvider>
Now I need to test a component that use this UserContext so let's say UserModal:
UserModal.test.jsx
import React from 'react'
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { UserProvider, UserContext } from '../context/UserContext'
import UserModal from '../components/UserModal'
// D A T A
import exampleUser from '../data/user.json' // Load user's data from a json file
describe('<UserModal />', () => {
let wrapper
const Wrapper = () => {
const { setCurrentUser } = React.useContext(UserContext)
React.useEffect(() => {
// Init UserContext value
setCurrentUser(exampleUser)
}, [])
return (
<UserProvider>
<UserModal />
</UserProvider>
)
}
beforeEach(() => {
wrapper = mount(<Wrapper />)
})
})
Problem is that when <UserModal /> is mounted inside of the <UserProvider>, I get an error that the currentUser in the UserContext is undefined. This error make sense because I call setCurrentUser() when the component is mounted once using React.useEffect(() => { }, []).
So have you an idea how I can mount() my <UserModal /> component inside of a context's provider in the way that the context is not undefined?
Your test should look like this:
import React from 'react'
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { UserProvider, UserContext } from '../context/UserContext'
import UserModal from '../components/UserModal'
// D A T A
import exampleUser from '../data/user.json' // Load user's data from a json file
describe('<UserModal />', () => {
let wrapper
const Wrapper = () => {
const { setCurrentUser } = React.useContext(UserContext)
React.useEffect(() => {
// Init UserContext value
setCurrentUser(exampleUser)
}, [])
return (
<UserModal />
)
}
beforeEach(() => {
wrapper = mount(<UserProvider><Wrapper /></UserProvider>)
})
})
Or see codesandbox here - simple test passes.
Note that UserProvider wraps Wrapper and not is used inside. It's like this because if you are using it inside, there is no UserContext to get with useContext hook, therefore there is no setCurrentUser function.
Help write a simple integration test for a component in React (the component uses the useContext hook). The test should verify that buttons were pressed and handlers called (it's my code: https://codesandbox.io/s/lingering-violet-n11hu).
The code of the component that validates the test:
import React, {useContext} from "react";
import {StoreContext} from "../../reducer/context";
import moment from "moment";
import Delay from "../delay/delay";
let queue = Promise.resolve();
const Interface = () => {
const {state, dispatch} = useContext(StoreContext);
const handleLogSet = e => {
const timeout = parseInt(e.target.getAttribute("data-delay"), 10);
const timePress = moment().format("LTS");
queue = queue.then(() => Delay(timeout, timePress)).then(res => dispatch({
type: "SET_LOG", payload: "\n" + res
}));
};
const handleReset = () => {
dispatch({type: "RESET"});
};
return (
<div className="block">
<button className="btn" data-delay="1" onClick={handleLogSet}>Кнопка 1</button>
<button className="btn" data-delay="2" onClick={handleLogSet}>Кнопка 2</button>
<button className="btn" data-delay="3" onClick={handleLogSet}>Кнопка 3</button>
<button className="btn" onClick={handleReset}>Reset</button>
<textarea value={state.join("")} readOnly={true}/>
</div>
);
};
export default Interface;
Tried different test options, but none work. I tried, for example, like this:
import {configure, shallow } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import Interface from "./interface";
import React, { useContext } from "react";
import { StoreContext } from "../../reducer/context";
configure({ adapter: new Adapter() });
const { state } = useContext(StoreContext);
it(`Click by button calls callback`, () => {
const handleLogSet = jest.fn();
const component = shallow(<Interface
state={state}
/>);
component.find(`.button`).simulate(`click`);
expect(handleLogSet).toHaveBeenCalledTimes(1);
});
Various errors were issued, including the following: "Invalid hook call. Hooks can only be called inside of the body of a function component".
I would be very grateful for an example of a working code and a brief explanation. Thanks a lot, everyone!
So everything was simple enough. It is worth noting that when testing components that use useContext using the shallow method of the Enzyme library, there are recognized difficulties. So far, it has not been possible to solve them directly.
The first thing to do is create a custom hook. You can do it like this:
import React, {useContext} from 'react';
export const useAppContext = () => useContext(AppContext);
const AppContext = React.createContext();
export default AppContext;
This is done in order not to use useContext directly in the component under test.
The e2e test itself looks something like this:
import React from "react";
import {configure, shallow} from "enzyme";
import * as AppContext from "../../reducer/context";
import Adapter from "enzyme-adapter-react-16";
import Interface from "./interface";
configure({adapter: new Adapter()});
it(`Click by Set and Reset buttons calls callback`, () => {
const contextValues = {state: ["Mock state"]};
const handleReset = jest.fn();
const handleLogSet = jest.fn();
jest
.spyOn(AppContext, "useAppContext")
.mockImplementation(() => contextValues);
const wrapper = shallow(
<Interface
onReset={handleReset}
onLogSet={handleLogSet}
/>
);
wrapper.find(`.block__btn--reset`).simulate(`click`);
expect(handleReset).toHaveBeenCalledTimes(1);
wrapper.find(`.block__btn--set`).forEach(item => {
item.simulate(`click`);
expect(handleReset).toHaveBeenCalledTimes(1);
});
});
Thus, we mimic the implementation of our custom code and pass this value to the context object.
I am new to testing redux connected components in React and trying to figure out how to test them.
Currently I'm using react-testing-library and having trouble setting up the my renderWithRedux function to correctly setup redux.
Here is a sample component:
import React, { Component } from 'react'
import { connect } from 'react-redux'
class Sample extends Component {
constructor(props) {
super(props);
this.state = {
...
}
}
componentDidMount() {
//do stuff
console.log(this.props)
}
render() {
const { user } = this.props
return(
<div className="sample">
{user.name}
</div>
)
}
}
const mapStateToProps = state => ({
user: state.user
})
export default connect(mapStateToProps, {})(Sample);
Here is a sample test:
import React from 'react';
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import { render, cleanup } from 'react-testing-library';
import Sample from '../components/sample/'
const user = {
id: 1,
name: "John Smith"
}}
function reducer(state = user, action) {
//dont need any actions at the moment
switch (action.type) {
default:
return state
}
}
function renderWithRedux(
ui,
{ initialState, store = createStore(reducer, initialState) } = {}
) {
return {
...render(<Provider store={store}>{ui}</Provider>),
store,
}
}
afterEach(cleanup)
test('<Sample> example text', () => {
const { getByTestId, getByLabelText } = renderWithRedux(<Sample />)
expect(getByText(user.name))
})
The user prop value always results as undefined. I have re-written this a couple of ways but can't seem to get it to work. If I pass the user data directly as a prop to Sample component in the test, it still resolves to be undefined.
I am learning from the tutorials and examples via the offical docs, like this one: https://github.com/kentcdodds/react-testing-library/blob/master/examples/tests/react-redux.js
Any pointers, tips or solutions would be greatly appreciated!
You should wrap the component inside Provider, here is the simple example
import React from 'react';
import { render } from '#testing-library/react';
import '#testing-library/jest-dom';
import { Provider } from "react-redux";
import configureMockStore from "redux-mock-store";
import TestedComponent from '../index';
const mockStore = configureMockStore();
const store = mockStore({});
const renderTestedComponent = () => {
return render(
<Provider store={store}>
<TestedComponent />
</Provider>
);
};
describe('test TestedComponent components', () => {
it('should be render the component correctly', () => {
const { container } = renderTestedComponent();
expect(container).toBeInTheDocument();
});
});
**Unable to Fire event using #testing-library**
// demo.test.js
import React from 'react'
import { Provider } from "react-redux";
import '#testing-library/react/cleanup-after-each'
import '#testing-library/jest-dom/extend-expect'
import { render, fireEvent } from '#testing-library/react'
// this is used to fire the event
// import userEvent from "#testing-library/user-event";
//import 'jest-localstorage-mock';
import ChangePassword from './ChangePassword';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
test('test 1-> Update User password', () => {
// global store
const getState = {
authUser :{
user : {
email: "test#gmail.com",
id: 0,
imageURL: null,
name: "test Solutions",
roleId: 1,
roleName: "testRole",
userName: "testUserName"
},
loading: false,
showErrorMessage: false,
errorDescription: ""
}
}; // initial state of the store
// const action = { type: 'LOGIN_USER' };
// const expectedActions = [action];
// const store = mockStore(getState, expectedActions);
const onSaveChanges = jest.fn();
const changePassword = jest.fn();
const store = mockStore(getState);
const { queryByText, getByLabelText, getByText , getByTestId , getByPlaceholderText, } = render(
<Provider store={store}>
<ChangePassword
onSaveChanges={onSaveChanges}
changePassword={changePassword}
/>
</Provider>,
)
// test 1. check the title of component
expect(getByTestId('updateTitle')).toHaveTextContent('Update your password');
// test 2. chekck the inputfile
expect(getByPlaceholderText('Old Password')) //oldpassword
expect(getByPlaceholderText('New Password')) //newpassword
expect(getByPlaceholderText('Confirm New Password')) //confpassword
// change the input values
fireEvent.change(getByPlaceholderText("Old Password"), {
target: { value: "theOldPasword" }
});
fireEvent.change(getByPlaceholderText("New Password"), {
target: { value: "#Ab123456" }
});
fireEvent.change(getByPlaceholderText("Confirm New Password"), {
target: { value: "#Ab123456" }
});
// check the changed input values
expect(getByPlaceholderText('Old Password').value).toEqual("theOldPasword");
expect(getByPlaceholderText('New Password').value).toEqual("#Ab123456");
expect(getByPlaceholderText('Confirm New Password').value).toEqual("#Ab123456");
expect(getByText('Save Changes')); // check the save change button
// calling onSave function
//fireEvent.click(getByTestId('savechange'))
// userEvent.click(getByText('Save Changes'));
})
I am building out an electron app with React and trying to use Material-UI for the UI elements. I added a datepicker and timepicker to a component and the input shows up in the electron app, however when you click on it, nothing happens. Not sure what I'm missing in order to get this to work correctly
Component :
import React, { Component } from 'react';
import DatePicker from 'material-ui/DatePicker';
import TimePicker from 'material-ui/TimePicker';
export default class Schedule extends Component {
render() {
return (
<div>
Pick a date :
<DatePicker id="date"/>
and time :
<TimePicker id="time"/>
</div>
)
}
}
index.js :
import 'babel-polyfill'; // generators
import React from 'react';
import { render as renderReact } from 'react-dom';
import debounce from 'debounce';
import configureStore from './store/configureStore';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
const state = JSON.parse(localStorage.getItem('state'));
const store = configureStore(state || {});
let App = require('./components/app').default;
const render = (Component) => {
renderReact(<MuiThemeProvider><Component {...store} /></MuiThemeProvider>, document.getElementById('root'));
};
if (module.hot) {
module.hot.accept('./components/app', function() {
let newApp = require('./components/app').default;
render(newApp);
});
}
const saveState = debounce(() => {
localStorage.setItem('state', JSON.stringify(store.getState()));
}, 1000);
store.subscribe(() => {
saveState();
render(App);
if (process.env.ENV === 'development') {
console.log('state', store.getState());
}
});
store.dispatch({ type: 'APP_INIT', store });
In your app.js, you need to import and execute the following plugin:
const injectTapEventPlugin = require('react-tap-event-plugin');
injectTapEventPlugin();