summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/semantics/forms/textfieldselection/select-event.html
blob: d1b46a22d8621586be46ba6a535ab9467b1aed2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
<!DOCTYPE html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<title>text field selection: select()</title>
<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
<link rel=help href="https://html.spec.whatwg.org/multipage/#textFieldSelection">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>

<textarea>foobar</textarea>
<input type="text" value="foobar">
<input type="search" value="foobar">
<input type="tel" value="1234">
<input type="url" value="https://example.com/">
<input type="password" value="hunter2">

<script>
"use strict";

const els = [document.querySelector("textarea"), ...document.querySelectorAll("input")];

const actions = [
  {
    label: "select()",
    action: el => el.select()
  },
  {
    label: "selectionStart",
    action: el => el.selectionStart = 1
  },
  {
    label: "selectionEnd",
    action: el => el.selectionEnd = el.value.length - 1
  },
  {
    label: "selectionDirection",
    action: el => el.selectionDirection = "backward"
  },
  {
    label: "setSelectionRange()",
    action: el => el.setSelectionRange(1, el.value.length - 1) // changes direction implicitly to none/forward
  },
  {
    label: "setRangeText()",
    action: el => el.setRangeText("newmiddle", el.selectionStart, el.selectionEnd, "select")
  },
  {
    label: "selectionStart out of range",
    action: el => el.selectionStart = 1000
  },
  {
    label: "selectionEnd out of range",
    action: el => el.selectionEnd = 1000
  },
  {
    label: "setSelectionRange out of range",
    action: el => el.setSelectionRange(1000, 2000)
  }
];

function waitForEvents() {
  // Engines differ in when these events are sent (see:
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1785615) so wait for both a
  // frame to be rendered, and a timeout.
  return new Promise(resolve => {
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        setTimeout(() => {
          resolve();
        });
      });
    });
  });
}

function initialize(el) {
  el.setRangeText("foobar", 0, el.value.length, "start");
  // Make sure to flush async dispatches
  return waitForEvents();
}

els.forEach((el) => {
  const elLabel = el.localName === "textarea" ? "textarea" : "input type " + el.type;

  actions.forEach((action) => {
    // promise_test instead of async_test is important because these need to happen in sequence (to test that events
    // fire if and only if the selection changes).
    promise_test(async t => {
      await initialize(el);

      const watcher = new EventWatcher(t, el, "select");

      const promise = watcher.wait_for("select").then(e => {
        assert_true(e.isTrusted, "isTrusted must be true");
        assert_true(e.bubbles, "bubbles must be true");
        assert_false(e.cancelable, "cancelable must be false");
      });

      action.action(el);

      return promise;
    }, `${elLabel}: ${action.label}`);

    promise_test(async t => {
      el.onselect = t.unreached_func("the select event must not fire the second time");

      action.action(el);

      await waitForEvents();
      el.onselect = null;
    }, `${elLabel}: ${action.label} a second time (must not fire select)`);

    promise_test(async t => {
      const element = el.cloneNode(true);
      let fired = false;
      element.addEventListener('select', () => fired = true, { once: true });

      action.action(element);

      await waitForEvents();
      assert_true(fired, "event didn't fire");

    }, `${elLabel}: ${action.label} disconnected node`);

    // Intentionally still using promise_test, as assert_unreachable does not
    // make the test fail inside a listener while t.unreached_func() does.
    promise_test(async t => {
      const element = el.cloneNode(true);
      let fired = false;
      element.addEventListener('select', () => fired = true, { once: true });

      action.action(element);

      assert_false(fired, "the select event must not fire synchronously");
      await waitForEvents();
      assert_true(fired, "event didn't fire");
    }, `${elLabel}: ${action.label} event queue`);

    promise_test(async t => {
      const element = el.cloneNode(true);
      let selectCount = 0;
      element.addEventListener('select', () => ++selectCount);
      assert_equals(element.selectionEnd, 0);

      action.action(element);
      action.action(element);

      await waitForEvents();
      assert_equals(selectCount, 1, "the select event must not fire twice");
    }, `${elLabel}: ${action.label} twice in disconnected node (must fire select only once)`);
  });
});
</script>