summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/windows/uia/browser_simplePatterns.js
blob: f464db0e1394ff66dc77ba99ea6f642421e442a2 (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* import-globals-from ../../../mochitest/role.js */
/* import-globals-from ../../../mochitest/states.js */
loadScripts(
  { name: "role.js", dir: MOCHITESTS_DIR },
  { name: "states.js", dir: MOCHITESTS_DIR }
);

/* eslint-disable camelcase */
const ExpandCollapseState_Collapsed = 0;
const ExpandCollapseState_Expanded = 1;
const ToggleState_Off = 0;
const ToggleState_On = 1;
const ToggleState_Indeterminate = 2;
/* eslint-enable camelcase */

/**
 * Test the Invoke pattern.
 */
addUiaTask(
  `
<button id="button">button</button>
<p id="p">p</p>
<input id="checkbox" type="checkbox">
  `,
  async function testInvoke() {
    await definePyVar("doc", `getDocUia()`);
    await assignPyVarToUiaWithId("button");
    await definePyVar("pattern", `getUiaPattern(button, "Invoke")`);
    ok(await runPython(`bool(pattern)`), "button has Invoke pattern");
    info("Calling Invoke on button");
    // The button will get focus when it is clicked.
    let focused = waitForEvent(EVENT_FOCUS, "button");
    // The UIA -> IA2 proxy doesn't fire the Invoked event.
    if (gIsUiaEnabled) {
      await setUpWaitForUiaEvent("Invoke_Invoked", "button");
    }
    await runPython(`pattern.Invoke()`);
    await focused;
    ok(true, "button got focus");
    if (gIsUiaEnabled) {
      await waitForUiaEvent();
      ok(true, "button got Invoked event");
    }

    await testPatternAbsent("p", "Invoke");
    // The Microsoft IA2 -> UIA proxy doesn't follow Microsoft's own rules.
    if (gIsUiaEnabled) {
      // Check boxes expose the Toggle pattern, so they should not expose the
      // Invoke pattern.
      await testPatternAbsent("checkbox", "Invoke");
    }
  }
);

/**
 * Test the Toggle pattern.
 */
addUiaTask(
  `
<input id="checkbox" type="checkbox" checked>
<button id="toggleButton" aria-pressed="false">toggle</button>
<button id="button">button</button>
<p id="p">p</p>

<script>
  // When checkbox is clicked and it is not checked, make it indeterminate.
  document.getElementById("checkbox").addEventListener("click", evt => {
    // Within the event listener, .checked is reversed and you can't set
    // .indeterminate. Work around this by deferring and handling the changes
    // ourselves.
    evt.preventDefault();
    const target = evt.target;
    setTimeout(() => {
      if (target.checked) {
        target.checked = false;
      } else {
        target.indeterminate = true;
      }
    }, 0);
  });

  // When toggleButton is clicked, set aria-pressed to true.
  document.getElementById("toggleButton").addEventListener("click", evt => {
    evt.target.ariaPressed = "true";
  });
</script>
  `,
  async function testToggle() {
    await definePyVar("doc", `getDocUia()`);
    await assignPyVarToUiaWithId("checkbox");
    await definePyVar("pattern", `getUiaPattern(checkbox, "Toggle")`);
    ok(await runPython(`bool(pattern)`), "checkbox has Toggle pattern");
    is(
      await runPython(`pattern.CurrentToggleState`),
      ToggleState_On,
      "checkbox has ToggleState_On"
    );
    // The IA2 -> UIA proxy doesn't fire ToggleState prop change events.
    if (gIsUiaEnabled) {
      info("Calling Toggle on checkbox");
      await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
      await runPython(`pattern.Toggle()`);
      await waitForUiaEvent();
      ok(true, "Got ToggleState prop change event on checkbox");
      is(
        await runPython(`pattern.CurrentToggleState`),
        ToggleState_Off,
        "checkbox has ToggleState_Off"
      );
      info("Calling Toggle on checkbox");
      await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
      await runPython(`pattern.Toggle()`);
      await waitForUiaEvent();
      ok(true, "Got ToggleState prop change event on checkbox");
      is(
        await runPython(`pattern.CurrentToggleState`),
        ToggleState_Indeterminate,
        "checkbox has ToggleState_Indeterminate"
      );
    }

    await assignPyVarToUiaWithId("toggleButton");
    await definePyVar("pattern", `getUiaPattern(toggleButton, "Toggle")`);
    ok(await runPython(`bool(pattern)`), "toggleButton has Toggle pattern");
    is(
      await runPython(`pattern.CurrentToggleState`),
      ToggleState_Off,
      "toggleButton has ToggleState_Off"
    );
    if (gIsUiaEnabled) {
      info("Calling Toggle on toggleButton");
      await setUpWaitForUiaPropEvent("ToggleToggleState", "toggleButton");
      await runPython(`pattern.Toggle()`);
      await waitForUiaEvent();
      ok(true, "Got ToggleState prop change event on toggleButton");
      is(
        await runPython(`pattern.CurrentToggleState`),
        ToggleState_On,
        "toggleButton has ToggleState_Off"
      );
    }

    await testPatternAbsent("button", "Toggle");
    await testPatternAbsent("p", "Toggle");
  }
);

/**
 * Test the ExpandCollapse pattern.
 */
addUiaTask(
  `
<details>
  <summary id="summary">summary</summary>
  details
</details>
<button id="popup" aria-haspopup="true">popup</button>
<button id="button">button</button>
<script>
  // When popup is clicked, set aria-expanded to true.
  document.getElementById("popup").addEventListener("click", evt => {
    evt.target.ariaExpanded = "true";
  });
</script>
  `,
  async function testExpandCollapse() {
    await definePyVar("doc", `getDocUia()`);
    await assignPyVarToUiaWithId("summary");
    await definePyVar("pattern", `getUiaPattern(summary, "ExpandCollapse")`);
    ok(await runPython(`bool(pattern)`), "summary has ExpandCollapse pattern");
    is(
      await runPython(`pattern.CurrentExpandCollapseState`),
      ExpandCollapseState_Collapsed,
      "summary has ExpandCollapseState_Collapsed"
    );
    // The IA2 -> UIA proxy doesn't fire ToggleState prop change events, nor
    // does it fail when Expand/Collapse is called on a control which is
    // already in the desired state.
    if (gIsUiaEnabled) {
      info("Calling Expand on summary");
      await setUpWaitForUiaPropEvent(
        "ExpandCollapseExpandCollapseState",
        "summary"
      );
      await runPython(`pattern.Expand()`);
      await waitForUiaEvent();
      ok(
        true,
        "Got ExpandCollapseExpandCollapseState prop change event on summary"
      );
      is(
        await runPython(`pattern.CurrentExpandCollapseState`),
        ExpandCollapseState_Expanded,
        "summary has ExpandCollapseState_Expanded"
      );
      info("Calling Expand on summary");
      await testPythonRaises(`pattern.Expand()`, "Expand on summary failed");
      info("Calling Collapse on summary");
      await setUpWaitForUiaPropEvent(
        "ExpandCollapseExpandCollapseState",
        "summary"
      );
      await runPython(`pattern.Collapse()`);
      await waitForUiaEvent();
      ok(
        true,
        "Got ExpandCollapseExpandCollapseState prop change event on summary"
      );
      is(
        await runPython(`pattern.CurrentExpandCollapseState`),
        ExpandCollapseState_Collapsed,
        "summary has ExpandCollapseState_Collapsed"
      );
      info("Calling Collapse on summary");
      await testPythonRaises(
        `pattern.Collapse()`,
        "Collapse on summary failed"
      );
    }

    await assignPyVarToUiaWithId("popup");
    // Initially, popup has aria-haspopup but not aria-expanded. That should
    // be exposed as collapsed.
    await definePyVar("pattern", `getUiaPattern(popup, "ExpandCollapse")`);
    ok(await runPython(`bool(pattern)`), "popup has ExpandCollapse pattern");
    // The IA2 -> UIA proxy doesn't expose ExpandCollapseState_Collapsed for
    // aria-haspopup without aria-expanded.
    if (gIsUiaEnabled) {
      is(
        await runPython(`pattern.CurrentExpandCollapseState`),
        ExpandCollapseState_Collapsed,
        "popup has ExpandCollapseState_Collapsed"
      );
      info("Calling Expand on popup");
      await setUpWaitForUiaPropEvent(
        "ExpandCollapseExpandCollapseState",
        "popup"
      );
      await runPython(`pattern.Expand()`);
      await waitForUiaEvent();
      ok(
        true,
        "Got ExpandCollapseExpandCollapseState prop change event on popup"
      );
      is(
        await runPython(`pattern.CurrentExpandCollapseState`),
        ExpandCollapseState_Expanded,
        "popup has ExpandCollapseState_Expanded"
      );
    }

    await testPatternAbsent("button", "ExpandCollapse");
  }
);

/**
 * Test the ScrollItem pattern.
 */
addUiaTask(
  `
<hr style="height: 100vh;">
<button id="button">button</button>
  `,
  async function testScrollItem(browser, docAcc) {
    await definePyVar("doc", `getDocUia()`);
    await assignPyVarToUiaWithId("button");
    await definePyVar("pattern", `getUiaPattern(button, "ScrollItem")`);
    ok(await runPython(`bool(pattern)`), "button has ScrollItem pattern");
    const button = findAccessibleChildByID(docAcc, "button");
    testStates(button, STATE_OFFSCREEN);
    info("Calling ScrollIntoView on button");
    // UIA doesn't have an event for this.
    let scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc);
    await runPython(`pattern.ScrollIntoView()`);
    await scrolled;
    ok(true, "Document scrolled");
    testStates(button, 0, 0, STATE_OFFSCREEN);
  }
);

