chromium/third_party/blink/web_tests/fast/forms/number/number-reject-invalid.html

<!DOCTYPE html>
<script src="../../../resources/js-test.js"></script>
<script>

if (window.internals)
    internals.runtimeFlags.langAttributeAwareFormControlUIEnabled = true;
else
    debug('Require testRunner.');
</script>

<input id="input-ar" lang="ar-eg" type="number">
<input id="input-fr" lang="fr-fr" type="number">
<input id="input-en" lang="en-us" type="number">

<script>
function displayValueForKeyInput(inputElement, keyInputs, optionalOldString, expectedValue) {
    inputElement.value = '';
    inputElement.focus();
    if (optionalOldString) {
        let caretPosition = optionalOldString.indexOf('|');
        if (caretPosition < 0)
          caretPosition = optionalOldString.length;
        else
          optionalOldString = optionalOldString.replace("|", "");
        document.execCommand('InsertText', false, optionalOldString);

        // Check that the value is what we expect
        inputElement.select();
        shouldBeEqualToString("window.getSelection().toString()", optionalOldString);

        // Update selection
        internals.setSelectionRangeForNumberType(inputElement, caretPosition, caretPosition);
    }
    document.execCommand('InsertText', false, keyInputs);
    inputElement.select();

    // Check expected value
    if (expectedValue === undefined)
      shouldBeUndefined(inputElement.value);
    else
      shouldBeEqualToNumber(inputElement.value, expectedValue);

    return window.getSelection().toString();
}

// Get language formats
var langs = ["ar-eg", "fr-fr", "en-us"];
var langSpecs = {};
var numberCharMapByLang = {};
// Check local formatting options
for (var lang of langs)
{
  var formatted = Number(123456.7).toLocaleString(lang);
  var oneFormatted = Number(1).toLocaleString(lang);
  var negativeFormatted = Number(-1).toLocaleString(lang);
  var oneIndex = negativeFormatted.indexOf(oneFormatted);
  var negativePrefix = "";
  if (oneIndex > 0)
    negativePrefix = negativeFormatted.substr(0, oneIndex);
  var negativeSuffix = "";
  if (negativeSuffix === oneFormatted)
    negativeSuffix = negativeFormatted.substr(oneIndex + 1);
  var specs = {
    "usesCommas": /123,456/.test(formatted),
    "usesArabicDecimalSeparator": formatted.indexOf("\u066b") !== -1,
    "latinDigits": formatted.indexOf("1") !== -1,
    "negativePrefix": negativePrefix,
    "negativePrefixLength": negativePrefix.length,
    "negativeSuffix": negativeSuffix,
    "negativeSuffixLength": negativeSuffix.length,
    "usesE": /[eE]/.test(Number(1e2).toLocaleString(lang, {"notation": "scientific"}))
  };
  specs.usesSingleCharFiltering = (specs.negativePrefixLength == 1 && specs.negativeSuffixLength == 0);
  langSpecs[lang] = specs;

  // Get replacement digits
  numberCharMapByLang[lang] = {};
  var formattedDigits = Number(1234567890.1).toLocaleString(lang, {
    useGrouping: false
  });
  for (var i = 1; i <= 10; i++)
  {
    var num = i % 10;
    var index = i - 1;
    numberCharMapByLang[lang][num] = formattedDigits[index];
  }
  numberCharMapByLang[lang]["."] = formattedDigits[10];
  var groupingSeparator = formatted[3];
  if (formattedDigits.indexOf(groupingSeparator) !== -1)
    formattedDigits = "";
  numberCharMapByLang[lang][","] = groupingSeparator;
}

function convertLocal(enValue, lang) {
  var value = "";
  var map = numberCharMapByLang[lang] || {};
  for (var i = 0; i < enValue.length; i++)
  {
    var c = enValue[i];
    value += (c in map) ? map[c] : c;
  }

  // Update sign if needed
  var specs = langSpecs[lang];
  if (enValue[0] === "-" && specs.negativePrefix !== "-")
    value = specs.negativePrefix + value.substr(1);
  if (enValue.substr(0, 2) === "|-" && specs.negativePrefix !== "-")
    value = "|" + specs.negativePrefix + value.substr(2);

  return value;
}

debug('Arabic number input should accept ASCII digits and Arabic digits, and reject others.');
var input_ar = document.getElementById('input-ar');
shouldBeEqualToString('displayValueForKeyInput(input_ar, "123.4", "", 123.4)', '123.4');
shouldBeEqualToString('displayValueForKeyInput(input_ar, "1.23E+19", "", 12300000000000000000)', '1.23E+19');
shouldBeEqualToString('displayValueForKeyInput(input_ar, "1.23e-1", "", 0.123)', '1.23e-1');
shouldBeEqualToString('displayValueForKeyInput(input_ar, "\u0661\u0669\u0660", "", 190)', '\u0661\u0669\u0660');
shouldBeEqualToString('displayValueForKeyInput(input_ar, "acdef", "", undefined)', 'e');

debug('');
debug('French number input should accept ASCII digits, comma, and full stop.');
var input_fr = document.getElementById('input-fr');
shouldBeEqualToString('displayValueForKeyInput(input_fr, "1234.56", "", 1234.56)', '1234.56');
shouldBeEqualToString('displayValueForKeyInput(input_fr, "1234,56", "", 1234.56)', '1234,56');

