Test properties that injected by React Redux - javascript

I have a component that renders a button if a property errorMessage is not null.
class App extends Component {
static propTypes = {
// Injected by React Redux
errorMessage: PropTypes.string,
resetErrorMessage: PropTypes.func.isRequired,
};
renderErrorMessage() {
const { errorMessage } = this.props;
if (!errorMessage) return null;
return (
<p id="error-message">
<b>{errorMessage}</b>{' '}
<button id="dismiss" onClick={this.props.resetErrorMessage()}>
Dismiss
</button>
</p>
);
}
render() {
return (
<div className="app">
{this.renderErrorMessage()}
</div>
);
}
}
The property injected by React Redux:
import { connect } from 'react-redux';
import App from '../components/App/App';
const mapStateToProps = (state, ownProps) => ({
errorMessage: state.errorMessage,
});
export default connect(mapStateToProps, {
resetErrorMessage: () => ({
type: 'RESET_ERROR_MESSAGE',
})
})(App);
As you can see I also have resetErrorMessage that clears errorMessage:
const errorMessage = (state = null, action) => {
const { type, error } = action;
if (type === RESET_ERROR_MESSAGE) {
return null;
} else if (error) {
return error;
}
return state;
};
How can I test my component and say if I click the button then button hides or if errorMessage is not null button shows?
I want to get something like this:
const props = {
errorMessage: 'Service Unavailable',
resetErrorMessage,
};
it('renders error message', () => {
const wrapper = shallow(<App {...props} />);
expect(wrapper.find('#error-message').length).toBe(1);
wrapper.find('#dismiss').simulate('click');
expect(wrapper.find('#error-message').length).toBe(0);
});
But now my problem is that if I simulate click to dismiss button - error message doesn't hide.

As I posted in the previous question you deleted, if you want to test button clicks your best bet would be to call the 'unconnected' component. If you want to test the connected component, then you have to pass a mockstore into it like so.
const wrapper = shallow(<App {...props} store={store} />);
So import the app in your test and just pass the resetErrorMessage function as a mocked function, such as what you do with jest.
const resetErrorMessage = jest.fn(() => {});
const wrapper = shallow(<App {...props} resetErrorMessage={resetErrorMessage} />);
wrapper.find('#dismiss').simulate('click');
expect(resetErrorMessage).toHaveBeenCalled();
My advice would be to only test the connected component when you want to manipulate directly from store changes.

Related

How to mock machine state in jest in react?

Below is the snippet for Homepage with its unit test in jest, which will display error message if state.includes('error') and display welcome message if state.includes('success').
while I run a unit test for Homepage component, beginning state is initial and then always changes to error skipping success state without any reason, which fails the unit test. Is there a way to inject the state inside the Apptest in the unit test so that it always get success state and skip error state.
log for state
console.log
<----- State is -------> initial
console.log
<----- State is -------> homePage.error
Can we mock the state so that new state is always homepage.success?
import React from 'react';
const Homepage = props => {
const { appState, globalDispatch, globalStore } = props;
const { store, transition, state } = appState;
const { welcomeMessage } = globalStore;
//useEffect hook to check the state
useEffect(() => {
console.log('<----- State is ------->', state);
});
return(
<React.Fragment>
{state.includes('error') ? (
<p>
There was error processing your request
</p>
): null}
{state.includes('success') ? (
<p>
{welcomeMessage}
</p>
): null}
</React.Fragment>
)
}
And the unit test for above component is
import React from 'react';
import { IntlProvider } from 'react-intl';
const AppTest = props => {
const { additionalData } = props;
const { globalDispatch, globalStore } = useMothershipContext();
const { theme } = useTheme({
globalStore,
globalDispatch
});
return (
<ThemeProvider theme={theme}>
<IntlProvider>
<Homepage
globalDispatch={globalDispatch}
globalStore={globalStore}
additionalData={additionalData}
/>
</IntlProvider>
</ThemeProvider>
);
};
beforeEach(() => {
jest.setTimeout(60000);
});
test('Stage 1.0 - Display Welcome Message', async () => {
const { getByText } = render(
<TestWrapper>
<AppTest additionalData={'Welcome Lorem Ipsum'} />
</TestWrapper>
);
await waitFor(() => getByText('Welcome Lorem Ipsum'));
const welcomeMessage = getByText('Welcome Lorem Ipsum');
expect(welcomeMessage).toBeTruthy();
});
And machine is
import { Machine } from 'xstate';
export default Machine({
id: 'homePage',
initial: 'initial',
states: {
initial: {
on: {
INITIALIZE_HOMEPAGE_SUCCESS: 'success',
INITIALIZE_HOMEPAGE_ERROR: 'error'
}
}
}
})

Enzyme mount wrapper is empty after simulate('click') in ReactApp

