Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues while testing Bootstrap components. #297

Closed
Nutelac opened this issue Dec 3, 2014 · 33 comments
Closed

Issues while testing Bootstrap components. #297

Nutelac opened this issue Dec 3, 2014 · 33 comments
Labels
docs Documentation related question

Comments

@Nutelac
Copy link

Nutelac commented Dec 3, 2014

I found few issues while testing app with React.addons.TestUtils.

1. ref does not work on ModalTrigger children

code
var Modal = React.createClass({
  render: function(){
    return Bootstrap.ModalTrigger({
      modal: Bootstrap.Modal({
        title: 'modal'
      })
    }, Bootstrap.Button({
      ref: 'button'
    }));
  }
});
test
var modal = TestUtils.renderIntoDocument(Modal(null));
expect(modal.refs.button).toBeTruthy(); // Expected undefined to be truthy.

2. can't change input value via TestUtils.Simulate.change

code
var Input = React.createClass({
  render: function(){
    return Bootstrap.Input({
      ref: 'input',
      type: 'text'
    });
  }
});
test
var input = TestUtils.renderIntoDocument(Input(null));
TestUtils.Simulate.change(input.refs.input.getDOMNode(), {
  target: {
    value: 'foo'
  }
});
expect(input.refs.input.getValue()).toBe('foo'); // Expected: null toBe: 'foo'

Is this expected behaviour or bug? Thank you.

@Nutelac
Copy link
Author

Nutelac commented Dec 4, 2014

Problem no. 1 explained here #202.

@paufabregat
Copy link

Same problem with no.2, did you figure out some workaround ?
Someone posted sort of the same issue on the react repo facebook/react#3151
but still no answers

@mathieumg
Copy link
Member

There is an answer now: facebook/react#3151 (comment)

@mtscout6 mtscout6 added question docs Documentation related labels Feb 27, 2015
@mtscout6
Copy link
Member

I think this is a good time to state that we should add some documentation on how to test components that use react-bootstrap components.

@dozoisch dozoisch added this to the 1.0.0 Release milestone May 3, 2015
@mbfisher
Copy link

Setting value directly works, however I'm still unable to get the onChange handler to be called using Simulate:

var Input = React.createClass({
  render: function(){
    return Bootstrap.Input({
      ref: 'input',
      type: 'text'
      onChange: function () {
        console.log('CHANGE');
      }
    });
  }
});

var input = TestUtils.renderIntoDocument(Input(null));

input.getInputDOMNode().value = 'foo';
expect(input.getValue()).to.eql('foo'); // all good

TestUtils.Simulate.change(input); // does nothing

@cgreening
Copy link

Try changing the code to:

TestUtils.Simulate.change(input.getInputDOMNode());

@AlexKVal
Copy link
Member

  1. ref does not work on ModalTrigger children

Now it works fine. The prove #916

@AlexKVal
Copy link
Member

2 can't change input value via TestUtils.Simulate.change

Input component is under heavy refactoring. #342

@chiedo
Copy link

chiedo commented Jul 7, 2015

I'm having the same issues described above by @mbfisher. Any ideas as to why the following does nothing? Having no luck trying to figure this out. Thanks in advance!

TestUtils.Simulate.change(input.getDOMNode());

@mathieumg
Copy link
Member

You need to do input.getInputDOMNode(). Notice the Input.

@chiedo
Copy link

chiedo commented Jul 7, 2015

Hey @mathieumg,
Thanks for the fast response. That doesn't work though since I'm getting the raw inputs I assume. This is ultimately what I have.

    inputs = TestUtils.scryRenderedDOMComponentsWithTag(component, "input");

    titleInput = inputs[0];

    titleInput.getDOMNode().value = "foo";
    TestUtils.Simulate.change(titleInput.getDOMNode());

@mathieumg
Copy link
Member

But then it's not the React-Bootstrap component, is it?

@chiedo
Copy link

chiedo commented Jul 7, 2015

I'm not sure @mathieumg
I am creating my inputs using react-bootstrap and adding my onChange event to the react-bootstrap input.

I'm assuming that react bootstrap just adds onChange listener onto the input it creates... but maybe I'm wrong.

One of those days... brick wall after brick wall trying to get these tests written.