/**
 * Test the Value pattern.
 */
addUiaTask(
  `
<input id="text" value="before">
<input id="textRo" readonly value="textRo">
<input id="textDis" disabled value="textDis">
<select id="select"><option selected>a</option><option>b</option></select>
<progress id="progress" value="0.5"></progress>
<input id="range" type="range" aria-valuetext="02:00:00">
<a id="link" href="https://example.com/">Link</a>
<div id="ariaTextbox" contenteditable role="textbox">before</div>
<button id="button">button</button>
  `,
  async function testValue() {
    await definePyVar("doc", `getDocUia()`);
    await assignPyVarToUiaWithId("text");
    await definePyVar("pattern", `getUiaPattern(text, "Value")`);
    ok(await runPython(`bool(pattern)`), "text has Value pattern");
    ok(
      !(await runPython(`pattern.CurrentIsReadOnly`)),
      "text has IsReadOnly false"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "before",
      "text has correct Value"
    );
    info("SetValue on text");
    await setUpWaitForUiaPropEvent("ValueValue", "text");
    await runPython(`pattern.SetValue("after")`);
    await waitForUiaEvent();
    is(
      await runPython(`pattern.CurrentValue`),
      "after",
      "text has correct Value"
    );

    await assignPyVarToUiaWithId("textRo");
    await definePyVar("pattern", `getUiaPattern(textRo, "Value")`);
    ok(await runPython(`bool(pattern)`), "textRo has Value pattern");
    ok(
      await runPython(`pattern.CurrentIsReadOnly`),
      "textRo has IsReadOnly true"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "textRo",
      "textRo has correct Value"
    );
    info("SetValue on textRo");
    await testPythonRaises(
      `pattern.SetValue("after")`,
      "SetValue on textRo failed"
    );

    await assignPyVarToUiaWithId("textDis");
    await definePyVar("pattern", `getUiaPattern(textDis, "Value")`);
    ok(await runPython(`bool(pattern)`), "textDis has Value pattern");
    ok(
      !(await runPython(`pattern.CurrentIsReadOnly`)),
      "textDis has IsReadOnly false"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "textDis",
      "textDis has correct Value"
    );
    // The IA2 -> UIA proxy doesn't fail SetValue for a disabled element.
    if (gIsUiaEnabled) {
      info("SetValue on textDis");
      await testPythonRaises(
        `pattern.SetValue("after")`,
        "SetValue on textDis failed"
      );
    }

    await assignPyVarToUiaWithId("select");
    await definePyVar("pattern", `getUiaPattern(select, "Value")`);
    ok(await runPython(`bool(pattern)`), "select has Value pattern");
    ok(
      !(await runPython(`pattern.CurrentIsReadOnly`)),
      "select has IsReadOnly false"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "a",
      "select has correct Value"
    );
    info("SetValue on select");
    await testPythonRaises(
      `pattern.SetValue("b")`,
      "SetValue on select failed"
    );

    await assignPyVarToUiaWithId("progress");
    await definePyVar("pattern", `getUiaPattern(progress, "Value")`);
    ok(await runPython(`bool(pattern)`), "progress has Value pattern");
    // Gecko a11y doesn't treat progress bars as read only, but it probably
    // should.
    todo(
      await runPython(`pattern.CurrentIsReadOnly`),
      "progress has IsReadOnly true"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "50%",
      "progress has correct Value"
    );
    info("SetValue on progress");
    await testPythonRaises(
      `pattern.SetValue("60%")`,
      "SetValue on progress failed"
    );

    await assignPyVarToUiaWithId("range");
    await definePyVar("pattern", `getUiaPattern(range, "Value")`);
    ok(await runPython(`bool(pattern)`), "range has Value pattern");
    is(
      await runPython(`pattern.CurrentValue`),
      "02:00:00",
      "range has correct Value"
    );

    await assignPyVarToUiaWithId("link");
    await definePyVar("pattern", `getUiaPattern(link, "Value")`);
    ok(await runPython(`bool(pattern)`), "link has Value pattern");
    is(
      await runPython(`pattern.CurrentValue`),
      "https://example.com/",
      "link has correct Value"
    );

    await assignPyVarToUiaWithId("ariaTextbox");
    await definePyVar("pattern", `getUiaPattern(ariaTextbox, "Value")`);
    ok(await runPython(`bool(pattern)`), "ariaTextbox has Value pattern");
    ok(
      !(await runPython(`pattern.CurrentIsReadOnly`)),
      "ariaTextbox has IsReadOnly false"
    );
    is(
      await runPython(`pattern.CurrentValue`),
      "before",
      "ariaTextbox has correct Value"
    );
    info("SetValue on ariaTextbox");
    await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
    await runPython(`pattern.SetValue("after")`);
    await waitForUiaEvent();
    is(
      await runPython(`pattern.CurrentValue`),
      "after",
      "ariaTextbox has correct Value"
    );

    await testPatternAbsent("button", "Value");
  }
);