debug('');
debug('English number input should accept ASCII digits and full stop, and no comma.');
var input_en = document.getElementById('input-en');
shouldBeEqualToString('displayValueForKeyInput(input_en, "1234.56", "", 1234.56)', '1234.56');
shouldBeEqualToString('displayValueForKeyInput(input_en, "-1234,56", "", -123456)', '-123456');
shouldBeEqualToString('displayValueForKeyInput(input_en, " 1234.56 ", "", 1234.56)', '1234.56');
shouldBeEqualToString('displayValueForKeyInput(input_en, "e", "-1|-1", -0.1)', '-1e-1');

debug('');
debug('Test all locales.');
var tests = [
  ["123,456,789E+10", "", 1234567890000000000, '123456789E+10'],
  [".", "1|e2", 100, '1.e2'],
  [".", "1e2|", 100, '1e2'],
  [".", "|-12", -12, '-12'],
  [".", "|1e-12", 0.0000000000001, '.1e-12'],
  ["e", "34|12", 34000000000000, '34e12'],
  ["e", "12|3e4", 1230000, '123e4'],
  ["e", "123|.4", 123.4, '123.4'],
  ["e", "12.3|4", 123000, '12.3e4'],
  ["+", "12|34", undefined, '12+34'],
  ["+", "-3|4e-12", -0.000000000034, '-34e-12'],
  ["+", "|1234", 1234, '+1234'],
  ["-", "123e|4", 0.0123, '123e-4'],
  ["-", "1|23e4", 1230000, '123e4'],
  ["-", "123e4|", 1230000, '123e4'],
  ["9", "|-1", -1, '-1'],
  ["9", "-|1", -91, '-91'],
  ["9", "1e|+2", 100, '1e+2'],
  ["1", "1e+|2", 1000000000000, '1e+12'],
  // Invalid
  [" abcdef ", "", undefined, 'e'],
  ["+1-2", "", undefined, '+1-2'],
  ["+1-2+2-3", "", undefined, '+1-223'],
  ["0-123-123+123", "", undefined, '0-123-123123'],
  ["10e123123e1231233e", "", undefined, '10e1231231231233'],
  ["1e2eee", "", 100, '1e2'],
  ["1e1e1e", "", 100000000000, '1e11'],
];
for (var test of tests)
{
  for (var lang of langs)
  {
    var specs = langSpecs[lang];
    var keyInputs = test[0];
    var oldString = test[1];
    var expectedNumericValue = test[2];
    var expectedStringValue = test[3];

    // Build raw new text.
    var rawNewText = keyInputs;
    if (oldString !== "")
    {
      var pos = oldString.indexOf("|");
      rawNewText = oldString.substr(0, pos) + keyInputs + oldString.substr(pos + 1);
    }

    // Skip test if there's a comma, but locale doesn't use commas.
    // Otherwise, it will be converted to a decimal separator, breaking the expected value.
    if (rawNewText.indexOf(",") !== -1 && !specs.usesCommas)
      continue;

    // Skip test if scientific notation doesn't use "e".
    if (rawNewText.toLowerCase().indexOf("e") !== -1 && !specs.usesE)
      continue;

    // Not uses_single_char_number_filtering_, so build actual value.
    if (!specs.usesSingleCharFiltering)
    {
      // Remove commas.
      expectedStringValue = rawNewText.replace(/,/g, "");

      // Figure out how this number will parse.
      var cleanExpectedStringValue = expectedStringValue.replace(/[^0-9.e+-]/gi, "");
      var scientificMatch = /e([+-]?[0-9]+)$/i.exec(cleanExpectedStringValue);
      var multiplier = 1;
      if (scientificMatch !== null)
      {
        cleanExpectedStringValue = cleanExpectedStringValue.replace(/e([+-]?[0-9]+)$/gi, "");
        multiplier = Math.pow(10, parseInt(scientificMatch[1], 10));
      }
      expectedNumericValue = Number(cleanExpectedStringValue);
      if (isNaN(expectedNumericValue))
        expectedNumericValue = undefined;
      else
        expectedNumericValue *= multiplier;
    }

    // Skip if a negative number and negative prefix is more than 1 character.
    // For ar-eg, Windows seems to strip \u061c when setting the initial text
    // causing results to differ.
    if (/^\|?-/.test(oldString) && specs.negativePrefixLength > 1)
      continue;

    // Change to locale if not in scientific notation (and valid).
    if (/^[+-]?([0-9]+(\.[0-9]*)|\.[0-9]+)?e[+-]?[0-9]+$/i.exec(expectedStringValue) === null)
    {
      keyInputs = convertLocal(keyInputs, lang);
      oldString = convertLocal(oldString, lang);
      expectedStringValue = convertLocal(expectedStringValue, lang);
    }

    // Skip if it includes the Arabic decimal separator. Windows differs from Mac/Linux.
    if (expectedStringValue.indexOf("\u066b") !== -1)
      continue;

    shouldBeEqualToString('displayValueForKeyInput(input_' + lang.replace(/-.*$/, '') + ', "' + keyInputs + '", "' + oldString + '", ' + expectedNumericValue + ')', expectedStringValue);
  }
}
</script>
</body>