I'm trying to test a registration component that has a Vertical Stepper with Jest/Enzyme and I keep hitting a wall when trying to simulate the user clicking "Next" .
expected behavior is to do nothing if the "Required" input fields are empty, however after doing the .simulate('click') following assertions fail with not finding any html in the wrapper.
The component is passed through react-redux connect() so I don't know if that would be related.
UserRegistration.js
import React from 'react';
import { connect } from 'react-redux';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step;
import StepLabel from '#material-ui/core/StepLabel;
import StepContent from '#material-ui/core/StepContent'
class UserRegistration extends React.Component {
constructor(props){
this.state = {
activeStep: 0,
inputData: {},
...
}
}
getStepContent = () => {
switch(this.state.activeStep)
case '...':
return
(<>
<input test-data="firstName"/>
...
</>);
...
}
render () {
const steps = ['Personal Info', 'Corporate Details', ...]
return (
<Stepper activeStep={this.state.activeStep} orientation="vertical">
{steps.map((label, index) => {
return (
<Step key={index}/>
<StepLabel>{label}</StepLabel>
<StepContent>
{this.getStepContent()}
<button data-test="btn-next" onClick={() => this.goNext()}> NEXT </button>
<button onClick={() => this.goBack()}> BACK </button>
)
}
}
</Stepper>
)
}
}
const mapStateToProps = () => {...}
const mapDispatchToProps = () => {...}
export default connect(mapStateToProps, mapDispatchToProps)(UserRegistration)
UserRegistration.test.js
const wrapper = mount(
<Provider store={store}
<UserCreate/>
</Provider>
)
it('Confirm REQUIRED fields rendered', () => {
expect(wrapper.find("input[data-test='firstName']").length).toEqual(1);
// PASS
});
it('Check if still on same step clicked NEXT with no user data', () => {
wrapper.find("button[data-test='btn-next']").simulate('click');
expect(wrapper.find("input[data-test='firstName']").length).toEqual(1);
// Expected value to equal: 1, Received: 0
})
Same outcome regardless of the element I'm looking up.
Any suggestions will be greatly appreciated.
You need to update. So you would change it:
it('Check if still on same step clicked NEXT with no user data', () => {
wrapper.find("button[data-test='btn-next']").simulate('click');
// Add this line
wrapper.update();
const button = wrapper.find("input[data-test='firstName']");
expect(button.length).toEqual(1);
// Expected value to equal: 1, Received: 0
});
Then the test should work as you intend.

Jest/Enzyme | Redux prop is not defined in test

I am using React-Redux, in a connected component and I want to test if a particular component is rendered. In order for that component to render 2 things must be true:
ListUsers must be an empty array
The securityMode should be basic.
I have already defined the securityMode in my component Props, with no problem. But the ListUsers prop, is coming through redux.
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
This is my component logic that should be tested:
renderNoResourceComponent = () => {
const { usersList, securityMode } = this.props;
const { selectedGroups } = this.state;
const filteredData = filterUserData(usersList, selectedGroups);
if (filteredData && filteredData.length === 0 && securityMode === 'BASIC') {
return (
<div className="center-block" data-test="no-resource-component">
<NoResource>
.............
</NoResource>
</div>
);
}
return null;
};
And this is the test I wrote:
describe('BASIC securityMode without Data', () => {
const props = {
securityMode: 'BASIC',
listUsers: () => {},
usersList: [] // This is the redux prop
};
it('should render NoResource component', () => {
const wrapper = shallow(<UsersOverviewScreen {...props} />);
const renderUsers = wrapper.find(`[data-test="no-resource-component"]`);
expect(renderUsers).toHaveLength(1);
});
});
But I get an error saying the userLists is not defined. How do I pass this redux prop so my component would pass. `I also need that prop for another set of tests, that needs data, which I need to mock.
Can someone guide me through this? Thank you..
What you want to do is export the component before its connocted to Redux and pass all the props it needs manually:
export class UsersOverviewScreen extends Component {
// ... your functions
render() {
return (
// ... your componont
);
}
}
function mapStateToProps(state) {
return {
securityMode: securityModeSelector(state),
usersList: state.users.list,
usersListFetching: state.users.listFetching
};
}
export default connect(mapStateToProps)(UsersOverviewScreen);
Now, in your tests you can import { UsersOverviewScreen } form 'path/to/UsersOverviewScreen';. You can create the props and pass it to the component like this:
const mockUsersLists = jest.fn(() => usersList || []);
const wrapper = shallow(<UsersOverviewScreen {...props} usersList={mockUsersLists} />);

Action doesn't update the store

|I have the following component based on this:
**WarningModal.js**
import React from 'react';
import ReactDOM from 'react-dom';
import {connect, Provider} from 'react-redux';
import PropTypes from 'prop-types';
import {Alert, No} from './pure/Icons/Icons';
import Button from './pure/Button/Button';
import Modal from './pure/Modal/Modal';
import {setWarning} from '../actions/app/appActions';
import configureStore from '../store/configureStore';
const store = configureStore();
export const WarningModal = (props) => {
const {message, withCleanup} = props;
const [
title,
text,
leave,
cancel
] = message.split('|');
const handleOnClick = () => {
props.setWarning(false);
withCleanup(true);
}
return(
<Modal>
<header>{title}</header>
<p>{text}</p>
<Alert />
<div className="modal__buttons-wrapper modal__buttons-wrapper--center">
<button
onClick={() => withCleanup(false)}
className="button modal__close-button button--icon button--icon-only button--text-link"
>
<No />
</button>
<Button id="leave-warning-button" className="button--transparent-bg" onClick={() => handleOnClick()}>{leave}</Button>
<Button id="cancel-warning-button" onClick={() => withCleanup(false)}>{cancel}</Button>
</div>
</Modal>
);
}
WarningModal.propTypes = {
withCleanup: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
setWarning: PropTypes.func.isRequired
};
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const WarningModalContainer = connect(mapStateToProps, {
setWarning
})(WarningModal);
export default (message, callback) => {
const modal = document.createElement('div');
document.body.appendChild(modal);
const withCleanup = (answer) => {
ReactDOM.unmountComponentAtNode(modal);
document.body.removeChild(modal);
callback(answer);
};
ReactDOM.render(
<Provider store={store}>
<WarningModalContainer
message={message}
withCleanup={withCleanup}
/>
</Provider>,
modal
);
};
the issue I have is that 'setWarning' doesn't update the state, it does get called as I have a debugger inside the action and the reducer but the actual property doesn't not change to 'false' when:
props.setWarning(false);
gets called.
I use the following to trigger the custom modal:
const togglePromptCondition =
location.hash === '#access-templates' || location.hash === '#security-groups'
? promptCondition
: isFormDirty || isWarning;
<Prompt message={promptMessage} when={togglePromptCondition} />
To test this even further I have added 2 buttons in the application to toggle 'isWarning' (the state property I am talking about) and it works as expected.
I think that although WarningModal is connected in actual fact it isn't.
REDUCER
...
case SET_WARNING:
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...
ACTION
...
export const setWarning = status => {
console.log('action called')
return {
type: SET_WARNING,
payload: status
}
};
...
UPDATE
After having to incorporates the following:
const mapStateToProps = state => {
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
I am now getting:
Maybe this could help?
You have to dispatch the actions in the action creator and the type of the action to dispatch should be always string.
Try this
const mapStateToProps = state => {
console.log(state)
return {
isWarning: state.app.isWarning
}
};
const mapDispatchToProps = dispatch => {
console.log(dispatch)
return {
setWarning: (status) => dispatch({ type: 'SET_WARNING', payload: status })
}
};
const WarningModalContainer = connect(mapStateToProps, mapDispatchToProps)(WarningModal);
REDUCER
...
case 'SET_WARNING':
console.log('reducer called: ', action)
return {
...state,
isWarning: action.payload
};
...

Redux-thunk with redux-form - not dispatching

Long post below, but not complicated!
I have setup my form:
NewCommentForm Component
class NewCommentForm extends Component {
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<Field component="input" type="text" name="title"/>
<Field component="textarea" type="text" name="content"/>
</form>
)
}
}
const mapStateToProps = (state) => ({})
// Actions are imported as 'import * as action from '../actions/comments'
NewCommentForm = connect(mapStateToProps, actions)(NewCommentForm)
NewCommentForm = reduxForm({
form: 'newComment',
onSubmit: actions.postComment // This is the problem!
})(NewCommentForm);
RemoteSubmitButton Component
class RemoteSubmitButton extends Component {
render() {
const { dispatch } = this.props;
return (
<button
type="button"
onClick={() => dispatch(submit('newComment'))}>Submit</button>
)
}
}
RemoteSubmitButton = connect()(RemoteSubmitButton);
Everything wrapped in NewComment Component:
class NewComment extends Component {
render() {
return (
<div className="new-comment">
<NewCommentForm />
<RemoteSubmitButton />
</div>
)
}
}
The problem is with the postComment function:
export const postComment = (comment) => {
console.log("Post comment - first;") // THIS ONE GETS CALLED
return (dispatch) => {
console.log("Post comment - second"); // THIS ONE IS NEVER CALLED
return api.postComment(comment).then(response => {
dispatch({
type: 'POST_COMMENT_SUCCESS',
response
});
});
}
}
that gets its api.postComment from another file:
export const postComment = (comment) => {
return axios.post(post_comment_url, {
comment
}).then(response => {
return response;
});
}
I have redux-thunk setup in my store:
import thunk from 'redux-thunk';
const configureStore = (railsProps) => {
const middlewares = [thunk];
const store = createStore(
reducers,
railsProps,
applyMiddleware(...middlewares)
);
return store;
};
Why after submitting the form using the RemoteSubmitButton the second part of the postComment function is never called? What did I do wrong?
The problem is because you are trying to use the action that is not connected with the react-redux connect. You have to use it inside the component that is connected to the redux.

Categories

Resources