testaro 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +502 -0
  3. package/aceconfig.js +7 -0
  4. package/commands.js +249 -0
  5. package/index.js +1248 -0
  6. package/package.json +39 -0
  7. package/procs/score/asp09.js +555 -0
  8. package/procs/test/allText.js +76 -0
  9. package/procs/test/allVis.js +17 -0
  10. package/procs/test/linksByType.js +90 -0
  11. package/procs/test/textOf.txt +73 -0
  12. package/scoring/correlation.js +74 -0
  13. package/scoring/correlations.json +327 -0
  14. package/scoring/data.json +26021 -0
  15. package/scoring/dupCounts.js +39 -0
  16. package/scoring/dupCounts.json +112 -0
  17. package/scoring/duplications.json +253 -0
  18. package/scoring/issues.json +304 -0
  19. package/scoring/packageData.js +171 -0
  20. package/scoring/packageIssues.js +34 -0
  21. package/scoring/rulesetData.json +15 -0
  22. package/tests/aatt.js +64 -0
  23. package/tests/alfa.js +107 -0
  24. package/tests/axe.js +109 -0
  25. package/tests/bulk.js +21 -0
  26. package/tests/embAc.js +36 -0
  27. package/tests/focAll.js +62 -0
  28. package/tests/focInd.js +99 -0
  29. package/tests/focOp.js +132 -0
  30. package/tests/hover.js +195 -0
  31. package/tests/ibm.js +89 -0
  32. package/tests/labClash.js +157 -0
  33. package/tests/linkUl.js +65 -0
  34. package/tests/menuNav.js +254 -0
  35. package/tests/motion.js +115 -0
  36. package/tests/radioSet.js +87 -0
  37. package/tests/role.js +164 -0
  38. package/tests/styleDiff.js +146 -0
  39. package/tests/tabNav.js +282 -0
  40. package/tests/wave.js +44 -0
  41. package/tests/zIndex.js +49 -0
  42. package/validation/batches/sample.json +13 -0
  43. package/validation/executors/sample.js +11 -0
  44. package/validation/scripts/app/sample.json +21 -0
  45. package/validation/scripts/test/bulk.json +39 -0
  46. package/validation/scripts/test/embAc.json +45 -0
  47. package/validation/scripts/test/focAll.json +59 -0
  48. package/validation/scripts/test/focInd.json +55 -0
  49. package/validation/scripts/test/focOp.json +53 -0
  50. package/validation/scripts/test/hover.json +47 -0
  51. package/validation/scripts/test/labClash.json +43 -0
  52. package/validation/scripts/test/linkUl.json +62 -0
  53. package/validation/scripts/test/menuNav.json +97 -0
  54. package/validation/scripts/test/motion.json +53 -0
  55. package/validation/scripts/test/radioSet.json +43 -0
  56. package/validation/scripts/test/role.json +42 -0
  57. package/validation/scripts/test/styleDiff.json +61 -0
  58. package/validation/scripts/test/tabNav.json +97 -0
  59. package/validation/scripts/test/zIndex.json +40 -0
  60. package/validation/targets/bulk/bad.html +48 -0
  61. package/validation/targets/bulk/good.html +15 -0
  62. package/validation/targets/embAc/bad.html +21 -0
  63. package/validation/targets/embAc/good.html +15 -0
  64. package/validation/targets/focAll/good.html +15 -0
  65. package/validation/targets/focAll/less.html +15 -0
  66. package/validation/targets/focAll/more.html +16 -0
  67. package/validation/targets/focInd/bad.html +31 -0
  68. package/validation/targets/focInd/good.html +22 -0
  69. package/validation/targets/focOp/bad.html +18 -0
  70. package/validation/targets/focOp/good.html +15 -0
  71. package/validation/targets/hover/bad.html +19 -0
  72. package/validation/targets/hover/good.html +15 -0
  73. package/validation/targets/labClash/bad.html +20 -0
  74. package/validation/targets/labClash/good.html +18 -0
  75. package/validation/targets/linkUl/bad.html +16 -0
  76. package/validation/targets/linkUl/good.html +30 -0
  77. package/validation/targets/linkUl/na.html +20 -0
  78. package/validation/targets/menuNav/bad.html +106 -0
  79. package/validation/targets/menuNav/bad.js +348 -0
  80. package/validation/targets/menuNav/good.html +106 -0
  81. package/validation/targets/menuNav/good.js +365 -0
  82. package/validation/targets/menuNav/style.css +22 -0
  83. package/validation/targets/motion/bad.css +15 -0
  84. package/validation/targets/motion/bad.html +16 -0
  85. package/validation/targets/motion/good.html +15 -0
  86. package/validation/targets/radioSet/bad.html +34 -0
  87. package/validation/targets/radioSet/good.html +27 -0
  88. package/validation/targets/role/bad.html +26 -0
  89. package/validation/targets/role/good.html +22 -0
  90. package/validation/targets/styleDiff/bad.html +35 -0
  91. package/validation/targets/styleDiff/good.html +36 -0
  92. package/validation/targets/tabNav/bad.html +51 -0
  93. package/validation/targets/tabNav/bad.js +35 -0
  94. package/validation/targets/tabNav/good.html +53 -0
  95. package/validation/targets/tabNav/good.js +83 -0
  96. package/validation/targets/tabNav/goodMoz.js +206 -0
  97. package/validation/targets/tabNav/style.css +34 -0
  98. package/validation/targets/zIndex/bad.html +17 -0
  99. package/validation/targets/zIndex/good.html +15 -0
