<!DOCTYPE html>
<meta charset=utf-8>
<title>Input element's type attribute changes state</title>
<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
<link rel=help href="https://html.spec.whatwg.org/multipage/#the-input-element">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>

  const INITIAL_VALUE = "  foo\rbar  ";

  // Sanitize algorithm implementations only for values used in this test.
  function sanitizeText(value) {
    switch (value) {
      case INITIAL_VALUE: return "  foobar  ";
      case "  foobar  ": return value;
      case "foobar": return value;
      case "50": return value;
      case "#000000": return value;
      case "": return value;
      default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
    }
  }
  function sanitizeEmailOrUrl(value) {
    switch (value) {
      case INITIAL_VALUE: return "foobar";
      case "  foobar  ": return "foobar";
      case "foobar": return value;
      case "50": return value;
      case "#000000": return value;
      case "": return value;
      default: throw new DOMException(`Internal Error: Should add support of "${value}"`, "NotSupportedError");
    }
  }
  function sanitizeTemporal(value) {
    // We have no test cases using valid temporal values.
    return "";
  }
  function sanitizeNumber(value) {
    switch (value) {
      case "50": return value;
      default:
      // We have no test cases using valid numbers other than "50".
        return "";
    }
  }
  function sanitizeRange(value) {
    // We have no test cases using valid numbers other than "50".
    return "50";
  }
  function sanitizeColor(value) {
    // We have no test cases using valid colors other than "#000000".
    return "#000000";
  }
  function browserSupportsInputTypeOf(inputType) {
    var inputTest = document.createElement("input");
    inputTest.type = inputType;
    return (inputTest.type === inputType);
  }


  var types = [
    { type: "hidden" },
    { type: "text", sanitizer: sanitizeText },
    { type: "search", sanitizer: sanitizeText },
    { type: "tel", sanitizer: sanitizeText },
    { type: "url", sanitizer: sanitizeEmailOrUrl },
    { type: "email", sanitizer: sanitizeEmailOrUrl },
    { type: "password", sanitizer: sanitizeText },
    { type: "datetime-local", sanitizer: sanitizeTemporal },
    { type: "date", sanitizer: sanitizeTemporal },
    { type: "month", sanitizer: sanitizeTemporal },
    { type: "week", sanitizer: sanitizeTemporal },
    { type: "time", sanitizer: sanitizeTemporal },
    { type: "number", sanitizer: sanitizeNumber },
    { type: "range", sanitizer: sanitizeRange },
    { type: "color", sanitizer: sanitizeColor },
    { type: "checkbox", defaultValue: "on" },
    { type: "radio", defaultValue: "on" },
    { type: "file" },
    { type: "submit" },
    { type: "image" },
    { type: "reset" },
    { type: "button" }
  ];

  const selectionStart = 2;
  const selectionEnd = 5;
  const selectionDirection = "backward";

  // Obtain selectionDirection after setting it to "none".
  // Some platforms don't support "none" direction, and "forward" is returned
  // in such platforms.
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#set-the-selection-direction
  function testNoneDirection() {
    const input = document.createElement("input");
    input.selectionDirection = "none";
    return input.selectionDirection;
  }
  const noneDirectionResult = testNoneDirection();

  for (var i = 0; i < types.length; i++) {
    for (var j = 0; j < types.length; j++) {
      const monthOrWeek = types[i].type === 'month' || types[i].type === 'week' || types[j].type === 'month' || types[j].type === 'week';
      if ((types[i] != types[j]) && monthOrWeek) {
        test(function() {
          assert_implements(browserSupportsInputTypeOf(types[i].type), "Support for input type " + types[i].type + " is required for this test.");
          assert_implements(browserSupportsInputTypeOf(types[j].type), "Support for input type " + types[j].type + " is required for this test.");
          var input = document.createElement("input");
          var expected = INITIAL_VALUE;
          input.type = types[i].type;
          if (types[i].type === "file") {
            assert_throws_dom("INVALID_STATE_ERR", function() {
              input.value = expected;
            });
            assert_equals(input.value, "");
          } else if (types[j].type === "file") {
            input.value = expected;
            input.type = types[j].type;  // change state
            assert_equals(input.value, "");
          } else {
            input.value = expected;
            expected = input.value;

            const previouslySelectable = (input.selectionStart !== null);

            if (previouslySelectable) {
              input.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
            }

            input.type = types[j].type;  // change state

            var preSanitizeValue = expected;
            // type[j] sanitization
            if (types[j].sanitizer) {
              expected = types[j].sanitizer(expected);
            }

            // type[j] defaultValue
            if (expected === "" && types[j].defaultValue) {
              expected = types[j].defaultValue;
            }

            assert_equals(input.value, expected, "input.value should be '" + expected + "' after change of state");

            const nowSelectable = (input.selectionStart !== null);

            if (nowSelectable) {
              if (previouslySelectable) {
                // Value might change after sanitization. The following checks are only valid when the value stays the same.
                if (preSanitizeValue === expected) {
                  assert_equals(input.selectionStart, selectionStart, "selectionStart should be unchanged");
                  assert_equals(input.selectionEnd, selectionEnd, "selectionEnd should be unchanged");
                  assert_equals(input.selectionDirection, selectionDirection, "selectionDirection should be unchanged");
                }
              } else {
                assert_equals(input.selectionStart, 0, "selectionStart should be 0");
                assert_equals(input.selectionEnd, 0, "selectionEnd should be 0");
                assert_equals(input.selectionDirection, noneDirectionResult,
                              `selectionDirection should be '{noneDirectionResult}'`);
              }
            }
          }
        }, "change state from " + types[i].type + " to " + types[j].type);
      }
    }
  }
</script>
