teampoltergeist / poltergeist

A PhantomJS driver for Capybara
MIT License
2.5k stars 415 forks source link

Change event not triggering after select using react #840

Closed aledustet closed 7 years ago

aledustet commented 7 years ago

Hello

I am using:

I have a view using react-rails (1.9.0), when an option is chosen from a select element, it updates the state of the component adding new values to the next select box, and so on chaining 3 of the select boxes. The problem is that in the tests, when the first one is selected it does what it is supposed to and updates the options on the other select, but nothing happens when the second value is selected. The dom looks fine when I am on the view and I don't have any warnings in the console. I have tried all sort of solutions:

All of this and still does not work. This is the .jsx that renders the select boxes, the chainedOptions is the one that gets called

chainedOptions() {
  return [<div key="options" className="option">
            <label>Option:</label>
            {this.options()}
          </div>,
          <div key="value" className="value">
            <label>Value:</label>
            {this.values()}
          </div>];
},

options() {    
  let options = _.map(this._namesOptions(), ((name) => {
    return <option key={name} value={name}>
        {name}
      </option>;
  }));
  return <select className="combox" onChange={this.selectOption} value={this.optionSelected || ""}>
    <option></option>
    {options}
  </select>
},

values() {
  if(!this.optionSelected) {
    return <label className="item-value">Select an option</label>;
  }
  else {
    let valuesOptions = _.map(this._valuesOptions(), ((value) => {
      return <option key={value.id} value={value.id}>
          {value.label}
        </option>;
    }));
    return <select className="combox" onChange={this.selectValue} value={this.selectedValue || ""}>
      <option></option>
      {valuesOptions}
    </select>
  }
}

This is how my test code looks:

{...}
    within '.content.active.row' do
      within '.contract' do
        select("Test Contract")
      end
      within '.option' do
        select("Test Option")
        # This is the step where the value should be selected
      end
      within '.value' do
        select("Test Value")
      end
    end    
{...}

The select "Test Option" returns the node selected, but it does not set the value on the select node, therefore the onChange is not triggered. I figured that if I can trigger the change event, maybe the events will be fired, but that is not happening.

If I remove the second div on the chainedOptions method, select "Test Option" actually selects the value, but I can't move forward because the rest of the data is not in there.

twalpole commented 7 years ago

What are you basing the statement "it does not set the value on the select node" on? Also, is there an option that would match "Test Option" on the screen already when you do select("Test Contract")? If so it's possible the test is running fast enough that it selects the existing "Test Option" before it is replaced with the updated one.

aledustet commented 7 years ago

I have been doing a number of tests on the console, debugging the test behavior, when I use find(selector) to find the select box, and check the value it returns "" after the select call has been made. The option is inside the select I checked on the console and when i use save_and_open_page all the options are shown. I have also tried using a sleep after selecting the value and it is still not being selected.

twalpole commented 7 years ago

@aledustet save_and_open_page is not valid since it saves the attributes not the properties, so won't necessarily accurately reflect the page state - taking a screenshot will show you what the current selections are. When you say s sleep after selecting the value - where exactly did you put the sleep, after the select("Test Contract") ?

aledustet commented 7 years ago

I have been using both, the screenshot to make sure the value of the first select box was set, and save_and_open_page to check for the options on the second select box, I have used the sleep after the select("Test Option") and on the console, I have used the same select call and it executes correctly returning the node selected, but it does not sets the value on the select node.

twalpole commented 7 years ago

@aledustet Put the sleep after select('Test Contract') - If the issue is that there is already a "Test Option" element on the page before 'Test Contract' is selected, and the test is selecting that before the replacement element is added to the page then that would make it wait a little bit and work - If that does make it work then we can work out a better solution that just sleeping.

aledustet commented 7 years ago

Thanks for the quick response @twalpole i have also tried that when i got to the code after the select ("Test Option") i popped a debugger and on the console i am able to get all this:

$ option_select = find(".option select")
$ => #<Capybara::Node::Element tag="select" path="//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]">
$ option_select.value
$ => ""
$ option_1 = find(:xpath, "//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]/OPTION[1]")
$ => #<Capybara::Node::Element tag="option" path="//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]/OPTION[1]">
$ option_1.value
$ => ""
$ option_2 = find(:xpath, "//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]/OPTION[2]")
$ => #<Capybara::Node::Element tag="option" path="//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]/OPTION[2]">
$ option_2.value
$ => "Test Option"
$ select("Test Option")
$ => #<Capybara::Node::Element tag="option" path="//HTML[1]/BODY[1]/DIV[3]/DIV[1]/DIV[1]/DIV[1]/DIV[1]/FORM[1]/DIV[1]/UL[1]/LI[1]/DIV[2]/DIV[1]/DIV[2]/DIV[1]/SELECT[1]/OPTION[2]">
$ option_select.value
$ => ""
$ select("Test Value")
$ => Capybara::ElementNotFound: Unable to find option "Test Value"
from /Users/dustet/.rvm/gems/ruby-2.3.0@website/gems/capybara-2.11.0/lib/capybara/node/finders.rb:44:in 'block in find'
$ find(".value select")
$ => Capybara::ElementNotFound: Unable to find css ".value select"
from /Users/dustet/.rvm/gems/ruby-2.3.0@website/gems/capybara-2.11.0/lib/capybara/node/finders.rb:44:in 'block in find'

That's the output i get when i check for the values of the select, as you can see the select finds the option no problem, and returns the node selected, but the value is not set and the change on the react code, does not populate the other select options.

twalpole commented 7 years ago

Interesting - it looks like maybe an incompatibility with react. You haven't set Capybara.ignore_hidden_elements = false and these are actually hidden elements being overwritten by a react widget by any chance? Can you produce a minimal reproducible test case? What select does is

select: (value) ->
    if @isDisabled()
      false
    else if value == false && !@element.parentNode.multiple
      false
    else
       this.trigger('focus', @element.parentNode)

      @element.selected = value
      this.changed()

      this.trigger('blur', @element.parentNode)
      true

so it definitely should be selecting the element (unless it's disabled)

One other thing to try would be calling `page.driver.clear_memory_cache' before each select just to make sure it's not a caching of ajax responses issue

aledustet commented 7 years ago

These are not widgets, that is part of a component I add that is part of a form. The list items are already part of the state of the main component and one selection provides the next select with the options data. this._namesOptions() fetch on the state the options associated with the .contract field and this._valuesOptions() fetches for the options associated with the name selected.In this case there isn't any hidden element, just plain old DOM elements, i know react handles it's own virtual DOM, the weird thing is that it works with the namesOption select reacting to the contractOptions select, but not on the second one, i have tried moving them to other parts of the DOM and still is not working only when i remove the last of the selects it function properly. One thing i am noticing is !@element.parentNode.multiple, does this means that it checks that the element is under a unique parent? Thanks for bearing with me on this one.

twalpole commented 7 years ago

That's checking for the multiple attribute, since you can only unselect when it's a multiple select box

aledustet commented 7 years ago

@twalpole finally nailed it, there was a data issue that was preventing the view to be correctly generated, good thing the test not passed cause i caught it on time, thanks for bearing with me. You can go ahead and close this. 👌

twalpole commented 7 years ago

@aledustet Good to hear that it wasn't Poltergeist :) Closing this.