@@ -0,0 +1,348 @@
1
+ // UTILITIES FOR EVENT HANDLERS
2
+
3
+ // Returns the menu controlled by a menu button.
4
+ const controlledMenu = button => document.getElementById(button.getAttribute('aria-controls'));
5
+ // Returns the button controlling a menu.
6
+ const controller = menu => document.body.querySelector(`button[aria-controls=${menu.id}`);
7
+ // Returns whether an element is a menu item.
8
+ const isMenuItem = element => element.getAttribute('role') === 'menuitem';
9
+ // Returns the focus type of a menu.
10
+ const focusTypeOf = menu => menu.hasAttribute('aria-activedescendant') ? 'fake' : 'true';
11
+ // Closes a menu controlled by a button.
12
+ const closeMenu = button => {
13
+ // If the menu is open:
14
+ if (button.ariaExpanded === 'true') {
15
+ // Close it.
16
+ button.setAttribute('aria-expanded', 'false');
17
+ const menu = controlledMenu(button);
18
+ menu.className = 'shut';
19
+ menu.tabIndex = -1;
20
+ }
21
+ };
22
+ // Returns the menu items of a menu.
23
+ const menuItemsOf = menu => {
24
+ const items = [];
25
+ const children = Array.from(menu.children);
26
+ children.forEach(child => {
27
+ if (isMenuItem(child)) {
28
+ items.push(child);
29
+ }
30
+ else {
31
+ const grandchild = child.firstElementChild;
32
+ if (grandchild.getAttribute('role')) {
33
+ items.push(grandchild);
34
+ }
35
+ }
36
+ });
37
+ return items;
38
+ };
39
+ // Returns the menu that a menu item is an item of.
40
+ const owningMenuOf = menuItem => {
41
+ const parent = menuItem.parentElement;
42
+ const parentRole = parent.getAttribute('role');
43
+ if (parentRole.startsWith('menu')){
44
+ return parent;
45
+ }
46
+ else {
47
+ const grandparent = parent.parentElement;
48
+ const grandparentRole = grandparent.getAttribute('role');
49
+ if (grandparentRole.startsWith('menu')) {
50
+ return grandparent;
51
+ }
52
+ else {
53
+ return null;
54
+ }
55
+ }
56
+ };
57
+ // Returns the index of the active menu item of a menu, or -1 if none.
58
+ const activeIndexOf = (isButton, buttonOrMenu) => {
59
+ const menu = isButton ? controlledMenu(buttonOrMenu) : buttonOrMenu;
60
+ const items = menuItemsOf(menu);
61
+ // If the menu is a fake focus manager, return the index.
62
+ if (menu.hasAttribute('aria-activedescendant')) {
63
+ const activeID = menu.getAttribute('aria-activedescendant');
64
+ if (activeID) {
65
+ const itemIDs = items.map(item => item.id);
66
+ return itemIDs.indexOf(activeID);
67
+ }
68
+ else {
69
+ return -1;
70
+ }
71
+ }
72
+ // Otherwise, i.e. if the menu is a true focus manager, return the index.
73
+ else {
74
+ const tabIndexes = items.map(item => item.tabIndex);
75
+ return tabIndexes.indexOf(0);
76
+ }
77
+ };
78
+ // Makes the specified (or the last if -1) menu item active and close any sibling menus.
79
+ const setActive = (focusType, menu, itemIndex) => {
80
+ // Identify the menu items.
81
+ const menuItems = menuItemsOf(menu);
82
+ // Identify the specified index.
83
+ const newIndex = itemIndex === -1 ? menuItems.length - 1 : itemIndex;
84
+ // For each menu item in the menu:
85
+ menuItems.forEach((item, index) => {
86
+ // If it has the specified index:
87
+ if (index === newIndex) {
88
+ // If the menu is a pseudofocus manager:
89
+ if (focusType === 'fake') {
90
+ // Mark the item as focal.
91
+ item.className = 'focal';
92
+ menu.setAttribute('aria-activedescendant', item.id);
93
+ // Ensure that the menu is in focus.
94
+ menu.tabIndex = 0;
95
+ menu.focus();
96
+ }
97
+ // Otherwise, if the menu is a true focus manager:
98
+ else if (focusType === 'true') {
99
+ // Make the item focusable.
100
+ item.tabIndex = 0;
101
+ // Focus it.
102
+ item.focus();
103
+ }
104
+ }
105
+ // Otherwise, i.e. if the menu item does not have the specified index:
106
+ else {
107
+ // If the menu is a pseudofocus manager:
108
+ if (focusType === 'fake') {
109
+ // Mark the item as nonfocal.
110
+ item.className = 'blurred';
111
+ }
112
+ // Otherwise, if the menu is a true focus manager:
113
+ else if (focusType === 'true') {
114
+ // Make the item nonfocusable.
115
+ item.tabIndex = -1;
116
+ }
117
+ // Close the menu controlled by the item if the item is a menu button.
118
+ closeMenu(item);
119
+ }
120
+ });
121
+ };
122
+ // Opens a menu controlled by a button and makes an item active.
123
+ const openMenu = (button, newIndex) => {
124
+ // Open the menu (Chrome fails to support settable ariaExpanded property).
125
+ button.setAttribute('aria-expanded', 'true');
126
+ const menu = controlledMenu(button);
127
+ menu.className = 'open';
128
+ const focusType = focusTypeOf(menu);
129
+ // If an active index was specified:
130
+ if (newIndex > -2) {
131
+ // Make the specified menu item active.
132
+ setActive(focusType, menu, newIndex);
133
+ }
134
+ // Otherwise, i.e. if no active index was specified:
135
+ else {
136
+ // Make the currently active menu item, or if none the first one, active.
137
+ const oldIndex = activeIndexOf(true, button);
138
+ setActive(focusType, menu, oldIndex > -1 ? oldIndex : 0);
139
+ }
140
+ };
141
+ // Navigates within a menu according to a key press and returns the newly active index.
142
+ const keyNav = (isBar, menu, key, focusType) => {
143
+ const oldIndex = activeIndexOf(false, menu);
144
+ const menuItems = menuItemsOf(menu);
145
+ const menuItemCount = menuItems.length;
146
+ // Initialize the chosen index as the current index.
147
+ let newIndex = oldIndex;
148
+ // If the request is for the next menu item:
149
+ if (['ArrowRight', 'ArrowDown'].includes(key)) {
150
+ // Change the index to the next menu item’s, without wrapping.
151
+ newIndex = Math.min(oldIndex + 1, menuItemCount - 1);
152
+ }
153
+ // Otherwise, if the request is for the previous menu item:
154
+ else if (['ArrowLeft', 'ArrowUp'].includes(key)) {
155
+ // Change the index to the previous menu item’s, without wrapping.
156
+ newIndex = Math.max(0, oldIndex - 1);
157
+ }
158
+ // Otherwise, if the request is for a menu item with an initial letter:
159
+ else if (/^[a-zA-Z]$/.test(key)) {
160
+ // Change the index to the next matching menu item’s, if there is one.
161
+ const matches = menuItems.map((item, index) =>
162
+ item.textContent.toLowerCase().trim().startsWith(key.toLowerCase()) ? index : -1
163
+ );
164
+ const laterMatches = matches.filter(index => index > -1 && index > oldIndex);
165
+ if (laterMatches.length) {
166
+ newIndex = laterMatches[0];
167
+ }
168
+ }
169
+ // Make the menu item with the new index active.
170
+ if (newIndex > -1) {
171
+ setActive(focusType, menu, newIndex);
172
+ }
173
+ // Return the new index.
174
+ return newIndex;
175
+ };
176
+
177
+ // EVENT LISTENERS
178
+
179
+ // Handle clicks.
180
+ document.body.addEventListener('click', event => {
181
+ // Identify the click target.
182
+ let target = event.target;
183
+ // If it is a menu item:
184
+ if (isMenuItem(target)) {
185
+ // Identify the menu that owns it.
186
+ const menu = owningMenuOf(target);
187
+ // If the owning menu exists:
188
+ if (menu) {
189
+ // Make the menu item active.
190
+ const itemIndex = menuItemsOf(menu).indexOf(target);
191
+ setActive(focusTypeOf(menu), menu, itemIndex);
192
+ // If the menu item is also a menu button:
193
+ if (target.tagName === 'BUTTON' && ['menu', 'true'].includes(target.ariaHasPopup)) {
194
+ // If the menu it controls is closed:
195
+ if (target.ariaExpanded === 'false') {
196
+ // Open it with the first item active.
197
+ openMenu(target, 0);
198
+ }
199
+ // Otherwise, if the menu it controls is open:
200
+ else if (target.ariaExpanded === 'true') {
201
+ // Close it.
202
+ closeMenu(target);
203
+ }
204
+ }
205
+ // Otherwise, if the menu item is also a link:
206
+ else if (target.tagName === 'A') {
207
+ // Identify the button controlling the menu, if any.
208
+ const ownerButton = controller(menu);
209
+ // If the menu has a controlling button:
210
+ if (ownerButton) {
211
+ // If the button is also a menu item:
212
+ if (isMenuItem(ownerButton)) {
213
+ // Identify the menu that it is an item of.
214
+ const ownerMenu = ownerMenu(ownerButton);
215
+ // If it exists:
216
+ if (ownerMenu) {
217
+ // Identify the menu button’s index as a menu item.
218
+ const ownerIndex = menuItemsOf(ownerMenu).indexOf(ownerButton);
219
+ // Make it active.
220
+ setActive(focusTypeOf(ownerMenu), ownerMenu, ownerIndex);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ });
228
+ // Handle key presses.
229
+ window.addEventListener('keydown', event => {
230
+ const key = event.key;
231
+ // If no ineligible modifier key was in effect when the key was depressed:
232
+ if (! (event.altKey || event.ctrlKey || event.metaKey)) {
233
+ // Identify whether the shift key was in effect.
234
+ const shift = event.shiftKey;
235
+ // Initialize the focused element.
236
+ let focus = document.activeElement;
237
+ // If it exists and is within the body:
238
+ if (focus && focus !== document.body) {
239
+ // Change it to the effectively focused element if different.
240
+ if (focus.hasAttribute('aria-activedescendant')) {
241
+ focus = document.getElementById(focus.getAttribute('aria-activedescendant'));
242
+ }
243
+ // If it is a menu item:
244
+ if (focus.getAttribute('role') === 'menuitem') {
245
+ // Identify the menu that the item is in.
246
+ const menu = owningMenuOf(focus);
247
+ // If it exists:
248
+ if (menu) {
249
+ // Identify the role of the menu.
250
+ const menuRole = menu.getAttribute('role');
251
+ // Identify whether the menu manages pseudofocus or true focus.
252
+ const focusType = focusTypeOf(menu);
253
+ // Identify the menu button controlling the menu, if any.
254
+ const ownerButton = controller(menu);
255
+ // Initialize the type of the focused element as inoperable.
256
+ let itemType = 'plain';
257
+ // Revise the type if it is operable.
258
+ const tagName = focus.tagName;
259
+ if (tagName === 'A') {
260
+ itemType = 'link';
261
+ }
262
+ else if (tagName === 'BUTTON') {
263
+ itemType = focus.ariaExpanded ? 'menuButton' : 'button';
264
+ }
265
+ // If the key is Enter:
266
+ if (key === 'Enter' && ! shift) {
267
+ // If the menu item is a link or ordinary button:
268
+ if (['link', 'button'].includes(itemType)) {
269
+ // Simulate a click on it.
270
+ focus.click();
271
+ }
272
+ // Otherwise, if the menu item is a menu button:
273
+ else if (itemType === 'menuButton') {
274
+ // Its menu must be closed, so open it, with the first item active.
275
+ openMenu(focus, 0);
276
+ }
277
+ }
278
+ // Otherwise, if the key is Space:
279
+ else if (key === ' ' && ! shift) {
280
+ // If the menu item is an ordinary button:
281
+ if (itemType === 'button') {
282
+ // Prevent default scrolling.
283
+ event.preventDefault();
284
+ // Simulate a click on the button.
285
+ focus.click();
286
+ }
287
+ // Otherwise, if the menu item is a menu button:
288
+ else if (itemType === 'menuButton') {
289
+ // Prevent default scrolling.
290
+ event.preventDefault();
291
+ // Its menu must be closed, so open it, with the first item active.
292
+ openMenu(focus, 0);
293
+ }
294
+ }
295
+ // Otherwise, if the key is Tab and the menu is closable:
296
+ else if (key === 'Tab' && ownerButton) {
297
+ // Close the menu.
298
+ closeMenu(ownerButton);
299
+ }
300
+ // Otherwise, if the key is Escape and the menu is closable:
301
+ else if (key === 'Escape' && ! shift && ownerButton) {
302
+ // Close the menu.
303
+ closeMenu(ownerButton);
304
+ // Focus its menu button.
305
+ ownerButton.focus();
306
+ }
307
+ // Otherwise, if the key is a letter:
308
+ else if (/^[a-zA-Z]$/.test(key)) {
309
+ // Navigate within the menu according to the key.
310
+ keyNav(true, menu, key, focusType);
311
+ }
312
+ // Otherwise, if the key navigates among parent menu items in a menu bar:
313
+ else if (['ArrowLeft', 'ArrowRight'].includes(key)) {
314
+ // If the focused element is a menu button in a menu bar:
315
+ if (itemType === 'menuButton' && menuRole === 'menubar') {
316
+ // Navigate within the menu bar according to the key.
317
+ keyNav(true, menu, key, focusType);
318
+ }
319
+ }
320
+ // Otherwise, if the key specially navigates within the menu:
321
+ else if (['Home', 'End'].includes(key) && ! shift) {
322
+ // Prevent default page scrolling.
323
+ event.preventDefault();
324
+ // Navigate within the menu according to the key.
325
+ keyNav(true, menu, key, focusType);
326
+ }
327
+ // Otherwise, if the key conditionally navigates or opens a menu:
328
+ else if (['ArrowUp', 'ArrowDown'].includes(key) && ! shift) {
329
+ // If the focused element is a menu button in a menu bar:
330
+ if (menuRole === 'menubar' && itemType === 'menuButton') {
331
+ // Prevent default scrolling.
332
+ event.preventDefault();
333
+ // Open the menu button’s menu.
334
+ openMenu(focus, key === 'ArrowUp' ? -1 : 0);
335
+ }
336
+ // Otherwise, if the active item is not a menu button and is in a menu:
337
+ else if (menuRole === 'menu' && itemType !== 'menuButton') {
338
+ // Prevent default scrolling.
339
+ event.preventDefault();
340
+ // Obey the key.
341
+ keyNav(false, menu, key, focusType);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+ });
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Page with standard menu navigation</title>
6
+ <meta name="description" content="tester">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="good.js" defer></script>
10
+ </head>
11
+ <body>
12
+ <main>
13
+ <h1>Page with standard menu navigation</h1>
14
+ <p>This page contains a menu bar. In it, the personalities submenu implements pseudofocus management and the technologies menu implements true focus management. <a href="https://www.w3.org/TR/wai-aria-practices-1.1/#menu">These two patterns</a> are recommended by the Accessible Rich Internet Applications Working Group.</p>
15
+ <p id="menuBarAbout">Choose a domain to toggle that domain&rsquo;s submenu open or closed. The submenu contains links to definitions (in a new tab).</p>
16
+ <ul id="menubar" role="menubar" class="menubar blockLink" aria-labelledby="menuBarAbout">
17
+ <li role="none">
18
+ <button
19
+ id="persButton"
20
+ role="menuitem"
21
+ type="button"
22
+ tabindex="0"
23
+ aria-haspopup="menu"
24
+ aria-expanded="false"
25
+ aria-controls="persMenu"
26
+ >
27
+ Personalities
28
+ </button>
29
+ <ul
30
+ id="persMenu"
31
+ role="menu"
32
+ aria-labelledby="persButton"
33
+ aria-activedescendant="introvert"
34
+ class="shut"
35
+ tabindex="-1"
36
+ >
37
+ <li role="none">
38
+ <a
39
+ id="introvert"
40
+ role="menuitem"
41
+ class="blurred"
42
+ tabindex="-1"
43
+ href="https://en.wiktionary.org/wiki/introvert"
44
+ target="_blank"
45
+ >
46
+ introvert
47
+ </a>
48
+ </li>
49
+ <li role="none">
50
+ <a
51
+ id="extravert"
52
+ role="menuitem"
53
+ class="blurred"
54
+ tabindex="-1"
55
+ href="https://en.wiktionary.org/wiki/extravert"
56
+ target="_blank"
57
+ >
58
+ extravert
59
+ </a>
60
+ </li>
61
+ </ul>
62
+ </li>
63
+ <li role="none">
64
+ <button
65
+ id="techButton"
66
+ role="menuitem"
67
+ type="button"
68
+ tabindex="-1"
69
+ aria-haspopup="menu"
70
+ aria-expanded="false"
71
+ aria-controls="techMenu"
72
+ >
73
+ Technologies
74
+ </button>
75
+ <ul
76
+ id="techMenu"
77
+ role="menu"
78
+ aria-labelledby="techButton"
79
+ class="shut"
80
+ >
81
+ <li role="none">
82
+ <a
83
+ tabindex="-1"
84
+ role="menuitem"
85
+ href="https://en.wikipedia.org/wiki/Prestressed_concrete"
86
+ target="_blank"
87
+ >
88
+ prestressed concrete
89
+ </a>
90
+ </li>
91
+ <li role="none">
92
+ <a
93
+ tabindex="-1"
94
+ role="menuitem"
95
+ href="https://en.wikipedia.org/wiki/Heat_pump"
96
+ target="_blank"
97
+ >
98
+ heat pump
99
+ </a>
100
+ </li>
101
+ </ul>
102
+ </li>
103
+ </ul>
104
+ </main>
105
+ </body>
106
+ </html>