gazzwi86
gazzwi86

Reputation: 1030

State is undefined when simulating click in react component test

I have been trying to test the behavior of my method handleFormSubmit(). When submit is clicked, the call should be triggered. This is working absolutely fine until I add the two lines listed below. It seems enzyme is updating the context or not calling the constructor?

Any help, much appreciated. I've stripped out what I can to keep the post brief.

The lines that fail - state is undefined

data.username = this.state.username.trim();
data.password = this.state.password.trim();

Component:

import React from 'react';

// Renders login form.
export default class LoginView extends React.Component {
  static propTypes = {
    model: React.PropTypes.object.isRequired,
    params: React.PropTypes.object.isRequired
  }

  constructor(props) {
    super(props);
    this.state = {
      username: '',
      password: ''
    };
  }

  handleUsernameChange(event) {
    this.setState({error: false, username: event.target.value});
  }

  handlePasswordChange(event) {
    this.setState({error: false, password: event.target.value});
  }

  handleFormSubmit(event) {
    event.preventDefault();

    let failureMsg = {},
      data = this.props.params,
      options = {
        success: (response) => {
          if (response.attributes.successfulLogin) {
            window.location = this.props.params.redirect +
              '?authenticationToken=' + response.get('authenticationToken') +
              this.createParams();
          } else {
            throw Error('Helpful error');
          }
        },
        error: () => {
          throw Error('Helpful error');
        }
      };

    // PROBLEM LINES!
    data.username = this.state.username.trim();
    data.password = this.state.password.trim();

    if (this.state.username && this.state.password && this.state.terms) {
      this.props.model.save(data, options);
    } else {
      this.setState({
        error: true,
        errorMessage: !this.state.username || !this.state.password ? LoginConstants.LOGIN.BLANK_INPUT : LoginConstants.LOGIN.BLANK_TERMS
      });
    }
  }

  render() {
    return (
      <form>
        <input type="text" ref="username" id="username" placeholder="Customer ID" onChange={this.handleUsernameChange.bind(this)} maxLength={75} value={this.state.username} />
        <input type="password" ref="password" id="password" placeholder="Password" onChange={this.handlePasswordChange.bind(this)} maxLength={75} value={this.state.password} />
        <button type="submit" ref="login" id="login" className="Button Button--primary u-marginT-md" onClick={this.handleFormSubmit.bind(this)}>LOGIN</button>
      </form>
    );
  }
}

Unit test:

  import React from 'react';
  import { mount } from 'enzyme';
  import LoginView from './LoginView';

  describe('Login screen', () => {
    let mountedComp;

    beforeAll(() => {
      mountedComp = mount(<LoginView />);
    });

    it('should show error if submitted blank', () => {
      mountedComp.instance().state = {
        username: '',
        password: ''
      };

      expect(mountedComp.find('#js-errorMessage').length).toEqual(0);
      mountedComp.ref('login').simulate('click', { preventDefault: () => undefined, state: {} });
      expect(mountedComp.find('#js-errorMessage').length).toBeGreaterThan(0);
    });
  });

Upvotes: 0

Views: 947

Answers (4)

gazzwi86
gazzwi86

Reputation: 1030

I managed to get around this by passing my params manually to my jsx object. I'm not sure this is the proper way to test, but it worked for now.

<LoginView model={model} params={params} />

Upvotes: 0

Michał Pierzchała
Michał Pierzchała

Reputation: 1850

I think the source of the problem may lay in this very line:

let data = this.props.params

In JavaScript it will not copy this.props.params object to data object by value, but by reference. It will set the same reference for data and this.props.params – which means if you change contents of data it will be reflected in this.props.params and vice-versa. So you're actually mutating props which are supposed to be immutable.

You should rather create a shallow copy, e.g. like that:

let data = Object.assign({}, this.props.params)

Upvotes: 0

Saif Ali Khan
Saif Ali Khan

Reputation: 848

try binding your onClick function in <form onClick={this.handleFormSubmit.bind(this)} >

Upvotes: 1

Hitesh Ranaut
Hitesh Ranaut

Reputation: 747

Looks like your "this.state: is out of scope. consider adding

var _self = this;

and use

 _self.state.username.trim();
 _self.state.password.trim();

Upvotes: 0

Related Questions