summaryrefslogtreecommitdiffstats
path: root/dom/html/test/forms/test_input_number_key_events.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/test/forms/test_input_number_key_events.html')
-rw-r--r--dom/html/test/forms/test_input_number_key_events.html238
1 files changed, 238 insertions, 0 deletions
diff --git a/dom/html/test/forms/test_input_number_key_events.html b/dom/html/test/forms/test_input_number_key_events.html
new file mode 100644
index 0000000000..eb537f5617
--- /dev/null
+++ b/dom/html/test/forms/test_input_number_key_events.html
@@ -0,0 +1,238 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935506
+-->
+<head>
+ <title>Test key events for number control</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script 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();
+SimpleTest.waitForFocus(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 == "KEY_ArrowUp" ? 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 ["KEY_ArrowUp", "KEY_ArrowDown"]) {
+ // 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("keydown", evt => evt.preventDefault(), {once: true});
+ 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>