Thanks for taking the time so far though!

@mathieumg
Copy link
Member

But from the snippet you just showed, it seems you're not using React-Bootstrap's Input?

@chiedo
Copy link

chiedo commented Jul 7, 2015

Below is my component:

/*
 * New Event form
 */
var React              = require('react');
var Link               = require('react-router').Link;
var BS                 = require('react-bootstrap');
var Input              = BS.Input;
var Button             = BS.Button;
var Col                = BS.Col;
var FormStateMixin     = require('../../mixins/FormState.js');
var EventActionCreator = require('../../actions/EventActionCreator.js');
var Navigation         = require('react-router').Navigation;
var moment             = require('moment');
var DateTimeField      = require('react-bootstrap-datetimepicker');
var _                  = require('lodash');


var NewEventForm =
  React.createClass({
    mixins: [
      FormStateMixin,
      Navigation
    ],
    getInitialState: function() {
      return {
        title: '',
        address: '',
        city: '',
        state: '',
        postal_code: '',
        country: '',
        description: '',
        start_time: String(Date.now()),
        // Defaults to having an endtime which is 2 hours later. The below is in milliseconds
        end_time: String(Date.now() + (2 * 60 * 60 * 1000)),
        errors: null
      };
    },
    handleKeyDown: function(e) {
      var ENTER = 13;
      if( e.keyCode == ENTER ) {
        this.handleCreateEventClick();
      }
    },

    isValidStartEndTime: function() {
      if (this.state.end_time < this.state.start_time) {
        alert("An event cannot end before it even starts!");
        var newEndTime = parseInt(this.state.start_time) + (2 * 60 * 60 * 1000);
        this.setState({
          end_time: String(newEndTime)
        });
      } else {
        return true;
      }
    },

    handleCreateEventClick: function() {
      if (this.isValidStartEndTime()) {
        EventActionCreator.createEvent(this.state);

        this.setState({
          errors: null
        });

        // navigates back to home page
        this.transitionTo('home');
      }

    },
    _handleStartTimeChange: function(ev) {
      var nextState = _.cloneDeep(this.state);
      // Update the state of the component. By using console.dir, I was able to see that
      // ev was equal to the new time data that we needed
      nextState["start_time"] = ev;

      // Update the component's state with the new state
      this.setState(nextState);
    },
    _handleEndTimeChange: function(ev) {
      var nextState = _.cloneDeep(this.state);
      // Update the state of the component. By using console.dir, I was able to see that
      // ev was equal to the new time data that we needed
      nextState["end_time"] = ev;

      // Update the component's state with the new state
      this.setState(nextState);
    },
    render:function(){
      /* If the colMd prop is passed, it will determine the cols to use for layout purposes. */
      var colMd = this.props.colMd || null;
      return (
        <form onKeyDown={this.handleKeyDown} className={this.props.className}>
          <Col md={colMd}>
            <Input type='title'
              className="form-elem-full"
              name='title'
              placeholder='Enter title...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='address'
              className="form-elem-full"
              name='address'
              placeholder='Enter address...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='city'
              className="form-elem-full"
              name='city'
              placeholder='Enter city...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='state'
              className="form-elem-full"
              name='state'
              placeholder='Enter state...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='postal_code'
              className="form-elem-full"
              name='postal_code'
              placeholder='Enter postal code...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='country'
              className="form-elem-full"
              name='country'
              placeholder='Enter country...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <Input type='description'
              className="form-elem-full"
              name='description'
              placeholder='Enter description...'
              onChange={this.handleInputChange} />
          </Col>

          <Col md={colMd}>
            <DateTimeField dateTime={this.state.start_time} onChange={this._handleStartTimeChange} inputFormat="MM/DD/YY h:mm A" />
          </Col>
          <Col md={colMd}>
            <DateTimeField dateTime={this.state.end_time} onChange={this._handleEndTimeChange} inputFormat="MM/DD/YY h:mm A" />
          </Col>

          <Col md={colMd}>
            <Button className='btn btn-primary event-create-btn'
              onClick={this.handleCreateEventClick}>
              Create Event
            </Button>
          </Col>
        </form>
      )
    }
  });
module.exports = NewEventForm;

Following is my test

