test_input_number_key_events.html (7313B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=935506 5 --> 6 <head> 7 <title>Test key events for number control</title> 8 <script src="/tests/SimpleTest/SimpleTest.js"></script> 9 <script src="/tests/SimpleTest/EventUtils.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 <meta charset="UTF-8"> 12 </head> 13 <body> 14 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935506">Mozilla Bug 935506</a> 15 <p id="display"></p> 16 <div id="content"> 17 <input id="input" type="number"> 18 </div> 19 <pre id="test"> 20 <script type="application/javascript"> 21 22 /** 23 * Test for Bug 935506 24 * This test checks how the value of <input type=number> changes in response to 25 * key events while it is in various states. 26 */ 27 SimpleTest.waitForExplicitFinish(); 28 SimpleTest.waitForFocus(function() { 29 test(); 30 SimpleTest.finish(); 31 }); 32 const defaultMinimum = "NaN"; 33 const defaultMaximum = "NaN"; 34 const defaultStep = 1; 35 36 // Helpers: 37 // For the sake of simplicity, we do not currently support fractional value, 38 // step, etc. 39 40 function getMinimum(element) { 41 return Number(element.min || defaultMinimum); 42 } 43 44 function getMaximum(element) { 45 return Number(element.max || defaultMaximum); 46 } 47 48 function getDefaultValue(element) { 49 return 0; 50 } 51 52 function getValue(element) { 53 return Number(element.value || getDefaultValue(element)); 54 } 55 56 function getStep(element) { 57 if (element.step == "any") { 58 return "any"; 59 } 60 var step = Number(element.step || defaultStep); 61 return step <= 0 ? defaultStep : step; 62 } 63 64 function getStepBase(element) { 65 return Number(element.getAttribute("min") || "NaN") || 66 Number(element.getAttribute("value") || "NaN") || 0; 67 } 68 69 function hasStepMismatch(element) { 70 var value = element.value; 71 if (value == "") { 72 value = 0; 73 } 74 var step = getStep(element); 75 if (step == "any") { 76 return false; 77 } 78 return ((value - getStepBase(element)) % step) != 0; 79 } 80 81 function floorModulo(x, y) { 82 return (x - y * Math.floor(x / y)); 83 } 84 85 function expectedValueAfterStepUpOrDown(stepFactor, element) { 86 var value = getValue(element); 87 if (isNaN(value)) { 88 value = 0; 89 } 90 var step = getStep(element); 91 if (step == "any") { 92 step = 1; 93 } 94 95 var minimum = getMinimum(element); 96 var maximum = getMaximum(element); 97 if (!isNaN(maximum)) { 98 // "max - (max - stepBase) % step" is the nearest valid value to max. 99 maximum = maximum - floorModulo(maximum - getStepBase(element), step); 100 } 101 102 // Cases where we are clearly going in the wrong way. 103 // We don't use ValidityState because we can be higher than the maximal 104 // allowed value and still not suffer from range overflow in the case of 105 // of the value specified in @max isn't in the step. 106 if ((value <= minimum && stepFactor < 0) || 107 (value >= maximum && stepFactor > 0)) { 108 return value; 109 } 110 111 if (hasStepMismatch(element) && 112 value != minimum && value != maximum) { 113 if (stepFactor > 0) { 114 value -= floorModulo(value - getStepBase(element), step); 115 } else if (stepFactor < 0) { 116 value -= floorModulo(value - getStepBase(element), step); 117 value += step; 118 } 119 } 120 121 value += step * stepFactor; 122 123 // When stepUp() is called and the value is below minimum, we should clamp on 124 // minimum unless stepUp() moves us higher than minimum. 125 if (element.validity.rangeUnderflow && stepFactor > 0 && 126 value <= minimum) { 127 value = minimum; 128 } else if (element.validity.rangeOverflow && stepFactor < 0 && 129 value >= maximum) { 130 value = maximum; 131 } else if (stepFactor < 0 && !isNaN(minimum)) { 132 value = Math.max(value, minimum); 133 } else if (stepFactor > 0 && !isNaN(maximum)) { 134 value = Math.min(value, maximum); 135 } 136 137 return value; 138 } 139 140 function expectedValAfterKeyEvent(key, element) { 141 return expectedValueAfterStepUpOrDown(key == "KEY_ArrowUp" ? 1 : -1, element); 142 } 143 144 function test() { 145 var elem = document.getElementById("input"); 146 elem.focus(); 147 148 elem.min = -5; 149 elem.max = 5; 150 elem.step = 2; 151 var defaultValue = 0; 152 var oldVal, expectedVal; 153 154 for (key of ["KEY_ArrowUp", "KEY_ArrowDown"]) { 155 // Start at middle: 156 oldVal = elem.value = -1; 157 expectedVal = expectedValAfterKeyEvent(key, elem); 158 synthesizeKey(key); 159 is(elem.value, String(expectedVal), "Test " + key + " for number control with value set between min/max (" + oldVal + ")"); 160 161 // Same again: 162 expectedVal = expectedValAfterKeyEvent(key, elem); 163 synthesizeKey(key); 164 is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); 165 166 // Start at maximum: 167 oldVal = elem.value = elem.max; 168 expectedVal = expectedValAfterKeyEvent(key, elem); 169 synthesizeKey(key); 170 is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the maximum (" + oldVal + ")"); 171 172 // Same again: 173 expectedVal = expectedValAfterKeyEvent(key, elem); 174 synthesizeKey(key); 175 is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); 176 177 // Start at minimum: 178 oldVal = elem.value = elem.min; 179 expectedVal = expectedValAfterKeyEvent(key, elem); 180 synthesizeKey(key); 181 is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the minimum (" + oldVal + ")"); 182 183 // Same again: 184 expectedVal = expectedValAfterKeyEvent(key, elem); 185 synthesizeKey(key); 186 is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); 187 188 // Test preventDefault(): 189 elem.addEventListener("keydown", evt => evt.preventDefault(), {once: true}); 190 oldVal = elem.value = 0; 191 expectedVal = 0; 192 synthesizeKey(key); 193 is(elem.value, String(expectedVal), "Test " + key + " for number control where scripted preventDefault() should prevent the value changing"); 194 195 // Test step="any" behavior: 196 var oldStep = elem.step; 197 elem.step = "any"; 198 oldVal = elem.value = 0; 199 expectedVal = expectedValAfterKeyEvent(key, elem); 200 synthesizeKey(key); 201 is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the midpoint and step='any' (" + oldVal + ")"); 202 elem.step = oldStep; // restore 203 204 // Test that invalid input blocks UI initiated stepping: 205 oldVal = elem.value = ""; 206 elem.select(); 207 sendString("abc"); 208 synthesizeKey(key); 209 is(elem.value, "", "Test " + key + " does nothing when the input is invalid"); 210 211 // Test that no value does not block UI initiated stepping: 212 oldVal = elem.value = ""; 213 elem.setAttribute("required", "required"); 214 elem.select(); 215 expectedVal = expectedValAfterKeyEvent(key, elem); 216 synthesizeKey(key); 217 is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set"); 218 219 // Same again: 220 expectedVal = expectedValAfterKeyEvent(key, elem); 221 synthesizeKey(key); 222 is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); 223 224 // Reset 'required' attribute: 225 elem.removeAttribute("required"); 226 } 227 228 // Test that key events are correctly dispatched 229 elem.max = ""; 230 elem.value = ""; 231 sendString("7837281"); 232 is(elem.value, "7837281", "Test keypress event dispatch for number control"); 233 } 234 235 </script> 236 </pre> 237 </body> 238 </html>