John Lippson
John Lippson

Reputation: 1319

Simulating form change in Enzyme/React unit test isn't changing field value

I'm making some basic form interaction tests

const setup = (errors=false) => {
  let props = {
    budget: {}, errors: {},
    onChange: () => {},
    onSave: () => {}
  };

  if (errors === true) {
    props.errors.budgetCategory= "error";
  }

  return shallow(<AddBudgetForm {...props} />);
};


describe("Fluctuating errors based on input values", () => {
  it("Renders an error when a number is present in the cost field", () => {
    let wrapper = setup();
    wrapper.find('[name="budgetCategory"]').simulate('change', { target: { value: '7' } });
    console.log(wrapper.find('[name="budgetCategory"]').props());
  });
});

I analyze the props on the console and see that the value field is still undefined...

{ className: 'form-control',
  placeholder: 'Enter Name of Budget Category',
  onChange: [Function: onChange],
  value: undefined,
  name: 'budgetCategory' }

Ideally what I am trying to test is that simulating the key presses of something that isn't a number should trigger the onChange handler to propogate errors to the form.

I tried adding a new onChange handler in the setup, but didn't work:

  let props = {
    budget: {}, errors: {},
    onChange: (e) => {props.budget.budgetCategory = e.target.value;},
    onSave: () => {}
  };

AddBudgetForm component

import React, { Component, PropTypes } from 'react';
import Alert from '../common/Alert';
import TextInput from '../common/TextInput';

const renderDays = () => {
  return Array(31).fill().map((_, i = 1) => <option key={i+1}>{i+1}</option>);
};

const errorsInForm = errors => {
  let error = false;
  Object.keys(errors).map(item => {
    if (errors[item]) { error = true; }
  });

  return error;
};

const generateValidationError = error => {
  return (
    <span style={{color: "red"}}>{error}</span>
  );
};

const AddBudgetForm = ({budget, onChange, onSave, errors}) => {
  return (
    <div name="AddBudgetForm">
      <form>
      {!errorsInForm(errors) &&
        <Alert
          name="add-budget-alert"
          alertType = "alert alert-info"
          fontAwesomeIcon = "fa fa-info"
          alertDescription = " Adding a budget is simple. Add a category such as groceries
          , allocate $200.00 per month and the day you'd like the budget to reset."
        />
      }

      {errorsInForm(errors) &&
        <Alert
          name="add-budget-alert"
          alertType = "alert alert-danger"
          fontAwesomeIcon = "fa fa-warning"
          alertDescription = " There are problems with the form submission. Ensure all values in the form are valid."
        />
      }

    <TextInput
        className="form-control"
        placeholder="Enter Name of Budget Category"
        onChange={onChange}
        value={budget.category}
        name="budgetCategory"
      />
    {errors.budgetCategory != "" && generateValidationError(errors.budgetCategory)}

      <div className="form-group input-group">
        <span className="input-group-addon"><i className="fa fa-usd"></i></span>
        <input
          className="form-control"
          placeholder="Monthly Budget Cost"
          onChange={onChange}
          value={budget.cost}
          name="budgetCost"
        />
      </div>
    {errors.budgetCost != "" && generateValidationError(errors.budgetCost)}

      <select
        className="form-control"
        onChange={onChange}
        value={budget.date}
        name="budgetDate"
      >
        <option>Select Day of Month Budget Item is Due</option>
          {renderDays()}
      </select>
    {errors.budgetDate != "" && generateValidationError(errors.budgetDate)}

      <br/>
      {(!errorsInForm(errors)) &&
        <button className="btn btn-primary" type="submit" onClick={() => onSave(budget)}>Add Budget</button>
      }
      {(errorsInForm(errors)) &&
        <button className="btn btn-primary" type="submit" disabled>Fix Form Errors</button>
      }
      </form>
    </div>
  );
};

AddBudgetForm.propTypes = {
  budget: PropTypes.object,
  onChange: PropTypes.func,
  onSave: PropTypes.func,
  errors: PropTypes.object
};

