<!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=935506 --> <head> <title>Test key events for number control</title> <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <meta charset="UTF-8"> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=935506">Mozilla Bug 935506</a> <p id="display"></p> <div id="content"> <input id="input" type="number"> </div> <pre id="test"> <script type="application/javascript"> /** * Test for Bug 935506 * This test checks how the value of <input type=number> changes in response to * key events while it is in various states. **/ SimpleTest.waitForExplicitFinish(); // Turn off Spatial Navigation because it hijacks arrow keydown events: SimpleTest.waitForFocus(function() { SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, function() { test(); SimpleTest.finish(); }); }); const defaultMinimum = "NaN"; const defaultMaximum = "NaN"; const defaultStep = 1; // Helpers: // For the sake of simplicity, we do not currently support fractional value, // step, etc. function getMinimum(element) { return Number(element.min || defaultMinimum); } function getMaximum(element) { return Number(element.max || defaultMaximum); } function getDefaultValue(element) { return 0; } function getValue(element) { return Number(element.value || getDefaultValue(element)); } function getStep(element) { if (element.step == "any") { return "any"; } var step = Number(element.step || defaultStep); return step <= 0 ? defaultStep : step; } function getStepBase(element) { return Number(element.getAttribute("min") || "NaN") || Number(element.getAttribute("value") || "NaN") || 0; } function hasStepMismatch(element) { var value = element.value; if (value == "") { value = 0; } var step = getStep(element); if (step == "any") { return false; } return ((value - getStepBase(element)) % step) != 0; } function floorModulo(x, y) { return (x - y * Math.floor(x / y)); } function expectedValueAfterStepUpOrDown(stepFactor, element) { var value = getValue(element); if (isNaN(value)) { value = 0; } var step = getStep(element); if (step == "any") { step = 1; } var minimum = getMinimum(element); var maximum = getMaximum(element); if (!isNaN(maximum)) { // "max - (max - stepBase) % step" is the nearest valid value to max. maximum = maximum - floorModulo(maximum - getStepBase(element), step); } // Cases where we are clearly going in the wrong way. // We don't use ValidityState because we can be higher than the maximal // allowed value and still not suffer from range overflow in the case of // of the value specified in @max isn't in the step. if ((value <= minimum && stepFactor < 0) || (value >= maximum && stepFactor > 0)) { return value; } if (hasStepMismatch(element) && value != minimum && value != maximum) { if (stepFactor > 0) { value -= floorModulo(value - getStepBase(element), step); } else if (stepFactor < 0) { value -= floorModulo(value - getStepBase(element), step); value += step; } } value += step * stepFactor; // When stepUp() is called and the value is below minimum, we should clamp on // minimum unless stepUp() moves us higher than minimum. if (element.validity.rangeUnderflow && stepFactor > 0 && value <= minimum) { value = minimum; } else if (element.validity.rangeOverflow && stepFactor < 0 && value >= maximum) { value = maximum; } else if (stepFactor < 0 && !isNaN(minimum)) { value = Math.max(value, minimum); } else if (stepFactor > 0 && !isNaN(maximum)) { value = Math.min(value, maximum); } return value; } function expectedValAfterKeyEvent(key, element) { return expectedValueAfterStepUpOrDown(key == "VK_UP" ? 1 : -1, element); } function test() { var elem = document.getElementById("input"); elem.focus(); elem.min = -5; elem.max = 5; elem.step = 2; var defaultValue = 0; var oldVal, expectedVal; for (key of ["VK_UP", "VK_DOWN"]) { // Start at middle: oldVal = elem.value = -1; expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control with value set between min/max (" + oldVal + ")"); // Same again: expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); // Start at maximum: oldVal = elem.value = elem.max; expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the maximum (" + oldVal + ")"); // Same again: expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); // Start at minimum: oldVal = elem.value = elem.min; expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the minimum (" + oldVal + ")"); // Same again: expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); // Test preventDefault(): elem.addEventListener("keypress", function(evt) { evt.preventDefault(); elem.removeEventListener("keypress", arguments.callee, false); }, false); oldVal = elem.value = 0; expectedVal = 0; synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control where scripted preventDefault() should prevent the value changing"); // Test step="any" behavior: var oldStep = elem.step; elem.step = "any"; oldVal = elem.value = 0; expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the midpoint and step='any' (" + oldVal + ")"); elem.step = oldStep; // restore // Test that invalid input blocks UI initiated stepping: oldVal = elem.value = ""; elem.select(); sendString("abc"); synthesizeKey(key, {}); is(elem.value, "", "Test " + key + " does nothing when the input is invalid"); // Test that no value does not block UI initiated stepping: oldVal = elem.value = ""; elem.setAttribute("required", "required"); elem.select(); expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set"); // Same again: expectedVal = expectedValAfterKeyEvent(key, elem); synthesizeKey(key, {}); is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control"); // Reset 'required' attribute: elem.removeAttribute("required"); } // Test that key events are correctly dispatched elem.max = ""; elem.value = ""; sendString("7837281"); is(elem.value, "7837281", "Test keypress event dispatch for number control"); } </script> </pre> </body> </html>