aboutsummaryrefslogtreecommitdiffstats
path: root/test/lib/react-trigger-change.js
blob: d169dd614c575dab75aea7a47837f1e99f543958 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// 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)
    }
  }
}