aboutsummaryrefslogblamecommitdiffstats
path: root/test/lib/react-trigger-change.js
blob: d169dd614c575dab75aea7a47837f1e99f543958 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13


                                                                                 
                                                     








                                                    
                                                     
 
            


                                                                                       
                                                     














                             








                                            


                                                                                    

                                                          
                                    
                       


     



                                    
               
                                                                                    

                                              
                                                       


         
               

   

                                
                          
                              

                              
                                        






                                                


                                              




                                                                    
                                                               



                                                      



                                     



                                    


                                              



                                                    
                                                      


                                                           
                                


                                         


                                               

                                                        
                                 

                                                           
                                               





                                                                                        



                                                                 




                                                             
                                                         


                                         


                                               

                                                         
                                                            



                                                      
                                                        

     
 
// Trigger React's synthetic change events on input, textarea and select elements
// https://github.com/vitalyq/react-trigger-change

/** ****************IMPORTANT NOTE******************/
/* This file is a modification of the             */
/* 'react-trigger-change' library linked above.   */
/* That library breaks when 'onFocus' events are  */
/* added to components under test because it      */
/* dispatches focus events to ensure changes are  */
/* triggered in some versions of IE.              */
/* This modification removes the accomodations    */
/* 'react-trigger-change' makes for IE to ensure  */
/* our tests can pass in chrome and firefox.      */
/** ************************************************/

'use strict'

// Constants and functions are declared inside the closure.
// In this way, reactTriggerChange can be passed directly to executeScript in Selenium.
module.exports = function reactTriggerChange (node) {
  var supportedInputTypes = {
    color: true,
    date: true,
    datetime: true,
    'datetime-local': true,
    email: true,
    month: true,
    number: true,
    password: true,
    range: true,
    search: true,
    tel: true,
    text: true,
    time: true,
    url: true,
    week: true,
  }
  var nodeName = node.nodeName.toLowerCase()
  var type = node.type
  var event
  var descriptor
  var initialValue
  var initialChecked
  var initialCheckedRadio

  // Do not try to delete non-configurable properties.
  // Value and checked properties on DOM elements are non-configurable in PhantomJS.
  function deletePropertySafe (elem, prop) {
    var desc = Object.getOwnPropertyDescriptor(elem, prop)
    if (desc && desc.configurable) {
      delete elem[prop]
    }
  }

  function getCheckedRadio (radio) {
    var name = radio.name
    var radios
    var i
    if (name) {
      radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]')
      for (i = 0; i < radios.length; i += 1) {
        if (radios[i].checked) {
          return radios[i] !== radio ? radios[i] : null
        }
      }
    }
    return null
  }

  function preventChecking (e) {
    e.preventDefault()
    if (!initialChecked) {
      e.target.checked = false
    }
    if (initialCheckedRadio) {
      initialCheckedRadio.checked = true
    }
  }

  if (nodeName === 'select' ||
    (nodeName === 'input' && type === 'file')) {
    // IE9-IE11, non-IE
    // Dispatch change.
    event = document.createEvent('HTMLEvents')
    event.initEvent('change', true, false)
    node.dispatchEvent(event)
  } else if ((nodeName === 'input' && supportedInputTypes[type]) ||
    nodeName === 'textarea') {
    // React 16
    // Cache artificial value property descriptor.
    // Property doesn't exist in React <16, descriptor is undefined.
    descriptor = Object.getOwnPropertyDescriptor(node, 'value')

    // Update inputValueTracking cached value.
    // Remove artificial value property.
    // Restore initial value to trigger event with it.
    initialValue = node.value
    node.value = initialValue + '#'
    deletePropertySafe(node, 'value')
    node.value = initialValue

    // React 0.14: IE10-IE11, non-IE
    // React 15: non-IE
    // React 16: IE10-IE11, non-IE
    event = document.createEvent('HTMLEvents')
    event.initEvent('input', true, false)
    node.dispatchEvent(event)

    // React 16
    // Restore artificial value property descriptor.
    if (descriptor) {
      Object.defineProperty(node, 'value', descriptor)
    }
  } else if (nodeName === 'input' && type === 'checkbox') {
    // Invert inputValueTracking cached value.
    node.checked = !node.checked

    // Dispatch click.
    // Click event inverts checked value.
    event = document.createEvent('MouseEvents')
    event.initEvent('click', true, true)
    node.dispatchEvent(event)
  } else if (nodeName === 'input' && type === 'radio') {
    // Cache initial checked value.
    initialChecked = node.checked

    // Find and cache initially checked radio in the group.
    initialCheckedRadio = getCheckedRadio(node)

    // React 16
    // Cache property descriptor.
    // Invert inputValueTracking cached value.
    // Remove artificial checked property.
    // Restore initial value, otherwise preventDefault will eventually revert the value.
    descriptor = Object.getOwnPropertyDescriptor(node, 'checked')
    node.checked = !initialChecked
    deletePropertySafe(node, 'checked')
    node.checked = initialChecked

    // Prevent toggling during event capturing phase.
    // Set checked value to false if initialChecked is false,
    // otherwise next listeners will see true.
    // Restore initially checked radio in the group.
    node.addEventListener('click', preventChecking, true)

    // Dispatch click.
    // Click event inverts checked value.
    event = document.createEvent('MouseEvents')
    event.initEvent('click', true, true)
    node.dispatchEvent(event)

    // Remove listener to stop further change prevention.
    node.removeEventListener('click', preventChecking, true)

    // React 16
    // Restore artificial checked property descriptor.
    if (descriptor) {
      Object.defineProperty(node, 'checked', descriptor)
    }
  }
}