jest.dontMock('object-assign');
jest.dontMock('lodash');
jest.dontMock('../NewEventForm.js');
jest.dontMock('moment');
jest.dontMock('react-bootstrap');
jest.dontMock('../../../test-utils/ReactRouterContext.js')

describe('New Event', function() {
    var EventStore         = require('../../../stores/EventStore.js');
  var React              = require('react/addons');
  var TestUtils          = React.addons.TestUtils;
  var ReactRouterContext = require('../../../test-utils/ReactRouterContext.js');
  var NewEventForm       = require('../NewEventForm.js');
  var BS                 = require('react-bootstrap');
  var EventActionCreator = require('../../../actions/EventActionCreator.js');
  var newEventForm;
  var submitButton;
  var inputs;
  var textAreas;


  var eventData = { "id":23, "title":"Ultron", "address":"555 batman lane", "city":"oeu", "state":"oeu", "postal_code":"oeu",
    "country":"eou", "description":"oeu", "start_time":String(Date.now()), "end_time":String(Date.now() + (2 * 60 * 60 * 1000)),
  };

  // Mock function call
  EventStore.get.mockReturnValue(eventData);

  NewEventForm = ReactRouterContext(NewEventForm);

  beforeEach(function() {
    newEventForm = TestUtils.renderIntoDocument(
      <NewEventForm />
    );

    inputs = TestUtils.scryRenderedDOMComponentsWithTag(newEventForm, "input");

    titleInput = inputs[0];
    addressInput = inputs[1];
    cityInput = inputs[2];
    stateInput = inputs[3];
    zipInput = inputs[4];
    descriptionInput = inputs[5];


  });

  it('sends a submit action upon clicking of submit', function() {
    //Fill out the form
    titleInput.getDOMNode().value = eventData.title;
    titleInput.value = eventData.title;
    TestUtils.Simulate.change(titleInput.getDOMNode());
    TestUtils.Simulate.change(titleInput);

    addressInput.getDOMNode().value = eventData.address;
    TestUtils.Simulate.change(addressInput.getDOMNode());

    cityInput.getDOMNode().value = eventData.city;
    TestUtils.Simulate.change(cityInput.getDOMNode());

    stateInput.getDOMNode().value = eventData.state;
    TestUtils.Simulate.change(stateInput.getDOMNode());

    zipInput.getDOMNode().value = eventData.postal_code;
    TestUtils.Simulate.change(zipInput.getDOMNode());

    descriptionInput.getDOMNode().value = eventData.description;
    TestUtils.Simulate.change(descriptionInput.getDOMNode());

    // Now handle the submission
    submitButton = TestUtils.findRenderedDOMComponentWithClass(newEventForm, "event-create-btn");

    TestUtils.Simulate.click(submitButton);

    expect(EventActionCreator.createEvent.mock.calls[0][0]).toEqual(eventData);
  });

});

@mathieumg
Copy link
Member

Oh, I see what you mean. I'm guessing handleInputChange is in one of the mixins, too? Also, in https://facebook.github.io/react/docs/test-utils.html#simulate they seem to say you have to give a new value when doing the change, otherwise, there's no "change" really.

@chiedo
Copy link

chiedo commented Jul 7, 2015

Correct, here is the handleInputChange mixin.

  handleInputChange: function(ev) {
    var nextState = _.cloneDeep(this.state);
    nextState[ev.target.name] = ev.target.value;
    this.setState(nextState);
  },

I'm using lodash.js for the _cloneDeep functionality.

Thanks again for your help thus far.

@mathieumg
Copy link
Member