export default AddBudgetForm;

Upvotes: 0

Views: 389

Answers (2)

Krasimir
Krasimir

Reputation: 13529

You are using a selector that targets actual DOM element right wrapper.find('[name="budgetCategory"]'). I assume that inside TextInput you have an input that gets the same name. Why not select the TextInput directly and call its prop like for example:

wrapper.find('TextInput').prop('onChange')(<some value here>)

Upvotes: 0

tgallacher
tgallacher

Reputation: 1672

From the docs, the .simulate() method only affects the event prop for the event you are simulating. In this case, the synthetic event args you passed will only be supplied to your onChange() function. The actual value props will be unaffected.

To confirm, simply update your custom onChange() handler to console.log the event object supplied to it, e.g.

let props = {
    budget: {}, errors: {},
    onChange: (event) => { console.log(event); },
    onSave: () => {}
};

Another gotcha on this, is that normal event bubbling won't happen with this simulation -- make sure to run the .simulate() method directly on the node you want to trigger the event.

Refactored AddBudgetForm NB: These are just minor suggestions, and are not necessarily the only correct way to do it.

import React, { Component, PropTypes } from 'react';
import Alert from '../common/Alert';
import TextInput from '../common/TextInput';

const renderDays = () => Array(31).fill().map( 
    (_, i = 1) => <option key={i+1}>{i+1}</option> 
);

/**
 * Returns true if a key has a non-null value.
 * @param  {Object} errors - Errors object
 * @return {Boolean} Is there an error?
 */
const errorsInForm = errors => 
    Object.keys(errors).reduce( (hasError, item) => hasError || item != null, false);

const generateValidationError = error => <span style={{color: "red"}}>{error}</span>;

const AddBudgetForm = ({ budget, onChange, onSave, errors = {} }) => (
  <div name="AddBudgetForm">
    <form>
    { ! errorsInForm(errors)
      ? (
      <Alert
        name="add-budget-alert"
        alertType = "alert alert-info"
        fontAwesomeIcon = "fa fa-info"
        alertDescription = " Adding a budget is simple. Add a category such as groceries
        , allocate $200.00 per month and the day you'd like the budget to reset."
      />)
      : (
      <Alert
        name="add-budget-alert"
        alertType = "alert alert-danger"
        fontAwesomeIcon = "fa fa-warning"
        alertDescription = " There are problems with the form submission. Ensure all values in the form are valid."
      />
      )
    }

  <TextInput
      className="form-control"
      placeholder="Enter Name of Budget Category"
      onChange={onChange}
      value={budget.category}
      name="budgetCategory"
    />
  { errors.budgetCategory != "" && 
    generateValidationError(errors.budgetCategory)
  }

    <div className="form-group input-group">
      <span className="input-group-addon"><i className="fa fa-usd"></i></span>
      <input
        className="form-control"
        placeholder="Monthly Budget Cost"
        onChange={onChange}
        value={budget.cost}
        name="budgetCost"
      />
    </div>
  { errors.budgetCost != "" && 
    generateValidationError(errors.budgetCost) 
  }

    <select
      className="form-control"
      onChange={onChange}
      value={budget.date}
      name="budgetDate"
    >
      <option>Select Day of Month Budget Item is Due</option>
        { renderDays() }
    </select>
  { errors.budgetDate != "" && 
    generateValidationError(errors.budgetDate)
  }

    <br/>
    { ! errorsInForm(errors) 
      ? <button className="btn btn-primary" type="submit" onClick={() => onSave(budget)}>Add Budget</button>
      : <button className="btn btn-primary" type="submit" disabled>Fix Form Errors</button>
    }
    </form>
  </div>
);

AddBudgetForm.propTypes = {
  budget: PropTypes.object,
  onChange: PropTypes.func,
  onSave: PropTypes.func,
  errors: PropTypes.object
};

export default AddBudgetForm;

Upvotes: 1

Related Questions