I've edited my previous post while you were making yours, just in case you didn't notice! Also, you can add js after the three tricks at the beginning of your code snippets. (Or whatever language they're in)

@chiedo
Copy link

chiedo commented Jul 7, 2015

Thanks! I didn't notice.
I've tried that also with no luck. I will give it another shot.

@chiedo
Copy link

chiedo commented Jul 7, 2015

Still no luck. Man... I may just go back to Selenium haha.

@mathieumg
Copy link
Member

Perhaps with findRenderedDOMComponentWithTag like it's done in https://github.com/react-bootstrap/react-bootstrap/blob/master/test/InputSpec.js ?

I have to admit, I'm not a React testing/mocking expert. :/

@chiedo
Copy link

chiedo commented Jul 7, 2015

Thanks man,
No problem at all. Maybe someone else will have some insight.

I can't use findRenderedDOMComponentWithTag since I have more than one input on the page.

Thanks so much for taking the time to look into this!

@AlexKVal
Copy link
Member

AlexKVal commented Jul 8, 2015

I've added this test to https://github.com/react-bootstrap/react-bootstrap/blob/master/test/InputSpec.js
This project uses mocha and friends

it.only('check input change', function () {
  let onChangeHandler = sinon.stub();

  let instance = ReactTestUtils.renderIntoDocument(
    <Input type='state' onChange={onChangeHandler} id='myComponent' />
  );

  let node = React.findDOMNode(ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input'));
  assert.equal(node.getAttribute('id'), 'myComponent'); // check that `input` renders OK

  // check that we can find input by searching multiple elements `scryRenderedDOMComponentsWithTag` method
  let inputs = ReactTestUtils.scryRenderedDOMComponentsWithTag(instance, 'input');
  assert.equal(React.findDOMNode(inputs[0]).getAttribute('id'), 'myComponent');

  // check both ways `change` simulation. Like it is done in RB tests
  ReactTestUtils.Simulate.change(node);
  // and like you are trying to do
  ReactTestUtils.Simulate.change(React.findDOMNode(inputs[0]));

  // all works fine
  onChangeHandler.should.have.been.called;
});
SUMMARY:
✔ 1 tests completed

I hope it will help you 🍒

@AlexKVal
Copy link
Member

AlexKVal commented Jul 8, 2015

Forget to notify you @chiedojohn about #297 (comment)
in the case you've unsubscribed from this issue.

@chiedo
Copy link

chiedo commented Jul 8, 2015

Thanks @AlexKVal, That still doesn't seem to work for my use-case though. In my use-case, I'm rendering a component that contains React-bootstrap Inputs.

It could be that I need to rethink how I'm writing my tests. This is looking more and more to be a React issue and not a react-bootstrap issue. Thanks for all of your help though.

@AlexKVal
Copy link
Member

AlexKVal commented Jul 8, 2015

👍
🎷🐢

@chiedo
Copy link

chiedo commented Jul 9, 2015

AHA! Figured it out. It's the darn just auto mocking.

I needed to add the following to the top of my test. Now things work as expected.

jest.dontMock('../../../mixins/FormState.js');

@AlexKVal
Copy link
Member

AlexKVal commented Jul 9, 2015

That's why many don't like jest and use mocha 😅

@chiedo
Copy link

chiedo commented Jul 9, 2015

Hahahahhaha. There are times like this when I am heavily considering going to mocha but it's personal now. I now intend to become a master of all things jest.

@Nutelac
Copy link
Author

Nutelac commented Jul 20, 2015

Quick tip: if you want to disable Jest automocking, just add this line into package.json:

"jest": {
  "unmockedModulePathPatterns": [""]
}

@lnmp4000
Copy link

I'm trying to write tests that check conditions against the contents of a dismissable Alert component. The problem is findRenderedDOMComponentWithTag is unable to find any children of the required type within the Alert.

e.g.

TestUtils.findRenderedDOMComponentWithTag(myAlert, 'span')

It works if the Alert is configured to not be dismissable.

Does anyone know how I can reference this children of a dismissable Alert, it is not possible for me to make these not dismissable. Is there some ref I can get hold of?

@taion
Copy link
Member

taion commented Sep 17, 2015

You're hitting https://github.com/react-bootstrap/react-bootstrap/blob/v0.25.2/src/Alert.js#L29

The Alert's child is whatever you pass in, so ultimately you can do whatever you need to get ahold of it.

@lnmp4000
Copy link

That would be the case in the trivial example I detailed, but in reality the Alert is embedded within another component. It is that component I am testing, and the contents of the alert are built based on internal state. I was attempting to look up rendered DOM components under that top level component (which just happen to be rendered inside an Alert) when I ran into this problem.

aryad14 pushed a commit to aryad14/react-bootstrap that referenced this issue Oct 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Documentation related question
Projects
None yet
Development

No branches or pull requests